mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 重构文章列表组件与页面逻辑,统一使用uh-article-card
1. 抽离并重构uh-article-card组件,支持多种布局模式与配置化参数 2. 归档页、文章列表页、首页统一使用uh-article-card替代原生实现 3. 移除冗余工具函数与状态管理,优化页面代码结构 4. 调整文章列表空状态提示文本与布局高度
This commit is contained in:
@@ -1,125 +1,181 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { checkThumbnailUrl } from '@/utils/url'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { formatTime } from '@/utils/formatTime'
|
||||
import type { ICategory, IPost } from '@/api/types/halo'
|
||||
import { computed } from 'vue'
|
||||
import { checkThumbnailUrl } from '@/utils/url'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { formatTime } from '@/utils/formatTime'
|
||||
import type { ICategory, IPost } from '@/api/types/halo'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
from?: string
|
||||
auditMode?: boolean
|
||||
article: IPost
|
||||
/** 卡片形态:list=常规列表卡(默认),grid=两列紧凑卡(隐藏分类/标签,摘要单行) */
|
||||
variant?: 'list' | 'grid'
|
||||
}>(), {
|
||||
auditMode: false,
|
||||
from: '',
|
||||
variant: 'list',
|
||||
})
|
||||
type CardLayout = 'image_top' | 'image_right' | 'image_bottom' | 'image_left'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
interface CardLayoutClasses {
|
||||
container: string
|
||||
cover: string
|
||||
contentWrapper: string
|
||||
footer: string
|
||||
authorGroup: string
|
||||
time: string
|
||||
tagCategory: string
|
||||
visits: string
|
||||
}
|
||||
|
||||
/** 卡片布局 class(由全局设置 layout 决定) */
|
||||
const cardType = computed(() => {
|
||||
const layout = settingStore.settings.layout
|
||||
// 首页双列时强制上图下文布局,除非显式指定其他
|
||||
if (props.from === 'home' && layout.home === 'h_row_col2') {
|
||||
if (!['tb_image_text', 'tb_text_image', 'only_text'].includes(layout.cardType)) {
|
||||
return [props.from, layout.home, 'tb_image_text']
|
||||
}
|
||||
return [props.from, layout.home, layout.cardType]
|
||||
}
|
||||
return [layout.home, layout.cardType]
|
||||
})
|
||||
/** 旧版全局 cardType → 新版 layout;未知值(如 only_text)由 effectiveLayout 兜底 image_top */
|
||||
const CARD_TYPE_TO_LAYOUT: Record<string, CardLayout> = {
|
||||
lr_image_text: 'image_left',
|
||||
lr_text_image: 'image_right',
|
||||
tb_image_text: 'image_top',
|
||||
tb_text_image: 'image_bottom',
|
||||
}
|
||||
|
||||
/** grid 紧凑模式(两列:隐藏分类/标签,摘要单行,底部精简) */
|
||||
const isGrid = computed(() => props.variant === 'grid')
|
||||
/** 单一事实源:每种布局的完整形态,模板不再有任何 order / 条件分支 */
|
||||
const CARD_LAYOUTS: Record<CardLayout, CardLayoutClasses> = {
|
||||
image_top: {
|
||||
container: 'flex flex-col gap-y-2',
|
||||
cover: '',
|
||||
contentWrapper: 'w-full',
|
||||
footer: 'flex items-center',
|
||||
authorGroup: 'flex-1 items-center justify-start gap-x-1',
|
||||
time: 'flex-1 text-center',
|
||||
tagCategory: '',
|
||||
visits: 'flex-1 justify-end',
|
||||
},
|
||||
image_bottom: {
|
||||
container: 'flex flex-col gap-y-2',
|
||||
cover: 'order-2',
|
||||
contentWrapper: 'w-full',
|
||||
footer: 'flex items-center',
|
||||
authorGroup: 'flex-1 items-center justify-center gap-x-1',
|
||||
time: 'flex-1 text-center',
|
||||
tagCategory: '',
|
||||
visits: 'flex-1 justify-center',
|
||||
},
|
||||
image_left: {
|
||||
container: 'flex gap-x-3 !p-2',
|
||||
cover: 'shrink-0 !w-36 !h-24',
|
||||
contentWrapper: 'w-0 flex-1 justify-between',
|
||||
footer: 'flex items-center justify-between',
|
||||
authorGroup: 'items-center gap-x-1',
|
||||
time: '!hidden',
|
||||
tagCategory: '!hidden',
|
||||
visits: '',
|
||||
},
|
||||
image_right: {
|
||||
container: 'flex gap-x-3 !p-2',
|
||||
cover: 'order-2 shrink-0 !w-36 !h-24',
|
||||
contentWrapper: 'order-1 w-0 flex-1 justify-between',
|
||||
footer: 'flex items-center justify-between',
|
||||
authorGroup: 'items-center gap-x-1',
|
||||
time: '!hidden',
|
||||
tagCategory: '!hidden',
|
||||
visits: '',
|
||||
},
|
||||
}
|
||||
|
||||
/** 发布时间格式化 yyyy-MM-dd */
|
||||
const publishTimeText = computed(() => {
|
||||
const time = props.article.spec.publishTime
|
||||
return time ? formatTime({ d: time, f: 'yyyy-MM-dd' }) : ''
|
||||
})
|
||||
const props = withDefaults(defineProps<{
|
||||
from ?: 'home' | 'articles' | 'archives' | ''
|
||||
auditMode ?: boolean
|
||||
article : IPost
|
||||
variant ?: 'list' | 'grid'
|
||||
layout ?: CardLayout
|
||||
}>(), {
|
||||
auditMode: false,
|
||||
from: '',
|
||||
variant: 'list',
|
||||
})
|
||||
|
||||
/** 阅读数(兼容 status.stats.visits 与旧版顶层 stats.visit) */
|
||||
const visitCount = computed(() => {
|
||||
return props.article.status?.stats?.visits ?? props.article.stats?.visit ?? 0
|
||||
})
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
function handleToArticleDetail() {
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/article-detail/article-detail?name=${props.article.metadata.name}`,
|
||||
animationType: 'slide-in-right',
|
||||
})
|
||||
}
|
||||
const isGrid = computed(() => props.variant === 'grid')
|
||||
|
||||
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}`,
|
||||
})
|
||||
}
|
||||
/** 实际生效布局:显式 layout > home/archives 跟随全局 cardType > image_top;窄列场景左右布局回退上图下文 */
|
||||
const effectiveLayout = computed<CardLayout>(() => {
|
||||
const followGlobal = props.from === 'home' || props.from === 'archives'
|
||||
let raw = props.layout
|
||||
if (!raw) {
|
||||
raw = followGlobal
|
||||
? CARD_TYPE_TO_LAYOUT[settingStore.settings.layout.cardType] ?? 'image_top'
|
||||
: 'image_top'
|
||||
}
|
||||
const narrow = isGrid.value || (props.from === 'home' && settingStore.settings.layout.home === 'h_row_col2')
|
||||
if (narrow && (raw === 'image_left' || raw === 'image_right')) {
|
||||
return 'image_top'
|
||||
}
|
||||
return raw
|
||||
})
|
||||
|
||||
const cardLayout = computed(() => CARD_LAYOUTS[effectiveLayout.value])
|
||||
|
||||
const publishTimeText = computed(() => {
|
||||
const time = props.article.spec.publishTime
|
||||
return time ? formatTime({ d: time, f: 'yyyy/MM/dd' }) : ''
|
||||
})
|
||||
|
||||
const visitCount = computed(() => {
|
||||
return props.article.status?.stats?.visits ?? props.article.stats?.visit ?? 0
|
||||
})
|
||||
|
||||
function handleToArticleDetail() {
|
||||
if (props.auditMode) {
|
||||
return
|
||||
}
|
||||
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 uh-shadow-xs relative overflow-hidden rounded-xl p-3"
|
||||
@click.stop="handleToArticleDetail()"
|
||||
>
|
||||
<text
|
||||
v-if="article.spec.pinned"
|
||||
class="text-gray-60 absolute right-6 top-6 z-1 rounded-lg bg-secondary px-2 py-1 text-xs"
|
||||
>
|
||||
置顶
|
||||
</text>
|
||||
<image
|
||||
:class="isGrid ? 'w-full h-24 rounded-lg' : 'w-full h-36 rounded-lg'"
|
||||
:src="checkThumbnailUrl(article.spec.cover)" mode="aspectFill" lazy-load
|
||||
/>
|
||||
<view class="w-full flex flex-col gap-y-2 text-sm">
|
||||
<view class="mt-2 truncate font-bold">
|
||||
{{ article.spec.title }}
|
||||
</view>
|
||||
<view :class="isGrid ? 'content line-clamp-1 text-gray-600' : 'content line-clamp-2 text-gray-600'">
|
||||
{{ article.status?.excerpt }}
|
||||
</view>
|
||||
<view v-if="!isGrid" 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="rounded-xl bg-secondary px-2 py-1 text-xs" @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="rounded-xl bg-secondary px-2 py-1 text-xs"
|
||||
>
|
||||
# {{ tag.spec.displayName }}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="mt-1 flex items-center justify-between text-xs text-gray-500">
|
||||
<view v-if="!isGrid" class="flex items-center gap-x-1">
|
||||
<image
|
||||
:src="article.owner.avatar" class="uh-global-card-glass h-5 w-5 rounded-full"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text>{{ article.owner.displayName }}</text>
|
||||
</view>
|
||||
<view class="flex items-center gap-x-2">
|
||||
{{ publishTimeText }}
|
||||
</view>
|
||||
<view class="visits">
|
||||
浏览
|
||||
<text class="number">{{ visitCount }}</text>
|
||||
次
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uh-global-card-glass uh-shadow-xs relative overflow-hidden rounded-xl p-3"
|
||||
:class="cardLayout.container" @click.stop="handleToArticleDetail()">
|
||||
<text v-if="article.spec.pinned"
|
||||
class="text-gray-60 absolute left-2 top-2 z-1 rounded-lg bg-secondary px-2 py-1 text-xs">
|
||||
置顶
|
||||
</text>
|
||||
<image :class="[isGrid ? 'w-full h-24 rounded-lg' : 'w-full h-36 rounded-lg', cardLayout.cover]"
|
||||
:src="checkThumbnailUrl(article.spec.cover)" mode="aspectFill" lazy-load />
|
||||
<view class="flex flex-col gap-y-2 text-sm" :class="cardLayout.contentWrapper">
|
||||
<view class="truncate font-bold">
|
||||
{{ article.spec.title }}
|
||||
</view>
|
||||
<view :class="isGrid ? 'content line-clamp-1 text-gray-600' : 'content line-clamp-2 text-gray-600'">
|
||||
{{ article.status?.excerpt }}
|
||||
</view>
|
||||
<view v-if="!isGrid" class="my-1 box-border flex flex-wrap gap-2" :class="cardLayout.tagCategory">
|
||||
<template v-if="article.categories && article.categories.length !== 0">
|
||||
<text v-for="cate in article.categories" :key="cate.metadata.name"
|
||||
class="rounded-xl bg-secondary px-2 py-1 text-xs" @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="rounded-xl bg-secondary px-2 py-1 text-xs">
|
||||
# {{ tag.spec.displayName }}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="flex items-center text-xs text-gray-500" :class="cardLayout.footer">
|
||||
<view v-if="!isGrid" class="flex items-center" :class="cardLayout.authorGroup">
|
||||
<image :src="article.owner.avatar" class="uh-global-card-glass h-5 w-5 rounded-full"
|
||||
mode="aspectFill" />
|
||||
<text class="truncate">{{ article.owner.displayName }}</text>
|
||||
</view>
|
||||
<text v-if="!isGrid" class="text-gray-400" :class="cardLayout.time">{{ publishTimeText }}</text>
|
||||
<view class="visits flex items-center gap-x-1" :class="cardLayout.visits">
|
||||
浏览
|
||||
<text class="number">{{ visitCount }}</text>
|
||||
次
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -3,11 +3,8 @@ 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 { checkThumbnailUrl } from '@/utils/url'
|
||||
import { sleep } from '@/utils/common'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
|
||||
definePage({
|
||||
@@ -19,10 +16,8 @@ definePage({
|
||||
})
|
||||
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||
const globalAppSettings = computed(() => settingStore.settings)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
@@ -43,17 +38,6 @@ const loadMoreText = ref('加载中...')
|
||||
const postLabelYearKey = 'content.halo.run/archive-year'
|
||||
const postLabelMonthKey = 'content.halo.run/archive-month'
|
||||
|
||||
/** 卡片布局偏好 → 原子类(对应旧版 cardType 样式变体) */
|
||||
const CARD_LAYOUTS: Record<string, { card: string, thumb: string, info: string }> = {
|
||||
lr_image_text: { card: '', thumb: 'h-[170rpx] w-[200rpx]', info: 'w-0 flex-1 pl-5' },
|
||||
lr_text_image: { card: '', thumb: 'order-2 h-[170rpx] w-[200rpx]', info: 'order-1 w-0 flex-1 pr-5' },
|
||||
tb_image_text: { card: 'flex-col', thumb: 'h-[220rpx] w-full', info: 'w-full pt-3' },
|
||||
tb_text_image: { card: 'flex-col', thumb: 'order-2 h-[220rpx] w-full', info: 'order-1 w-full pb-3' },
|
||||
only_text: { card: '', thumb: 'hidden', info: 'py-1' },
|
||||
}
|
||||
|
||||
const calcCardLayout = computed(() => CARD_LAYOUTS[globalAppSettings.value.layout.cardType] || CARD_LAYOUTS.lr_image_text)
|
||||
|
||||
/* ---------------- 数据处理 ---------------- */
|
||||
/** 按 tab 分组文章 */
|
||||
function handleGetPosts(list: IPost[]): Record<string, IPost[]> {
|
||||
@@ -124,7 +108,6 @@ async function handleGetData() {
|
||||
const posts = handleGetPosts(filtered)
|
||||
dataList.value = handleGetShowDataList(posts)
|
||||
cacheDataList.value = filtered
|
||||
c
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
@@ -207,15 +190,6 @@ function handleOnTabChange(e: { index: number }) {
|
||||
uni.pageScrollTo({ scrollTop: 0, duration: 500 })
|
||||
}
|
||||
|
||||
function handleToArticleDetail(article: IPost) {
|
||||
if (calcAuditModeEnabled.value)
|
||||
return
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/article-detail/article-detail?name=${article.metadata.name}`,
|
||||
animationType: 'slide-in-right',
|
||||
})
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
@@ -226,11 +200,6 @@ function handleToTopPage(duration = 500) {
|
||||
})
|
||||
}
|
||||
|
||||
function formatTime(time?: string): string {
|
||||
// 与旧项目一致:yyyy年MM月dd日 星期w
|
||||
return time ? formatTimeUtil({ d: time, f: 'yyyy年MM月dd日 星期w' }) : ''
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
handleGetData()
|
||||
|
||||
@@ -301,27 +270,14 @@ onReachBottom(() => {
|
||||
<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">
|
||||
<view
|
||||
<view v-if="item.posts.length !== 0" class="flex flex-col gap-y-4">
|
||||
<uh-article-card
|
||||
v-for="post in item.posts"
|
||||
:key="post.metadata.name"
|
||||
class="uh-global-card-glass mb-4 flex rounded-2xl p-4"
|
||||
:class="calcCardLayout.card"
|
||||
@click="handleToArticleDetail(post)"
|
||||
>
|
||||
<image class="post-thumbnail shrink-0 rounded-lg" :class="calcCardLayout.thumb" :src="checkThumbnailUrl(post.spec.cover)" mode="aspectFill" lazy-load />
|
||||
<view class="post-info min-w-0" :class="calcCardLayout.info">
|
||||
<view class="post-info-title overflow-hidden text-ellipsis whitespace-nowrap text-[28rpx] text-gray-900 font-bold">
|
||||
{{ post.spec.title }}
|
||||
</view>
|
||||
<view class="post-info-summary line-clamp-2 mt-2 text-[24rpx] text-gray-400">
|
||||
{{ post.status?.excerpt }}
|
||||
</view>
|
||||
<view class="post-info-time mt-2 text-[24rpx] text-gray-400">
|
||||
日期:{{ formatTime(post.spec.publishTime) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
from="archives"
|
||||
:article="post"
|
||||
:audit-mode="calcAuditModeEnabled"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="post-empty py-6 text-[26rpx] text-gray-400">
|
||||
该日期下暂无归档文章!
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 文章列表页(替代 unibest 模板占位页)
|
||||
* 标准布局:uh-navbar + useDataLoadingStatus 四态 + uh-data-loading + 分页加载 + 回顶
|
||||
* 2026-09-08:新增分类筛选 + 排序(参考投票中心胶囊弹层),列表改 grid 两列紧凑卡片
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getCategoryList, getPostList } from '@/api/halo'
|
||||
@@ -11,6 +6,7 @@ import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { checkAvatarUrl } from '@/utils/url'
|
||||
import { t } from '@/locale'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { sleep } from '@/utils/common'
|
||||
import type { ICategory, IPost } from '@/api/types/halo'
|
||||
|
||||
definePage({
|
||||
@@ -32,16 +28,13 @@ const hasNext = ref(false)
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref(t('common.loading'))
|
||||
|
||||
/* ---------------- 筛选与排序(内联:分类参考图库顶部,排序一排) ---------------- */
|
||||
interface IFilterOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
/** 分类列表(筛选选项数据源) */
|
||||
const categoryList = ref<ICategory[]>([])
|
||||
|
||||
/** 排序参数映射 */
|
||||
const sortMap: Record<string, string[]> = {
|
||||
default: ['spec.pinned,desc', 'spec.publishTime,desc'],
|
||||
latest: ['spec.publishTime,desc'],
|
||||
@@ -49,13 +42,11 @@ const sortMap: Record<string, string[]> = {
|
||||
pinned: ['spec.pinned,desc'],
|
||||
}
|
||||
|
||||
/** 分类选项(含"全部",参考图库顶部设计) */
|
||||
const categoryOptions = computed<IFilterOption[]>(() => [
|
||||
{ label: '全部', value: '' },
|
||||
...categoryList.value.map(c => ({ label: c.spec.displayName, value: c.metadata.name })),
|
||||
])
|
||||
|
||||
/** 排序选项(一排内联) */
|
||||
const sortOptions: IFilterOption[] = [
|
||||
{ label: '默认排序', value: 'default' },
|
||||
{ label: '最新', value: 'latest' },
|
||||
@@ -63,7 +54,6 @@ const sortOptions: IFilterOption[] = [
|
||||
{ label: '置顶', value: 'pinned' },
|
||||
]
|
||||
|
||||
/** 各维度当前选中值(空串 = 全部) */
|
||||
const filterValues = ref<Record<string, string>>({ category: '', sort: 'default' })
|
||||
|
||||
/** 切换分类/排序:重置分页并重新查询 */
|
||||
@@ -91,7 +81,6 @@ async function handleGetCategoryList() {
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetArticleList() {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
// 审核模式:真实文章按 audit-data posts 过滤(数组顺序即展示顺序)
|
||||
const auditPostNames = appConfigStore.auditData.spec?.posts || []
|
||||
try {
|
||||
const res = await getPostList({ page: 1, size: 0, sort: ['spec.publishTime,desc'] })
|
||||
@@ -100,6 +89,7 @@ async function handleGetArticleList() {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
await sleep(600)
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = t('common.noMore')
|
||||
}
|
||||
@@ -120,7 +110,6 @@ async function handleGetArticleList() {
|
||||
loadMoreText.value = t('common.loading')
|
||||
|
||||
try {
|
||||
// 应用分类筛选与排序参数
|
||||
const params = {
|
||||
...queryParams.value,
|
||||
category: filterValues.value.category || undefined,
|
||||
@@ -134,6 +123,7 @@ async function handleGetArticleList() {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
await sleep(600)
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
|
||||
}
|
||||
@@ -145,19 +135,8 @@ async function handleGetArticleList() {
|
||||
finally {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
fail: (err) => {
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(() => {
|
||||
handleGetCategoryList()
|
||||
handleGetArticleList()
|
||||
@@ -191,13 +170,11 @@ onReachBottom(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 自定义导航 -->
|
||||
<view class="min-h-screen w-screen flex flex-col bg-page">
|
||||
<uh-navbar default-title="文章列表" title-color="text-gray-900" />
|
||||
|
||||
<!-- 第一行:分类 Tab(参考图库顶部设计) -->
|
||||
<wd-sticky v-if="categoryOptions.length > 1" class="w-full">
|
||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-2">
|
||||
<scroll-view :scroll-x="true" :show-scrollbar="false" class="w-full whitespace-nowrap pt-2">
|
||||
<view
|
||||
v-for="cate in categoryOptions" :key="cate.value"
|
||||
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-flex border rounded-2xl px-4 py-1 text-sm"
|
||||
@@ -209,8 +186,7 @@ onReachBottom(() => {
|
||||
</scroll-view>
|
||||
</wd-sticky>
|
||||
|
||||
<!-- 第二行:排序一排 -->
|
||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap">
|
||||
<scroll-view :scroll-x="true" :show-scrollbar="false" class="w-full whitespace-nowrap">
|
||||
<view class="box-border flex gap-2 px-3 py-2">
|
||||
<view
|
||||
v-for="opt in sortOptions" :key="opt.value"
|
||||
@@ -223,16 +199,14 @@ onReachBottom(() => {
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== DataLoadingStatusEnum.Success"
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="博主还没有发布文章呢~"
|
||||
min-height="60vh"
|
||||
empty-text="啊偶,还没有任何内容哦~"
|
||||
min-height="75vh"
|
||||
@refresh="handleGetArticleList"
|
||||
/>
|
||||
|
||||
<!-- 文章列表(grid 两列) -->
|
||||
<view v-else class="box-border flex flex-col gap-4 p-3">
|
||||
<view class="grid grid-cols-2 gap-3">
|
||||
<uh-article-card
|
||||
|
||||
+219
-237
@@ -1,274 +1,256 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom, onShow } from '@dcloudio/uni-app'
|
||||
import { getPostList } from '@/api/halo'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||
import { t } from '@/locale'
|
||||
import { useMaintenanceIntercept } from '@/hooks/useMaintenanceIntercept'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom, onShow } from '@dcloudio/uni-app'
|
||||
import { getPostList } from '@/api/halo'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { useSettingStore } from '@/store/setting'
|
||||
import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
|
||||
import { t } from '@/locale'
|
||||
import { useMaintenanceIntercept } from '@/hooks/useMaintenanceIntercept'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { IPost } from '@/api/types/halo'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '首页',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '首页',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const settingStore = useSettingStore()
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
/** 维护拦截(插件可用性 + 维护模式,任一命中跳维护页;与入口页共用 hooks) */
|
||||
const { interceptOrContinue } = useMaintenanceIntercept()
|
||||
/** 是否已被拦截(配置已带维护键时同步置位,避免首载闪跳) */
|
||||
const intercepted = ref(!!appConfigStore.configs.maintenance)
|
||||
/** 维护拦截(插件可用性 + 维护模式,任一命中跳维护页;与入口页共用 hooks) */
|
||||
const { interceptOrContinue } = useMaintenanceIntercept()
|
||||
/** 是否已被拦截(配置已带维护键时同步置位,避免首载闪跳) */
|
||||
const intercepted = ref(!!appConfigStore.configs.maintenance)
|
||||
|
||||
const haloConfigs = computed(() => appConfigStore.configs)
|
||||
const haloConfigs = computed(() => appConfigStore.configs)
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref(t('common.loading'))
|
||||
const articleList = ref<IPost[]>([])
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref(t('common.loading'))
|
||||
const articleList = ref<IPost[]>([])
|
||||
|
||||
const result = ref<{ hasNext: boolean }>({ hasNext: false })
|
||||
const result = ref<{ hasNext : boolean }>({ hasNext: false })
|
||||
|
||||
const queryParams = ref({
|
||||
size: 10,
|
||||
page: 1,
|
||||
sort: ['spec.pinned,desc', 'spec.publishTime,desc'],
|
||||
})
|
||||
const queryParams = ref({
|
||||
size: 10,
|
||||
page: 1,
|
||||
sort: ['spec.pinned,desc', 'spec.publishTime,desc'],
|
||||
})
|
||||
|
||||
/* ---------------- 最新推荐模式(默认/置顶/最新/最旧) ---------------- */
|
||||
const recommendTabs = [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '置顶', value: 'pinned' },
|
||||
{ label: '最新', value: 'latest' },
|
||||
{ label: '最旧', value: 'oldest' },
|
||||
]
|
||||
/* ---------------- 最新推荐模式(默认/置顶/最新/最旧) ---------------- */
|
||||
const recommendTabs = [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '置顶', value: 'pinned' },
|
||||
{ label: '最新', value: 'latest' },
|
||||
{ label: '最旧', value: 'oldest' },
|
||||
]
|
||||
|
||||
const recommendMode = ref<'default' | 'pinned' | 'latest' | 'oldest'>('default')
|
||||
const recommendMode = ref<'default' | 'pinned' | 'latest' | 'oldest'>('default')
|
||||
|
||||
/** 各模式对应排序参数(默认 = 置顶优先 + 发布时间倒序) */
|
||||
const recommendSortMap: Record<string, string[]> = {
|
||||
default: ['spec.pinned,desc', 'spec.publishTime,desc'],
|
||||
pinned: ['spec.pinned,desc'],
|
||||
latest: ['spec.publishTime,desc'],
|
||||
oldest: ['spec.publishTime,asc'],
|
||||
}
|
||||
/** 各模式对应排序参数(默认 = 置顶优先 + 发布时间倒序) */
|
||||
const recommendSortMap : Record<string, string[]> = {
|
||||
default: ['spec.pinned,desc', 'spec.publishTime,desc'],
|
||||
pinned: ['spec.pinned,desc'],
|
||||
latest: ['spec.publishTime,desc'],
|
||||
oldest: ['spec.publishTime,asc'],
|
||||
}
|
||||
|
||||
/** 切换推荐模式:重置分页并重新查询 */
|
||||
function handleRecommendModeChange(mode: 'default' | 'pinned' | 'latest' | 'oldest') {
|
||||
if (recommendMode.value === mode)
|
||||
return
|
||||
recommendMode.value = mode
|
||||
isLoadMore.value = false
|
||||
articleList.value = []
|
||||
queryParams.value.page = 1
|
||||
queryParams.value.sort = recommendSortMap[mode]
|
||||
handleGetArticleList()
|
||||
}
|
||||
/** 切换推荐模式:重置分页并重新查询 */
|
||||
function handleRecommendModeChange(mode : 'default' | 'pinned' | 'latest' | 'oldest') {
|
||||
if (recommendMode.value === mode)
|
||||
return
|
||||
recommendMode.value = mode
|
||||
isLoadMore.value = false
|
||||
articleList.value = []
|
||||
queryParams.value.page = 1
|
||||
queryParams.value.sort = recommendSortMap[mode]
|
||||
handleGetArticleList()
|
||||
}
|
||||
|
||||
/* ---------------- 计算属性 ---------------- */
|
||||
const appInfo = computed(() => {
|
||||
const appInfoData = haloConfigs.value.appConfig?.appInfo as { name?: string, logo?: string } | undefined
|
||||
return {
|
||||
name: appInfoData?.name || 'uni-halo',
|
||||
logo: checkImageUrl(appInfoData?.logo),
|
||||
}
|
||||
})
|
||||
/* ---------------- 计算属性 ---------------- */
|
||||
const appInfo = computed(() => {
|
||||
const appInfoData = haloConfigs.value.appConfig?.appInfo as { name ?: string, logo ?: string } | undefined
|
||||
return {
|
||||
name: appInfoData?.name || 'uni-halo',
|
||||
logo: checkImageUrl(appInfoData?.logo),
|
||||
}
|
||||
})
|
||||
|
||||
const bloggerInfo = computed(() => {
|
||||
const blogger = haloConfigs.value.authorConfig?.blogger as { nickname?: string, avatar?: string } | undefined
|
||||
return {
|
||||
nickname: blogger?.nickname || '',
|
||||
avatar: checkAvatarUrl(blogger?.avatar),
|
||||
}
|
||||
})
|
||||
const bloggerInfo = computed(() => {
|
||||
const blogger = haloConfigs.value.authorConfig?.blogger as { nickname ?: string, avatar ?: string } | undefined
|
||||
return {
|
||||
nickname: blogger?.nickname || '',
|
||||
avatar: checkAvatarUrl(blogger?.avatar),
|
||||
}
|
||||
})
|
||||
|
||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||
|
||||
const globalAppSettings = computed(() => settingStore.settings)
|
||||
const globalAppSettings = computed(() => settingStore.settings)
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleQuery() {
|
||||
handleGetArticleList()
|
||||
}
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleQuery() {
|
||||
handleGetArticleList()
|
||||
}
|
||||
|
||||
/** 文章列表 */
|
||||
async function handleGetArticleList() {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
// 审核模式:真实文章按 audit-data posts 过滤(数组顺序即展示顺序)
|
||||
const auditPostNames = appConfigStore.auditData.spec?.posts || []
|
||||
try {
|
||||
const res = await getPostList({ page: 1, size: 0, sort: ['spec.publishTime,desc'] })
|
||||
const filtered = res.data.items.filter(item => auditPostNames.includes(item.metadata.name))
|
||||
articleList.value = filtered.map((item) => {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = t('common.noMore')
|
||||
}
|
||||
catch (err) {
|
||||
console.error('获取审核文章失败', err)
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = t('common.loadFailed')
|
||||
}
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
return
|
||||
}
|
||||
/** 文章列表 */
|
||||
async function handleGetArticleList() {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
// 审核模式:真实文章按 audit-data posts 过滤(数组顺序即展示顺序)
|
||||
const auditPostNames = appConfigStore.auditData.spec?.posts || []
|
||||
try {
|
||||
const res = await getPostList({ page: 1, size: 0, sort: ['spec.publishTime,desc'] })
|
||||
const filtered = res.data.items.filter(item => auditPostNames.includes(item.metadata.name))
|
||||
articleList.value = filtered.map((item) => {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = t('common.noMore')
|
||||
}
|
||||
catch (err) {
|
||||
console.error('获取审核文章失败', err)
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = t('common.loadFailed')
|
||||
}
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
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 getPostList({ ...toRaw(queryParams.value) })
|
||||
result.value.hasNext = res.data.hasNext
|
||||
articleList.value = (isLoadMore.value
|
||||
? articleList.value.concat(res.data.items)
|
||||
: res.data.items).map((item) => {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
|
||||
}
|
||||
catch (err) {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = t('common.loadFailed')
|
||||
console.error('获取文章失败', err)
|
||||
}
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
try {
|
||||
const res = await getPostList({ ...toRaw(queryParams.value) })
|
||||
result.value.hasNext = res.data.hasNext
|
||||
articleList.value = (isLoadMore.value
|
||||
? articleList.value.concat(res.data.items)
|
||||
: res.data.items).map((item) => {
|
||||
item.owner.avatar = checkAvatarUrl(item.owner.avatar)
|
||||
return item
|
||||
})
|
||||
updateLoadingStatus(articleList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
loadMoreText.value = res.data.hasNext ? t('common.loadMore') : t('common.noMore')
|
||||
}
|
||||
catch (err) {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
loadMoreText.value = t('common.loadFailed')
|
||||
console.error('获取文章失败', err)
|
||||
}
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 跳转 ---------------- */
|
||||
/* ---------------- 跳转 ---------------- */
|
||||
|
||||
/** 更多文章:跳转文章列表页 */
|
||||
function handleToArticles() {
|
||||
uni.navigateTo({ url: '/pages-blog/articles/articles' })
|
||||
}
|
||||
function handleToArticles() {
|
||||
uni.navigateTo({ url: '/pages-blog/articles/articles' })
|
||||
}
|
||||
|
||||
function handleOnLogoToPage() {
|
||||
uni.switchTab({ url: '/pages/tabbar/about/about' })
|
||||
}
|
||||
function handleOnLogoToPage() {
|
||||
uni.switchTab({ url: '/pages/tabbar/about/about' })
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
fail: (err) => {
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
function init() {
|
||||
if (!intercepted.value) {
|
||||
handleQuery()
|
||||
}
|
||||
}
|
||||
init()
|
||||
|
||||
function init() {
|
||||
if (!intercepted.value) {
|
||||
handleQuery()
|
||||
}
|
||||
}
|
||||
init()
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
// 维护检查
|
||||
onShow(async () => {
|
||||
intercepted.value = await interceptOrContinue()
|
||||
console.log('拦截状态', intercepted.value)
|
||||
})
|
||||
|
||||
// 维护检查
|
||||
onShow(async () => {
|
||||
intercepted.value = await interceptOrContinue()
|
||||
console.log('拦截状态', intercepted.value)
|
||||
})
|
||||
onPullDownRefresh(() => {
|
||||
isLoadMore.value = false
|
||||
queryParams.value.page = 1
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
isLoadMore.value = false
|
||||
queryParams.value.page = 1
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||
return
|
||||
}
|
||||
if (result.value.hasNext) {
|
||||
queryParams.value.page += 1
|
||||
isLoadMore.value = true
|
||||
handleGetArticleList()
|
||||
}
|
||||
else {
|
||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||
}
|
||||
})
|
||||
onReachBottom(() => {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||
return
|
||||
}
|
||||
if (result.value.hasNext) {
|
||||
queryParams.value.page += 1
|
||||
isLoadMore.value = true
|
||||
handleGetArticleList()
|
||||
}
|
||||
else {
|
||||
uni.showToast({ icon: 'none', title: t('common.noMoreData') })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 轮播 -->
|
||||
<uh-home-banner />
|
||||
<view class="min-h-screen w-screen flex flex-col bg-page">
|
||||
<!-- 轮播 -->
|
||||
<uh-home-banner />
|
||||
|
||||
<!-- 公告 -->
|
||||
<uh-home-notify />
|
||||
<!-- 公告 -->
|
||||
<uh-home-notify />
|
||||
|
||||
<!-- 快捷导航 -->
|
||||
<uh-home-quick-nav />
|
||||
<!-- 快捷导航 -->
|
||||
<uh-home-quick-nav />
|
||||
|
||||
<!-- 精选分类 -->
|
||||
<uh-home-category />
|
||||
<!-- 精选分类 -->
|
||||
<uh-home-category />
|
||||
|
||||
<!-- 最新文章 -->
|
||||
<uh-section-title class="mb-4 box-border px-3">
|
||||
最新推荐
|
||||
<template #right>
|
||||
<view class="flex items-center gap-2">
|
||||
<!-- 推荐模式分段器:默认 / 置顶 / 最新 -->
|
||||
<view class="uh-global-card-glass uh-shadow-xs flex scale-95 items-center gap-1 border rounded-lg p-0.5">
|
||||
<view
|
||||
v-for="tab in recommendTabs" :key="tab.value"
|
||||
class="rounded-md px-2 py-0.5 text-xs"
|
||||
:class="recommendMode === tab.value ? 'bg-secondary text-gray-900' : 'text-gray-500'"
|
||||
@click="handleRecommendModeChange(tab.value as 'default' | 'pinned' | 'latest' | 'oldest')"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 更多(查看全部文章) -->
|
||||
<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="24rpx" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</uh-section-title>
|
||||
<!-- 最新文章 -->
|
||||
<uh-section-title class="mb-4 box-border px-3">
|
||||
最新推荐
|
||||
<template #right>
|
||||
<view class="flex items-center gap-2">
|
||||
<!-- 推荐模式分段器:默认 / 置顶 / 最新 -->
|
||||
<view class="uh-global-card-glass uh-shadow-xs flex items-center border rounded-lg p-0.5">
|
||||
<view v-for="tab in recommendTabs" :key="tab.value" class="rounded-md px-2 py-0.5 text-xs"
|
||||
:class="recommendMode === tab.value ? 'bg-secondary text-gray-900' : 'text-gray-500'"
|
||||
@click="handleRecommendModeChange(tab.value as 'default' | 'pinned' | 'latest' | 'oldest')">
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
<view class="rounded-md px-2 py-0.5 text-xs text-gray-500" @click="handleToArticles()">
|
||||
更多
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="false"
|
||||
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="24rpx" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</uh-section-title>
|
||||
|
||||
<!-- 加载/错误占位 -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
min-height="36vh" @refresh="handleQuery"
|
||||
/>
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
min-height="36vh" @refresh="handleQuery" />
|
||||
|
||||
<block v-else>
|
||||
<view class="box-border 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" :audit-mode="calcAuditModeEnabled"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt-3 box-border pb-5 text-center text-xs text-gray-400">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<uh-notify-dialog />
|
||||
</template>
|
||||
<block v-else>
|
||||
<view class="box-border 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"
|
||||
:audit-mode="calcAuditModeEnabled" layout="image_bottom"/>
|
||||
</view>
|
||||
<view class="mt-3 box-border pb-5 text-center text-xs text-gray-400">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<uh-notify-dialog />
|
||||
</template>
|
||||
Reference in New Issue
Block a user