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(會把首訪者的渠道碼烙給所有人)。