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,97 @@
|
||||
import type { TenantContext } from '~/app/types'
|
||||
|
||||
type RuntimeConfig = ReturnType<typeof useRuntimeConfig>
|
||||
|
||||
/**
|
||||
* 从 Host 解析租户上下文
|
||||
*
|
||||
* 策略:
|
||||
* 1. 如果 host 匹配 `{prefix}-{tenantId}.{baseDomain}` 模式(前缀走白名单、
|
||||
* 主域走配置清单),从子域名提取 tenantId
|
||||
* - 白名单前缀:site / shop / store / mp / app / oa(由 subdomainPrefixes 配置)
|
||||
* - 主域清单:shoplnk.cn / sitelink.cn / wsdns.cn(由 baseDomains 配置)
|
||||
* 2. 否则返回环境变量默认值(后续可由域名绑定表查询覆盖)
|
||||
*
|
||||
* 注意:子域名解析仅为「兜底」来源(优先级低于 appDomain 绑定表与
|
||||
* app_product.domain),因此后台绑定的合法自定义域名天然优先,不会被子域名覆盖。
|
||||
*/
|
||||
export function resolveTenantFromHost(
|
||||
host: string,
|
||||
baseDomains: string[] | string,
|
||||
prefixes: string[] | string,
|
||||
defaultTenantId: string,
|
||||
defaultAppId: string,
|
||||
defaultTemplateId: string
|
||||
): TenantContext {
|
||||
const hostname = host.split(':')[0]
|
||||
|
||||
// 防御性类型保障:runtimeConfig 序列化可能将数组退化为字符串
|
||||
const safeDomains = Array.isArray(baseDomains)
|
||||
? baseDomains
|
||||
: String(baseDomains || '').split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
const safePrefixes = Array.isArray(prefixes)
|
||||
? prefixes
|
||||
: String(prefixes || '').split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
|
||||
const domains = (safeDomains && safeDomains.length
|
||||
? safeDomains
|
||||
: ['shoplnk.cn']
|
||||
).map(d => escapeRegex(d.trim())).filter(Boolean).join('|')
|
||||
|
||||
const prefixList = (safePrefixes && safePrefixes.length
|
||||
? safePrefixes
|
||||
: ['site']
|
||||
).map(p => escapeRegex(p.trim())).filter(Boolean).join('|')
|
||||
|
||||
// 匹配 {site|shop|store|mp|app|oa}-{tenantId}.{baseDomain}
|
||||
const subdomainPattern = new RegExp(
|
||||
`^(?:${prefixList})-(\\d+)\\.(?:${domains})$`,
|
||||
'i'
|
||||
)
|
||||
const match = hostname.match(subdomainPattern)
|
||||
|
||||
if (match && match[1]) {
|
||||
return {
|
||||
tenantId: match[1],
|
||||
appId: defaultAppId,
|
||||
templateId: defaultTemplateId,
|
||||
source: 'subdomain',
|
||||
host
|
||||
}
|
||||
}
|
||||
|
||||
// 默认使用环境变量
|
||||
return {
|
||||
tenantId: defaultTenantId,
|
||||
appId: defaultAppId,
|
||||
templateId: defaultTemplateId,
|
||||
source: 'env',
|
||||
host
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 event.context 获取租户上下文
|
||||
* 如果不存在则回退到运行时配置默认值
|
||||
*/
|
||||
export function getTenantFromContext(
|
||||
event: Parameters<Parameters<typeof defineEventHandler>[0]>[0],
|
||||
config: RuntimeConfig
|
||||
): TenantContext {
|
||||
const ctx = event.context?.tenant as TenantContext | undefined
|
||||
|
||||
if (ctx?.tenantId) {
|
||||
return ctx
|
||||
}
|
||||
|
||||
return {
|
||||
tenantId: config.public.tenantId as string,
|
||||
appId: config.public.appId as string,
|
||||
templateId: config.public.templateId as string,
|
||||
source: 'env'
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegex(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
Reference in New Issue
Block a user