Files
wecom_it_smart_desk/docs/02-技术文档/技术方案-REQ-用户-001-群聊入口接线-v1.0.md
T
Simon 5311a526af feat(h5/chat): 员工端群聊按钮接线 → 参与者面板(REQ-用户-001)
- InputBar.vue 重写 handleGroupChat():无会话 toast「请先发起会话」;有会话 → 开关参与者面板;零参与者且展开时补邀请引导。
- 契约常量 GROUP_CHAT_NO_CONVERSATION_TIP / GROUP_CHAT_EMPTY_TIP 与 InputBar.test.ts 完全对齐;删除字面量 '群聊功能开发中' 与 startGroupChat 调用。
- InputBar.test.ts 102/102 通过;契约测试已同步。
- 新增技术方案 docs/02-技术文档/技术方案-REQ-用户-001-群聊入口接线-v1.0.md(方案 A store 驱动)。
- 新增任务说明书 docs/07-项目管理/任务说明书/任务说明书-REQ-用户-001-群聊入口接线.md(按模板)。
- PRD-REQ-用户-001-群聊双模式-v1.0.md 头部补「关联文档」双向链 + 状态「待评审」→「已实现」。

PRD: docs/01-产品文档/05-用户端H5/PRD-REQ-用户-001-群聊双模式-v1.0.md
REF:  REQ-用户-001-群聊入口接线(坐席端不动,按用户拍板 q-1)
2026-08-09 13:16:27 +08:00

16 KiB
Raw Blame History

技术方案 — 员工端 H5 群聊入口接线

需求编号: REQ-用户-001-群聊入口接线 版本: v1.0 日期: 2026-08-08 作者: 高见远(架构师) 状态: 已实现 子系统: 用户端H5 模块: 群聊 关联文档:

  • PRD: docs/01-产品文档/05-用户端H5/PRD-REQ-用户-001-群聊双模式-v1.0.md
  • 既有技术方案: docs/02-技术文档/技术架构/技术方案-REQ-用户-001-群聊双模式-v1.0.md
  • 既有架构设计: docs/02-技术文档/01-架构设计/群聊参与者展开缩略双模式-架构设计.md
  • 任务说明书: docs/07-项目管理/任务说明书/任务说明书-REQ-用户-001-群聊入口接线.md

补充参考(非三件套,仅供实现比对,路径均已核验存在):

  • 原型(用户端): docs/01-产品文档/05-用户端H5/原型-REQ-用户-001-群聊双模式-v1.0.html
  • 原型(坐席端): docs/01-产品文档/04-坐席工作台/原型-REQ-坐席-003-群聊双模式-v1.0.html
  • 工具栏基线交付清单: docs/01-产品文档/02-会话管理/交付-REQ-会话-001-工具栏统一设计v1.9-开发交付清单.md

一、背景与目标

1.1 需求触发原点

使用者(IT 支持组组长)提出的原始诉求是两句话:

  1. 确认坐席端的群聊功能到底做没做?
  2. 如果做了,把员工端工具栏上那颗「群聊」按钮接上。

1.2 核查结论:坐席端早已实现

代码核查确认,坐席端群聊能力完整可用,本次无需任何改动。其入口是「邀请」按钮,而非名为「群聊」的按钮:

能力 文件 · 行号 状态
参与者横条 src/frontend-agent/src/components/chat/ChatArea.vue L38 <ParticipantBar> 已实现
邀请事件出口 ChatArea.vue L50 @invite="showInviteParticipantDialog = true" 已实现
邀请参与者弹窗 ChatArea.vue L221-222 <InviteParticipantDialog v-model="showInviteParticipantDialog"> 已实现
摇人邀请弹窗 ChatArea.vue L214 <InviteDialog> 已实现
就地展开面板 src/frontend-agent/src/components/conversation/ParticipantBar.vue L78 <ParticipantExpandedPanel> 已实现
组件注册 ChatArea.vue L277-279 三处 import 已实现

"功能没做"是误判,误判的根因是文档不可发现PRD PRD-REQ-用户-001-群聊双模式-v1.0.md 头部缺失 关联文档 字段,从 PRD 无法反向索引到技术方案与架构设计;而实现层的入口叫「邀请」、需求层的名字叫「群聊」,术语不对齐,检索时对不上。本次一并回写 PRD 关联链路(见 §六)。

1.3 本方案的目标

本方案不新增任何群聊能力,只记录并固化一件事:员工端 H5 工具栏「群聊」按钮的入口接线。

REQ-用户-001 的群聊双模式(缩略头像条 + 展开参与者面板 + 邀请 + 退出)在双端均已完整实现,后端接口与 WebSocket 双池推送全部打通。唯一缺口是:员工端 H5 那颗 title="群聊" 的按钮,点击后只弹一句 showToast('群聊功能开发中') —— 这句占位 toast 正是使用者判定"功能未开发"的直接来源。

本次改动即:把这句占位 toast 换成对已有 store action 的调用。

维度 决策
后端改动
store 改动 toggleParticipantPanel() 已存在并已导出)
新增组件
前端改动文件 InputBar.vue ×1 + 测试 ×2
本次范围 仅员工端 H5,坐席端零改动(用户已拍板)

二、范围与边界

2.1 在范围内

  • 员工端 H5src/frontend-h5/**
  • InputBar.vuehandleGroupChat() 的入口接线
  • 两个测试文件的断言同步
  • PRD ↔ 技术方案 ↔ 任务说明书 三件套关联回写

2.2 明确不做(Out of Scope

  • 0 后端改动:不改接口、不改模型、不改推送逻辑
  • 不部署:仅留工作区改动,部署另行安排
  • 坐席端不动src/frontend-agent/** 零改动(已有「邀请」入口,用户已拍板)
  • 不新增坐席端第 6 个工具栏按钮;将来若做,采用「邀请」改名为「群聊」的方式,保持 5 键基线(方向已定,本次不执行)
  • 不做需求编号收敛、不处理其他文档治理项
  • 不改 ChatPanel.vue L69 的 ParticipantStrip 渲染条件

三、实现细节

以下文件与行号均以当前工作区代码为准,逐条 Read 核验。

3.1 入口断点(改造前)

位置 内容
src/frontend-h5/src/components/chat/InputBar.vue L345-360 拱形工具栏第 5 个按钮:L348 title="群聊"、L349 aria-label="群聊"、L350 @click="handleGroupChat"
src/frontend-h5/src/components/chat/InputBar.vue L872-874v1.9 时期) 占位实现 showToast('群聊功能开发中')

改造前的函数体只有一行 toast,且注释写着「store 暂无 group chat action……后续接入真实群聊会话时,把 toast 替换为 store.startGroupChat() 即可」。该注释在写下之后即已过期 —— toggleParticipantPanel() 早已在 store 落地,而 startGroupChat 是一个从未存在过的虚构 action。注释与代码事实脱节,是按钮长期停留在占位态的直接原因。

3.2 目标实现(改造后 · 已落地)

InputBar.vue 现状 L867-899(常量 L868 / L871,函数 L886-899):

/** 无会话时点击群聊的提示文案。 */
const GROUP_CHAT_NO_CONVERSATION_TIP = '请先发起会话'

/** 会话内暂无其他同事时的邀请引导文案。 */
const GROUP_CHAT_EMPTY_TIP = '还没有其他同事,点击「邀请参与者」拉同事进群'

function handleGroupChat(): void {
  if (!currentConversation.value) {
    showToast(GROUP_CHAT_NO_CONVERSATION_TIP)
    return
  }

  const hasParticipants = store.participants.length > 0
  store.toggleParticipantPanel()

  // 仅在「由收起切换为展开」且暂无其他同事时补引导,避免收起面板时也弹提示
  if (!hasParticipants && store.participantPanelVisible) {
    showToast(GROUP_CHAT_EMPTY_TIP)
  }
}

与原始设计的差异说明:契约描述为「先算 nextVisible = !participantPanelVisible,若 !hasParticipants && nextVisible 则提示」。落地实现改为toggle() 再读 store.participantPanelVisible,两者语义完全等价(toggle 后的实际值即 nextVisible),且读实际状态比预测值更稳健 —— 若将来 action 内部新增条件分支,实现不会与预测值脱节。文案常量与三分支行为与契约一致。

3.3 handleGroupChat 行为契约(三分支)

# 前置条件 toast toggleParticipantPanel()
1 无当前会话 '请先发起会话' 不调用(提前 return
2 有会话 + participants.length > 0 调用 1 次
3 有会话 + participants.length === 0 且切换为展开 '还没有其他同事,点击「邀请参与者」拉同事进群' 调用 1 次

分支 3 的设计依据:零参与者时 ChatPanel.vue L69 的 ParticipantStrip 不渲染,用户界面上没有任何群聊线索,因此必须由 toast 补一句引导。而面板本身照常打开 —— ParticipantList 在零被邀请人时仍会渲染「坐席 + 我(发起人)」,且会话发起人的 canInvite 恒为 true,「+ 邀请参与者」按钮天然可达,不会出现空白面板。

3.4 依赖的 store 契约(只读引用,零修改)

src/frontend-h5/src/stores/conversation.ts

契约项 行号 说明
currentConversation L135 ref<ConversationInfo | null>(null) — 分支 1 的判据
participants L191 ref<ParticipantItem[]>([]) — 分支 2/3 的判据
participantPanelVisible L194 ref<boolean>(false) — 面板显隐单一真值源
toggleParticipantPanel() L1296 participantPanelVisible.value = !participantPanelVisible.value;无网络请求、无持久化
导出 L2185 / L2214 状态与 action 均已在 return 块内
重置 L1367 会话清理时置回 false

⚠️ 语义提示:这是 toggle 而非 open。当前按钮为唯一触发点,用户在面板内通过关闭按钮直接置 false,不会与 toggle 产生状态错位。若后续新增第二个触发入口,需评估是否补一个 openParticipantPanel() 语义化 action。

3.5 承载面板的容器(零修改)

src/frontend-h5/src/components/chat/ChatPanel.vue

位置 内容
L69 <ParticipantStrip v-if="store.participants.length > 0" /> — 缩略头像条,零参与者时不渲染
L133-142 底部弹层 <van-popup :show="store.participantPanelVisible" position="bottom" round teleport="body" :style="{ maxHeight: '60vh' }">,其中 L141 挂载 <ParticipantList />

原始描述记为 L135-142,实测弹层完整块为 L133-142van-popup 起始标签在 L133,L132 为注释行),<ParticipantList /> 位于 L141。以实测为准。

3.6 为什么不在 InputBar 里挂 InviteParticipantSheet

InviteParticipantSheet.vue 由面板内部自行挂载handleGroupChat 不直接调用它:

  • 挂载点一:ParticipantStrip.vue L91
  • 挂载点二:ParticipantList.vue L115

若 InputBar 再挂一次,全应用将出现第三份同名弹层实例,带来状态互相覆盖与 v-model 归属混乱的风险。分支 3 的引导文案明确指向面板内的「邀请参与者」按钮,用户路径为:群聊按钮 → 面板打开 → 点面板内「+ 邀请参与者」→ 已挂载的 Sheet 弹出,全程复用既有实例。

3.7 架构一致性:为何选 store 驱动而非 emit

InputBar.vue L903 注释白纸黑字写明:「InputBar 是 v1.3 唯一坐席入口,不向 ChatPanel emit。」 全文件 defineEmits 零匹配,InputBar 至今保持「零 emit、纯 store 驱动」的单向数据流。为一个按钮破例会留下两套并存的通信范式,故定案 store 驱动 —— 这也让改动收敛到 1 个业务文件。


四、测试影响

4.1 受影响的测试文件

文件 改动性质
src/frontend-h5/src/components/chat/InputBar.test.ts 同步断言
src/frontend-h5/src/components/chat/__tests__/InputBar.vitest.test.ts 同步断言

4.2 契约常量对齐

两个测试文件必须复刻 InputBar.vue 的文案常量,不得内联裸字符串:

常量
GROUP_CHAT_NO_CONVERSATION_TIP '请先发起会话'
GROUP_CHAT_EMPTY_TIP '还没有其他同事,点击「邀请参与者」拉同事进群'

现状:InputBar.test.ts L177 / L180 已对齐(含注释标注「与 InputBar.vue 对齐」)。

4.3 弃用串必须 0 命中

弃用串 断言位置 说明
'群聊功能开发中' InputBar.test.ts L848 · InputBar.vitest.test.ts L867 含注释在内,InputBar.vue 全文不得再出现
'startGroupChat' InputBar.test.ts L861 · InputBar.vitest.test.ts L871 虚构 action,含注释在内不得再被引用

R1 风险(预期内的红):存量测试原本断言 showToast('群聊功能开发中'),接线后必然变红。这是预期结果,不得靠删除测试绕过,必须改为断言新契约。

4.4 测试覆盖现状

用例 位置
三分支行为契约 InputBar.test.ts L797 describe('InputBar v2.1 — handleGroupChat 三分支行为契约')
按钮可访问性属性不变 InputBar.vitest.test.ts L625title / aria-label / @click 三属性正则)
接线目标为 toggleParticipantPanel InputBar.test.ts L861 · InputBar.vitest.test.ts L847

五、验收标准

5.1 功能验收

# 场景 预期
A1 未发起会话 → 点「群聊」 toast「请先发起会话」;打开参与者面板
A2 有会话(含参与者)→ 点「群聊」 参与者面板打开;再点一次关闭(开/关可逆)
A3 有会话 + 零参与者 → 点「群聊」 面板打开 toast「还没有其他同事,点击「邀请参与者」拉同事进群」
A4 A3 状态下再点「群聊」收起面板 面板关闭,重复弹邀请引导
A5 全代码库 无任何 startGroupChat 调用;InputBar.vue'群聊功能开发中'
A6 A3 中点面板内「+ 邀请参与者」 复用既有 InviteParticipantSheet,无第三处挂载
A7 工具栏基线 5 按钮顺序 emoji / 文件 / 坐席 / 语音 / 群聊 不变,拱形轨道无形变,L345-360 模板未被改动

5.2 质量门禁

  • pnpm test 全量通过,重点 InputBar 相关 2 个测试文件
  • ESLint 无新增告警(showToast import 在分支 1/3 仍被使用,不会变成 unused)
  • 坐席端零改动,不参与本次回归

5.3 文档验收

  • PRD 头部 关联文档 字段存在,且指向本方案 + 既有技术方案 + 既有架构设计
  • 本方案 关联文档 反向指向 PRD,双向一致
  • 任务说明书已建立并被三件套互相引用

六、关联文档回写(本轮同步执行)

本轮同时修复「PRD 不可发现」这一根因:

文档 回写内容
PRD-REQ-用户-001-群聊双模式-v1.0.md 头部新增 关联文档 字段(指向既有技术方案 + 既有架构设计 + 本方案);状态待评审已实现
本方案 关联文档 指向 PRD + 既有技术方案 + 既有架构设计 + 任务说明书
任务说明书-REQ-用户-001-群聊入口接线.md 新建,引用上述四份文档

遗留观察(不在本轮范围)docs/02-技术文档/01-架构设计/群聊参与者展开缩略双模式-架构设计.md 头部的 PRD 指向 docs/01-产品文档/02-会话管理/群聊参与者展开缩略双模式-PRD.md,与 05-用户端H5/PRD-REQ-用户-001-群聊双模式-v1.0.md 构成同一需求的两份 PRD(两份文件均真实存在,非死链)。属需求编号收敛范畴,本轮按用户要求不处理,登记备查。


七、影响面与风险

7.1 影响面

文件 改动性质 风险
src/frontend-h5/src/components/chat/InputBar.vue 替换单个函数体 + 新增 2 个文案常量 🟢 极低
src/frontend-h5/src/components/chat/InputBar.test.ts 同步断言 🟢 极低
src/frontend-h5/src/components/chat/__tests__/InputBar.vitest.test.ts 同步断言 🟢 极低

零影响面清单:后端 · store · 路由 · 网络请求 · 数据库 · ChatPanel.vue · ParticipantList.vue · ParticipantStrip.vue · InviteParticipantSheet.vue · 坐席端全部文件 —— 均不改动。

7.2 风险登记

# 风险 等级 缓解
R1 存量测试断言旧占位文案,改动后必红 🟠 已纳入交付清单,两个测试文件同步更新;不得删测试绕过
R2 showToast import 变为未使用 🟡 分支 1/3 仍在使用,import 保持有效
R3 toggle 语义在未来多入口场景下状态错位 🟡 当前单入口;后续新增入口时引入 openParticipantPanel()
R4 坐席未接入且零被邀请人时,面板仅一行「我」 🟡 可接受;分支 3 的 toast 已给出下一步引导
R5 误改工具栏 DOM,破坏 v1.9 拱形轨道基线 🟠 严禁改动 L345-360 模板,只改 script;验收项 A7 比对 5 按钮基线

八、变更记录

日期 版本 变更 作者
2026-08-08 v1.0 初版;定案 store 驱动方案,落地三分支契约;补齐 需求编号 / 子系统 / 模块 / 关联文档 头部字段;状态置为「已实现」;同步 PRD 关联回写 高见远