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

feat: 新增公告模块、数据加载hook与多项体验优化

- 新增公告全流程功能:首页公告轮播、公告列表页、公告详情页
- 新增统一数据加载状态hook useDataLoading与配套测试
- 重构配置加载逻辑,新增统一bootstrap静态配置拉取
- 新增维护模式、恋爱模块支持与验证码防刷功能
- 优化投票卡片、联系页、关于页UI样式
- 新增测试页面与全局玻璃卡片样式
- 修复markdown容器内边距与首页生命周期逻辑
This commit is contained in:
小莫唐尼
2026-09-04 02:57:25 +08:00
parent 902faa70e7
commit 5f22f4fa01
39 changed files with 4474 additions and 2244 deletions
+160 -160
View File
@@ -1,183 +1,183 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { getCategoryList, getCategoryPostList } from '@/api/halo'
import { useAppConfigStore } from '@/store/appConfig'
import { checkThumbnailUrl } from '@/utils/url'
import { t } from '@/locale'
import { useDataLoadingStatus, DataLoadingStatusEnum } from '@/hooks/useDataLoadingStatus'
import type { ICategory, IPost } from '@/api/types/halo'
import { computed, ref } from 'vue'
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { getCategoryList } from '@/api/halo'
import { useAppConfigStore } from '@/store/appConfig'
import { checkThumbnailUrl } from '@/utils/url'
import { t } from '@/locale'
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
import type { ICategory } from '@/api/types/halo'
definePage({
style: {
navigationBarTitleText: '分类',
enablePullDownRefresh: true,
backgroundColor: '#f6f3ee',
},
})
definePage({
style: {
navigationBarTitleText: '分类',
enablePullDownRefresh: true,
backgroundColor: '#f6f3ee',
},
})
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 categoryConfig = computed(() => haloConfigs.value.pageConfig?.categoryConfig)
const categoryConfig = computed(() => haloConfigs.value.pageConfig?.categoryConfig)
/* ---------------- 状态 ---------------- */
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
const queryParams = ref({
size: 20,
page: 1,
fieldSelector: ['spec.hideFromList=false'],
})
const hasNext = ref(false)
const dataList = ref<ICategory[]>([])
const isLoadMore = ref(false)
const loadMoreText = ref(t('common.loading'))
/* ---------------- 状态 ---------------- */
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
const queryParams = ref({
size: 20,
page: 1,
fieldSelector: ['spec.hideFromList=false'],
})
const hasNext = ref(false)
const dataList = ref<ICategory[]>([])
const isLoadMore = ref(false)
const loadMoreText = ref(t('common.loading'))
function handleResetInit() {
dataList.value = []
queryParams.value.page = 1
hasNext.value = false
isLoadMore.value = false
loadMoreText.value = t('common.loading')
}
function handleResetInit() {
dataList.value = []
queryParams.value.page = 1
hasNext.value = false
isLoadMore.value = false
loadMoreText.value = t('common.loading')
}
function handleInitPage() {
handleResetInit()
handleGetData()
}
function handleInitPage() {
handleResetInit()
handleGetData()
}
/* ---------------- 数据加载 ---------------- */
async function handleGetData() {
updateLoadingStatus(DataLoadingStatusEnum.Loading)
// 审核模式
if (calcAuditModeEnabled.value) {
const auditCategoryNames = appConfigStore.auditData.spec?.categories || []
try {
const res = await getCategoryList({ page: 1, size: 99999 })
const filtered = res.data.items
.filter(item => auditCategoryNames.includes(item.metadata.name))
.map(item => ({
...item,
postCount: item.postCount ?? 0,
spec: { ...item.spec, cover: checkThumbnailUrl(item.spec.cover, true) },
}))
const orderMap = new Map(auditCategoryNames.map((name, index) => [name, index]))
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999))
dataList.value = filtered
updateLoadingStatus(DataLoadingStatusEnum.Success)
loadMoreText.value = t('common.noMore')
uni.hideLoading()
uni.stopPullDownRefresh()
}
catch (err) {
console.error(err)
updateLoadingStatus(DataLoadingStatusEnum.Error)
loadMoreText.value = t('common.loadFailed')
}
return
}
/* ---------------- 数据加载 ---------------- */
async function handleGetData() {
updateLoadingStatus(DataLoadingStatusEnum.Loading)
// 审核模式
if (calcAuditModeEnabled.value) {
const auditCategoryNames = appConfigStore.auditData.spec?.categories || []
try {
const res = await getCategoryList({ page: 1, size: 99999 })
const filtered = res.data.items
.filter(item => auditCategoryNames.includes(item.metadata.name))
.map(item => ({
...item,
postCount: item.postCount ?? 0,
spec: { ...item.spec, cover: checkThumbnailUrl(item.spec.cover, true) },
}))
const orderMap = new Map(auditCategoryNames.map((name, index) => [name, index]))
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999))
dataList.value = filtered
updateLoadingStatus(DataLoadingStatusEnum.Success)
loadMoreText.value = t('common.noMore')
uni.hideLoading()
uni.stopPullDownRefresh()
}
catch (err) {
console.error(err)
updateLoadingStatus(DataLoadingStatusEnum.Error)
loadMoreText.value = t('common.loadFailed')
}
return
}
if (!isLoadMore.value) {
updateLoadingStatus(DataLoadingStatusEnum.Loading)
}
loadMoreText.value = t('common.loading')
if (!isLoadMore.value) {
updateLoadingStatus(DataLoadingStatusEnum.Loading)
}
loadMoreText.value = t('common.loading')
try {
const res = await getCategoryList({ ...queryParams.value })
try {
const res = await getCategoryList({ ...queryParams.value })
updateLoadingStatus(DataLoadingStatusEnum.Success)
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
hasNext.value = res.data.hasNext
updateLoadingStatus(DataLoadingStatusEnum.Success)
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
hasNext.value = res.data.hasNext
const tempItems = res.data.items.map(item => ({
...item,
postCount: item.postCount ?? 0,
spec: { ...item.spec, cover: checkThumbnailUrl(item.spec.cover, true) },
}))
const tempItems = res.data.items.map(item => ({
...item,
postCount: item.postCount ?? 0,
spec: { ...item.spec, cover: checkThumbnailUrl(item.spec.cover, true) },
}))
dataList.value = isLoadMore.value
? dataList.value.concat(tempItems)
: tempItems
dataList.value = isLoadMore.value
? dataList.value.concat(tempItems)
: tempItems
}
catch (err) {
console.error(err)
updateLoadingStatus(DataLoadingStatusEnum.Error)
loadMoreText.value = t('common.loadFailed')
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
}, 500)
}
}
}
catch (err) {
console.error(err)
updateLoadingStatus(DataLoadingStatusEnum.Error)
loadMoreText.value = t('common.loadFailed')
}
finally {
setTimeout(() => {
uni.hideLoading()
uni.stopPullDownRefresh()
}, 500)
}
}
function handleToCategory(category: ICategory) {
if (calcAuditModeEnabled.value) {
return
}
uni.navigateTo({
url: `/pages-blog/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`,
})
}
function handleToCategory(category : ICategory) {
if (calcAuditModeEnabled.value) {
return
}
uni.navigateTo({
url: `/pages-blog/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`,
})
}
/* ---------------- 生命周期 ---------------- */
onMounted(() => {
handleInitPage()
})
/* ---------------- 生命周期 ---------------- */
onPullDownRefresh(() => {
handleResetInit()
handleGetData()
})
onMounted(() => {
handleInitPage()
})
onPullDownRefresh(() => {
handleResetInit()
handleGetData()
})
onReachBottom(() => {
if (calcAuditModeEnabled.value) {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
return
}
if (hasNext.value) {
queryParams.value.page += 1
isLoadMore.value = true
handleGetData()
}
else {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
}
})
onReachBottom(() => {
if (calcAuditModeEnabled.value) {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
return
}
if (hasNext.value) {
queryParams.value.page += 1
isLoadMore.value = true
handleGetData()
}
else {
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
}
})
</script>
<template>
<view class="bg-page min-h-screen w-screen flex flex-col p-3 box-border">
<!-- 骨架屏 -->
<view v-if="loadingStatus !== DataLoadingStatusEnum.Success">
<uh-data-loading :loading-status="loadingStatus" />
</view>
<view class="box-border min-h-screen w-screen flex flex-col bg-page p-3">
<!-- 骨架屏 -->
<view v-if="loadingStatus !== DataLoadingStatusEnum.Success">
<uh-data-loading :loading-status="loadingStatus" />
</view>
<block v-else>
<view class="grid grid-cols-2 gap-3">
<view v-for="(item, index) in dataList" :key="index"
class="relative w-full box-border rounded-xl overflow-hidden uh-global-card-glass"
@click="handleToCategory(item)">
<image v-if="item.spec.cover" class="block h-32 w-full" :src="item.spec.cover" mode="aspectFill" />
<view class="absolute bottom-0 left-0 h-[140rpx] w-full bg-gradient-to-b from-black/0 to-black/30" />
<view class="absolute bottom-0 left-0 box-border w-full p-2.5 flex flex-col gap-1">
<text class="text-sm text-white font-bold truncate">
{{ item.spec.displayName }}
</text>
<text class="text-xs text-white opacity-80">
{{ item.postCount }} 篇文章
</text>
</view>
</view>
</view>
<view class="w-full py-5 text-center text-xs text-gray-400">
{{ loadMoreText }}
</view>
</block>
</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 relative box-border w-full overflow-hidden rounded-xl"
@click="handleToCategory(item)"
>
<image v-if="item.spec.cover" class="block h-32 w-full" :src="item.spec.cover" mode="aspectFill" />
<view class="absolute bottom-0 left-0 h-[140rpx] w-full from-black/0 to-black/30 bg-gradient-to-b" />
<view class="absolute bottom-0 left-0 box-border w-full flex flex-col gap-1 p-2.5">
<text class="truncate text-sm text-white font-bold">
{{ item.spec.displayName }}
</text>
<text class="text-xs text-white opacity-80">
{{ item.postCount }} 篇文章
</text>
</view>
</view>
</view>
<view class="w-full py-5 text-center text-xs text-gray-400">
{{ loadMoreText }}
</view>
</block>
</view>
</template>