docs(spec): 微信员工 stat modal Phase B 设计(列对齐云客 + 客户微信→带窗聊天)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-08 13:12:13 +08:00
parent 5fd9878c38
commit 2a33f944ec
@@ -0,0 +1,114 @@
# 微信员工 stat modal 列对齐 + 客户微信→聊天(Phase B)设计
> 日期:2026-08-08|分支:`feat/yunke-data-mirror`|路线图 #2 Phase B
> 前置:Phase A 完成(`GET /api/staff-chat-stats` 聚合端点 + StaffList 5 数字改读本地 summary + 日期选择器 + modal 按 metric 过滤+合成分页)。
> 数据/交互:**全本地**(运行时不碰云客)。所有云客行为 **2026-08-08 Playwright 真机核实**(crm2.yunkecn.com `/cms/wechat/wechatlist`)。
> 上游设计:`docs/superpowers/specs/2026-08-05-yunke-ui-alignment-design.md`(列+交互对齐云客,element plus 自然样式)。
## 1. 目标与范围
**目标**:Phase A 的 5 数字 stat modal 与云客完全对齐——补齐云客的列(序号/员工信息/员工微信),并让 modal 内「客户微信」可点 → 该员工×客户的聊天详情(带 Phase A 日期窗过滤)。
**在本 plan(Phase B)**:
1. **StaffList stat modal 列对齐云客**:`序号 / 客户微信(可点→聊天) / [仅有效沟通:发送消息数·接收消息数] / 员工信息 / 员工微信`。
2. **modal 客户微信 二级可点**:点客户微信 → `/chat/wechat/{员工wxid}/{客户talker_id}?start=&end=`(同页 `router.push`,带 StaffList 当前日期窗)。
3. **ChatTimeline 读日期窗 query**:`?start=&end=` 存在时按窗过滤消息(复用 `getMessages` 已有的 start/end);不存在时全量(现行行为,向后兼容)。
**不在本 plan(后续 / 总 spec 第 5 步)**:
- 聊天页**页内日期选择器**、**会话列表**、`/api/sessions` 聚合端点 → 总 spec 第 5 步「ChatTimeline 对齐」。
- 员工姓名 → 云客独立员工详情页(`userDetailNew`)、主 badge/昵称 → 云客聊天窗口(会话列表)→ 第 5 步(本期维持 Plan 1 降级:drawer / 跳最近一条聊天)。
- 新标签页(`window.open`)—— 云客 3 处点击都新开 tab,我们用 SPA 同页 `router.push`(做法 A 已定)。
- 客户微信**脱敏**显示 —— 不做(见 §4 P2)。
## 2. 云客真机核实(2026-08-08)
### 2.1 stat modal 列(点 5 数字弹出)
| modal | 云客列(逐列 DOM 核实) |
|---|---|
| 沟通人数 / 跟进客户数 / 员工最后回复数 / 未回复客户数 | 序号 / 客户微信(可点) / 员工信息 / 员工微信 |
| 有效沟通数 | 序号 / 客户微信(可点) / **发送消息数 / 接收消息数** / 员工信息 / 员工微信 |
- **客户微信** = `<a href="/cms/wechat/detail/message/{ownerWxid}/{talkerWxid}/1?start=&end=">`,文本脱敏「昵称(备注)微信号」。**唯一可点列**。
- **员工信息** = `<div><p>员工姓名</p><p>微信手机号</p></div>`(纯展示)。
- **员工微信** = 头像 + 昵称 + 微信号(纯展示)。
- 末尾空表头是 Ant 的**滚动条占位 cell,不是操作列**(modal **无按行操作按钮**)。
### 2.2 modal 客户微信 → 聊天详情
点客户微信 `<a>` **新开 tab** 到 `/cms/wechat/detail/message/{ownerWxid}/{customerWxid}/1?start={ms}&end={ms}`:
- 聊天页自带「开始日期/结束日期」选择器,**预填** modal 带来的 start/end(实测 `2026-08-06 00:00:00 ~ 2026-08-08 23:59:59`,即近 3 天 = 今日+前 2 天,与我们 Phase A `defaultDateRange` 精确一致)。
- 消息**按此窗过滤**(可在页内改窗)。
### 2.3 员工姓名 / 主 badge(本期不动,仅记录云客行为)
- **员工姓名** → 新 tab `/cms/customer/userDetailNew?userId={userId}`(独立员工详情页)。
- **主 badge / 昵称** → 新 tab `/cms/wechat/salewechat/message/wechat-window/1/{wxid}/filehelper`(微信聊天窗口,带会话列表)。
- 两者都是云客独立大页面,本期维持 Plan 1 降级(drawer / 跳最近聊天),完整对齐留总 spec 第 5 步。
## 3. 前端改动(纯前端,无后端)
### 3.1 `yunkes/src/views/StaffList.vue` — modal 列补齐 + 客户微信可点
- **`commModalColsFor(metric)`** 改为返回云客列序:
- `序号`:`{prop:'_serial', label:'序号', width:70, align:'center'}`
- `客户微信`:`{prop:'friend_name', label:'客户微信', minWidth:160, onClick: goChat}`(显示 `friend_name` + `talker_id`;`onClick` 由 `YkStaffDetailModal` 渲染为 link button)
- **仅 `effective`**:插入 `{prop:'send_count', label:'发送消息数', width:110, align:'center'}` / `{prop:'receive_count', label:'接收消息数', width:110, align:'center'}`
- `员工信息`:`{prop:'staff_info', label:'员工信息', minWidth:140}`
- `员工微信`:`{prop:'staff_wx', label:'员工微信', minWidth:160}`
- **fetcher 合成分页时给每行注入字段**(在现有 slice 之后 map):
- `_serial = (page-1)*page_size + i + 1`(跨页连续序号)
- `staff_info` = 员工姓名 + 微信手机号(取自 staff `row`:`userName` + `phone`;字段最终映射 plan 核实 `phone` vs `salePhone`)
- `staff_wx` = 昵称 + 微信号(`nickName` + `account_id`)
- **`goChat(customer)`**(闭包捕获 staff `row`):
```ts
router.push({
name: 'chat',
params: { channel: 'wechat', ownerId: row.account_id, talkerId: customer.talker_id },
query: { start: String(statsStart.value), end: String(statsEnd.value) },
})
```
- 守卫:`row.account_id` 与 `customer.talker_id` 都存在才跳;缺失静默 no-op(与 `YkStaffDetailModal` 的 `onClick?` 一致)。
- **客户微信显示不脱敏**(私有镜像,显示 `friend_name` 全名 + `talker_id`;与 CustomerList/GroupList 一致)。
- **员工姓名→drawer、主badge/昵称→viewChat:不动**(维持 Plan 1)。
> `YkStaffDetailModal.vue` **不改**:序号/员工信息/员工微信都靠 fetcher 注入字段实现(cols 仍数据驱动);客户微信用其已有的 `onClick` 渲染为 link button。
> 单元格视觉:员工信息/员工微信 云客是双行/带头像,我们用单行文本(`name 手机号` / `nick wxid`)——总 spec 已声明「不对齐 antd vs el-table 像素级视觉」,信息一致即可。
### 3.2 `yunkes/src/views/ChatTimeline.vue` — 读日期窗 query
- 加 `useRoute()`,读 `route.query.start` / `route.query.end`(ms-epoch 字符串,可选)。
- 存在且为合法数字 → 透传 `getMessages({ channel, owner_account_id, talker_id/room_id, start, end, page, page_size })`(`getMessages` 已支持 start/end)。
- 不存在 → 现行全量行为(向后兼容:CustomerList「查看聊天记录」、StaffList badge `viewChat` 等入口不受影响)。
- 副标题:有窗时追加「· 时段:{start} ~ {end}」(`formatTime` 格式化)。
- 分页不变(asc 翻页照旧,total 随窗变化)。
### 3.3 不改
- `YkStaffDetailModal.vue`、后端、`api/index.ts`、`types.ts`(复用 Phase A 的 `StaffChatCustomer`/`StaffChatStats`)。
- `router/index.ts`(`/chat/:channel/:ownerId/:talkerId` 已存在;query 透传无需改路由)。
## 4. 关键决策(已与用户确认 2026-08-08)
1. **P1 = a**:ChatTimeline **仅带窗过滤**(从 modal 进时),**页内日期选择器留总 spec 第 5 步**。理由:钻取的本质行为(按窗看该客户聊天)已对齐云客;选择器属聊天页通用功能,第 5 步加会话列表/查询栏时大概率重排版,现做有返工风险。
2. **P2 = 显示全名**(不脱敏):私有镜像的意义即看全云客脱敏的数据;与 CustomerList/GroupList/ChatTimeline 一致。云客脱敏是其多租户隐私策略,与我们的场景无关。
3. **同页 `router.push`**(不 `window.open` 新 tab)—— SPA 惯例。
4. **员工姓名 / badge 维持降级**(与总 spec 第 5 步排序一致)。
5. **默认近 3 天窗**:Phase A `defaultDateRange`(今日+前 2 天,00:00~23:59:59)与云客 stat 窗实测精确一致。
## 5. 文件清单(预期)
- 改 `yunkes/src/views/StaffList.vue`:`commModalColsFor` 列补齐 + fetcher 注入 `_serial`/`staff_info`/`staff_wx` + `goChat`(客户微信 onClick → 带窗跳聊天)。
- 改 `yunkes/src/views/ChatTimeline.vue`:`useRoute` 读 start/end → 透传 `getMessages` + 副标题显窗。
- `YkStaffDetailModal.vue` **不改**;无后端;无新表/迁移。
## 6. 验收
- `vue-tsc -b` exit 0,无 console 报错。
- **浏览器实测**(本地后端 + smoke.db,不碰云客):
- stat modal 列对齐云客:序号 / 客户微信 / [有效沟通多发送·接收] / 员工信息 / 员工微信;无操作列。
- 点 modal 客户微信 → 跳 `/chat/wechat/{owner}/{talker}` 且 URL 带 `start/end`;ChatTimeline 仅显示窗内消息(改 StaffList 日期窗 → modal 数字与跳转窗同步)。
- 从其它入口进 ChatTimeline(无 query)→ 全量(向后兼容)。
- 员工姓名→drawer、badge→最近聊天 不变。
- network 仅 localhost(无云客)。
## 7. 风险
- **ChatTimeline 过滤 + 分页交互**:getMessages 已支持 start/end + 分页,但需确认 asc 翻页 + total 在窗下符合预期(plan 核实 + 浏览器验)。
- **员工信息/员工微信 字段映射**:`phone` vs `salePhone`、微信号用 `account_id` 还是 `alias`——plan 读 Account raw_json 核实后定(缺失显示 `--`)。
- **客户微信 onClick 数据完备性**:`row.account_id`(员工 wxid)+ `customer.talker_id` 必须都在;Phase A 已保证 customer 有 talker_id,staff row 有 account_id。
- **未对齐项(已声明)**:页内日期选择器、会话列表、员工详情页、新标签页、脱敏——均为刻意留白(总 spec 第 5 步),非缺陷。