function getOrigin(url: string) { try { return new URL(url).origin } catch { return '' } } /** * 解析布尔型环境变量:'true'→true,'false'→false,其余→null(交由后台配置决定)。 */ function toBool(v?: string): boolean | null { if (v === 'true') return true if (v === 'false') return false return null } const tenantId = process.env.NUXT_PUBLIC_TENANT_ID || '' const appId = process.env.NUXT_PUBLIC_APP_ID || 'website' const templateId = process.env.NUXT_PUBLIC_TEMPLATE_ID || 'template-01' // 强制模板 ID:一旦设置则拥有最高优先级,覆盖应用/站点绑定(主要用于本地调试) const forceTemplateId = process.env.NUXT_PUBLIC_FORCE_TEMPLATE_ID || '' // 站点名称覆盖:设置后优先于后端 AppProduct / CMS 返回值(多用于本地调试或强制品牌名) const siteName = process.env.NUXT_PUBLIC_SITE_NAME || '' const serverApiBase = process.env.NUXT_PUBLIC_SERVER_API_BASE || 'https://server.websoft.top/api' const modulesApiBase = process.env.NUXT_PUBLIC_MODULES_API_BASE || 'https://hjc-api.websoft.top/api' const appApiBase = process.env.NUXT_PUBLIC_APP_API_BASE || 'https://websopy-api.websoft.top' const fileServerBase = process.env.NUXT_PUBLIC_FILE_SERVER_BASE || getOrigin(serverApiBase) || 'https://server.websoft.top' const baseDomain = process.env.NUXT_PUBLIC_BASE_DOMAIN || 'shoplnk.cn' // 多主域清单:子域名租户解析支持的主域(逗号分隔)。 // 默认 shoplnk.cn,sitelink.cn,wsdns.cn;baseDomain 仅作兼容保留(取第一个)。 const baseDomains = ( process.env.NUXT_PUBLIC_BASE_DOMAINS || 'shoplnk.cn,sitelink.cn,wsdns.cn' ).split(',').map(s => s.trim()).filter(Boolean) // 子域名前缀白名单:仅这些前缀 + 数字租户号 + 主域 会被识别为平台子域名租户 const subdomainPrefixes = ( process.env.NUXT_PUBLIC_SUBDOMAIN_PREFIXES || 'site,shop,store,mp,app,oa' ).split(',').map(s => s.trim()).filter(Boolean) // 联系电话覆盖:当后端 phone 被脱敏时,作为兜底显示 const phone = process.env.NUXT_PUBLIC_PHONE || '' const WATCH_IGNORED = [ '**/.git/**', '**/node_modules/**', '**/.nuxt/**', '**/.output/**', '**/dist/**', '**/coverage/**', '**/.cache/**', '**/.workbuddy/**' ] // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, modules: ['@nuxtjs/tailwindcss', '@ant-design-vue/nuxt'], app: { head: { htmlAttrs: { lang: 'zh-CN' }, titleTemplate: '%s', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: 'robots', content: 'index,follow' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, // 提前与内容/接口源站建立连接,降低首字节与图片加载延迟 { rel: 'preconnect', href: 'https://server.websoft.top', crossorigin: '' }, { rel: 'preconnect', href: 'https://hjc-api.websoft.top', crossorigin: '' }, { rel: 'dns-prefetch', href: 'https://server.websoft.top' }, { rel: 'dns-prefetch', href: 'https://hjc-api.websoft.top' }, // 思源黑体(Noto Sans SC,SIL 开源许可,版权无忧)网页字体,全站统一字体 { rel: 'preconnect', href: 'https://fonts.googleapis.cn' }, { rel: 'preconnect', href: 'https://fonts.gstatic.cn', crossorigin: '' }, { rel: 'stylesheet', href: 'https://fonts.googleapis.cn/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap' } ], // 首屏绘制前注入 .js 类,配合 animations.css 的 `.js [data-reveal]` // 规则实现「无 JS 时内容可见、有 JS 时先隐藏再由滚动揭示」,杜绝 SSR 闪烁 script: [ { innerHTML: "document.documentElement.classList.add('js')", tagPosition: 'head' } ] } }, tailwindcss: { cssPath: '~/assets/css/tailwind.css', configPath: 'tailwind.config.cjs' }, experimental: { appManifest: false }, // 全局 SSR,保证 SEO routeRules: { '/renewal': { ssr: true } }, runtimeConfig: { // 仅服务端可见 subscriptionCacheTtl: Number(process.env.NUXT_SUBSCRIPTION_CACHE_TTL || 60), public: { tenantId, appId, templateId, forceTemplateId, siteName, serverApiBase, modulesApiBase, appApiBase, fileServerBase, baseDomain, baseDomains, subdomainPrefixes, phone, // 微信二维码覆盖:设置后优先于 CMS 返回的 wxQrcode(用于本地调试或强制指定二维码) // ⚠️ 不要写硬编码默认图!必须保持空字符串,让 Footer 从 siteInfo.config.wxQrcode / siteInfo.qrCode 读取后台上传 wxQrcode: process.env.NUXT_PUBLIC_WX_QRCODE || '', // 在线留言滑块验证码开关覆盖(可选): // 默认 null → 由后台「网站设置」setting.requireCaptcha 决定; // 设 true/false 可在不改后台的情况下强制开/关(仅部署级覆盖,后台显式配置优先)。 contactCaptcha: toBool(process.env.NUXT_PUBLIC_CONTACT_CAPTCHA) } }, css: ['~/assets/css/tailwind.css', '~/assets/css/animations.css'], vite: { server: { // 本地开发允许用自定义域名(如 cz-hro.shoplnk.cn)访问, // 否则 Vite 会在请求进入中间件前直接 403(Blocked request. This host is not allowed) // 以 "." 开头的条目表示允许该域名及其所有子域。 // 注意:这里只是放宽 Vite 的 host 校验,真正「是否解析为合法租户」仍由 // tenant 中间件按白名单前缀 + 主域清单判断;非白名单子域不会命中租户。 allowedHosts: ['.shoplnk.cn', '.sitelink.cn', '.wsdns.cn', 'localhost', '127.0.0.1'], watch: { ignored: WATCH_IGNORED } }, optimizeDeps: { include: ['dayjs'] } }, nitro: { watchOptions: { ignored: WATCH_IGNORED } }, watchers: { chokidar: { ignored: WATCH_IGNORED } } })