2b69686795
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引 - 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理 - 实现/article、/case、/product及/page动态路由兼容列表与详情展示 - 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置 - 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持 - 模板增强支持CMS单页内容加载及SEO信息动态设置 - 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { $fetch } from 'ofetch'
|
||
import { useStorage } from 'nitropack/runtime'
|
||
import type { ApiEnvelope, CmsSiteInfo } from '~/app/types'
|
||
|
||
type RuntimeConfig = ReturnType<typeof useRuntimeConfig>
|
||
|
||
/**
|
||
* 直接调用 CMS 获取站点信息
|
||
* CMS 接口:/cms/cms-website/getSiteInfo
|
||
*
|
||
* 供 SSR 中间件(tenant.ts)预取使用:结果写入 event.context.siteInfo,
|
||
* 再由 site-init.server.ts 注入 useState('site-info'),保证 SSR 首屏
|
||
* 渲染模板时 siteTemplateId 已可用(网站数据库优先于应用绑定/环境默认)。
|
||
*
|
||
* 与 /api/site/info handler 各自独立调用 CMS,互不依赖:
|
||
* handler 面向直接 API 调用方(返回原始响应),本函数面向 SSR 预取(返回解包数据)。
|
||
*
|
||
* @returns 解包后的 CmsSiteInfo;失败或 tenantId 为空返回 null(调用方回退默认模板)
|
||
*/
|
||
export async function getCmsSiteInfo(
|
||
tenantId: string,
|
||
config: RuntimeConfig,
|
||
query: Record<string, unknown> = {}
|
||
): Promise<CmsSiteInfo | null> {
|
||
if (!tenantId) return null
|
||
const modulesApiBase = config.public.modulesApiBase as string
|
||
try {
|
||
const res = await $fetch<ApiEnvelope<CmsSiteInfo> | CmsSiteInfo>(
|
||
'/cms/cms-website/getSiteInfo',
|
||
{
|
||
baseURL: modulesApiBase,
|
||
headers: { TenantId: tenantId },
|
||
query,
|
||
timeout: 6000,
|
||
retry: 1
|
||
}
|
||
)
|
||
const envelope = res as ApiEnvelope<CmsSiteInfo>
|
||
return envelope?.data ?? (res as CmsSiteInfo)
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 在线留言是否要求滑块验证码。
|
||
* 来源优先级(从高到低):
|
||
* 1) CMS 后台「网站设置」setting.requireCaptcha(权威,后台配置即生效)
|
||
* 2) 运行时配置 public.contactCaptcha(本地调试/部署级覆盖,NUXT_PUBLIC_CONTACT_CAPTCHA=true|false)
|
||
* 3) 默认 false(未配置则不显示滑块、不强制验证)
|
||
*
|
||
* 结果按租户缓存 5 分钟(仅缓存 CMS 解析出的 raw 值),避免每次提交都回源 CMS。
|
||
*/
|
||
const siteKv = useStorage('site-cache')
|
||
const CAPTCHA_CONFIG_TTL = 5 * 60 * 1000
|
||
|
||
type CaptchaReqCache = { raw: boolean | null; ts: number }
|
||
|
||
function resolveCaptchaRequired(raw: boolean | null, envOverride: boolean | null): boolean {
|
||
if (raw !== null) return raw
|
||
if (envOverride !== null) return envOverride
|
||
return false
|
||
}
|
||
|
||
export async function getContactCaptchaRequired(
|
||
tenantId: string,
|
||
config: RuntimeConfig
|
||
): Promise<boolean> {
|
||
const envOverride =
|
||
typeof config.public.contactCaptcha === 'boolean' ? config.public.contactCaptcha : null
|
||
|
||
const cacheKey = `captcha-req:${tenantId}`
|
||
if (tenantId) {
|
||
try {
|
||
const cached = await siteKv.getItem<CaptchaReqCache>(cacheKey)
|
||
if (cached && Date.now() - cached.ts < CAPTCHA_CONFIG_TTL) {
|
||
return resolveCaptchaRequired(cached.raw, envOverride)
|
||
}
|
||
} catch {
|
||
// 缓存读取失败则回源
|
||
}
|
||
}
|
||
|
||
try {
|
||
const info = await getCmsSiteInfo(tenantId, config)
|
||
const raw = (info?.setting?.requireCaptcha as boolean | undefined) ?? null
|
||
if (tenantId) {
|
||
await siteKv.setItem(cacheKey, { raw, ts: Date.now() })
|
||
}
|
||
return resolveCaptchaRequired(raw, envOverride)
|
||
} catch {
|
||
// CMS 不可达时,回退到环境变量覆盖(若也未设置则默认 false)
|
||
return envOverride ?? false
|
||
}
|
||
}
|