Files
hjc-web/app/templates/template-08/pages/Home.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

227 lines
9.9 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>
<HeroSection />
<!-- 业务板块区(多栏目卡片,对齐 gxhrtc 公告类官网风格) -->
<section v-if="blocks.length" class="py-12 lg:py-16 bg-[var(--t8-bg-soft)]">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid lg:grid-cols-2 gap-6 lg:gap-8">
<div
v-for="block in blocks"
:key="block.navigationId"
class="flex flex-col"
>
<!-- 标题栏(金棕) -->
<div class="flex items-center justify-between bg-[var(--t8-primary)] text-white px-5 py-3">
<h3 class="font-bold text-lg truncate">{{ block.title }}</h3>
<NuxtLink :to="getNavLink(block)" class="text-sm hover:underline whitespace-nowrap ml-3">查看更多 &gt;</NuxtLink>
</div>
<!-- 列表 -->
<ul class="flex-1 bg-white border border-[var(--t8-border)] border-t-0 divide-y divide-[var(--t8-border)]">
<li
v-for="(item, idx) in (blockArticles[block.navigationId] || [])"
:key="`item-${item.articleId || item.productId || item.id || idx}`"
>
<NuxtLink
:to="detailLink(block.model, item)"
class="flex items-start justify-between gap-3 px-5 py-3 hover:bg-[var(--t8-primary-soft)] transition-colors group"
>
<span class="text-[var(--t8-text)] group-hover:text-[var(--t8-primary)] transition-colors truncate min-w-0">{{ item.title }}</span>
<span class="text-xs text-[var(--t8-muted)] flex-shrink-0 pt-0.5">{{ formatDate(item.publishTime) }}</span>
</NuxtLink>
</li>
<li
v-if="!(blockArticles[block.navigationId] || []).length"
:key="`empty-${block.navigationId}`"
class="px-5 py-8 text-center text-sm text-[var(--t8-text-secondary)]"
>
暂无内容
</li>
</ul>
</div>
</div>
</div>
</section>
<!-- 关于我们 -->
<AboutSection
title-color="var(--t8-text)"
accent-color="var(--t8-primary)"
summary-color="var(--t8-text-secondary)"
link-color="var(--t8-primary)"
container-class="container mx-auto px-4 py-12 sm:px-6 lg:px-8 grid lg:grid-cols-2 gap-12 items-center"
image-radius="rounded-2xl"
/>
<!-- 新闻中心(读取 news-center 父栏目下的子栏目,带封面图) -->
<section v-if="newsCenterBlocks.length" class="py-12 lg:py-16 bg-white" style="display: none;">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="mb-6">
<h2 class="text-2xl sm:text-3xl font-bold text-[var(--t8-text)]">{{ newsCenterTitle }}</h2>
<div class="w-16 h-1 bg-[var(--t8-primary)] rounded-full mt-3"></div>
</div>
<div class="grid lg:grid-cols-2 gap-6 lg:gap-8">
<div
v-for="block in newsCenterBlocks"
:key="block.navigationId"
class="flex flex-col"
>
<!-- 标题栏(金棕) -->
<div class="flex items-center justify-between bg-[var(--t8-primary)] text-white px-5 py-3">
<h3 class="font-bold text-lg truncate">{{ block.title }}</h3>
<NuxtLink :to="getNavLink(block)" class="text-sm hover:underline whitespace-nowrap ml-3">查看更多 &gt;</NuxtLink>
</div>
<!-- 带图列表 -->
<ul class="flex-1 bg-white border border-[var(--t8-border)] border-t-0 divide-y divide-[var(--t8-border)]">
<li
v-for="(item, idx) in (newsCenterArticles[block.navigationId] || [])"
:key="`item-${item.articleId || item.id || idx}`"
>
<NuxtLink
:to="detailLink(block.model, item)"
class="flex items-center gap-4 px-5 py-3 hover:bg-[var(--t8-primary-soft)] transition-colors group"
>
<img
v-if="articleImage(item)"
:src="articleImage(item)"
:alt="item.title"
class="w-16 h-16 object-cover rounded flex-shrink-0"
loading="lazy"
>
<div v-else class="w-16 h-16 bg-[var(--t8-primary-soft)] rounded flex-shrink-0 flex items-center justify-center text-[var(--t8-muted)] text-xs">无图</div>
<div class="min-w-0 flex-1">
<p class="text-[var(--t8-text)] group-hover:text-[var(--t8-primary)] transition-colors truncate">{{ item.title }}</p>
<span class="text-xs text-[var(--t8-muted)]">{{ formatDate(item.publishTime) }}</span>
</div>
</NuxtLink>
</li>
<li
v-if="!(newsCenterArticles[block.navigationId] || []).length"
:key="`empty-${block.navigationId}`"
class="px-5 py-8 text-center text-sm text-[var(--t8-text-secondary)]"
>
暂无内容
</li>
</ul>
</div>
</div>
</div>
</section>
<!-- 联系 CTA -->
<section class="py-16 lg:py-20 text-white text-center" :style="{ backgroundImage: 'var(--t8-navbar-bg)' }" style="display: none">
<div class="container mx-auto px-4">
<h2 class="text-2xl sm:text-3xl font-bold mb-4">准备好开始合作了吗?</h2>
<p class="mb-8 text-white/90 max-w-xl mx-auto">立即联系我们,获取专属企业官网解决方案,开启数字化增长之旅。</p>
<button
type="button"
class="inline-flex items-center justify-center px-8 py-3 bg-white text-[var(--t8-primary-dark)] font-semibold rounded-lg hover:bg-[var(--t8-primary-soft)] transition-colors"
@click="openConsult()"
>
立即咨询
</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
/**
* 模板 8 - 首页(金棕企业风,对齐 gxhrtc 招标公告类官网)
* - HeroSection 全宽轮播
* - 多业务板块:读取父级栏目 gg-info 下的子栏目,每子栏目取前 6 条
* - 关于我们 + 新闻中心(news-center 父栏目下的子栏目,带封面图)+ 底部 CTA
*/
import dayjs from 'dayjs'
import HeroSection from '../components/HeroSection.vue'
import type { CmsNavigation, Article, Product, CaseItem, PageResult, ApiEnvelope } from '~/types'
import { collectDescendantNavIds } from '~/utils/nav-tree'
import { getNavLink } from '~/utils'
const { siteInfo, siteName, navigations, allNavigations, fetchSiteInfo } = useSite()
const { openConsult } = useConsult()
await fetchSiteInfo()
/** 按标识递归查找父栏目(优先 code,其次 path 含标识,再次 title 匹配) */
function findParentNavByKey(key: string, items: CmsNavigation[] = []): CmsNavigation | undefined {
for (const nav of items) {
if (nav.code === key || nav.path?.includes(key) || nav.title === key) {
return nav
}
if (nav.children?.length) {
const found = findParentNavByKey(key, nav.children)
if (found) return found
}
}
return undefined
}
/** 业务栏目板块:取 gg-info 父栏目下的子栏目 */
const blocks = computed<CmsNavigation[]>(() => {
const parent = findParentNavByKey('gg-info', allNavigations.value || [])
return (parent?.children || []).filter((n) => !n.hide && !n.deleted)
})
/** 新闻中心板块:取 news-center 父栏目下的子栏目 */
const newsCenterParent = computed(() => findParentNavByKey('news-center', allNavigations.value || []))
const newsCenterTitle = computed(() => newsCenterParent.value?.title || '新闻动态')
const newsCenterBlocks = computed<CmsNavigation[]>(() => {
return (newsCenterParent.value?.children || []).filter((n) => !n.hide && !n.deleted)
})
/** 每个板块的文章列表 */
const blockArticles = ref<Record<number, (Article | Product | CaseItem)[]>>({})
await Promise.all(
blocks.value.map(async (nav) => {
const ids = collectDescendantNavIds(nav.navigationId, allNavigations.value || [])
const api = nav.model === 'product' ? '/api/product/list' : nav.model === 'case' ? '/api/case/list' : '/api/article/list'
try {
const res = await $fetch<ApiEnvelope<PageResult<any>> | PageResult<any>>(api, {
query: { categoryIds: ids.length ? ids.join(',') : undefined, page: 1, limit: 6 }
})
const list = ((res as ApiEnvelope<PageResult<any>>)?.data?.list ?? (res as PageResult<any>)?.list) as (Article | Product | CaseItem)[] || []
blockArticles.value[nav.navigationId] = list
} catch {
blockArticles.value[nav.navigationId] = []
}
})
)
/** 新闻中心板块的文章列表(带封面图,每子栏目取前 6 条) */
const newsCenterArticles = ref<Record<number, Article[]>>({})
await Promise.all(
newsCenterBlocks.value.map(async (nav) => {
const ids = collectDescendantNavIds(nav.navigationId, allNavigations.value || [])
try {
const res = await $fetch<ApiEnvelope<PageResult<any>> | PageResult<any>>('/api/article/list', {
query: { categoryIds: ids.length ? ids.join(',') : undefined, page: 1, limit: 6 }
})
const list = ((res as ApiEnvelope<PageResult<any>>)?.data?.list ?? (res as PageResult<any>)?.list) as Article[] || []
newsCenterArticles.value[nav.navigationId] = list
} catch {
newsCenterArticles.value[nav.navigationId] = []
}
})
)
function detailLink(model: string | undefined, item: any): string {
const id = item.id || item.articleId || item.productId
if (model === 'product') return `/product/${id}`
if (model === 'case') return `/case/${id}`
return `/article/${id}`
}
/** 文章封面图(image > cover > photo,兼容 CMS 不同返回字段) */
function articleImage(item: any): string {
return item?.image || item?.cover || item?.photo || ''
}
function formatDate(date?: string) {
if (!date) return ''
return dayjs(date).format('YYYY-MM-DD')
}
</script>