18 KiB
Design Document: 福利与任务管理模块前端迁移
Overview
本设计文档描述福利与任务管理模块从老项目迁移到新项目的技术方案。该模块包含奖励管理、签到配置、任务管理和权益等级四个子模块,需要开发后端API和前端页面。
Architecture
系统架构
┌─────────────────────────────────────────────────────────────┐
│ Frontend (Vue 3 + Element Plus) │
├─────────────────────────────────────────────────────────────┤
│ views/business/ │
│ ├── reward/ # 奖励管理页面 │
│ ├── signconfig/ # 签到配置页面 │
│ ├── task/ # 任务管理页面 │
│ └── qylevel/ # 权益等级页面 │
├─────────────────────────────────────────────────────────────┤
│ api/business/ │
│ ├── reward.ts # 奖励管理API │
│ ├── signconfig.ts # 签到配置API │
│ ├── task.ts # 任务管理API │
│ └── qylevel.ts # 权益等级API │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Backend (ASP.NET Core) │
├─────────────────────────────────────────────────────────────┤
│ HoneyBox.Admin.Business/ │
│ ├── Controllers/ │
│ │ ├── RewardController.cs │
│ │ ├── SignConfigController.cs │
│ │ ├── TaskController.cs │
│ │ └── QyLevelController.cs │
│ ├── Services/ │
│ │ ├── RewardService.cs │
│ │ ├── SignConfigService.cs │
│ │ ├── TaskService.cs │
│ │ └── QyLevelService.cs │
│ └── Models/ │
│ ├── Reward/ │
│ ├── SignConfig/ │
│ ├── Task/ │
│ └── QyLevel/ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Database (SQL Server) │
├─────────────────────────────────────────────────────────────┤
│ rewards # 奖励表(已存在) │
│ sign_configs # 签到配置表(已存在) │
│ tasks # 任务表(已存在) │
│ equity_levels # 权益等级表(需创建) │
│ equity_level_prizes # 权益等级奖品表(需创建) │
└─────────────────────────────────────────────────────────────┘
技术栈
- 前端: Vue 3 + TypeScript + Element Plus + Vite
- 后端: ASP.NET Core (.NET 10) + Entity Framework Core
- 数据库: SQL Server
Components and Interfaces
后端API接口设计
1. 奖励管理 API
GET /api/business/reward # 获取奖励列表
GET /api/business/reward/{id} # 获取奖励详情
POST /api/business/reward # 创建奖励
PUT /api/business/reward/{id} # 更新奖励
DELETE /api/business/reward/{id} # 删除奖励
PUT /api/business/reward/{id}/status # 更新奖励状态
GET /api/business/reward/by-reward-id # 根据reward_id获取奖励列表
POST /api/business/reward/batch # 批量更新奖励
2. 签到配置 API
GET /api/business/signconfig # 获取签到配置列表
GET /api/business/signconfig/{id} # 获取签到配置详情
POST /api/business/signconfig # 创建签到配置
PUT /api/business/signconfig/{id} # 更新签到配置
DELETE /api/business/signconfig/{id} # 删除签到配置
PUT /api/business/signconfig/{id}/status # 更新状态
PUT /api/business/signconfig/{id}/sort # 更新排序
PUT /api/business/signconfig/{id}/reward # 更新奖励配置
3. 任务管理 API
GET /api/business/task # 获取任务列表
GET /api/business/task/{id} # 获取任务详情
POST /api/business/task # 创建任务
PUT /api/business/task/{id} # 更新任务
DELETE /api/business/task/{id} # 删除任务(软删除)
4. 权益等级 API
GET /api/business/qylevel # 获取权益等级列表
GET /api/business/qylevel/{id} # 获取权益等级详情
PUT /api/business/qylevel/{id} # 更新权益等级
GET /api/business/qylevel/{id}/prizes # 获取等级奖品列表
POST /api/business/qylevel/{id}/prizes # 添加等级奖品
PUT /api/business/qylevel/prizes/{prizeId} # 更新等级奖品
DELETE /api/business/qylevel/prizes/{prizeId} # 删除等级奖品(软删除)
前端组件设计
1. 奖励管理页面
views/business/reward/
├── list.vue # 奖励列表主页面
└── components/
├── RewardSearchForm.vue # 搜索表单组件
├── RewardTable.vue # 奖励表格组件
└── RewardFormDialog.vue # 奖励表单弹窗
2. 签到配置页面
views/business/signconfig/
├── list.vue # 签到配置主页面
└── components/
├── SignConfigTable.vue # 配置表格组件
├── SignConfigFormDialog.vue # 配置表单弹窗
└── RewardConfigDialog.vue # 奖励配置弹窗
3. 任务管理页面
views/business/task/
├── list.vue # 任务列表主页面
└── components/
├── TaskSearchForm.vue # 搜索表单组件
├── TaskTable.vue # 任务表格组件
└── TaskFormDialog.vue # 任务表单弹窗
4. 权益等级页面
views/business/qylevel/
├── list.vue # 权益等级主页面
└── components/
├── QyLevelTable.vue # 等级表格组件
├── QyLevelFormDialog.vue # 等级表单弹窗
├── QyLevelPrizeDialog.vue # 奖品管理弹窗
└── QyLevelPrizeFormDialog.vue # 奖品表单弹窗
Data Models
后端数据模型
RewardModels.cs
public class RewardListRequest : PagedRequest
{
public int? RewardType { get; set; }
public string? Keyword { get; set; }
}
public class RewardResponse
{
public int Id { get; set; }
public string RewardId { get; set; }
public int RewardType { get; set; }
public string RewardTypeName { get; set; }
public int? RewardExtend { get; set; }
public decimal RewardValue { get; set; }
public string? Description { get; set; }
public int Status { get; set; }
public DateTime? CreatedAt { get; set; }
public CouponInfo? Coupon { get; set; }
}
public class RewardCreateRequest
{
public int RewardType { get; set; }
public int? RewardExtend { get; set; }
public decimal RewardValue { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
}
public class RewardBatchRequest
{
public string RewardId { get; set; }
public string RewardIdPrefix { get; set; }
public List<RewardItem> Rewards { get; set; }
}
SignConfigModels.cs
public class SignConfigListRequest : PagedRequest
{
public int Type { get; set; } = 1;
public string? Keyword { get; set; }
}
public class SignConfigResponse
{
public int Id { get; set; }
public int Type { get; set; }
public int? Day { get; set; }
public string? Title { get; set; }
public string? Icon { get; set; }
public int Status { get; set; }
public int? Sort { get; set; }
public string RewardId { get; set; }
public string? Description { get; set; }
public DateTime? CreatedAt { get; set; }
public List<RewardResponse> Rewards { get; set; }
}
public class SignConfigCreateRequest
{
public int Type { get; set; }
public int? Day { get; set; }
public string Title { get; set; }
public string? Icon { get; set; }
public int? Sort { get; set; }
public string? Description { get; set; }
public List<RewardItem> Rewards { get; set; }
}
TaskModels.cs
public class TaskListRequest : PagedRequest
{
public string? Keyword { get; set; }
public int? Type { get; set; }
}
public class TaskResponse
{
public int Id { get; set; }
public string Title { get; set; }
public int Type { get; set; }
public string TypeName { get; set; }
public int Cate { get; set; }
public int? IsImportant { get; set; }
public int? Number { get; set; }
public int? ZNumber { get; set; }
public int? Sort { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class TaskCreateRequest
{
public string Title { get; set; }
public int Type { get; set; }
public int Cate { get; set; }
public int? IsImportant { get; set; }
public int Number { get; set; }
public int ZNumber { get; set; }
public int? Sort { get; set; }
}
QyLevelModels.cs
public class QyLevelListRequest : PagedRequest
{
public string? Keyword { get; set; }
}
public class QyLevelResponse
{
public int Id { get; set; }
public int Level { get; set; }
public string Title { get; set; }
public int RequiredPoints { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class QyLevelPrizeListRequest : PagedRequest
{
public int? Type { get; set; }
public string? Keyword { get; set; }
}
public class QyLevelPrizeResponse
{
public int Id { get; set; }
public int QyLevelId { get; set; }
public int QyLevel { get; set; }
public int Type { get; set; }
public string TypeName { get; set; }
public string Title { get; set; }
public int? CouponId { get; set; }
public int? Quantity { get; set; }
public decimal? Value { get; set; }
public decimal? ExchangePrice { get; set; }
public decimal? ReferencePrice { get; set; }
public decimal? Probability { get; set; }
public string? Image { get; set; }
public string? PrizeCode { get; set; }
public int? Sort { get; set; }
public DateTime CreatedAt { get; set; }
public CouponInfo? Coupon { get; set; }
}
public class QyLevelPrizeCreateRequest
{
public int Type { get; set; }
public string? Title { get; set; }
public int? CouponId { get; set; }
public int? Quantity { get; set; }
public decimal? Value { get; set; }
public decimal? ExchangePrice { get; set; }
public decimal? ReferencePrice { get; set; }
public decimal? Probability { get; set; }
public string? Image { get; set; }
public int? ShangId { get; set; }
public int? Sort { get; set; }
}
数据库表设计
equity_levels 表(需创建)
CREATE TABLE equity_levels (
id INT IDENTITY(1,1) PRIMARY KEY,
level INT NOT NULL,
title NVARCHAR(100) NOT NULL,
required_points INT NOT NULL DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE(),
updated_at DATETIME2 DEFAULT GETDATE(),
deleted_at DATETIME2 NULL
);
equity_level_prizes 表(需创建)
CREATE TABLE equity_level_prizes (
id INT IDENTITY(1,1) PRIMARY KEY,
qy_level_id INT NOT NULL,
qy_level INT NOT NULL,
type TINYINT NOT NULL DEFAULT 2,
title NVARCHAR(255) NULL,
coupon_id INT NULL,
z_num INT NULL DEFAULT 0,
man_price DECIMAL(10,2) NULL DEFAULT 0,
jian_price DECIMAL(10,2) NULL DEFAULT 0,
effective_day INT NULL DEFAULT 0,
jiang_price DECIMAL(10,2) NULL DEFAULT 0,
money DECIMAL(10,2) NULL DEFAULT 0,
sc_money DECIMAL(10,2) NULL DEFAULT 0,
probability DECIMAL(5,2) NULL DEFAULT 0,
imgurl NVARCHAR(500) NULL,
prize_code NVARCHAR(100) NULL,
shang_id INT NULL,
sort INT NULL DEFAULT 0,
created_at DATETIME2 DEFAULT GETDATE(),
updated_at DATETIME2 DEFAULT GETDATE(),
deleted_at DATETIME2 NULL,
FOREIGN KEY (qy_level_id) REFERENCES equity_levels(id)
);
Correctness Properties
A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
Property 1: 奖励类型筛选正确性
For any reward type filter value, all returned rewards should have the matching reward_type field value.
Validates: Requirements 1.2
Property 2: 关键词搜索正确性
For any keyword search string, all returned items should contain the keyword in their title field (case-insensitive).
Validates: Requirements 1.3, 5.2
Property 3: 状态切换一致性
For any status toggle operation, the item's status should change from its current value to the opposite value (0→1 or 1→0).
Validates: Requirements 1.4, 3.4
Property 4: 条件字段显示正确性
For any reward type selection, the form should display coupon dropdown when type is 1 (coupon), and value input when type is 2/3/4 (diamond/currency).
Validates: Requirements 2.1, 2.2
Property 5: 正数值验证
For any numeric input field (reward value, required count, reward points), the system should reject values less than or equal to 0.
Validates: Requirements 2.5, 6.2, 6.3, 8.2, 8.3
Property 6: 签到类型切换数据隔离
For any sign config type switch (daily/cumulative), the returned configurations should only contain items of the selected type.
Validates: Requirements 3.2
Property 7: 任务分类筛选正确性
For any task category filter, all returned tasks should have the matching type field value.
Validates: Requirements 5.3
Property 8: 概率值范围验证
For any probability input for equity level prizes, the system should validate the value is between 0 and 100 with at most 2 decimal places.
Validates: Requirements 9.5
Property 9: API响应格式一致性
For any API call, the response should contain code, message, and data fields with consistent structure.
Validates: Requirements 10.7
Property 10: 分页参数正确传递
For any paginated list request, the page number and page size parameters should be correctly passed to the backend and reflected in the response.
Validates: Requirements 1.1, 3.3, 5.1, 7.1
Error Handling
前端错误处理
- 网络错误: 显示网络连接失败提示,提供重试按钮
- 验证错误: 在表单字段下方显示具体错误信息
- 业务错误: 使用 Element Plus Message 组件显示错误消息
- 权限错误: 跳转到无权限页面或显示权限不足提示
后端错误处理
- 参数验证失败: 返回 400 Bad Request,包含具体字段错误信息
- 资源不存在: 返回 404 Not Found
- 业务规则违反: 返回 400 Bad Request,包含业务错误描述
- 服务器错误: 返回 500 Internal Server Error,记录详细日志
错误响应格式
{
"code": 400,
"message": "参数验证失败",
"data": {
"errors": {
"title": "标题不能为空",
"number": "任务次数必须大于0"
}
}
}
Testing Strategy
单元测试
-
后端服务测试
- 测试各Service方法的业务逻辑
- 测试数据验证规则
- 测试边界条件
-
前端组件测试
- 测试表单验证逻辑
- 测试条件显示逻辑
- 测试API调用参数
属性测试
使用 FsCheck (.NET) 进行属性测试,验证以下属性:
- 搜索参数传递属性: 验证搜索参数正确传递到后端
- 分页参数传递属性: 验证分页参数正确处理
- 表单验证属性: 验证必填字段和数值范围验证
- 状态切换属性: 验证状态切换的一致性
- 类型筛选属性: 验证类型筛选的正确性
测试配置
- 每个属性测试运行最少 100 次迭代
- 使用随机生成的测试数据
- 测试标签格式:
Feature: welfare-task-frontend, Property N: {property_text}
集成测试
- 测试完整的CRUD流程
- 测试跨模块的数据关联(如签到配置与奖励的关联)
- 测试权限控制