1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00
Files
uni-halo/src/api/uni-halo.ts
T
小莫唐尼 260d6965e3 refactor: 重构偏好设置与恋爱模块,对齐插件端字段结构
1. 重构布局偏好字段映射,移除嵌套layout结构,直接使用插件端同名字段
2. 新增恋爱模块token管理与密码解锁功能,支持恋爱故事/相册/清单的密码保护
3. 重构首页快捷导航,支持插件端直接配置导航项
4. 清理废弃配置字段,统一站点默认值与插件端结构
5. 修复文章卡片布局读取逻辑,适配新的偏好字段结构
2026-09-10 00:07:33 +08:00

534 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* uni-halo 自定义 API 接口定义
*/
import { http } from '@/http/alova'
import { RequestFrom } from '@/http/tools/enum'
import type { IResponse } from '@/http/types'
import { getCache } from '@/utils/storage'
import { getLoveModuleToken } from '@/utils/loveModuleToken'
import { getNologinEmail, getOpenid } from '@/utils/auth'
import { getPersonalToken } from '@/store/token'
import type {
IAppConfig,
IAuditDataResult,
IBannerPublicDetail,
IBannerPublicItem,
ICommentWidgetConfig,
IDoubanDetail,
IHaloGlobalConfig,
ILoveAlbum,
ILoveAlbumListReq,
ILoveAlbumListRes,
ILoveConfig,
ILoveDailyItem,
ILoveDailyItemListReq,
ILoveDailyItemListRes,
ILoveStory,
ILoveStoryListReq,
ILoveStoryListRes,
IMiniProgramLink,
IMiniProgramLinkGroupedRes,
IMiniProgramLinkGroupOption,
IMiniProgramLinkSubmissionForm,
INoticeDetail,
INoticeListRes,
INoticeListVo,
IQRCodeInfo,
IRestrictReadCheckReq,
IRestrictReadCheckRes,
ISubmitLinkForm,
IUpdateCheckRes,
IVoteDetail,
IVoteListReq,
IVoteListRes,
IVoteSubmitReq,
RestrictReadType,
} from './types/uni-halo'
/** 评论验证码 cookie key */
const COMMENT_WIDGET_CAPTCHA_COOKIES = 'comment-widget-captcha'
/* ==================== plugin-uni-halo 配置 ==================== */
/**
* 获取应用配置
* 注意:cacheFor: 0 绕过 alova 内存缓存——维护模式/配置变更后需即时生效
* (维护页刷新、入口/首页拦截均依赖本接口的最新状态)
*/
export function getAppConfigs() {
return http.Get<IResponse<IAppConfig>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/getConfigs', {
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取审核模式数据(公开接口;auditModeEnabled=true 时返回选中引用列表,否则 {enabled:false})
*/
export function getAuditData() {
return http.Get<IResponse<IAuditDataResult>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/audit-data', {
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取首页轮播图列表(公开;按 priority 有序,脱敏不含 content)
*/
export function getBanners() {
return http.Get<IResponse<IBannerPublicItem[]>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/banners', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取轮播图详情(公开;含 content 富文本 HTML)
*/
export function getBannerDetail(name: string) {
return http.Get<IResponse<IBannerPublicDetail>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/banners/${name}`, {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取 Halo 全局配置信息
*/
export function getHaloGlobalInfo() {
return http.Get<IResponse<IHaloGlobalConfig>>('/actuator/globalinfo', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取评论组件配置
*/
export function getCommentWidgetConfig() {
return http.Get<IResponse<ICommentWidgetConfig>>('/apis/api.commentwidget.halo.run/v1alpha1/config', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 检查更新(uhalo-upgrade 插件对接,插件本体引入后业务侧入口)
*/
export function checkVersion(baseUrl: string) {
return http.Get<IResponse<IUpdateCheckRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/upgrade/checkVersion', {
params: { baseUrl },
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取二维码信息
*/
export function getQRCodeInfo(key: string) {
return http.Get<IResponse<IQRCodeInfo>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/getQRCodeInfo/${key}`, {
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 防刷验证码(plugin-uni-halo,2026-09-03 客户端接入) ==================== */
/** 防刷验证码(服务端 captcha/generate 返回;受保护写接口 403 时响应体亦附带新验证码) */
export interface IPluginCaptcha {
id: string
imageBase64: string
}
/** 受保护写接口携带的验证码 query(未触发时省略,触发后每次提交都需携带,一次性) */
export interface ICaptchaQuery {
captchaId?: string
captchaCode?: string
}
/**
* 获取防刷验证码(公开;适用 POST /mini-program-links/submissions 与
* /love-albums/{name}/unlock;站点关闭验证码或该功能不在生效范围时无需携带)
*/
export function getPluginCaptcha() {
return http.Get<IResponse<IPluginCaptcha>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/captcha/generate', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/** 仅当携带有效验证码时拼接 query 参数 */
export function buildCaptchaQuery(captcha?: ICaptchaQuery | null): ICaptchaQuery | undefined {
if (!captcha || !captcha.captchaId || !captcha.captchaCode)
return undefined
return { captchaId: captcha.captchaId, captchaCode: captcha.captchaCode }
}
/* ==================== 通知公告(plugin-uni-halo,2026-09-03 客户端接入) ==================== */
/**
* 公告分页列表(公开,仅已发布;脱敏不含 content,内嵌类型信息)。
* 注意:公开接口当前仅支持分页,类型筛选/排序由页面本地聚合处理(公告量小)。
*/
export function getNotices(params: { page?: number, size?: number }) {
return http.Get<IResponse<INoticeListRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/notices', {
params,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 最新一条已发布公告(公开;无数据返回 null,供首页/我的弹窗)
*/
export function getNoticeLatest() {
return http.Get<IResponse<INoticeListVo | null>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/notices/latest', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 公告详情(公开;含 content 富文本 HTML 与内嵌类型信息;不存在/删除中返回 404)
*/
export function getNoticeDetail(name: string) {
return http.Get<IResponse<INoticeDetail>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/notices/${name}`, {
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 恋爱模块 ==================== */
/**
* 获取恋爱配置(纪念日 + 恋人信息)
*/
export function getLoveConfig() {
return http.Get<IResponse<ILoveConfig>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-config', {
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取恋爱相册列表(lovePhoto 模块设密码时需携带模块解锁 token)
*/
export function getLoveAlbums(params: ILoveAlbumListReq) {
const token = getLoveModuleToken('lovePhoto')
return http.Get<IResponse<ILoveAlbumListRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums', {
params: { ...params, ...(token ? { token } : {}) },
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取恋爱相册详情(lovePhoto 模块设密码时需携带模块解锁 token)
*/
export function getLoveAlbumByName(name: string, params: ILoveAlbumListReq) {
const token = getLoveModuleToken('lovePhoto')
return http.Get<IResponse<ILoveAlbum>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums/${name}`, {
params: { ...params, ...(token ? { token } : {}) },
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 密码解锁相册(lovePhoto 模块设密码时需携带模块解锁 token)
*/
export function unlockAlbum(name: string, password: string, captcha?: ICaptchaQuery | null) {
const token = getLoveModuleToken('lovePhoto')
return http.Post<IResponse<{ token: string, photos?: unknown[] }>>(
`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums/${name}/unlock`,
{
password,
},
{
params: { ...buildCaptchaQuery(captcha), ...(token ? { token } : {}) },
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 恋爱模块入口解锁(模块密码,签发 30 分钟 token;模块:ourStory/lovePhoto/loveDaily
*/
export function unlockLoveModule(module: string, password: string) {
return http.Post<IResponse<{ token: string }>>(
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-modules/unlock',
{
module,
password,
},
{
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 获取恋爱清单列表(分页)(loveDaily 模块设密码时需携带模块解锁 token)
*/
export function getLoveDailyItems(params: ILoveDailyItemListReq) {
const token = getLoveModuleToken('loveDaily')
return http.Get<IResponse<ILoveDailyItemListRes>>(
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-daily-items',
{
params: { ...params, ...(token ? { token } : {}) },
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 获取恋爱故事列表(ourStory 模块设密码时需携带模块解锁 token)
*/
export function getLoveStories(params: ILoveStoryListReq) {
const token = getLoveModuleToken('ourStory')
return http.Get<IResponse<ILoveStoryListRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-stories', {
params: { ...params, ...(token ? { token } : {}) },
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 小程序链接(plugin-uni-halo) ==================== */
/**
* 获取小程序链接分组列表(grouped=true,仅可见,按分组聚合返回)
*/
export function getMiniProgramLinkGroupedList() {
return http.Get<IResponse<IMiniProgramLinkGroupedRes>>(
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/mini-program-links',
{
params: { grouped: true },
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 获取小程序链接分组选项(/types,仅可见链接引用的分组)
*/
export function getMiniProgramLinkTypes() {
return http.Get<IResponse<IMiniProgramLinkGroupOption[]>>(
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/mini-program-links/types',
{
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 获取小程序链接详情(仅可见,不存在返回 404)
*/
export function getMiniProgramLinkDetail(name: string) {
return http.Get<IResponse<IMiniProgramLink>>(
`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/mini-program-links/${name}`,
{
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/**
* 提交小程序链接申请(公开接口,落库为待审核;受 linkConfig.submissionEnabled 开关
* 与防刷验证码 captchaConfig 控制)
*/
export function submitMiniProgramLinkApplication(data: IMiniProgramLinkSubmissionForm, captcha?: ICaptchaQuery | null) {
return http.Post<IResponse<unknown>>(
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/mini-program-links/submissions',
{
spec: data,
},
{
params: buildCaptchaQuery(captcha),
meta: { requestFrom: RequestFrom.Halo },
},
)
}
/* ==================== 受限阅读(tools.muyin.site) ==================== */
/**
* 受限阅读检查(密码/验证码/登录/付费/评论)
*/
export function requestRestrictReadCheck(restrictType: RestrictReadType, code: string, keyId: string) {
const data: IRestrictReadCheckReq = {
code,
templateType: 'post',
restrictType,
keyId,
}
return http.Post<IResponse<IRestrictReadCheckRes>>('/apis/tools.muyin.site/v1alpha1/restrict-read/check', data, {
headers: {
'Authorization': getToolsAuthorization(),
'Wechat-Session-Id': getOpenid(),
'nologin-email': getNologinEmail(),
},
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 创建验证码(受限阅读)
*/
export function createVerificationCode() {
return http.Get<IResponse<string>>('/apis/tools.muyin.site/v1alpha1/restrict-read/create', {
headers: {
'Authorization': getToolsAuthorization(),
'Wechat-Session-Id': getOpenid(),
},
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 友链提交(linkssubmit.muyin.site) ==================== */
/**
* 提交友链
*/
export function submitLink(form: ISubmitLinkForm) {
return http.Post<IResponse<{ msg?: string, message?: string }>>('/apis/linkssubmit.muyin.site/v1alpha1/submit', form, {
headers: {
'Authorization': getLinksSubmitAuthorization(),
'Wechat-Session-Id': getOpenid(),
},
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 投票(api.vote.kunkunyu.com) ==================== */
/**
* 投票列表
*/
export function getVoteList(params: IVoteListReq) {
return http.Get<IResponse<IVoteListRes>>('/apis/api.vote.kunkunyu.com/v1alpha1/votes', {
params,
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 投票详情
*/
export function getVoteDetail(name: string) {
return http.Get<IResponse<IVoteDetail>>(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/detail`, {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 投票用户列表
*/
export function getVoteUserList(name: string) {
return http.Get<IResponse<unknown[]>>(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/user-list`, {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 提交投票
* @param canAnonymously 是否允许匿名;非匿名时带个人 token
*/
export function submitVote(name: string, data: IVoteSubmitReq, canAnonymously = true) {
const headers: Record<string, string> = {}
if (!canAnonymously) {
const token = getPersonalToken()
if (token)
headers.Authorization = `Bearer ${token}`
}
return http.Post<IResponse<unknown>>(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/submit`, data, {
headers,
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 豆瓣(api.douban.moony.la) ==================== */
/**
* 获取豆瓣条目详情
*/
export function getDoubanDetail(url: string) {
return http.Get<IResponse<IDoubanDetail>>('/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail', {
params: { url },
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 评论验证码(api.commentwidget.halo.run) ==================== */
/**
* 获取评论验证码
*/
export function getCommentWidgetCaptcha() {
return http.Get<IResponse<string>>('/apis/api.commentwidget.halo.run/v1alpha1/captcha/-/generate', {
cacheFor: 0,
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 数据看板(api.data.statistics.xhhao.com) ==================== */
/** 图表统计数据 */
export interface IDataStatistics {
tags: { name: string, count: number }[]
categories: { name: string, total: number }[]
articles: { date: string, count: number }[]
comments: { username: string, count: number }[]
top10Articles: { name: string, views: number }[]
[key: string]: unknown
}
/**
* 获取图表统计数据
* @description 标签、分类、文章发布趋势、评论活跃用户、热门文章 top10
*/
export function getChartData() {
return http.Get<IResponse<IDataStatistics>>('/apis/api.data.statistics.xhhao.com/v1alpha1/chart/data', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取 Github 配置信息
*/
export function getGithubConfig() {
return http.Get<IResponse<unknown>>('/apis/api.data.statistics.xhhao.com/v1alpha1/github/config', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/**
* 获取 Uptime Kuma 状态页面数据
*/
export function getUptimeKumaStatus() {
return http.Get<IResponse<unknown>>('/apis/api.data.statistics.xhhao.com/v1alpha1/uptime/status', {
meta: { requestFrom: RequestFrom.Halo },
})
}
/* ==================== 内部辅助 ==================== */
/**
* 工具类插件授权头(源自应用配置 pluginConfig.toolsPlugin.Authorization)
*/
function getToolsAuthorization(): string {
return getAppConfigFromStore().pluginConfig?.toolsPlugin?.Authorization || ''
}
/**
* 友链提交插件授权头(源自应用配置 pluginConfig.linksSubmitPlugin.Authorization)
*/
function getLinksSubmitAuthorization(): string {
return getAppConfigFromStore().pluginConfig?.linksSubmitPlugin?.Authorization || ''
}
/**
* 读取应用配置(store 未就绪时兜底为空)
* 注:待 src/store/appConfig.ts 建立后改为 useAppConfigStore 读取
*/
function getAppConfigFromStore(): IAppConfig {
// TODO: 迁移 store 后改为从 Pinia 读取,避免每次从 storage 解析
const cached = getCache<IAppConfig>('APP_GLOBAL_CONFIGS')
return cached || {}
}
/** 评论验证码 cookie key(供拦截器/页面使用) */
export { COMMENT_WIDGET_CAPTCHA_COOKIES }
export type { ILoveDailyItem, ILoveStory }