Files
hjc-web/app/components/RecommendArticlesSection.vue
T
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +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 || item.createTime) }}
<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>