mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 重构项目页面与组件,优化整体结构与体验
1. 重构分类页面路由与实现,替换旧分类详情页为文章列表页 2. 统一使用自定义导航栏,替换原有原生导航配置 3. 重构加载状态管理,统一使用useDataLoadingStatus hook 4. 优化插件可用性检查逻辑与组件展示 5. 调整数据加载占位与空状态样式 6. 优化文章卡片样式与交互,新增分类跳转功能 7. 重构设置页枚举选择弹窗,使用wd-picker替换自定义实现 8. 删除废弃的IPluginAvailable类型与相关代码
This commit is contained in:
+1
-2
@@ -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<IResponse<IPluginAvailable>>(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, {
|
||||
return http.Get<IResponse<boolean>>(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, {
|
||||
cacheFor: 0,
|
||||
meta: { requestFrom: RequestFrom.Halo },
|
||||
})
|
||||
|
||||
@@ -456,10 +456,3 @@ export interface IUpvoteReq {
|
||||
name: string
|
||||
plural?: string
|
||||
}
|
||||
|
||||
/* ---------- 插件可用性 ---------- */
|
||||
|
||||
export interface IPluginAvailable {
|
||||
available: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
@@ -3,20 +3,17 @@ import { computed } from 'vue'
|
||||
import { checkThumbnailUrl } from '@/utils/url'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { formatTime } from '@/utils/formatTime'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
import type { IPost,ICategory } from '@/api/types/halo'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 来源页面(home 时跟随首页布局) */
|
||||
from ?: string
|
||||
auditMode?: boolean
|
||||
article : IPost
|
||||
}>(), {
|
||||
auditMode: false,
|
||||
from: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'on-click', article: IPost): void
|
||||
}>()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
/** 卡片布局 class(由全局设置 layout 决定) */
|
||||
@@ -43,16 +40,30 @@ const visitCount = computed(() => {
|
||||
return props.article.status?.stats?.visits ?? props.article.stats?.visit ?? 0
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
emit('on-click', props.article)
|
||||
function handleToArticleDetail() {
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/article-detail/article-detail?name=${props.article.metadata.name}`,
|
||||
animationType: 'slide-in-right',
|
||||
})
|
||||
}
|
||||
|
||||
function handleToCategory(category : ICategory) {
|
||||
if (props.auditMode) {
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/category-articles/category-articles?name=${category.metadata.name}&title=${category.spec.displayName}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="uh-global-card-glass overflow-hidden relative rounded-xl p-3" @click="handleClick">
|
||||
<!-- v-if="article.spec.pinned" -->
|
||||
<text class="absolute right-6 top-6 z-1 bg-secondary text-gray-60 text-xs px-2 py-1 rounded-lg"> 置顶 </text>
|
||||
<image class="w-full h-36 rounded-lg" :src="checkThumbnailUrl(article.spec.cover)" mode="aspectFill" lazy-load />
|
||||
<view class="uh-global-card-glass uh-shadow-xs overflow-hidden relative rounded-xl p-3"
|
||||
@click.stop="handleToArticleDetail()">
|
||||
<text v-if="article.spec.pinned"
|
||||
class="absolute right-6 top-6 z-1 bg-secondary text-gray-60 text-xs px-2 py-1 rounded-lg"> 置顶 </text>
|
||||
<image class="w-full h-36 rounded-lg" :src="checkThumbnailUrl(article.spec.cover)" mode="aspectFill"
|
||||
lazy-load />
|
||||
<view class="flex flex-col w-full gap-y-2 text-sm">
|
||||
<view class="mt-2 font-bold truncate">
|
||||
{{ article.spec.title }}
|
||||
@@ -60,9 +71,24 @@ function handleClick() {
|
||||
<view class="content line-clamp-2 text-gray-600">
|
||||
{{ article.status?.excerpt }}
|
||||
</view>
|
||||
<view class="my-1 box-border flex flex-wrap gap-2">
|
||||
<template v-if="article.categories && article.categories.length !== 0">
|
||||
<text v-for="cate in article.categories" :key="cate.metadata.name"
|
||||
class="py-1 px-2 text-xs rounded-xl bg-secondary" @click.stop="handleToCategory(cate)">
|
||||
{{ cate.spec.displayName }}
|
||||
</text>
|
||||
</template>
|
||||
<template v-if="article.tags && article.tags.length !== 0">
|
||||
<text v-for="tag in article.tags" :key="tag.metadata.name"
|
||||
class="py-1 px-2 text-xs rounded-xl bg-secondary">
|
||||
# {{ tag.spec.displayName }}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="flex items-center justify-between text-xs text-gray-500">
|
||||
<view class="flex items-center gap-x-1">
|
||||
<image :src="article.owner.avatar" class="uh-global-card-glass rounded-full w-5 h-5" mode="aspectFill"></image>
|
||||
<image :src="article.owner.avatar" class="uh-global-card-glass rounded-full w-5 h-5"
|
||||
mode="aspectFill"></image>
|
||||
<text>{{article.owner.displayName}}</text>
|
||||
</view>
|
||||
<view class="flex items-center gap-x-2">
|
||||
|
||||
@@ -18,7 +18,7 @@ interface IProps {
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
loadingStatus: 'loading',
|
||||
minHeight: '75vh',
|
||||
minHeight: '80vh',
|
||||
loadingText: '稍等,正在加载中哦',
|
||||
errorText: '哎呀,加载失败了呢~',
|
||||
emptyText: '啊偶,暂时没有数据呢~',
|
||||
|
||||
@@ -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}`,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, string>
|
||||
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,
|
||||
}))
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view v-if="pluginInfo"
|
||||
class="uh-plugin-unavailable mx-auto my-auto box-border flex flex-col gap-6 p-10 text-[28rpx]"
|
||||
:class="{ border: useBorder, decoration: useDecoration }" :style="[calcCustomStyle]">
|
||||
<!-- 图标 -->
|
||||
<image class="plugin-logo box-border h-[120rpx] w-[120rpx] rounded-3xl" :src="pluginInfo.logo"
|
||||
mode="scaleToFill" />
|
||||
<!-- 名称 -->
|
||||
<view class="plugin-name box-border text-[32rpx] text-[#333] font-bold">
|
||||
<view v-if="pluginInfo" class="mx-auto my-auto box-border flex flex-col items-center justify-center gap-6 text-sm">
|
||||
|
||||
<wd-icon class-prefix="uhemoji-icon" name="-cry" size="160rpx"></wd-icon>
|
||||
|
||||
<view class="box-border text-lg text-gray-900 font-bold">
|
||||
{{ pluginInfo.name }}
|
||||
</view>
|
||||
|
||||
<!-- 自定义错误提示 -->
|
||||
<view v-if="errorText" class="plugin-tip box-border border-2 rounded-xl border-dashed px-5 py-2.5 text-[24rpx]"
|
||||
style="border-color: #f2c97d; color: #f0a020;">
|
||||
<view v-if="errorText" class=" text-yellow-500 text-sm">
|
||||
{{ errorText }}
|
||||
</view>
|
||||
|
||||
<!-- 反馈按钮/复制地址 -->
|
||||
<view class="plugin-btns box-border w-full">
|
||||
<view class="w-full flex flex-col gap-y-4">
|
||||
<uh-button custom-class="!rounded-full py-2 !uh-shadow-xs" @click="handleRefresh()">
|
||||
{{props.checking?'正在刷新':'刷新试试'}}
|
||||
</uh-button>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<wd-button type="warning" plain block size="medium" open-type="contact">
|
||||
<uh-button custom-class="bg-white py-2 !rounded-full" open-type="contact">
|
||||
提交反馈
|
||||
</wd-button>
|
||||
</uh-button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<!-- 刷新按钮 -->
|
||||
<view class="flex justify-center">
|
||||
<wd-button size="small" plain type="info" @click="emit('on-refresh')">
|
||||
刷新试试
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<view class="plugin-copyright text-[20rpx] text-[#999]" style="transform: scale(0.9) translateY(20px);">
|
||||
提示:请确保 Halo 博客已安装相关插件
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.uh-plugin-unavailable {
|
||||
&.border {
|
||||
border: 2rpx solid #eee;
|
||||
}
|
||||
|
||||
&.decoration {
|
||||
background-color: rgb(255 255 255 / 95%);
|
||||
box-shadow: 0 0 12rpx rgb(226 232 240 / 35%);
|
||||
backdrop-filter: blur(6rpx);
|
||||
border-top: 12rpx solid rgb(3 169 244);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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
|
||||
intercepted: boolean;
|
||||
/** 命中原因;未命中为 null */
|
||||
reason: MaintenanceInterceptReason | 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,31 +23,41 @@ export const MAINTENANCE_PLUGIN_ID = 'plugin-uni-halo'
|
||||
* onLoad(async () => { if (await interceptOrContinue()) return })
|
||||
*/
|
||||
export function useMaintenanceIntercept() {
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const appConfigStore = useAppConfigStore();
|
||||
const reason = ref<MaintenanceInterceptReason | null>(null);
|
||||
/** 主插件可用性 hook(checkIntercept 内 await check 后读取 available) */
|
||||
const { available: pluginAvailable, check: checkPluginAvailable } = usePluginAvailable(MAINTENANCE_PLUGIN_ID)
|
||||
const { available: pluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
pluginId: MAINTENANCE_PLUGIN_ID
|
||||
});
|
||||
|
||||
/**
|
||||
* 检查是否命中拦截(插件可用性 + 维护模式)。
|
||||
* @param force 是否强制刷新配置(默认 false 走 bootstrap TTL 缓存)
|
||||
*/
|
||||
async function checkIntercept(force = false): Promise<IMaintenanceInterceptResult> {
|
||||
await checkPluginAvailable()
|
||||
if (!pluginAvailable.value)
|
||||
return { intercepted: true, reason: 'plugin' }
|
||||
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' };
|
||||
}
|
||||
|
||||
return { intercepted: false, reason: null };
|
||||
}
|
||||
|
||||
/** 跳转维护页(带原因参数,供维护页区分默认/配置文案) */
|
||||
function redirectToMaintenance(reason: MaintenanceInterceptReason) {
|
||||
uni.redirectTo({ url: `${MAINTENANCE_PAGE_PATH}?from=${reason}` })
|
||||
uni.redirectTo({ url: `${MAINTENANCE_PAGE_PATH}?from=${reason}` });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,11 +65,10 @@ export function useMaintenanceIntercept() {
|
||||
* @returns true = 已命中并跳转,调用方应中断后续逻辑;false = 放行
|
||||
*/
|
||||
async function interceptOrContinue(force = false): Promise<boolean> {
|
||||
const { intercepted, reason } = await checkIntercept(force)
|
||||
if (intercepted && reason)
|
||||
redirectToMaintenance(reason)
|
||||
return intercepted
|
||||
const { intercepted, reason } = await checkIntercept(force);
|
||||
if (intercepted && reason) redirectToMaintenance(reason);
|
||||
return intercepted;
|
||||
}
|
||||
|
||||
return { checkIntercept, redirectToMaintenance, interceptOrContinue }
|
||||
return { reason, checkIntercept, redirectToMaintenance, interceptOrContinue };
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
* 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({
|
||||
@@ -28,15 +28,27 @@ export const NeedPluginIds = Object.freeze({
|
||||
PluginSearchWidget: 'PluginSearchWidget',
|
||||
PluginCommentWidget: 'PluginCommentWidget',
|
||||
PluginVote: 'vote',
|
||||
PluginDataStatistics: 'data-statistics',
|
||||
})
|
||||
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;
|
||||
}
|
||||
|
||||
/** 依赖插件清单 */
|
||||
@@ -48,8 +60,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
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',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-ryemX'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginPhotos,
|
||||
@@ -58,8 +70,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '图库管理',
|
||||
desc: '图库功能模块所需要的插件',
|
||||
logo: checkUrl('/plugins/PluginPhotos/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-BmQJW',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-BmQJW'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginLinks,
|
||||
@@ -68,8 +80,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '链接管理',
|
||||
desc: '链接管理模块,用于网站友情链接功能模块',
|
||||
logo: checkUrl('/plugins/PluginLinks/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-hfbQg',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-hfbQg'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginMoments,
|
||||
@@ -78,8 +90,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '瞬间',
|
||||
desc: '提供一个轻量级的内容图文、视频、音频等内容展示',
|
||||
logo: checkUrl('/plugins/PluginMoments/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-SnwWD',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-SnwWD'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginSearchWidget,
|
||||
@@ -88,8 +100,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '搜索组件',
|
||||
desc: '为应用提供统一的搜索组件',
|
||||
logo: checkUrl('/plugins/PluginSearchWidget/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-DlacW',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-DlacW'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginCommentWidget,
|
||||
@@ -98,8 +110,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '评论组件',
|
||||
desc: '为用户前台提供完整的评论解决方案',
|
||||
logo: checkUrl('/plugins/PluginCommentWidget/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-YXyaD',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-YXyaD'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginVote,
|
||||
@@ -108,8 +120,8 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '投票管理',
|
||||
desc: '投票模块所需要的插件,用于展示投票和提交投票',
|
||||
logo: checkUrl('/plugins/vote/assets/logo.png'),
|
||||
url: 'https://www.halo.run/store/apps/app-veyvzyhv',
|
||||
},
|
||||
url: 'https://www.halo.run/store/apps/app-veyvzyhv'
|
||||
}
|
||||
],
|
||||
[
|
||||
NeedPluginIds.PluginDataStatistics,
|
||||
@@ -118,47 +130,37 @@ export const NeedPlugins = new Map<string, IPluginInfo>([
|
||||
name: '数据看板',
|
||||
desc: '为 Halo2 提供强大的数据可视化统计功能,支持 Umami 流量统计、uptime、网站内部数据图表(标签、分类、文章趋势、评论排行、热门文章等)',
|
||||
logo: checkUrl('/plugins/data-statistics/assets/logo.svg'),
|
||||
url: 'https://www.halo.run/store/apps/app-rtnbbgfk',
|
||||
},
|
||||
],
|
||||
])
|
||||
url: 'https://www.halo.run/store/apps/app-rtnbbgfk'
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
export function usePluginAvailable(option: IPluginAvailableOption) {
|
||||
const { pluginId, callback, tips = '功能正在开发中...', initalAvailable = false } = option;
|
||||
const checking = ref(false);
|
||||
const available = ref(initalAvailable ?? false);
|
||||
|
||||
/**
|
||||
* 检查插件是否启用、安装
|
||||
* @param pluginId 插件 id
|
||||
* @returns true = 安装、启用;false = 未安装启用
|
||||
*/
|
||||
export async function checkNeedPluginAvailable(pluginId: string): Promise<boolean> {
|
||||
function check(): Promise<boolean> {
|
||||
return new Promise<boolean>(async (resolve) => {
|
||||
try {
|
||||
const available = await checkPluginAvailable(pluginId)
|
||||
return available?.data?.available !== false
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`检查插件 ${pluginId} 可用性失败`, err)
|
||||
return false
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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<boolean> {
|
||||
checking.value = true
|
||||
try {
|
||||
available.value = await checkNeedPluginAvailable(pluginId)
|
||||
return available.value
|
||||
}
|
||||
finally {
|
||||
checking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return { available, checking, check }
|
||||
return { pluginId, tips, checking, available, check };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { onLoad } from '@dcloudio/uni-app'
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '关于项目',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -37,6 +38,9 @@ onLoad(() => {
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col overflow-hidden bg-page px-4 pb-8 pt-6">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="关于项目" title-color="text-gray-900" />
|
||||
|
||||
<!-- Hero 名片卡(主题色光斑透过毛玻璃形成柔和渐变) -->
|
||||
<view class="hero-wrap relative">
|
||||
<view class="absolute h-[220rpx] w-[220rpx] rounded-full bg-[rgba(185,228,36,0.32)] -right-8 -top-8" />
|
||||
@@ -68,7 +72,7 @@ onLoad(() => {
|
||||
:class="index < links.length - 1 ? 'border-b border-black/5' : ''"
|
||||
@click="copyText(link.copy, link.tip)"
|
||||
>
|
||||
<view class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl border border-black/5" :style="{ backgroundColor: link.tileColor + '1A' }">
|
||||
<view class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl border border-black/5" :style="{ backgroundColor: `${link.tileColor}1A` }">
|
||||
<text class="text-[30rpx] font-bold" :style="{ color: link.tileColor }">{{ link.tileLetter }}</text>
|
||||
</view>
|
||||
<view class="min-w-0 flex flex-1 flex-col">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getPostList } from '@/api/halo'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { checkThumbnailUrl } from '@/utils/url'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
@@ -16,6 +17,7 @@ definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '归档',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -26,7 +28,7 @@ const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||
const globalAppSettings = computed(() => settingStore.settings)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const activeTabIndex = ref(0)
|
||||
const queryParams = ref({ size: 10, page: 1 })
|
||||
const result = ref<{ hasNext: boolean }>({ hasNext: false })
|
||||
@@ -125,14 +127,16 @@ async function handleGetData() {
|
||||
const posts = handleGetPosts(filtered)
|
||||
dataList.value = handleGetShowDataList(posts)
|
||||
cacheDataList.value = filtered
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
loadMoreText.value = '呜呜,没有更多数据啦~'
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
return
|
||||
@@ -142,7 +146,7 @@ async function handleGetData() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
}
|
||||
else {
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
}
|
||||
loadMoreText.value = '加载中...'
|
||||
|
||||
@@ -178,12 +182,14 @@ async function handleGetData() {
|
||||
cacheDataList.value = res.data.items
|
||||
}
|
||||
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
finally {
|
||||
@@ -192,6 +198,12 @@ async function handleGetData() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 顶部 tab 定义(同收藏页胶囊 chip) */
|
||||
const archiveTabs = [
|
||||
{ key: 'month', label: '按月份查看' },
|
||||
{ key: 'year', label: '按年份查看' },
|
||||
]
|
||||
|
||||
function handleOnTabChange(e: { index: number }) {
|
||||
activeTabIndex.value = e.index
|
||||
queryParams.value.page = 1
|
||||
@@ -250,27 +262,38 @@ onReachBottom(() => {
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 顶部 tab(玻璃吸顶,wd-tabs 需用 wd-tab 子组件声明页签) -->
|
||||
<view class="archive-tabs uh-global-card-glass sticky top-0 z-10">
|
||||
<wd-tabs v-model="activeTabIndex" align="center" custom-style="background: transparent;" @change="handleOnTabChange">
|
||||
<wd-tab title="按月份查看" />
|
||||
<wd-tab title="按年份查看" />
|
||||
</wd-tabs>
|
||||
</view>
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="归档" title-color="text-gray-900" />
|
||||
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="loading !== 'success'">
|
||||
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
|
||||
<!-- 顶部 tab(吸顶玻璃胶囊 chip,同收藏页) -->
|
||||
<wd-sticky>
|
||||
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
||||
<view class="flex gap-2 px-3 pb-1 pt-3">
|
||||
<view
|
||||
v-for="(tab, index) in archiveTabs" :key="tab.key"
|
||||
class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-5 py-1.5 text-sm"
|
||||
:class="activeTabIndex === index ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||
@click="handleOnTabChange({ index })"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</wd-sticky>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
:empty-text="calcAuditModeEnabled ? '暂无归档的内容' : '暂无归档的文章'"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<block v-else>
|
||||
<view v-if="dataList.length === 0" class="list-empty min-h-[60vh] flex items-center justify-center">
|
||||
<wd-empty :description="calcAuditModeEnabled ? '暂无归档的内容' : '暂无归档的文章'" />
|
||||
</view>
|
||||
|
||||
<!-- 时间线 -->
|
||||
<view v-else class="timeline px-4 pt-3">
|
||||
<view class="timeline px-4 pt-3">
|
||||
<view v-for="(item, index) in dataList" :key="item.key" class="timeline-item flex">
|
||||
<view class="timeline-left w-[96rpx] flex shrink-0 flex-col items-center">
|
||||
<view class="timeline-dot mt-2 h-4 w-4 rounded-full bg-secondary shadow-[0_0_0_8rpx_rgba(215,249,76,0.3)]" />
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
/* ---------------- 跳转 ---------------- */
|
||||
function handleToCate(category : { metadata : { name : string }, spec : { displayName : string } }) {
|
||||
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}`,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '文章列表',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
|
||||
setTimeout(() => {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Success)
|
||||
}, 3000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="w-full min-h-screen bg-page">
|
||||
<uh-navbar default-title="文章列表" :need-placeholder="true" title-color="text-gray-900"></uh-navbar>
|
||||
|
||||
<!-- 内容区:由于 uh-navbar 内置有占位,所以我们的页面的主要内容应该从这里开始,比如这里就可以设置内边距或者其他样式,最外层的 <view class="w-full min-h-screen bg-page"> 仅作为容器-->
|
||||
<view class="box-border px-3">
|
||||
<!-- 加载状态 -->
|
||||
<uh-data-loading v-if="loadingStatus!==DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
min-height="80vh"></uh-data-loading>
|
||||
|
||||
<!-- 实际内容 -->
|
||||
<view v-else>
|
||||
请求成功啦
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
+23
-20
@@ -1,24 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 分类详情页(源自旧项目 pagesA/category-detail,新建复刻)
|
||||
* 展示某分类下的文章列表,分页加载
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { getCategoryPostList } from '@/api/halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '分类详情',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const queryParams = ref({ size: 10, page: 0 })
|
||||
const name = ref('')
|
||||
const pageTitle = ref('加载中...')
|
||||
const navbarTitle = ref('分类详情')
|
||||
const hasNext = ref(false)
|
||||
const dataList = ref<IPost[]>([])
|
||||
const isLoadMore = ref(false)
|
||||
@@ -26,25 +25,27 @@ const loadMoreText = ref('')
|
||||
|
||||
async function handleGetData() {
|
||||
if (!isLoadMore.value) {
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
}
|
||||
loadMoreText.value = '加载中...'
|
||||
|
||||
try {
|
||||
const res = await getCategoryPostList(name.value, { ...queryParams.value })
|
||||
uni.setNavigationBarTitle({ title: `${pageTitle.value} (共${res.data.total}篇)` })
|
||||
navbarTitle.value = `${pageTitle.value} (共${res.data.total}篇)`
|
||||
hasNext.value = res.data.hasNext
|
||||
dataList.value = isLoadMore.value
|
||||
? dataList.value.concat(res.data.items)
|
||||
: res.data.items
|
||||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||||
setTimeout(() => {
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
}, 500)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
finally {
|
||||
@@ -74,6 +75,7 @@ function handleToTopPage(duration = 500) {
|
||||
onLoad((options) => {
|
||||
name.value = options?.name || ''
|
||||
pageTitle.value = options?.title || '分类详情'
|
||||
navbarTitle.value = pageTitle.value
|
||||
handleGetData()
|
||||
})
|
||||
|
||||
@@ -96,25 +98,27 @@ onReachBottom(() => {
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: pageTitle.value,
|
||||
path: `/pages-blog/category-detail/category-detail?name=${name.value}&title=${pageTitle.value}`,
|
||||
path: `/pages-blog/category-articles/category-articles?name=${name.value}&title=${pageTitle.value}`,
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: pageTitle.value,
|
||||
path: `/pages-blog/category-detail/category-detail?name=${name.value}&title=${pageTitle.value}`,
|
||||
path: `/pages-blog/category-articles/category-articles?name=${name.value}&title=${pageTitle.value}`,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col py-6" style="background-color: #fafafd;">
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="loading !== 'success'">
|
||||
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
|
||||
</view>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col" style="background-color: #fafafd;">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar :default-title="navbarTitle" title-color="text-gray-900" />
|
||||
|
||||
<block v-else>
|
||||
<view v-if="dataList.length === 0" class="empty h-[60vh] flex items-center justify-center">
|
||||
<wd-empty description="该分类下暂无文章" />
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="该分类下暂无文章"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<block v-else>
|
||||
@@ -127,7 +131,6 @@ onShareTimeline(() => ({
|
||||
<view class="load-text py-5 text-center text-[24rpx] text-[#999]">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="to-top-btn fixed bottom-[100rpx] right-6 z-6 h-[72rpx] w-[72rpx] flex items-center justify-center rounded-full bg-white shadow-sm" @click="handleToTopPage()">
|
||||
<wd-icon name="arrow-up" size="20px" color="#03a9f4" />
|
||||
@@ -3,14 +3,15 @@
|
||||
* 联系博主页(源自旧项目 pagesA/contact,新建复刻)
|
||||
* 数字名片式设计:Hero 名片卡(渐变光斑透卡) + 品牌色字母瓦片联系方式列表,点击复制
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { computed, ref, watch, watchEffect } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { checkAvatarUrl } from '@/utils/url'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '联系博主',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -58,6 +59,12 @@ const platformMeta: Record<string, { color: string, letter: string }> = {
|
||||
|
||||
const calcIsNotEmpty = computed(() => result.value.some(item => item.value !== ''))
|
||||
|
||||
/* ---------------- 加载状态机(本地配置,状态直接推导) ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
watchEffect(() => {
|
||||
updateLoadingStatus(calcIsNotEmpty.value ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty)
|
||||
})
|
||||
|
||||
function handleGetData() {
|
||||
for (const key in socialConfig.value) {
|
||||
if (key === 'enabled')
|
||||
@@ -84,14 +91,13 @@ function handleOnClick(item: { value: string, name: string }) {
|
||||
watch(socialConfig, () => {
|
||||
handleGetData()
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
onLoad(() => {
|
||||
uni.setNavigationBarTitle({ title: '联系博主' })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen overflow-hidden bg-page px-4 pb-10 pt-6">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="联系博主" title-color="text-gray-900" />
|
||||
|
||||
<!-- Hero 名片卡(主题色光斑透过毛玻璃形成柔和渐变) -->
|
||||
<view class="hero-wrap relative">
|
||||
<view class="absolute h-[220rpx] w-[220rpx] rounded-full bg-[rgba(185,228,36,0.32)] -right-8 -top-8" />
|
||||
@@ -113,8 +119,12 @@ onLoad(() => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<block v-if="calcIsNotEmpty">
|
||||
<!-- 联系方式(状态机:无联系方式 → empty 态) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="30vh"
|
||||
empty-text="暂无联系方式" empty-sub-text="" @refresh="handleGetData"
|
||||
/>
|
||||
<template v-else>
|
||||
<uh-section-title class="mb-3 mt-6 text-[30rpx]">
|
||||
联系方式
|
||||
</uh-section-title>
|
||||
@@ -137,9 +147,6 @@ onLoad(() => {
|
||||
<wd-icon name="copy" size="28rpx" color="#c8c2b4" class="shrink-0" />
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else class="pt-12">
|
||||
<wd-empty description="暂无联系方式" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { ref } from 'vue'
|
||||
import { onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getChartData } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { IDataStatistics } from '@/api/uni-halo'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '数据看板',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -16,7 +18,7 @@
|
||||
const { available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable(uniHaloPluginId)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const statistics = ref<IDataStatistics>({
|
||||
tags: [],
|
||||
categories: [],
|
||||
@@ -109,7 +111,7 @@
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
uni.showLoading({ mask: true, title: '加载中...' })
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getChartData()
|
||||
statistics.value = res.data
|
||||
@@ -118,11 +120,14 @@
|
||||
handleTrendArticlesChart()
|
||||
handleUserCommentsChart()
|
||||
handleTop10ArticlesChart()
|
||||
loading.value = 'success'
|
||||
// 五类统计数据全部为空 → 空态
|
||||
const hasData = [res.data.tags, res.data.categories, res.data.articles, res.data.comments, res.data.top10Articles]
|
||||
.some(list => list.length > 0)
|
||||
updateLoadingStatus(hasData ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
@@ -155,10 +160,13 @@
|
||||
|
||||
<template>
|
||||
<view class="bg-page box-border min-h-screen w-screen p-3">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="数据看板" title-color="text-gray-900" />
|
||||
|
||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="uniHaloPluginId"
|
||||
error-text="阿偶,检测到当前插件没有安装或者启用,无法使用功能哦,请联系管理员" @on-refresh="handleGetData" />
|
||||
<template v-else>
|
||||
<uh-data-loading v-if="loading !== 'success'" :loading-status="loading" @refresh="handleGetData" />
|
||||
<uh-data-loading v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" empty-text="暂无统计数据" @refresh="handleGetData" />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else class="content flex flex-col gap-3">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useAppConfigStore } from '@/store/appConfig'
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '免责声明',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -40,6 +41,9 @@ function copyText(content: string, tips = '复制成功') {
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen bg-white p-12 text-[30rpx] text-[#303133] leading-[1.65]">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="免责声明" title-color="text-gray-900" />
|
||||
|
||||
<!-- 通过配置 -->
|
||||
<view v-if="disclaimersContent" style="min-height: 100%;" v-html="disclaimersContent" />
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getFriendLinkGroupList, getFriendLinkList } from '@/api/halo'
|
||||
import { getMiniProgramLinkGroupedList } from '@/api/uni-halo'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||
import type { ILink, ILinkGroup } from '@/api/types/halo'
|
||||
@@ -20,6 +21,7 @@ definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '友情链接',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -40,6 +42,12 @@ const { available: miniPluginAvailable, check: checkMiniPluginAvailable } = useP
|
||||
/* ---------------- tabs ---------------- */
|
||||
const activeTabIndex = ref(0)
|
||||
|
||||
/** 顶部 tab 定义(同收藏页胶囊 chip;审核模式下小程序 tab 隐藏) */
|
||||
const friendLinkTabs = computed(() => [
|
||||
{ key: 'site', label: '站点' },
|
||||
...(appConfigStore.auditModeEnabled ? [] : [{ key: 'mini', label: '小程序' }]),
|
||||
])
|
||||
|
||||
function handleOnTabChange(e: { index: number }) {
|
||||
activeTabIndex.value = e.index
|
||||
}
|
||||
@@ -52,7 +60,7 @@ watch(() => appConfigStore.auditModeEnabled, (enabled) => {
|
||||
|
||||
/* ==================== 站点 tab(plugin-links) ==================== */
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus: siteLoadingStatus, updateLoadingStatus: updateSiteLoadingStatus } = useDataLoadingStatus()
|
||||
const queryParams = ref({ size: 10, page: 1 })
|
||||
const detail = ref<{ show: boolean, data: ILink | null }>({ show: false, data: null })
|
||||
const hasNext = ref(false)
|
||||
@@ -77,13 +85,13 @@ async function handleGetLinkGroupData() {
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateSiteLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetData() {
|
||||
if (!isLoadMore.value) {
|
||||
loading.value = 'loading'
|
||||
updateSiteLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
}
|
||||
loadMoreText.value = ''
|
||||
|
||||
@@ -106,13 +114,15 @@ async function handleGetData() {
|
||||
}))
|
||||
dataList.value = dataList.value.concat(list)
|
||||
setTimeout(() => {
|
||||
loading.value = 'success'
|
||||
updateSiteLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||||
}, 500)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateSiteLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
finally {
|
||||
@@ -163,24 +173,26 @@ function calcSiteThumbnail(val?: string): string {
|
||||
|
||||
/* ==================== 小程序 tab(plugin-uni-halo) ==================== */
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const miniLoading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus: miniLoadingStatus, updateLoadingStatus: updateMiniLoadingStatus } = useDataLoadingStatus()
|
||||
const miniGroups = ref<IMiniProgramLinkGroupVo[]>([])
|
||||
const miniDetail = ref<{ show: boolean, data: IMiniProgramLink | null }>({ show: false, data: null })
|
||||
const applyShow = ref(false)
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetMiniProgramLinks() {
|
||||
miniLoading.value = 'loading'
|
||||
updateMiniLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getMiniProgramLinkGroupedList()
|
||||
miniGroups.value = res.data || []
|
||||
setTimeout(() => {
|
||||
miniLoading.value = 'success'
|
||||
updateMiniLoadingStatus(
|
||||
miniGroups.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
}, 500)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
miniLoading.value = 'error'
|
||||
updateMiniLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
@@ -321,13 +333,24 @@ onReachBottom(() => {
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 顶部 tabs -->
|
||||
<view class="tabs-wrap uh-global-card-glass sticky top-0 z-10 px-6">
|
||||
<wd-tabs v-model="activeTabIndex" align="left" custom-style="background: transparent;" @change="handleOnTabChange">
|
||||
<wd-tab title="站点" />
|
||||
<wd-tab v-if="!appConfigStore.auditModeEnabled" title="小程序" />
|
||||
</wd-tabs>
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="友情链接" title-color="text-gray-900" />
|
||||
|
||||
<!-- 顶部 tab(吸顶玻璃胶囊 chip,同收藏页) -->
|
||||
<wd-sticky>
|
||||
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
||||
<view class="flex gap-2 px-3 pb-1 pt-3">
|
||||
<view
|
||||
v-for="(tab, index) in friendLinkTabs" :key="tab.key"
|
||||
class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-5 py-1.5 text-sm"
|
||||
:class="activeTabIndex === index ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||
@click="handleOnTabChange({ index })"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</wd-sticky>
|
||||
|
||||
<!-- ==================== 站点 tab ==================== -->
|
||||
<template v-if="activeTabIndex === 0">
|
||||
@@ -338,18 +361,18 @@ onReachBottom(() => {
|
||||
@on-refresh="handleGetLinkGroupData"
|
||||
/>
|
||||
<template v-else>
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="loading !== 'success'">
|
||||
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="siteLoadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="siteLoadingStatus"
|
||||
empty-text="啊偶,博主还没有朋友呢~"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-else class="content pt-4">
|
||||
<view v-if="dataList.length === 0" class="h-[60vh] flex items-center justify-center content-empty">
|
||||
<wd-empty description="啊偶,博主还没有朋友呢~" />
|
||||
</view>
|
||||
|
||||
<!-- 友链列表 -->
|
||||
<view v-else class="link-list flex flex-col gap-4 px-4 pb-4">
|
||||
<view class="link-list flex flex-col gap-4 px-4 pb-4">
|
||||
<view v-for="link in dataList" :key="link.metadata?.name || link.spec.displayName">
|
||||
<!-- 色彩版 -->
|
||||
<view
|
||||
@@ -436,19 +459,18 @@ onReachBottom(() => {
|
||||
@on-refresh="handleGetMiniProgramLinks"
|
||||
/>
|
||||
<template v-else>
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="miniLoading !== 'success'">
|
||||
<uh-data-loading :loading-status="miniLoading" @refresh="handleGetMiniProgramLinks" />
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="miniLoadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="miniLoadingStatus"
|
||||
empty-text="还没有收录的小程序呢~"
|
||||
@refresh="handleGetMiniProgramLinks"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-else class="content flex flex-1 flex-col">
|
||||
<!-- 空态 -->
|
||||
<view v-if="miniGroups.length === 0" class="h-[60vh] flex items-center justify-center content-empty">
|
||||
<wd-empty description="还没有收录的小程序呢~" />
|
||||
</view>
|
||||
|
||||
<!-- 分组列表 -->
|
||||
<view v-else class="mini-link-list flex-1 px-6 py-4">
|
||||
<view class="mini-link-list flex-1 px-6 py-4">
|
||||
<view v-for="group in miniGroups" :key="group.groupName || 'ungrouped'" class="group-item mb-8">
|
||||
<view class="group-title mb-4 flex items-center">
|
||||
<text class="mr-2 inline-block h-[28rpx] w-[8rpx] rounded-full bg-secondary" />
|
||||
|
||||
@@ -16,13 +16,14 @@ import { generateUUID } from '@/utils/uuid'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import { randomTagColor } from '@/utils/random'
|
||||
import { markdownConfig } from '@/config/markdown'
|
||||
import { useDataLoading } from '@/hooks/useDataLoading'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { IMoment } from '@/api/types/halo'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '瞬间详情',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
// 下拉/回弹露出的窗口底色对齐页面底色
|
||||
backgroundColor: '#f6f3ee',
|
||||
},
|
||||
@@ -88,24 +89,29 @@ function buildMomentCard(res: IMoment): MomentCard {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 数据加载(useDataLoading 试点:状态由 hook 接管) ---------------- */
|
||||
const { data: moment, status, run: loadMoment } = useDataLoading(
|
||||
async (): Promise<MomentCard> => {
|
||||
/* ---------------- 数据加载(useDataLoadingStatus 状态机接管) ---------------- */
|
||||
const moment = ref<MomentCard | null>(null)
|
||||
const { loadingStatus: status, updateLoadingStatus } = useDataLoadingStatus()
|
||||
|
||||
async function loadMoment() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getMomentByName(queryName.value)
|
||||
uni.setNavigationBarTitle({ title: '瞬间详情' })
|
||||
return buildMomentCard(res.data)
|
||||
},
|
||||
{
|
||||
onSuccess: (card) => {
|
||||
const card = buildMomentCard(res.data)
|
||||
moment.value = card
|
||||
// 对象无键视为空 → 空态(与原 useDataLoading 默认判空一致)
|
||||
updateLoadingStatus(
|
||||
Object.keys(card).length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
nextTick(() => {
|
||||
createVideoContexts(card.videos || [])
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
uni.setNavigationBarTitle({ title: '瞬间详情' })
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
catch (err) {
|
||||
console.error('[moment-detail] 加载失败', err)
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 标签颜色(随机模式下按数据稳定,避免每次渲染重新随机变色) */
|
||||
const calcTagColors = computed(() => {
|
||||
@@ -255,7 +261,6 @@ function handleToTopPage(duration = 500) {
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad((options) => {
|
||||
uni.setNavigationBarTitle({ title: '瞬间加载中...' })
|
||||
queryName.value = options?.name || ''
|
||||
loadMoment()
|
||||
})
|
||||
@@ -280,6 +285,9 @@ onShareTimeline(() => ({
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen bg-page px-4 pb-8 pt-4">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="瞬间详情" title-color="text-gray-900" />
|
||||
|
||||
<!-- 状态区(加载中/失败可重试/空) -->
|
||||
<uh-data-loading
|
||||
v-if="status !== 'success'"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getNoticeDetail } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { checkIsUrl } from '@/utils/url'
|
||||
import { markdownConfig } from '@/config/markdown'
|
||||
import type { INoticeDetail } from '@/api/types/uni-halo'
|
||||
@@ -15,11 +16,12 @@ import type { INoticeDetail } from '@/api/types/uni-halo'
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '公告详情',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
const notFound = ref(false)
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const name = ref('')
|
||||
const detail = ref<INoticeDetail | null>(null)
|
||||
|
||||
const spec = computed(() => detail.value?.spec)
|
||||
@@ -41,17 +43,6 @@ function formatDate(value?: string): string {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack()
|
||||
}
|
||||
else {
|
||||
uni.switchTab({ url: '/pages/tabbar/home/home' })
|
||||
}
|
||||
}
|
||||
|
||||
/** 外链 → 复用 website 承载页 */
|
||||
function handleToExternal() {
|
||||
if (!spec.value?.link)
|
||||
return
|
||||
@@ -63,57 +54,44 @@ function handleToExternal() {
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
const name = options?.name || ''
|
||||
if (!name) {
|
||||
notFound.value = true
|
||||
loading.value = false
|
||||
/** 加载公告详情(状态机;404/无数据 → 空态,其余错误 → error 态,可重试) */
|
||||
async function loadDetail() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
if (!name.value) {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getNoticeDetail(name)
|
||||
const res = await getNoticeDetail(name.value)
|
||||
detail.value = res.data || null
|
||||
if (!detail.value?.spec) {
|
||||
notFound.value = true
|
||||
}
|
||||
updateLoadingStatus(
|
||||
detail.value?.spec ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty,
|
||||
)
|
||||
}
|
||||
catch (err) {
|
||||
console.error('公告详情加载失败', err)
|
||||
const code = (err as { code?: number }).code
|
||||
notFound.value = code === 404
|
||||
if (code !== 404) {
|
||||
uni.showToast({ icon: 'none', title: '公告加载失败' })
|
||||
updateLoadingStatus(code === 404 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Error)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
name.value = options?.name || ''
|
||||
loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="notice-detail min-h-screen w-screen bg-white pb-12">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loading" class="flex flex-col items-center justify-center py-40">
|
||||
<text class="text-[26rpx] text-[#999]">
|
||||
加载中...
|
||||
</text>
|
||||
</view>
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="公告详情" title-color="text-gray-900" />
|
||||
|
||||
<!-- 不存在/已下线 -->
|
||||
<view v-else-if="notFound" class="flex flex-col items-center justify-center py-40">
|
||||
<text class="text-[60rpx]">
|
||||
🕳️
|
||||
</text>
|
||||
<text class="mt-6 text-[26rpx] text-[#999]">
|
||||
公告不存在或已下线
|
||||
</text>
|
||||
<view class="mt-10">
|
||||
<wd-button size="small" type="primary" plain @click="handleBack">
|
||||
返回
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 加载/错误/空态(状态机) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="55vh"
|
||||
error-text="公告加载失败" empty-text="公告不存在或已下线" empty-sub-text=""
|
||||
@refresh="loadDetail"
|
||||
/>
|
||||
|
||||
<!-- 正文 -->
|
||||
<view v-else class="px-6 py-6">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getPostListByKeyword } from '@/api/halo'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { markdownConfig } from '@/config/markdown'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import { debounce } from '@/utils/debounce'
|
||||
@@ -15,6 +16,7 @@ definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '内容搜索',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -26,7 +28,7 @@ const uniHaloPluginId = 'plugin-search-widget'
|
||||
const { available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable(uniHaloPluginId)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const queryParams = ref({
|
||||
keyword: '',
|
||||
limit: 50,
|
||||
@@ -52,19 +54,26 @@ const calcAniDelays = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/** 空态文案(无关键词提示输入;有关键词提示未搜到) */
|
||||
const emptyText = computed(() =>
|
||||
queryParams.value.keyword ? `未搜到 ${queryParams.value.keyword} 相关内容` : '请输入关键词搜索',
|
||||
)
|
||||
|
||||
/* ---------------- 搜索 ---------------- */
|
||||
async function handleGetData() {
|
||||
if (calcAuditModeEnabled.value)
|
||||
return
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getPostListByKeyword({ ...queryParams.value })
|
||||
loading.value = 'success'
|
||||
dataList.value = (res.data as unknown as { hits?: typeof dataList.value }).hits || []
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
@@ -76,7 +85,7 @@ async function handleGetData() {
|
||||
function handleOnSearch() {
|
||||
if (!queryParams.value.keyword) {
|
||||
dataList.value = []
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||
}
|
||||
else {
|
||||
handleGetData()
|
||||
@@ -128,7 +137,7 @@ onLoad(async () => {
|
||||
}
|
||||
// 关键词非空(如带参进入)时自动搜索,否则展示空态
|
||||
if (!queryParams.value.keyword) {
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||
}
|
||||
else {
|
||||
handleGetData()
|
||||
@@ -145,21 +154,26 @@ onPullDownRefresh(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="内容搜索" title-color="text-gray-900" />
|
||||
|
||||
<uh-plugin-unavailable
|
||||
v-if="!uniHaloPluginAvailable"
|
||||
:plugin-id="uniHaloPluginId"
|
||||
error-text="检测到当前插件没有安装或者启用,无法使用搜索功能哦,请联系管理员"
|
||||
@on-refresh="handleOnSearch"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<!-- 顶部搜索框(玻璃吸顶,呼应首页搜索条) -->
|
||||
<view class="search-bar uh-global-card-glass sticky top-0 z-10 px-3 py-2">
|
||||
<view class="search-input h-[72rpx] flex items-center gap-3 rounded-full bg-[#f6f3ee] px-5">
|
||||
<wd-icon name="search" size="16px" color="#a8a294" />
|
||||
<!-- 顶部搜索框-->
|
||||
<wd-sticky class="">
|
||||
<view class="w-screen box-border px-3 py-2">
|
||||
<view class="uh-global-card-glass h-9 flex items-center gap-3 rounded-full px-5">
|
||||
<wd-icon name="search" size="16px" />
|
||||
<input
|
||||
v-model="queryParams.keyword"
|
||||
class="search-field flex-1 text-[26rpx] text-gray-900"
|
||||
class="flex-1 text-[26rpx] text-gray-900"
|
||||
placeholder="哈喽,想看些什么呢~"
|
||||
placeholder-class="text-gray-400"
|
||||
confirm-type="search"
|
||||
@@ -167,42 +181,40 @@ onPullDownRefresh(() => {
|
||||
@confirm="handleOnSearch"
|
||||
>
|
||||
<view v-if="queryParams.keyword" class="clear-btn flex items-center" @click="queryParams.keyword = ''; handleOnSearch()">
|
||||
<wd-icon name="close" size="14px" color="#a8a294" />
|
||||
<wd-icon name="close" size="14px" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-sticky>
|
||||
|
||||
<!-- 骨架屏 -->
|
||||
<view v-if="loading === 'loading'" class="loading-wrap p-3">
|
||||
<wd-skeleton :row="4" :animated="true" />
|
||||
</view>
|
||||
<view v-else-if="loading === 'error'" class="min-h-[60vh] flex items-center justify-center content-empty">
|
||||
<wd-empty description="搜索异常" />
|
||||
</view>
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== 'success'"
|
||||
:loading-status="loadingStatus"
|
||||
min-height="65vh"
|
||||
error-text="搜索异常"
|
||||
:empty-text="emptyText"
|
||||
@refresh="handleOnSearch"
|
||||
/>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-else class="content pt-4">
|
||||
<view v-if="dataList.length === 0" class="min-h-[60vh] flex items-center justify-center content-empty">
|
||||
<wd-empty v-if="!queryParams.keyword" description="请输入关键词搜索" />
|
||||
<wd-empty v-else :description="`未搜到 ${queryParams.keyword} 相关内容`" />
|
||||
</view>
|
||||
|
||||
<block v-else>
|
||||
<!-- 内容区域(成功态) -->
|
||||
<view v-else class="box-border pt-2 px-3 flex flex-col gap-y-3">
|
||||
<block v-if="dataList.length !== 0">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
class="article-card fade-up uh-global-card-glass mx-4 mb-4 flex flex-col overflow-hidden rounded-2xl p-4"
|
||||
class="uh-global-card-glass uh-shadow-xs border flex flex-col overflow-hidden rounded-2xl p-4"
|
||||
:style="{ animationDelay: `${calcAniDelays[index]}ms` }"
|
||||
@click="handleToDetail(item)"
|
||||
>
|
||||
<view class="card-head mb-3 flex items-center">
|
||||
<view
|
||||
class="type-tag mr-3 shrink-0 rounded-md px-1.5 py-0.5 text-[20rpx] leading-none"
|
||||
:class="isArticle(item) ? 'bg-secondary text-[#4d7c0f]' : 'bg-[#e8e3d8] text-gray-600'"
|
||||
class="type-tag mr-3 shrink-0 rounded-md px-1.5 py-1 text-xs leading-none"
|
||||
:class="isArticle(item) ? 'bg-secondary text-gray-900' : 'bg-blue-500 text-gray-50'"
|
||||
>
|
||||
{{ isArticle(item) ? '文章' : '瞬间' }}
|
||||
</view>
|
||||
<text class="card-title flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-[28rpx] text-gray-900 font-bold">{{ item.title }}</text>
|
||||
<text class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-gray-900 font-bold">{{ item.title }}</text>
|
||||
</view>
|
||||
<mp-html
|
||||
class="evan-markdown"
|
||||
@@ -219,13 +231,6 @@ onPullDownRefresh(() => {
|
||||
:show-language-name="true"
|
||||
copy-by-long-press
|
||||
/>
|
||||
<view class="card-foot mt-3 flex items-center">
|
||||
<text class="text-[24rpx] text-gray-400">{{ item.updateTimestamp ? `最近更新:${formatTimeUtil({ d: item.updateTimestamp, f: 'yyyy年MM月dd日 HH点mm分ss秒' })}` : '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="to-top-btn uh-global-card-glass fixed bottom-[100rpx] right-6 z-6 h-[72rpx] w-[72rpx] flex items-center justify-center rounded-full" @click="handleToTopPage()">
|
||||
<wd-icon name="arrow-up" size="20px" color="#6b7280" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { DefaultAppSettings } from '@/config/appSettings'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
@@ -157,6 +157,32 @@
|
||||
handleCloseEnum()
|
||||
}
|
||||
|
||||
/* ---------------- wd-picker 弹层数据 ---------------- */
|
||||
/** 枚举弹层列(首项「跟随站点默认」,空串哨兵映射 null) */
|
||||
const enumColumns = computed(() => {
|
||||
const def = enumSheet.value.def
|
||||
if (!def)
|
||||
return []
|
||||
return [
|
||||
{ label: '跟随站点默认', value: '' },
|
||||
...(def.options || []).map(opt => ({ label: opt.label, value: opt.value })),
|
||||
]
|
||||
})
|
||||
|
||||
/** 当前选中列值(单列;跟随站点默认时为空串) */
|
||||
const enumValue = computed(() => {
|
||||
const def = enumSheet.value.def
|
||||
if (!def)
|
||||
return ['']
|
||||
return [isFollowing(def) ? '' : String(valueOf(def.path) ?? '')]
|
||||
})
|
||||
|
||||
/** wd-picker 确认:空串哨兵还原为「跟随站点默认」 */
|
||||
function handlePickerConfirm(payload: { value: (string | number)[] }) {
|
||||
const picked = String(payload.value[0] ?? '')
|
||||
handleChooseEnum(picked === '' ? null : picked)
|
||||
}
|
||||
|
||||
/** 当前枚举项是否处于「跟随站点默认」 */
|
||||
function isFollowing(def : PrefDef) : boolean {
|
||||
return !isOverridden(def.path)
|
||||
@@ -247,7 +273,7 @@
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<wd-switch :model-value="def.path" @change="handleSwitchChange(def, $event)" />
|
||||
<wd-switch :model-value="valueOf(def.path) === true" @change="handleSwitchChange(def, $event)" />
|
||||
</view>
|
||||
<!-- 枚举选择(指示器位置) -->
|
||||
<view v-else class="pick-row flex items-center justify-between px-4 py-4"
|
||||
@@ -276,34 +302,17 @@
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部操作栏(玻璃悬浮-->
|
||||
<!-- 底部操作栏(玻璃悬浮) -->
|
||||
<view class="box-border w-full px-2">
|
||||
<uh-button custom-class="uh-global-card-glass py-2 !rounded-full"
|
||||
@click="handleResetAll">恢复默认</uh-button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 枚举选择底部弹层 -->
|
||||
<wd-popup v-model="enumSheet.show" position="bottom" closable custom-style="border-radius: 24rpx 24rpx 0 0;"
|
||||
@close="handleCloseEnum">
|
||||
<view v-if="enumSheet.def" class="enum-sheet box-border w-full pb-[env(safe-area-inset-bottom)]">
|
||||
<view class="enum-title py-6 text-center text-[30rpx] text-gray-900 font-bold">
|
||||
{{ enumSheet.def.label }}
|
||||
</view>
|
||||
<view class="enum-item flex items-center justify-between px-6 py-5"
|
||||
:class="isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
||||
@click="handleChooseEnum(null)">
|
||||
<text class="text-[28rpx]">跟随站点默认</text>
|
||||
<wd-icon v-if="isFollowing(enumSheet.def)" name="check" size="16px" color="#4d7c0f" />
|
||||
</view>
|
||||
<view v-for="opt in enumSheet.def.options" :key="opt.value"
|
||||
class="enum-item flex items-center justify-between border-t border-[#f0ece2] px-6 py-5"
|
||||
:class="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def) ? 'bg-secondary text-[#4d7c0f]' : 'text-gray-900'"
|
||||
@click="handleChooseEnum(opt.value)">
|
||||
<text class="text-[28rpx]">{{ opt.label }}</text>
|
||||
<wd-icon v-if="valueOf(enumSheet.def.path) === opt.value && !isFollowing(enumSheet.def)"
|
||||
name="check" size="16px" color="#4d7c0f" />
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
<!-- 枚举选择弹层(wd-picker 自带底部弹层与工具栏) -->
|
||||
<wd-picker
|
||||
v-model:visible="enumSheet.show" :title="enumSheet.def?.label || ''" :columns="enumColumns"
|
||||
:model-value="enumValue" confirm-button-text="确定" cancel-button-text="取消"
|
||||
@confirm="handlePickerConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
@@ -6,6 +6,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { getVoteDetail, submitVote } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { calcVotePercent, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import type { IVote, IVoteDetail, IVoteOption } from '@/api/types/uni-halo'
|
||||
@@ -14,11 +15,12 @@ definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '投票详情',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const submitLoading = ref(false)
|
||||
const pageTitle = ref('加载中...')
|
||||
const safeAreaBottom = ref(24)
|
||||
@@ -76,7 +78,7 @@ function handleCalcIsChecked(option: { id?: string }): boolean {
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
pageTitle.value = '加载中...'
|
||||
try {
|
||||
const res = await getVoteDetail(name.value)
|
||||
@@ -133,19 +135,20 @@ async function handleGetData() {
|
||||
vote.value = tempVote
|
||||
detail.value = res
|
||||
setTimeout(() => {
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(
|
||||
tempVote ? DataLoadingStatusEnum.Success : DataLoadingStatusEnum.Empty,
|
||||
)
|
||||
}, 200)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
pageTitle.value = '加载失败,请重试...'
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
uni.setNavigationBarTitle({ title: pageTitle.value })
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
@@ -269,18 +272,21 @@ onShareTimeline(() => ({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col py-6 pb-[160rpx]" style="background-color: #fafafd;">
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="loading !== 'success'">
|
||||
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
|
||||
</view>
|
||||
|
||||
<block v-else>
|
||||
<view v-if="!vote" class="empty h-[60vh] flex items-center justify-center">
|
||||
<wd-empty description="未查询到数据" />
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col pb-[160rpx]" style="background-color: #fafafd;">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar :default-title="pageTitle" title-color="text-gray-900" />
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="未查询到数据"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<block v-else>
|
||||
<template v-if="vote">
|
||||
<!-- 投票信息 -->
|
||||
<view class="vote-card mx-6 mb-6 flex flex-col overflow-hidden rounded-xl bg-white p-6 shadow-sm">
|
||||
<view class="sub-title relative box-border pl-6 text-[30rpx]">
|
||||
@@ -392,7 +398,7 @@ onShareTimeline(() => ({
|
||||
提交投票
|
||||
</wd-button>
|
||||
</view>
|
||||
</block>
|
||||
</template>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getVoteList } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import type { IVoteItem } from '@/api/types/uni-halo'
|
||||
|
||||
@@ -13,6 +14,7 @@ definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '投票中心',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -24,7 +26,7 @@ const uniHaloPluginId = 'plugin-vote'
|
||||
const { available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable(uniHaloPluginId)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const loading = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const dataList = ref<IVoteItem[]>([])
|
||||
const hasNext = ref(false)
|
||||
const queryParams = ref({ page: 1, size: 10 })
|
||||
@@ -33,7 +35,9 @@ const loadMoreText = ref('加载中...')
|
||||
|
||||
async function handleGetData() {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
loading.value = 'success'
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
loadMoreText.value = '呜呜,没有更多数据啦~'
|
||||
uni.stopPullDownRefresh()
|
||||
return
|
||||
@@ -41,22 +45,24 @@ async function handleGetData() {
|
||||
|
||||
uni.showLoading({ mask: true, title: '加载中...' })
|
||||
if (!isLoadMore.value) {
|
||||
loading.value = 'loading'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
}
|
||||
loadMoreText.value = '加载中...'
|
||||
|
||||
try {
|
||||
const res = await getVoteList({ ...queryParams.value })
|
||||
loading.value = 'success'
|
||||
hasNext.value = res.data.hasNext || false
|
||||
dataList.value = isLoadMore.value
|
||||
? dataList.value.concat(res.data.items)
|
||||
: res.data.items
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
loadMoreText.value = hasNext.value ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
loading.value = 'error'
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||||
}
|
||||
finally {
|
||||
@@ -117,6 +123,9 @@ onReachBottom(() => {
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="投票中心" title-color="text-gray-900" />
|
||||
|
||||
<uh-plugin-unavailable
|
||||
v-if="!uniHaloPluginAvailable"
|
||||
:plugin-id="uniHaloPluginId"
|
||||
@@ -124,16 +133,17 @@ onReachBottom(() => {
|
||||
@on-refresh="handleGetData"
|
||||
/>
|
||||
<template v-else>
|
||||
<!-- 加载/错误占位 -->
|
||||
<view v-if="loading !== 'success'">
|
||||
<uh-data-loading :loading-status="loading" @refresh="handleGetData" />
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="博主还未发布投票~"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-else class="content flex flex-col gap-4 p-3">
|
||||
<view v-if="dataList.length === 0" class="min-h-[60vh] flex items-center justify-center content-empty">
|
||||
<wd-empty description="博主还未发布投票~" />
|
||||
</view>
|
||||
<block v-else>
|
||||
<block v-if="dataList.length !== 0">
|
||||
<uh-vote-card
|
||||
v-for="vote in dataList"
|
||||
:key="vote.metadata?.name"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const settingStore = useSettingStore()
|
||||
// 维护拦截
|
||||
const { interceptOrContinue, redirectToMaintenance } = useMaintenanceIntercept()
|
||||
const { reason, interceptOrContinue, redirectToMaintenance } = useMaintenanceIntercept()
|
||||
|
||||
/** 通过二维码 scene 获取文章 id */
|
||||
async function getPostIdByQRCode(key : string) : Promise<string | null> {
|
||||
@@ -84,7 +84,7 @@
|
||||
}
|
||||
catch (err) {
|
||||
console.error('入口页初始化失败', err)
|
||||
redirectToMaintenance()
|
||||
redirectToMaintenance(reason.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -121,11 +121,10 @@
|
||||
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}`,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
handleInitPage()
|
||||
})
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 图库页(源自旧项目 pages/tabbar/gallery/gallery.vue,新建复刻)
|
||||
* 功能:相册分组切换 + 图片列表(瀑布流/网格) + 图片预览
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getPhotoGroupList, getPhotoListByGroupName } from '@/api/halo'
|
||||
@@ -25,9 +21,19 @@
|
||||
|
||||
const galleryConfig = computed(() => haloConfigs.value.pageConfig?.galleryConfig)
|
||||
|
||||
/** 依赖插件(plugin-photos) */
|
||||
const uniHaloPluginId = 'plugin-photos'
|
||||
const { available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable(uniHaloPluginId, false)
|
||||
/** 依赖插件(PluginPhotos) */
|
||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
pluginId: 'PluginPhotos',
|
||||
tips: '很抱歉,功能正在维护中...',
|
||||
callback: (isAvailable) => {
|
||||
if (!isAvailable) { return }
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 0,
|
||||
})
|
||||
handleGetCategory()
|
||||
}
|
||||
})
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
@@ -152,8 +158,6 @@
|
||||
uni.stopPullDownRefresh()
|
||||
return
|
||||
}
|
||||
|
||||
// 开始正常数据请求
|
||||
handleGetCategory()
|
||||
})
|
||||
|
||||
@@ -169,8 +173,7 @@
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
if (!uniHaloPluginAvailable.value)
|
||||
return
|
||||
if (!uniHaloPluginAvailable.value) { return }
|
||||
if (calcAuditModeEnabled.value) {
|
||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||
return
|
||||
@@ -188,8 +191,8 @@
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="uniHaloPluginId"
|
||||
error-text="检测到当前插件没有安装或者启用,无法使用图库功能哦,请联系管理员" @on-refresh="handleGetCategory" />
|
||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||
:checking="checking" @on-refresh="checkPluginAvailable" />
|
||||
<template v-else>
|
||||
<wd-sticky v-if="category.list.length!==0">
|
||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-3">
|
||||
|
||||
@@ -124,15 +124,9 @@
|
||||
}
|
||||
|
||||
/* ---------------- 跳转 ---------------- */
|
||||
function handleToArticleDetail(article : IPost) {
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/article-detail/article-detail?name=${article.metadata.name}`,
|
||||
animationType: 'slide-in-right',
|
||||
})
|
||||
}
|
||||
|
||||
function handleToSearch() {
|
||||
uni.navigateTo({ url: '/pages-blog/search/search' })
|
||||
function handleToArticles() {
|
||||
uni.navigateTo({ url: '/pages-blog/articles/articles' })
|
||||
}
|
||||
|
||||
function handleOnLogoToPage() {
|
||||
@@ -202,10 +196,10 @@
|
||||
|
||||
<!-- 最新文章 -->
|
||||
<uh-section-title class="mb-4 box-border px-3">
|
||||
最新内容
|
||||
最新推荐
|
||||
<template #right>
|
||||
<view class="uh-global-card-glass flex items-center justify-center rounded-md p-1 text-gray-400"
|
||||
@click="handleToSearch()">
|
||||
<view class="uh-global-card-glass flex items-center justify-center gap-x-1 rounded-md p-1 text-gray-400"
|
||||
@click="handleToArticles()">
|
||||
<wd-icon name="arrow-right" size="12px" />
|
||||
</view>
|
||||
</template>
|
||||
@@ -217,8 +211,9 @@
|
||||
|
||||
<block v-else>
|
||||
<view class="flex flex-col gap-y-3 p-3 pt-0" :class="globalAppSettings.layout.home">
|
||||
<uh-article-card v-for="(article, index) in articleList" :key="index" from="home" :article="article"
|
||||
@on-click="handleToArticleDetail" />
|
||||
<uh-article-card v-for="(article, index) in articleList" :key="index"
|
||||
from="home" :article="article" :audit-mode="calcAuditModeEnabled"
|
||||
/>
|
||||
</view>
|
||||
<view class="load-text mt-3 pb-5 text-center text-xs text-gray-400">
|
||||
{{ loadMoreText }}
|
||||
|
||||
Reference in New Issue
Block a user