Files
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

156 lines
6.1 KiB
Vue
Raw Permalink 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 || article.createTime">
发布时间{{ formatDate(article.publishTime || article.createTime) }}
</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>