Files
hjc-web/app/components/RecommendArticlesSection.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

138 lines
5.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>
<!--
推荐资讯区块(共享组件)
数据:useRecommend('article') —— 读取后台勾选 recommend=1 的文章(全站跨栏目精选)
显隐:由父级(首页)通过后台「首页区块」开关 v-if 控制;本组件只负责渲染,
无数据时显示空状态占位(符合「允许空」语义)。
复用:其他模板(如 template-07)只需 import 本组件并传入各自主题色即可。
-->
<section class="py-16 lg:py-20 bg-white">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="text-2xl sm:text-3xl font-bold mb-4" :style="{ color: titleColor }" data-reveal>{{ title }}</h2>
<p v-if="subtitle" class="max-w-2xl mx-auto" :style="{ color: subtitleColor }" data-reveal data-reveal-delay="100">{{ subtitle }}</p>
</div>
<div v-if="items.length > 0" :class="['grid gap-6', gridClass]">
<article
v-for="(item, index) in items"
:key="item.id || item.articleId"
class="bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow anim-card"
data-reveal
:data-reveal-delay="index * 100"
>
<NuxtLink :to="`/article/${item.id || item.articleId}`">
<div class="aspect-video bg-gray-100 flex items-center justify-center anim-card-media overflow-hidden">
<img
v-if="item.image || item.cover || item.photo"
:src="articleImage(item)"
:alt="item.title"
class="w-full h-full object-cover"
loading="lazy"
@error="onImgError($event)"
>
<span v-else class="text-gray-400">暂无图片</span>
</div>
</NuxtLink>
<div class="p-5">
<div class="text-xs text-gray-500 mb-2">
{{ formatDate(item.publishTime) }}
<span v-if="item.categoryName" class="ml-2" :style="{ color: accentColor }">{{ item.categoryName }}</span>
</div>
<h3 class="text-lg font-semibold mb-2 line-clamp-2 hover:text-blue-600 transition-colors" :style="{ color: titleColor }">
<NuxtLink :to="`/article/${item.id || item.articleId}`">{{ item.title }}</NuxtLink>
</h3>
<p class="text-sm line-clamp-2" :style="{ color: subtitleColor }">{{ stripHtml(item.summary) }}</p>
</div>
</article>
</div>
<!-- 空状态(区块开关开启但无推荐内容时显示) -->
<div v-else class="text-center text-gray-400 py-12">
<p>暂无推荐内容</p>
</div>
<div v-if="moreLink" class="text-center mt-10">
<NuxtLink
:to="moreLink"
class="inline-flex items-center font-semibold hover:opacity-80 transition-opacity"
:style="{ color: accentColor }"
>
{{ moreText }}
<svg class="w-4 h-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</NuxtLink>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { computed } from 'vue'
import { useRecommend } from '~/composables/useRecommend'
import { useFileUrl } from '~/composables/useFileUrl'
import { stripHtml } from '~/utils'
import type { Article } from '~/types'
const props = withDefaults(
defineProps<{
title?: string
subtitle?: string
/** 主强调色:分类标签、链接、查看更多 */
accentColor?: string
/** 标题文字色 */
titleColor?: string
/** 副标题/摘要文字色 */
subtitleColor?: string
/** 展示列数(2/3/4) */
columns?: number
/** 展示条数 */
limit?: number
/** 无推荐文章时是否回退最新文章(默认 false:未勾推荐则显示空占位) */
fallbackToLatest?: boolean
/** 查看更多链接 */
moreLink?: string
/** 查看更多文案 */
moreText?: string
}>(),
{
title: '推荐资讯',
subtitle: '了解企业最新资讯与行业动态',
accentColor: '#3b82f6',
titleColor: '#111827',
subtitleColor: '#4b5563',
columns: 3,
limit: 3,
fallbackToLatest: false,
moreLink: '',
moreText: '查看更多'
}
)
const { fileUrl } = useFileUrl()
const { items } = useRecommend<Article>('article', { limit: props.limit, fallbackToLatest: props.fallbackToLatest })
const gridClass = computed(() => {
if (props.columns >= 4) return 'sm:grid-cols-2 lg:grid-cols-4'
if (props.columns === 2) return 'md:grid-cols-2'
return 'md:grid-cols-3'
})
function formatDate(date?: string) {
if (!date) return ''
return dayjs(date).format('YYYY-MM-DD')
}
function articleImage(item: Article) {
const raw = item.image || item.cover || item.photo || ''
return raw ? fileUrl(raw) : ''
}
function onImgError(e: Event) {
const el = e.target as HTMLImageElement
if (el) el.style.display = 'none'
}
</script>