From 3a30a41ca1f9568264fc552c867fc105ce47bf65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Sat, 8 Aug 2026 16:14:51 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E2=91=A3=20=E6=94=B6=E7=AA=84?= =?UTF-8?q?=E8=87=B3=E5=BE=AE=E4=BF=A1=E8=AF=AD=E9=9F=B3(WA=20=E6=9C=AA?= =?UTF-8?q?=E6=A0=B8=E5=AE=9E=E4=BA=91=E5=AE=A2+=E5=88=97=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=8D=E5=90=8C=E2=86=92=E5=90=8E=E7=BB=AD,?= =?UTF-8?q?=E9=81=B5=E5=BE=AA=E6=B5=8F=E8=A7=88=E5=99=A8=E4=BC=98=E5=85=88?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- ...-08-08-yunke-calllist-cell-click-design.md | 70 +++++++++---------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/docs/superpowers/specs/2026-08-08-yunke-calllist-cell-click-design.md b/docs/superpowers/specs/2026-08-08-yunke-calllist-cell-click-design.md index f44997f..f3a79c8 100644 --- a/docs/superpowers/specs/2026-08-08-yunke-calllist-cell-click-design.md +++ b/docs/superpowers/specs/2026-08-08-yunke-calllist-cell-click-design.md @@ -1,23 +1,22 @@ -# CallList 列内可点(④)设计 +# CallList 列内可点(④,微信语音)设计 -> 日期:2026-08-08|分支:`feat/yunke-data-mirror`|路线图 ④ +> 日期:2026-08-08|分支:`feat/yunke-data-mirror`|路线图 ④(先做微信语音) > 前置:Phase B(stat modal 客户微信→`/chat/{ch}/{owner}/{talker}`)+ step 5(badge/员工→账号级 `/chat/{ch}/{owner}` 聊天窗 + ChatSidebar)。 > 数据/交互:全本地(运行时不碰云客)。云客「微信语音通话列表」**2026-08-08 Playwright 真机核实**(`/cms/wechat/voice-call-list`)。 ## 1. 目标与范围 -**目标**:CallList 的「微信语音」「WA 语音」两 tab 里,**客户信息 / 员工微信 / 员工信息** 三类单元格可点,行为对齐云客——复用 Phase B + step 5 已建的导航。 +**目标**:CallList 的「微信语音」tab 里,**客户信息 / 员工微信 / 员工信息** 三类单元格可点,行为对齐云客——复用 Phase B + step 5 已建的导航。 -**在本 plan**: -- **微信语音(voice_call)/ WA 语音(wa_voice_call)** tab,每行使三类单元格可点: - - **客户信息**(微信/WA 昵称)→ `/chat/{chatChannel}/{owner_account_id}/{talker_id}`(该客户 1:1 聊天;复用 Phase B)。 - - **员工微信**(昵称)→ `/chat/{chatChannel}/{owner_account_id}`(账号级聊天窗;复用 step 5)。 - - **员工信息**(姓名)→ `openDrawer(row)`(通话全字段详情;云客姓名→员工详情页,我们降级 drawer,同 StaffList)。 +**在本 plan(仅微信语音 voice_call tab)**,每行使三类单元格可点: +- **客户信息**(微信昵称)→ `/chat/wechat/{owner_account_id}/{talker_id}`(该客户 1:1 聊天;复用 Phase B)。 +- **员工微信**(昵称)→ `/chat/wechat/{owner_account_id}`(账号级聊天窗;复用 step 5)。 +- **员工信息**(姓名)→ `openDrawer(row)`(通话全字段详情;云客姓名→员工详情页,我们降级 drawer,同 StaffList)。 -**不在本 plan**: -- **电话(phone_call)tab**:号码型,无 wechat/WA chat 映射;云客电话列表是另一套 CRM。**维持不可点**(row-click→drawer 保留),待电话数据映射明确后再议。 -- 客户/员工的云客独立详情页(`userDetailNew` 等):维持降级(drawer / 聊天窗),与 StaffList 一致。 -- 录音播放、行点击→drawer:现状不变。 +**不在本 plan(后续)**: +- **WA 语音(wa_voice_call)tab**:云客 WA 语音列表**未真机核实**,且本地 WA tab 列结构与微信语音不同(无独立「员工微信」列,wechat 昵称是「员工信息」子行)→ **先核实云客 WA 再做**(避免凭推断构建,遵循浏览器优先原则)。本 plan 只预留 `chatChannel()` 映射,不改 WA tab 模板。 +- **电话(phone_call)tab**:号码型,无 wechat/WA chat 映射;云客电话列表是另一套 CRM。维持不可点(row-click→drawer 保留)。 +- 客户/员工的云客独立详情页(`userDetailNew`):维持降级(drawer / 聊天窗)。 ## 2. 云客真机核实(2026-08-08,微信语音通话列表) @@ -29,62 +28,57 @@ **结论**:3 类可点单元格,目标 = 客户聊天 / 账号聊天窗 / 员工详情——全部我们已有对应物。 -## 3. 前端(仅 `yunkes/src/views/CallList.vue`) +## 3. 前端(仅 `yunkes/src/views/CallList.vue`,仅 voice_call 模板块) -- **call-type → chat-channel 映射**:call 的 `channel` 是通话类型(`voice_call`/`wa_voice_call`/`phone_call`),chat 路由用 `wechat`/`whatsapp`。加 helper: +- **call-type → chat-channel 映射**(helper;本 plan 只用 voice_call→wechat,WA 留待后续): ```ts function chatChannel(ch: CallChannel): 'wechat' | 'whatsapp' | null { return ch === 'voice_call' ? 'wechat' : ch === 'wa_voice_call' ? 'whatsapp' : null } ``` -- **读 owner/talker**(经 `readField`,CallList 已用): - - 语音:`owner = readField(row,'weChatId')`,`talker = readField(row,'talker')`(fallback `row.call_number`)。 - - WA:owner/talker 用 WA 字段(plan 核实具体键名,如 `uid`/`talker` 或 `waId`)。 +- **读 owner/talker**(经 `cell()`/`readField`,CallList 已用):语音 `owner = cell(row,'weChatId')`,`talker = cell(row,'talker')`(fallback `row.call_number`)。两键已在 `CALL_FIELD_LABELS`(callFields.ts)且现模板已渲染。 - **3 个 onClick handler**: ```ts function viewCallCustomer(row: PhoneCall) { - const ch = chatChannel(row.channel); const owner = cell(row,'weChatId') /* WA 另定 */; const talker = cell(row,'talker') - if (!ch || !owner || !talker) return - router.push({ name: 'chat', params: { channel: ch, ownerId: String(owner), talkerId: String(talker) } }) + const owner = cell(row, 'weChatId'); const talker = cell(row, 'talker') + if (!owner || !talker) return + router.push({ name: 'chat', params: { channel: 'wechat', ownerId: String(owner), talkerId: String(talker) } }) } function viewCallStaff(row: PhoneCall) { - const ch = chatChannel(row.channel); const owner = cell(row,'weChatId') /* WA 另定 */ - if (!ch || !owner) return - router.push({ name: 'chat', params: { channel: ch, ownerId: String(owner) } }) + const owner = cell(row, 'weChatId') + if (!owner) return + router.push({ name: 'chat', params: { channel: 'wechat', ownerId: String(owner) } }) } // 员工信息姓名 → openDrawer(row)(已有) ``` > 不带 `start/end` query:聊天窗默认近 2 周(step 5),近期通话在其内可见;简化。 -- **模板**:把对应单元格的昵称/姓名文本包成 ``(与 StaffList 数字单元格一致风格);缺失字段则显 `--`(不可点)。三处: - - 微信语音/wa语音 `客户信息` 列的昵称行 → `viewCallCustomer`。 - - 微信语音/wa语音 `员工微信` 列的昵称行 → `viewCallStaff`。 - - 微信语音/wa语音 `员工信息` 列的姓名行 → `openDrawer`。 -- **电话 tab 模板不动**。 +- **模板(仅 `callType === 'voice_call'` 块,3 处)**:把对应单元格的昵称/姓名文本包成 ``(与 StaffList 数字单元格一致风格);缺失字段则显 `--`(不可点): + - `客户信息` 列的微信昵称行 → `viewCallCustomer`。 + - `员工微信` 列的昵称行 → `viewCallStaff`。 + - `员工信息` 列的姓名行 → `openDrawer`。 +- **WA语音 / 电话 tab 模板不动**。 - `import { useRouter } from 'vue-router'` + `const router = useRouter()`。 ## 4. 关键决策(已与用户确认 2026-08-08) -1. **范围 = 微信语音 + WA语音**;电话不动(无 chat 映射)。 +1. **范围 = 仅微信语音(voice_call)**;WA语音(未核实云客 + 列结构不同)+ 电话(无 chat 映射)→ 后续。 2. **复用 Phase B(客户→1:1 chat)+ step 5(员工→账号级聊天窗)+ drawer(员工详情降级)**——不新增导航逻辑/路由/组件。 -3. **不带日期窗 query**:聊天窗默认近 2 周覆盖近期通话;简化。 -4. **call-type→chat-channel 映射**(voice_call→wechat, wa_voice_call→whatsapp)。 -5. **WA owner/talker 字段** plan 核实 raw_json 键名(语音已确认 weChatId/talker)。 +3. **不带日期窗 query**:聊天窗默认近 2 周覆盖近期通话。 +4. WA 待云客真机核实其 WA 语音列表的可点单元格后,再做(小补丁,复用同模式)。 ## 5. 文件清单 -- 改 `yunkes/src/views/CallList.vue`(加 `useRouter`、`chatChannel`、3 个 onClick、模板 6 处单元格包 link button)。**仅此一文件**;无后端、无路由、无新组件。 +- 改 `yunkes/src/views/CallList.vue`(加 `useRouter`、`chatChannel`、`viewCallCustomer`/`viewCallStaff`、voice_call 模板 3 处单元格包 link button)。**仅此一文件**;无后端、无路由、无新组件。 ## 6. 验收 - `vue-tsc -b` exit 0。 - **浏览器实测**(本地 smoke.db,无云客): - 微信语音 tab:点客户昵称 → `/chat/wechat/{owner}/{talker}`(侧栏+该会话);点员工微信昵称 → `/chat/wechat/{owner}`(账号级聊天窗+空态);点员工姓名 → drawer。 - - WA语音 tab:同上,channel=whatsapp(若 smoke.db 有 WA 语音数据)。 - - 电话 tab:单元格不可点,row-click→drawer 不变。 + - WA语音 / 电话 tab:单元格不可点,row-click→drawer 不变(未改动)。 - 缺字段格显 `--` 不可点;network 仅 localhost。 ## 7. 风险 -- **WA 字段键名**:WA 语音 raw_json 的 owner uid / talker 键名 plan 核实(语音 weChatId/talker 已确认);缺失→该格不可点。 -- **smoke.db WA 语音数据**:可能稀疏(Plan 4a 实跑 WA 593 条进库,应可测;若无则以语音为主验)。 -- **owner/talker 类型**:raw_json 值可能非 string,onClick 里 `String(...)` 强制 + 守卫。 +- **owner/talker 缺失**:部分通话 raw_json 可能缺 `weChatId`/`talker` → 该格显 `--` 不可点(守卫已加),不阻塞。 +- **smoke.db 微信语音数据**:Plan 4a 实跑微信语音 2193 条进库,可测。