diff --git a/src/api/halo.ts b/src/api/halo.ts index 5b2d21d..50d41e3 100644 --- a/src/api/halo.ts +++ b/src/api/halo.ts @@ -24,7 +24,6 @@ import type { IPhotoGroupListRes, IPhotoListReq, IPhotoListRes, - IPluginAvailable, IPost, IPostListReq, IPostListRes, @@ -293,7 +292,7 @@ export function postTrackersCounter(data: ITrackerCounterReq) { * 检查插件是否可用 */ export function checkPluginAvailable(name: string) { - return http.Get>(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, { + return http.Get>(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, { cacheFor: 0, meta: { requestFrom: RequestFrom.Halo }, }) diff --git a/src/api/types/halo.ts b/src/api/types/halo.ts index 711c43e..94f676f 100644 --- a/src/api/types/halo.ts +++ b/src/api/types/halo.ts @@ -456,10 +456,3 @@ export interface IUpvoteReq { name: string plural?: string } - -/* ---------- 插件可用性 ---------- */ - -export interface IPluginAvailable { - available: boolean - reason?: string -} diff --git a/src/components/uh-article-card/uh-article-card.vue b/src/components/uh-article-card/uh-article-card.vue index abfd507..243e0e2 100644 --- a/src/components/uh-article-card/uh-article-card.vue +++ b/src/components/uh-article-card/uh-article-card.vue @@ -1,79 +1,105 @@ + + \ No newline at end of file diff --git a/src/components/uh-data-loading/uh-data-loading.vue b/src/components/uh-data-loading/uh-data-loading.vue index a4baa37..a1e4338 100644 --- a/src/components/uh-data-loading/uh-data-loading.vue +++ b/src/components/uh-data-loading/uh-data-loading.vue @@ -18,7 +18,7 @@ interface IProps { const props = withDefaults(defineProps(), { loadingStatus: 'loading', - minHeight: '75vh', + minHeight: '80vh', loadingText: '稍等,正在加载中哦', errorText: '哎呀,加载失败了呢~', emptyText: '啊偶,暂时没有数据呢~', diff --git a/src/components/uh-home-category/uh-home-category.vue b/src/components/uh-home-category/uh-home-category.vue index 69ad8d1..5499bf4 100644 --- a/src/components/uh-home-category/uh-home-category.vue +++ b/src/components/uh-home-category/uh-home-category.vue @@ -57,7 +57,7 @@ if (calcAuditModeEnabled.value) return uni.navigateTo({ - url: `/pages-blog/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`, + url: `/pages-blog/category-articles/category-articles?name=${category.metadata.name}&title=${category.spec.displayName}`, }) } diff --git a/src/components/uh-plugin-unavailable/uh-plugin-unavailable.vue b/src/components/uh-plugin-unavailable/uh-plugin-unavailable.vue index be913ab..43b8d14 100644 --- a/src/components/uh-plugin-unavailable/uh-plugin-unavailable.vue +++ b/src/components/uh-plugin-unavailable/uh-plugin-unavailable.vue @@ -3,17 +3,11 @@ import { NeedPlugins } from '@/hooks/usePluginAvailable' const props = withDefaults(defineProps<{ - /** 插件名称(与 NeedPlugins 中的 id 对应) */ pluginId : string errorText ?: string - useDecoration ?: boolean - useBorder ?: boolean - customStyle ?: Record + checking : boolean }>(), { errorText: '', - useDecoration: true, - useBorder: true, - customStyle: () => ({}), }) const emit = defineEmits<{ @@ -25,75 +19,40 @@ const info = NeedPlugins.get(props.pluginId) return info || { id: props.pluginId, - name: props.pluginId, + name: props.name, desc: '', - logo: '', url: '', } }) - const defaultStyle = { - width: '80vw', - borderRadius: '24rpx', + function handleRefresh() { + if (props.checking) { return } + emit('on-refresh') } - - const calcCustomStyle = computed(() => ({ - ...defaultStyle, - ...props.customStyle, - })) - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/hooks/useMaintenanceIntercept.ts b/src/hooks/useMaintenanceIntercept.ts index ee4ee44..1e65b84 100644 --- a/src/hooks/useMaintenanceIntercept.ts +++ b/src/hooks/useMaintenanceIntercept.ts @@ -1,27 +1,19 @@ -/** - * 维护拦截 hook(2026-09-04) - * 统一「主插件未激活 / 维护模式开启」两项检查与维护页跳转,供入口页与首页等 - * 页面复用(auto-import 已配置 src/hooks,页面直接调用无需 import)。 - * 拦截规则:任一命中即跳转 /pages/maintenance/maintenance?from=reason, - * 维护页按 reason 展示默认(未配置维护信息)或配置文案。设计见插件 - * .docs/maintenance-config-design.md §8。 - */ -import { useAppConfigStore } from '@/store/appConfig' +import { useAppConfigStore } from '@/store/appConfig'; /** 维护拦截原因:plugin 主插件未激活 / maintenance 维护模式开启 */ -export type MaintenanceInterceptReason = 'plugin' | 'maintenance' +export type MaintenanceInterceptReason = 'plugin' | 'maintenance'; export interface IMaintenanceInterceptResult { - /** 是否命中拦截(需要跳转维护页) */ - intercepted: boolean - /** 命中原因;未命中为 null */ - reason: MaintenanceInterceptReason | null + /** 是否命中拦截(需要跳转维护页) */ + intercepted: boolean; + /** 命中原因;未命中为 null */ + reason: MaintenanceInterceptReason | null; } /** 维护页路径 */ -export const MAINTENANCE_PAGE_PATH = '/pages/maintenance/maintenance' +export const MAINTENANCE_PAGE_PATH = '/pages/maintenance/maintenance'; /** 主插件 ID(与 utils/plugin NeedPluginIds.PluginUniHalo 一致) */ -export const MAINTENANCE_PLUGIN_ID = 'plugin-uni-halo' +export const MAINTENANCE_PLUGIN_ID = 'plugin-uni-halo'; /** * 维护拦截能力:检查 + 跳转封装 @@ -31,43 +23,52 @@ export const MAINTENANCE_PLUGIN_ID = 'plugin-uni-halo' * onLoad(async () => { if (await interceptOrContinue()) return }) */ export function useMaintenanceIntercept() { - const appConfigStore = useAppConfigStore() - /** 主插件可用性 hook(checkIntercept 内 await check 后读取 available) */ - const { available: pluginAvailable, check: checkPluginAvailable } = usePluginAvailable(MAINTENANCE_PLUGIN_ID) + const appConfigStore = useAppConfigStore(); + const reason = ref(null); + /** 主插件可用性 hook(checkIntercept 内 await check 后读取 available) */ + const { available: pluginAvailable, check: checkPluginAvailable } = usePluginAvailable({ + pluginId: MAINTENANCE_PLUGIN_ID + }); - /** - * 检查是否命中拦截(插件可用性 + 维护模式)。 - * @param force 是否强制刷新配置(默认 false 走 bootstrap TTL 缓存) - */ - async function checkIntercept(force = false): Promise { - await checkPluginAvailable() - if (!pluginAvailable.value) - return { intercepted: true, reason: 'plugin' } + /** + * 检查是否命中拦截(插件可用性 + 维护模式)。 + * @param force 是否强制刷新配置(默认 false 走 bootstrap TTL 缓存) + */ + async function checkIntercept(force = false): Promise { + await checkPluginAvailable(); + if (!pluginAvailable.value) { + reason.value = 'plugin'; + return { intercepted: true, reason: 'plugin' }; + } - const { ok } = await appConfigStore.bootstrap({ force }) - if (!ok) - return { intercepted: false, reason: null } - if (appConfigStore.configs.maintenance) - return { intercepted: true, reason: 'maintenance' } + const { ok } = await appConfigStore.bootstrap({ force }); + if (!ok) { + reason.value = null; + return { intercepted: false, reason: null }; + } - return { intercepted: false, reason: null } - } + if (appConfigStore.configs.maintenance) { + reason.value = 'maintenance'; + return { intercepted: true, reason: 'maintenance' }; + } - /** 跳转维护页(带原因参数,供维护页区分默认/配置文案) */ - function redirectToMaintenance(reason: MaintenanceInterceptReason) { - uni.redirectTo({ url: `${MAINTENANCE_PAGE_PATH}?from=${reason}` }) - } + return { intercepted: false, reason: null }; + } - /** - * 一站式:检查并跳转维护页。 - * @returns true = 已命中并跳转,调用方应中断后续逻辑;false = 放行 - */ - async function interceptOrContinue(force = false): Promise { - const { intercepted, reason } = await checkIntercept(force) - if (intercepted && reason) - redirectToMaintenance(reason) - return intercepted - } + /** 跳转维护页(带原因参数,供维护页区分默认/配置文案) */ + function redirectToMaintenance(reason: MaintenanceInterceptReason) { + uni.redirectTo({ url: `${MAINTENANCE_PAGE_PATH}?from=${reason}` }); + } - return { checkIntercept, redirectToMaintenance, interceptOrContinue } + /** + * 一站式:检查并跳转维护页。 + * @returns true = 已命中并跳转,调用方应中断后续逻辑;false = 放行 + */ + async function interceptOrContinue(force = false): Promise { + const { intercepted, reason } = await checkIntercept(force); + if (intercepted && reason) redirectToMaintenance(reason); + return intercepted; + } + + return { reason, checkIntercept, redirectToMaintenance, interceptOrContinue }; } diff --git a/src/hooks/usePluginAvailable.ts b/src/hooks/usePluginAvailable.ts index d461079..94cc894 100644 --- a/src/hooks/usePluginAvailable.ts +++ b/src/hooks/usePluginAvailable.ts @@ -15,150 +15,152 @@ * handleGetData() * }) */ -import { ref } from 'vue' -import { checkPluginAvailable } from '@/api/halo' -import { checkUrl } from '@/utils/url' +import { ref } from 'vue'; +import { checkPluginAvailable } from '@/api/halo'; +import { checkUrl } from '@/utils/url'; /** 依赖插件 ID 常量 */ export const NeedPluginIds = Object.freeze({ - PluginUniHalo: 'plugin-uni-halo', - PluginPhotos: 'PluginPhotos', - PluginLinks: 'PluginLinks', - PluginMoments: 'PluginMoments', - PluginSearchWidget: 'PluginSearchWidget', - PluginCommentWidget: 'PluginCommentWidget', - PluginVote: 'vote', - PluginDataStatistics: 'data-statistics', -}) + PluginUniHalo: 'plugin-uni-halo', + PluginPhotos: 'PluginPhotos', + PluginLinks: 'PluginLinks', + PluginMoments: 'PluginMoments', + PluginSearchWidget: 'PluginSearchWidget', + PluginCommentWidget: 'PluginCommentWidget', + PluginVote: 'vote', + PluginDataStatistics: 'data-statistics' +}); + +interface IPluginAvailableOption { + pluginId: string; + // 用于提示用户插件未安装或未启用时显示的提示信息 + tips?: string; + // 是否直接隐藏UI,不显示 uh-plugin-unavailable 组件 + hideUI?: boolean; + // 是否默认启用插件 + initalAvailable?: boolean; + // 检查完成后的回调函数 + callback?: (available: boolean) => void; +} export interface IPluginInfo { - id: string - name: string - desc: string - logo: string - url: string + id: string; + name: string; + desc: string; + logo: string; + url: string; } /** 依赖插件清单 */ export const NeedPlugins = new Map([ - [ - NeedPluginIds.PluginUniHalo, - { - id: 'plugin-uni-halo', - name: 'UniHalo配置', - desc: 'uni-halo 核心插件,未安装和启用的情况下,将无法使用 uni-halo,请检查是否已安装和启用', - logo: checkUrl('/plugins/plugin-uni-halo/assets/logo.png'), - url: 'https://www.halo.run/store/apps/app-ryemX', - }, - ], - [ - NeedPluginIds.PluginPhotos, - { - id: 'PluginPhotos', - name: '图库管理', - desc: '图库功能模块所需要的插件', - logo: checkUrl('/plugins/PluginPhotos/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-BmQJW', - }, - ], - [ - NeedPluginIds.PluginLinks, - { - id: 'PluginLinks', - name: '链接管理', - desc: '链接管理模块,用于网站友情链接功能模块', - logo: checkUrl('/plugins/PluginLinks/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-hfbQg', - }, - ], - [ - NeedPluginIds.PluginMoments, - { - id: 'PluginMoments', - name: '瞬间', - desc: '提供一个轻量级的内容图文、视频、音频等内容展示', - logo: checkUrl('/plugins/PluginMoments/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-SnwWD', - }, - ], - [ - NeedPluginIds.PluginSearchWidget, - { - id: 'PluginSearchWidget', - name: '搜索组件', - desc: '为应用提供统一的搜索组件', - logo: checkUrl('/plugins/PluginSearchWidget/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-DlacW', - }, - ], - [ - NeedPluginIds.PluginCommentWidget, - { - id: 'PluginCommentWidget', - name: '评论组件', - desc: '为用户前台提供完整的评论解决方案', - logo: checkUrl('/plugins/PluginCommentWidget/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-YXyaD', - }, - ], - [ - NeedPluginIds.PluginVote, - { - id: 'vote', - name: '投票管理', - desc: '投票模块所需要的插件,用于展示投票和提交投票', - logo: checkUrl('/plugins/vote/assets/logo.png'), - url: 'https://www.halo.run/store/apps/app-veyvzyhv', - }, - ], - [ - NeedPluginIds.PluginDataStatistics, - { - id: 'data-statistics', - name: '数据看板', - desc: '为 Halo2 提供强大的数据可视化统计功能,支持 Umami 流量统计、uptime、网站内部数据图表(标签、分类、文章趋势、评论排行、热门文章等)', - logo: checkUrl('/plugins/data-statistics/assets/logo.svg'), - url: 'https://www.halo.run/store/apps/app-rtnbbgfk', - }, - ], -]) + [ + NeedPluginIds.PluginUniHalo, + { + id: 'plugin-uni-halo', + name: 'UniHalo配置', + desc: 'uni-halo 核心插件,未安装和启用的情况下,将无法使用 uni-halo,请检查是否已安装和启用', + logo: checkUrl('/plugins/plugin-uni-halo/assets/logo.png'), + url: 'https://www.halo.run/store/apps/app-ryemX' + } + ], + [ + NeedPluginIds.PluginPhotos, + { + id: 'PluginPhotos', + name: '图库管理', + desc: '图库功能模块所需要的插件', + logo: checkUrl('/plugins/PluginPhotos/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-BmQJW' + } + ], + [ + NeedPluginIds.PluginLinks, + { + id: 'PluginLinks', + name: '链接管理', + desc: '链接管理模块,用于网站友情链接功能模块', + logo: checkUrl('/plugins/PluginLinks/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-hfbQg' + } + ], + [ + NeedPluginIds.PluginMoments, + { + id: 'PluginMoments', + name: '瞬间', + desc: '提供一个轻量级的内容图文、视频、音频等内容展示', + logo: checkUrl('/plugins/PluginMoments/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-SnwWD' + } + ], + [ + NeedPluginIds.PluginSearchWidget, + { + id: 'PluginSearchWidget', + name: '搜索组件', + desc: '为应用提供统一的搜索组件', + logo: checkUrl('/plugins/PluginSearchWidget/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-DlacW' + } + ], + [ + NeedPluginIds.PluginCommentWidget, + { + id: 'PluginCommentWidget', + name: '评论组件', + desc: '为用户前台提供完整的评论解决方案', + logo: checkUrl('/plugins/PluginCommentWidget/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-YXyaD' + } + ], + [ + NeedPluginIds.PluginVote, + { + id: 'vote', + name: '投票管理', + desc: '投票模块所需要的插件,用于展示投票和提交投票', + logo: checkUrl('/plugins/vote/assets/logo.png'), + url: 'https://www.halo.run/store/apps/app-veyvzyhv' + } + ], + [ + NeedPluginIds.PluginDataStatistics, + { + id: 'data-statistics', + name: '数据看板', + desc: '为 Halo2 提供强大的数据可视化统计功能,支持 Umami 流量统计、uptime、网站内部数据图表(标签、分类、文章趋势、评论排行、热门文章等)', + logo: checkUrl('/plugins/data-statistics/assets/logo.svg'), + url: 'https://www.halo.run/store/apps/app-rtnbbgfk' + } + ] +]); -/** - * 检查插件是否启用、安装 - * @param pluginId 插件 id - * @returns true = 安装、启用;false = 未安装启用 - */ -export async function checkNeedPluginAvailable(pluginId: string): Promise { - try { - const available = await checkPluginAvailable(pluginId) - return available?.data?.available !== false - } - catch (err) { - console.error(`检查插件 ${pluginId} 可用性失败`, err) - return false - } -} - -export function usePluginAvailable(pluginId: string, initial = true) { - /** 插件是否可用(默认 true,避免首帧闪现插件不可用占位;需要先置 false 的页面传 initial=false) */ - const available = ref(initial) - /** 是否校验中 */ - const checking = ref(false) - - /** - * 执行插件可用性校验(刷新 available) - * @returns 当前是否可用(与 available.value 一致,便于一次性调用方直接取返回值) - */ - async function check(): Promise { - checking.value = true - try { - available.value = await checkNeedPluginAvailable(pluginId) - return available.value - } - finally { - checking.value = false - } - } - - return { available, checking, check } +export function usePluginAvailable(option: IPluginAvailableOption) { + const { pluginId, callback, tips = '功能正在开发中...', initalAvailable = false } = option; + const checking = ref(false); + const available = ref(initalAvailable ?? false); + + /** + * 检查插件是否启用、安装 + */ + function check(): Promise { + return new Promise(async (resolve) => { + try { + checking.value = true; + const result = await checkPluginAvailable(pluginId); + console.log(`检查插件 ${pluginId} 可用性成功`, result); + available.value = result.data; + resolve(result.data); + } catch (err) { + console.error(`检查插件 ${pluginId} 可用性失败`, err); + available.value = false; + resolve(false); + } finally { + typeof callback === 'function' && callback(available.value); + checking.value = false; + } + }); + } + + return { pluginId, tips, checking, available, check }; } diff --git a/src/pages-blog/about/about.vue b/src/pages-blog/about/about.vue index af0477b..0513aab 100644 --- a/src/pages-blog/about/about.vue +++ b/src/pages-blog/about/about.vue @@ -8,6 +8,7 @@ import { onLoad } from '@dcloudio/uni-app' definePage({ style: { navigationBarTitleText: '关于项目', + navigationStyle: 'custom', }, }) @@ -37,6 +38,9 @@ onLoad(() => {