Files
hjc-web/app/templates/template-10/pages/NewsDetail.vue
T
weicw-dshsa d013e64500 fix(cms): 其余 9 套模板一并改读 publishTime,消除静默失效隐患
上一提交只改了 template-07(当时 cms_website.template_id = 7),其余模板
仍读已废弃的 projectTime、文章页仍写 publishTime || createTime。
由于 CaseItem 带索引签名,这些引用不报编译错误,只在换模板时静默显示
系统创建时间——而 template_id 是数据库里可随时改的数字,改它不需发版。

本次补全全部模板:
- template-01..10 的 CaseDetail 改读 publishTime,标签更正为「发布时间」
- 其中 8 套原先没有 formatDate,一并补 dayjs 导入与格式化
  (此前直出 2026-09-23 19:06:37 完整时间戳)
- 文章侧 38 处 publishTime || createTime 统一为 publishTime
- useModuleRoute 的 SEO datePublished 不再回退 createTime
  (结构化数据应与页面展示的发布时间一致)

保留 server/api/sitemap.xml.ts 的 lastmod 回退链——那是抓取提示不是页面
展示,回退到创建时间是合理的保守行为。

现在官网上已不存在读取 createTime 的内容页。ADR 0016 中记录的该技术债
已标记为消除。
2026-09-29 03:50:58 +08:00

156 lines
6.0 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 bg-[var(--t10-bg-soft)] pb-16">
<!-- Banner -->
<section
class="t10-page-banner"
style="background-image: url('/images/template-10/news-banner.jpg'); min-height: 280px;"
>
<div class="t10-container t10-banner-inner">
<nav class="t10-breadcrumb mb-3">
<NuxtLink to="/">首页</NuxtLink>
<span class="mx-2">/</span>
<NuxtLink :to="newsNav?.path || '/article'">{{ newsNav?.title || '企业资讯' }}</NuxtLink>
<span class="mx-2">/</span>
<span class="text-white">{{ article?.title || '资讯详情' }}</span>
</nav>
<h1 class="text-3xl sm:text-4xl font-bold text-white">{{ newsNav?.title || '企业资讯' }}</h1>
</div>
</section>
<div class="t10-container py-10">
<div v-if="pending" class="flex justify-center py-12">
<SiteLoading />
</div>
<SiteError v-else-if="error" message="获取资讯详情失败" />
<template v-else-if="article">
<div class="max-w-4xl mx-auto bg-white rounded-xl border border-[var(--t10-border)] overflow-hidden">
<img
v-if="coverImage"
:src="coverImage"
:alt="article.title"
class="w-full h-[240px] lg:h-[360px] object-cover"
@error="onImgError"
>
<div class="p-6 lg:p-10">
<h1 class="text-2xl lg:text-3xl font-bold text-[var(--t10-text-strong)] mb-4">
{{ article.title }}
</h1>
<div class="flex flex-wrap items-center gap-4 text-sm text-[var(--t10-muted-2)] mb-8 pb-6 border-b border-[var(--t10-border)]">
<span v-if="article.publishTime">
发布时间:{{ formatDate(article.publishTime) }}
</span>
<span v-if="article.author">作者:{{ article.author }}</span>
<span v-if="article.source">来源:{{ article.source }}</span>
</div>
<div class="prose max-w-none">
<RichText :content="article.content" />
</div>
<PageAttachments
v-if="article.attachments?.length"
:attachments="article.attachments"
class="mt-10"
/>
</div>
</div>
<!-- 相关资讯 -->
<div v-if="relatedNews.length" class="mt-12">
<h2 class="t10-section-title t10-section-title-center">相关资讯</h2>
<div class="grid md:grid-cols-3 gap-6">
<NuxtLink
v-for="item in relatedNews"
:key="item.id ?? item.articleId"
:to="`/article/${item.id ?? item.articleId}`"
class="group bg-white rounded-xl border border-[var(--t10-border)] overflow-hidden hover:shadow-md hover:border-[var(--t10-primary)] transition-all"
>
<div class="aspect-[16/10] overflow-hidden bg-[var(--t10-bg-gray)]">
<img
:src="itemImage(item)"
:alt="item.title"
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
loading="lazy"
@error="onImgError"
>
</div>
<div class="p-5">
<h3 class="font-medium text-[var(--t10-text)] t10-clamp-2 group-hover:text-[var(--t10-primary)] transition-colors">
{{ item.title }}
</h3>
</div>
</NuxtLink>
</div>
</div>
</template>
<div v-else class="text-center py-20 text-[var(--t10-muted-2)]">
资讯不存在或已下架
</div>
</div>
</div>
</template>
<script setup lang="ts">
/**
* 模板 10 - 新闻资讯详情页(创芯存储科技蓝风格)
*/
import dayjs from 'dayjs'
import type { Article, PageResult, ApiEnvelope } from '~/types'
import { getCompressedImageUrl } from '~/utils/image'
const { allNavigations, fetchSiteInfo } = useSite()
const route = useRoute()
await fetchSiteInfo()
const articleId = computed(() => route.params.id as string)
const { data: articleRes, pending, error } = await useFetch<{ data?: Article }>('/api/article/detail', {
key: `article-detail-${articleId.value}`,
query: { id: articleId.value }
})
const article = computed<Article | undefined>(() => articleRes.value?.data)
const newsNav = computed(() => {
const navs = allNavigations.value || []
return navs.find((n) => n.model === 'article')
})
const FALLBACK_IMAGE = '/images/template-10/news-default.jpg'
const coverImage = computed(() => {
const a = article.value as Record<string, unknown> | undefined
const raw = (a?.cover || a?.image || a?.photo || (Array.isArray(a?.images) ? (a.images as string[])[0] : '')) as string
return raw ? getCompressedImageUrl(raw, { width: 1200 }) : FALLBACK_IMAGE
})
function onImgError(e: Event) {
const el = e.target as HTMLImageElement
if (el && !el.src.endsWith(FALLBACK_IMAGE)) el.src = FALLBACK_IMAGE
}
function itemImage(item: Article): string {
const anyItem = item as Record<string, unknown>
const raw = (anyItem.image || anyItem.cover || anyItem.photo
|| (Array.isArray(anyItem.images) ? (anyItem.images as string[])[0] : '')) as string
return raw ? getCompressedImageUrl(raw, { width: 600 }) : FALLBACK_IMAGE
}
const { data: relatedRes } = await useFetch<ApiEnvelope<PageResult<Article>> | PageResult<Article>>('/api/article/list', {
key: `article-related-${articleId.value}`,
query: { page: 1, limit: 3 },
server: false
})
const relatedNews = computed<Article[]>(() => {
const d = relatedRes.value as (ApiEnvelope<PageResult<Article>> | PageResult<Article>) | null
if (!d) return []
const list = ((d as ApiEnvelope<PageResult<Article>>)?.data?.list
?? (d as PageResult<Article>)?.list
?? []) as Article[]
return list.filter((a) => String((a as Record<string, unknown>).id ?? (a as Record<string, unknown>).articleId) !== articleId.value).slice(0, 3)
})
function formatDate(date?: unknown) {
if (!date) return ''
return dayjs(String(date)).format('YYYY-MM-DD HH:mm')
}
</script>