mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
d528d214b5
1. 调整全局默认卡片样式为image_top,更新各页面默认布局配置 2. 给文章卡片添加variant属性适配双列/单列布局 3. 优化文章内容区域文字样式与评论列表空状态 4. 简化快捷导航组件代码,移除冗余样式与多余props 5. 优化markdown渲染配置与评论头像空格格式
303 lines
9.9 KiB
Vue
303 lines
9.9 KiB
Vue
<script lang="ts" setup>
|
||
import { computed, ref } from 'vue'
|
||
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 { sleep } from '@/utils/common'
|
||
import type { IPost } from '@/api/types/halo'
|
||
|
||
definePage({
|
||
style: {
|
||
navigationBarTitleText: '归档',
|
||
enablePullDownRefresh: true,
|
||
navigationStyle: 'custom',
|
||
},
|
||
})
|
||
|
||
const appConfigStore = useAppConfigStore()
|
||
|
||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||
|
||
const settingStore = useSettingStore()
|
||
|
||
/** 归档页列表布局(偏好设置驱动:single=单列 / double=双列) */
|
||
const archivesListLayout = computed(() => settingStore.settings.layout.archives.listLayout)
|
||
|
||
/* ---------------- 状态 ---------------- */
|
||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||
const activeTabIndex = ref(0)
|
||
const queryParams = ref({ size: 10, page: 1 })
|
||
const result = ref<{ hasNext: boolean }>({ hasNext: false })
|
||
const cacheDataList = ref<IPost[]>([])
|
||
const dataList = ref<{
|
||
sort: number
|
||
key: string
|
||
year: string
|
||
month: string
|
||
posts: IPost[]
|
||
}[]>([])
|
||
const isLoadMore = ref(false)
|
||
const loadMoreText = ref('加载中...')
|
||
|
||
const postLabelYearKey = 'content.halo.run/archive-year'
|
||
const postLabelMonthKey = 'content.halo.run/archive-month'
|
||
|
||
/* ---------------- 数据处理 ---------------- */
|
||
/** 按 tab 分组文章 */
|
||
function handleGetPosts(list: IPost[]): Record<string, IPost[]> {
|
||
const posts: Record<string, IPost[]> = {}
|
||
list.forEach((item) => {
|
||
const labels = item.metadata.labels || {}
|
||
let postItemKey = ''
|
||
if (activeTabIndex.value === 0) {
|
||
postItemKey = `${labels[postLabelYearKey]}-${labels[postLabelMonthKey]}`
|
||
}
|
||
else {
|
||
postItemKey = `${labels[postLabelYearKey]}`
|
||
}
|
||
if (posts[postItemKey]) {
|
||
posts[postItemKey].push(item)
|
||
}
|
||
else {
|
||
posts[postItemKey] = [item]
|
||
}
|
||
})
|
||
return posts
|
||
}
|
||
|
||
/** 处理成显示数据(分组 + 排序) */
|
||
function handleGetShowDataList(posts: Record<string, IPost[]>): typeof dataList.value {
|
||
const listResult: typeof dataList.value = []
|
||
Object.keys(posts).forEach((key) => {
|
||
const postData = {
|
||
sort: 0,
|
||
key,
|
||
year: key,
|
||
month: '',
|
||
posts: posts[key],
|
||
}
|
||
if (activeTabIndex.value === 0) {
|
||
const splitDate = key.split('-')
|
||
postData.year = splitDate[0]
|
||
postData.month = splitDate[1]
|
||
postData.sort = Number(key.replace('-', ''))
|
||
}
|
||
else {
|
||
postData.sort = Number(key)
|
||
}
|
||
listResult.push(postData)
|
||
})
|
||
listResult.sort((a, b) => Number(b.sort) - Number(a.sort))
|
||
return listResult
|
||
}
|
||
|
||
/** 去重缓存列表 */
|
||
function handleUniqueCacheDatalist(list: IPost[]): IPost[] {
|
||
const seen = new Set<string>()
|
||
return list.filter((item) => {
|
||
return seen.has(item.metadata.name) ? false : (seen.add(item.metadata.name), true)
|
||
})
|
||
}
|
||
|
||
/* ---------------- 数据加载 ---------------- */
|
||
async function handleGetData() {
|
||
if (calcAuditModeEnabled.value) {
|
||
// 审核模式:真实文章按 audit-data posts 过滤(数组顺序即展示顺序)
|
||
const auditPostNames = appConfigStore.auditData.spec?.posts || []
|
||
try {
|
||
const res = await getPostList({ page: 1, size: 99999, sort: ['spec.publishTime,desc'] })
|
||
const filtered = res.data.items.filter(item => auditPostNames.includes(item.metadata.name))
|
||
const orderMap = new Map(auditPostNames.map((name, index) => [name, index]))
|
||
filtered.sort((a, b) => (orderMap.get(a.metadata.name) ?? 999) - (orderMap.get(b.metadata.name) ?? 999))
|
||
const posts = handleGetPosts(filtered)
|
||
dataList.value = handleGetShowDataList(posts)
|
||
cacheDataList.value = filtered
|
||
updateLoadingStatus(
|
||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||
)
|
||
loadMoreText.value = '呜呜,没有更多数据啦~'
|
||
uni.stopPullDownRefresh()
|
||
}
|
||
catch (err) {
|
||
console.error(err)
|
||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||
}
|
||
return
|
||
}
|
||
|
||
if (isLoadMore.value) {
|
||
uni.showLoading({ title: '加载中...' })
|
||
}
|
||
else {
|
||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||
}
|
||
loadMoreText.value = '加载中...'
|
||
|
||
try {
|
||
// getPostList 返回 IResponse<IPostListRes>,数据在 .data 下
|
||
const res = await getPostList({ ...queryParams.value })
|
||
result.value = { hasNext: res.data.hasNext }
|
||
const posts = handleGetPosts(res.data.items)
|
||
const showDataList = handleGetShowDataList(posts)
|
||
|
||
if (isLoadMore.value) {
|
||
cacheDataList.value = handleUniqueCacheDatalist([...cacheDataList.value, ...res.data.items])
|
||
// 合并增量数据
|
||
showDataList.forEach((item) => {
|
||
const find = dataList.value.find(x => x.key === item.key)
|
||
if (find) {
|
||
item.posts.forEach((post) => {
|
||
if (!find.posts.some(x => x.metadata.name === post.metadata.name)) {
|
||
find.posts.push(post)
|
||
}
|
||
})
|
||
}
|
||
})
|
||
showDataList.forEach((post) => {
|
||
if (!dataList.value.some(x => x.key === post.key)) {
|
||
dataList.value.push(post)
|
||
}
|
||
})
|
||
dataList.value.sort((a, b) => Number(b.sort) - Number(a.sort))
|
||
}
|
||
else {
|
||
dataList.value = showDataList
|
||
cacheDataList.value = res.data.items
|
||
}
|
||
await sleep(500)
|
||
updateLoadingStatus(
|
||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||
)
|
||
loadMoreText.value = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~'
|
||
}
|
||
catch (err) {
|
||
console.error(err)
|
||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||
loadMoreText.value = '加载失败,请下拉刷新!'
|
||
}
|
||
finally {
|
||
uni.stopPullDownRefresh()
|
||
}
|
||
}
|
||
|
||
/** 顶部 tab 定义(同收藏页胶囊 chip) */
|
||
const archiveTabs = [
|
||
{ key: 'month', label: '按月份查看' },
|
||
{ key: 'year', label: '按年份查看' },
|
||
]
|
||
|
||
function handleOnTabChange(e: { index: number }) {
|
||
activeTabIndex.value = e.index
|
||
queryParams.value.page = 1
|
||
dataList.value = handleGetShowDataList(handleGetPosts(cacheDataList.value))
|
||
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
|
||
}
|
||
|
||
function handleToTopPage(duration = 500) {
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration,
|
||
fail: (err) => {
|
||
console.error('回顶失败', err)
|
||
},
|
||
})
|
||
}
|
||
|
||
/* ---------------- 生命周期 ---------------- */
|
||
handleGetData()
|
||
|
||
onPullDownRefresh(() => {
|
||
isLoadMore.value = false
|
||
queryParams.value.page = 1
|
||
handleGetData()
|
||
})
|
||
|
||
onReachBottom(() => {
|
||
if (calcAuditModeEnabled.value) {
|
||
uni.showToast({ icon: 'none', title: '没有更多数据了' })
|
||
return
|
||
}
|
||
if (result.value.hasNext) {
|
||
queryParams.value.page += 1
|
||
isLoadMore.value = true
|
||
handleGetData()
|
||
}
|
||
else {
|
||
uni.showToast({ icon: 'none', title: '没有更多数据了' })
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="min-h-screen w-screen flex flex-col bg-page">
|
||
<!-- 自定义导航 -->
|
||
<uh-navbar default-title="内容归档" title-color="text-gray-900" />
|
||
|
||
<!-- 顶部-->
|
||
<wd-sticky>
|
||
<scroll-view scroll-x class="w-full whitespace-nowrap">
|
||
<view class="box-border 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>
|
||
|
||
<uh-data-loading v-if="loadingStatus !== 'success'"
|
||
:loading-status="loadingStatus"
|
||
:empty-text="calcAuditModeEnabled ? '暂无归档的内容' : '暂无归档的文章'"
|
||
@refresh="handleGetData"
|
||
/>
|
||
|
||
|
||
<!-- 内容区域 -->
|
||
<block v-else>
|
||
<!-- 时间线 -->
|
||
<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)]" />
|
||
<view v-if="index !== dataList.length - 1" class="timeline-line mt-2 w-[2rpx] flex-1 bg-black/5" />
|
||
</view> -->
|
||
<!-- <view class="timeline-content min-w-0 flex-1 pb-10 pl-5"> -->
|
||
<view class="flex-1 pb-10">
|
||
<view class="mb-3 flex items-center gap-2">
|
||
<text class="text-md text-gray-900 font-bold">{{ item.year }}年</text>
|
||
<text v-if="activeTabIndex === 0" class="text-md text-gray-900 font-bold">{{ item.month }}月</text>
|
||
<text class="rounded-full bg-secondary px-2 py-1 text-xs text-gray-500 leading-none">共 {{ item.posts.length }} 篇{{ calcAuditModeEnabled ? '内容' : '文章' }}</text>
|
||
</view>
|
||
|
||
<view v-if="item.posts.length !== 0"
|
||
:class="archivesListLayout === 'double' ? 'grid grid-cols-2 gap-3' : 'flex flex-col gap-y-4'">
|
||
<uh-article-card
|
||
v-for="post in item.posts"
|
||
:key="post.metadata.name"
|
||
from="archives"
|
||
:variant="archivesListLayout === 'double' ? 'grid' : 'list'"
|
||
:article="post"
|
||
:audit-mode="calcAuditModeEnabled"
|
||
/>
|
||
</view>
|
||
<view v-else class="post-empty py-6 text-[26rpx] text-gray-400">
|
||
该日期下暂无归档文章!
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="load-text pb-6 text-center text-[24rpx] text-gray-400">
|
||
{{ loadMoreText }}
|
||
</view>
|
||
</block>
|
||
</view>
|
||
</template>
|