2b69686795
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
194 lines
7.0 KiB
Vue
194 lines
7.0 KiB
Vue
<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/about-banner.jpg'); min-height: 300px;"
|
|
>
|
|
<div class="t10-container t10-banner-inner">
|
|
<nav class="t10-breadcrumb mb-3">
|
|
<NuxtLink to="/">首页</NuxtLink>
|
|
<span class="mx-2">/</span>
|
|
<span class="text-white">{{ newsNav?.title || '企业资讯' }}</span>
|
|
</nav>
|
|
<h1 class="text-3xl sm:text-4xl font-bold text-white mb-2">{{ pageTitle }}</h1>
|
|
<p class="text-lg text-white/80 tracking-[0.2em] uppercase">News</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 子导航 -->
|
|
<div class="border-b border-[var(--t10-border)] bg-white">
|
|
<div class="t10-container">
|
|
<div class="flex flex-wrap items-center justify-between py-4 gap-4">
|
|
<nav class="flex flex-wrap gap-6 text-sm lg:pl-[460px]">
|
|
<NuxtLink
|
|
v-for="link in subNavs"
|
|
:key="link.path"
|
|
:to="link.path"
|
|
class="text-[var(--t10-text-secondary)] hover:text-[var(--t10-primary)] transition-colors"
|
|
:class="{ '!text-[var(--t10-primary)] font-medium': route.path === link.path }"
|
|
>
|
|
{{ link.title }}
|
|
</NuxtLink>
|
|
</nav>
|
|
<nav class="text-xs text-[var(--t10-muted-2)]">
|
|
<NuxtLink to="/" class="hover:text-[var(--t10-primary)]">首页</NuxtLink>
|
|
<span class="mx-2">/</span>
|
|
<span class="text-[var(--t10-text)]">{{ pageTitle }}</span>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="t10-container py-10">
|
|
<h2 class="t10-section-title t10-section-title-center">{{ pageTitle }}</h2>
|
|
|
|
<div v-if="pending" class="flex justify-center py-12">
|
|
<SiteLoading />
|
|
</div>
|
|
<SiteError v-else-if="error" message="获取资讯列表失败" />
|
|
|
|
<div v-else class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<NuxtLink
|
|
v-for="item in newsList"
|
|
: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 mb-3 min-h-[48px]">
|
|
{{ item.title }}
|
|
</h3>
|
|
<p class="text-sm text-[var(--t10-muted-2)] t10-clamp-2 mb-4 min-h-[40px]">
|
|
{{ summaryOf(item) }}
|
|
</p>
|
|
<span class="text-xs text-[var(--t10-muted)]">{{ formatDate(item.publishTime || item.createTime) }}</span>
|
|
</div>
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<!-- 空状态 -->
|
|
<div v-if="!pending && !error && newsList.length === 0" class="text-center py-20 text-[var(--t10-muted-2)]">
|
|
暂无资讯内容
|
|
</div>
|
|
|
|
<!-- 分页 -->
|
|
<div v-if="totalPages > 1" class="flex justify-center mt-12">
|
|
<nav class="flex items-center gap-1.5 flex-wrap justify-center">
|
|
<button
|
|
v-for="p in totalPages"
|
|
:key="p"
|
|
class="min-w-[36px] h-9 px-2 text-sm border rounded transition-colors"
|
|
:class="p === currentPage
|
|
? 'bg-[var(--t10-primary)] border-[var(--t10-primary)] text-white'
|
|
: 'bg-white border-[var(--t10-border)] text-[var(--t10-text)] hover:border-[var(--t10-primary)] hover:text-[var(--t10-primary)]'"
|
|
@click="currentPage = p"
|
|
>
|
|
{{ p }}
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
/**
|
|
* 模板 10 - 新闻资讯列表页(创芯存储科技蓝风格)
|
|
*/
|
|
import dayjs from 'dayjs'
|
|
import type { Article, PageResult, ApiEnvelope, CmsNavigation } from '~/types'
|
|
import { getCompressedImageUrl } from '~/utils/image'
|
|
import { stripHtml } from '~/utils'
|
|
|
|
const { allNavigations, fetchSiteInfo } = useSite()
|
|
const route = useRoute()
|
|
|
|
await fetchSiteInfo()
|
|
|
|
const navigationId = computed<number | undefined>(() => {
|
|
const qid = route.query.navId as string
|
|
if (qid) {
|
|
const n = Number(qid)
|
|
if (!Number.isNaN(n)) return n
|
|
}
|
|
const pid = route.params.id as string
|
|
if (pid) {
|
|
const n = Number(pid)
|
|
if (!Number.isNaN(n)) return n
|
|
}
|
|
return undefined
|
|
})
|
|
|
|
const currentPage = ref(1)
|
|
const limit = 9
|
|
|
|
const { data, pending, error } = await useFetch<
|
|
ApiEnvelope<PageResult<Article>> | PageResult<Article>
|
|
>('/api/article/list', {
|
|
key: `article-list-${navigationId.value ?? 'all'}-p${currentPage.value}`,
|
|
query: {
|
|
page: currentPage,
|
|
limit,
|
|
...(navigationId.value ? { navigationId: navigationId.value } : {})
|
|
},
|
|
watch: [currentPage, navigationId]
|
|
})
|
|
|
|
const newsList = computed<Article[]>(() => {
|
|
const envelope = data.value as ApiEnvelope<PageResult<Article>>
|
|
const direct = data.value as PageResult<Article>
|
|
return envelope?.data?.list || direct?.list || []
|
|
})
|
|
|
|
const totalCount = computed(() => {
|
|
const envelope = data.value as ApiEnvelope<PageResult<Article>>
|
|
const direct = data.value as PageResult<Article>
|
|
return envelope?.data?.count || envelope?.data?.total || direct?.count || direct?.total || 0
|
|
})
|
|
const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / limit)))
|
|
|
|
const newsNav = computed<CmsNavigation | undefined>(() => {
|
|
const navs = allNavigations.value || []
|
|
return navs.find((n) => n.navigationId === navigationId.value) || navs.find((n) => n.model === 'article')
|
|
})
|
|
|
|
const pageTitle = computed(() => newsNav.value?.title || '企业资讯')
|
|
|
|
const subNavs = [
|
|
{ title: '关于创芯', path: '/about' },
|
|
{ title: '新闻资讯', path: '/article' },
|
|
{ title: '联系我们', path: '/contact' }
|
|
]
|
|
|
|
const FALLBACK_IMAGE = '/images/template-10/news-default.jpg'
|
|
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
|
|
}
|
|
function onImgError(e: Event) {
|
|
const el = e.target as HTMLImageElement
|
|
if (el && !el.src.endsWith(FALLBACK_IMAGE)) el.src = FALLBACK_IMAGE
|
|
}
|
|
function summaryOf(item: Article): string {
|
|
const anyItem = item as Record<string, unknown>
|
|
const s = (anyItem.summary || anyItem.description || anyItem.content || '') as string
|
|
return stripHtml(s).slice(0, 100)
|
|
}
|
|
function formatDate(date?: unknown) {
|
|
if (!date) return ''
|
|
return dayjs(String(date)).format('YYYY-MM-DD')
|
|
}
|
|
</script>
|