501 lines
14 KiB
Markdown
501 lines
14 KiB
Markdown
# Design Document
|
||
|
||
## Overview
|
||
|
||
本设计文档描述了HoneyBox后台管理系统用户管理模块前端迁移的技术方案。该模块将老项目(PHP ThinkPHP + Layui)的用户管理前端页面迁移到新项目(ASP.NET Core + Vue 3 + Element Plus)。
|
||
|
||
主要工作包括:
|
||
1. 创建Vue 3前端页面和组件
|
||
2. 实现API调用层
|
||
3. 补充缺失的后端API接口
|
||
4. 实现数据展示和交互功能
|
||
|
||
## Architecture
|
||
|
||
### 前端架构
|
||
|
||
```
|
||
admin-web/src/
|
||
├── api/
|
||
│ └── business/
|
||
│ └── user.ts # 用户管理API
|
||
├── views/
|
||
│ └── business/
|
||
│ └── user/
|
||
│ ├── index.vue # 用户列表主页面
|
||
│ ├── components/
|
||
│ │ ├── UserSearchForm.vue # 搜索表单组件
|
||
│ │ ├── UserTable.vue # 用户表格组件
|
||
│ │ ├── MoneyChangeDialog.vue # 资金变动对话框
|
||
│ │ ├── GiftCouponDialog.vue # 赠送优惠券对话框
|
||
│ │ ├── GiftCardDialog.vue # 赠送卡牌对话框
|
||
│ │ ├── UserBoxDialog.vue # 用户盒柜对话框
|
||
│ │ ├── UserOrderDialog.vue # 用户订单对话框
|
||
│ │ ├── UserTeamDialog.vue # 下级用户对话框
|
||
│ │ ├── MoneyDetailDialog.vue # 流水明细对话框
|
||
│ │ └── IpLogDialog.vue # IP登录历史对话框
|
||
│ ├── profit-loss.vue # 用户盈亏统计页面
|
||
│ ├── vip.vue # VIP等级管理页面
|
||
│ ├── invite-stats.vue # 用户邀请统计页面
|
||
│ └── login-stats.vue # 用户登录统计页面
|
||
└── router/
|
||
└── modules/
|
||
└── business.ts # 业务模块路由
|
||
```
|
||
|
||
### 后端架构
|
||
|
||
```
|
||
HoneyBox.Admin.Business/
|
||
├── Controllers/
|
||
│ └── UserController.cs # 用户管理控制器(补充API)
|
||
├── Services/
|
||
│ ├── UserBusinessService.cs # 用户业务服务(补充方法)
|
||
│ └── Interfaces/
|
||
│ └── IUserBusinessService.cs
|
||
└── Models/
|
||
└── User/
|
||
├── UserModels.cs # 用户相关模型
|
||
├── UserBoxModels.cs # 用户盒柜模型(新增)
|
||
├── UserOrderModels.cs # 用户订单模型(新增)
|
||
├── MoneyDetailModels.cs # 流水明细模型(新增)
|
||
└── StatsModels.cs # 统计相关模型(新增)
|
||
```
|
||
|
||
## Components and Interfaces
|
||
|
||
### 前端API接口定义
|
||
|
||
```typescript
|
||
// api/business/user.ts
|
||
|
||
// 用户列表查询参数
|
||
interface UserListQuery {
|
||
uid?: string
|
||
pid?: string
|
||
mobile?: string
|
||
nickname?: string
|
||
lastLoginIp?: string
|
||
startTime?: string
|
||
endTime?: string
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
|
||
// 用户列表响应
|
||
interface UserListItem {
|
||
id: number
|
||
uid: string
|
||
nickname: string
|
||
headimg: string
|
||
mobile: string
|
||
money: number
|
||
integral: number
|
||
money2: number
|
||
status: number
|
||
istest: number
|
||
vipLevel: number
|
||
addtime: string
|
||
lastLoginTime: string
|
||
lastLoginIp: string
|
||
pidInfo: { id: number; uid: string; nickname: string } | null
|
||
// 消费统计
|
||
userHegui: number // 盒柜价值
|
||
userAllTotal: number // 总消费
|
||
userWeixinTotal: number // 微信支付
|
||
userUseMoney: number // 余额支付
|
||
userUseIntegral: number // 积分支付
|
||
userGoodslistMoney: number // 回收货币
|
||
userGoodslistMoney2: number // 发货价值
|
||
userGoodslistMoney3: number // 总出货价值
|
||
}
|
||
|
||
// 资金变动请求
|
||
interface MoneyChangeRequest {
|
||
type: 'money' | 'integral' | 'money2'
|
||
action: 'add' | 'sub'
|
||
amount: number
|
||
remark?: string
|
||
}
|
||
|
||
// 用户盒柜查询参数
|
||
interface UserBoxQuery {
|
||
userId: number
|
||
status?: number
|
||
goodslistTitle?: string
|
||
goodTitle?: string
|
||
startTime?: string
|
||
endTime?: string
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
|
||
// 用户盒柜项
|
||
interface UserBoxItem {
|
||
goodslistTitle: string
|
||
goodslistMoney: number
|
||
goodslistPrice: number
|
||
goodslistImgurl: string
|
||
shangId: number
|
||
shangTitle: string
|
||
addtime: string
|
||
orderId: number
|
||
orderNum: string
|
||
goodsId: number
|
||
goodTitle: string
|
||
status: number
|
||
fhStatus: number | null
|
||
fhRemarks: string | null
|
||
}
|
||
|
||
// 流水明细查询参数
|
||
interface MoneyDetailQuery {
|
||
userId: number
|
||
type?: number
|
||
changeType?: 'add' | 'sub'
|
||
content?: string
|
||
startTime?: string
|
||
endTime?: string
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
|
||
// 流水明细项
|
||
interface MoneyDetailItem {
|
||
id: number
|
||
changeMoney: number
|
||
money: number
|
||
type: number
|
||
content: string
|
||
other: string
|
||
addtime: string
|
||
}
|
||
|
||
// 用户盈亏列表查询
|
||
interface ProfitLossListQuery {
|
||
uid?: string
|
||
startTime?: string
|
||
endTime?: string
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
|
||
// 用户盈亏项
|
||
interface ProfitLossItem {
|
||
userId: number
|
||
uid: string
|
||
nickname: string
|
||
headimg: string
|
||
mobile: string
|
||
money: number
|
||
integral: number
|
||
money2: number
|
||
orderCount: number
|
||
orderZheTotal: number
|
||
money1: number // RMB支付
|
||
money2Pay: number // 钻石支付
|
||
useMoney: number // 用户支付金额
|
||
fhMoney: number // 发货金额
|
||
bbMoney: number // 背包金额
|
||
syMoney: number // 剩余达达券
|
||
yueMoney: number // 盈亏金额
|
||
profitStatus: string // 盈利/亏损
|
||
}
|
||
|
||
// 用户邀请统计项
|
||
interface InviteStatsItem {
|
||
index: number
|
||
userId: number
|
||
uid: string
|
||
nickname: string
|
||
headimg: string
|
||
inviteNumber: number
|
||
sumOrder: number
|
||
sumPrice: number
|
||
countMobile: number
|
||
info: InviteUserInfo[]
|
||
}
|
||
|
||
// 登录统计数据
|
||
interface LoginStatsData {
|
||
labels: string[]
|
||
values: number[]
|
||
totalLogins: number
|
||
activeUsers?: number
|
||
}
|
||
```
|
||
|
||
### 后端API接口补充
|
||
|
||
```csharp
|
||
// UserController.cs 补充的API
|
||
|
||
// 获取用户盒柜列表
|
||
[HttpGet("{id:int}/box")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserBox(int id, [FromQuery] UserBoxQuery query);
|
||
|
||
// 获取用户订单列表
|
||
[HttpGet("{id:int}/orders")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserOrders(int id, [FromQuery] UserOrderQuery query);
|
||
|
||
// 获取用户余额流水明细
|
||
[HttpGet("{id:int}/money-detail")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserMoneyDetail(int id, [FromQuery] MoneyDetailQuery query);
|
||
|
||
// 获取用户积分流水明细
|
||
[HttpGet("{id:int}/integral-detail")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserIntegralDetail(int id, [FromQuery] MoneyDetailQuery query);
|
||
|
||
// 获取用户钻石流水明细
|
||
[HttpGet("{id:int}/score-detail")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserScoreDetail(int id, [FromQuery] MoneyDetailQuery query);
|
||
|
||
// 获取用户IP登录历史
|
||
[HttpGet("{id:int}/ip-logs")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetUserIpLogs(int id, [FromQuery] int page = 1, [FromQuery] int pageSize = 20);
|
||
|
||
// 获取用户邀请统计
|
||
[HttpGet("invite-stats")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetInviteStats([FromQuery] InviteStatsQuery query);
|
||
|
||
// 获取用户登录统计
|
||
[HttpGet("login-stats")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetLoginStats([FromQuery] LoginStatsQuery query);
|
||
|
||
// 获取用户盈亏列表
|
||
[HttpGet("profit-loss-list")]
|
||
[BusinessPermission("user:view")]
|
||
Task<IActionResult> GetProfitLossList([FromQuery] ProfitLossListQuery query);
|
||
|
||
// 绑定用户手机号
|
||
[HttpPut("{id:int}/mobile")]
|
||
[BusinessPermission("user:edit")]
|
||
Task<IActionResult> BindMobile(int id, [FromBody] BindMobileRequest request);
|
||
|
||
// 重置用户签到数据
|
||
[HttpPut("{id:int}/sign-reset")]
|
||
[BusinessPermission("user:edit")]
|
||
Task<IActionResult> ResetUserSign(int id);
|
||
|
||
// 清空用户UID
|
||
[HttpDelete("{id:int}/uid")]
|
||
[BusinessPermission("user:clear")]
|
||
Task<IActionResult> ClearUid(int id);
|
||
```
|
||
|
||
## Data Models
|
||
|
||
### 前端数据模型
|
||
|
||
用户列表数据流:
|
||
```
|
||
API Response -> UserListItem[] -> UserTable Component -> Display
|
||
```
|
||
|
||
资金变动数据流:
|
||
```
|
||
MoneyChangeDialog -> MoneyChangeRequest -> API -> Response -> Refresh List
|
||
```
|
||
|
||
### 后端数据模型补充
|
||
|
||
```csharp
|
||
// UserBoxModels.cs
|
||
public class UserBoxQuery
|
||
{
|
||
public int? Status { get; set; }
|
||
public string? GoodslistTitle { get; set; }
|
||
public string? GoodTitle { get; set; }
|
||
public DateTime? StartTime { get; set; }
|
||
public DateTime? EndTime { get; set; }
|
||
public int Page { get; set; } = 1;
|
||
public int PageSize { get; set; } = 20;
|
||
}
|
||
|
||
public class UserBoxItem
|
||
{
|
||
public string GoodslistTitle { get; set; }
|
||
public decimal GoodslistMoney { get; set; }
|
||
public decimal GoodslistPrice { get; set; }
|
||
public string GoodslistImgurl { get; set; }
|
||
public int ShangId { get; set; }
|
||
public string ShangTitle { get; set; }
|
||
public DateTime Addtime { get; set; }
|
||
public int OrderId { get; set; }
|
||
public string OrderNum { get; set; }
|
||
public int GoodsId { get; set; }
|
||
public string GoodTitle { get; set; }
|
||
public int Status { get; set; }
|
||
public int? FhStatus { get; set; }
|
||
public string? FhRemarks { get; set; }
|
||
}
|
||
|
||
// MoneyDetailModels.cs
|
||
public class MoneyDetailQuery
|
||
{
|
||
public int? Type { get; set; }
|
||
public string? ChangeType { get; set; }
|
||
public string? Content { get; set; }
|
||
public DateTime? StartTime { get; set; }
|
||
public DateTime? EndTime { get; set; }
|
||
public int Page { get; set; } = 1;
|
||
public int PageSize { get; set; } = 50;
|
||
}
|
||
|
||
public class MoneyDetailItem
|
||
{
|
||
public int Id { get; set; }
|
||
public decimal ChangeMoney { get; set; }
|
||
public decimal Money { get; set; }
|
||
public int Type { get; set; }
|
||
public string Content { get; set; }
|
||
public string? Other { get; set; }
|
||
public DateTime Addtime { get; set; }
|
||
}
|
||
|
||
// StatsModels.cs
|
||
public class LoginStatsQuery
|
||
{
|
||
public string Type { get; set; } = "day"; // day, week, month
|
||
public int? Year { get; set; }
|
||
public DateTime? StartDate { get; set; }
|
||
public DateTime? EndDate { get; set; }
|
||
}
|
||
|
||
public class LoginStatsResponse
|
||
{
|
||
public List<string> Labels { get; set; }
|
||
public List<int> Values { get; set; }
|
||
public int TotalLogins { get; set; }
|
||
public int? ActiveUsers { get; set; }
|
||
}
|
||
|
||
public class ProfitLossListQuery
|
||
{
|
||
public string? Uid { get; set; }
|
||
public DateTime? StartTime { get; set; }
|
||
public DateTime? EndTime { get; set; }
|
||
public int Page { get; set; } = 1;
|
||
public int PageSize { get; set; } = 20;
|
||
}
|
||
|
||
public class ProfitLossItem
|
||
{
|
||
public int UserId { get; set; }
|
||
public string Uid { get; set; }
|
||
public string Nickname { get; set; }
|
||
public string Headimg { get; set; }
|
||
public string Mobile { get; set; }
|
||
public decimal Money { get; set; }
|
||
public decimal Integral { get; set; }
|
||
public decimal Money2 { get; set; }
|
||
public int OrderCount { get; set; }
|
||
public decimal OrderZheTotal { get; set; }
|
||
public decimal Money1 { get; set; }
|
||
public decimal Money2Pay { get; set; }
|
||
public decimal UseMoney { get; set; }
|
||
public decimal FhMoney { get; set; }
|
||
public decimal BbMoney { get; set; }
|
||
public decimal SyMoney { get; set; }
|
||
public decimal YueMoney { get; set; }
|
||
public string ProfitStatus { get; set; }
|
||
}
|
||
```
|
||
|
||
## 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* 用户列表搜索请求,当管理员输入搜索条件时,API调用的查询参数应该与用户输入的搜索条件完全匹配。
|
||
|
||
**Validates: Requirements 1.2**
|
||
|
||
### Property 2: 分页参数正确传递
|
||
|
||
*For any* 分页请求,当管理员点击分页控件时,API调用的page和pageSize参数应该与用户选择的页码和每页数量一致。
|
||
|
||
**Validates: Requirements 1.4**
|
||
|
||
### Property 3: 资金变动参数验证
|
||
|
||
*For any* 资金变动请求,当操作类型为"扣除"且金额大于用户当前余额时,系统应该返回错误提示而不是执行扣除操作。
|
||
|
||
**Validates: Requirements 2.3**
|
||
|
||
### Property 4: 用户状态切换一致性
|
||
|
||
*For any* 用户状态变更操作,封号操作应该将status设为0,解封操作应该将status设为1,且操作后用户列表应该显示正确的状态。
|
||
|
||
**Validates: Requirements 3.1, 3.2**
|
||
|
||
### Property 5: 盈亏计算正确性
|
||
|
||
*For any* 用户盈亏数据,盈亏金额应该等于:用户支付金额 - 发货金额 - 背包金额 - 剩余达达券,且盈亏状态应该根据盈亏金额正负正确显示。
|
||
|
||
**Validates: Requirements 6.4**
|
||
|
||
### Property 6: API响应格式一致性
|
||
|
||
*For any* 后端API响应,响应格式应该符合统一的ApiResponse结构:{ code: number, message: string, data: T },其中code为0表示成功。
|
||
|
||
**Validates: Requirements 10.1-10.10**
|
||
|
||
## Error Handling
|
||
|
||
### 前端错误处理
|
||
|
||
1. **网络错误**: 显示"网络连接失败"提示,允许用户重试
|
||
2. **401未授权**: 自动刷新token或跳转登录页
|
||
3. **403无权限**: 显示"没有操作权限"提示
|
||
4. **业务错误**: 显示后端返回的错误消息
|
||
5. **表单验证错误**: 在表单字段下方显示验证错误信息
|
||
|
||
### 后端错误处理
|
||
|
||
1. **参数验证失败**: 返回400状态码和验证错误详情
|
||
2. **资源不存在**: 返回404状态码和"用户不存在"消息
|
||
3. **业务规则违反**: 返回业务错误码和描述消息
|
||
4. **服务器错误**: 返回500状态码和通用错误消息
|
||
|
||
## Testing Strategy
|
||
|
||
### 单元测试
|
||
|
||
1. **前端组件测试**
|
||
- 测试搜索表单组件的参数收集
|
||
- 测试对话框组件的表单验证
|
||
- 测试表格组件的数据渲染
|
||
|
||
2. **后端服务测试**
|
||
- 测试UserBusinessService的各个方法
|
||
- 测试数据查询和计算逻辑
|
||
|
||
### 属性测试
|
||
|
||
使用属性测试验证以下属性:
|
||
- 搜索参数传递正确性
|
||
- 分页参数传递正确性
|
||
- 资金变动参数验证
|
||
- 盈亏计算正确性
|
||
- API响应格式一致性
|
||
|
||
### 集成测试
|
||
|
||
1. **API集成测试**
|
||
- 测试完整的API请求-响应流程
|
||
- 测试权限验证
|
||
|
||
2. **端到端测试**
|
||
- 测试用户列表页面的完整功能流程
|
||
- 测试资金变动的完整操作流程
|