143 lines
5.7 KiB
Markdown
143 lines
5.7 KiB
Markdown
# 任务中心领取 Bug 修复计划
|
||
|
||
## 问题描述
|
||
小程序(bindbox-mini)任务中心,活动前端领取不了但是可以看到。
|
||
|
||
## 根因分析
|
||
|
||
### 核心问题:前后端进度数据源不一致
|
||
|
||
**后端 `ClaimTier`**(service.go:738)使用 `TierProgressMap`(基于窗口化、受任务时间约束的进度)来校验是否达标:
|
||
```go
|
||
if tp, ok := progress.TierProgressMap[tierID]; ok {
|
||
currentOrderCount = tp.OrderCount // 窗口化进度,受任务 StartTime 约束
|
||
}
|
||
```
|
||
|
||
**前端 `isTierClaimable`**(index.vue:387-437)使用的是 `subProgress`(活动级别汇总)或全局进度(`orderCount` / `orderAmount`),**完全没有使用 `tier_progress_map`**。
|
||
|
||
### 触发条件
|
||
|
||
最近提交 `e0db875` 修改了 `computeTimeWindow`:
|
||
- **修改前**:`lifetime` / `since_registration` / 空窗口 → `return nil, nil`(不限时间)
|
||
- **修改后**:`lifetime` / 默认窗口 → `return taskStart, taskEnd`(受任务时间约束)
|
||
|
||
这导致 `TierProgressMap` 中的进度值被任务时间限制,但 API 返回的 `order_count` / `sub_progress` 全局进度仍然不受时间限制(service.go:618-622 用 `nil, nil` 查询)。
|
||
|
||
### 不一致的结果
|
||
|
||
| 数据源 | 时间约束 | 进度值 | 使用方 |
|
||
|--------|---------|--------|--------|
|
||
| `TierProgressMap` | 受任务 StartTime/EndTime 约束 | 较小 | 后端 ClaimTier |
|
||
| `SubProgress` / 全局进度 | 无时间约束 | 较大 | 前端 isTierClaimable |
|
||
| API 返回的 `order_count` | 无时间约束(或活动级别) | 较大 | 前端 isTierClaimable |
|
||
|
||
**场景举例**:
|
||
- 用户在任务创建前有 5 笔历史订单,任务创建后有 2 笔新订单
|
||
- 任务档位要求 `order_count >= 3`
|
||
- 前端看到全局 `orderCount = 7`(不限时间) → 显示"领取"按钮
|
||
- 后端 `TierProgressMap.OrderCount = 2`(只统计任务开始后) → 返回"任务条件未达成"
|
||
|
||
**或者反过来**:
|
||
- 前端也使用 `subProgress` 做判断,但 `subProgress` 的统计可能不包含某些场景的数据
|
||
- 导致前端 `isTierClaimable` 返回 `false`,按钮不出现
|
||
- 用户看到任务但无法领取
|
||
|
||
## 任务类型
|
||
- [x] 后端 (→ 后端逻辑修复)
|
||
- [x] 前端 (→ 前端判断修复)
|
||
|
||
## 技术方案
|
||
|
||
**方案 A(推荐):后端 API 返回 `tier_progress_map`,前端使用**
|
||
|
||
让前后端使用同一份进度数据源(`TierProgressMap`),确保判断一致。
|
||
|
||
### 实施步骤
|
||
|
||
#### Step 1:后端 - API Response 增加 `tier_progress_map` 字段
|
||
|
||
**文件**: `internal/api/task_center/tasks_app.go`
|
||
|
||
1. 在 `taskProgressResponse` 结构体中添加 `TierProgressMap` 字段:
|
||
```go
|
||
type tierProgressItem struct {
|
||
TierID int64 `json:"tier_id"`
|
||
OrderCount int64 `json:"order_count"`
|
||
OrderAmount int64 `json:"order_amount"`
|
||
InviteCount int64 `json:"invite_count"`
|
||
FirstOrder bool `json:"first_order"`
|
||
}
|
||
|
||
type taskProgressResponse struct {
|
||
// ... existing fields ...
|
||
TierProgress []tierProgressItem `json:"tier_progress"` // 新增
|
||
}
|
||
```
|
||
|
||
2. 在 `GetTaskProgressForApp` handler 中填充该字段。
|
||
|
||
#### Step 2:前端 - `isTierClaimable` 优先使用 `tier_progress`
|
||
|
||
**文件**: `bindbox-mini/pages-user/tasks/index.vue`
|
||
|
||
1. 在 `fetchData` 中解析并存储 `tier_progress` 到 `taskProgress[taskId]`
|
||
2. 修改 `isTierClaimable` 函数,优先从 `tierProgress` 中查找对应 tier 的进度
|
||
3. 修改 `getTierProgressText` 和 `getTierProgressPercent`,同步使用新数据源
|
||
|
||
```js
|
||
function isTierClaimable(task, tier) {
|
||
const progress = taskProgress[task.id] || {}
|
||
|
||
// 优先使用 tier 级别窗口化进度(与后端 ClaimTier 保持一致)
|
||
if (progress.tierProgress) {
|
||
const tp = progress.tierProgress.find(t => t.tier_id === tier.id)
|
||
if (tp) {
|
||
const metric = tier.metric || ''
|
||
const threshold = tier.threshold || 0
|
||
const operator = tier.operator || '>='
|
||
|
||
let current = 0
|
||
if (metric === 'first_order') return tp.first_order || false
|
||
else if (metric === 'order_count') current = tp.order_count || 0
|
||
else if (metric === 'order_amount') current = tp.order_amount || 0
|
||
else if (metric === 'invite_count') current = tp.invite_count || 0
|
||
|
||
if (operator === '>=') return current >= threshold
|
||
if (operator === '==') return current === threshold
|
||
if (operator === '>') return current > threshold
|
||
return current >= threshold
|
||
}
|
||
}
|
||
|
||
// fallback: 原有逻辑
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### Step 3:同步修改进度显示
|
||
|
||
修改 `getTierProgressText` 和 `getTierProgressPercent` 也优先使用 `tierProgress` 数据,确保用户看到的进度和可领取状态一致。
|
||
|
||
### 关键文件
|
||
|
||
| 文件 | 操作 | 说明 |
|
||
|------|------|------|
|
||
| `internal/api/task_center/tasks_app.go:106-170` | 修改 | 添加 tier_progress 到响应体 |
|
||
| `bindbox-mini/pages-user/tasks/index.vue:387-437` | 修改 | isTierClaimable 使用 tier_progress |
|
||
| `bindbox-mini/pages-user/tasks/index.vue:440-478` | 修改 | getTierProgressText 使用 tier_progress |
|
||
| `bindbox-mini/pages-user/tasks/index.vue:590-630` | 修改 | getTierProgressPercent 使用 tier_progress |
|
||
| `bindbox-mini/pages-user/tasks/index.vue:551-576` | 修改 | fetchData 解析 tier_progress |
|
||
|
||
### 风险与缓解
|
||
|
||
| 风险 | 缓解措施 |
|
||
|------|----------|
|
||
| 前端旧版本未使用 `tier_progress` 字段 | 保持原有 `order_count`、`sub_progress` 字段不变,`tier_progress` 为新增字段,向后兼容 |
|
||
| `tier_progress_map` 为空(数据库无 tiers 配置) | 前端 fallback 到原有 `subProgress` / 全局进度逻辑 |
|
||
| 已部署但未刷新前端的用户 | `tier_progress` 是附加字段,不影响旧逻辑 |
|
||
|
||
### SESSION_ID(供 /ccg:execute 使用)
|
||
- CODEX_SESSION: N/A(未使用外部模型)
|
||
- GEMINI_SESSION: N/A(未使用外部模型)
|