alphaH bf0901673d fix(乐收赢): 三个收款页的 keep-alive 组件名与菜单 route_name 对齐(此前静默失效) пре 2 недеља
..
README.md bf0901673d fix(乐收赢): 三个收款页的 keep-alive 组件名与菜单 route_name 对齐(此前静默失效) пре 2 недеља
fe-task3-mutate.mjs 0ad00abed5 feat(收款): 进件页改成「我的商户」列表 —— 每行一个下一步,操作员不用选 пре 3 недеља
fe-update-harness.mjs 0ad00abed5 feat(收款): 进件页改成「我的商户」列表 —— 每行一个下一步,操作员不用选 пре 3 недеља
fe-update-keycheck.mjs fe127af949 chore(进件): 另外三支验证台挪出批次工作区,交付说明同步 пре 3 недеља
fe-update-mutate.mjs fe127af949 chore(进件): 另外三支验证台挪出批次工作区,交付说明同步 пре 3 недеља
lsy-keepalive-harness.mjs bf0901673d fix(乐收赢): 三个收款页的 keep-alive 组件名与菜单 route_name 对齐(此前静默失效) пре 2 недеља
task-9-fix1-harness.mjs e7e29d48fd chore(进件): 把 Task 9 验证台挪出批次工作区,交付说明同步 пре 3 недеља

README.md

批次三-B 的验证台 —— 怎么跑、看什么

这五支是验收用的:不连库、不连上游、不发任何网络请求,也不需要登录凭据。 它们直接编译真的 Vue SFC、跑 frontend/ 里那份真的 vue、驱动页面的真 setup(), 只把 @/api 与上传子组件桩掉。

来源与搬迁经过见 .claude/plan/2026-09-23-lsy-onboarding-B-交付说明.md §四 4。 本 README 的每一条命令与读数都是 2026-09-23 实跑取的,不是转述,但取自两个时点:

  • 第 1~4 节里除 fe-update-harness.mjs 之外的读数,取自交付提交 20a222b;
  • fe-update-harness.mjs 与第 3 节的 fe-task3-mutate.mjs,是在「乐收赢全流程傻瓜化」Task 3 (进件页重写成「我的商户」列表,计划见 .claude/plan/2026-09-23-lsy-simple-flow.md)里 扩写 / 新增的,读数取自该任务尚未提交的工作区:页面 frontend/src/views/module_payment/collection/onboarding/index.vue = 58650 字节 / sha256 ef04f76615739c3712f38e874223e0fc3e0d13df76ac75afea3aca0e684d6356(LF 形态, git ls-files --eol 实测 i/lf w/lf;CRLF 形态 60041 字节,见第 4 节)。

0. 公共前提

  • Node:实测 v22.22.3。
  • 工作目录:README 里的命令以仓库根 D:/project2/payment-platform 为 cwd 写。 台子内部用的是绝对路径,换 cwd 不影响结果(已实测:从 D:\ 跑同样通过)。
  • 依赖 frontend/node_modules:台子用 createRequire 指到 frontend/package.json 来加载 vue 与 @vue/compiler-sfc ⇒ 那个目录不存在时加载这一关就过不去,先 pnpm i。
  • 跑之前 git status -- frontend/ 应当是干净的。第 1、2 节那三支只读,跑完仍干净; 第 3 节两台变异驱动会临时改生产文件(3a 自己还原,3b 要手动 restore)—— 所以「跑完还干净」这句只对前三支成立。

1. 两支要传页面路径的台子(不传会崩)

这两支把目标页面当命令行参数读(process.argv[2]):

PG="D:/project2/payment-platform/frontend/src/views/module_payment/collection/onboarding/index.vue"

node .claude/plan/2026-09-23-lsy-onboarding-B-harness/task-9-fix1-harness.mjs "$PG"
node .claude/plan/2026-09-23-lsy-onboarding-B-harness/fe-update-harness.mjs "$PG"

忘了传参数会怎样:立刻崩,TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string ... Received undefined (栈顶是 compileSfc 里的 readFileSync),退出码 1。看到这个就是漏了页面路径,不是台子坏了。

判成败只看 SUMMARY 行

两支都会在最后打一行汇总。不要看退出码:台子故意不设 process.exitCode (fe-update-harness.mjs 第 14 行的注释就是这么写的),所以有 FAIL 也退 0; 唯一会给出非 0 退出码的情况是上面那种「参数没传」的崩溃。

所以判据是这两行里的 fail=:

SUMMARY <页面路径>: pass=25 fail=0      ← task-9-fix1-harness.mjs
SUMMARY <页面路径>: pass=89 fail=0      ← fe-update-harness.mjs(Task 3 之前是 43)

有失败时同一行会追加失败用例名:... fail=2 -> <用例名> | <用例名>。

上面两个读数都是 2026-09-23 实跑的(各 1 次,exit=0):pass=25 取自交付提交 20a222b; pass=89 取自「傻瓜化」Task 3 的工作区(页面指纹见文首)。

★ 顺手可以把 stderr 丢掉,屏幕只剩 PASS 与 SUMMARY(实测:fe-update-harness 加 2>/dev/null 后 stdout 91 行、grep -c "Vue warn" = 0;这个行数随用例数走,43 条那会儿是 27 行) —— 台子的告警与桩报错都在 stderr,stdout 是干净的判据流。

各自钉什么

  • task-9-fix1-harness.mjs(25 条):进件页 Task 9 那处修复(商户号回显 / 警示)的判定逻辑, 含两条正对照(G4「正常商户号 ⇒ 显示出来」、G5「正常商户号 ⇒ 不置警示」) —— 没有正对照的话「一律不显示」也会全绿。
  • fe-update-harness.mjs(89 条):同一支台子,两批被测逻辑。原 43 条是 R-7c 的 「建号 / 更新」两个模式的报文形状 —— U. 更新腿键白名单(U1/U1b:不含 merchant_name_business)、 V. 建号腿正对照(一个都没被改坏)、W. 渲染投影、Y. 更新腿的失败与形状、 Z. 键集(Z5/Z6:更新集与建号集里都没有 category / tenant_id / rate_code)。 Task 3 新增的五段钉的是重写后新出现的判定:

| 段 | 钉什么 | |---|---| | X. | 打开两张表单的入口 —— 新建从干净表单起步;「改资料」模式与商户号预填,但不把建号表单的值带过去 | | P. | 进度列:四个 review_status 逐字映射,表外的值一律不猜(正对照:未知值 ⇒ 空) | | A. | 每行恰好一个主按钮 + 至多一个文字按钮;A7 是正对照(APPROVED 没有提交审核入口)、A5 是基数闸(五档各恰好一个主按钮) | | C. | 提交审核报文:6 个业务字段 + mch_no,空白整个省掉;12 个资质码表;大写 L 那个坑 | | S. | 409 当成「已提交」而不是失败(S2 关弹窗、S4 常驻警示);另配一个 400 正对照,证明它认的是那个码值而不是「任何失败都当 409」 | | L. | 列表读法要解两层 .data(响应拦截器不解包);L5 是正对照 —— 只解一层时读不到行 |

★ 台子里有一处已删的空断言(原 S3)和一处已改的脆弱写法(L2 原用 [0].mch_no), 两处的理由都写在代码注释里,别当漏做补回去。详见 .claude/plan/2026-09-23-lsy-simple-flow.md Task 3 的「变异暴露出的两条我自己的问题」。

已知局限(别把它的绿读大)

  • 模板不渲染 ⇒ 它证明的是函数判定逻辑,不证明浏览器里的表现。
  • stderr 上会出现一条 [Vue warn]: onMounted is called when there is no active component instance... —— 这是桩环境没有组件实例导致的,预期内,不是失败。
  • stderr 上还会有一段桩出来的报错(例如 级联数据加载失败: Error: 网络挂了), 那是故意注入的网络失败用例在走错误分支,同样不是失败。 (这两条都实测过只在 stderr:2>/dev/null 之后 stdout 里一个都没有。)

2. 静态键名核对台(不需要参数)

node .claude/plan/2026-09-23-lsy-onboarding-B-harness/fe-update-keycheck.mjs

退出码 0 + 末行 SUMMARY keycheck: 全绿。它比对前后端两侧的字段键名集合, 确认没有拼错的键(跨端键名拼错在运行时只会表现为「字段悄悄没发出去」)。

★ 这支是五支里唯一搬过家时改过内容的一支:原先写死了兄弟台子的工作区绝对路径, 搬完就成了一个已删除的文件 ⇒ 改成按自身位置相对定位 (fileURLToPath(new URL("./fe-update-harness.mjs", import.meta.url)))。 所以它必须与 fe-update-harness.mjs 待在同一个目录里。

3. 变异驱动(跑之前先看清楚)

两台驱动形状不同,别互相套用:fe-update-mutate.mjs 是「一次一个变异 + 手动 restore」, fe-task3-mutate.mjs 是「四轮一口气跑完 + 每轮自己还原」。

3a. fe-task3-mutate.mjs —— Task 3 的两向变异,一条命令跑完

node .claude/plan/2026-09-23-lsy-onboarding-B-harness/fe-task3-mutate.mjs

不需要参数,也不需要事后 restore:它每一轮都在 finally 里按字节还原, 还原后再复核 sha256 是否回到基线,末行还会再打一次「基线复核 sha …(未被留脏)」。

判据是最后那行:MUTATE-SUMMARY: 全绿。四个变异各自预期红的用例:

变异 打的是 预期该红
M-APPROVED-ALSO-SUBMIT APPROVED 那档也挂提交审核入口 A7、A8
M-409-CODE-CHANGED 把 409 换成一个表外的码值 S2、S4
M-LIST-ONE-LAYER 列表只解一层 .data L1、L2、L5(正对照自己也红)
H-REORDER-DECLS 无害:换个声明顺序 (无,必须仍 89/0)

最后一行那格是这台子存在的理由:没有它,「该红的都红了」也可能只是台子对任何改动都敏感。

几个会让读数作废、但驱动器自己会拦下来的情况(都会打印出来并计入非 0 退出码): 锚点命中次数 ≠ 1(说明变异打偏了,不会去改文件)、还原后 sha ≠ 基线、台子没跑完 (spawnSync ⇒ 变异把台子打崩时驱动器不跟着死,这一档单独判,不会被读成「没红 ⇒ 通过」)。

3b. fe-update-mutate.mjs —— R-7c 的变异,需要手动还原

node .claude/plan/2026-09-23-lsy-onboarding-B-harness/fe-update-mutate.mjs restore
  • restore:把备份 .mut-bak 还原回生产文件。没有备份时什么都不做,退出码 0。
  • 传其它任何 id(M-U1 / M-U2 / M-U3 / M-U5 / M-T1 / M-T2):会真的改生产前端文件 (frontend/src/views/module_payment/collection/onboarding/index.vue 与 frontend/src/api/module_payment/onboarding.ts),并在同目录留 .mut-bak。 跑完必须用 restore 还原,再用 git status -- frontend/ 确认干净。

验收不需要跑变异。

  • 跑 3a:一条命令,跑完看末行 MUTATE-SUMMARY,不用再 restore,末行会自己报「未被留脏」。
  • 跑 3b:按「先 git status 干净 → 变异 → 跑第 1 节的两支台子看读数变化 → restore → 再 git status 干净」的顺序。
  • 两台驱动都只动工作区文件、不动索引,所以跑完 git status 该与跑前逐行相同。 (跑 3a 实测过:跑前跑后 git status --porcelain diff 无差异、git diff --cached --stat 空。)

4. 核指纹时注意行尾

本仓 core.autocrlf = true 且没有 .gitattributes ⇒ 重新 checkout 会把工作区文件抹成 CRLF。 两种形态都实测过,不是按行数推算的(LF 即当前工作区形态):

文件 LF 字节 LF sha256 CRLF 字节 CRLF sha256
task-9-fix1-harness.mjs 10796 7f07de4099c96a23c56c1238aeb002857ed8bc10c83458feab5b979cac5bc095 11049 81271e98d40456c3a594669e796ff3d49681eee9b2491bf8ba1766f09e4a362f
fe-update-harness.mjs 24707 a93e0f8e4dd86340f8db29766ecc6336335e00a9b3e190e219135bb296805150 25189 17e1bd83b73123bd1a941c0f0f1cdc6396e0c513638cad47753ef86baa1ef81d
fe-update-keycheck.mjs 6874 3d4033167ed8950cda71c3396d77fe4968f70aafef915f2616193a675eba1767 7002 2e1eb43d829ea7e4aabc330c77d4a5605c4b4672a580d21ace255fbfa26325e4
fe-update-mutate.mjs 4197 a3e68dff6d58fd86a6b219c2de50f98493434bc929c544d9f77bb1af9565a330 4294 fafacc0c8e41091a870054098c2de20dc46f90670890983dcd5101f9d8d65621
fe-task3-mutate.mjs 6575 4003028451629bcbef57cf40bfc99c67e7879084640cfdf4b5cd8cfa7abb42dc 6706 6640b0083e02066ebccc85d34c99064d7d2ec9af5ae85cedf9eff77228c87ddf

被测页面(Task 3 那两行的读数钉在它的这个版本上):

文件 LF 字节 LF sha256 CRLF 字节 CRLF sha256
collection/onboarding/index.vue 58650 ef04f76615739c3712f38e874223e0fc3e0d13df76ac75afea3aca0e684d6356 60041 34e3cb98fd44c8835e26f79d7e3805d3a7a9b42be65fc01e5510dd12d90dfea8

核出 CRLF 那组数不是文件被改过,是行尾。 (上面两张表共 12 个读数,是用同一个换算脚本一次跑出来的:把读入的字节按 \r\n ↔ \n 互转后各自 sha256。 该脚本对 task-9-fix1-harness.mjs 复现出的 CRLF 值 81271e98… 与早先独立量到的一致 ⇒ 换算方式没跑偏。)


5. lsy-keepalive-harness.mjs —— keep-alive 组件名匹配(2026-09-28 新增)

要证的一句话:<keep-alive :include> 匹配的是组件名,而 cachedViews 里放的是 路由名(= sys_menu.route_name)⇒ 两者不逐字相同时,那页的 keep_alive=true 静默失效(vue 不报错,只是不缓存;症状是「切走再切回来,筛选条件/分页/正在等的二维码全没了」)。

链路三跳(全部从源码量过,不是推的): sys_menu.route_name → permission.store.ts 造路由时 name: item.route_name → tags-view.store.ts 的 addCachedView 把路由名推进 cachedViews → layouts/components/AppMain/index.vue:6 的 <keep-alive :include="cachedViews"> 拿它去比组件名。

node .claude/plan/2026-09-23-lsy-onboarding-B-harness/lsy-keepalive-harness.mjs \
  "D:/project2/payment-platform/frontend/src/views/module_payment/collection/order/index.vue" order \
  [可选的 include 覆盖值]

编真 SFC(@vue/compiler-sfc)、用 frontend/node_modules 里那份真 vue 挂进真 <keep-alive>,数 setup() 跑了几次 —— 缓存命中后再激活不会重跑 setup。 一次跑给两向读数:include=[本页真名] 期望 1;include=[必然不匹配的串] 期望 2(正对照, 没有它,setups=1 也可能只是计数器没在工作)。

读数(2026-09-28 实跑)

五个页面各自自比,5/5 PASS(hit=1 miss=2):

页面 组件名(修后) 活库 route_name
collection/order CollectionOrder CollectionOrder(id=269)
collection/refund CollectionRefund CollectionRefund(id=270)
collection/merchant CollectionMerchant CollectionMerchant(id=271)
collection/cashier CollectionCashier CollectionCashier
collection/onboarding CollectionOnboarding CollectionOnboarding(id=272)

活库那一列取自 lsy-cashier-menu.sql 头部记的 2026-09-23 只读实测(id 269–272)。 ★ 前三个页面在 2026-09-28 之前写的是 Leshouying*(lsy-menu-rename.sql 把库从 Leshouying* 改成 Collection* 时,SFC 的 name 没跟着改)—— 即三个页面的 keep_alive 一直是失效的。 lsy-cashier-menu.sql:32-40 早记过这条,当时按「不相关的不动」留下未修。

变异(两向的另一半):把 order 页的 name 改回 LeshouyingOrder,include 仍喂 CollectionOrder ⇒ SUMMARY FAIL hit=2 miss=2(不缓存)。这证明上面那些 PASS 是 行为差别,不是文字差别。还原后 git status -- frontend/ 只剩那三个待提交文件。

已知局限(别把它的绿读大)

  • 模板不渲染(inlineTemplate: false)⇒ 它证明的是缓存语义,不是浏览器里长什么样。
  • 用 createRenderer 接一套哑节点操作,不引 jsdom。有的页面 onMounted 会碰 window (如 cashier 页),台子里有一行 globalThis.window = globalThis 的垫片 —— ★ 那是台子的垫片,不是页面的缺陷,别去改页面。
  • 桩是通用的(@/api/* 与 element-plus 都换成「取什么都是可调用、返回 Promise」的代理) ⇒ 它验不到任何接口契约。残留 import 会被一句兜底断言当场炸掉,不会悄悄跑出假绿 (实测:qrcode 与 SingleImageUpload 两条就是这么被发现并补上的)。
  • 不设 process.exitCode(与同目录各台一致)⇒ 失败也退 0,只认最后那行 SUMMARY。

指纹

文件 LF 字节 LF sha256 CRLF 字节 CRLF sha256
lsy-keepalive-harness.mjs 7718 2d5a9c9a677dafd1d57d84eae6e0afc8d11ce7526d0aae2e66dffa15327c7571 7888 cd97a29a36877aeb88656b2271d232e960850f7cc3c94329b256ed009a04d1fe
collection/order/index.vue 10531 66b144354bb612081aeca65ef54b002c194f29ec2eab5d840fea7fa6ea7b9700 10789 1559fbb99ad173c7995f5491dfdd96def53b8b894dfc38fcb41ea30e73ef315a
collection/refund/index.vue 4720 0a6c782061522baad7a97a794f51f78b214bcf960141aed6546e8a8291322b2b 4860 1c00b8b0f89f46adaf8d0a64999817415c16194bf2e5fe8218d3a72493df5ea7
collection/merchant/index.vue 11178 2df3a00d5440b08487cd409d9923b72dd76fa5659fc75a436e4e900ebcb449b6 11431 bebaac71b14e2c331d32c96d7d55d83fbc255b8a9b4233307891bf1a3ab60942

三个 .vue 的 git ls-files --eol 实测 i/lf w/crlf(工作区即 CRLF 形态); .mjs 是 2026-09-28 新写的,工作区当前是 LF(CR 数 0)—— 提交后随 core.autocrlf=true 会变 CRLF。