chore(task): archive 04-28-feat-points-ledger
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
# IMPLEMENTATION_PLAN:积分流水列表功能
|
||||
|
||||
## 概述
|
||||
|
||||
本计划按 trellis 工作流制定,遵循 `schema → repository → service → router` 后端分层和 `data → presentation` 前端分层原则。
|
||||
|
||||
## 前置条件确认
|
||||
|
||||
| 条件 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| `points_ledger` 表存在 | ✅ | 结构完整,有索引支持分页 |
|
||||
| Repository 写入方法存在 | ✅ | `append_ledger()` 已实现 |
|
||||
| 积分中心页面存在 | ✅ | `CoinCenterScreen` 可添加入口 |
|
||||
| 前端 points feature 存在 | ✅ | `features/points/` 目录已存在 |
|
||||
|
||||
## 实现步骤
|
||||
|
||||
### Step 1: 后端 Schema 定义
|
||||
|
||||
**文件**:`backend/src/v1/points/schemas.py`
|
||||
|
||||
**新增内容**:
|
||||
```python
|
||||
class LedgerItem(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True, serialize_by_alias=True)
|
||||
|
||||
id: str
|
||||
direction: int # 1=收入, -1=支出
|
||||
amount: int = Field(ge=1)
|
||||
balance_after: int = Field(alias="balanceAfter", ge=0)
|
||||
change_type: str = Field(alias="changeType")
|
||||
display_text: str = Field(alias="displayText")
|
||||
created_at: str = Field(alias="createdAt")
|
||||
|
||||
|
||||
class LedgerListResponse(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True, serialize_by_alias=True)
|
||||
|
||||
items: list[LedgerItem]
|
||||
next_cursor: str | None = Field(alias="nextCursor", default=None)
|
||||
has_more: bool = Field(alias="hasMore")
|
||||
```
|
||||
|
||||
### Step 2: 后端 Repository 方法
|
||||
|
||||
**文件**:`backend/src/v1/points/repository.py`
|
||||
|
||||
**新增方法**:
|
||||
```python
|
||||
async def list_ledger(
|
||||
self,
|
||||
*,
|
||||
user_id: UUID,
|
||||
limit: int,
|
||||
cursor: datetime | None = None,
|
||||
) -> tuple[list[PointsLedger], bool]:
|
||||
# 按 created_at DESC 分页查询
|
||||
# cursor 为上一页最后一条的 created_at
|
||||
# 多查一条判断 has_more
|
||||
```
|
||||
|
||||
### Step 3: 后端 Service 方法
|
||||
|
||||
**文件**:`backend/src/v1/points/service.py`
|
||||
|
||||
**新增方法**:
|
||||
```python
|
||||
async def get_ledger_list(
|
||||
self,
|
||||
*,
|
||||
user_id: UUID,
|
||||
limit: int = 20,
|
||||
cursor: str | None = None,
|
||||
) -> tuple[list[LedgerItem], str | None, bool]:
|
||||
# 1. 解析 cursor 为 datetime
|
||||
# 2. 调用 repository.list_ledger()
|
||||
# 3. 组装 display_text(根据 change_type)
|
||||
# 4. 返回 (items, next_cursor, has_more)
|
||||
```
|
||||
|
||||
**display_text 映射**:
|
||||
```python
|
||||
CHANGE_TYPE_TEXT = {
|
||||
"register": ("注册赠送", "Registration bonus"),
|
||||
"purchase": ("购买积分包", "Purchase credits"),
|
||||
"consume": ("AI 对话消耗", "AI chat cost"),
|
||||
"adjust": ("系统调整", "System adjustment"),
|
||||
"refund": ("退款", "Refund"),
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: 后端 Router 端点
|
||||
|
||||
**文件**:`backend/src/v1/points/router.py`
|
||||
|
||||
**新增端点**:
|
||||
```python
|
||||
@router.get("/ledger", response_model=LedgerListResponse)
|
||||
async def get_points_ledger(
|
||||
service: Annotated[PointsService, Depends(get_points_service)],
|
||||
current_user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
cursor: str | None = Query(default=None),
|
||||
) -> LedgerListResponse:
|
||||
...
|
||||
```
|
||||
|
||||
### Step 5: 前端 Model 定义
|
||||
|
||||
**文件**:`apps/lib/features/points/data/models/ledger_item.dart`
|
||||
|
||||
```dart
|
||||
class LedgerItem {
|
||||
const LedgerItem({
|
||||
required this.id,
|
||||
required this.direction,
|
||||
required this.amount,
|
||||
required this.balanceAfter,
|
||||
required this.changeType,
|
||||
required this.displayText,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final int direction; // 1=收入, -1=支出
|
||||
final int amount;
|
||||
final int balanceAfter;
|
||||
final String changeType;
|
||||
final String displayText;
|
||||
final String createdAt;
|
||||
|
||||
factory LedgerItem.fromJson(Map<String, dynamic> json) => ...;
|
||||
}
|
||||
|
||||
class LedgerListResult {
|
||||
const LedgerListResult({
|
||||
required this.items,
|
||||
this.nextCursor,
|
||||
required this.hasMore,
|
||||
});
|
||||
|
||||
final List<LedgerItem> items;
|
||||
final String? nextCursor;
|
||||
final bool hasMore;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: 前端 API 方法
|
||||
|
||||
**文件**:`apps/lib/features/points/data/apis/points_api.dart`
|
||||
|
||||
**新增方法**:
|
||||
```dart
|
||||
Future<LedgerListResult> getLedger({
|
||||
int limit = 20,
|
||||
String? cursor,
|
||||
}) async {
|
||||
final query = {'limit': limit};
|
||||
if (cursor != null) query['cursor'] = cursor;
|
||||
final response = await _dio.get('/api/v1/points/ledger', queryParameters: query);
|
||||
return LedgerListResult.fromJson(response.data);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 7: 前端流水列表页面
|
||||
|
||||
**文件**:`apps/lib/features/points/presentation/screens/points_ledger_screen.dart`
|
||||
|
||||
**功能**:
|
||||
- 标题:积分流水
|
||||
- 列表项:类型图标 + 文案 + 金额(颜色区分) + 时间
|
||||
- 分页加载:滚动到底部加载更多
|
||||
- 空状态:暂无流水记录
|
||||
- Loading 状态
|
||||
|
||||
### Step 8: 重命名 AccountDeleteScreen 为 AccountDataScreen
|
||||
|
||||
**文件操作**:
|
||||
1. 重命名文件:`account_delete_screen.dart` → `account_data_screen.dart`
|
||||
2. 重命名类:`AccountDeleteScreen` → `AccountDataScreen`
|
||||
3. 更新类内状态名:`_AccountDeleteScreenState` → `_AccountDataScreenState`
|
||||
|
||||
**页面结构变更**:
|
||||
```dart
|
||||
// 之前:只有删除账号入口
|
||||
body: ListView(
|
||||
children: [
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(icon: Icons.delete_outline_rounded, ...),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 之后:积分流水 + 删除账号
|
||||
body: ListView(
|
||||
children: [
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.receipt_long_rounded,
|
||||
title: l10n.pointsLedgerTitle, // 积分流水
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: _openPointsLedger,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
title: l10n.settingsDeleteAccountTitle,
|
||||
tint: colors.error,
|
||||
background: colors.surfaceContainerHighest,
|
||||
titleColor: colors.error,
|
||||
showDivider: false,
|
||||
onTap: _confirmDelete,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
```
|
||||
|
||||
**新增方法**:
|
||||
```dart
|
||||
Future<void> _openPointsLedger() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => PointsLedgerScreen(
|
||||
apiClient: widget.apiClient,
|
||||
userId: widget.userId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**新增 widget 参数**:
|
||||
```dart
|
||||
class AccountDataScreen extends StatefulWidget {
|
||||
const AccountDataScreen({
|
||||
super.key,
|
||||
required this.onDeleteAccount,
|
||||
required this.apiClient, // 新增
|
||||
required this.userId, // 新增
|
||||
});
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Step 9: 更新 SettingsScreen 导入和调用
|
||||
|
||||
**文件**:`apps/lib/features/settings/presentation/screens/settings_screen.dart`
|
||||
|
||||
**修改**:
|
||||
1. 更新导入:`import 'account_data_screen.dart';`(替换原 `account_delete_screen.dart`)
|
||||
2. 更新 `_openAccountDelete()` 方法,传递新增参数:
|
||||
```dart
|
||||
Future<void> _openAccountData() async {
|
||||
final deleted = await Navigator.of(context).push<bool>(
|
||||
MaterialPageRoute<bool>(
|
||||
builder: (_) => AccountDataScreen(
|
||||
onDeleteAccount: widget.onDeleteAccount,
|
||||
apiClient: widget.apiClient,
|
||||
userId: widget.userId,
|
||||
),
|
||||
),
|
||||
);
|
||||
...
|
||||
}
|
||||
```
|
||||
3. 更新菜单项标题(如需要)
|
||||
|
||||
### Step 10: 添加 i18n 文案
|
||||
|
||||
**文件**:
|
||||
- `apps/lib/l10n/app_zh.arb`
|
||||
- `apps/lib/l10n/app_en.arb`
|
||||
- `apps/lib/l10n/app_zh_hant.arb`
|
||||
|
||||
**新增文案**:
|
||||
```json
|
||||
"pointsLedgerTitle": "积分流水",
|
||||
"pointsLedgerEmpty": "暂无流水记录",
|
||||
"pointsLedgerLoadingMore": "加载中..."
|
||||
```
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 后端验证
|
||||
|
||||
```bash
|
||||
# 启动后端
|
||||
./infra/scripts/app.sh start
|
||||
|
||||
# 测试 API
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
"http://localhost:8000/api/v1/points/ledger?limit=10"
|
||||
```
|
||||
|
||||
### 前端验证
|
||||
|
||||
```bash
|
||||
cd apps
|
||||
flutter run
|
||||
# 导航到设置 -> 积分中心 -> 点击「查看流水」
|
||||
```
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **RLS 策略**:`points_ledger` 表已有 RLS,确保查询只返回当前用户数据
|
||||
2. **游标分页**:使用 `created_at` 作为游标,注意处理相同时间戳的情况
|
||||
3. **性能**:索引 `ix_points_ledger_user_created_at` 已存在,查询性能有保障
|
||||
4. **空状态**:新用户可能无流水,需处理空列表情况
|
||||
|
||||
## 文件变更清单
|
||||
|
||||
### 后端新增/修改
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `backend/src/v1/points/schemas.py` | 修改 | 新增 LedgerItem、LedgerListResponse |
|
||||
| `backend/src/v1/points/repository.py` | 修改 | 新增 list_ledger() |
|
||||
| `backend/src/v1/points/service.py` | 修改 | 新增 get_ledger_list() |
|
||||
| `backend/src/v1/points/router.py` | 修改 | 新增 GET /ledger 端点 |
|
||||
|
||||
### 前端新增/修改
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `apps/lib/features/points/data/models/ledger_item.dart` | 新增 | 流水项模型 |
|
||||
| `apps/lib/features/points/data/apis/points_api.dart` | 修改 | 新增 getLedger() |
|
||||
| `apps/lib/features/points/presentation/screens/points_ledger_screen.dart` | 新增 | 流水列表页面 |
|
||||
| `apps/lib/features/settings/presentation/screens/coin_center_screen.dart` | 修改 | 添加入口按钮 |
|
||||
| `apps/lib/l10n/app_*.arb` | 修改 | 新增文案 |
|
||||
@@ -0,0 +1,201 @@
|
||||
# PRD:积分流水列表功能
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
当前应用已有完整的积分账户系统:
|
||||
- 积分账户表:`user_points`
|
||||
- 积分业务流水:`points_ledger`(记录所有积分变动)
|
||||
- 积分余额查询接口:`GET /api/v1/points/balance`
|
||||
- 积分中心页面:`CoinCenterScreen`
|
||||
|
||||
用户在积分中心可以看到余额和购买套餐,但无法查看积分的收支明细。本任务目标是增加积分流水列表功能,让用户了解自己的积分变动历史。
|
||||
|
||||
## 2. 当前事实
|
||||
|
||||
### 2.1 后端现状
|
||||
|
||||
**已有的 `points_ledger` 表结构**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | UUID | 主键 |
|
||||
| `user_id` | UUID | 用户 ID |
|
||||
| `direction` | smallint | 方向:1=收入,-1=支出 |
|
||||
| `amount` | bigint | 变动数量(正数) |
|
||||
| `balance_after` | bigint | 变动后余额 |
|
||||
| `change_type` | varchar | 类型:register/consume/adjust/purchase/refund |
|
||||
| `biz_type` | varchar | 业务类型:chat/payment(可空) |
|
||||
| `biz_id` | UUID | 业务 ID(可空) |
|
||||
| `event_id` | varchar | 幂等事件 ID |
|
||||
| `operator_id` | UUID | 操作者 ID(可空) |
|
||||
| `metadata` | jsonb | 扩展元数据 |
|
||||
| `created_at` | timestamptz | 创建时间 |
|
||||
|
||||
**已有的索引**:
|
||||
- `ix_points_ledger_user_created_at`:支持按用户+时间倒序查询
|
||||
- `uq_points_ledger_user_event`:用户+事件唯一约束
|
||||
|
||||
**已有的 Repository 方法**:
|
||||
- `append_ledger()`:写入流水
|
||||
- `has_ledger_event()`:检查事件是否存在
|
||||
|
||||
**缺失**:
|
||||
- 无分页查询流水列表方法
|
||||
- 无 HTTP API 端点
|
||||
|
||||
### 2.2 前端现状
|
||||
|
||||
**已有的积分中心页面**:
|
||||
- `apps/lib/features/settings/presentation/screens/coin_center_screen.dart`
|
||||
- 显示余额和套餐卡片
|
||||
|
||||
**已有的 API 调用**:
|
||||
- `apps/lib/features/points/data/apis/points_api.dart`:仅支持 `getPackages()`
|
||||
|
||||
**缺失**:
|
||||
- 无流水列表 API 调用
|
||||
- 无流水列表页面/组件
|
||||
|
||||
## 3. 需求范围
|
||||
|
||||
### 3.1 必须实现
|
||||
|
||||
**后端**:
|
||||
- 新增 `GET /api/v1/points/ledger` 接口
|
||||
- 支持分页(游标分页,按 `created_at` 倒序)
|
||||
- 返回流水列表,包含:时间、类型、金额、余额、描述
|
||||
|
||||
**前端**:
|
||||
- 在积分中心页面添加「查看流水」入口
|
||||
- 新建流水列表页面,支持分页加载
|
||||
- 流水项展示:类型图标、类型文案、金额(+绿色/-红色)、时间、余额
|
||||
|
||||
### 3.2 本期不做
|
||||
|
||||
- 按类型筛选(可作为后续优化)
|
||||
- 导出流水
|
||||
- 流水详情页
|
||||
|
||||
## 4. 数据契约
|
||||
|
||||
### 4.1 API 接口
|
||||
|
||||
**请求**:
|
||||
```
|
||||
GET /api/v1/points/ledger?limit=20&cursor={created_at_iso}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"direction": 1,
|
||||
"amount": 100,
|
||||
"balanceAfter": 500,
|
||||
"changeType": "purchase",
|
||||
"displayText": "购买积分包",
|
||||
"createdAt": "2026-04-28T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"nextCursor": "2026-04-27T10:00:00Z",
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 change_type 对应展示文案
|
||||
|
||||
| change_type | direction | 展示文案(中文) | 展示文案(英文) |
|
||||
|-------------|-----------|------------------|------------------|
|
||||
| register | 1 | 注册赠送 | Registration bonus |
|
||||
| purchase | 1 | 购买积分包 | Purchase credits |
|
||||
| consume | -1 | AI 对话消耗 | AI chat cost |
|
||||
| adjust | ±1 | 系统调整 | System adjustment |
|
||||
| refund | -1 | 退款 | Refund |
|
||||
|
||||
## 5. 技术方案
|
||||
|
||||
### 5.1 后端实现
|
||||
|
||||
**Repository 层**(`v1/points/repository.py`):
|
||||
```python
|
||||
async def list_ledger(
|
||||
self,
|
||||
*,
|
||||
user_id: UUID,
|
||||
limit: int,
|
||||
cursor: datetime | None = None,
|
||||
) -> tuple[list[PointsLedger], bool]:
|
||||
# 查询 points_ledger 表
|
||||
# 按 created_at DESC 分页
|
||||
# 返回 (items, has_more)
|
||||
```
|
||||
|
||||
**Service 层**(`v1/points/service.py`):
|
||||
```python
|
||||
async def get_ledger_list(
|
||||
self,
|
||||
*,
|
||||
user_id: UUID,
|
||||
limit: int = 20,
|
||||
cursor: str | None = None,
|
||||
) -> LedgerListResult:
|
||||
# 调用 repository
|
||||
# 组装 display_text
|
||||
# 返回响应
|
||||
```
|
||||
|
||||
**Router 层**(`v1/points/router.py`):
|
||||
```python
|
||||
@router.get("/ledger", response_model=LedgerListResponse)
|
||||
async def get_points_ledger(...):
|
||||
...
|
||||
```
|
||||
|
||||
### 5.2 前端实现
|
||||
|
||||
**目录结构**:
|
||||
```
|
||||
apps/lib/features/points/
|
||||
├── data/
|
||||
│ ├── apis/
|
||||
│ │ └── points_api.dart # 新增 getLedger()
|
||||
│ └── models/
|
||||
│ ├── package_info.dart # 已有
|
||||
│ └── ledger_item.dart # 新增
|
||||
└── presentation/
|
||||
└── screens/
|
||||
└── points_ledger_screen.dart # 新增
|
||||
```
|
||||
|
||||
**入口**:
|
||||
- 在 `CoinCenterScreen` 的余额卡片下方添加「查看流水」按钮
|
||||
- 点击后导航到 `PointsLedgerScreen`
|
||||
|
||||
## 6. 实现步骤
|
||||
|
||||
### Phase 1: 后端 API
|
||||
|
||||
1. 新增 Schema:`LedgerItem`、`LedgerListResponse`(`v1/points/schemas.py`)
|
||||
2. 新增 Repository 方法:`list_ledger()`(`v1/points/repository.py`)
|
||||
3. 新增 Service 方法:`get_ledger_list()`(`v1/points/service.py`)
|
||||
4. 新增 Router 端点:`GET /ledger`(`v1/points/router.py`)
|
||||
5. 编写单元测试
|
||||
|
||||
### Phase 2: 前端 UI
|
||||
|
||||
1. 新增 Model:`LedgerItem`(`features/points/data/models/ledger_item.dart`)
|
||||
2. 新增 API 方法:`getLedger()`(`features/points/data/apis/points_api.dart`)
|
||||
3. 新增页面:`PointsLedgerScreen`(`features/points/presentation/screens/points_ledger_screen.dart`)
|
||||
4. 修改 `CoinCenterScreen`,添加入口按钮
|
||||
5. 添加 i18n 文案(`app_zh.arb`、`app_en.arb`、`app_zh_hant.arb`)
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
- [ ] 后端 API 返回正确的分页数据
|
||||
- [ ] 前端能正确加载并展示流水列表
|
||||
- [ ] 流水类型显示对应文案
|
||||
- [ ] 金额按收支方向显示不同颜色
|
||||
- [ ] 分页加载正常工作
|
||||
- [ ] 无数据时显示空状态
|
||||
- [ ] 加载中显示 loading 状态
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"title": "feat: 积分流水列表功能",
|
||||
"slug": "feat-points-ledger",
|
||||
"status": "completed",
|
||||
"created_at": "2026-04-28",
|
||||
"developer": "opencode",
|
||||
"description": "增加积分流水列表功能,入口放在「账号与数据」页面",
|
||||
"prd": "prd.md",
|
||||
"implementation_plan": "IMPLEMENTATION_PLAN.md",
|
||||
"checklist": [
|
||||
{
|
||||
"phase": "backend",
|
||||
"items": [
|
||||
{
|
||||
"task": "新增 LedgerItem、LedgerListResponse Schema",
|
||||
"file": "backend/src/v1/points/schemas.py",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "新增 list_ledger() Repository 方法",
|
||||
"file": "backend/src/v1/points/repository.py",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "新增 get_ledger_list() Service 方法",
|
||||
"file": "backend/src/v1/points/service.py",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "新增 GET /ledger Router 端点",
|
||||
"file": "backend/src/v1/points/router.py",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "后端 API 测试通过",
|
||||
"done": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"phase": "frontend",
|
||||
"items": [
|
||||
{
|
||||
"task": "新增 LedgerItem 模型",
|
||||
"file": "apps/lib/features/points/data/models/ledger_item.dart",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "新增 getLedger() API 方法",
|
||||
"file": "apps/lib/features/points/data/apis/points_api.dart",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "新增 PointsLedgerScreen 页面",
|
||||
"file": "apps/lib/features/points/presentation/screens/points_ledger_screen.dart",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "重命名 AccountDeleteScreen → AccountDataScreen,添加积分流水入口",
|
||||
"file": "apps/lib/features/settings/presentation/screens/account_data_screen.dart",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "更新 SettingsScreen 导入和调用",
|
||||
"file": "apps/lib/features/settings/presentation/screens/settings_screen.dart",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "添加 i18n 文案",
|
||||
"file": "apps/lib/l10n/app_*.arb",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "运行 flutter gen-l10n 生成代码",
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"task": "前端功能测试通过",
|
||||
"done": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"completedAt": "2026-04-28"
|
||||
}
|
||||
Reference in New Issue
Block a user