feat(app): 添加多模板关于我们页面及相关路由和404页面

- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
2026-09-08 12:13:44 +08:00
commit 2b69686795
381 changed files with 59891 additions and 0 deletions
+227
View File
@@ -0,0 +1,227 @@
<template>
<div class="bg-white">
<!-- BannerCMS 内页横幅title=about & position=inner_page兜底静态默认图 -->
<section
class="t10-page-banner"
:style="`background-image: url('${aboutBannerImage}'); min-height: 320px;`"
>
<div class="t10-container t10-banner-inner py-16 lg:py-20">
<nav class="t10-breadcrumb mb-4" aria-label="面包屑">
<NuxtLink to="/" class="hover:text-white transition-colors">首页</NuxtLink>
<span class="mx-2">/</span>
<span class="text-white">{{ pageTitle }}</span>
</nav>
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-3">{{ pageTitle }}</h1>
<p class="text-lg text-white/80 tracking-[0.2em] uppercase">About Us</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 aboutSubNavs"
: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="t10-breadcrumb 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>
<!-- 企业定位 / 企业目标 -->
<section class="py-14 lg:py-20 bg-white">
<div class="t10-container">
<div class="grid md:grid-cols-2 gap-6">
<div class="bg-[var(--t10-bg-soft)] rounded-xl p-8 lg:p-10 border border-[var(--t10-border)] hover:border-[var(--t10-primary)] transition-colors">
<h2 class="text-xl font-bold text-[var(--t10-text-strong)] mb-4">企业定位</h2>
<p class="text-[var(--t10-text-secondary)] leading-relaxed">
{{ companyPosition || '深圳市创芯存储技术有限公司成立于2020年,主营DRAM与Flash存储芯片,是一家专注于为网络通讯、消费电子、AI、汽车电子、工业控制等高增长领域提供高性能和高可靠性存储芯片解决方案的创新型企业。' }}
</p>
</div>
<div class="bg-[var(--t10-bg-soft)] rounded-xl p-8 lg:p-10 border border-[var(--t10-border)] hover:border-[var(--t10-primary)] transition-colors">
<h2 class="text-xl font-bold text-[var(--t10-text-strong)] mb-4">企业目标</h2>
<p class="text-[var(--t10-text-secondary)] leading-relaxed">
{{ companyGoal || '致力于通过稳定的产品、有竞争力的价格、持续的供应和超越客户期望的服务,为客户创造满意度、忠诚度和口碑的价值。' }}
</p>
</div>
</div>
</div>
</section>
<!-- 使命 愿景 价值观 -->
<section
class="relative py-16 lg:py-24 bg-cover bg-center bg-fixed"
style="background-image: url('/images/template-10/about-mission-bg.jpg');"
>
<div class="absolute inset-0 bg-gradient-to-r from-white/95 via-white/90 to-white/70" />
<div class="t10-container relative z-10">
<div class="max-w-2xl">
<h2 class="text-2xl lg:text-3xl font-bold text-[var(--t10-text-strong)] mb-8">使命 愿景 价值观</h2>
<div class="space-y-5 text-[var(--t10-text-secondary)]">
<div class="flex gap-4">
<span class="font-semibold text-[var(--t10-text-strong)] w-12 flex-shrink-0">愿景</span>
<p>紧跟人工智能存储需求成长成为行业值得信赖的存储解决方案提供商</p>
</div>
<div class="flex gap-4">
<span class="font-semibold text-[var(--t10-text-strong)] w-12 flex-shrink-0">使命</span>
<p>成为客户最具价值合作伙伴持续为客户创造满意度忠诚度和口碑的价值</p>
</div>
<div class="flex gap-4">
<span class="font-semibold text-[var(--t10-text-strong)] w-12 flex-shrink-0">价值观</span>
<p>成长诚信积累更重要</p>
</div>
</div>
</div>
</div>
</section>
<!-- 致力于为客户创造 -->
<section class="py-14 lg:py-20 bg-[var(--t10-bg-soft)]">
<div class="t10-container text-center">
<h2 class="text-2xl lg:text-3xl font-bold text-[var(--t10-text-strong)] mb-6">
{{ siteName || 'C-MVP' }} 致力于为客户创造
</h2>
<NuxtLink to="/contact" class="t10-btn-primary mb-12">
满意度忠诚度口碑的价值
</NuxtLink>
<!-- 价值循环图 -->
<div class="relative max-w-3xl mx-auto mt-12">
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div
v-for="(item, i) in valueCycle"
:key="i"
class="bg-white rounded-xl p-6 border border-[var(--t10-border)] shadow-sm hover:border-[var(--t10-primary)] transition-colors"
>
<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-[var(--t10-primary-soft)] flex items-center justify-center text-[var(--t10-primary)]">
<svg v-if="i === 0" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
<svg v-else-if="i === 1" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg v-else-if="i === 2" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg v-else class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<h3 class="font-semibold text-[var(--t10-text-strong)]">{{ item.title }}</h3>
<p class="text-sm text-[var(--t10-muted-2)] mt-1">{{ item.desc }}</p>
</div>
</div>
<div class="hidden md:flex absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-24 h-24 rounded-full bg-[var(--t10-primary)] text-white items-center justify-center text-lg font-bold shadow-lg">
价值
</div>
</div>
</div>
</section>
<!-- 正文内容后台维护 -->
<section v-if="hasContent" class="py-14 lg:py-20 bg-white">
<div class="t10-container max-w-4xl">
<div class="bg-white rounded-xl border border-[var(--t10-border)] p-6 lg:p-10">
<RichText :content="pageData?.content" />
<PageAttachments
v-if="pageData?.attachments?.length"
:attachments="pageData.attachments"
/>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
/**
* 模板 10 - 关于我们页(创芯存储科技蓝风格)
*/
import type { PageDetail, CmsBanner, ApiEnvelope, PageResult } from '~/types'
import { ensureFullUrl } from '~/utils/image'
const { siteInfo, siteName, fetchSiteInfo } = useSite()
const route = useRoute()
await fetchSiteInfo()
// CMS 内页横幅:position=inner_page,匹配 title=about 的分组取背景图
const DEFAULT_ABOUT_BANNER = '/images/template-10/about-banner.jpg'
const { data: innerBannerRes } = await useFetch<ApiEnvelope<PageResult<CmsBanner>> | PageResult<CmsBanner>>('/api/banner', {
query: { position: 'inner_page' }
})
const aboutBannerImage = computed<string>(() => {
const res = innerBannerRes.value as (ApiEnvelope<PageResult<CmsBanner>> | PageResult<CmsBanner>) | null
if (!res) return DEFAULT_ABOUT_BANNER
const list = ((res as ApiEnvelope<PageResult<CmsBanner>>)?.data?.list
?? (res as PageResult<CmsBanner>)?.list
?? []) as CmsBanner[]
// 匹配 title=about(分组标题或明细标题)的首条有效图片
const aboutItem = list.find((b) => {
if (!b.image && !b.imageUrl && !b.pic) return false
return b.title?.toLowerCase() === 'about'
})
if (aboutItem) {
return ensureFullUrl(aboutItem.image || aboutItem.imageUrl || aboutItem.pic || '')
}
// 兜底:inner_page 下任意首条有图项(无 title 精确匹配时取第一张)
const anyItem = list.find((b) => !!(b.image || b.imageUrl || b.pic))
if (anyItem) {
return ensureFullUrl(anyItem.image || anyItem.imageUrl || anyItem.pic || '')
}
return DEFAULT_ABOUT_BANNER
})
const { data: pageData } = await useFetch<PageDetail>('/api/page/detail', {
key: 'page-about',
query: { path: 'about' }
})
const pageTitle = computed(() => pageData.value?.title?.trim() || '关于我们')
const hasContent = computed(() => Boolean(pageData.value?.hasContent && pageData.value?.content))
const companyPosition = computed(() => {
const cfg = siteInfo.value?.config as Record<string, unknown> | undefined
return cfg?.position as string | undefined
})
const companyGoal = computed(() => {
const cfg = siteInfo.value?.config as Record<string, unknown> | undefined
return cfg?.goal as string | undefined
})
const aboutSubNavs = [
{ title: '关于创芯', path: '/about' },
{ title: '新闻资讯', path: '/article' },
{ title: '联系我们', path: '/contact' }
]
const valueCycle = [
{ title: '稳定的产品', desc: '严苛测试验证' },
{ title: '超越客户期望的服务', desc: '快速响应支持' },
{ title: '有竞争力的价格', desc: '优化成本结构' },
{ title: '持续的供应', desc: '保障交付周期' }
]
usePageSeo(
{
title: pageTitle.value,
path: '/about',
keywords: pageData.value?.keywords || undefined,
description: pageData.value?.description || undefined
},
siteInfo.value
)
</script>