systemStore
framework 内置的系统配置 Pinia store,composable 名是 useSystemStore(不是 useSystemInfoStore,源码位于 src/runtime/store/system.ts)。
承载后端 /system/info 接口返回的全站配置:SEO header、导航菜单、广告位、热词、上传接口、统计配置等。所有页面直接 useSystemStore() 读取,无需手动 fetch。
基础用法
<script setup lang="ts">
const systemStore = useSystemStore()
// 读取系统信息
const title = computed(() => systemStore.info.header?.site_title ?? '默认站名')
const ogImage = computed(() => systemStore.info.header?.og_image)
</script>
<template>
<h1>{{ title }}</h1>
<nav v-if="systemStore.isLoaded">
<NuxtLink v-for="m in systemStore.menus" :key="m.nav_id" :to="m.link || `/${m.code}`">
{{ m.name }}
</NuxtLink>
</nav>
</template>
自动初始化
framework 内置 system.server.ts plugin 在 SSR 期自动拉 /system/info 写入此 store,业务无需手动调用 fetch。失败时 system.client.ts 在 client hydrate 后兜底重拉。
整站无后端 API 的场景用 systemInfo: false 关闭拉取:
// nuxt.config.ts
swiftConfig: {
systemInfo: false, // 整站不拉
// systemInfo: { ignorePaths: ['/preview'] }, // 或路径级跳过
}
不存在「systemInfo 路径豁免」这类独立配置项;详见 framework 配置项 §systemInfo。
State
| 字段 | 类型 | 说明 |
|---|---|---|
info | Ref<SystemInfo> | 完整系统信息(含 header / menus / ads / 上传接口 / 统计配置 等) |
channelCode | Ref<string> | 渠道码 cookie 引用(持久化 30 天) |
shareCode | Ref<string> | 邀请码 cookie 引用(持久化 30 天) |
Getters(computed)
| 字段 | 类型 | 说明 |
|---|---|---|
isLoaded | Ref<boolean> | 系统信息是否已从服务端加载(false 时 info 为默认兜底值) |
menus | Ref<MenuItem[]> | 导航菜单列表(info.menus 派生,空数组兜底) |
hotKeywords | Ref<HotKeyword[]> | 热词推荐列表(info.keywords 派生) |
ads | Ref<AdSlots | null> | 广告位配置(info.ads 派生) |
imageDomain | Ref<string> | 图片 CDN 域名前缀(info.image_domain 派生) |
Actions
| 方法 | 签名 | 说明 |
|---|---|---|
setInfo(data) | (data: SystemInfo) => void | 写入完整系统信息并置 _loaded=true(应用初始化时由 system.server 自动调) |
setChannelCode(code) | (string) => void | 写入渠道码 cookie(已有值时不覆盖,保护首次归属) |
setShareCode(code) | (string) => void | 写入邀请码 cookie(同上,不覆盖) |
getChannelCode() | () => string | 读取渠道码(登录接口传参用,未设置时返回 '') |
getShareCode() | () => string | 读取邀请码(登录接口传参用) |
initCode() | () => void | 从当前路由 query 读取 channelCode / shareCode 写 cookie(仅当未设置时) |
reset() | () => void | 重置为默认兜底值(测试用) |
SystemInfo 主要类型
interface SystemInfo {
header?: SystemHeader // SEO meta(site_title / keywords / description / og_image)
site_url?: string // 官方主域名(业务用,如「最新地址」跳转链接;与 useSeo origin 决策无关,详见 framework#87)
img_key?: string // 图片加密 key(解密 .bnc 用)
websocket_key?: string
image_domain?: string // 图片 CDN 域名前缀
upload_image_url?: string // 图片上传接口
upload_file_url?: string // 文件上传接口
menus?: MenuItem[]
ads?: AdSlots
keywords?: HotKeyword[] // 热词推荐
domain_statistic?: DomainStatistic[]
global_statistic?: string // 全局统计脚本片段
service_link?: string // 客服链接
group_link?: string // 官方社群
permanent_url?: string // 永久域名落地页
tracker?: { appId?: string; channel?: string; encryptedConfig?: string; endpoint?: string; siteKey?: string } // 埋点通道下发配置:appId+encryptedConfig=集团 WebSDK 通道、endpoint(+siteKey)=自建通道(#228);两者皆缺则静默不启用
// ...更多字段(service_email / place_ad / contact_us / center_customer_* 等)
[key: string]: unknown // 后端可扩展任意字段
}
interface SystemHeader {
site_title?: string
keywords?: string // SEO 关键词(逗号分隔)
description?: string // SEO 描述
og_image?: string // OG / Twitter 分享封面
/** 旧后端字段,og_image 的长期兼容兜底(后端协议兼容、非 framework 废弃 API;新代码一律用 og_image) */
img?: string
}
完整字段列表见 src/runtime/store/system.ts 顶部 interface 定义。
渠道码 / 邀请码语义(重要)
setChannelCode / setShareCode 的"已有值不覆盖"语义仅针对同一会话内的多次访问(保护首次渠道归属)。跨用户场景由 userStore.logout() 清空 cookie 来重置 —— 切勿改成"总是覆盖",否则会破坏首次归属保护。
initCode() 安全策略:
- 种植时点在 client(fw#212):由
system.client.ts在 hydration 早期调用,SSR 期不写渠道 cookie——SSR 写入会被 ISR 缓存整组重放 Set-Cookie、绕过「已有值不覆盖」保护 - 防御性收紧:
?switch=1路径下(guest middleware 的"已登录切换账号"旁路)不接受 URL 注入的渠道码 / 邀请码,阻断反常构造链接(如/login?switch=1&channelCode=111)对未绑定用户的渠道码种植
与 /system/info 接口约定
后端实际返回示例(按 framework 内 frameworkInternalRequest 解密后的 data 字段):
{
"header": {
"site_title": "Swift Web 示例",
"keywords": "示例,关键词",
"description": "SEO 描述",
"og_image": "https://cdn.example.com/og-default.jpg"
},
"site_url": "https://example.com",
"img_key": "aes_image_decrypt_key",
"image_domain": "https://img.example.com",
"menus": [{ "name": "首页", "nav_id": "1", "code": "home", "link": "", "extend": "" }],
"upload_image_url": "https://upload.example.com/image",
"ads": { /* ... */ }
}
SSR 拉取不带渠道参数
system.server.ts 拉 /system/info 时不带 channel_code / share_code(避免 ISR 路径上首访者参数烤进缓存广播)。后端按"默认渠道"返回;业务侧如需渠道差异化 systemInfo,应在 client hydrate 后由 auth.client.ts 完成 channel cookie 写入后显式触发二次拉取(此能力暂未内置,待业务有真实需求再实现)。
详见 渲染模式快速指南 §渠道差异化。
相关
userStore— 用户身份 + token + 设备 IDuseSeo— SEO meta 注入器(不从systemStore.info.header隐式兜底,SEO 字段全显式;header 字段仍供业务读取,与 SEO 解耦)- systemInfo ModuleOptions — 拉取开关 + 自定义端点
注意事项
info初始是兜底默认值,不是空 —— 未从服务端加载时是createDefaultSystemInfo()的结构完整的默认对象。判断「数据到了没」要用isLoadedgetter,别靠info是否为空(永远不为空),也别靠某个字段有没有值。- 渠道码 / 邀请码「首次写入即锁定」,后续不覆盖 ——
setChannelCode/setShareCode在已有值时故意不覆盖,保证渠道归属算首次来源;initCode()从当前路由 query 读取写入,cookie 持久 30 天。想改现有值需要先清 cookie,直接再调 set 是无效的。 - SEO 不从本 store 隐式兜底 ——
useSeo()的 title / description 一律取页面显式传入;info.header里的 SEO 相关字段要用,得由页面自己读出来再传进useSeo()(数据流:后端 → store → 页面 → useSeo)。framework 绝不绕开useSeo()改 head。 - ISR 页面上它是「全站共享配置」而非用户态 —— 站点配置对所有访客一致,进 ISR 缓存是安全的;但渠道 / 邀请码是随访客而变的,别把它们渲染进 ISR 页面的 HTML(会把首访者的渠道码烙给所有人)。