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
+123
View File
@@ -0,0 +1,123 @@
import { useHead, useRequestURL, useSeoMeta, useRuntimeConfig } from '#app'
import type { CmsSiteInfo } from '~/types'
type SeoInput = {
title: string
description?: string
keywords?: string
path?: string
image?: string
type?: 'website' | 'article' | 'product'
siteName?: string
publishedTime?: string
modifiedTime?: string
}
function getSiteOrigin() {
if (import.meta.client) return window.location.origin
try {
return useRequestURL().origin
} catch {
return ''
}
}
/**
* 页面 SEO 设置
* 设置 TDK、Open Graph、Twitter Card、Canonical URL
*/
export function usePageSeo(input: SeoInput, site?: CmsSiteInfo | null) {
const origin = getSiteOrigin()
const url = input.path && origin ? new URL(input.path, origin).toString() : undefined
const overrideSiteName = (useRuntimeConfig().public.siteName as string) || ''
const siteName = input.siteName || overrideSiteName || site?.websiteName || ''
const description = input.description || site?.comments || site?.content || ''
const keywords = input.keywords || site?.keywords || ''
const image = input.image || site?.websiteLogo || ''
const fullTitle = siteName ? `${input.title} - ${siteName}` : input.title
useSeoMeta({
title: fullTitle,
description,
keywords,
ogTitle: input.title,
ogDescription: description,
ogType: input.type || 'website',
ogSiteName: siteName,
...(image ? { ogImage: image } : {}),
...(url ? { ogUrl: url } : {}),
twitterCard: 'summary_large_image',
twitterTitle: input.title,
twitterDescription: description,
...(image ? { twitterImage: image } : {}),
...(input.publishedTime ? { articlePublishedTime: input.publishedTime } : {}),
...(input.modifiedTime ? { articleModifiedTime: input.modifiedTime } : {})
})
if (url) {
useHead({
link: [{ rel: 'canonical', href: url }]
})
}
}
/**
* 注入 JSON-LD 结构化数据
*/
export function useJsonLd(data: Record<string, unknown> | Record<string, unknown>[]) {
const items = Array.isArray(data) ? data : [data]
useHead({
script: items.map((item) => ({
type: 'application/ld+json',
innerHTML: JSON.stringify(item)
}))
})
}
/**
* 注入企业组织结构化数据
*/
export function useOrganizationSeo(site?: CmsSiteInfo | null) {
if (!site) return
const origin = getSiteOrigin()
const rawPhone = site.phone || ''
const phone = rawPhone && !rawPhone.includes('*')
? rawPhone
: (useRuntimeConfig().public.phone as string) || site.config?.tel || rawPhone || ''
const email = site.config?.email || site.email || ''
const address = site.address || site.config?.address || ''
const logo = site.websiteLogo || ''
const overrideSiteName = (useRuntimeConfig().public.siteName as string) || ''
useJsonLd({
'@context': 'https://schema.org',
'@type': 'Organization',
name: overrideSiteName || site.websiteName || '',
url: origin,
...(logo ? { logo: new URL(logo, origin).toString() } : {}),
...(phone ? { telephone: phone } : {}),
...(email ? { email: email } : {}),
...(address ? { address: { '@type': 'PostalAddress', address } } : {})
})
}
/**
* 注入面包屑结构化数据
*/
export function useBreadcrumbSeo(items: { name: string; url: string }[]) {
const origin = getSiteOrigin()
useJsonLd({
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: new URL(item.url, origin).toString()
}))
})
}