feat(home): 更新首页新闻栏目数据获取逻辑

- 将硬编码的新闻栏目数据替换为从CMS动态获取
- 添加了新闻栏目数据类型定义和解析函数
- 实现了新闻文章链接生成和标题解析功能
- 更新了页面参数传递,将navigationId改为categoryId
- 添加了加载状态和空数据状态的UI显示
- 集成了文章详情页的动态路由跳转功能
This commit is contained in:
2026-01-29 16:55:35 +08:00
parent 682e264a6f
commit 0f5f70522a
2 changed files with 101 additions and 43 deletions

View File

@@ -215,7 +215,7 @@ const {
async () => { async () => {
if (!isValidNavigationId.value) return null if (!isValidNavigationId.value) return null
return await pageCmsArticle({ return await pageCmsArticle({
navigationId: navigationId.value, categoryId: navigationId.value,
page: page.value, page: page.value,
limit: limit.value, limit: limit.value,
keywords: keywords.value || undefined keywords: keywords.value || undefined

View File

@@ -26,22 +26,30 @@
</div> </div>
<div class="mt-6 grid grid-cols-12 gap-6"> <div class="mt-6 grid grid-cols-12 gap-6">
<div v-for="c in columns" :key="c.title" class="col-span-12 lg:col-span-4"> <div v-for="c in columns" :key="c.key" class="col-span-12 lg:col-span-4">
<div class="panel"> <div class="panel">
<div class="column-head"> <div class="column-head">
<div class="column-title">{{ c.title }}</div> <div class="column-title">{{ c.title }}</div>
<a href="#" class="column-more" @click.prevent>更多 +</a> <NuxtLink v-if="c.moreTo" :to="c.moreTo" class="column-more">更多 +</NuxtLink>
<span v-else class="column-more opacity-60">更多 +</span>
</div> </div>
<div class="column-list"> <div class="column-list">
<a <template v-if="newsPending">
v-for="it in c.items" <div class="column-empty">加载中...</div>
:key="it" </template>
class="column-item" <template v-else-if="!c.items.length">
href="#" <div class="column-empty">暂无文章</div>
@click.prevent </template>
> <template v-else>
{{ it }} <NuxtLink
</a> v-for="(it, idx) in c.items"
:key="String(it.articleId ?? it.code ?? `${c.key}-${idx}`)"
class="column-item"
:to="resolveArticleLink(it, c.navId)"
>
{{ resolveArticleTitle(it) }}
</NuxtLink>
</template>
</div> </div>
</div> </div>
</div> </div>
@@ -83,6 +91,10 @@ import { usePageSeo } from '@/composables/usePageSeo'
import { getAdByCode } from '@/api/cms/cmsAd' import { getAdByCode } from '@/api/cms/cmsAd'
import type { CmsAd } from '@/api/cms/cmsAd/model' import type { CmsAd } from '@/api/cms/cmsAd/model'
import { COMPANY } from '@/config/company' import { COMPANY } from '@/config/company'
import { listCmsNavigation } from '@/api/cms/cmsNavigation'
import type { CmsNavigation } from '@/api/cms/cmsNavigation/model'
import { pageCmsArticle } from '@/api/cms/cmsArticle'
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
usePageSeo({ usePageSeo({
title: '首页', title: '首页',
@@ -163,38 +175,78 @@ const services = [
} }
] ]
const columns = [ const NEWS_PARENT_ID = 4548
{
title: '公司动态', type HomeNewsColumn = {
items: [ key: string
'桂乐淘官方网站上线公告', navId?: number
'业务范围与资质信息已更新', title: string
'合作咨询:欢迎留言,我们将尽快联系', moreTo: string | null
'合规提示:涉及许可项目以许可文件为准', items: CmsArticle[]
'更多动态敬请期待...' }
]
}, function resolveNavTitle(nav: CmsNavigation) {
{ return String(nav.title || nav.label || nav.code || '').trim()
title: '行业资讯', }
items: [
'生物基材料与绿色低碳趋势速览', function resolveArticleTitle(a: CmsArticle) {
'食品流通与预包装食品合规要点', return String(a.title || a.code || '未命名文章').trim()
'冷链与生鲜品质管理建议', }
'更多资讯敬请期待...',
'更多资讯敬请期待...' function resolveArticleLink(a: CmsArticle, navId?: number) {
] const articleId = typeof a.articleId === 'number' && Number.isFinite(a.articleId) ? a.articleId : NaN
}, const code = String(a.code || '').trim()
{ return {
title: '合规与公告', path: '/article-item',
items: [ query: {
'一般项目:凭营业执照依法自主开展经营活动', id: Number.isFinite(articleId) ? String(articleId) : undefined,
'许可项目:依法须经批准的项目,经批准后方可开展', code: !Number.isFinite(articleId) && code ? code : undefined,
'具体经营项目以相关部门批准文件或许可证件为准', navId: typeof navId === 'number' && Number.isFinite(navId) ? String(navId) : undefined
'更多公告敬请期待...', }
'更多公告敬请期待...'
]
} }
] }
const { data: newsRaw, pending: newsPending } = await useAsyncData<HomeNewsColumn[]>(
`home-news-${NEWS_PARENT_ID}`,
async () => {
const navs = await listCmsNavigation({ parentId: NEWS_PARENT_ID }).catch(() => [])
const top3 = [...navs]
.filter((it) => typeof it?.navigationId === 'number')
.sort((a, b) => (a.sortNumber ?? 0) - (b.sortNumber ?? 0))
.slice(0, 3)
const bundles = await Promise.all(
top3.map(async (nav) => {
const navId = nav.navigationId
const title = resolveNavTitle(nav) || (typeof navId === 'number' ? `栏目 ${navId}` : '栏目')
const page = typeof navId === 'number'
? await pageCmsArticle({ categoryId: navId, page: 1, limit: 10 }).catch(() => null)
: null
return {
key: String(navId ?? title),
navId,
title,
moreTo: typeof navId === 'number' ? `/article/${navId}` : null,
items: page?.list ?? []
} satisfies HomeNewsColumn
})
)
return bundles
}
)
const columns = computed<HomeNewsColumn[]>(() => {
if (newsRaw.value?.length) return newsRaw.value
if (!newsPending.value) return []
// Keep layout stable while SSR/client is loading.
return [
{ key: 'news-loading-1', navId: undefined, title: '加载中', moreTo: null, items: [] },
{ key: 'news-loading-2', navId: undefined, title: '加载中', moreTo: null, items: [] },
{ key: 'news-loading-3', navId: undefined, title: '加载中', moreTo: null, items: [] }
]
})
const compliance = [ const compliance = [
{ {
@@ -393,6 +445,12 @@ function scrollToCompany() {
padding: 10px 14px 14px; padding: 10px 14px 14px;
} }
.column-empty {
padding: 12px 0;
font-size: 13px;
color: rgba(0, 0, 0, 0.45);
}
.column-item { .column-item {
display: block; display: block;
padding: 8px 0; padding: 8px 0;