Files
hjc-web/app/templates/template-07/pages/CaseList.vue
T
weicw-dshsa cfe0749aff feat(cms): 案例/文章列表与详情改读 publishTime
配合后端拆出独立的 publish_time 列(见 ADR 0016)。

- 案例列表/详情、文章列表/详情统一取 publishTime,不再回退 createTime
  (createTime 是系统记录时间,运营没设过,展示它等于显示错误值)
- 案例详情页标签由「项目时间」更正为「发布时间」,并补 dayjs 格式化
  (原先直出 2026-09-23 19:06:37 完整时间戳)
- server/utils/case.ts 废弃 projectTime,改为输出 publishTime
- CaseItem 类型同步

仅改 template-07:其余 9 套模板的 CaseDetail 仍读已废弃的 projectTime,
因 CaseItem 带索引签名不会报编译错误,只会在换模板时静默显示创建时间。
该技术债已记入 docs/adr/0016 的「后果」一节,换模板前须先补这 9 套。
2026-09-29 03:36:04 +08:00

136 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="min-h-screen py-16 bg-gray-50">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h1 class="text-3xl font-bold text-gray-900 mb-4">{{ pageTitle }}</h1>
<p class="text-gray-600 max-w-2xl mx-auto">展示我们的成功案例与行业经验</p>
</div>
<div v-if="pending" class="flex justify-center py-12">
<SiteLoading />
</div>
<SiteError v-else-if="error" message="获取案例列表失败" />
<div v-else-if="cases.length > 0" class="max-w-[1200px] mx-auto bg-white rounded-xl shadow-sm px-6 py-2">
<ul class="divide-y divide-gray-100">
<li v-for="item in cases" :key="item.id">
<NuxtLink :to="`/case/${item.id}`" class="block py-4 group">
<div class="flex items-baseline gap-4">
<span class="shrink-0 text-xs text-gray-400 tabular-nums">
{{ formatDate(item.publishTime) }}
</span>
<h2 class="flex-1 min-w-0 text-base text-gray-900 truncate group-hover:text-blue-600 transition-colors">
{{ item.title }}
</h2>
</div>
<div class="mt-1 pl-[5.5rem]">
<p v-if="stripHtml(item.summary)" class="text-sm text-gray-500 line-clamp-1">
{{ stripHtml(item.summary) }}
</p>
<div v-if="item.clientName" class="mt-1 text-xs text-gray-400">
客户:{{ item.clientName }}
</div>
</div>
</NuxtLink>
</li>
</ul>
</div>
<!-- 空状态 -->
<div v-else class="text-center py-20">
<p class="text-gray-500">暂无案例内容</p>
</div>
<!-- 分页 -->
<div v-if="totalPages > 1" class="flex justify-center mt-12">
<nav class="flex items-center gap-2">
<button
v-for="p in totalPages"
:key="p"
class="px-4 py-2 text-sm rounded-lg transition-colors"
:class="p === currentPage ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 hover:bg-blue-50'"
@click="currentPage = p"
>
{{ p }}
</button>
</nav>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import type { CaseItem, PageResult } from '~/types'
import { collectDescendantNavIds } from '~/utils/nav-tree'
/**
* 模板 1 - 案例列表页
*/
const route = useRoute()
const { allNavigations, fetchSiteInfo } = useSite()
// 确保导航数据已加载(用于栏目标题与分类判定)
await fetchSiteInfo()
// 列表(栏目)入口 /case/{navigationId} 时按分类过滤;/case 根目录展示全部案例
const navigationId = computed<number | undefined>(() => {
const id = route.params.id
return id ? Number(id) : undefined
})
// 聚合:父栏目访问时递归收集自身 + 所有后代栏目 navigationId,交给后端 IN 查询
const categoryIds = computed<string | undefined>(() => {
const ids = collectDescendantNavIds(navigationId.value, allNavigations.value || [])
return ids.length ? ids.join(',') : undefined
})
const currentPage = ref(1)
const limit = 12
const { data, pending, error } = await useFetch<PageResult<CaseItem>>('/api/case/list', {
key: `case-list-${categoryIds.value ?? navigationId.value ?? 'all'}-p${currentPage.value}`,
query: {
page: currentPage,
limit,
...(categoryIds.value
? { categoryIds: categoryIds.value }
: (navigationId.value ? { navigationId: navigationId.value } : {}))
},
watch: [currentPage, categoryIds, navigationId]
})
const cases = computed(() => data.value?.list || [])
const totalCount = computed(() => data.value?.count ?? 0)
const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / limit)))
// 切换栏目时回到第 1 页
watch([categoryIds, navigationId], () => {
currentPage.value = 1
})
// 栏目标题:命中导航取导航标题,否则用模块默认名
const pageTitle = computed(() => {
const id = navigationId.value
if (id == null) return '案例展示'
const navs = (allNavigations.value || []) as any[]
const find = (items: any[]): any => {
for (const it of items) {
if (it.navigationId === id) return it
if (it.children?.length) {
const f = find(it.children)
if (f) return f
}
}
return undefined
}
return find(navs)?.title || '案例展示'
})
function formatDate(date?: string) {
if (!date) return ''
return dayjs(date).format('YYYY-MM-DD')
}
</script>