From de08bd09c7b1c2bdc68db9086e7092299ca8276d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Thu, 6 Aug 2026 11:11:37 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20Plan=203=20=E2=80=94=20GroupList=20?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E4=BA=91=E5=AE=A2=EF=BC=88=E9=87=87=E9=9B=86?= =?UTF-8?q?=20+=20TDD=20plan=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit phase0 采集云客「沟通」板块群列表(微信 wechatdetail/2 + WA friendlist 群列表 tab), 写 Plan 3(分 channel tab 各自列 + viewChat),经 plan-reviewer 评审 Approved。 已知 gap:smoke.db 0 群(空表验证)+ WA 群抓取缺失(字段推断)。 Co-Authored-By: Claude --- .../2026-08-06-yunke-grouplist-alignment.md | 607 ++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-06-yunke-grouplist-alignment.md diff --git a/docs/superpowers/plans/2026-08-06-yunke-grouplist-alignment.md b/docs/superpowers/plans/2026-08-06-yunke-grouplist-alignment.md new file mode 100644 index 0000000..1ef945c --- /dev/null +++ b/docs/superpowers/plans/2026-08-06-yunke-grouplist-alignment.md @@ -0,0 +1,607 @@ +# GroupList 列结构 + 交互对齐云客 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 把 GroupList(群列表)对齐云客「沟通」板块的群列表——分 微信群 / WhatsApp群 两个 tab,各自复刻云客列(wechat 7 列 / WA 8 列)+ 操作列「查看聊天记录」跳群聊天,与已完成的 CustomerList(Plan 2) 同款模式。 + +**Architecture:** 纯前端改造(后端 `getGroups` + `chat_group` 表已在;Plan 1 已加 groups `user_id` 筛选)。`GroupList.vue` 从「统一 8 列 + channel radio」重构为「`el-tabs` 分 channel + 每个 channel 独立列 + 操作列跳 group-chat」(CustomerList 同款)。复用 `groupFields`(`readField`/`buildGroupSections`) + `customerFields.fromTypeLabel`(Plan 2 已加) + `cellTimeText` / `yk-pair` / `yk-staffinfo` / drawer。 + +**Tech Stack:** Vue3 + TS + Element Plus + Pinia(前端)。**无后端改动**。 + +**Spec:** `docs/superpowers/specs/2026-08-05-yunke-ui-alignment-design.md` +**参考(模式来源):** `docs/superpowers/plans/2026-08-06-yunke-customerlist-alignment.md`(Plan 2,已完成;GroupList 照搬其 el-tabs 分 channel + 复合单元格 + drawer + viewChatFromDrawer 模式) + +--- + +## 已采集事实(Playwright 验证,2026-08-06) + +**微信群列表** `/cms/wechat/wechatlist/wechatdetail/2/{wxId}?ymdStart=0000-00-00&ymdEnd=YYYY-MM-DD`(title「微信群」,tabs 微信客户/微信群): +- 列(7):群信息(群名 `chatRoomName`) / 数据来源(`fromType`→文案) / 群主信息(昵称+wxId) / 群成员数量(`memberCount`) / 群成员好友数量(待核) / 消息数量(`messageCount`) / 操作 +- 交互:**仅操作列可点**——「查看聊天记录」→ 新 tab 群聊天;「导出HTML聊天记录」→ **不复刻** +- 数字/群名/群主列**不可点**(纯展示,同客户列表) +- 字段确定性:`chatRoomName`/`memberCount`/`messageCount` ✅(scraper `_normalize_chat_group` + `groupFields.STRUCTURED_ALIAS` 确认);`fromType` / 群主昵称 / 群主 wxId / 群成员好友数量 ⚠️ **字段名待 smoke.db 有群数据核对**(见附录) + +**WhatsApp 群列表** `/cms/wechat/waBusiness/friendlist` 的「群列表」tab(title「客户列表」,tabs 好友列表/群列表): +- 列(8):序号 / 员工信息(`salesNickName`+`salePhone`) / 员工WhatsApp(`userName`+`uid`) / 群信息(群名) / 群消息数量(`messageCount`?) / 添加时间(`createTime`?) / 最新消息时间(`lastUpdate`?) / 操作 +- 交互:**仅操作列可点**——「查看聊天记录」→ 新 tab 群聊天 +- ⚠️ **WA 群抓取缺失**(spec 真实验证未列 WA 群;`whatsapp.py` 只有 account 的 `group_num` 计数、无 group 实体抓取;`groupFields.ts` 只有 wechat 群字段):WA 群 tab 字段全为从云客采集 + WA customer/account 类比**推断**,无 raw_json/数据支撑,**UI 将空表**。待 WA 群抓取实现后核对字段。 + +**关键差异(决策依据):** yunkes 现状 GroupList 是统一 8 列 + channel radio;云客是每 channel 群列表列不同(wechat 7 / WA 8)。按 spec「列对齐云客」+ Plan 2 CustomerList 已验证模式,重构为分 tab(**用户已确认「UI 对齐先做」方向**)。 + +**⚠️ 数据 gap(重要,影响验证):** smoke.db `chat_group` 表 **0 行**(无群数据)。本 plan UI 对齐代码正确,但浏览器验证为**空表**(列结构对、无数据行)。wechat 群抓取在(`scrape_wechat_groups`),smoke.db 未填;WA 群抓取缺失。抓取 gap(跑 wechat 群抓取填 smoke.db + 实现 WA 群抓取)是**单独的后端/数据工作,不阻塞本 UI 对齐 plan**。 + +**运行命令(验证用):** +- 前端:`cd yunkes && npm run dev` +- 前端类型:`cd yunkes && npx vue-tsc -b`(exit 0) +- 后端(提供空表数据):`cd yunke-server && DB_URL="sqlite+aiosqlite:///./smoke.db" .venv/Scripts/python.exe -m uvicorn app.api.main:app --port 8000 --log-level warning` +- 后端测试:`cd yunke-server && .venv/Scripts/python.exe -m pytest tests/ -q`(应仍 ≥330 绿;本 plan 无后端改动) + +--- + +## File Map + +**前端(yunkes/src/):** +- Modify `views/GroupList.vue` — 重构:统一 8 列 → `el-tabs` 分 微信群 / WhatsApp群,各自列 + 操作列 `viewChat`(group-chat) + `owner_account_id` query 预筛 + drawer 保留 +- **不改** `utils/groupFields.ts`(`readField`/`buildGroupSections` 复用;待核字段经 `readField` raw_json fallback 读取,失败显示 `--`,不必现在加 alias) +- **不改** `utils/customerFields.ts`(复用 Plan 2 的 `fromTypeLabel`) + +**不改动:** 后端(`getGroups`/`chat_group` 在)、router(`group-chat` 路由在)、api、其他 .vue + +--- + +## Task 1: GroupList.vue 重写(分 tab + 各自列 + 操作列 + query 预筛) + +> 整文件重写(统一 8 列 → 分 tab 各自列)。drawer 保留现状结构(avatar + buildGroupSections + 查看聊天记录)。完全照搬 Plan 2 CustomerList 的 `tabState`/`loadTab`/`cellTimeText`/`viewChatFromDrawer`/channel watch/query watch 模式。 + +**Files:** +- Modify: `yunkes/src/views/GroupList.vue`(整文件替换为下方代码) + +- [ ] **Step 1: 用以下内容整体替换 `GroupList.vue`** + +```vue + + + + + +``` + +- [ ] **Step 2: 类型检查** +```bash +cd yunkes && npx vue-tsc -b +``` +预期:exit 0。常见排查:`fromTypeLabel` 未导出 → 确认 Plan 2 Task 1 已加(`@/utils/customerFields`);`Group` 上无某字段 → 核对 `api/types.ts`;`group-chat` 路由名/params → 核对 `router/index.ts`(应 `{channel, ownerId, roomId}`)。drawer 已用 `viewChatFromDrawer` wrapper,**不要**用 `!`。 + +- [ ] **Step 3: Commit** +```bash +git add yunkes/src/views/GroupList.vue +git commit -m "$(cat <<'EOF' +feat(groups): align GroupList to 云客 — per-channel tabs, view-chat action + +Co-Authored-By: Claude +EOF +)" +``` + +--- + +## Task 2: 端到端验证 + 收尾 + +> smoke.db 0 群 → 浏览器验证为**空表**(列结构对、无数据行)。重点验:两 tab 列头、tab 切换、操作按钮、空表提示、vue-tsc、pytest。 + +- [ ] **Step 1: 起后端 + 前端**(若未在跑) +```bash +cd yunke-server && DB_URL="sqlite+aiosqlite:///./smoke.db" .venv/Scripts/python.exe -m uvicorn app.api.main:app --port 8000 --log-level warning +cd yunkes && npm run dev +``` + +- [ ] **Step 2: 浏览器验证 `/groups`** + - 微信群 tab:7 列表头(群信息/数据来源/群主信息/群成员数量/群成员好友数量/消息数量/操作)与云客一致;空表提示「暂无微信群数据」。 + - WhatsApp群 tab:8 列表头(序号/员工信息/员工WhatsApp/群信息/群消息数量/添加时间/最新消息时间/操作);空表提示。 + - 切 tab:不报错(各自空 loadTab)。 + - StaffList → 点员工「群聊」(viewGroups) → `/groups?owner_account_id=X` + 落对应 tab + 「清除员工筛选」chip。 + - (无数据 → viewChat/drawer 点行无法验,待有群数据后补验) + +- [ ] **Step 3: 后端回归** +```bash +cd yunke-server && .venv/Scripts/python.exe -m pytest tests/ -q +``` +预期:全 PASS(≥330;本 plan 无后端改动)。 + +- [ ] **Step 4: vue-tsc 复核** +```bash +cd yunkes && npx vue-tsc -b +``` +预期:exit 0。 + +- [ ] **Step 5: 截图留档**(两 tab 空表 + 列头),更新本 plan 附录。 + +- [ ] **Step 6: Commit(仅当验证阶段有代码微调时;纯验证无改动则跳过)** +```bash +git add yunkes/src/views/GroupList.vue +git commit -m "test(groups): e2e verify GroupList alignment vs 云客 (empty: smoke.db 0 groups)" +``` + +--- + +## 收尾验收清单 + +- [ ] `cd yunkes && npx vue-tsc -b` exit 0,无 console 报错。 +- [ ] `/groups` 两 tab 列名/顺序/操作按钮与云客一致(截图留档)。 +- [ ] StaffList「群聊」→ GroupList 预筛 + chip + 清除,联动正确。 +- [ ] 后端 pytest 仍 ≥330 绿(无后端改动)。 +- [ ] **已知限制(数据 gap,非本 plan 范围)**:smoke.db `chat_group` 0 行 → 表格空表(列结构对);待 wechat 群抓取填 smoke.db + WA 群抓取实现后,字段映射按附录核对 + 补数据验证。 + +--- + +## 字段映射附录 + 待核 + gap + +### 微信群(云客 wechatdetail/2)→ yunkes Group 字段 +| 云客列 | raw_json key(readField) | 确定性 | +|---|---|---| +| 群信息(群名) | `chatRoomName`(结构化 `room_name`) | ✅ | +| 数据来源 | `fromType` | ⚠️ 待核(group raw_json 是否有;复用 fromTypeLabel) | +| 群主信息(昵称) | `userName` | ⚠️ 待核("开\*果" 是 userName 还是 ownerNickName) | +| 群主信息(wxId) | `wxId` | ⚠️ 待核(群主 wxId 字段名;可能是 ownerWxId) | +| 群成员数量 | `memberCount`(结构化 `member_count`) | ✅ | +| 群成员好友数量 | `contactCount` | ⚠️ 待核(字段名猜测;云客显示好友数) | +| 消息数量 | `messageCount`(结构化 `message_count`) | ✅ | +| 操作 | — | viewChat → group-chat ✅ | + +### WhatsApp 群(云客 waBusiness/friendlist 群列表)→ yunkes Group 字段 +| 云客列 | raw_json key(推断) | 确定性 | +|---|---|---| +| 序号 | 行号(waSerial) | ✅ | +| 员工信息 | `salesNickName` + `salePhone` | ⚠️ 全推断(WA 群无抓取/raw_json) | +| 员工WhatsApp | `userName` + `uid` | ⚠️ | +| 群信息 | `chatRoomName` | ⚠️ | +| 群消息数量 | `messageCount` | ⚠️ | +| 添加时间 | `createTime` | ⚠️ | +| 最新消息时间 | `lastUpdate` | ⚠️ | +| 操作 | — | viewChat ✅ | + +**核对方法(待数据就绪):** smoke.db 有群数据后,`select raw_json from chat_group where channel='wechat' limit 1` dump keys → 核对上表 ⚠️ 字段名,修正 readField key。WA 群待抓取实现后同理。 + +--- + +## 后续(不在本 plan 范围) + +- **群抓取 gap(数据工作,单独 plan):** + - wechat:跑 `scrape_wechat_groups` 填 smoke.db(抓取代码在,需跑同步)。 + - whatsapp:实现 WA 群抓取(`whatsapp.py` 加 group 实体抓取 + `_normalize`;spec WhatsApp 现未列群,需先探云客 WA 群接口)。 +- Plan 4: ChatTimeline 会话列表 + `/api/sessions` 聚合端点。 +- Plan 5: SearchView + AccountList 删除。