From 2a33f944ecdb4f6dc9d07ffb1edecc6936d99e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Sat, 8 Aug 2026 13:12:13 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E5=BE=AE=E4=BF=A1=E5=91=98?= =?UTF-8?q?=E5=B7=A5=20stat=20modal=20Phase=20B=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=EF=BC=88=E5=88=97=E5=AF=B9=E9=BD=90=E4=BA=91=E5=AE=A2=20+=20?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E5=BE=AE=E4=BF=A1=E2=86=92=E5=B8=A6=E7=AA=97?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- ...6-08-08-yunke-staff-modal-phase2-design.md | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-08-yunke-staff-modal-phase2-design.md diff --git a/docs/superpowers/specs/2026-08-08-yunke-staff-modal-phase2-design.md b/docs/superpowers/specs/2026-08-08-yunke-staff-modal-phase2-design.md new file mode 100644 index 0000000..540dd54 --- /dev/null +++ b/docs/superpowers/specs/2026-08-08-yunke-staff-modal-phase2-design.md @@ -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 核实) | +|---|---| +| 沟通人数 / 跟进客户数 / 员工最后回复数 / 未回复客户数 | 序号 / 客户微信(可点) / 员工信息 / 员工微信 | +| 有效沟通数 | 序号 / 客户微信(可点) / **发送消息数 / 接收消息数** / 员工信息 / 员工微信 | + +- **客户微信** = ``,文本脱敏「昵称(备注)微信号」。**唯一可点列**。 +- **员工信息** = `

员工姓名

微信手机号

`(纯展示)。 +- **员工微信** = 头像 + 昵称 + 微信号(纯展示)。 +- 末尾空表头是 Ant 的**滚动条占位 cell,不是操作列**(modal **无按行操作按钮**)。 + +### 2.2 modal 客户微信 → 聊天详情 +点客户微信 `
` **新开 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 步),非缺陷。