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
@@ -0,0 +1,159 @@
<template>
<footer class="bg-[var(--t10-footer-bg)] text-[var(--t10-footer-text)]">
<div class="t10-container py-12 lg:py-16">
<!-- 上排Logo 联系信息 + 链接列 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-10 lg:gap-8">
<!-- 公司信息 -->
<div class="lg:col-span-2">
<SiteBrand
link-class="inline-flex items-center gap-2 mb-5"
:logo="siteLogo"
:icon="siteIcon"
:name="siteName"
logo-class="h-[44px] w-auto max-w-[220px] object-contain brightness-0 invert"
icon-class="h-8 w-auto object-contain brightness-0 invert"
name-class="text-xl font-bold text-white truncate max-w-[220px]"
>
<template #fallback-icon>
<span class="text-xl font-bold text-white">C-MVP</span>
</template>
</SiteBrand>
<ul class="space-y-3 text-sm">
<li v-if="phone" class="flex items-start gap-3">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
<a :href="`tel:${phone}`" class="hover:text-white transition-colors">{{ phone }}</a>
</li>
<li v-if="email" class="flex items-start gap-3">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<a :href="`mailto:${email}`" class="hover:text-white transition-colors break-all">{{ email }}</a>
</li>
<li v-if="siteUrl" class="flex items-start gap-3">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
<a :href="siteUrl" target="_blank" rel="noopener" class="hover:text-white transition-colors break-all">{{ siteUrl }}</a>
</li>
<li v-if="address" class="flex items-start gap-3">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>{{ address }}</span>
</li>
</ul>
</div>
<!-- 链接列 -->
<div v-for="col in linkColumns" :key="col.title">
<h3 class="text-white font-semibold mb-5">{{ col.title }}</h3>
<ul class="space-y-2.5 text-sm">
<li v-for="link in col.links" :key="link.navigationId || link.path">
<NuxtLink
:to="getNavLink(link)"
:target="link.target === '_blank' ? '_blank' : undefined"
class="hover:text-white transition-colors"
>
{{ link.title }}
</NuxtLink>
</li>
</ul>
</div>
</div>
<!-- 分隔线 -->
<div class="mt-12 pt-8 border-t border-white/15">
<div class="flex flex-col md:flex-row items-center justify-between gap-4 text-xs text-center md:text-left">
<span>{{ copyright || `Copyright © ${new Date().getFullYear()} ${siteName || ''} 版权所有` }}</span>
<div class="flex flex-wrap items-center justify-center gap-x-4 gap-y-2">
<a
v-if="icpNo"
href="https://beian.miit.gov.cn"
target="_blank"
rel="nofollow"
class="hover:text-white transition-colors"
>{{ icpNo }}</a>
<a
v-if="siteInfo?.policeNo"
:href="policeUrl"
target="_blank"
rel="nofollow"
class="hover:text-white transition-colors"
>{{ siteInfo.policeNo }}</a>
<a
rel="nofollow"
href="https://site.websoft.top"
target="_blank"
class="hover:text-white transition-colors"
>Powered by ·企业官网</a>
</div>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
/**
* 模板 10 - Footer 组件(创芯存储科技蓝风格)
* 科技蓝底 + 公司信息 + 四列链接 + 备案版权
*/
import type { CmsNavigation } from '~/types'
const {
siteInfo, siteName, siteLogo, siteIcon,
phone, email, address, copyright, icpNo,
navigations, allNavigations, fetchSiteInfo
} = useSite()
await fetchSiteInfo()
const siteUrl = computed(() => {
const url = siteInfo.value?.domain || ''
if (!url) return ''
return url.startsWith('http') ? url : `https://${url}`
})
const policeUrl = computed(() => {
const no = String(siteInfo.value?.policeNo || '')
const code = no.replace(/\D/g, '')
return code
? `https://beian.mps.gov.cn/#/query/webSearch?code=${code}`
: 'https://beian.mps.gov.cn/'
})
/** 按模型或标题智能分组链接 */
const linkColumns = computed(() => {
const navs = navigations.value || []
const all = allNavigations.value || []
const cols: { title: string, links: CmsNavigation[] }[] = []
const findByModel = (model: string) => all.filter((n) => n.model === model && (n.parentId || 0) === 0).slice(0, 6)
const findByKeyword = (...keywords: string[]) => {
return navs.filter((n) => keywords.some((k) => n.title.includes(k))).slice(0, 6)
}
const products = findByModel('product')
if (products.length) cols.push({ title: products[0]?.title || '产品中心', links: products[0]?.children?.slice(0, 6) || [products[0]].filter(Boolean) })
const cases = findByModel('case')
if (cases.length) cols.push({ title: cases[0]?.title || '行业解决方案', links: cases[0]?.children?.slice(0, 6) || [cases[0]].filter(Boolean) })
const aboutLinks = findByKeyword('关于', '资讯', '联系', '加入')
if (aboutLinks.length) cols.push({ title: '关于我们', links: aboutLinks })
const partnerLinks = findByKeyword('合作', '伙伴', '服务')
if (partnerLinks.length) cols.push({ title: '合作伙伴', links: partnerLinks })
// 兜底:用底部导航补足列数
if (cols.length < 3) {
const bottom = (navs || []).filter((n) => n.model !== 'index').slice(0, 6)
if (bottom.length) cols.push({ title: '快速链接', links: bottom })
}
return cols
})
</script>