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,123 @@
|
||||
import { $fetch } from 'ofetch'
|
||||
import type { ApiEnvelope } from '~/app/types'
|
||||
|
||||
type RuntimeConfig = ReturnType<typeof useRuntimeConfig>
|
||||
|
||||
/** app_template 表行(仅取解析目录名所需字段) */
|
||||
interface AppTemplateRow {
|
||||
id?: number
|
||||
/** 模板标识,形如 'template-08',与前端 app/templates/<code>/ 目录一一对应 */
|
||||
code?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
interface AppTemplatePage {
|
||||
list?: AppTemplateRow[]
|
||||
count?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板主键 → 模板标识(code)映射
|
||||
*
|
||||
* 【为什么需要这层映射】
|
||||
* 上游 getSiteInfo 只返回 cms_website.template_id(app_template 自增主键),
|
||||
* 而前端模板目录名与 app_template.code 对应。历史上主键出现过跳号(id=8 缺失),
|
||||
* 导致「按主键补零推导目录名」整体错位一位(gxhrtc.shoplnk.cn 事故)。
|
||||
*
|
||||
* 因此 SSR 侧统一在此反查模板表,把权威的 code 回填进 siteInfo.templateCode,
|
||||
* 前端 resolveTemplateKey() 优先按 code 解析,主键仅兜底。
|
||||
*
|
||||
* 待上游 getSiteInfo 直接返回 templateCode 后,本模块会自动降级为「上游未返回时的兜底」,
|
||||
* 无需改动前端解析逻辑(tenant.ts 中已判断 templateCode 为空才查表)。
|
||||
*/
|
||||
|
||||
/** 缓存有效期:模板表极少变动,10 分钟足够,且避免每次 SSR 都打上游 */
|
||||
const CACHE_TTL = 10 * 60 * 1000
|
||||
|
||||
let cache: { map: Map<number, string>; expireAt: number } | null = null
|
||||
/** 并发去重:同一时刻多个 SSR 请求只发起一次上游查询 */
|
||||
let inflight: Promise<Map<number, string>> | null = null
|
||||
|
||||
/**
|
||||
* 拉取全量模板表并构建 id → code 映射
|
||||
* 后端: GET {appApiBase}/api/app/template/page(无需鉴权)
|
||||
*/
|
||||
async function fetchTemplateMap(config: RuntimeConfig): Promise<Map<number, string>> {
|
||||
const appApiBase = config.public.appApiBase as string
|
||||
const map = new Map<number, string>()
|
||||
|
||||
const res = await $fetch<ApiEnvelope<AppTemplatePage> | AppTemplatePage>(
|
||||
'/api/app/template/page',
|
||||
{
|
||||
baseURL: appApiBase,
|
||||
query: { page: 1, limit: 200 },
|
||||
timeout: 5000,
|
||||
retry: 1
|
||||
}
|
||||
)
|
||||
|
||||
const data = (res as ApiEnvelope<AppTemplatePage>)?.data ?? (res as AppTemplatePage)
|
||||
for (const row of data?.list || []) {
|
||||
if (row?.id && typeof row.code === 'string' && row.code.trim()) {
|
||||
map.set(Number(row.id), row.code.trim().toLowerCase())
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 id → code 映射(带缓存 + 并发去重 + 失败降级)
|
||||
* 上游异常时返回上一次的有效缓存(若有),否则返回空映射,由调用方回退主键推导。
|
||||
*/
|
||||
async function getTemplateMap(config: RuntimeConfig): Promise<Map<number, string>> {
|
||||
const now = Date.now()
|
||||
if (cache && cache.expireAt > now) return cache.map
|
||||
if (inflight) return inflight
|
||||
|
||||
inflight = fetchTemplateMap(config)
|
||||
.then((map) => {
|
||||
// 空结果不覆盖有效缓存,避免上游偶发返回空表导致全站回退主键推导
|
||||
if (map.size > 0) {
|
||||
cache = { map, expireAt: Date.now() + CACHE_TTL }
|
||||
}
|
||||
return cache?.map ?? map
|
||||
})
|
||||
.catch(() => cache?.map ?? new Map<number, string>())
|
||||
.finally(() => {
|
||||
inflight = null
|
||||
})
|
||||
|
||||
return inflight
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模板主键反查模板标识(code)
|
||||
*
|
||||
* @returns 形如 'template-08';查不到返回 ''(调用方回退主键推导)
|
||||
*/
|
||||
export async function getTemplateCodeById(
|
||||
templateId: number | string | null | undefined,
|
||||
config: RuntimeConfig
|
||||
): Promise<string> {
|
||||
if (templateId === null || templateId === undefined || templateId === '') return ''
|
||||
|
||||
// 已经是目录名形式(上游直接返回 code),无需查表
|
||||
if (typeof templateId === 'string' && templateId.startsWith('template-')) {
|
||||
return templateId.trim().toLowerCase()
|
||||
}
|
||||
|
||||
const id = Number(templateId)
|
||||
if (!id || Number.isNaN(id) || id <= 0) return ''
|
||||
|
||||
try {
|
||||
const map = await getTemplateMap(config)
|
||||
return map.get(id) || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空缓存(后台改动模板表后可通过重启或调用此方法立即生效,主要供测试使用) */
|
||||
export function clearTemplateMapCache(): void {
|
||||
cache = null
|
||||
}
|
||||
Reference in New Issue
Block a user