Files
gxwebsoft 2b69686795 feat(app): 添加多模板关于我们页面及相关路由和404页面
- 新增404页面,优化未找到页面体验,避免被搜索引擎索引
- 增加文件代理接口,隐藏真实文件服务器地址,支持文件请求代理
- 实现/article、/case、/product及/page动态路由兼容列表与详情展示
- 添加动态CMS页面兼容入口处理旧式路径,统一路由与SEO设置
- 新增模板1、模板7、模板2、模板3关于我们页面,实现多模板支持
- 模板增强支持CMS单页内容加载及SEO信息动态设置
- 配置环境变量及Git忽略文件规则辅助开发和构建环境管理
2026-09-08 12:13:44 +08:00

96 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}