Files
yunke/docs/superpowers/specs/2026-08-08-yunke-staff-modal-phase2-design.md
T

115 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微信员工 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 步),非缺陷。