import type { AppProduct, TenantContext } from '~/types' /** * 获取当前应用产品信息(AppProduct) * * SSR:优先从 event.context.tenant.appProduct 读取(中间件已查询) * 客户端:从 SSR 注入的 useState 读取;若为空则调用 /api/app/info 补查 */ export function useApp() { const appInfo = useState('app-info', () => null) const loading = useState('app-loading', () => false) const error = useState('app-error', () => null) // SSR 时直接从中间件识别结果注入,避免额外请求 if (import.meta.server && !appInfo.value) { const event = useRequestEvent() const ctx = event?.context?.tenant as TenantContext | undefined if (ctx?.appProduct) { appInfo.value = ctx.appProduct } } /** 主动获取应用信息(客户端兜底 / 手动刷新) */ async function fetchAppInfo() { if (appInfo.value) return appInfo.value loading.value = true error.value = null try { const res = await $fetch('/api/app/info') appInfo.value = res return res } catch (e: any) { error.value = e?.message || '获取应用信息失败' return null } finally { loading.value = false } } /** 应用名称 */ const appName = computed(() => appInfo.value?.productName || '') /** 应用编码 */ const appCode = computed(() => appInfo.value?.productCode || '') /** 绑定域名 */ const appDomain = computed(() => appInfo.value?.domain || '') /** 应用 Logo */ const appLogo = computed(() => appInfo.value?.logo || '') /** 应用图标(站点小图标,用于 Header/Footer Logo 回退) */ const appIcon = computed(() => appInfo.value?.icon || '') /** * 关联模板目录名(template-XX):【code 优先,主键兜底】 * templateCode 与前端模板目录一一对应;templateId 主键存在跳号错位风险,仅兜底。 * 为空时返回 '' 由上层回退默认模板。 */ const appTemplateId = computed(() => resolveTemplateKey(appInfo.value?.templateCode, appInfo.value?.templateId) ) /** 是否已过期 */ const appExpired = computed(() => { const t = appInfo.value?.expirationTime if (!t) return false return new Date(t).getTime() < Date.now() }) return { appInfo, loading, error, fetchAppInfo, appName, appCode, appDomain, appLogo, appIcon, appTemplateId, appExpired } }