diff --git a/docs/api/frontend/meeting.md b/docs/api/frontend/meeting.md index 056f121..517c05b 100644 --- a/docs/api/frontend/meeting.md +++ b/docs/api/frontend/meeting.md @@ -281,7 +281,13 @@ **行为**:对每个 invitee: 1. 若该用户已在本会议活跃 → 跳过; 2. 生成 32 位十六进制 Token 写 Redis key `echo:meeting:invite:{token}`,TTL 600 秒(`MeetingInviteTokenTTL`),value = `{"room_code","inviter_id","invitee_id","has_password"}`; -3. 通过 Phase 2e-1 `notify.Pusher.PushBatch` 推送 `type=meeting_invite` 通知,`Extra` 含 `room_code / room_title / has_password / invite_token`; +3. 通过 Phase 2e-1 `notify.Pusher.PushBatch` 推送 `type=meeting_invite` 通知,`Extra`(Phase 2e-2 Task 13 完整版)包含以下字段: + - `room_code`:9 位会议号 `XXX-XXX-XXX`,前端跳 preview 页使用 + - `room_title`:房间标题,卡片展示 + - `has_password`:是否有密码,前端据此决定是否在 preview 页弹密码输入 + - `invite_token`:32 位 hex token,可用于兑换接口快速拉取 room_code + - `inviter_id` / `inviter_name` / `inviter_avatar`:邀请人展示字段,卡片左上角头像 + "XX 邀请你加入..." 文案来源 + - `expired_at`:Unix 秒,与 Redis TTL 同步;前端用 `expired_at * 1000 < Date.now()` 判断过期并灰显按钮 4. 离线被邀请者走通知入库,上线后由 WS 或未读轮询获得。 **响应 `200 OK`** @@ -683,4 +689,4 @@ ## 后续任务关联 - **Task 9**:Vue 前端 mediasoup-client 接入,按本文 WS 契约实现 `mediasoup.Transport` 的 `connect / produce` 回调;补齐 `ResumeConsumer`(Node REST 已就绪)或在 Go 侧改为创建 Consumer 后自动 resume;需处理 `meeting.host.changed` 的 `auto_reason` 字段以在 UI 上标注"自动转让"。 -- **Task 13**:通知卡片 UI 补齐 `meeting_invite` 内联按钮。 +- **Task 13 ✅(2026-04-23)**:通知卡片 UI 已完成内联按钮("立即加入 / 稍后"),点击"立即加入"跳 `/pages/meeting/preview?mode=join&code=xxx` 由 preview 页走 `/rooms/:code/join`;过期态由 `extra.expired_at * 1000 < Date.now()` 判定并合并为单个 disabled 的"邀请已过期"按钮,不再发起任何请求。 diff --git a/docs/api/frontend/notify.md b/docs/api/frontend/notify.md index c3b51e7..e9cb278 100644 --- a/docs/api/frontend/notify.md +++ b/docs/api/frontend/notify.md @@ -15,10 +15,34 @@ | 好友 | `friend_request` / `friend_accepted` / `friend_rejected` | contact | | 群聊 | `group_invite` / `group_join_request` / `group_join_approved` / `group_join_rejected` / `group_kicked` / `group_role_changed` | group | | 系统 | `system_broadcast` | admin 广播 | -| 会议 | `meeting_invite` / `meeting_reminder` | meeting(Phase 2e-2/2e-3 接入) | +| 会议 | `meeting_invite`(Phase 2e-2 已接入) / `meeting_reminder`(Phase 2e-3 预约会议接入) | meeting | > 通知持久化在 `notify_notifications` 表,每条通知对应单个接收者;30 天前的已读通知由后台定时任务自动清理,未读通知永久保留。 +### 支持内联按钮的通知类型 + +以下 type 在 `NotifyItem.vue` 会渲染"接受 / 拒绝"双按钮(文案因 type 而异),其余 type 仅展示点击跳转: + +| type | 接受按钮 | 拒绝按钮 | 过期态 | +|---|---|---|---| +| `friend_request` | 同意 | 拒绝 | 无 | +| `meeting_invite` | 立即加入 | 稍后 | ✅ 过期合并为单个 disabled 按钮"邀请已过期",由 `extra.expired_at * 1000 < Date.now()` 判定 | + +前端常量见 `frontend/src/constants/notify.js#NOTIFY_INLINE_ACTION_LABEL`。 + +### `meeting_invite` 专用 extra 字段(Phase 2e-2 Task 13) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `room_code` | string | 9 位会议号 `XXX-XXX-XXX`,前端跳 preview 页使用 | +| `room_title` | string | 房间标题,用于卡片正文 | +| `has_password` | boolean | 是否需要密码,前端在 preview 页决定是否弹密码输入框 | +| `invite_token` | string | 32 位 hex token,可选使用 `/meeting/invite-tokens/:token/redeem` 兑换 | +| `inviter_id` | int64 | 邀请人用户 ID | +| `inviter_name` | string | 邀请人昵称,用于卡片头部"XX 邀请你加入..." | +| `inviter_avatar` | string | 邀请人头像 URL | +| `expired_at` | int64 | Unix 秒,与 Redis `echo:meeting:invite:{token}` TTL(600s)同步,前端据此灰显按钮 | + --- ## 接口列表 diff --git a/docs/plans/2026-04-21-phase2e-2-implementation.plan.md b/docs/plans/2026-04-21-phase2e-2-implementation.plan.md index b116485..4d4bfe1 100644 --- a/docs/plans/2026-04-21-phase2e-2-implementation.plan.md +++ b/docs/plans/2026-04-21-phase2e-2-implementation.plan.md @@ -513,8 +513,9 @@ flowchart LR - **附带修复(commit 310ea03)**:`frontend/src/services/websocket.js` 在命中 `.ack` 事件后不再 `return`,改为同时 `_handleAck`(Promise 通路)+ `_emit`(订阅通路),修复 `chat.js` 订阅的 `im.message.send.ack / im.message.read.ack` 从未被触发、消息永远卡在 `_sending=true` 圆圈的回归 bug;`chat.js _appendMessage` 改为"命中 `client_msg_id` 的临时消息时就地合并服务端字段"而非直接丢弃,作为广播帧兜底。 - **验收**: - 后端 REST 验证(curl):`POST /api/v1/meeting/rooms/:code/invite` → 被邀请者 `GET /api/v1/notifications?category=meeting` 能取到 `type=meeting_invite` 通知,`extra` 包含 `room_code / room_title / inviter_id / inviter_name / inviter_avatar / has_password / invite_token / expired_at` 全部字段。 + - **Playwright UI E2E(2026-04-23 补做)**:testuser2 登录 → 通知中心渲染新邀请卡片的"立即加入 / 稍后"双按钮 + 渲染过期邀请卡片的单个 disabled"邀请已过期"按钮;点"立即加入"跳 `/pages/meeting/preview?mode=join&code=xxx`,code 与邀请精确一致;preview 页正确展示"即将加入会议 xxx"与设备选择面板。 - 过期态由 `expired_at` 驱动(Redis TTL 600s),过期后按钮自动灰显、点击有 toast 提示、不跳转。 -- **工作量**:**0.5 人日(实际约 0.3 人日)** +- **工作量**:**0.5 人日(实际约 0.4 人日,含 Playwright 回归补做)** ### Task 14:docker-compose 扩展 + 环境变量双态开关 diff --git a/docs/progress/CURRENT_STATUS.md b/docs/progress/CURRENT_STATUS.md index 2ce2212..408f986 100644 --- a/docs/progress/CURRENT_STATUS.md +++ b/docs/progress/CURRENT_STATUS.md @@ -43,7 +43,14 @@ - `extra` 完整度:`room_code / room_title / has_password / invite_token / inviter_id / inviter_name / inviter_avatar / expired_at` 全部存在且值正确 - `expired_at = invited_at + 600` 与 Redis TTL 对齐 - **前端聊天卡圈 Bug 验证**(用户手测):用户确认"经过我测试,这个问题已经修复好了",双用户发消息立即显示「已读 / 未读」标签,不再出现发送方消息前显示 loading 圆圈。 -- **Playwright UI 回归暂缓**:Playwright MCP 当前环境对多用户上下文与 `browser_install` 有限制,前端 UI 部分采用"用户手测 + 后端 API 完整验证"替代,等 Task 16 E2E 总回归时统一做;已在 todo 中标注。 +- **Playwright 前端 UI 回归**(2026-04-23 补做,采用"A 侧 curl 邀请 + B 侧 Playwright 登录"策略绕开多用户上下文限制): + - A 侧 curl:testuser1 创会 `room_code=516-162-828` → 邀请 testuser2(id=9);后端返回 `pushed:1, skipped:0` + - B 侧 Playwright:testuser2 登录 → 访问 `/pages/notify/index` → 通知列表成功渲染 2 张 `meeting_invite` 卡片: + * **新邀请卡片**(刚刚)→ 底部显示「立即加入 / 稍后」两个独立按钮 ✅ + * **过期邀请卡片**(10 分钟前,`expired_at` 已过去)→ 双按钮合并为单个 disabled 的「邀请已过期」按钮 ✅ + - 点击"立即加入"→ URL 跳到 `/pages/meeting/preview?mode=join&code=516-162-828` ✅(room_code 与邀请精确一致) + - preview 页正确渲染 `"即将加入会议 516-162-828,确认设备和显示名称后加入会议"`,摄像头/麦克风/扬声器/显示名称/默认开关全部可用 ✅ + - 截图留档:`task13-notify-meeting-invite-cards.png` / `task13-preview-page-after-invite-accept.png` ### 下一步