WIP-332 第二件 · themePrimary 全链路消费补丁交接包

出品:扣子一号(coze1,闸门 admin-console/src)
时间:2026-09-15 夜班
范围声明:本席只落地 admin-console/src(a 端表单);下方 c 端(miniprogram/)与后端(backend/)补丁不在本席闸门,按夜班分派表交「我」(统筹代推人)代推/派发,不越闸门直改。

一、读源核对结果(事实,已逐文件核验 main)

themePrimary 是治理快照 config-governance-v1 的第 6 个冻结字段(SNAPSHOT_FIELDS)。全链路现状:

环节文件现状结论
治理写入/校验backend/app/routers/miniapp_config_governance.py_validate_snapshot 要求 themePrimary 为 6 位 HEX;_sync_brand 把含 themePrimary 的完整 6 字段快照 json.dumps 写入 site_config('brand') JSONB✅ 已写
直写校验backend/app/services/site_config_registry.py L114 _validate_brand仅校验「brand 必须是对象」,字段全放行✅ a 端直写 6 字段不被 422
c 端读出口backend/app/routers/miniapp_site.py L71-85 get_site_config顶层只展平 5 字段(brandName/slogan/tibetanText/logoUrl/heroBgUrl),无 themePrimary;响应也不返回嵌套 brand 对象❌ 断点 BLK-332-3(后端域)
a 端表单admin-console/src/services/brand.ts、pages/BrandConfig.tsx328 初版仅 5 字段✅ 本件已补第 6 字段(见 §二)
c 端品牌层miniprogram/miniprogram/utils/brand.tsBrandAssets 仅 5 字段,ensureBrandLoaded 只读顶层 5 字段❌ 缺字段(c 端域)
c 端类型miniprogram/miniprogram/typings/api.d.ts L571 SiteConfig仅 5 品牌字段❌ 缺类型
c 端消费home 顶栏 hero 品牌区 / brand-foot / 分享卡 / 关于全站强调色走 app.wxss --color-primary:#9B2915 常量,无运行时主题色❌ 未消费

关键结论:themePrimary 数据已在库里(brand JSONB),但 c 端无任何读出口——GET /config/site 顶层不展平、也不回嵌套 brand。因此 c 端补丁(§三)必须以后端补展平(§四)为前置,二者需同批代推,单独上 c 端补丁读不到值(会安静走兜底转山红,视觉无变化但功能未生效)。

兜底色:c 端 app.wxss --color-primary = #9B2915(转山红),全链路缺省统一用它。


二、本席已落地(admin-console/src,已随本件提交)


三、c 端补丁(miniprogram/,交统筹代推人派发 · 建议归小冈/前端)

前置:§四 后端展平已上线同环境。以下 diff 基于 main 当前行号给出,代推时以实际为准。

3.1 miniprogram/typings/api.d.ts(SiteConfig 加字段)

 export interface SiteConfig {
   brandName: string
   slogan: string
   tibetanText: string
   logoUrl: string
   heroBgUrl: string
+  /** 主题色 #RRGGBB(治理快照第 6 字段;后端展平,缺省由 c 端兜底转山红) */
+  themePrimary?: string
   points: PointConfig[]

3.2 miniprogram/utils/brand.ts(品牌层扩字段 + 严格 HEX 归一)

 export interface BrandAssets {
   brandName: string
   slogan: string
   tibetanText: string
   logoUrl: string
   heroBgUrl: string
+  themePrimary: string
 }

+/** c 端主题色兜底:转山红,与 app.wxss --color-primary 一致 */
+export const DEFAULT_THEME_PRIMARY = '#9B2915'
+const HEX6 = /^#[0-9a-fA-F]{6}$/
+
 export function defaultBrand(): BrandAssets {
   return {
     brandName: '神山秘境',
     slogan: '',
     tibetanText: '',
     logoUrl: '',
     heroBgUrl: '',
+    themePrimary: DEFAULT_THEME_PRIMARY,
   }
 }
@@
     if (site) {
       cache = {
         brandName: site.brandName || defaultBrand().brandName,
         slogan: site.slogan || '',
         tibetanText: site.tibetanText || '',
         logoUrl: site.logoUrl || '',
         heroBgUrl: site.heroBgUrl || '',
+        themePrimary: HEX6.test(site.themePrimary || '')
+          ? (site.themePrimary as string).toUpperCase()
+          : DEFAULT_THEME_PRIMARY,
       }
     }

3.3 home 顶栏/品牌尾消费主题色(运行时 CSS 变量,最小侵入)

小程序不支持运行时改 wxss 变量选择器,但支持页面根节点 inline style 设置 CSS 变量,子节点 var(--theme-primary) 即可跟随,无需把每个色值改成 setData。

pages/home/home.ts onLoad:

   ensureBrandLoaded().then(() => {
     const b = getBrand()
-    if (b.brandName) this.setData({ 't.brandName': b.brandName })
+    this.setData({
+      ...(b.brandName ? { 't.brandName': b.brandName } : {}),
+      themeVars: `--theme-primary:${b.themePrimary};`,
+    })
   })

并在 data 里加 themeVars: ''。

pages/home/home.wxml 最外层节点注入变量,并把品牌区/品牌尾强调色改用变量:

-<view class="page">
+<view class="page" style="{{themeVars}}">
-  <safe-img src="/images/draft/logo.png" mode="aspectFit" img-class="brand-logo" />
-  <text class="brand-tibetan">གངས་རིན་པོ་ཆེ</text>
+  <safe-img src="{{b.logoUrl || '/images/draft/logo.png'}}" mode="aspectFit" img-class="brand-logo" />
+  <text class="brand-tibetan" style="color:var(--theme-primary);">{{b.tibetanText || 'གངས་རིན་པོ་ཆེ'}}</text>

(brand-foot 的 brand-name、以及顶栏主按钮/选中态如需跟随,同样把写死色值换成 var(--theme-primary);app.wxss 保留 --color-primary 作为未注入变量页面的兜底,建议 home.wxss 内加 .brand-name,.brand-tibetan{ color:var(--theme-primary, var(--color-primary)); }。)

注:logo/tibetan 运行时化超出纯 themePrimary 范围,321 品牌层本就预留了 logoUrl/tibetanText 但 home 仍硬编码——是否一并接线由统筹决定;最小补丁只做主题色,logo/tibetan 保持现状也可。

3.4 分享卡 / 关于页

3.5 c 端验证步骤(代推后)

补丁已由本席在沙箱本地预验证(2026-09-15):临时应用 §3.1+§3.2(typings + brand.ts 读源层)与 §3.3(home.ts setData themeVars 消费层)后 tsc --noEmit -p tsconfig.json 基线 0 → 补丁后仍 0 错误;验证后已 git checkout HEAD 全部还原,c 端源码零改动零残留(未越闸门、未提交)。代推人应用后仍需跑一遍步骤 1 复核。
  1. cd miniprogram && npm run typecheck(tsc --noEmit strict)0 错误(AGENTS §7 前端提交硬门槛)
  2. 微信开发者器:admin-console 改 themePrimary 为与转山红差异明显的色(如 #0E7C66)→ 保存 → 治理 publish → c 端冷启动(清缓存/重新进入)→ home 顶栏品牌名/藏文/主按钮变绿
  3. 后端不返回 themePrimary(回归兜底):冷启动后仍为转山红 #9B2915,不白屏不报错
  4. 非法值(后端被手改成 red):c 端 HEX6 判定失败,安静兜底转山红

四、后端补丁(backend/,交统筹代推人派发 · 阻塞 BLK-332-3)

backend/app/routers/miniapp_site.py get_site_config L74-78 顶层补一行展平:

             "logoUrl": brand.get("logoUrl", ""),
             "heroBgUrl": brand.get("heroBgUrl", ""),
+            "themePrimary": brand.get("themePrimary", ""),

五、阻断与下一步(第三件的前置)