Files
Neo-ZQYY/docs/_overview/04b-feedback/P1-3-4-cross-page-params.md
Neo 509cf43284 chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引,
解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。

主要内容:
- 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系
- 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 /
  7 业务概念 / 8+1 AI 矩阵 / 22 术语)
- 02a-miniprogram-page-matrix 小程序 21 页业务指纹
- 02b-adminweb-page-matrix admin-web 19 路由业务指纹
- 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算)
- 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项)
- 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定)
- 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留)
- WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日)
- WAVE-1-KICKOFF.md Wave 1 实施 kickoff
- GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板

反馈调研产物:
- 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出)
- 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出)
- 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出)
- NEO-DECISIONS-LOG 累积决策记录

关键追加发现 8 处 D Bug(原蓝本 0):
- P0-3 看板沙箱接入(Wave 1 W1-T1)
- P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit 17f045a)
- P0-5 致命 2 (JWT aud 缺失, 已修 commit 17f045a)
- P0-6 clearAllTasks 守卫 (Wave 3)
- P0-8 DBViewer 黑名单漏 (已修 commit 17f045a)
- P1-3 task-detail 跳转传 task_id 而非 customer_id
- P2-7 board-finance 隐式 null
- 2 个独立 Bug (page_context.created_at + ClueCategory 字典)

参考: docs/_overview/00-index.md
2026-05-04 07:38:28 +08:00

368 lines
24 KiB
Markdown

# 小程序跨页传值规范性深度调研(P1-3 + P1-4 触发)
> 日期: 2026-05-04
> 触发: Neo 在 04b P1-3 + P1-4 反馈"同意初步判断,但谨慎起见,要从页面和产品设计出发,对业务进行理解的情况下,深入调研这个问题(尤其整个 APP 页面与角色的传值规范性方面)"
> 范围: 小程序 21 页全部跨页跳转 + 角色权限相关传值 + 公共组件 ai-float-button / board-tab-bar
> 调研口径: **只读不改**, 单文档产出
> 状态机考量: 仅 5 类跳转 API: `wx.navigateTo` / `wx.redirectTo` / `wx.switchTab` / `wx.reLaunch` / `wx.navigateBack`
---
## 一、跳转矩阵全表
下表覆盖小程序 21 页全部 `wx.navigateTo` / `wx.redirectTo` / `wx.switchTab` / `wx.reLaunch` 调用(`wx.navigateBack` 仅在"目标页期望"列出现)。
代号约定:
- `id` 默认指资源主键(taskId / customerId / coachId / historyId, 视目标页而定)
- `--` 表示无参数(空 URL)
- onLoad 期望字段以代码实际读取为准
| # | 源页 | 跳转方法 | 目标页 | 参数 key:value | 目标页 onLoad 期望字段 | 是否一致 | 问题类型 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | login | reLaunch | task-list / board-finance / my-profile (动态首页) | -- | -- | 一致 | -- |
| 2 | login | reLaunch | reviewing | -- | -- | 一致 | -- |
| 3 | login | reLaunch | apply | -- | -- | 一致 | -- |
| 4 | login | reLaunch | no-permission | -- | -- | 一致 | -- |
| 5 | apply | reLaunch | login (兜底) | -- | -- | 一致 | -- |
| 6 | apply | reLaunch | reviewing | -- | -- | 一致 | -- |
| 7 | apply | reLaunch | no-permission | -- | -- | 一致 | -- |
| 8 | apply | reLaunch | <动态首页 by getRoleHome> | -- | -- | 一致 | -- |
| 9 | apply | navigateBack(fail→reLaunch login) | 上一页 / login | -- | -- | 一致 | -- |
| 10 | reviewing | reLaunch | <动态首页> / no-permission / apply / login | siteCode + role + phone + employeeNumber (cancel 重申请) | apply.onLoad 解 4 字段 | 一致 | -- |
| 11 | no-permission | reLaunch | login | -- | -- | 一致 | -- |
| 12 | no-permission | reLaunch | <动态首页> | -- | -- | 一致 | -- |
| 13 | no-permission | reLaunch | apply | siteCode + role + phone + employeeNumber | apply.onLoad 同上 | 一致 | -- |
| 14 | no-permission | reLaunch | reviewing | -- | -- | 一致 | -- |
| 15 | task-list | navigateTo | task-detail | id=<task.id> | id 或 memberId | 一致 | -- |
| 16 | task-list | navigateTo | performance | -- | scrollToBottom 可选 | 一致 | -- |
| 17 | task-list | navigateTo | performance | scrollToBottom=1 | scrollToBottom 解析 '1' | 一致 | -- |
| 18 | task-list | navigateTo | chat | taskId=<task.id> | taskId 分支 | 一致 | -- |
| 19 | my-profile | navigateTo (utils/router.navigateTo) | chat-history / performance / notes | -- | -- | 一致 | -- |
| 20 | my-profile | reLaunch | login | -- | -- | 一致 | -- |
| 21 | task-detail | navigateBack | 上一页 | -- | -- | 一致 | -- |
| 22 | task-detail | navigateTo | chat | taskId=<detail.id> | taskId 分支(contextType=task) | 一致 | -- |
| 23 | task-detail | navigateTo | customer-service-records | customerId=<detail.customerId 或 fallback 到 detail.id> | customerId 或 id | **不一致(B)** | P1-3 |
| 24 | performance | navigateBack(fail→switchTab task-list) | 上一页 / 任务 tab | -- | -- | 一致 | -- |
| 25 | performance | navigateTo | performance-records | -- | -- | 一致 | -- |
| 26 | performance | navigateTo | task-detail | memberId=<member.memberId> | id 或 memberId | 一致(2026-03-25 修复) | 历史 P1-4 |
| 27 | performance | navigateTo | task-detail | id=<rec.taskId> 或 memberId=<rec.memberId> | id 或 memberId | 一致 | -- |
| 28 | performance | navigateTo | performance-records | -- | -- | 一致 | -- |
| 29 | performance-records | navigateBack(fail→switchTab) | 上一页 | -- | -- | 一致 | -- |
| 30 | performance-records | navigateTo | task-detail | memberId=<row.memberId> | id 或 memberId | 一致 | -- |
| 31 | board-finance | switchTab/redirectTo | board-finance / board-customer / board-coach | -- | -- | 一致 | -- |
| 32 | board-customer | switchTab/redirectTo | 同上 | -- | -- | 一致 | -- |
| 33 | board-customer | navigateTo | customer-detail | id=<row.id> | id 或 customerId | 一致 | -- |
| 34 | board-coach | switchTab/redirectTo | 同上 | -- | -- | 一致 | -- |
| 35 | board-coach | navigateTo | coach-detail | id=<row.id> | id (仅) | 一致 | -- |
| 36 | customer-detail | navigateTo | customer-records | customerId=<detail.id> | customerId 或 id | 一致 | **D(命名/语义混乱)** P1-10 |
| 37 | customer-detail | navigateTo | chat | customerId=<detail.id> | customerId 分支 | 一致 | -- |
| 38 | customer-records | -- | (无下游跳转) | -- | -- | -- | -- |
| 39 | customer-service-records | navigateBack | 上一页 | -- | -- | 一致 | -- |
| 40 | coach-detail | navigateTo | customer-detail | id=<cid> (3 个不同入口: 最近服务客户/收藏客户/常客) | id 或 customerId | 一致 | -- |
| 41 | coach-detail | navigateTo | coach-service-records | coachId=<coachId> | coachId(必填,缺失则退回) | 一致 | -- |
| 42 | coach-detail | navigateTo | chat | coachId=<id> | coachId 分支 | 一致 | -- |
| 43 | coach-service-records | navigateBack(fail→switchTab board-finance) | 上一页 / 看板 | -- | -- | 一致 | -- |
| 44 | coach-service-records | navigateTo | customer-detail | id=<memberId> | id 或 customerId | 一致(memberId<=0 拦截) | 关联 P1-12 |
| 45 | chat | navigateBack | 上一页 | -- | -- | 一致 | -- |
| 46 | chat | navigateTo | <动态 link>(消息内可点链接) | -- | -- | 取决于 link 内容 | **D(无校验)** |
| 47 | chat-history | navigateBack | 上一页 | -- | -- | 一致 | -- |
| 48 | chat-history | navigateTo | chat | historyId=<row.id> | historyId 分支 | 一致 | -- |
| 49 | dev-tools | reLaunch | <任意页 by dataset.url> | (跟随 url 拼接) | -- | 一致 | -- |
| 50 | ai-float-button(组件,被 6 页引用) | navigateTo | chat | sourcePage / pageFilters / customerId(可选) | sourcePage 分支(看板类) | 一致 | -- |
| 51 | board-tab-bar(组件) | switchTab | task-list / board-finance / my-profile | -- | -- | 一致 | -- |
| 52 | dev-fab(组件) | navigateTo | dev-tools | -- | -- | 一致 | -- |
| 53 | notes | navigateBack | 上一页 | -- | -- | 一致 | -- |
合计: 53 条跳转(合并重复入口后), 其中 **2 条不一致 / 命名混乱**(行 23、36), 1 条无校验(行 46)。
---
## 二、规范性问题清单(按严重度)
### P0 级(语义错位,功能不通)
#### 问题 P0-1: task-detail → customer-service-records 传 detail.id 当 customerId 用 (即 P1-3 后半段)
- 源页: `pages/task-detail/task-detail.ts` L437-441
- 目标页: `pages/customer-service-records/customer-service-records.ts`
- 现状:
```ts
const customerId = (this.data.detail as any)?.customerId || this.data.detail?.id || ''
wx.navigateTo({ url: `/pages/customer-service-records/customer-service-records?customerId=${customerId}` })
```
当 TASK-2 响应未返回 `customerId` 字段时, fallback 拿 `detail.id`(taskId), 把 taskId 当作 customerId 拼到 URL.
- 目标页 onLoad: `const id = options?.customerId || options?.id || ''`, 拿到的是 taskId, 后续按 taskId 调 `fetchCustomerServiceRecords` 必然返回空或错乱.
- 期望: TASK-2 响应里加 `customer_id` / `member_id` 字段, 前端不再用 fallback.
- 修复方案:
- 后端: `GET /api/xcx/tasks/{id}` 响应增加 `customerId`(JOIN dim_member);
- 前端: 删除 `|| this.data.detail?.id` 的 fallback, 拿不到 customerId 时显式 toast"客户信息缺失".
#### 问题 P0-2: chat 多入口路由当前实现一致性不足 (P1-11)
- 源页: 3 入口
- `task-detail` → `chat?taskId=`(已正确, 走 contextType=task)
- `customer-detail` → `chat?customerId=`(已正确, contextType=customer)
- `coach-detail` → `chat?coachId=`(已正确, contextType=coach)
- `chat-history` → `chat?historyId=`(走历史 chatId)
- `task-list 上下文菜单"问问助手"` → `chat?taskId=`(已正确)
- `ai-float-button` → `chat?sourcePage=...&pageFilters=...&customerId=...`(看板类入口)
- 目标页 onLoad: 已经根据 `historyId / taskId / customerId / coachId / sourcePage / 无参` 6 分支处理
- 现状评估: 与 04b P1-11 描述存在偏差 — chat.ts L214-263 已经做了 6 分支, P1-11 描述的"loadMessages 仅用 customerId"已不适用(代码已演进)
- 残留风险:
- 后端接口侧: `fetchChatMessagesByContext(contextType, contextId)` 是否后端已支持 4 种 contextType(task / customer / coach / general / board-*) 待验证
- `general` / `board-*` 几个 contextType 的 chatId 派生策略是否后端已实现待与后端核对
- 修复方案: 与后端核对 `GET /api/xcx/chat/messages?contextType=...&contextId=...` 是否已上线全部 contextType 路由
- 建议判定: **降为 P1**(前端代码已修, 后端契约待补)
### P1 级(命名/语义混乱, 功能可走通但易踩坑)
#### 问题 P1-1: customer-detail "消费记录"卡片绑定的方法名是 onViewServiceRecords 但实际跳消费记录页 (P1-10 的根因)
- 源页: `customer-detail.wxml` L177-181
```xml
<!-- 消费记录 -->
<view class="card-header" bindtap="onViewServiceRecords">
<text class="section-title title-orange">消费记录</text>
</view>
```
- 处理: `onViewServiceRecords` 实际跳 `customer-records`(消费视角), 名字写成"Service"完全是误导.
- 影响: 看代码读不出真实跳转目标; 改 bug 时容易跳错页面.
- 期望: 方法名改为 `onViewConsumptionRecords` / `goConsumptionRecords`, 与文案"消费记录"一致.
- 修复方案: 简单重命名(WXML + TS), 不影响功能.
- 与 P1-10 关系: 04b P1-10 询问"查看消费记录跳哪个目标页", 答案是 **customer-records**(消费记录页, 2026-03-29 新建), 现状已正确, 但 **方法命名误导**.
#### 问题 P1-2: task-detail.onLoad fallback 优先级模糊
- 现状: `onLoad({ id?, memberId? })` 同时支持两种语义, 但是当 url 里 **同时** 出现 `?id=xxx&memberId=yyy` 时 优先使用 id 走 loadData, 不走 memberId.
- 入口分布: 5 个入口都只传一个键, 故无实际冲突, 但接口边界模糊(若未来其他页传两个键, 行为不直观).
- 期望: 显式声明"id 优先, 仅 id 缺失时才用 memberId", 在文档里写清楚.
#### 问题 P1-3: 字段命名风格不统一 (camelCase vs lowercase)
- 全局清单(纯 URL key, 不含值):
- `id`(单字母 / 没有所属命名空间) → 使用面: 大量(task-list, board-customer, board-coach, coach-service-records, customer-records, customer-service-records 等都用)
- `customerId` / `coachId` / `taskId` / `historyId` / `memberId` / `siteCode` / `employeeNumber` / `sourcePage` / `pageFilters` / `scrollToBottom` / `phone` / `role` / `timeDimension` / `areaFilter` / `dimension` / `typeFilter` / `projectFilter`
- 风格: 以 camelCase 为主. 问题:
- **`id` 缺乏所属语义**: 同一个 `id` 在 task-list 是 taskId, 在 board-customer 是 customerId, 在 board-coach 是 coachId. 排查时容易看 url 看错.
- **混用 `id` 与 `customerId`**: customer-records / customer-service-records / customer-detail 三页的 onLoad 都做了 `options?.customerId || options?.id` 兼容, 这种"二选一"兼容是历史污渍.
- 期望: 统一约定"目标页接收明确语义键(taskId / customerId / coachId), 不再接受裸 `id`"; 兼容期保留 `id || customerId` 的回退, 但所有 navigateTo 调用方迁移到明确键.
#### 问题 P1-4: 字段类型(string vs number)隐式不一致
- url 参数本质是 string, 但有些目标页直接 `Number()` 转换(如 coach-service-records.onLoad), 有些直接用 string(如 customer-records 把 id 写入 setData 用作 string).
- 现状:
- coach-service-records: `Number(options?.coachId)` 后判断 `Finite && >0`, 否则退回(已正确)
- customer-detail / customer-records / customer-service-records / coach-detail: 全部用 string, 后端调用时再做转换
- task-detail.loadByMember: `String(detail.id)` 显式转换
- 期望: URL 参数全部按 string 处理, 调后端时转换由 service 层做; 业务侧不要混用.
### P2 级(兜底缺失 / 体验问题)
#### 问题 P2-1: 多数目标页缺少必填字段缺失时的 toast / fallback
- 现状: 仅 `coach-service-records` 在 coachId 缺失时有 `wx.showToast('缺少助教标识') + setTimeout navigateBack` 的兜底.
- 其他页(customer-detail / customer-records / customer-service-records / customer-detail / coach-detail / task-detail / chat)在拿不到必填字段时, 大多数走"setData empty / pageState='empty'"或者直接调后端拿空响应, **不会 toast 提醒**.
- 期望: 关键详情页(customer-detail / coach-detail / task-detail / customer-service-records / coach-service-records / customer-records / chat) 全部加必填字段缺失 toast.
#### 问题 P2-2: chat 内嵌消息 link 跳转无安全校验
- chat.ts L552-556: `wx.navigateTo({ url: link })` — link 来自 AI 回复消息中的可点链接, 无白名单校验.
- 风险:
- link 若指向 tabBar 页面会失败(navigateTo 不能去 tab 页)
- link 包含恶意构造参数时无防护
- 期望: 增加白名单(只允许 `/pages/...` 前缀 + 已知页面路径), navigateTo 失败时降级 switchTab.
#### 问题 P2-3: dev-tools.ts 的 reLaunch 跳"任意页"无白名单校验
- dev-tools.ts L156-158: `const url = "/" + e.currentTarget.dataset.url; wx.reLaunch({ url })` — 来自 dataset, 调试页可接受, 但生产编译要确保 dev-tools 入口被关闭(已有 `wx:if="{{false}}"` 隐藏 dev-fab).
#### 问题 P2-4: customer-detail 跳 customer-records 时 banner 字段重复加载
- customer-detail 已有完整客户信息, customer-records.onLoad 只接 customerId, 然后再调 `fetchCustomerConsumptionRecords` 重新拉 banner.
- 后果: 多一次接口往返, 但跳转后用户在 customer-records 看到的 banner 数据是新 fetch 的, 与 customer-detail 一致性不强.
- 期望: 后端响应里 banner 字段(name/avatar/storage)由 customer-records 自己拉(已是现状), 不需要前端透传; 若考虑性能, 可走 globalData 暂存上一页客户对象, 但这是优化项.
### 角色相关传值问题清单(独立维度)
#### 问题 R-1: 跳转链路中没有携带角色信息
- 现状: 所有 navigateTo / redirectTo 调用 **都不带 role 参数**. 目标页通过 `checkPageAccess(pageRoute)` 在 onShow 调 `auth-guard` 模块从 globalData(后端 fetchMe 返回)读取角色和权限码.
- 这是合理设计: URL 不应携带角色, 角色应来自后端权威源.
- 但意味着: 如果后端 permissions 与页面预期角色不匹配(例如散客访问助教页), 由 auth-guard 拦截并跳 no-permission, 不靠 URL 角色.
#### 问题 R-2: site_id(门店 ID)切换不影响传值
- site_id 不出现在任何 URL 参数里, 后端通过 JWT + `app.current_site_id` 会话变量过滤. 这是 RLS 双 schema 设计, 跨店切换由后端处理, 前端 URL 不感知, **正确无问题**.
#### 问题 R-3: 散客模式(P1-12)的判断分散
- 当前 `coach-service-records` 用 `Number(options?.coachId) > 0` 判合法, `performance.onCustomerTap` 用 `mid <= 0` 判散客拦截.
- 散客约定不统一(NULL / 0 / -1 / <=0 都有兼容代码), 这是 P1-12 的范畴, 与传值规范同源.
- 期望: 散客统一约定 + 跳转拦截统一抽到 utils 函数(如 `isScattered(memberId)`).
---
## 三、传值规范建议
### 3.1 字段命名规范
强制约定:
- URL 参数键统一使用 **camelCase**(已是事实标准, 文档化)
- **禁止使用裸 `id`**, 必须带语义前缀: `taskId` / `customerId` / `coachId` / `historyId` / `memberId` / `noteId` / `recordId`
- 兼容期 `id || customerId` 的双键兼容保留 6 个月, 之后强制只接受语义键
### 3.2 参数 key 命名约定(全集 + 语义)
| key | 含义 | 类型 | 来源 / 出现页 |
| --- | --- | --- | --- |
| `taskId` | 任务 id | string(数字字符串) | task-list / chat / task-detail / performance |
| `customerId` | 客户(会员) id, 等价 memberId | string | task-detail / customer-detail / customer-records / customer-service-records / chat |
| `coachId` | 助教 id | string | coach-detail / coach-service-records / chat |
| `historyId` | 对话历史 chatId | string | chat-history / chat |
| `memberId` | 会员 id (与 customerId 同义) | string(数字字符串) | performance / performance-records / task-detail / coach-service-records |
| `siteCode` | 门店编码(申请预填) | string(URI 编码) | reviewing → apply / no-permission → apply |
| `employeeNumber` | 员工号(申请预填) | string(URI 编码) | 同上 |
| `phone` | 手机号(申请预填) | string(URI 编码) | 同上 |
| `role` | 角色(申请预填) | string(URI 编码) | 同上 |
| `sourcePage` | AI 上下文来源页 | string(枚举) | ai-float-button → chat |
| `pageFilters` | AI 看板筛选参数集 | string(URI + JSON) | ai-float-button → chat |
| `scrollToBottom` | 业绩页滚到底部标记 | string('1' / 缺省) | task-list → performance |
| `timeDimension` 等单键过滤 | 旧入口兼容 | string | ai-float-button → chat(回退路径) |
**统一约定**:
- `customerId` 与 `memberId` **同义**(后端 dim_member.id), 但小程序前端两套用法都存在. 期望: 跨页 URL 统一 `customerId`, 内部页面 data 里可以叫 `memberId`(看后端字段名). 迁移历史 url 期 `memberId` 兼容期 6 个月, 之后只接 `customerId`.
### 3.3 目标页 onLoad 必填字段校验模式
推荐模板(以 coach-service-records 为标杆):
```ts
onLoad(options: Record<string, string | undefined>) {
const requiredId = Number(options?.coachId)
const validId = Number.isFinite(requiredId) && requiredId > 0 ? requiredId : 0
if (validId === 0) {
wx.showToast({ title: '缺少助教标识', icon: 'none' })
setTimeout(() => wx.navigateBack({
fail: () => wx.switchTab({ url: '/pages/board-finance/board-finance' })
}), 1000)
return
}
this.setData({ coachId: validId })
this.loadData(validId)
}
```
**所有详情页应遵循这个模板**:
1. 把 `options?.<key>` 提取出来做类型/合法性校验
2. 缺失时明确 toast(中文文案 + icon='none')
3. setTimeout 后 navigateBack(并提供 fail 兜底, 防止从分享链接首页直入)
4. 合法时再 setData + 触发数据加载
### 3.4 跨角色 / 跨店传值约定
- **角色**: 不通过 URL 携带, 由 auth-guard 从 globalData / fetchMe 读取
- **门店**: 不通过 URL 携带, 由 JWT + 后端 RLS 处理
- **散客判断**: 在跳转源头拦截(`memberId <= 0` 一律不跳, toast"未知客户不提供查看详情"), 等待 P1-12 决策后将判断函数集中到 `utils/customer.ts`(待新建)
---
## 四、修复方案分组
### 必修(P0): 后端补字段 + 前端去 fallback
| 项 | 模块 | 改动 | 工作量 |
| --- | --- | --- | --- |
| F1 | 后端 TASK-2 | `GET /api/xcx/tasks/{id}` 响应增加 `customerId`(JOIN dim_member 由 task.member_id 派生) | 小 |
| F2 | 前端 task-detail | 删除 `customerId = detail?.customerId || detail?.id` 的 fallback, 缺失时显式 toast | 小 |
| F3 | 后端 chat by-context | 验证 `GET /api/xcx/chat/messages?contextType=task/customer/coach/general/board-*&contextId=` 已支持全部 contextType | 中 |
### 应修(P1): 字段命名/类型统一 + 命名重构
| 项 | 模块 | 改动 | 工作量 |
| --- | --- | --- | --- |
| F4 | customer-detail.ts + customer-detail.wxml | `onViewServiceRecords` 重命名为 `onViewConsumptionRecords`, WXML bindtap 同步 | 极小 |
| F5 | board-customer / board-coach | 跳 customer-detail / coach-detail 时, 把 `?id=` 改为 `?customerId=` / `?coachId=`(语义化), 目标页保留兼容期 | 小 |
| F6 | task-list | 跳 task-detail 时, `?id=` 改为 `?taskId=`(目标页兼容期保留 id) | 小 |
| F7 | coach-service-records | 跳 customer-detail 时, `?id=${memberId}` 改为 `?customerId=${memberId}` | 极小 |
| F8 | docs(SPEC) | 制定"小程序跨页传值规范"SPEC, 见 §五.建议 | 中 |
### 可修(P2): 兜底补全 + 安全加固
| 项 | 模块 | 改动 | 工作量 |
| --- | --- | --- | --- |
| F9 | customer-detail / coach-detail / task-detail / customer-records / customer-service-records / coach-service-records / chat | 必填字段缺失统一加 toast + 退回上一页 | 中 |
| F10 | chat 消息 link 跳转 | 增加白名单(`/pages/` 前缀 + 已知页面集), 失败降级 switchTab | 小 |
| F11 | utils/customer.ts | 新建 `isScattered(memberId)` 函数, 统一散客判断, 等 P1-12 决策后填实现 | 小 |
### 已确认无需修(误报或已修)
- **performance → task-detail 传 customerName 而非 task_id (P1-4)**: 已于 2026-03-25 修复, 现 `onCustomerTap` 传 `memberId`, `onRecordTap` 优先传 `taskId` fallback `memberId`. **04b P1-4 描述已过时, 应在 04b 反馈中标注"已修复"**.
- **chat.loadMessages 仅用 customerId (P1-11)**: 已于多次迭代修复, 现 chat.onLoad 走 6 分支(historyId / taskId / customerId / coachId / sourcePage / 无参), **04b P1-11 描述已过时**, 仅后端契约待核对.
---
## 五、给 Neo 的决策清单
### 决策 D1: 是否同意"必修"清单全部进 Wave 1-3?
**子项**:
- D1a: F1 + F2(后端 TASK-2 补 customerId, 前端去 fallback) — 是否进 Wave 1?
- D1b: F3(后端 chat by-context 全 contextType 验证) — 是否单独立项核对后端实现?
### 决策 D2: 是否同意"应修"清单的命名重构?
**子项**:
- D2a: F4(customer-detail 方法名重命名) — 极小工作量, 建议直接合并到 P0 修复 PR
- D2b: F5 + F6 + F7(URL 参数从裸 `id` 迁移到语义键) — 涉及 4 页, 建议在 Wave 2 单独立项, 跨页协调
- D2c: F8(SPEC 化) — 是否新建 `docs/miniprogram-dev/spec/cross-page-params-spec.md`?
### 决策 D3: 是否同意"可修"清单的优先级排序?
**子项**:
- D3a: F9(必填字段 toast 兜底) — 7 个页面, 中等工作量, 体验提升明显
- D3b: F10(chat link 白名单) — 安全加固, 轻量
- D3c: F11(散客判断集中) — 等 P1-12 决策后再做
### 决策 D4: P1-4 与 P1-11 在 04b 文档中的状态更新
- 是否同意将 P1-4 标注"已修复(2026-03-25), 仅文档过时"?
- 是否同意将 P1-11 标注"前端已修(6 分支), 后端契约待核对"?
### 决策 D5: 是否制定独立 SPEC
建议新建 `docs/miniprogram-dev/spec/cross-page-params-spec.md`, 内容包含:
- §3.1-3.4 的命名规范、key 全集、onLoad 模板、跨角色约定
- 历史兼容期(双键)的退役时间表
- 测试矩阵(每个跳转的合法/缺失/类型错路径)
---
## 六、调研附录
### 6.1 调研覆盖度
- 21 页 100% 覆盖(login / apply / reviewing / no-permission / task-list / board-finance / my-profile / task-detail / notes / performance / performance-records / board-customer / board-coach / customer-detail / customer-service-records / customer-records / coach-detail / coach-service-records / chat / chat-history / dev-tools)
- 公共组件 ai-float-button / board-tab-bar / dev-fab 100% 覆盖
- utils/router.ts / utils/auth-guard.ts(getRoleHome / checkPageAccess) 已查
- 共抓取 53 条跳转(去重前约 70 条), 含组件触发跳转
### 6.2 调研口径限制
- 仅调研代码现状, 未跑实际跳转流程验证
- 未深入后端 API 契约 — F3(chat by-context) 待与后端核对
- 未涉及 H5 原型 / demo-miniprogram(MOCK 标杆禁改) — 设计参考另议
### 6.3 与 04b 已知问题对照
| 04b 问题 | 调研结论 | 后续动作 |
| --- | --- | --- |
| P1-3 (task-detail → chat / customer-service-records 传 detail.id) | chat 路径已修, customer-service-records 路径仍是 fallback bug | F1+F2 必修 |
| P1-4 (performance → task-detail 传 customerName) | 已于 2026-03-25 修, 04b 描述过时 | 仅更新 04b 状态 |
| P1-10 (customer-detail "查看消费记录"跳哪) | 跳 customer-records(消费记录页, 现状正确), 但方法名 onViewServiceRecords 误导 | F4 重命名 |
| P1-11 (chat 多入口 / loadMessages 仅用 customerId) | 前端已 6 分支, 后端契约待核 | F3 待核对 |
| P1-12 (散客 memberId 取值约定) | 与 R-3 / F11 同源 | 等 Neo 决策 + 抽函数 |
---
> 本调研结论与 Neo 反馈的"初步判断不变"一致: **同意必修后端补 task_id / customer_id 字段 + 前端改跳转参数**, 且额外发现 P1-1(命名误导)、P1-3(裸 id 不规范)等若干非 P1-3/P1-4 问题, 建议一并制定 SPEC 系统化解决.