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

9.5 KiB
Raw Blame History

微信员工 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):
    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 步),非缺陷。