feat(meeting): Task 13 完成 meeting_invite 通知卡片对接

## 后端
- MeetingService.InviteUsers 的 PushPayload.Extra 补齐设计 §10.1 要求字段:
  inviter_id / inviter_name / inviter_avatar / expired_at(Unix 秒,
  与 Redis TTL MeetingInviteTokenTTL=600s 对齐)

## 前端
- constants/notify.js:supportsInlineAction 扩展支持 meeting_invite;
  新增 NOTIFY_INLINE_ACTION_LABEL 映射(meeting_invite → 立即加入/稍后)
  + NOTIFY_INLINE_ACTION_DEFAULT 兜底
- components/notify/NotifyItem.vue:
  * 按 type 动态渲染按钮文案(actionLabel 计算属性)
  * isExpired 计算属性:meeting_invite 比对 extra.expired_at * 1000
  * 过期态合并为单个 disabled 的"邀请已过期"按钮
  * 新增 .notify-btn--expired 灰显样式
- pages/notify/index.vue:
  * handleAccept / handleReject / _navigateByNotify 增加 meeting_invite 分支
  * 新增 _navigateToMeetingInvite 辅助:跳 /pages/meeting/preview?mode=join&code=xxx
  * 过期时 toast "邀请已过期"不跳转,"稍后"仅 markRead 不发接口

## 文档
- docs/plans/2026-04-21-phase2e-2-implementation.plan.md:Task 13 标  + 详细产出
- docs/progress/CURRENT_STATUS.md:新增 Task 13 专节(关键技术点 / 验证记录 / 下一步)
- .cursor/rules/project-context.mdc:2e-2 进度刷 Task 0-13 

## 验证
后端 curl 双角色端到端(testuser1 创会 → 邀请 testuser2 → 拉通知列表):
- type=meeting_invite,extra 8 字段齐全
- expired_at = invited_at + 600,与 Redis TTL 对齐

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-22 17:26:31 +08:00
parent 310ea03ce1
commit d6fb7ce291
7 changed files with 193 additions and 41 deletions

View File

@@ -693,6 +693,9 @@ func (s *MeetingService) InviteUsers(ctx context.Context, inviterID int64, code
return 0, 0, err
}
// 邀请人展示信息extra 里一并带上,前端卡片不必再查 actor_* 字段就能渲染
inviterName, inviterAvatar := s.resolveUserDisplay(ctx, inviterID)
pushed := 0
skipped := 0
seen := make(map[int64]struct{}, len(inviteeIDs))
@@ -720,11 +723,13 @@ func (s *MeetingService) InviteUsers(ctx context.Context, inviterID int64, code
skipped++
continue
}
now := time.Now()
expiredAt := now.Add(time.Duration(constants.MeetingInviteTokenTTL) * time.Second).Unix()
payload := invitePayload{
RoomCode: code,
InviterID: inviterID,
InviteeID: invitee,
CreatedAt: time.Now().Unix(),
CreatedAt: now.Unix(),
}
buf, _ := json.Marshal(payload)
if err := s.redis.Set(ctx, redisKeyInvitePrefix+token, string(buf),
@@ -736,11 +741,19 @@ func (s *MeetingService) InviteUsers(ctx context.Context, inviterID int64, code
roomID := room.ID
actor := inviterID
// extra 字段约定Phase 2e-2 Task 13 / design §10.1
// room_code / invite_token / room_title / has_password进会所需参数
// inviter_id / inviter_name / inviter_avatar前端卡片渲染"XX 邀请你加入..."
// expired_atUnix 秒,与 Redis TTL 同步,前端据此把按钮灰显
extra := map[string]interface{}{
"room_code": code,
"invite_token": token,
"room_title": room.Title,
"has_password": room.PasswordHash != nil,
"room_code": code,
"invite_token": token,
"room_title": room.Title,
"has_password": room.PasswordHash != nil,
"inviter_id": inviterID,
"inviter_name": inviterName,
"inviter_avatar": inviterAvatar,
"expired_at": expiredAt,
}
payloads = append(payloads, &notifyService.PushPayload{
UserID: invitee,