feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div class="text-center px-6">
|
||||
<p class="text-7xl font-bold text-gray-300 mb-4">404</p>
|
||||
<h1 class="text-2xl font-semibold text-gray-800 mb-3">页面未找到</h1>
|
||||
<p class="text-gray-500 mb-8">抱歉,您访问的页面不存在或已被移除。</p>
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="inline-flex items-center justify-center px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
返回首页
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 404 页面(使用 blank 布局避免 Header/Footer 干扰)
|
||||
*/
|
||||
definePageMeta({
|
||||
layout: 'blank'
|
||||
})
|
||||
|
||||
// 错误页禁止被搜索引擎收录
|
||||
useSeoMeta({
|
||||
robots: 'noindex, nofollow'
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="templatePage" v-if="templatePage" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 动态 CMS 页面路由(兼容入口)
|
||||
* CMS 导航主要使用 /page/:id, /article/:id, /product/:id 格式
|
||||
* 此路由作为兼容入口,处理旧式路径如 /about, /services 等
|
||||
*/
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug as string
|
||||
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, siteName, navigations, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const templatePage = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
templatePage.value = components?.Page || null
|
||||
|
||||
|
||||
// 尝试从导航中匹配 slug
|
||||
const matchedNav = computed(() => {
|
||||
const navs = navigations.value || []
|
||||
const findNav = (items: any[]): any => {
|
||||
for (const item of items) {
|
||||
if (item.path === `/${slug}` || item.code === slug) return item
|
||||
if (item.children?.length) {
|
||||
const found = findNav(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return findNav(navs)
|
||||
})
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: matchedNav.value?.title || slug,
|
||||
path: `/${slug}`
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 关于我们路由(独立页面类型,与 contact 同级)
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.About || null
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: '关于我们',
|
||||
path: '/about'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
|
||||
useBreadcrumbSeo([
|
||||
{ name: '首页', url: '/' },
|
||||
{ name: '关于我们', url: '/about' }
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" :key="route.fullPath" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 文章/新闻 路由(栏目列表 与 详情 二合一) /article/:id
|
||||
* 按模块名单数命名:列表(栏目)/article/{navigationId},详情 /article/{id}。
|
||||
* 靠「id 是否该模块已知栏目 navigationId」运行时判定。
|
||||
* 旧链接 /news/*、/newss/* 由 server/middleware/z-news-detail-redirect.ts 301 到 /article/*。
|
||||
*
|
||||
* 10626 租户专属:/article/4722(专业人才团队)走写死 TalentTeam,
|
||||
* 不依赖 cms-article 模块拉列表,避免 cms-article 写入受 token 限制而落空。
|
||||
*/
|
||||
import TalentTeam from '~/templates/template-07/pages/TalentTeam.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const id = String(route.params.id)
|
||||
let pageComponent = null as unknown
|
||||
if (id === '4722') {
|
||||
pageComponent = TalentTeam
|
||||
} else {
|
||||
;({ pageComponent } = await useModuleRoute('article'))
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 新闻列表路由(根) /article
|
||||
* 按模块名单数命名:列表根为 /article(旧规范为 /news)。
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, navigations, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.NewsList || null
|
||||
|
||||
|
||||
// 从导航中查找新闻栏目标题
|
||||
const newsNav = computed(() => {
|
||||
const navs = navigations.value || []
|
||||
return navs.find((n) => n.model === 'article')
|
||||
})
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: newsNav.value?.title || '新闻资讯',
|
||||
path: '/article'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" :key="route.fullPath" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 案例 路由(栏目列表 与 详情 二合一) /case/:id
|
||||
* 按模块名单数命名:列表(栏目)/case/{navigationId},详情 /case/{id}。
|
||||
* 靠「id 是否该模块已知栏目 navigationId」运行时判定。
|
||||
* 旧链接 /cases/* 由 server/middleware/z-news-detail-redirect.ts 301 到 /case/*。
|
||||
*/
|
||||
const { route, pageComponent } = await useModuleRoute('case')
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 案例列表路由(根) /case
|
||||
* 按模块名单数命名:列表根为 /case(旧规范为 /cases)。
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.CaseList || null
|
||||
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: '案例展示',
|
||||
path: '/case'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* /cases(旧复数路径)→ /case(新单数规范)客户端重定向
|
||||
*
|
||||
* 服务端已有 server/middleware/z-news-detail-redirect.ts 处理 301,
|
||||
* 本文件覆盖客户端导航场景(SPA 路由切换不经过服务端中间件)。
|
||||
*/
|
||||
await navigateTo('/case', { redirectCode: 301 })
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 联系我们路由
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.Contact || null
|
||||
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: '联系我们',
|
||||
path: '/contact'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteHome />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 首页路由
|
||||
* 使用 SiteHome 组件动态加载当前模板的首页
|
||||
*/
|
||||
const { siteInfo, siteName, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: siteName.value || '首页',
|
||||
description: siteInfo.value?.comments || undefined,
|
||||
keywords: siteInfo.value?.keywords || undefined,
|
||||
path: '/'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
|
||||
useOrganizationSeo(siteInfo.value)
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="templatePage" v-if="templatePage" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* CMS 页面路由
|
||||
* /page/:id → 既支持旧的导航节点 ID(数字),也支持新版「单页管理」的 path(slug)
|
||||
* 数字 id → 按 navigationId 取 cms_navigation + cms_design 内容
|
||||
* 非数字 slug → 按 path 取 cms_page 已发布单页(website-admin「单页管理」模块)
|
||||
*/
|
||||
const route = useRoute()
|
||||
const id = route.params.id as string
|
||||
const isNumeric = /^\d+$/.test(id)
|
||||
const slug = isNumeric ? undefined : id
|
||||
|
||||
// 规范路由收敛:单页的规范地址是顶层 /about、/contact(独立富组件),
|
||||
// /page/about、/page/contact 仅作兼容入口,统一 301 到规范地址,避免重复页与 SEO 分散。
|
||||
const CANONICAL_PAGE_ROUTE: Record<string, string> = { about: '/about', contact: '/contact' }
|
||||
if (slug && CANONICAL_PAGE_ROUTE[slug]) {
|
||||
// 规范地址收敛:用 Nuxt 标准的 navigateTo 301 重定向(createError 仅渲染错误页,不会真正跳转)
|
||||
throw navigateTo(CANONICAL_PAGE_ROUTE[slug], { redirectCode: 301 })
|
||||
}
|
||||
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, navigations, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const templatePage = shallowRef<Component | null>(null)
|
||||
const components = await loadTemplate()
|
||||
// 语义化 slug 优先映射到独立页面组件(about→关于我们、contact→联系我们),
|
||||
// 避免有专属组件的 slug 落入通用单页 Page 逻辑——CMS 未录入正文时会显示空状态。
|
||||
// (这些页面已有结构化组件,内容更丰富,无需依赖 CMS 单页正文。)
|
||||
const standaloneComp =
|
||||
slug === 'about' ? components?.About
|
||||
: slug === 'contact' ? components?.Contact
|
||||
: null
|
||||
templatePage.value = standaloneComp || components?.Page || null
|
||||
|
||||
// 从导航中查找当前页面信息(仅数字 id 时有效)
|
||||
const currentNav = computed(() => {
|
||||
if (isNumeric) {
|
||||
const navs = navigations.value || []
|
||||
const findNav = (items: any[]): any => {
|
||||
for (const item of items) {
|
||||
if (String(item.navigationId) === id) return item
|
||||
if (item.children?.length) {
|
||||
const found = findNav(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return findNav(navs)
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
// 非数字 slug:拉取单页 SEO 信息(标题/关键词/描述/头图)
|
||||
let seoData = ref<{
|
||||
title?: string
|
||||
keywords?: string
|
||||
description?: string
|
||||
photo?: string | null
|
||||
} | null>(null)
|
||||
if (!isNumeric && slug) {
|
||||
const { data } = await useFetch<{
|
||||
title?: string
|
||||
keywords?: string
|
||||
description?: string
|
||||
photo?: string | null
|
||||
}>('/api/page/detail', {
|
||||
query: { path: slug }
|
||||
})
|
||||
seoData = data
|
||||
}
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: seoData.value?.title || currentNav.value?.title || (slug === 'about' ? '关于我们' : slug === 'contact' ? '联系我们' : '页面'),
|
||||
path: route.fullPath,
|
||||
keywords: seoData.value?.keywords,
|
||||
description: seoData.value?.description,
|
||||
image: seoData.value?.photo || undefined
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="templatePage" v-if="templatePage" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 模板预览路由
|
||||
* 可通过 /preview?templateId=template-02 预览指定模板
|
||||
*/
|
||||
const route = useRoute()
|
||||
const previewTemplateId = route.query.templateId as string
|
||||
|
||||
const { loadTemplate, registerTemplate } = useTemplate()
|
||||
|
||||
// 注册所有模板
|
||||
const templates = await import('~/templates')
|
||||
templates.default.forEach(registerTemplate)
|
||||
|
||||
const templatePage = shallowRef<Component | null>(null)
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate(previewTemplateId)
|
||||
templatePage.value = components?.Home || null
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" :key="route.fullPath" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 产品 路由(栏目列表 与 详情 二合一) /product/:id
|
||||
* 按模块名单数命名:列表(栏目)/product/{navigationId},详情 /product/{id}。
|
||||
* 靠「id 是否该模块已知栏目 navigationId」运行时判定。
|
||||
* 旧链接 /products/* 由 server/middleware/z-news-detail-redirect.ts 301 到 /product/*。
|
||||
*/
|
||||
const { route, pageComponent } = await useModuleRoute('product')
|
||||
</script>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 产品列表路由(根) /product
|
||||
* 按模块名单数命名:列表根为 /product(旧规范为 /products)。
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
const { siteInfo, fetchSiteInfo } = useSite()
|
||||
|
||||
await fetchSiteInfo()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.ProductList || null
|
||||
|
||||
|
||||
usePageSeo(
|
||||
{
|
||||
title: '产品中心',
|
||||
path: '/product'
|
||||
},
|
||||
siteInfo.value
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<Component :is="pageComponent" v-if="pageComponent" />
|
||||
<SiteLoading v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 续费引导路由
|
||||
* 使用空白布局
|
||||
*/
|
||||
const { loadTemplate } = useTemplate()
|
||||
|
||||
const pageComponent = shallowRef<Component | null>(null)
|
||||
|
||||
|
||||
// 同步加载当前模板(SSR/客户端路由切换均直接 await,避免先闪 SiteLoading)
|
||||
const components = await loadTemplate()
|
||||
pageComponent.value = components?.Renewal || null
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user