Files
yunke/docs/superpowers/specs/2026-08-08-yunke-calllist-cell-click-design.md
T

5.8 KiB
Raw Blame History

CallList 列内可点(④,微信语音)设计

日期: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 的「微信语音」tab 里,客户信息 / 员工微信 / 员工信息 三类单元格可点,行为对齐云客——复用 Phase B + step 5 已建的导航。

在本 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(后续):

  • 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,微信语音通话列表)

逐单元格 DOM 核实(首行):

  • 客户信息:<a href="/cms/wechat/detail/message/{ownerWxid}/{customerWxid}/1?start={callTime}&scrollToBottom=0">微信昵称</a> → 该客户 1:1 聊天(带 callTime 定位)。
  • 员工微信:<a href="/cms/wechat/salewechat/message/wechat-window/1/{ownerWxid}/1">昵称</a> → 账号级聊天窗。
  • 员工信息:<div class="link">姓名</div> → 员工详情页。
  • 通话信息 / 通话录音 / 通话时长:不可点(录音是播放按钮)。

结论:3 类可点单元格,目标 = 客户聊天 / 账号聊天窗 / 员工详情——全部我们已有对应物。

3. 前端(仅 yunkes/src/views/CallList.vue,仅 voice_call 模板块)

  • call-type → chat-channel 映射(helper;本 plan 只用 voice_call→wechat,WA 留待后续):
    function chatChannel(ch: CallChannel): 'wechat' | 'whatsapp' | null {
      return ch === 'voice_call' ? 'wechat' : ch === 'wa_voice_call' ? 'whatsapp' : null
    }
    
  • 读 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:
    function viewCallCustomer(row: PhoneCall) {
      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 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),近期通话在其内可见;简化。

  • 模板(仅 callType === 'voice_call' 块,3 处):把对应单元格的昵称/姓名文本包成 <el-button link type="primary" size="small" @click.stop="...">(与 StaffList 数字单元格一致风格);缺失字段则显 --(不可点):
    • 客户信息 列的微信昵称行 → viewCallCustomer。
    • 员工微信 列的昵称行 → viewCallStaff。
    • 员工信息 列的姓名行 → openDrawer。
  • WA语音 / 电话 tab 模板不动。
  • import { useRouter } from 'vue-router' + const router = useRouter()。

4. 关键决策(已与用户确认 2026-08-08)

  1. 范围 = 仅微信语音(voice_call);WA语音(未核实云客 + 列结构不同)+ 电话(无 chat 映射)→ 后续。
  2. 复用 Phase B(客户→1:1 chat)+ step 5(员工→账号级聊天窗)+ drawer(员工详情降级)——不新增导航逻辑/路由/组件。
  3. 不带日期窗 query:聊天窗默认近 2 周覆盖近期通话。
  4. WA 待云客真机核实其 WA 语音列表的可点单元格后,再做(小补丁,复用同模式)。

5. 文件清单

  • 改 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:单元格不可点,row-click→drawer 不变(未改动)。
    • 缺字段格显 -- 不可点;network 仅 localhost。

7. 风险

  • owner/talker 缺失:部分通话 raw_json 可能缺 weChatId/talker → 该格显 -- 不可点(守卫已加),不阻塞。
  • smoke.db 微信语音数据:Plan 4a 实跑微信语音 2193 条进库,可测。