A端 可配置三项 · 交付与验收报告

波波 2026-09-25 · 范围:A 文案字典(白标)/ B 藏汉双语 A端 可配 / C 平台参数页
验收口径:本地通过不算,staging 真调 + 真 Chrome 渲染才算。

一、结论速览

项交付验收证据
A 文案字典TextDictionary.tsx + 3069 键目录(esbuild 真打包,零改 C端)本地真渲染搜索/编辑/孤儿项全通
B 藏汉双语后端 4 键 + 095 迁移 + B端 i18n 运行时 + A端 4 开关staging 全链路 PUT→库→B端 GET 实测;双闸生效
C 平台参数页39 可配 / 16 只读 / 服务端脱敏staging 真数据 + 假私钥脱敏实测
门禁四路→六路接线门禁 + 词典门禁 + 3 个反证脚本(23 错例)全部反证命中

已推送:2901674e(主体)、f1c1d591(A端 渲染验收)。staging 已自动部署,smoke 26/26 全绿。

未部署:B端 与 A端 的 dist 均未上 prod(prod 前端需人工部署,等你令)。


二、A · 文案字典(=白标能力)

通道选择(本轮最关键的判断):C端 v2 实际消费的是 content_items,不是 site_texts。

链路:miniprogram-v2/app.ts:17 → loadSiteTexts() → GET /api/miniapp/content/items

→ 过滤 texts.<page>.<key> → applySiteTexts() → pageTexts() 合并。

site_texts(064 迁移、WIP-289)是 B端 SiteTexts.tsx 用的另一套,C端 不读。

⇒ 走 content_items 才能"改完立刻生效",不是半拉子工程。

目录怎么来的:admin-console/scripts/extract-texts-catalog.mjs

用 esbuild 真打包 C端 utils/texts.ts 并调 pageTexts() 取运行时真值(该常量没 export,

正则提取会切碎含冒号/引号/换行的条目)。产出 64 页 / 3069 键,与源码逐条比对 0 偏差。

页面设计:核心是「按内容搜索」(搜"神山秘境"揪出所有品牌名文案),

刻意不做批量自动替换——批量替换会把禁词/术语/品牌名一起误伤。

孤儿改写(库里有、目录里已无此键)不会静默丢弃,标注「C 端代码已无此默认值」。


三、B · 藏汉双语 A端 可配

架构:A端 配 → system_settings 存 → B端 读,运营改完无需发版。

(settings.py 是无过滤全表返回,会把 wx_pay_private_key 明文给前端,

我不做它的第一个调用方;加固属渔佳新媒体域,已单独上报未代改)

三条铁律:① bo 空一律回落 zh(绝不返回空串)② 拉取失败不阻断首屏(弱网)

③ showTibetan = bilingualEnabled AND boReady 双闸,前端不自行判断。

翻译进度不靠人填:extract-locale-progress.mjs 真打包 dict.ts 算出

147 条 / 已译 0 / 0%,A端 语言配置页直接显示,并在「进度未满却开了 boReady」时硬告警。


四、C · 平台参数页

staging 实测返回:可配置 39 · 只读 16 · 脱敏 0 · 未登记 0 · 库内 55 行,

分组 = 福钥 14 / 分销 10 / 订单 6 / 平台 5 / 语言 4。

脱敏实测(安全关键路径,不靠推理):在 staging 插入假值

wx_pay_private_key = FAKE-SECRET-DO-NOT-LEAK → 端点返回

redacted: ['wx_pay_private_key'],响应体里搜不到明文(键名如实报出,值不出库)。

服务端强校验实测:b_locale_default=zh-CN → 422「只能是 zh / bo / both」;

b_locale_bo_ready=maybe → 422「只能是 true / false」。资金/语言口径不能只靠前端。

prod 安全现状复核:5 个敏感键(wx_pay_private_key/apiv3_key/serial_no/mchid/weather_api_key)

在 prod 确实存在且值为空串(长度 0) ⇒ 是隐患不是现行泄漏,与上一轮表述一致。

staging 这 5 键根本不存在,所以 redacted:0 是如实报出,不是漏报。


五、门禁:本轮抓到的两个真缺陷

1. 幽灵菜单项 /retail-products(已删)

真 Chrome 渲染实测:侧边栏只有 38 项,缺 3 项。查证后发现

/retail-products 在 App.tsx 无路由、pages/ 下也无组件,

商户点了被 <Route path="*"> 兜底弹回工作台 —— tsc 与 vite build 全程一声不吭。

是我上一轮补菜单时加错的。商品管理实际由 /supplier-products(供货商品)承担,已删除。

2. 门禁反向盲区(已补⑥)

旧门禁只做「包白名单 → 入口」的正向检查,查不出"菜单里有、路由里没有"。

新增⑥反向校验:菜单项必须「有路由」且「能被行业包 ∪ 基础包 ∪ DEFERRED 放行」。

判据收紧过程(重要):⑥一上来自报 16 项红,其中 14 项是误报——

PACKS 只写了 4 个业态漏了 scenic/other,且没把基础包恒开(BASE_ROUTES)算进放行集合。

补齐后收敛到 2 项真红。


六、反证:每个门禁都配了可复跑的反证

脚本错例数覆盖
scripts/revproof_pack_wiring.py8门禁漏项/无路由/入口消失/三处全无/详情页跳转被删/幽灵菜单/DEFERRED 被摘/模板串归一
scripts/revproof_locale_dict.py8R1 元组少项 / R2 中文空 / R3 假翻译 / R4 key 重复 / R5 Layout 引用缺失 / R6 菜单无映射 / R0 判据静默失效 / 基线
scripts/revproof_locale_progress.py4填 3 条→进度变 3、假翻译计入 4、R3 拦截、还原

词典门禁特意加了 R0:解析不出 PATH_KEY 块时报红而不是跳过——

跳过=判据静默失效,这是我自己踩过的坑(正则漏了 Record<...,string> 的 >)。


七、渲染验收(构建过 ≠ 能渲染)

⇒ 38/38 菜单项仍为中文,不空白、不出现 undefined/NaN、不漏 nav.* key

两次自我纠错留痕:

  1. 一开始报"A端 两页白屏"是冤枉——admin-console 用 BrowserRouter,我写成 /#/xxx

被弹回 /dashboard,报错其实是 Dashboard 对我假数据的反应。

  1. 报"孤儿改写丢了"也是冤枉——孤儿行追加在 3072 行末尾,分页第 1 页看不到;

改成按真实用法搜索 ghost,找得到且标注正确。


八、待你裁定(未擅动)

#事项现状建议
1/supplier-questionnaire有路由、在 retail 包,三处入口全无(真孤儿,只能 URL 直达)。语义属入驻前流程我判其应归 C端,但 retail 包归我、C端归小齐 ⇒ 跨域不擅动。三选一:删页面 / 挂菜单 / 移出 B端
2/retail-orders 菜单文案「收银台」易被误读成"平台收钱"✅ 已办(同日 56e8e34a):改「收款记录」,并连带把收银/结账口径在零售·餐饮·车队三业态统一为「登记收款/记录收款」;酒店客账「结账」按业务实际保留。详见下方 §追加
3prod 前端部署B端/A端 dist 都已构建通过,未上 prod等你令;prod 迁移需 095 + 人工 DEPLOY_WITH_MIGRATIONS=1

追加(同日 09-25):合规文案口径改正 —— 义父令「改」

上一节把「收银台」列为待裁定第 2 条,义父批「改」,已办(commit 56e8e34a)。

判据:不是一刀切,是口径分离

场景后端实际行为文案
现场即时记账(零售 / 餐饮 / 车队)settle 只写 pay_method + paid_fen + paid_at,登记即收款、无待支付中间态收款记录 / 登记收款 / 记录收款
酒店客账(Folio / 交接班)押金、挂账、多笔收款、离店结清保留「结账」(改了反而不准确)

一刀切把酒店「结账」也改掉,属于为了口径统一牺牲准确性——这条用脚本

must_keep 判据锁住(HotelFolios.tsx 9 处、ShiftHandover.tsx 3 处必须仍在),

并在渲染层做了反向证明:打开 /folios 断言页面仍含「结账」,失败即说明改过头。

改了什么:30 处 / 14 文件

验收:b-workbench/scripts/verify_wording_local.mjs(新增,13 项全绿)

打本地 dist + 拦截 API 造假数据 + 真 Chrome,不碰 prod。

为什么不复用 verify_retail_render.mjs:那个打线上 b.kailashi.cn 且要真商户 token,

本轮前端尚未部署 ⇒ 打本地才能现在就验,不必等发布。

断言覆盖:新词在位、旧词(收银台/收银开单/结账)清零、控制台零错误、

侧边栏可见入口、以及 /folios 仍含「结账」 的反向证明。

回归:verify_locale_render.mjs 全绿、locale-dict-test.mjs 29/29、接线/词典两门禁绿

(接线门禁仍剩 /supplier-questionnaire 真孤儿,跨域待裁定,未擅动)。

教训(留痕)

看截断输出就下"清单完整"结论,连漏 4 批。 第一批我用 head -40 / tail -60

看扫描结果,中间的 services/retail.ts、FleetRides.tsx、ProductEdit.tsx、

RetailDashboard.tsx 根本没进视野 ⇒ 以为 12 处就完事,实际 30 处。

全称结论必须跑全量不截断复扫,且复扫判据要同时包含「该清零」和「该保留」两类,

前者防漏改、后者防改过头。


追加二(09-25 上午):B端 上线 prod + 生产端到端验收(commit de4fafd6)

义父令「继续」⇒ 把已交付的东西走完最后一公里:真实环境验收。

部署

B端 前端部署在本人全权范围内,已执行:

步骤结果
备份原目录/srv/kailash/nginx/html/bworkbench.bak.20260925102429(可整目录回滚)
打包上传COPYFILE_DISABLE=1 tar czf(禁 rsync,避中文名乱码)
解包._* 残留 0
同步根级 assetsindex.html 引用的是绝对路径 /assets/,只解包到 bworkbench/assets/ 会白屏 —— 必须再 cp 到 html/assets/
reloadnginx -t 通过后 -s reload,js/css/首页三个资源均 200

生产验收:b-workbench/scripts/verify_prod_render.mjs(新增,全绿)

为什么必须有这一层:本地 dist + 假数据只证明「前端没写错」,中间还隔着

nginx、资源 hash、后端真实数据三件事,任一出问题现象都是白屏而本地全绿。

本轮运气好,prod 上有真数据可验:商户 8001 开了 restaurant,库里

dining_orders 有 4 条真实订单(created/preparing/served/paid 四态齐全),

所以直接渲染出真实 UI,证据强于本地:

Tabs  : 待处理 / 备餐中 / 已上菜 / 已收款 / 已取消
行按钮: 备餐 / 记录收款 / 取消
页面  : 无「结账」、无「收银」

/restaurant-dashboard、/workbench 同步验收,无 PAGEERROR,无词典 key 泄漏。

自我更正(留痕)

验收脚本只打印侧边栏前 12 项,我看到全是「住宿·房务」,一度以为 8001 的餐饮菜单

没显示、怀疑是第二个「桌面不可见」缺陷。补诊断脚本打印全部 27 项后发现餐饮四项

都在(🍽️餐饮概览 / 📖菜单管理 / 🪑桌台管理 / 🧾点餐订单,分组也有「餐饮」)。

是切片打印导致看漏,不是缺陷。 与上午「head/tail 漏 4 批」同源——

报缺陷前一定先看全量数据。

上线前置清单(未办,等义父令)

#事项为什么现在不做
1prod 执行 095 迁移(b_locale_* 四键,现状无)词典藏文 0% 未译 + 双闸 ⇒ 开了也显示中文,对用户零差别;且 A端 未部署,单跑迁移就是半成品。建议真要上藏文时一次性做完
2A端 dist 部署到 /srv/kailash/nginx/html/adminconsole/权限归属不明确,未擅动
3prod retail 包零商户开通目前只有 merchant 1(冈仁波齐城堡酒店,唯一真实)开 hotel;8001 开 hotel+restaurant;8021 开 vehicle ⇒ 零售 UI 暂无真实承载商户,只能靠本地 dist 验收覆盖

追加三(09-25 上午):义父三条令 —— 藏文 / A端归我 / 灌数据

① 藏文:AI 初稿 147/147 + 运营覆盖层(commit feaa58de)

义父令「藏文可以先借用 AI 翻译,后期在 A端 人工修改」。

关键判断:只翻译、不建通道 = 改一个词要发一次版 = 等于不可维护。

所以两件事必须一起做,否则"后期可改"是句空话。

项内容
翻译dict.ts 147/147 全部填入。UI 常用词多属标准对应;业务术语(夜审/核销/RevPAR/核载)为意译。未经藏族母语者校对
来源标注dict.ts 顶部显著写明「AI 初稿,信任等级:可看、不可当作最终稿」
双闸b_locale_bo_ready 仍默认 false ⇒ 不打开就不显示藏文
覆盖层A端 改 content_items 的 b_dict.<key> → GET /merchant-console/bo-dict → B端 同名覆盖,改词不用发版

覆盖层三条约定(与内置词典同款):空值不覆盖(否则运营清空 ⇒ 变空白)、

拉取失败不阻断首屏、形状异常静默退回内置。复用 content_items 不新增表。

"藏文不编造"这条铁律没作废,只是换了防线位置:从"bo 必须留空"

变成"来源如实标注 + 双闸把关 + 随时可改"。

② A端 归我 ⇒ 已上线

role 不是 kailash。locale 组出现,B端 公开端点 configured:true。

另有 16 个 AppleDouble ._* 残留需清理。

locale 组在位、脱敏区在位、进度卡在位;text-dictionary 3069 键目录无白屏。

顺带修了一个 rebase 埋的雷

api.ts 两组 listContentItems/deleteContentItem 并存 ⇒ tsc 报重复声明,

运行时后声明覆盖先声明,一直跑的是另一组。而我那组还漏了 API_BASE

(写成字面量 '/admin/content/items',正确应为 CONTENT 常量)——

错路径没被暴露,正因为它从未被调用。已删我这组、保留正确那组,

并给保留组补形状校验(防 .map 崩白屏)。

判据更新(两处前提过期,不是产品缺陷)

原判据新判据
bo 必须全空 / countTranslated()=0 / 进度 0%不许半套:要么全译要么全无(一半藏文一半空 ⇒ 双语界面半中文半藏文,比纯中文更糟)
渲染:词典未译必须回落中文拆两个方向:② 强开时真显示藏文(38/38、无中藏混排);②b 双闸关闭时必须回落中文(残留 0)

验收

待办

工作量独立,本轮来不及。

下次构建才在 prod 生效;在此之前 B端 用内置译文正常工作,不报错。