# 红包与游戏横幅 — 架构文档 > 对应 Gitea issues #19–#24 > 参考实现:`im-client-ios-swift-demo` RedEnvelopeSendView + BannerViewModel + PlatformBannerViewModel > Bug 参考:bug 截图文件夹(currencyType=PEA 错误 + [红包] 文本未渲染) --- ## 1. 功能范围 | Issue | 功能 | 状态 | |-------|------|------| | #19 | currencyType 动态化(修复 PEA 硬编码) | ✅ 已实现 | | #20 | 红包消息气泡(typ=8,三态 UI) | ✅ 已实现 | | #21 | 领取红包(/app/api/wallet/rp/receive) | ✅ 已实现 | | #22 | 发送红包 UI(STANDARD_RP + LUCKY_RP) | ✅ 已实现 | | #23 | BannerViewModel — 游戏横幅 + WS NewRound | ✅ 已实现 | | #24 | 游戏悬浮按钮(MiniAppFloatButton) | ✅ 已实现 | --- ## 2. 目录结构 ``` data/remote/ ├── red_envelope_request.dart # 发送/领取 API Request └── banner_request.dart # 游戏横幅 API Request features/chat/ ├── di/ │ └── red_envelope_provider.dart # 红包 DI 装配 ├── presentation/ │ └── banner_view_model.dart # 游戏横幅 ViewModel ├── usecases/ │ ├── send_red_envelope_usecase.dart # 发送红包 │ ├── receive_red_envelope_usecase.dart # 领取红包 │ └── fetch_banner_usecase.dart # 拉取游戏横幅 └── view/widgets/ ├── red_envelope_bubble.dart # 红包气泡(typ=8) ├── send_red_envelope_sheet.dart # 发送 BottomSheet ├── banner_view.dart # 游戏横幅条 └── miniapp_float_button.dart # 游戏悬浮按钮 ``` --- ## 3. 关键数据格式 ### 3.1 红包消息 content(typ=8) ```json { "id": "rp_xxx", "rp_type": "STANDARD_RP", "remark": "恭喜发财", "total_amount": "10.00", "total_num": 5, "rp_status": 0 } ``` **rp_status 含义:** | 值 | 含义 | UI | |----|------|----| | 0/1 | 未领取 | 橙色 + 领取按钮 | | 2 | 已领取 | 灰色 "已领取" | | 3 | 已过期 | 灰色 "红包已过期" | | 4 | 已抢完 | 灰色 "手慢了" | | 6 | 等待开奖(NN_RP) | 橙色 + 等待 | ### 3.2 发送红包 API **POST /payment/rp/send** ```json { "amount": "10.00", "currencyType": "PEA", "chatID": 12345, "chatType": 2, "rpType": "STANDARD_RP", "recipientIDs": [], "rpNum": 5, "remark": "恭喜发财", "msgSendTime": 1234567890000000 } ``` **⚠️ currencyType 规则:** - `workspaceId > 0` → 从 `/workspace/workspace/get` 取 `workspace.currency`(如 `"USDT"`) - `workspaceId == 0` → `"PEA"`(默认) ### 3.3 领取红包 API **POST /app/api/wallet/rp/receive** ```json { "rpID": "rp_xxx", "chatID": 12345, "rpType": "STANDARD_RP", "sendRpMsgID": 99999, "supportMask": true, "supportHideTail": true } ``` **⚠️ 必须 JSON typed body(非 form 字符串),否则 code=30007** --- ## 4. 数据流 ### 4.1 发送红包 ``` ChatPage → SendRedEnvelopeSheet └─ SendRedEnvelopeViewModel.send(params) ├─ currencyType = workspaceId > 0 ? workspace.currency : "PEA" ├─ SendRedEnvelopeUseCase.execute() → POST /payment/rp/send → rpID └─ 构建 rawContent JSON → 通过 ChatViewModel.sendMessage(typ=8, content) ``` ### 4.2 领取红包 ``` RedEnvelopeBubble.onTap └─ ReceiveRedEnvelopeUseCase.execute(rpID, chatID, rpType, messageId) └─ POST /app/api/wallet/rp/receive → grabFlag=true → SnackBar 显示金额 → grabFlag=false → SnackBar 显示原因 ``` ### 4.3 游戏横幅 ``` ChatPage (群聊) └─ BannerViewModel(chatId).build() ├─ 解析 Group.topic JSON → gameId ├─ FetchBannerUseCase(gameId) → POST /lucky/banner/get ├─ Timer 每秒 tick → countdown └─ SocketManager 消息流 → 过滤 miniapp.NewRound → applyGameInfo(bean) → 刷新横幅 ``` --- ## 5. Provider 设计 ``` sendRedEnvelopeUseCaseProvider → networkSdkApiProvider receiveRedEnvelopeUseCaseProvider → networkSdkApiProvider fetchBannerUseCaseProvider → networkSdkApiProvider bannerViewModelProvider.family(chatId) ``` --- ## 6. Bug 修复说明 | Bug | 原因 | 修复 | |-----|------|------| | code=150001 | iOS 硬编码 `currencyType=PEA`,workspace 用 USDT | Flutter 动态取 workspace.currency | | `[红包]` 文本 | 旧消息 typ=1 content="[红包]",未路由到红包气泡 | `typ=8` → RedEnvelopeBubble;typ=1 content=="[红包]" fallback 检测 | --- ## 7. 待完成 - **地雷红包 / 牛牛红包**:MINE_RP + NN_RP 发送 UI(需 rp-config/get) - **Mini-app WebView**:MiniAppFloatButton 点击后打开小程序(#25) - **领取详情页**:抢红包排行榜(iOS RedEnvelopeDetailSheet) - **Game settle polling**:drawTime 后每 3s 轮询直到新一期