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

欄位類型說明
infoRef<SystemInfo>完整系統資訊(含 header / menus / ads / 上傳介面 / 統計設定 等)
channelCodeRef<string>渠道碼 cookie 引用(持久化 30 天)
shareCodeRef<string>邀請碼 cookie 引用(持久化 30 天)

Getters(computed)

欄位類型說明
isLoadedRef<boolean>系統資訊是否已從伺服器端載入(false 時 info 為預設兜底值)
menusRef<MenuItem[]>導航選單列表(info.menus 派生,空陣列兜底)
hotKeywordsRef<HotKeyword[]>熱詞推薦列表(info.keywords 派生)
adsRef<AdSlots | null>廣告位配置(info.ads 派生)
imageDomainRef<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 + 裝置 ID
  • useSeo — SEO meta 注入器(不從 systemStore.info.header 隱式兜底,SEO 欄位全顯式;header 欄位仍供業務讀取,與 SEO 解耦)
  • systemInfo ModuleOptions — 拉取開關 + 自訂端點

注意事項

  • info 初始是兜底預設值,不是空 —— 未從伺服端載入時是 createDefaultSystemInfo() 的結構完整的預設物件。判斷「資料到了沒」要用 isLoaded getter,別靠 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(會把首訪者的渠道碼烙給所有人)。