1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

refactor: 重构维护模式相关逻辑,拆分维护说明与详情

- 拆分维护接口类型,新增notice和description字段区分纯文本说明与富文本详情
- 重构useMaintenanceIntercept钩子,统一维护拦截逻辑
- 新增维护详情弹窗组件,优化维护页UI与交互
- 修复按钮布局、滚动置顶黑名单等细节问题
- 优化公告弹窗组件样式与代码结构
This commit is contained in:
小莫唐尼
2026-09-04 16:05:09 +08:00
parent 570c394501
commit 629b1f9219
14 changed files with 676 additions and 1035 deletions
+209 -226
View File
@@ -1,250 +1,233 @@
<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'
import { useAppConfigStore } from '@/store/appConfig'
import { checkImageUrl } from '@/utils/url'
import { t } from '@/locale'
import { usePluginAvailable } from '@/utils/plugin'
import type { IPhoto, IPhotoGroup } from '@/api/types/halo'
import { computed, ref, watch } from 'vue'
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { getPhotoGroupList, getPhotoListByGroupName } from '@/api/halo'
import { useAppConfigStore } from '@/store/appConfig'
import { checkImageUrl } from '@/utils/url'
import { t } from '@/locale'
import { usePluginAvailable } from '@/utils/plugin'
import type { IPhoto, IPhotoGroup } from '@/api/types/halo'
definePage({
style: {
navigationBarTitleText: '图库',
enablePullDownRefresh: true,
},
})
definePage({
style: {
navigationBarTitleText: '图库',
enablePullDownRefresh: true,
},
})
const appConfigStore = useAppConfigStore()
const haloConfigs = computed(() => appConfigStore.configs)
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
const appConfigStore = useAppConfigStore()
const haloConfigs = computed(() => appConfigStore.configs)
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
const galleryConfig = computed(() => haloConfigs.value.pageConfig?.galleryConfig)
const galleryConfig = computed(() => haloConfigs.value.pageConfig?.galleryConfig)
/** 依赖插件(plugin-photos) */
const uniHaloPluginId = 'plugin-photos'
const uniHaloPluginAvailable = ref(true)
/** 依赖插件(plugin-photos) */
const uniHaloPluginId = 'plugin-photos'
const uniHaloPluginAvailable = ref(true)
/* ---------------- 状态 ---------------- */
const loading = ref<'loading' | 'success' | 'error'>('loading')
const category = ref<{ activeIndex: number, list: IPhotoGroup[] }>({
activeIndex: 0,
list: [],
})
const queryParams = ref({ size: 10, page: 1, group: '' })
const isLoadMore = ref(false)
const loadMoreText = ref('')
const hasNext = ref(false)
const dataList = ref<IPhoto[]>([])
const lock = ref(false)
/* ---------------- 状态 ---------------- */
const loading = ref<'loading' | 'success' | 'error' | 'empty'>('loading')
const category = ref<{ activeIndex : number, list : IPhotoGroup[] }>({
activeIndex: 0,
list: [],
})
const queryParams = ref({ size: 10, page: 1, group: '' })
const isLoadMore = ref(false)
const loadMoreText = ref('')
const hasNext = ref(false)
const dataList = ref<IPhoto[]>([])
const lock = ref(false)
/* ---------------- 数据加载 ---------------- */
async function handleGetCategory() {
if (calcAuditModeEnabled.value) {
// 审核模式:仅展示所选图库分组(galleryGroups)内的照片,未分组照片不展示
const auditGroupNames = appConfigStore.auditData.spec?.galleryGroups || []
try {
const res = await getPhotoGroupList({ page: 1, size: 0 })
const filtered = ((res.data as unknown as IPhotoGroup[] | undefined) || [])
.filter(item => auditGroupNames.includes(item.metadata.name))
.sort((a, b) => a.spec.priority - b.spec.priority)
category.value.list = filtered
if (category.value.list.length !== 0) {
queryParams.value.group = category.value.list[0].metadata.name || ''
handleGetData(true)
}
else {
loading.value = 'success'
loadMoreText.value = t('common.noMore')
uni.stopPullDownRefresh()
}
}
catch (e) {
console.error(e)
loading.value = 'error'
category.value = { activeIndex: 0, list: [] }
}
return
}
try {
const res = await getPhotoGroupList({ page: 1, size: 0 })
category.value.list = ((res.data as unknown as IPhotoGroup[] | undefined) || [])
.sort((a, b) => a.spec.priority - b.spec.priority)
category.value.list.unshift({ metadata: { name: undefined }, spec: { displayName: '全部', priority: 0 } })
if (category.value.list.length !== 0) {
queryParams.value.group = category.value.list[0].metadata.name || ''
handleGetData(true)
}
}
catch (e) {
console.error(e)
loading.value = 'error'
category.value = { activeIndex: 0, list: [] }
}
}
/* ---------------- 数据加载 ---------------- */
async function handleGetCategory() {
if (calcAuditModeEnabled.value) {
// 审核模式:仅展示所选图库分组(galleryGroups)内的照片,未分组照片不展示
const auditGroupNames = appConfigStore.auditData.spec?.galleryGroups || []
try {
const res = await getPhotoGroupList({ page: 1, size: 0 })
const filtered = ((res.data as unknown as IPhotoGroup[] | undefined) || [])
.filter(item => auditGroupNames.includes(item.metadata.name))
.sort((a, b) => a.spec.priority - b.spec.priority)
category.value.list = filtered
if (category.value.list.length !== 0) {
queryParams.value.group = category.value.list[0].metadata.name || ''
handleGetData(true)
}
else {
loading.value = 'success'
loadMoreText.value = t('common.noMore')
uni.stopPullDownRefresh()
}
}
catch (e) {
console.error(e)
loading.value = 'error'
category.value = { activeIndex: 0, list: [] }
}
return
}
try {
const res = await getPhotoGroupList({ page: 1, size: 0 })
category.value.list = ((res.data as unknown as IPhotoGroup[] | undefined) || [])
.sort((a, b) => a.spec.priority - b.spec.priority)
category.value.list.unshift({ metadata: { name: undefined }, spec: { displayName: '全部', priority: 0 } })
if (category.value.list.length !== 0) {
queryParams.value.group = category.value.list[0].metadata.name || ''
handleGetData(true)
}
}
catch (e) {
console.error(e)
loading.value = 'error'
category.value = { activeIndex: 0, list: [] }
}
}
async function handleGetData(isClearList = false) {
if (isClearList) {
dataList.value = []
queryParams.value.page = 1
}
async function handleGetData(isClearList = false) {
if (isClearList) {
dataList.value = []
queryParams.value.page = 1
}
if (!isLoadMore.value) {
loading.value = 'loading'
}
loadMoreText.value = ''
if (!isLoadMore.value) {
loading.value = 'loading'
}
loadMoreText.value = ''
try {
const res = await getPhotoListByGroupName({ ...queryParams.value })
hasNext.value = res.data.hasNext
loading.value = 'success'
if (res.data.items.length !== 0) {
const list = res.data.items.map(item => ({
...item,
spec: { ...item.spec, url: checkImageUrl(item.spec.url || item.spec.cover) },
}))
dataList.value = isLoadMore.value
? dataList.value.concat(list)
: list
}
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
}
catch (err) {
console.error(err)
loading.value = 'error'
loadMoreText.value = t('common.loadFailed')
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
lock.value = false
}, 500)
}
}
try {
const res = await getPhotoListByGroupName({ ...queryParams.value })
hasNext.value = res.data.hasNext
if (res.data.items.length !== 0) {
const list = res.data.items.map(item => ({
...item,
spec: { ...item.spec, url: checkImageUrl(item.spec.url || item.spec.cover) },
}))
dataList.value = isLoadMore.value
? dataList.value.concat(list)
: list
}
loading.value = dataList.value.length !== 0 ? 'success' : 'empty'
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
}
catch (err) {
console.error(err)
loading.value = 'error'
loadMoreText.value = t('common.loadFailed')
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
lock.value = false
}, 500)
}
}
function handleGetDataByCategory(index: number, cate: IPhotoGroup) {
queryParams.value.group = cate.metadata.name || ''
queryParams.value.page = 1
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
dataList.value = []
category.value.activeIndex = index
handleGetData(true)
}
function handleGetDataByCategory(index : number, cate : IPhotoGroup) {
queryParams.value.group = cate.metadata.name || ''
queryParams.value.page = 1
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
dataList.value = []
category.value.activeIndex = index
handleGetData(true)
}
/* ---------------- 图片预览 ---------------- */
function handlePreview(data: IPhoto) {
const current = dataList.value.findIndex(x => x.metadata.name === data.metadata.name)
uni.previewImage({
current,
urls: dataList.value.map(x => x.spec.url),
indicator: 'number',
loop: true,
})
}
/* ---------------- 图片预览 ---------------- */
function handlePreview(data : IPhoto) {
const current = dataList.value.findIndex(x => x.metadata.name === data.metadata.name)
uni.previewImage({
current,
urls: dataList.value.map(x => x.spec.url),
indicator: 'number',
loop: true,
})
}
/* ---------------- 生命周期 ---------------- */
onLoad(async () => {
// 检查插件可用性
uniHaloPluginAvailable.value = await usePluginAvailable(uniHaloPluginId)
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
})
/* ---------------- 生命周期 ---------------- */
onLoad(async () => {
// 检查插件可用性
uniHaloPluginAvailable.value = await usePluginAvailable(uniHaloPluginId)
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
})
watch(galleryConfig, (newVal) => {
if (!newVal)
return
uni.setNavigationBarTitle({ title: newVal.pageTitle || t('page.gallery.title') })
handleGetCategory()
}, { deep: true, immediate: true })
watch(galleryConfig, (newVal) => {
if (!newVal)
return
uni.setNavigationBarTitle({ title: newVal.pageTitle || t('page.gallery.title') })
handleGetCategory()
}, { deep: true, immediate: true })
onPullDownRefresh(() => {
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
dataList.value = []
isLoadMore.value = false
queryParams.value.page = 1
handleGetData(true)
})
onPullDownRefresh(() => {
if (!uniHaloPluginAvailable.value) {
uni.stopPullDownRefresh()
return
}
dataList.value = []
isLoadMore.value = false
queryParams.value.page = 1
handleGetData(true)
})
onReachBottom(() => {
if (!uniHaloPluginAvailable.value)
return
if (calcAuditModeEnabled.value) {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
return
}
if (hasNext.value) {
queryParams.value.page += 1
isLoadMore.value = true
handleGetData(false)
}
else {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
}
})
onReachBottom(() => {
if (!uniHaloPluginAvailable.value)
return
if (calcAuditModeEnabled.value) {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
return
}
if (hasNext.value) {
queryParams.value.page += 1
isLoadMore.value = true
handleGetData(false)
}
else {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
}
})
</script>
<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"
/>
<template v-else>
<wd-sticky>
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-3">
<view
v-for="(cate, index) in category.list" :key="cate.spec.displayName"
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-block border rounded-2xl px-4 py-1 text-sm"
:class="{
'bg-primary text-gray-900 font-bold': index === category.activeIndex,
}" @click="handleGetDataByCategory(index, cate)"
>
{{ cate.spec.displayName }}({{ cate.status?.photoCount ?? 0 }})
</view>
</scroll-view>
</wd-sticky>
<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" />
<template v-else>
<wd-sticky v-if="category.list.length!==0">
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-3">
<view v-for="(cate, index) in category.list" :key="cate.spec.displayName"
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-block border rounded-2xl px-4 py-1 text-sm"
:class="{ 'bg-primary text-gray-900 font-bold': index === category.activeIndex }"
@click="handleGetDataByCategory(index, cate)">
{{ cate.spec.displayName }} <text
v-if="cate.spec.displayName!=='全部'">({{ cate.status?.photoCount ?? 0 }})</text>
</view>
</scroll-view>
</wd-sticky>
<!-- 加载/错误占位(统一 uh-data-loading,错误可重试) -->
<view v-if="loading !== 'success'" class="box-border p-3">
<uh-data-loading :loading-status="loading" @refresh="handleGetCategory" />
</view>
<!-- 加载/错误占位 -->
<view v-if="loading !== 'success'" class="box-border p-3">
<uh-data-loading :loading-status="loading" @refresh="handleGetCategory" />
</view>
<!-- 内容区域 -->
<view v-else class="box-border w-full p-3">
<view
v-if="dataList.length === 0"
class="h-[70vh] w-full flex items-center justify-center content-empty"
>
<wd-empty description="博主还没有分享图片~" />
</view>
<block v-else>
<!-- 瀑布流(双列) -->
<view class="grid grid-cols-2 gap-3">
<view
v-for="(item, index) in dataList" :key="index"
class="uh-global-card-glass h-38 w-full overflow-hidden rounded-xl"
>
<image
class="h-full w-full" :src="item.spec.url" mode="aspectFill" lazy-load
@click="handlePreview(item)"
/>
</view>
</view>
<view class="load-text w-full py-4 text-center text-xs text-gray-500">
{{ loadMoreText }}
</view>
</block>
</view>
</template>
</view>
</template>
<!-- 内容区域 -->
<view v-else class="box-border w-full p-3">
<view class="grid grid-cols-2 gap-2.5">
<view v-for="(item, index) in dataList" :key="index"
class="uh-global-card-glass h-38 w-full overflow-hidden rounded-xl">
<image class="h-full w-full" :src="item.spec.url" mode="aspectFill" lazy-load
@click="handlePreview(item)" />
</view>
</view>
<view class="load-text w-full py-4 text-center text-xs text-gray-500">
{{ loadMoreText }}
</view>
</view>
</template>
</view>
</template>