feat(redpacket): 红包与游戏横幅全量实现 (#19~#24)

- #19 fix: SendRedEnvelopeUseCase 动态取 currencyType(workspaceId>0 取
  workspace.currency,修复 iOS 硬编码 PEA → 150001 错误)
- #20: RedEnvelopeBubble typ=8,四态(橙色领取/已领/过期/抢完)+ 领取按钮
- #21: ReceiveRedEnvelopeUseCase POST /app/api/wallet/rp/receive,
  typed JSON body(避免 code=30007),SnackBar 反馈
- #22: SendRedEnvelopeSheet BottomSheet,STANDARD_RP + LUCKY_RP,
  发送成功后构建 typ=8 content JSON 回调给 ChatPage
- #23: BannerViewModel Notifier,Group.topic 双格式解析(JSON object/string),
  FetchBannerUseCase + Timer 倒计时 + applyNewRound WS 接口
- #24: BannerView 游戏横幅条(状态/倒计时/上期结果),
  MiniAppFloatButton 悬浮按钮(hasGame 显示/隐藏,onTap TODO #25)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
pp-bot
2026-03-23 23:11:29 +09:00
parent 83774f5f61
commit d9539d391c
12 changed files with 1616 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
# 红包与游戏横幅 — 架构文档
> 对应 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 | 发送红包 UISTANDARD_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 红包消息 contenttyp=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` → RedEnvelopeBubbletyp=1 content=="[红包]" fallback 检测 |
---
## 7. 待完成
- **地雷红包 / 牛牛红包**MINE_RP + NN_RP 发送 UI需 rp-config/get
- **Mini-app WebView**MiniAppFloatButton 点击后打开小程序(#25
- **领取详情页**抢红包排行榜iOS RedEnvelopeDetailSheet
- **Game settle polling**drawTime 后每 3s 轮询直到新一期