DTL · L-20260825-04
文档编号:DOC-A04-DTL / L-20260825-04
维护人:听写 DT(DOC-A01 PMG V8.3 §人机协作模型)
关联 DTL:L-20260825-03
撰写日期:2026-08-25
任务:WP-06 收尾——5 端 WhoamiProvider 注入 + 登录页接入 useWhoami().login + RN 版 Provider
一、任务背景
L-20260825-03 §3.4 列出 WP-06 后续 4 项 TODO:
1. 后端 /api/v2/bff/whoami 接口实现(依赖后端,本轮不做)
2. 5 端 layout.tsx 注入 WhoamiProvider ✅
3. 5 端登录页接入 useWhoami().login ✅
4. RN 端 RN 版 WhoamiProvider(不能用 'use client')✅
本轮在用户「上述工作全部顺序执行」指令下完成 #2/#3/#4(纯前端部分),#1 留待后端排期。
二、交付文件清单
2.1 新增文件(5)
| 文件(相对 dip1/frontend/) |
类型 |
内容 |
| packages/shared/src/client/whoami-context.rn.tsx |
新增 |
RN 版 WhoamiProvider(无 'use client',内存态无持久化,0 新增 npm 依赖) |
| apps/mfr-portal/app/providers.tsx |
新增 |
MFR Web 客户端 Providers 包裹器 |
| apps/opr-admin/app/providers.tsx |
新增 |
OPR Web 客户端 Providers 包裹器 |
| apps/lgp-portal/app/providers.tsx |
新增 |
LGP Web 客户端 Providers 包裹器 |
| docs/工作日志/dtl/L-20260825-04(本日志) |
新增 |
WP-06 收尾交付日志 |
2.2 修改文件(11)
| 文件(相对 dip1/frontend/) |
变更 |
| packages/shared/package.json |
exports 新增 ./client.rn 子路径(指向 whoami-context.rn.tsx) |
| packages/shared/src/client/index.ts |
加 "use client" 指令——否则 Next 14 SSR 时 re-export 的 WhoamiProvider/useWhoami 被替换为 undefined |
| apps/mfr-portal/app/layout.tsx |
body 内包裹 <Providers>{children}</Providers> |
| apps/mfr-portal/app/login/page.tsx |
setToken → useWhoami().login(token, "mfr-portal"),加 user=null 错误兜底 |
| apps/mfr-portal/lib/auth.ts |
TOKEN_KEY 从 dip1.jwt 改为 dip1.token(对齐 shared TOKEN_STORAGE_KEY) |
| apps/mfr-portal/tsconfig.json |
paths 移除 @dip1/shared/* 通配符,让 webpack 走 node_modules + package.json exports 解析(修复 'use client' 边界) |
| apps/opr-admin/app/layout.tsx |
body 内包裹 <Providers>{children}</Providers> |
| apps/opr-admin/app/login/page.tsx |
setToken → useWhoami().login(token, "opr-admin"),加 user=null 错误兜底 |
| apps/opr-admin/lib/auth.ts |
TOKEN_KEY 从 dip1.jwt 改为 dip1.token(对齐 shared TOKEN_STORAGE_KEY) |
| apps/opr-admin/components/Sidebar.tsx |
加 useWhoami():显示 user.display_name + logout() 双清(clearToken + Provider logout) |
| apps/opr-admin/tsconfig.json |
同 mfr-portal,paths 移除 @dip1/shared/* 通配符 |
| apps/lgp-portal/app/layout.tsx |
body 内包裹 <Providers>{children}</Providers> |
| apps/lgp-portal/app/login/page.tsx |
从占位页改为完整登录页(textarea + useWhoami().login(token, "lgp-portal") + 错误兜底) |
| apps/wkr-app/App.tsx |
顶部 import WhoamiProvider from @dip1/shared/client.rn;export default 改为 AppWithProvider 包裹 <WhoamiProvider app="wkr-app"> |
| apps/wkr-app/tsconfig.json |
加 moduleResolution: "bundler"(expo/tsconfig.base 默认不支持 package.json exports 子路径) |
| apps/cst-app/App.tsx |
顶部 import WhoamiProvider;export default function App 内直接 <WhoamiProvider app="cst-app">…</WhoamiProvider> 包裹 |
| apps/cst-app/tsconfig.json |
同 wkr-app,加 moduleResolution: "bundler" |
三、5 端注入矩阵
| 端 |
类型 |
layout/App 注入 |
登录页接入 |
TOKEN 存储 |
| MFR 门户 |
Next 14 Web |
✅ providers.tsx + layout.tsx |
✅ useWhoami().login(token, "mfr-portal") |
✅ dip1.token(对齐 shared) |
| OPR 后台 |
Next 14 Web |
✅ providers.tsx + layout.tsx |
✅ useWhoami().login(token, "opr-admin") |
✅ dip1.token |
| LGP 门户 |
Next 14 Web |
✅ providers.tsx + layout.tsx |
✅ useWhoami().login(token, "lgp-portal") |
— (无 lib/auth) |
| WKR App |
Expo RN |
✅ AppWithProvider wrapper |
— (无登录页,DEV 模式 Mock) |
内存态(RN 版无 localStorage) |
| CST App |
Expo RN |
✅ App 内直接包裹 |
— (无登录页) |
内存态 |
四、关键设计决策
D1:RN 版 Provider 用内存态而非 AsyncStorage
- 背景:RN 端无 localStorage,标准方案是引入
@react-native-async-storage/async-storage
- 决策:内存态(Module-level state),App 重启会丢失
- 理由:
- 保持 0 新增 npm 依赖红线(PMG V8.3 §前端工程红线)
- 开发期通过 DEV_TEST_USERS Mock 重新登录即可恢复
- P1 WP-04 后端接口就绪后再评估接入 AsyncStorage
- 影响:RN 端 App 重启需重新登录(开发期可接受)
D2:3 个 Web 端 lib/auth.ts TOKEN_KEY 统一为 dip1.token
- 背景:原 mfr-portal / opr-admin 用
dip1.jwt,shared 包用 dip1.token
- 决策:统一为
dip1.token(对齐 shared TOKEN_STORAGE_KEY)
- 理由:
- WhoamiProvider 写入
dip1.token,lib/auth.ts getToken 也读 dip1.token,保证 API client 自动携带 token
- 兼容现有 register/page.tsx 等使用 setToken/getToken 的页面,无需改造
- 影响:旧
dip1.jwt key 中的 token 会失效,用户需重新登录(开发期可接受)
D3:tsconfig paths 移除 @dip1/shared/* 通配符
- 背景:原 paths 有
@dip1/shared/* → ../../packages/shared/src/*,让 webpack 通过 paths 解析子路径
- 问题:paths 解析绕过了 node_modules + package.json exports,导致 'use client' 边界处理失效,SSR 时 WhoamiProvider 为 undefined
- 决策:移除
@dip1/shared/* 通配符,仅保留 @dip1/shared 主路径
- 结果:tsc + webpack 都通过 node_modules + package.json exports 解析
@dip1/shared/client,'use client' 边界正确传递
- 验证:7/7 typecheck 通过 + 8/8 路由 200 OK
D4:packages/shared/src/client/index.ts 加 "use client"
- 背景:client/index.ts 是 .ts 文件(无 JSX),re-export 了 'use client' 的 WhoamiProvider
- 问题:Next 14 SSR 编译时,re-export 文件缺少 'use client' 指令,client reference 未传递,导致 WhoamiProvider 在 SSR 时为 undefined
- 决策:在 client/index.ts 顶部加
"use client" 指令
- 理由:Next 14 文档明确要求 re-export 'use client' 组件的文件也需要 'use client'
- 验证:修复后 SSR 渲染正常
D5:RN 端 tsconfig 加 moduleResolution: "bundler"
- 背景:expo/tsconfig.base 默认 moduleResolution 不支持 package.json exports 子路径
- 问题:tsc 报
Cannot find module '@dip1/shared/client.rn'
- 决策:在 cst-app 和 wkr-app 的 tsconfig.json 中显式加
moduleResolution: "bundler"
- 结果:tsc 能解析
@dip1/shared/client.rn 子路径
五、阻塞与解决
B1:tsconfig paths 通配符绕过 'use client' 边界
- 现象:OPR / MFR dev server 报 500,digest
2152732032,错误 "Element type is invalid: expected a string ... but got: undefined"
- 根因:tsconfig paths
@dip1/shared/* 让 webpack 通过源文件路径解析 @dip1/shared/client,绕过了 node_modules + package.json exports,导致 Next 14 的 'use client' 边界处理失效
- 解法:移除 paths 通配符
@dip1/shared/*,仅保留主路径 @dip1/shared;让 tsc + webpack 都通过 node_modules + package.json exports 解析子路径
- 验证:7/7 typecheck + 8/8 路由 200 OK
B2:client/index.ts re-export 缺 'use client'
- 现象:即使 paths 修复后,SSR 仍然 500(digest 不变,说明 webpack 缓存命中)
- 根因:client/index.ts 是 .ts 文件,re-export 'use client' 的 WhoamiProvider,但自身无 'use client' 指令,Next 14 SSR 时 client reference 未传递
- 解法:client/index.ts 顶部加
"use client" 指令
- 验证:清理 .next 缓存 + 重启 dev server 后 8/8 路由 200 OK
B3:webpack 缓存命中导致修复不生效
- 现象:修改 client/index.ts 后,dev server 仍报相同 digest 错误
- 根因:Next 14 dev server 的 webpack 缓存命中,没有重新编译
- 解法:停掉 OPR / MFR / LGP 三个 dev server,删除 .next 缓存目录,重启 dev server
- 验证:30s 编译完成后 8/8 路由 200 OK
六、验证结果
6.1 typecheck
7/7 projects 0 errors:
- @dip1/shared ✅
- @dip1/ui-web ✅
- @dip1/cst-app ✅
- @dip1/wkr-app ✅
- @dip1/lgp-portal ✅
- @dip1/mfr-portal ✅
- @dip1/opr-admin ✅
6.2 冒烟测试(8 路由)
| URL |
状态 |
Length |
| http://localhost:3000/login (OPR) |
200 |
6,696 |
| http://localhost:3001/login (MFR) |
200 |
7,253 |
| http://localhost:3002/login (LGP) |
200 |
6,830 |
| http://localhost:3000/cpt (OPR) |
200 |
26,650 |
| http://localhost:3000/wkr-demo (OPR) |
200 |
21,029 |
| http://localhost:3001/quote/demo (MFR) |
200 |
38,902 |
| http://localhost:3000/dashboard (OPR) |
200 |
9,272 |
| http://localhost:3001/quote (MFR) |
200 |
18,012 |
6.3 登录页关键元素验证
| 端 |
关键元素 |
结果 |
| OPR /login |
page-title / page-sub / login-box / btn / Dev JWT / Keycloak / textarea / submit |
8/8 ✅ |
| MFR /login |
page-title / page-sub / login-box / btn / Dev JWT / PPL / textarea / register / mfr-test |
9/9 ✅ |
七、本轮合规边界确认(PMG V8.3 三 AI 隔离)
| 行为 |
规则 |
本轮执行 |
合规性 |
| 听码 WDE git push / Gitee 远程 |
❌ 严禁 |
✅ 未执行 |
✅ 合规 |
| 听码 WDE 生产部署 / ocm/ 目录 |
❌ 严禁 |
✅ 未执行 |
✅ 合规 |
| 文档日志即时性 |
✅ 强制 |
✅ 任务完成立即写 DTL |
✅ 合规 |
| 0 新增 npm 依赖 |
✅ 红线 |
✅ RN 版 Provider 用内存态,无 AsyncStorage |
✅ 合规 |
八、WP-06 收尾后剩余 TODO
| 项 |
依赖 |
启动条件 |
后端 /api/v2/bff/whoami 接口实现 |
后端开发排期 |
后端就绪后 |
| RN 端接入 AsyncStorage 持久化 |
P1 WP-04 后端接口 + AsyncStorage 依赖评估 |
P1 启动后 |
| 5 端登录页接入 Keycloak 25 OIDC 授权码流程 |
后端 Keycloak 配置 + RS256 |
V2 阶段 |
九、关联引用
十、修订记录
| 版本 |
日期 |
修订人 |
修订内容 |
| V1.0 |
2026-08-25 |
DT |
初始版本:WP-06 收尾交付(5 端 Provider 注入 + 登录页接入 + RN 版 Provider + 5 设计决策 + 3 阻塞记录) |
本日志为 WP-06 收尾最终交付。后续 P1 WP-04/05/07 启动需后端接口就绪 + 用户明确指令。