Merge branch 'master' of 192.168.1.40:outsource/HaniBlindBox

This commit is contained in:
zpc 2026-01-04 12:20:19 +08:00
commit dc022da181
21 changed files with 868 additions and 63 deletions

View File

@ -0,0 +1,371 @@
# HoneyBox 后台管理系统 - 业务模块迁移清单
## 迁移概述
本文档列出了从 PHP (ThinkPHP 6.0) 后台迁移到 ASP.NET Core 的所有业务模块。
基础权限框架(登录、菜单、角色、权限、部门)已完成,现进入业务模块迁移阶段。
---
## 一、模块优先级分类
### P0 - 核心业务模块(必须优先迁移)
| 序号 | 模块名称 | PHP 控制器 | 功能说明 | 预计工时 |
|------|----------|-----------|----------|----------|
| 1 | 首页仪表盘 | Index.php | 数据统计概览、今日数据、趋势图表 | 1天 |
| 2 | 用户管理 | User.php | 用户列表、封禁/解封、资金变动、VIP管理、下级用户 | 3天 |
| 3 | 商品管理 | Goods.php, GoodsType.php | 盒子管理、奖品配置、库存管理、9种盒子类型 | 4天 |
| 4 | 订单管理 | Order.php | 订单列表、卡单处理、发货管理、退款、导出 | 3天 |
### P1 - 重要业务模块
| 序号 | 模块名称 | PHP 控制器 | 功能说明 | 预计工时 |
|------|----------|-----------|----------|----------|
| 5 | 财务管理 | Finance.php | 消费排行、余额明细、积分明细、充值记录 | 2天 |
| 6 | 提现管理 | Withdraw.php | 提现申请列表、审核、打款 | 1天 |
| 7 | 抽奖配置 | Draw.php, Cardextractor.php | 抽奖活动配置、抽卡机配置 | 2天 |
| 8 | 系统配置 | Config.php | 支付配置、上传配置、系统参数 | 1天 |
### P2 - 营销与内容模块
| 序号 | 模块名称 | PHP 控制器 | 功能说明 | 预计工时 |
|------|----------|-----------|----------|----------|
| 9 | 优惠券管理 | Coupon.php, CouponReceive.php | 优惠券创建、发放、领取记录 | 2天 |
| 10 | 奖励管理 | Reward.php | 奖励配置、发放规则 | 1天 |
| 11 | 排行榜管理 | Rank.php, UserRank.php | 排行榜配置、用户排名 | 1天 |
| 12 | 广告管理 | Advert.php, AdvertType.php | 广告位管理、广告投放 | 1天 |
| 13 | 单页管理 | Danye.php | 静态页面管理(关于我们、协议等) | 0.5天 |
| 14 | 公告管理 | NewsController.php | 系统公告、消息推送 | 0.5天 |
### P3 - 辅助功能模块
| 序号 | 模块名称 | PHP 控制器 | 功能说明 | 预计工时 |
|------|----------|-----------|----------|----------|
| 15 | 统计报表 | Statistics.php | 数据分析、报表导出 | 2天 |
| 16 | 签到配置 | SignConfig.php | 签到奖励配置 | 0.5天 |
| 17 | 任务管理 | TaskList.php | 任务配置、完成奖励 | 1天 |
| 18 | 福利屋 | WelfareHouse.php | 福利活动管理 | 1天 |
| 19 | 秒杀管理 | Seckill.php | 秒杀活动配置 | 1天 |
| 20 | 钻石管理 | Diamond.php | 钻石充值配置 | 0.5天 |
| 21 | 悬浮球 | FloatBall.php | 悬浮球配置 | 0.5天 |
| 22 | 文件上传 | Upload.php, Upload2.php | 文件上传服务 | 0.5天 |
---
## 二、详细模块说明
### 2.1 首页仪表盘 (Index)
**功能清单:**
- 今日注册用户数
- 今日消费金额
- 今日订单数
- 总用户数/总订单数/总收入
- 近7天/30天趋势图
- 广告数据统计
**API 设计:**
```
GET /api/admin/dashboard/overview # 概览数据
GET /api/admin/dashboard/trend # 趋势数据
GET /api/admin/dashboard/ads # 广告统计
```
---
### 2.2 用户管理 (User)
**功能清单:**
- 用户列表(分页、搜索、筛选)
- 用户详情(基本信息、消费统计、盒柜价值)
- 资金变动(余额、积分、钻石)
- 封号/解封
- 清空手机号/微信绑定
- 设置测试账号
- 赠送优惠券/卡牌
- VIP 等级管理
- 用户盈亏统计
- 下级用户列表
- 流水明细
**API 设计:**
```
GET /api/admin/business/users # 用户列表
GET /api/admin/business/users/{id} # 用户详情
PUT /api/admin/business/users/{id}/status # 封号/解封
PUT /api/admin/business/users/{id}/money # 资金变动
PUT /api/admin/business/users/{id}/test # 设置测试账号
DELETE /api/admin/business/users/{id}/mobile # 清空手机号
DELETE /api/admin/business/users/{id}/wechat # 清空微信绑定
POST /api/admin/business/users/{id}/coupon # 赠送优惠券
POST /api/admin/business/users/{id}/card # 赠送卡牌
GET /api/admin/business/users/{id}/team # 下级用户
GET /api/admin/business/users/{id}/profit # 盈亏统计
GET /api/admin/business/vip # VIP等级列表
PUT /api/admin/business/vip/{id} # 编辑VIP等级
```
---
### 2.3 商品管理 (Goods)
**功能清单:**
- 盒子列表9种类型一番赏、无限赏、擂台赏、抽卡机、福袋、幸运赏、盲盒、扭蛋、福利屋
- 盒子新增/编辑/删除
- 盒子上架/下架
- 奖品管理(添加、编辑、删除、库存调整)
- 奖品等级配置
- 盒子类型管理
- 盒子扩展信息
- 商品分类管理
**API 设计:**
```
GET /api/admin/business/goods # 盒子列表
GET /api/admin/business/goods/{id} # 盒子详情
POST /api/admin/business/goods # 新增盒子
PUT /api/admin/business/goods/{id} # 编辑盒子
DELETE /api/admin/business/goods/{id} # 删除盒子
PUT /api/admin/business/goods/{id}/status # 上架/下架
GET /api/admin/business/goods/{id}/prizes # 奖品列表
POST /api/admin/business/goods/{id}/prizes # 添加奖品
PUT /api/admin/business/goods/prizes/{id} # 编辑奖品
DELETE /api/admin/business/goods/prizes/{id} # 删除奖品
GET /api/admin/business/goods-types # 盒子类型列表
GET /api/admin/business/prize-levels # 奖品等级列表
```
---
### 2.4 订单管理 (Order)
**功能清单:**
- 购买订单列表
- 卡单列表(支付成功但未发奖)
- 订单详情(奖品明细)
- 兑换订单列表(回收)
- 发货订单列表
- 发货处理(单个/批量)
- 订单导出Excel
- 物流信息查询
**API 设计:**
```
GET /api/admin/business/orders # 订单列表
GET /api/admin/business/orders/{id} # 订单详情
GET /api/admin/business/orders/stuck # 卡单列表
POST /api/admin/business/orders/{id}/retry # 重试发奖
GET /api/admin/business/orders/recovery # 兑换订单
GET /api/admin/business/orders/shipping # 发货订单
PUT /api/admin/business/orders/{id}/ship # 发货处理
POST /api/admin/business/orders/batch-ship # 批量发货
GET /api/admin/business/orders/export # 导出订单
```
---
### 2.5 财务管理 (Finance)
**功能清单:**
- 消费排行榜
- 余额明细(充值、消费、提现)
- 积分明细
- 钻石明细
- 充值记录
- 微信支付日志
**API 设计:**
```
GET /api/admin/business/finance/ranking # 消费排行
GET /api/admin/business/finance/money # 余额明细
GET /api/admin/business/finance/integral # 积分明细
GET /api/admin/business/finance/diamond # 钻石明细
GET /api/admin/business/finance/recharge # 充值记录
GET /api/admin/business/finance/wxpay-logs # 微信支付日志
```
---
### 2.6 提现管理 (Withdraw)
**功能清单:**
- 提现申请列表
- 提现审核(通过/拒绝)
- 提现打款(微信企业付款)
- 提现记录查询
**API 设计:**
```
GET /api/admin/business/withdraws # 提现列表
PUT /api/admin/business/withdraws/{id}/audit # 审核
POST /api/admin/business/withdraws/{id}/pay # 打款
```
---
### 2.7 抽奖配置 (Draw, Cardextractor)
**功能清单:**
- 抽奖活动列表
- 抽奖活动配置
- 抽卡机配置
- 卡牌商品管理
**API 设计:**
```
GET /api/admin/business/draws # 抽奖活动列表
POST /api/admin/business/draws # 新增活动
PUT /api/admin/business/draws/{id} # 编辑活动
DELETE /api/admin/business/draws/{id} # 删除活动
GET /api/admin/business/card-extractors # 抽卡机列表
POST /api/admin/business/card-extractors # 新增抽卡机
PUT /api/admin/business/card-extractors/{id}
DELETE /api/admin/business/card-extractors/{id}
```
---
### 2.8 系统配置 (Config)
**功能清单:**
- 支付配置(微信支付参数)
- 上传配置腾讯云COS
- 系统参数(站点名称、客服等)
- 短信配置
- 分享配置
**API 设计:**
```
GET /api/admin/business/config/{group} # 获取配置组
PUT /api/admin/business/config/{group} # 更新配置组
```
---
## 三、数据库表映射
业务模块需要访问 `honey_box` 业务数据库,主要涉及以下表:
| 业务表 | 说明 | 对应模块 |
|--------|------|----------|
| users | 用户表 | 用户管理 |
| user_account | 用户账户扩展 | 用户管理 |
| user_vip | VIP等级配置 | 用户管理 |
| goods | 盒子表 | 商品管理 |
| goods_list | 奖品表 | 商品管理 |
| goods_type | 盒子类型 | 商品管理 |
| goods_extend | 盒子扩展信息 | 商品管理 |
| shang | 奖品等级 | 商品管理 |
| order | 订单表 | 订单管理 |
| order_list | 订单奖品明细 | 订单管理 |
| order_list_send | 发货记录 | 订单管理 |
| profit_money | 余额流水 | 财务管理 |
| profit_integral | 积分流水 | 财务管理 |
| profit_money2 | 钻石流水 | 财务管理 |
| user_recharge | 充值记录 | 财务管理 |
| withdraw | 提现记录 | 提现管理 |
| coupon | 优惠券 | 优惠券管理 |
| coupon_receive | 优惠券领取 | 优惠券管理 |
| advert | 广告 | 广告管理 |
| advert_type | 广告类型 | 广告管理 |
| config | 系统配置 | 系统配置 |
---
## 四、技术实现要点
### 4.1 跨库查询
HoneyBox.Admin 需要同时访问两个数据库:
- `honey_box_admin` - 后台管理数据(管理员、角色、菜单等)
- `honey_box` - 业务数据(用户、商品、订单等)
**实现方案:**
```csharp
// 在 HoneyBox.Admin 中引用 HoneyBox.Model
// 注入两个 DbContext
services.AddDbContext<AdminDbContext>(options =>
options.UseSqlServer(adminConnectionString));
services.AddDbContext<HoneyBoxDbContext>(options =>
options.UseSqlServer(businessConnectionString));
```
### 4.2 业务服务层
`HoneyBox.Admin/Services/Business/` 下创建业务服务:
- UserBusinessService - 用户业务服务
- GoodsBusinessService - 商品业务服务
- OrderBusinessService - 订单业务服务
- FinanceBusinessService - 财务业务服务
### 4.3 控制器组织
`HoneyBox.Admin/Controllers/Business/` 下创建业务控制器:
```
Controllers/
├── AuthController.cs # 认证(已完成)
├── MenuController.cs # 菜单(已完成)
├── RoleController.cs # 角色(已完成)
├── AdminUserController.cs # 管理员(已完成)
├── DepartmentController.cs # 部门(已完成)
├── PermissionController.cs # 权限(已完成)
├── OperationLogController.cs # 操作日志(已完成)
└── Business/ # 业务模块(待迁移)
├── DashboardController.cs # 仪表盘
├── UserController.cs # 用户管理
├── GoodsController.cs # 商品管理
├── OrderController.cs # 订单管理
├── FinanceController.cs # 财务管理
├── WithdrawController.cs # 提现管理
├── CouponController.cs # 优惠券管理
└── ConfigController.cs # 系统配置
```
---
## 五、前端页面清单
### 5.1 需要新增的前端页面
| 页面路径 | 页面名称 | 对应后端模块 |
|----------|----------|--------------|
| /dashboard | 仪表盘 | Dashboard |
| /business/user | 用户管理 | User |
| /business/user/vip | VIP管理 | User |
| /business/goods | 商品管理 | Goods |
| /business/goods/type | 盒子类型 | Goods |
| /business/goods/prize | 奖品管理 | Goods |
| /business/order | 订单管理 | Order |
| /business/order/shipping | 发货管理 | Order |
| /business/finance | 财务管理 | Finance |
| /business/withdraw | 提现管理 | Withdraw |
| /business/coupon | 优惠券管理 | Coupon |
| /business/advert | 广告管理 | Advert |
| /business/config | 系统配置 | Config |
---
## 六、迁移计划时间表
| 阶段 | 模块 | 预计工时 | 建议顺序 |
|------|------|----------|----------|
| 第1周 | 首页仪表盘 + 用户管理 | 4天 | 1 |
| 第2周 | 商品管理 | 4天 | 2 |
| 第3周 | 订单管理 + 财务管理 | 5天 | 3 |
| 第4周 | 提现管理 + 系统配置 + 抽奖配置 | 4天 | 4 |
| 第5周 | 优惠券 + 广告 + 其他营销模块 | 5天 | 5 |
| 第6周 | 统计报表 + 剩余辅助模块 | 5天 | 6 |
**总预计工时:约 27 天6周**
---
## 七、下一步行动
1. **确认本清单** - 确认模块优先级和迁移顺序
2. **创建业务控制器目录** - `Controllers/Business/`
3. **配置跨库访问** - 引用 HoneyBox.Model注入 HoneyBoxDbContext
4. **开始 P0 模块迁移** - 从仪表盘和用户管理开始
确认后可以开始创建详细的实现任务清单。

View File

@ -84,6 +84,18 @@ public class AdminUserController : ControllerBase
return ApiResponse.Success("删除成功");
}
/// <summary>
/// 获取管理员已分配的角色ID列表
/// </summary>
/// <param name="id">管理员ID</param>
/// <returns>角色ID列表</returns>
[HttpGet("{id:long}/roles")]
public async Task<ApiResponse<List<long>>> GetRoles(long id)
{
var result = await _adminUserService.GetRoleIdsAsync(id);
return ApiResponse<List<long>>.Success(result);
}
/// <summary>
/// 分配角色给管理员
/// </summary>
@ -96,6 +108,18 @@ public class AdminUserController : ControllerBase
return ApiResponse.Success("分配成功");
}
/// <summary>
/// 获取管理员已分配的专属菜单ID列表
/// </summary>
/// <param name="id">管理员ID</param>
/// <returns>菜单ID列表</returns>
[HttpGet("{id:long}/menus")]
public async Task<ApiResponse<List<long>>> GetMenus(long id)
{
var result = await _adminUserService.GetMenuIdsAsync(id);
return ApiResponse<List<long>>.Success(result);
}
/// <summary>
/// 分配用户专属菜单
/// </summary>

View File

@ -93,6 +93,18 @@ public class RoleController : ControllerBase
return ApiResponse.Success("删除成功");
}
/// <summary>
/// 获取角色已分配的菜单ID列表
/// </summary>
/// <param name="id">角色ID</param>
/// <returns>菜单ID列表</returns>
[HttpGet("{id:long}/menus")]
public async Task<ApiResponse<List<long>>> GetMenus(long id)
{
var result = await _roleService.GetMenuIdsAsync(id);
return ApiResponse<List<long>>.Success(result);
}
/// <summary>
/// 分配菜单给角色
/// </summary>
@ -105,6 +117,18 @@ public class RoleController : ControllerBase
return ApiResponse.Success("分配成功");
}
/// <summary>
/// 获取角色已分配的权限编码列表
/// </summary>
/// <param name="id">角色ID</param>
/// <returns>权限编码列表</returns>
[HttpGet("{id:long}/permissions")]
public async Task<ApiResponse<List<string>>> GetPermissions(long id)
{
var result = await _roleService.GetPermissionCodesAsync(id);
return ApiResponse<List<string>>.Success(result);
}
/// <summary>
/// 分配权限给角色
/// </summary>
@ -113,7 +137,7 @@ public class RoleController : ControllerBase
[HttpPut("{id:long}/permissions")]
public async Task<ApiResponse> AssignPermissions(long id, [FromBody] AssignPermissionsRequest request)
{
await _roleService.AssignPermissionsAsync(id, request.PermissionIds);
await _roleService.AssignPermissionsAsync(id, request.PermissionCodes);
return ApiResponse.Success("分配成功");
}
}

View File

@ -8,7 +8,7 @@ namespace HoneyBox.Admin.Models.Role;
public class AssignPermissionsRequest
{
/// <summary>
/// 权限ID列表
/// 权限编码列表
/// </summary>
public List<long> PermissionIds { get; set; } = new();
public List<string> PermissionCodes { get; set; } = new();
}

View File

@ -266,6 +266,21 @@ public class AdminUserService : IAdminUserService
_logger.LogInformation("删除管理员成功: {UserId} - {Username}", id, user.Username);
}
/// <inheritdoc />
public async Task<List<long>> GetRoleIdsAsync(long userId)
{
var user = await _dbContext.AdminUsers.FindAsync(userId);
if (user == null)
{
throw new AdminException(AdminErrorCodes.InvalidParameter, "管理员不存在");
}
return await _dbContext.AdminUserRoles
.Where(ur => ur.AdminUserId == userId)
.Select(ur => ur.RoleId)
.ToListAsync();
}
/// <inheritdoc />
public async Task AssignRolesAsync(long userId, List<long> roleIds)
{
@ -294,6 +309,21 @@ public class AdminUserService : IAdminUserService
_logger.LogInformation("管理员 {UserId} 分配角色成功,角色数量: {Count}", userId, roleIds.Count);
}
/// <inheritdoc />
public async Task<List<long>> GetMenuIdsAsync(long userId)
{
var user = await _dbContext.AdminUsers.FindAsync(userId);
if (user == null)
{
throw new AdminException(AdminErrorCodes.InvalidParameter, "管理员不存在");
}
return await _dbContext.AdminUserMenus
.Where(um => um.AdminUserId == userId)
.Select(um => um.MenuId)
.ToListAsync();
}
/// <inheritdoc />
public async Task AssignMenusAsync(long userId, List<long> menuIds)
{

View File

@ -43,6 +43,13 @@ public interface IAdminUserService
/// <param name="id">管理员ID</param>
Task DeleteAsync(long id);
/// <summary>
/// 获取管理员已分配的角色ID列表
/// </summary>
/// <param name="userId">管理员ID</param>
/// <returns>角色ID列表</returns>
Task<List<long>> GetRoleIdsAsync(long userId);
/// <summary>
/// 分配角色给管理员
/// </summary>
@ -50,6 +57,13 @@ public interface IAdminUserService
/// <param name="roleIds">角色ID列表</param>
Task AssignRolesAsync(long userId, List<long> roleIds);
/// <summary>
/// 获取管理员已分配的专属菜单ID列表
/// </summary>
/// <param name="userId">管理员ID</param>
/// <returns>菜单ID列表</returns>
Task<List<long>> GetMenuIdsAsync(long userId);
/// <summary>
/// 分配用户专属菜单
/// </summary>

View File

@ -42,6 +42,13 @@ public interface IRoleService
/// <param name="id">角色ID</param>
Task DeleteAsync(long id);
/// <summary>
/// 获取角色已分配的菜单ID列表
/// </summary>
/// <param name="roleId">角色ID</param>
/// <returns>菜单ID列表</returns>
Task<List<long>> GetMenuIdsAsync(long roleId);
/// <summary>
/// 分配菜单给角色
/// </summary>
@ -49,12 +56,19 @@ public interface IRoleService
/// <param name="menuIds">菜单ID列表</param>
Task AssignMenusAsync(long roleId, List<long> menuIds);
/// <summary>
/// 获取角色已分配的权限编码列表
/// </summary>
/// <param name="roleId">角色ID</param>
/// <returns>权限编码列表</returns>
Task<List<string>> GetPermissionCodesAsync(long roleId);
/// <summary>
/// 分配权限给角色
/// </summary>
/// <param name="roleId">角色ID</param>
/// <param name="permissionIds">权限ID列表</param>
Task AssignPermissionsAsync(long roleId, List<long> permissionIds);
/// <param name="permissionCodes">权限编码列表</param>
Task AssignPermissionsAsync(long roleId, List<string> permissionCodes);
/// <summary>
/// 获取所有角色(下拉选择用)

View File

@ -190,6 +190,21 @@ public class RoleService : IRoleService
_logger.LogInformation("删除角色成功: {RoleId} - {RoleName}", id, role.Name);
}
/// <inheritdoc />
public async Task<List<long>> GetMenuIdsAsync(long roleId)
{
var role = await _dbContext.Roles.FindAsync(roleId);
if (role == null)
{
throw new AdminException(AdminErrorCodes.RoleNotFound, "角色不存在");
}
return await _dbContext.RoleMenus
.Where(rm => rm.RoleId == roleId)
.Select(rm => rm.MenuId)
.ToListAsync();
}
/// <inheritdoc />
public async Task AssignMenusAsync(long roleId, List<long> menuIds)
{
@ -219,7 +234,22 @@ public class RoleService : IRoleService
}
/// <inheritdoc />
public async Task AssignPermissionsAsync(long roleId, List<long> permissionIds)
public async Task<List<string>> GetPermissionCodesAsync(long roleId)
{
var role = await _dbContext.Roles.FindAsync(roleId);
if (role == null)
{
throw new AdminException(AdminErrorCodes.RoleNotFound, "角色不存在");
}
return await _dbContext.RolePermissions
.Where(rp => rp.RoleId == roleId)
.Join(_dbContext.Permissions, rp => rp.PermissionId, p => p.Id, (rp, p) => p.Code)
.ToListAsync();
}
/// <inheritdoc />
public async Task AssignPermissionsAsync(long roleId, List<string> permissionCodes)
{
var role = await _dbContext.Roles.FindAsync(roleId);
if (role == null)
@ -232,18 +262,23 @@ public class RoleService : IRoleService
_dbContext.RolePermissions.RemoveRange(existingPermissions);
// 添加新关联
if (permissionIds.Any())
if (permissionCodes.Any())
{
var newPermissions = permissionIds.Distinct().Select(permissionId => new RolePermission
// 根据权限编码获取权限ID
var permissions = await _dbContext.Permissions
.Where(p => permissionCodes.Contains(p.Code))
.ToListAsync();
var newPermissions = permissions.Select(p => new RolePermission
{
RoleId = roleId,
PermissionId = permissionId
PermissionId = p.Id
});
_dbContext.RolePermissions.AddRange(newPermissions);
}
await _dbContext.SaveChangesAsync();
_logger.LogInformation("角色 {RoleId} 分配权限成功,权限数量: {Count}", roleId, permissionIds.Count);
_logger.LogInformation("角色 {RoleId} 分配权限成功,权限数量: {Count}", roleId, permissionCodes.Count);
}
/// <inheritdoc />

View File

@ -19,7 +19,7 @@ export interface AdminUserQuery {
keyword?: string
departmentId?: number
status?: number
pageIndex: number
page: number
pageSize: number
}
@ -116,7 +116,7 @@ export function setAdminUserStatus(id: number, status: number): Promise<ApiRespo
// 重置密码
export function resetPassword(data: ResetPasswordRequest): Promise<ApiResponse<null>> {
return request({
url: `/admin/users/${data.userId}/password`,
url: `/admin/users/${data.userId}/reset-password`,
method: 'put',
data: { newPassword: data.newPassword }
})

View File

@ -22,7 +22,7 @@ export interface OperationLogQuery {
module?: string
startDate?: string
endDate?: string
pageIndex: number
page: number
pageSize: number
}

View File

@ -13,7 +13,7 @@ export interface Role {
export interface RoleQuery {
keyword?: string
status?: number
pageIndex: number
page: number
pageSize: number
}

View File

@ -92,6 +92,7 @@ const handleCommand = async (command: string) => {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--header-bg);
}
.header-left {
@ -103,11 +104,12 @@ const handleCommand = async (command: string) => {
.collapse-btn {
font-size: 20px;
cursor: pointer;
color: #606266;
color: var(--text-regular);
transition: color var(--transition-duration) var(--transition-timing);
}
.collapse-btn:hover {
color: #409eff;
color: var(--primary-color);
}
.header-right {
@ -120,9 +122,24 @@ const handleCommand = async (command: string) => {
align-items: center;
gap: 8px;
cursor: pointer;
padding: 6px 12px;
border-radius: var(--border-radius-base);
transition: background-color var(--transition-duration) var(--transition-timing);
}
.user-info:hover {
background-color: var(--bg-hover);
}
.username {
color: #606266;
color: var(--text-regular);
}
:deep(.el-breadcrumb__inner) {
color: var(--text-secondary);
}
:deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) {
color: var(--text-primary);
}
</style>

View File

@ -9,9 +9,10 @@
:default-active="activeMenu"
:collapse="collapse"
:unique-opened="true"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409eff"
background-color="transparent"
text-color="var(--sidebar-text)"
active-text-color="var(--sidebar-text-active)"
class="sidebar-menu"
router
>
<SidebarItem
@ -46,6 +47,7 @@ const activeMenu = computed(() => route.path)
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--sidebar-bg);
}
.sidebar-logo {
@ -53,26 +55,66 @@ const activeMenu = computed(() => route.path)
display: flex;
align-items: center;
justify-content: center;
background-color: #263445;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
}
.logo-title {
color: #fff;
color: var(--text-white);
font-size: 18px;
font-weight: bold;
letter-spacing: 1px;
}
.logo-title-mini {
color: var(--text-white);
font-size: 18px;
font-weight: bold;
}
.logo-title-mini {
color: #fff;
font-size: 18px;
font-weight: bold;
.sidebar-menu {
border-right: none !important;
}
:deep(.el-menu) {
border-right: none;
background-color: transparent !important;
}
:deep(.el-menu--collapse) {
width: 64px;
}
:deep(.el-menu-item) {
margin: 4px 8px;
border-radius: var(--border-radius-base);
transition: all var(--transition-duration) var(--transition-timing);
}
:deep(.el-menu-item:hover) {
background-color: var(--sidebar-item-hover) !important;
}
:deep(.el-menu-item.is-active) {
background-color: var(--sidebar-item-active) !important;
color: var(--sidebar-text-active) !important;
font-weight: 500;
}
:deep(.el-sub-menu__title) {
margin: 4px 8px;
border-radius: var(--border-radius-base);
transition: all var(--transition-duration) var(--transition-timing);
}
:deep(.el-sub-menu__title:hover) {
background-color: var(--sidebar-item-hover) !important;
}
:deep(.el-sub-menu .el-menu) {
background-color: transparent !important;
}
:deep(.el-sub-menu .el-menu-item) {
margin: 2px 8px 2px 16px;
}
</style>

View File

@ -49,9 +49,10 @@ const toggleCollapse = () => {
}
.layout-aside {
background-color: #304156;
transition: width 0.3s;
background-color: var(--sidebar-bg);
transition: width var(--transition-duration) var(--transition-timing);
overflow: hidden;
border-right: 1px solid var(--border-lighter);
}
.layout-main {
@ -60,10 +61,10 @@ const toggleCollapse = () => {
}
.layout-header {
height: 50px;
height: var(--header-height);
padding: 0;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
background-color: var(--header-bg);
box-shadow: var(--header-shadow);
display: flex;
align-items: center;
}
@ -71,7 +72,7 @@ const toggleCollapse = () => {
.layout-content {
flex: 1;
padding: 20px;
background-color: #f0f2f5;
background-color: var(--bg-page);
overflow: auto;
}

View File

@ -1,3 +1,6 @@
/* 引入主题变量 */
@import './variables.css';
* {
margin: 0;
padding: 0;
@ -7,23 +10,57 @@
html, body {
height: 100%;
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
background-color: var(--bg-page);
}
#app {
height: 100%;
}
/* 滚动条样式 */
/* 滚动条样式 - 浅蓝主题 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
background-color: var(--primary-light);
border-radius: 3px;
}
::-webkit-scrollbar-track {
background-color: #f5f7fa;
::-webkit-scrollbar-thumb:hover {
background-color: var(--primary-color);
}
::-webkit-scrollbar-track {
background-color: var(--bg-light);
}
/* 全局链接样式 */
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
color: var(--primary-dark);
}
/* 卡片通用样式 */
.el-card {
border-radius: var(--border-radius-large);
border: 1px solid var(--border-lighter);
box-shadow: var(--box-shadow-light);
}
/* 表格头部样式 */
.el-table th.el-table__cell {
background-color: var(--bg-light) !important;
color: var(--text-primary);
}
/* 按钮悬停效果增强 */
.el-button--primary:hover {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}

View File

@ -0,0 +1,116 @@
/**
* HoneyBox 管理后台 - 主题变量文件
* 浅蓝色主题 (Light Blue Theme)
*
* 使用方式: var(--变量名)
* 切换主题: 修改 :root 下的变量值即可
*/
:root {
/* ==================== 品牌色 ==================== */
--primary-color: #4A90D9; /* 主色调 - 天空蓝 */
--primary-light: #74B9FF; /* 主色浅色 */
--primary-dark: #2B7DE9; /* 主色深色 */
--primary-bg: #E8F4FD; /* 主色背景 */
/* ==================== 功能色 ==================== */
--success-color: #67C23A; /* 成功色 */
--warning-color: #E6A23C; /* 警告色 */
--danger-color: #F56C6C; /* 危险色 */
--info-color: #909399; /* 信息色 */
/* ==================== 文字色 ==================== */
--text-primary: #303133; /* 主要文字 */
--text-regular: #606266; /* 常规文字 */
--text-secondary: #909399; /* 次要文字 */
--text-placeholder: #C0C4CC; /* 占位文字 */
--text-white: #FFFFFF; /* 白色文字 */
/* ==================== 边框色 ==================== */
--border-base: #DCDFE6; /* 基础边框 */
--border-light: #E4E7ED; /* 浅色边框 */
--border-lighter: #EBEEF5; /* 更浅边框 */
--border-extra-light: #F2F6FC; /* 极浅边框 */
/* ==================== 背景色 ==================== */
--bg-white: #FFFFFF; /* 白色背景 */
--bg-page: #F5F9FC; /* 页面背景 - 极浅蓝 */
--bg-light: #F0F7FF; /* 浅色背景 */
--bg-hover: #E8F4FD; /* 悬停背景 */
/* ==================== 侧边栏 ==================== */
--sidebar-bg: #F0F7FF; /* 侧边栏背景 - 浅蓝白 */
--sidebar-logo-bg: #4A90D9; /* Logo区背景 - 主蓝色 */
--sidebar-text: #606266; /* 侧边栏文字 */
--sidebar-text-active: #4A90D9; /* 侧边栏激活文字 */
--sidebar-item-hover: #E8F4FD; /* 菜单项悬停背景 */
--sidebar-item-active: #E8F4FD; /* 菜单项激活背景 */
--sidebar-submenu-bg: #E8F4FD; /* 子菜单背景 */
--sidebar-width: 200px; /* 侧边栏宽度 */
--sidebar-width-collapsed: 64px; /* 折叠后宽度 */
/* ==================== 顶部栏 ==================== */
--header-bg: #FFFFFF; /* 顶部栏背景 */
--header-height: 50px; /* 顶部栏高度 */
--header-shadow: 0 1px 4px rgba(74, 144, 217, 0.1); /* 顶部栏阴影 */
/* ==================== 登录页 ==================== */
--login-bg-start: #74B9FF; /* 登录页渐变起始色 */
--login-bg-end: #4A90D9; /* 登录页渐变结束色 */
--login-box-shadow: 0 4px 20px rgba(74, 144, 217, 0.15); /* 登录框阴影 */
/* ==================== 圆角 ==================== */
--border-radius-small: 4px;
--border-radius-base: 6px;
--border-radius-large: 8px;
--border-radius-round: 20px;
/* ==================== 阴影 ==================== */
--box-shadow-light: 0 2px 12px rgba(0, 0, 0, 0.05);
--box-shadow-base: 0 2px 12px rgba(74, 144, 217, 0.1);
--box-shadow-dark: 0 4px 20px rgba(74, 144, 217, 0.15);
/* ==================== 过渡动画 ==================== */
--transition-duration: 0.3s;
--transition-timing: ease;
}
/* ==================== Element Plus 主题覆盖 ==================== */
:root {
/* 覆盖 Element Plus 主色 */
--el-color-primary: var(--primary-color);
--el-color-primary-light-3: #7AB4E8;
--el-color-primary-light-5: #A4CDF0;
--el-color-primary-light-7: #CEE6F8;
--el-color-primary-light-8: #E3F0FB;
--el-color-primary-light-9: #F0F7FF;
--el-color-primary-dark-2: #3B73AE;
/* 覆盖功能色 */
--el-color-success: var(--success-color);
--el-color-warning: var(--warning-color);
--el-color-danger: var(--danger-color);
--el-color-info: var(--info-color);
/* 覆盖文字色 */
--el-text-color-primary: var(--text-primary);
--el-text-color-regular: var(--text-regular);
--el-text-color-secondary: var(--text-secondary);
--el-text-color-placeholder: var(--text-placeholder);
/* 覆盖边框色 */
--el-border-color: var(--border-base);
--el-border-color-light: var(--border-light);
--el-border-color-lighter: var(--border-lighter);
--el-border-color-extra-light: var(--border-extra-light);
/* 覆盖背景色 */
--el-bg-color: var(--bg-white);
--el-bg-color-page: var(--bg-page);
--el-bg-color-overlay: var(--bg-white);
/* 覆盖圆角 */
--el-border-radius-base: var(--border-radius-base);
--el-border-radius-small: var(--border-radius-small);
--el-border-radius-round: var(--border-radius-round);
}

View File

@ -12,9 +12,9 @@ export interface ApiResponse<T = any> {
// 分页结果
export interface PagedResult<T> {
items: T[]
list: T[]
total: number
pageIndex: number
page: number
pageSize: number
totalPages: number
}

View File

@ -3,7 +3,7 @@
<el-row :gutter="20">
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-icon" style="background: #409eff">
<div class="stat-icon stat-icon-primary">
<el-icon size="32"><User /></el-icon>
</div>
<div class="stat-content">
@ -14,7 +14,7 @@
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-icon" style="background: #67c23a">
<div class="stat-icon stat-icon-success">
<el-icon size="32"><UserFilled /></el-icon>
</div>
<div class="stat-content">
@ -25,7 +25,7 @@
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-icon" style="background: #e6a23c">
<div class="stat-icon stat-icon-warning">
<el-icon size="32"><Menu /></el-icon>
</div>
<div class="stat-content">
@ -36,7 +36,7 @@
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-icon" style="background: #f56c6c">
<div class="stat-icon stat-icon-danger">
<el-icon size="32"><OfficeBuilding /></el-icon>
</div>
<div class="stat-content">
@ -137,6 +137,12 @@ onMounted(() => {
display: flex;
align-items: center;
padding: 20px;
transition: all var(--transition-duration) var(--transition-timing);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--box-shadow-dark);
}
.stat-card :deep(.el-card__body) {
@ -148,14 +154,30 @@ onMounted(() => {
.stat-icon {
width: 64px;
height: 64px;
border-radius: 8px;
border-radius: var(--border-radius-large);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
color: var(--text-white);
margin-right: 16px;
}
.stat-icon-primary {
background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary-color) 100%);
}
.stat-icon-success {
background: linear-gradient(135deg, #95d475 0%, var(--success-color) 100%);
}
.stat-icon-warning {
background: linear-gradient(135deg, #f0c78a 0%, var(--warning-color) 100%);
}
.stat-icon-danger {
background: linear-gradient(135deg, #f89898 0%, var(--danger-color) 100%);
}
.stat-content {
flex: 1;
}
@ -163,12 +185,12 @@ onMounted(() => {
.stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
color: var(--text-primary);
}
.stat-label {
font-size: 14px;
color: #909399;
color: var(--text-secondary);
margin-top: 4px;
}
@ -178,12 +200,12 @@ onMounted(() => {
.welcome-content h2 {
margin: 0 0 16px 0;
color: #303133;
color: var(--primary-color);
}
.welcome-content p {
margin: 8px 0;
color: #606266;
color: var(--text-regular);
}
.quick-actions {
@ -191,4 +213,11 @@ onMounted(() => {
gap: 12px;
flex-wrap: wrap;
}
:deep(.el-card__header) {
background-color: var(--bg-light);
border-bottom: 1px solid var(--border-lighter);
font-weight: 500;
color: var(--text-primary);
}
</style>

View File

@ -102,15 +102,42 @@ const handleLogin = async () => {
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: linear-gradient(135deg, var(--login-bg-start) 0%, var(--login-bg-end) 100%);
position: relative;
overflow: hidden;
}
/* 装饰性背景圆 */
.login-container::before {
content: '';
position: absolute;
width: 600px;
height: 600px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
top: -200px;
right: -200px;
}
.login-container::after {
content: '';
position: absolute;
width: 400px;
height: 400px;
background: rgba(255, 255, 255, 0.08);
border-radius: 50%;
bottom: -150px;
left: -150px;
}
.login-box {
width: 400px;
padding: 40px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
background: var(--bg-white);
border-radius: var(--border-radius-large);
box-shadow: var(--login-box-shadow);
position: relative;
z-index: 1;
}
.login-header {
@ -120,8 +147,12 @@ const handleLogin = async () => {
.login-header h1 {
font-size: 24px;
color: #303133;
color: var(--text-primary);
margin: 0;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.login-form {
@ -130,5 +161,25 @@ const handleLogin = async () => {
.login-btn {
width: 100%;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
border: none;
transition: all var(--transition-duration) var(--transition-timing);
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(74, 144, 217, 0.4);
}
:deep(.el-input__wrapper) {
border-radius: var(--border-radius-base);
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px var(--primary-light) inset;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px var(--primary-color) inset;
}
</style>

View File

@ -53,7 +53,7 @@
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.pageIndex"
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
@ -156,7 +156,7 @@ const total = ref(0)
const queryParams = reactive<RoleQuery>({
keyword: '',
status: undefined,
pageIndex: 1,
page: 1,
pageSize: 10
})
@ -220,7 +220,7 @@ const fetchData = async () => {
loading.value = true
try {
const res = await getRoleList(queryParams)
roleList.value = res.data.items
roleList.value = res.data.list
total.value = res.data.total
} finally {
loading.value = false
@ -228,14 +228,14 @@ const fetchData = async () => {
}
const handleSearch = () => {
queryParams.pageIndex = 1
queryParams.page = 1
fetchData()
}
const handleReset = () => {
queryParams.keyword = ''
queryParams.status = undefined
queryParams.pageIndex = 1
queryParams.page = 1
fetchData()
}

View File

@ -79,7 +79,7 @@
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.pageIndex"
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
@ -219,7 +219,7 @@ const queryParams = reactive<AdminUserQuery>({
keyword: '',
departmentId: undefined,
status: undefined,
pageIndex: 1,
page: 1,
pageSize: 10
})
@ -308,7 +308,7 @@ const fetchData = async () => {
loading.value = true
try {
const res = await getAdminUserList(queryParams)
userList.value = res.data.items
userList.value = res.data.list
total.value = res.data.total
} finally {
loading.value = false
@ -327,7 +327,7 @@ const loadBaseData = async () => {
}
const handleSearch = () => {
queryParams.pageIndex = 1
queryParams.page = 1
fetchData()
}
@ -335,7 +335,7 @@ const handleReset = () => {
queryParams.keyword = ''
queryParams.departmentId = undefined
queryParams.status = undefined
queryParams.pageIndex = 1
queryParams.page = 1
fetchData()
}