听码 WDE 外派任务单 · MFR-Portal 双 build 修复(8.218 basePath 版 + 112 品类修复版)
| 字段 |
值 |
| 任务编号 |
WDE-TASK-L-20260828-01 |
| 签发方 |
听写 DT(依据用户级测试 DTL L-20260828-03 + bundle 诊断实锤) |
| 执行方 |
听码 WDE(dip1 本地编码) |
| 优先级 |
P0——8.218 MFR Portal JS 全 404 不可用;112 品类切换失效 |
| 三 AI 合规 |
听码仅本地编码 + build + 出包至 dist/;部署由听云执行 |
一、bundle 诊断实锤(听写已完成,听码免侦察)
| 环境 |
事实 |
| 8.218 |
HTML 能加载(handle_path 剥前缀 → :3000 返回 HTML),但引用的 7 个 chunk 全 404——无 basePath build 引用裸 /_next/*,被 Caddy 兜底给 OPR-Admin(其 basePath=/admin 无法服务)→ JS 完全没加载 → dev/token 失败 → 无法登录 |
| 112 |
bundle 固化 http://112.74.108.189:9100(build 时注入了 NEXT_PUBLIC_API_BASE_URL)→ API 正常;但品类卡片点击失效(生产 build 独有,本地 dev 正常) |
| 共同点 |
两环境 buildId 相同 lmwJrmNajqxAVE3f9pD2a,但 bundle 固化 env 不同(分别 build) |
| 本地代码 |
next.config.mjs 已有 basePath: "/mfr"(从未 build 部署) |
二、WBS
W1 · 8.218 专用包(basePath 版)
cd dip1/frontend/apps/mfr-portal
# build 时注入(PowerShell 语法):
$env:NEXT_PUBLIC_API_BASE_URL="http://8.218.165.94"
pnpm build
# 产物:.next + .next/standalone(next.config 已有 output:"standalone")
- 打包含
.next/standalone + .next/static(→ standalone/.next/static)+ public(→ standalone/public),命名 mfr-portal-8218-basepath-YYYYMMDD-HHmm.tar.gz,SHA256 登记
- 注意:basePath=/mfr 已在 next.config.mjs 中,无需改动
W2 · 112 专用包(无 basePath + 品类修复)
- 临时改 next.config.mjs:
basePath: "/mfr" 注释掉(或改 false)
$env:NEXT_PUBLIC_API_BASE_URL="http://112.74.108.189:9100" → build → 出包 mfr-portal-112-nobasepath-*.tar.gz
- 改回 next.config.mjs(basePath 保留,勿提交临时状态)
W3 · 品类卡片 bug 排查(P1-1,本地生产模式复现)
# 用 W2 的 build 产物本地起生产模式:
$env:NEXT_PUBLIC_API_BASE_URL="http://127.0.0.1:8000"
pnpm start # (standalone 则 node .next/standalone/.../server.js)
- 浏览器测 8 品类卡片点击 → card-selected 迁移?
- 若复现:排查生产 build 差异(StrictMode / 编译优化 / 事件委托),修代码后重新出 W2 包
- 若不复现:结论 = 112 现网 build 落后于仓库代码,部署新 W2 包即修复,代码零改动
- 已知线索:本地 dev(:3001)点击正常、React fiber 存在 onClick handler 但 setState 不触发重渲染
W4 · 测试基建补强(P1-3,顺手小修)
DispatchPendingList.tsx 第 75-76 行空值防御:
setItems(res.items ?? []);
setTotal(res.total ?? 0);
三、验收标准(听码本地自证)
| # |
断言 |
预期 |
| A1 |
W1 包本地 standalone 启动(PORT=3002)→ curl http://127.0.0.1:3002/mfr/login |
200,HTML 中 chunk 引用带 /mfr/_next/ 前缀 |
| A2 |
W1 包 HTML 中搜索 NEXT_PUBLIC 固化值 |
含 http://8.218.165.94(chunk 内 api URL 固化) |
| A3 |
W3 生产模式浏览器点击品类卡片 |
card-selected 迁移 + 工时推荐值变化(或给出复现+修复) |
| A4 |
W2 包 HTML chunk 引用 |
裸 /_next/(无前缀)+ 固化 http://112.74.108.189:9100 |
| A5 |
全量 pytest 基线 |
不回退(309 passed);前端 typecheck 0 error |
| A6 |
W4 |
空 API 响应不再 TypeError(?? [] 生效) |
四、交付与流转
- 两个包放
dip1/frontend/apps/mfr-portal/dist/,登记文件名 + SHA256 + 尺寸
- 回填本单 §五回执 → WDL 日志 → 通知听写转存
ocm/private/deploy/ 并派单听云
- 听云侧部署指引(听写届时签发):8.218 替换 standalone 目录 + systemd/PM2 重启(听云熟知现场);112 同流程;8.218 稳定后可移除 Caddy @mfrNav workaround
五、回执
回填:听码 WDE · 2026-08-28 · WDL L-20260828-01(dip1/docs/worklog/wdl/)
W1~W4 结果
| WBS |
结果 |
| W1 8.218 basePath 包 |
✅ mfr-portal-8218-basepath-20260828-1253.tar.gz(30,166,374 B · SHA256 e34505caa36ca7dfea6d96b8f7c21707c233678e7eb060951254c278b2716a87),basePath=/mfr 未改码(next.config.mjs 原有) |
| W2 112 无 basePath 包 |
✅ mfr-portal-112-nobasepath-20260828-1224.tar.gz(30,167,383 B · SHA256 32bd1d86f89f2822957f3c058ad1be519f42aa26ddbc50477e6bfa631fe80db2),basePath 临时注释构建后已改回(git 无残留) |
| W3 品类卡片排查 |
✅ 不复现 → 结论=112 现网 build 落后于仓库代码,部署 W2 包即修复,代码零改动。生产模式实证见 A3 |
| W4 DispatchPendingList 空值防御 |
✅ setItems(res.items ?? []) / setTotal(res.total ?? 0) 已改(opr-admin L75-76) |
构建期阻塞项修复(计划外 · 必做,否则任何 build 都失败)
pnpm build 报 Type 'React.ReactNode' is not assignable ... bigint:next@14.2.35(锁文件实际版本,package.json 为 ^14.2.5)next/types/index.d.ts 引用 react/experimental → 经 next 内嵌 @types/react@18.2.79 的 experimental.d.ts 级联 canary.d.ts → DO_NOT_USE_..._REACT_NODES 接口被增强注入 bigints: bigint → 未显式 import React 的文件中 UMD 全局 React.ReactNode 含 bigint,与显式 import 的 18.3.31 ReactNode 不可互赋。修复(4 文件,仅类型标注,0 运行时变更):apps/mfr-portal/app/{layout,providers}.tsx + apps/opr-admin/app/{layout,providers}.tsx 的 React.ReactNode → import type { ReactNode } from "react"。这也解释了 L-20260824-02 后任何增量 build 必失败(providers.tsx 为 WP-F11 新增未提交文件触发该路径)。
A1~A6 断言输出
| # |
断言 |
结果 |
| A1 |
W1 包 standalone PORT=3002 |
✅ curl /mfr/login → 200;chunk 引用 src="/mfr/_next/static/chunks/*";GET /mfr/_next/static/chunks/webpack-*.js → 200 |
| A2 |
W1 chunk 固化值 |
✅ grep -l "http://8.218.165.94" 命中 494-824d6dc7fcb6a5b6.js + app/quote/page-0164f1ba6540019b.js |
| A3 |
生产模式点击品类卡片 |
✅ headless Chrome CDP 实测(:3002/mfr/quote,注入 MFR-ADM dev token):点击「灯具」→ sel: 0→1(card-selected 迁移)+ 推荐工时 4.2h→1.5h(=LAMP mu 值)+ 0 console 错误 |
| A4 |
W2 包 HTML chunk |
✅ 裸 /_next/static/(无 /mfr 前缀)+ 494-9d69461c557ae9fa.js 固化 http://112.74.108.189:9100 |
| A5 |
回归基线 |
✅ pytest 309 passed / 0 failed(116s,对齐 L-20260827-01 基线);mfr-portal + opr-admin typecheck 均 0 error |
| A6 |
W4 空值防御 |
✅ ?? [] / ?? 0 生效,typecheck 通过(空 API 响应不再 TypeError) |
包清单
| 包 |
尺寸 |
SHA256 |
用途 |
dist/mfr-portal-8218-basepath-20260828-1253.tar.gz |
30,166,374 B |
e34505caa36ca7dfea6d96b8f7c21707c233678e7eb060951254c278b2716a87 |
8.218(含 basePath=/mfr + 固化 API http://8.218.165.94) |
dist/mfr-portal-112-nobasepath-20260828-1224.tar.gz |
30,167,383 B |
32bd1d86f89f2822957f3c058ad1be519f42aa26ddbc50477e6bfa631fe80db2 |
112(无 basePath + 固化 API http://112.74.108.189:9100) |
两包结构:standalone/(含 server.js + 实文件 node_modules)+ standalone/apps/mfr-portal/.next/static + standalone/apps/mfr-portal/public;10855 文件条目、0 符号链接(tar 已解引用 junction,Linux 解包可直接 node server.js)。
偏差与备注
- A3 用 W1 包验证(basePath 版 :3002/mfr/quote):W2/W1 同源码同 build 流程仅 basePath/env 不同,点击行为等价;结论外推至 W2 包成立。
- 本机 Windows 解包 tar 到深路径会踩 MAX_PATH(standalone 内 pnpm 嵌套路径 >260 字符);Linux 生产无此限制,包内文件已逐一核对完整(server.js/static/node_modules 齐备)。
- 事故记录:执行中一次
rm -rf .next 经 standalone junction 误伤 .pnpm 存储部分内容,已 pnpm install --force 全量恢复(1003 包 reused)并回归验证。教训:dip1 前端禁用 rm -rf .next,构建清理由 next build 自行处理。
- 部署指引(听云):8.218 解包 →
PORT=3000 node server.js(Caddy handle_path /mfr/* 现状兼容);112 同流程;8.218 稳定后可移除 Caddy @mfrNav workaround。
— 听码 WDE 回执完毕,待听写转存 ocm/private/deploy/ 并派单听云 —