From 614680a4eb417d021c809ef944b6dd4901acbd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 25 Aug 2026 11:57:29 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E5=91=98=E5=B7=A5=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=AA=97=E5=8F=A3=E5=AF=B9=E9=BD=90=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?(StaffChat=E4=BA=94=E8=A7=86=E5=9B=BE:=E5=8D=95=E8=81=8A/?= =?UTF-8?q?=E7=BE=A4=E8=81=8A/=E6=9C=8B=E5=8F=8B=E5=9C=88/=E9=80=9A?= =?UTF-8?q?=E8=AE=AF=E5=BD=95/=E7=BB=9F=E8=AE=A1=E5=88=86=E6=9E=90,?= =?UTF-8?q?=E5=9F=BA=E4=BA=8E2026-08-25=E4=BA=91=E5=AE=A2=E7=9C=9F?= =?UTF-8?q?=E6=9C=BA=E6=8A=93=E5=8C=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- ...26-08-25-yunke-staff-chat-window-design.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-25-yunke-staff-chat-window-design.md diff --git a/docs/superpowers/specs/2026-08-25-yunke-staff-chat-window-design.md b/docs/superpowers/specs/2026-08-25-yunke-staff-chat-window-design.md new file mode 100644 index 0000000..487964c --- /dev/null +++ b/docs/superpowers/specs/2026-08-25-yunke-staff-chat-window-design.md @@ -0,0 +1,170 @@ +# 云客员工聊天窗口对齐设计(StaffChat 五视图) + +- 日期:2026-08-25 +- 状态:已批准(用户逐节确认) +- 范围:微信员工列表 → 员工信息列昵称点击进入的聊天窗口页,对齐云客全部 5 个视图(单聊/群聊/朋友圈/通讯录/统计分析),含功能与数据 + +## 1. 背景与目标 + +现有 `ChatTimeline.vue`(双栏:会话侧栏+消息流)与云客员工聊天窗口差距大。云客真机(2026-08-25 Playwright 实测,员工 `wxid_wo97u1u2x11w22`)该页面是**三栏五视图**结构:左侧员工资料卡+导航,中间会话列表,右侧内容区按导航切换五种视图。 + +目标:新建独立页面完整对齐(布局/交互/数据口径),并与云客保持一致。旧 `ChatTimeline` 保留给 WhatsApp 渠道,不动。 + +### 用户决策记录(2026-08-25 逐项确认) + +| 决策点 | 结论 | +|---|---| +| 范围 | 全部 5 个视图(含用户未提的统计分析) | +| 渠道 | 只做微信版;WA 保持现有页面 | +| 删除聊天记录按钮 | **不复刻**(镜像数据删了只有重扫能找回) | +| 敏感词统计 | tab 占位空态(待 C1 敏感词功能接入,见 2026-08-24 数据分析方案) | +| 实现方案 | 方案 A:独立新页 `StaffChat.vue`,组件拆分,旧页不动 | + +## 2. 云客真机事实(对齐依据) + +页面 URL:`/cms/wechat/salewechat/message/wechat-window/{tab}/{wxId}/{friendId}`,tab 路径段 1=单聊 2=群聊;朋友圈/通讯录/统计分析是独立路由但共享左栏。 + +### 2.1 布局 + +``` +┌─────────┬──────────────┬────────────────────────┐ +│ 员工资料卡 │ 好友/群|聊天记录▼ │ 对话标题 │ +│ 昵称 │ [搜索框] │ [近14天窗][关键词][类型▼]│ +│ 真名·电话 │ ┌──────────┐ │ [导出聊天记录] │ +│ 微信号 │ │聊天好友│已删除│ │ 消息流(加载更多+日期分隔) │ +│─────────│ └──────────┘ │ │ +│ 单聊/群聊/ │ 📌置顶区(可折叠)│ │ +│ 朋友圈/ │ 会话卡列表 │ │ +│ 通讯录/ │ (头像+今日N │ │ +│ 统计分析 │ +相对时间) │ │ +└─────────┴──────────────┴────────────────────────┘ +``` + +### 2.2 接口事实(真机抓包) + +| 视图 | 接口 | 关键参数/字段 | +|---|---|---| +| 员工资料卡 | `GET /wechatweb/wxFriends/getUserAndFriendInfo?wechatId=&friendId=` | `saleName` 昵称、`realName` 真名、`salePhone`、`alias`/`wechatId`、`HeadImageUrl` 头像 | +| 会话列表(单聊) | `POST /wechatweb/wxFriends/talkerList` form | `type=1, isDel=0|1, pageNumber, pageSize=20, wechatId`;行:`friendWechatId/nickName(脱敏)/remarkName/messageCount(今日)/headUrl/lastChatTime/showHead(-1=置顶)/memberCount`;`topCount` 置顶数、`hasMore` 翻页 | +| 会话列表(群聊) | 同上 | `type=2`(无 isDel);群名不脱敏 | +| 朋友圈 | `POST /wechatweb/wechatFriendster/getFriendsterInfo` form | `startTime/endTime(空), pageNum, pageSize=10, wechatId`;行:`id/snsId/friendsterName/friendsterWXIdImg/content/createTime(秒)/type(1图文/28纯文字)/praise[]{name,praiseImg}/comment[]/imgUrlsList[](OSS URL)/praiseNum`;头部 `saleNickName/headImageUrl/saleAlias/bgImg`;`hashNext` 翻页 | +| 通讯录 | `POST /wechatweb/wxFriends/friend_list/es` | 即逐员工客户列表(已抓过);表格列:客户微信(昵称+微信号)/数据来源/客户备注名/最近沟通时间/发送条数/接收条数/操作(查看聊天记录+导出HTML聊天记录) | +| 统计计数 | `POST /wechatweb/wechatcount/getSaleWechatIdInfoCount` | `{addFriend, addGroup, redAndTrans, sensitiveCount, delFriend, blackFriend, weChatVoiceCount}` | +| 统计明细 | `POST /wechatweb/wxFriends/getAddFriendOrGroup` form | `pageNum, pageSize, friendTypeYunke=1, wechatId, startdate, enddate`;行:客户微信(可点→聊天)/客户备注/员工微信/时间 | + +其他 UI 事实: +- 右列默认日期窗**近 14 天**(`今日00:00-14天 ~ 今日23:59:59`),统计视图默认**近 7 天** +- 类型下拉 10 类:文本/图片/语音消息/语音通话/视频通话/文件/小程序名片/链接/红包/转账 +- 「好友/群|聊天记录」下拉:聊天记录模式=左侧按全量消息关键词搜索 +- 置顶区含文件传输助手,可「折叠置顶聊天」 +- 已删除好友 tab 存在(实测 3 条老好友,最近联系时间可显示到 2025 年) +- 资料卡 cursor=pointer 但点击无可见行为(不复刻点击) + +## 3. 架构(方案 A) + +``` +路由: /staff-chat/:ownerId/:tab(single|group|moments|contacts|stats)/:talkerId? + 默认 tab=single;talkerId 可空(账号级入口) + +StaffChat.vue (页面骨架) +├── StaffChatSide.vue 左栏: 员工资料卡 + 5 导航按钮 +├── tab=single|group → SessionPane.vue (中列) + ChatMain.vue (右列) +├── tab=moments → MomentsPane.vue +├── tab=contacts → ContactsPane.vue +└── tab=stats → StatsPane.vue +``` + +- `SessionPane`/`ChatMain` 从 `ChatSidebar`/`ChatTimeline` 复制改造(非原地重构);`MessageBubble`、`buildMessageSections` 直接复用 +- `ChatTimeline.vue`/`ChatSidebar.vue` 不动,WA 继续用;未提交 WIP(`sidebarProfile` 传参、`delete_flag` query 类型)随本设计消化:ChatTimeline 还原 WIP,`delete_flag` 参数由新 sessions API 取代 +- 入口改造:`StaffList.vue` 微信行昵称/主badge → `/staff-chat/{account_id}/single`;modal 内点客户微信带日期窗 query;`CustomerList.vue` 微信行「查看聊天记录」同步切新页 + +## 4. 数据层 + +### 4.1 新表 `moments_post` + +| 列 | 类型 | 说明 | +|---|---|---| +| id | VARCHAR(64) PK | 云客 data.id | +| wechat_id | VARCHAR(128) idx | 归属员工 | +| sns_id | VARCHAR(64) | 云客 snsId | +| author_name / author_wxid / author_avatar | VARCHAR | 发布者 | +| content | TEXT | 文案 | +| type_raw | VARCHAR(16) | 1=图文 28=纯文字 | +| create_time | BIGINT idx | 发布时间(ms) | +| praise_json / comment_json / image_urls | JSON | 点赞/评论/图片 | +| raw_json + fetched_at/updated_at | | 惯例字段 | + +### 4.2 新抓取实体 `moments`(页面 fetch 方式) + +- 浏览器 `fetch POST getFriendsterInfo`,`pageSize=100` 逐员工翻页到 `hashNext=false` +- 接入 `scrape_via_page.py` 实体注册、`full_sync_all.sh` 加一步、调度 4h 增量(水位=本地 max(create_time),仅扫有新朋友圈的员工——云客接口无 per-员工增量标记,简化为全量浅扫第一页有新才继续) +- 图片接入现有 media 本地化批处理(OSS URL 型) + +### 4.3 API 改动 + +| API | 改动 | +|---|---| +| `GET /api/accounts` | `AccountOut` + `avatar`(从 `raw_json.headImageUrl` 提取,WA 侧无此键则 null) | +| `GET /api/sessions` | + `is_del` 参数(join `customer.delete_flag`,NULL 视为 0);行 + `pinned`(`talker_id='filehelper'`) | +| `GET /api/moments` 新 | `?wechat_id=&page=&page_size=` 帖子 desc + 头部 `{nickname, avatar, alias}` | +| `GET /api/staff-stats` 新 | `?wechat_id=&start=&end=` 计数;`&kind=` 明细行 | +| `GET /api/messages` | + `msg_type` 过滤(映射云客 10 类)+ `q` 全文关键词(聊天记录模式用) | + +### 4.4 统计口径(现有表本地聚合) + +| tab | 口径 | +|---|---| +| 新的客户 | `customer.add_time` 窗内(实测 62% 有值);缺失兜底=该对话首条消息时间 | +| 新的群 | `chat_group` 建群时间(无则跳过该 tab 计数,显示现存量) | +| 红包转账 | `message.type_raw=49 AND (raw_json.paymsgid 非空 OR text 含 appmsg type 2001/2000)`,合并一类;明细金额从 XML `` 提取,失败显 `—` | +| 删除客户 | `customer.delete_flag=1`(实测 53 行;无时间字段→计数=现存量,时间列显最近 updated) | +| 拉黑客户 | `customer.is_block=1`(实测 8 行,同上) | +| 语音通话 | `phone_call.user_id=员工 AND start_call_time 窗内` | +| 敏感词 | 占位空态「词库未配置,待 C1 敏感词功能接入」 | + +## 5. 各视图细节(对齐云客) + +### 单聊/群聊 +- 中列顶部「好友/群 | 聊天记录」下拉双模式 + 搜索框(昵称/wxid 或全量消息关键词) +- 单聊有 `聊天好友|已删除好友` tab(群聊无)→ sessions `is_del` +- 置顶区:filehelper 固定置顶 + 可折叠;置顶不参与今日红点 +- 会话卡:头像+昵称+`今日N`红点+相对时间(今天 `HH:mm`/昨天/今年 `M月D日`/往年全日期) +- 右列:默认近 14 天窗(清空=全部)、关键词、类型下拉 10 类、导出按钮(复用 `/export/chat-html`) +- 消息流:加载更多/上一页 + 日期分隔条;点会话切路由 talkerId +- 已删除好友会话照常显示历史消息 + +### 朋友圈 +- feed 卡片:作者头像+名 → 内容 → 九宫格图片(点击预览)→ 时间 → 点赞人列表 +- 滚动翻页;空态提示先跑 moments 抓取;缺图占位框 + +### 通讯录 +- `微信客户|微信群` tab + 搜索(昵称/微信号/备注/手机号) +- 客户表 7 列:客户微信(头像+昵称+微信号)/数据来源/客户备注名/最近沟通时间/发送条数/接收条数/操作(查看聊天记录+导出HTML聊天记录) +- 微信群 tab:群名/群主/成员数/最近群消息时间/查看群聊 +- 复用 `/api/customers`、`/api/groups`(owner 过滤已支持) + +### 统计分析 +- 7 tab 带计数徽标,默认近 7 天窗 +- 明细 4 列:客户微信(可点→聊天)/客户备注/员工微信/时间 +- 敏感词空态占位 + +## 6. 错误处理 + +- 每视图独立 loading/error(`el-alert`),互不影响 +- 翻页失败→显示已加载+重试;图片失效→占位框;缺字段→`—` +- 统计某类无数据→空表格+计数 0;非法 tab→重定向 single +- 与现有页面一致的后端未起/超时错误条 + +## 7. 测试 + +- 后端 pytest:`test_api_moments.py`(分页/排序/空库)、`test_api_staff_stats.py`(7 类口径+窗口边界+add_time 兜底+红包判定)、sessions 增补(is_del/置顶)、accounts 增补(avatar)、scraper 归一化单测(fixture=真机 JSON) +- 前端 vitest:路由跳转、tab 切换、相对时间格式化 +- 验收:本地前后端 + Playwright 走查 5 视图,与云客真机对比 + +## 8. 实施顺序 + +1. 后端:表+scraper → API(accounts/sessions/messages 改动,moments/staff-stats 新增) +2. 前端骨架:路由+左栏+单聊视图 +3. 群聊/通讯录/统计分析视图 +4. 朋友圈视图 +5. 入口改造 + 走查 + 提交