From e0a4bc4ba2ad55385ed14a38ba8ce4576953296e Mon Sep 17 00:00:00 2001 From: gpu Date: Mon, 5 Jan 2026 00:05:04 +0800 Subject: [PATCH] 3333 --- .kiro/specs/menu-crud/design.md | 188 +++++++++++++ .kiro/specs/menu-crud/requirements.md | 84 ++++++ .kiro/specs/menu-crud/tasks.md | 84 ++++++ .../Controllers/AdminUserController.cs | 9 + .../Controllers/AuthController.cs | 4 + .../Controllers/DepartmentController.cs | 5 + .../Controllers/MenuController.cs | 3 + .../Controllers/RoleController.cs | 6 + .../Extensions/ServiceCollectionExtensions.cs | 3 +- server/HoneyBox/src/HoneyBox.Admin/Program.cs | 5 +- .../HoneyBox.Admin/admin-web/src/api/menu.ts | 64 +++++ .../admin-web/src/router/index.ts | 26 ++ .../admin-web/src/views/password/index.vue | 114 ++++++++ .../admin-web/src/views/profile/index.vue | 36 +++ .../admin-web/src/views/system/menu/index.vue | 259 +++++++++++++++++- .../HoneyBox.Admin/admin-web/vite.config.ts | 2 +- 16 files changed, 877 insertions(+), 15 deletions(-) create mode 100644 .kiro/specs/menu-crud/design.md create mode 100644 .kiro/specs/menu-crud/requirements.md create mode 100644 .kiro/specs/menu-crud/tasks.md create mode 100644 server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/password/index.vue create mode 100644 server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/profile/index.vue diff --git a/.kiro/specs/menu-crud/design.md b/.kiro/specs/menu-crud/design.md new file mode 100644 index 00000000..dcaa8ca1 --- /dev/null +++ b/.kiro/specs/menu-crud/design.md @@ -0,0 +1,188 @@ +# Design Document: Menu CRUD + +## Overview + +完善菜单管理页面的 CRUD 功能。后端 API 已完整实现,本设计主要关注前端实现:补充 API 接口定义、实现表单对话框、完善操作逻辑。 + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Menu Page (index.vue) │ +├─────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ +│ │ Menu Table │ │ Menu Dialog │ │ Delete Confirm │ │ +│ │ (Tree View) │ │ (Form) │ │ Dialog │ │ +│ └─────────────┘ └─────────────┘ └─────────────────┘ │ +├─────────────────────────────────────────────────────────┤ +│ Menu API (menu.ts) │ +│ getMenuTree | createMenu | updateMenu | deleteMenu │ +├─────────────────────────────────────────────────────────┤ +│ Backend API (已实现) │ +│ GET /menus | POST /menus | PUT /menus/{id} | DELETE │ +└─────────────────────────────────────────────────────────┘ +``` + +## Components and Interfaces + +### Menu API 接口补充 + +```typescript +// 创建菜单请求 +export interface CreateMenuRequest { + parentId: number + name: string + path?: string + component?: string + icon?: string + menuType: number // 1=目录, 2=菜单, 3=按钮 + permission?: string + sortOrder: number + status: number // 1=显示, 0=隐藏 + isExternal: boolean + isCache: boolean +} + +// 更新菜单请求 +export interface UpdateMenuRequest { + parentId: number + name: string + path?: string + component?: string + icon?: string + menuType: number + permission?: string + sortOrder: number + status: number + isExternal: boolean + isCache: boolean +} + +// API 函数 +export function createMenu(data: CreateMenuRequest): Promise> +export function updateMenu(id: number, data: UpdateMenuRequest): Promise> +export function deleteMenu(id: number): Promise> +export function getMenuById(id: number): Promise> +``` + +### Menu Form 表单字段 + +| 字段 | 类型 | 组件 | 验证规则 | +|------|------|------|----------| +| parentId | number | el-tree-select | 可选,默认0表示顶级 | +| name | string | el-input | 必填 | +| menuType | number | el-radio-group | 必填,1/2/3 | +| icon | string | el-input | 可选,目录/菜单时显示 | +| path | string | el-input | 菜单类型时必填 | +| component | string | el-input | 菜单类型时必填 | +| permission | string | el-input | 按钮类型时必填 | +| sortOrder | number | el-input-number | 必填,>=0 | +| status | number | el-radio-group | 必填,0/1 | +| isExternal | boolean | el-switch | 可选 | +| isCache | boolean | el-switch | 可选 | + +### 表单验证逻辑 + +```typescript +const formRules = { + name: [{ required: true, message: '请输入菜单名称' }], + menuType: [{ required: true, message: '请选择菜单类型' }], + path: [{ + required: true, + validator: (rule, value, callback) => { + if (formData.menuType === 2 && !value) { + callback(new Error('菜单类型必须填写路由路径')) + } else { + callback() + } + } + }], + component: [{ + required: true, + validator: (rule, value, callback) => { + if (formData.menuType === 2 && !value) { + callback(new Error('菜单类型必须填写组件路径')) + } else { + callback() + } + } + }], + permission: [{ + validator: (rule, value, callback) => { + if (formData.menuType === 3 && !value) { + callback(new Error('按钮类型必须填写权限标识')) + } else { + callback() + } + } + }] +} +``` + +## Data Models + +### 表单数据模型 + +```typescript +interface MenuFormData { + id: number + parentId: number + name: string + path: string + component: string + icon: string + menuType: number + permission: string + sortOrder: number + status: number + isExternal: boolean + isCache: boolean +} + +// 默认值 +const defaultFormData: MenuFormData = { + id: 0, + parentId: 0, + name: '', + path: '', + component: '', + icon: '', + menuType: 2, + permission: '', + sortOrder: 0, + status: 1, + isExternal: false, + isCache: true +} +``` + +## Correctness Properties + +*A property is a characteristic or behavior that should hold true across all valid executions of a system.* + +本功能主要是 UI 交互,大部分验证在后端完成。以下是可测试的属性: + +**Property 1: 表单提交数据完整性** +*For any* valid form submission, the request payload SHALL contain all required fields based on menuType. +**Validates: Requirements 6.1, 6.2, 6.3** + +**Property 2: 父菜单选择排除自身** +*For any* menu being edited, the parent menu selector SHALL NOT include the menu itself or its descendants. +**Validates: Requirements 4.3** + +## Error Handling + +| 场景 | 处理方式 | +|------|----------| +| API 请求失败 | 显示 ElMessage.error 提示错误信息 | +| 表单验证失败 | 阻止提交,显示字段错误提示 | +| 删除有子菜单的菜单 | 后端返回错误,前端显示提示 | +| 网络超时 | 显示网络错误提示 | + +## Testing Strategy + +由于本功能主要是 UI 交互,测试策略以手动测试为主: + +1. **功能测试**:验证新增、编辑、删除操作正常工作 +2. **表单验证测试**:验证必填字段和条件必填逻辑 +3. **边界测试**:测试空数据、特殊字符等边界情况 diff --git a/.kiro/specs/menu-crud/requirements.md b/.kiro/specs/menu-crud/requirements.md new file mode 100644 index 00000000..819ba54d --- /dev/null +++ b/.kiro/specs/menu-crud/requirements.md @@ -0,0 +1,84 @@ +# Requirements Document + +## Introduction + +完善 HoneyBox.Admin 后台管理系统的菜单管理页面功能。当前菜单管理页面只有展示功能,需要实现完整的 CRUD 操作,包括新增菜单、编辑菜单、添加子菜单和删除菜单。 + +## Glossary + +- **Menu_Page**: 菜单管理页面组件 +- **Menu_API**: 前端菜单 API 模块 +- **Menu_Form**: 菜单表单对话框组件 +- **Menu_Tree**: 菜单树形表格 + +## Requirements + +### Requirement 1: 前端 API 补充 + +**User Story:** As a developer, I want to have complete menu API functions, so that the frontend can call backend CRUD operations. + +#### Acceptance Criteria + +1. THE Menu_API SHALL export createMenu function to call POST /api/admin/menus +2. THE Menu_API SHALL export updateMenu function to call PUT /api/admin/menus/{id} +3. THE Menu_API SHALL export deleteMenu function to call DELETE /api/admin/menus/{id} +4. THE Menu_API SHALL export getMenuById function to call GET /api/admin/menus/{id} +5. THE Menu_API SHALL define CreateMenuRequest and UpdateMenuRequest TypeScript interfaces + +### Requirement 2: 新增菜单功能 + +**User Story:** As an administrator, I want to add new menus, so that I can expand the system navigation structure. + +#### Acceptance Criteria + +1. WHEN the user clicks "新增菜单" button, THE Menu_Page SHALL display a form dialog +2. THE Menu_Form SHALL include fields: parentId (tree select), name, path, component, icon, menuType, permission, sortOrder, status, isExternal, isCache +3. WHEN the user submits a valid form, THE Menu_Page SHALL call createMenu API and refresh the menu tree +4. IF the API returns an error, THEN THE Menu_Page SHALL display the error message +5. WHEN the form is submitted successfully, THE Menu_Page SHALL close the dialog and show success message + +### Requirement 3: 添加子菜单功能 + +**User Story:** As an administrator, I want to add child menus under existing menus, so that I can create hierarchical navigation. + +#### Acceptance Criteria + +1. WHEN the user clicks "添加子菜单" button on a menu row, THE Menu_Page SHALL display the form dialog with parentId pre-filled +2. THE Menu_Form SHALL show the parent menu name in the parentId field +3. WHEN the user submits the form, THE Menu_Page SHALL create the menu as a child of the selected parent + +### Requirement 4: 编辑菜单功能 + +**User Story:** As an administrator, I want to edit existing menus, so that I can update menu information. + +#### Acceptance Criteria + +1. WHEN the user clicks "编辑" button on a menu row, THE Menu_Page SHALL display the form dialog with existing data +2. THE Menu_Form SHALL populate all fields with the current menu data +3. THE Menu_Form SHALL allow changing the parent menu (with validation to prevent circular reference) +4. WHEN the user submits a valid form, THE Menu_Page SHALL call updateMenu API and refresh the menu tree +5. IF the API returns an error, THEN THE Menu_Page SHALL display the error message + +### Requirement 5: 删除菜单功能 + +**User Story:** As an administrator, I want to delete menus, so that I can remove unused navigation items. + +#### Acceptance Criteria + +1. WHEN the user clicks "删除" button on a menu row, THE Menu_Page SHALL display a confirmation dialog +2. THE confirmation dialog SHALL warn about deleting child menus if the menu has children +3. WHEN the user confirms deletion, THE Menu_Page SHALL call deleteMenu API and refresh the menu tree +4. IF the API returns an error, THEN THE Menu_Page SHALL display the error message +5. WHEN deletion is successful, THE Menu_Page SHALL show success message + +### Requirement 6: 表单验证 + +**User Story:** As an administrator, I want form validation, so that I can ensure data integrity. + +#### Acceptance Criteria + +1. THE Menu_Form SHALL require name field (non-empty) +2. WHEN menuType is "菜单" (type 2), THE Menu_Form SHALL require path and component fields +3. WHEN menuType is "按钮" (type 3), THE Menu_Form SHALL require permission field +4. THE Menu_Form SHALL validate path format (must start with / for internal routes) +5. THE Menu_Form SHALL validate sortOrder as a non-negative integer diff --git a/.kiro/specs/menu-crud/tasks.md b/.kiro/specs/menu-crud/tasks.md new file mode 100644 index 00000000..3ac48d23 --- /dev/null +++ b/.kiro/specs/menu-crud/tasks.md @@ -0,0 +1,84 @@ +# Implementation Plan: Menu CRUD + +## Overview + +完善菜单管理页面的 CRUD 功能。后端 API 已完整实现,本计划专注于前端实现。 + +## Tasks + +- [x] 1. 补充前端 Menu API 接口 + - [x] 1.1 添加 TypeScript 接口定义 + - 添加 CreateMenuRequest 接口 + - 添加 UpdateMenuRequest 接口 + - _Requirements: 1.5_ + - [x] 1.2 添加 API 函数 + - 添加 createMenu 函数 (POST /admin/menus) + - 添加 updateMenu 函数 (PUT /admin/menus/{id}) + - 添加 deleteMenu 函数 (DELETE /admin/menus/{id}) + - 添加 getMenuById 函数 (GET /admin/menus/{id}) + - _Requirements: 1.1, 1.2, 1.3, 1.4_ + +- [x] 2. 实现菜单表单对话框 + - [x] 2.1 添加表单状态和数据 + - 添加 dialogVisible, isEdit, formRef, submitLoading 状态 + - 添加 formData reactive 对象 + - 添加 menuTreeForSelect 计算属性(用于父菜单选择) + - _Requirements: 2.1, 2.2_ + - [x] 2.2 添加表单验证规则 + - 实现 name 必填验证 + - 实现 menuType 条件验证(菜单类型需要 path/component,按钮类型需要 permission) + - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_ + - [x] 2.3 添加表单对话框模板 + - 添加 el-dialog 组件 + - 添加 el-form 包含所有字段 + - 添加 el-tree-select 用于父菜单选择 + - 根据 menuType 动态显示/隐藏字段 + - _Requirements: 2.2, 3.2_ + +- [x] 3. 实现新增菜单功能 + - [x] 3.1 实现 handleAdd 方法 + - 重置表单数据为默认值 + - 设置 isEdit = false + - 打开对话框 + - _Requirements: 2.1_ + - [x] 3.2 实现 handleAddChild 方法 + - 重置表单数据 + - 设置 parentId 为当前行的 id + - 打开对话框 + - _Requirements: 3.1, 3.2, 3.3_ + +- [x] 4. 实现编辑菜单功能 + - [x] 4.1 实现 handleEdit 方法 + - 设置 isEdit = true + - 填充表单数据 + - 打开对话框 + - _Requirements: 4.1, 4.2_ + +- [x] 5. 实现表单提交功能 + - [x] 5.1 实现 handleSubmit 方法 + - 验证表单 + - 根据 isEdit 调用 createMenu 或 updateMenu API + - 成功后关闭对话框并刷新数据 + - 失败时显示错误信息 + - _Requirements: 2.3, 2.4, 2.5, 4.4, 4.5_ + +- [x] 6. 实现删除菜单功能 + - [x] 6.1 实现 handleDelete 方法 + - 显示确认对话框 + - 调用 deleteMenu API + - 成功后刷新数据 + - 失败时显示错误信息 + - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_ + +- [x] 7. Checkpoint - 功能验证 + - 验证新增菜单功能正常 + - 验证添加子菜单功能正常 + - 验证编辑菜单功能正常 + - 验证删除菜单功能正常 + - 验证表单验证逻辑正确 + +## Notes + +- 后端 API 已完整实现,无需修改后端代码 +- 参考角色管理页面 (role/index.vue) 和部门管理页面 (department/index.vue) 的实现模式 +- 表单字段根据 menuType 动态显示:目录(1)显示图标,菜单(2)显示路径/组件/图标,按钮(3)显示权限标识 diff --git a/server/HoneyBox/src/HoneyBox.Admin/Controllers/AdminUserController.cs b/server/HoneyBox/src/HoneyBox.Admin/Controllers/AdminUserController.cs index 38af883f..aa1dce16 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Controllers/AdminUserController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Controllers/AdminUserController.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using HoneyBox.Admin.Filters; using HoneyBox.Admin.Models.AdminUser; using HoneyBox.Admin.Models.Common; using HoneyBox.Admin.Services; @@ -54,6 +55,7 @@ public class AdminUserController : ControllerBase /// 创建请求 /// 新管理员ID [HttpPost] + [OperationLog("管理员管理", "创建管理员")] public async Task> Create([FromBody] CreateAdminUserRequest request) { var createdBy = GetCurrentUserId(); @@ -67,6 +69,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 更新请求 [HttpPut("{id:long}")] + [OperationLog("管理员管理", "更新管理员")] public async Task Update(long id, [FromBody] UpdateAdminUserRequest request) { await _adminUserService.UpdateAsync(id, request); @@ -78,6 +81,7 @@ public class AdminUserController : ControllerBase /// /// 管理员ID [HttpDelete("{id:long}")] + [OperationLog("管理员管理", "删除管理员")] public async Task Delete(long id) { await _adminUserService.DeleteAsync(id); @@ -102,6 +106,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 分配请求 [HttpPut("{id:long}/roles")] + [OperationLog("管理员管理", "分配角色")] public async Task AssignRoles(long id, [FromBody] AssignRolesRequest request) { await _adminUserService.AssignRolesAsync(id, request.RoleIds); @@ -126,6 +131,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 分配请求 [HttpPut("{id:long}/menus")] + [OperationLog("管理员管理", "分配专属菜单")] public async Task AssignMenus(long id, [FromBody] AssignUserMenusRequest request) { await _adminUserService.AssignMenusAsync(id, request.MenuIds); @@ -138,6 +144,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 分配请求 [HttpPut("{id:long}/department")] + [OperationLog("管理员管理", "分配部门")] public async Task AssignDepartment(long id, [FromBody] AssignDepartmentRequest request) { await _adminUserService.AssignDepartmentAsync(id, request.DepartmentId); @@ -150,6 +157,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 状态请求 [HttpPut("{id:long}/status")] + [OperationLog("管理员管理", "设置状态")] public async Task SetStatus(long id, [FromBody] SetStatusRequest request) { await _adminUserService.SetStatusAsync(id, request.Status == 1); @@ -162,6 +170,7 @@ public class AdminUserController : ControllerBase /// 管理员ID /// 重置密码请求 [HttpPut("{id:long}/reset-password")] + [OperationLog("管理员管理", "重置密码")] public async Task ResetPassword(long id, [FromBody] ResetPasswordRequest request) { await _adminUserService.ResetPasswordAsync(id, request.NewPassword); diff --git a/server/HoneyBox/src/HoneyBox.Admin/Controllers/AuthController.cs b/server/HoneyBox/src/HoneyBox.Admin/Controllers/AuthController.cs index 673c37eb..32b9377e 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Controllers/AuthController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Controllers/AuthController.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using HoneyBox.Admin.Filters; using HoneyBox.Admin.Models.Auth; using HoneyBox.Admin.Models.Common; using HoneyBox.Admin.Services; @@ -30,6 +31,7 @@ public class AuthController : ControllerBase /// 登录响应 [HttpPost("login")] [AllowAnonymous] + [OperationLog("认证", "登录")] public async Task> Login([FromBody] LoginRequest request) { var ipAddress = GetClientIpAddress(); @@ -56,6 +58,7 @@ public class AuthController : ControllerBase /// 修改密码请求 [HttpPut("password")] [Authorize] + [OperationLog("认证", "修改密码")] public async Task ChangePassword([FromBody] ChangePasswordRequest request) { var userId = GetCurrentUserId(); @@ -68,6 +71,7 @@ public class AuthController : ControllerBase /// [HttpPost("logout")] [Authorize] + [OperationLog("认证", "退出登录")] public async Task Logout() { var userId = GetCurrentUserId(); diff --git a/server/HoneyBox/src/HoneyBox.Admin/Controllers/DepartmentController.cs b/server/HoneyBox/src/HoneyBox.Admin/Controllers/DepartmentController.cs index 4e17c9d5..e0b7cc32 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Controllers/DepartmentController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Controllers/DepartmentController.cs @@ -1,3 +1,4 @@ +using HoneyBox.Admin.Filters; using HoneyBox.Admin.Models.AdminUser; using HoneyBox.Admin.Models.Common; using HoneyBox.Admin.Models.Department; @@ -53,6 +54,7 @@ public class DepartmentController : ControllerBase /// 创建请求 /// 新部门ID [HttpPost] + [OperationLog("部门管理", "创建部门")] public async Task> Create([FromBody] CreateDepartmentRequest request) { var id = await _departmentService.CreateAsync(request); @@ -65,6 +67,7 @@ public class DepartmentController : ControllerBase /// 部门ID /// 更新请求 [HttpPut("{id:long}")] + [OperationLog("部门管理", "更新部门")] public async Task Update(long id, [FromBody] UpdateDepartmentRequest request) { await _departmentService.UpdateAsync(id, request); @@ -76,6 +79,7 @@ public class DepartmentController : ControllerBase /// /// 部门ID [HttpDelete("{id:long}")] + [OperationLog("部门管理", "删除部门")] public async Task Delete(long id) { await _departmentService.DeleteAsync(id); @@ -88,6 +92,7 @@ public class DepartmentController : ControllerBase /// 部门ID /// 分配请求 [HttpPut("{id:long}/menus")] + [OperationLog("部门管理", "分配菜单")] public async Task AssignMenus(long id, [FromBody] AssignDepartmentMenusRequest request) { await _departmentService.AssignMenusAsync(id, request.MenuIds); diff --git a/server/HoneyBox/src/HoneyBox.Admin/Controllers/MenuController.cs b/server/HoneyBox/src/HoneyBox.Admin/Controllers/MenuController.cs index 4cb0287f..d6f366f4 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Controllers/MenuController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Controllers/MenuController.cs @@ -54,6 +54,7 @@ public class MenuController : ControllerBase /// 创建请求 /// 新菜单ID [HttpPost] + [OperationLog("菜单管理", "创建菜单")] public async Task> Create([FromBody] CreateMenuRequest request) { var id = await _menuService.CreateAsync(request); @@ -66,6 +67,7 @@ public class MenuController : ControllerBase /// 菜单ID /// 更新请求 [HttpPut("{id:long}")] + [OperationLog("菜单管理", "更新菜单")] public async Task Update(long id, [FromBody] UpdateMenuRequest request) { await _menuService.UpdateAsync(id, request); @@ -77,6 +79,7 @@ public class MenuController : ControllerBase /// /// 菜单ID [HttpDelete("{id:long}")] + [OperationLog("菜单管理", "删除菜单")] public async Task Delete(long id) { await _menuService.DeleteAsync(id); diff --git a/server/HoneyBox/src/HoneyBox.Admin/Controllers/RoleController.cs b/server/HoneyBox/src/HoneyBox.Admin/Controllers/RoleController.cs index e40f4709..59a9b8ac 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Controllers/RoleController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Controllers/RoleController.cs @@ -1,3 +1,4 @@ +using HoneyBox.Admin.Filters; using HoneyBox.Admin.Models.Common; using HoneyBox.Admin.Models.Role; using HoneyBox.Admin.Services; @@ -64,6 +65,7 @@ public class RoleController : ControllerBase /// 创建请求 /// 新角色ID [HttpPost] + [OperationLog("角色管理", "创建角色")] public async Task> Create([FromBody] CreateRoleRequest request) { var id = await _roleService.CreateAsync(request); @@ -76,6 +78,7 @@ public class RoleController : ControllerBase /// 角色ID /// 更新请求 [HttpPut("{id:long}")] + [OperationLog("角色管理", "更新角色")] public async Task Update(long id, [FromBody] UpdateRoleRequest request) { await _roleService.UpdateAsync(id, request); @@ -87,6 +90,7 @@ public class RoleController : ControllerBase /// /// 角色ID [HttpDelete("{id:long}")] + [OperationLog("角色管理", "删除角色")] public async Task Delete(long id) { await _roleService.DeleteAsync(id); @@ -111,6 +115,7 @@ public class RoleController : ControllerBase /// 角色ID /// 分配请求 [HttpPut("{id:long}/menus")] + [OperationLog("角色管理", "分配菜单")] public async Task AssignMenus(long id, [FromBody] AssignMenusRequest request) { await _roleService.AssignMenusAsync(id, request.MenuIds); @@ -135,6 +140,7 @@ public class RoleController : ControllerBase /// 角色ID /// 分配请求 [HttpPut("{id:long}/permissions")] + [OperationLog("角色管理", "分配权限")] public async Task AssignPermissions(long id, [FromBody] AssignPermissionsRequest request) { await _roleService.AssignPermissionsAsync(id, request.PermissionCodes); diff --git a/server/HoneyBox/src/HoneyBox.Admin/Extensions/ServiceCollectionExtensions.cs b/server/HoneyBox/src/HoneyBox.Admin/Extensions/ServiceCollectionExtensions.cs index a60df2c8..9fb798ec 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Extensions/ServiceCollectionExtensions.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Extensions/ServiceCollectionExtensions.cs @@ -74,9 +74,8 @@ public static class ServiceCollectionExtensions // 启用静态文件服务 app.UseStaticFiles(); - // 启用认证和授权 + // 启用认证 (Authorization 需要在 UseRouting 之后调用,所以不在这里调用) app.UseAuthentication(); - app.UseAuthorization(); return app; } diff --git a/server/HoneyBox/src/HoneyBox.Admin/Program.cs b/server/HoneyBox/src/HoneyBox.Admin/Program.cs index ba04bad1..63fd546c 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/Program.cs +++ b/server/HoneyBox/src/HoneyBox.Admin/Program.cs @@ -40,11 +40,14 @@ if (app.Environment.IsDevelopment()) app.MapScalarApiReference(); } -// Use HoneyBox Admin middleware (Static files, Authentication, Authorization) +// Use HoneyBox Admin middleware (Static files, Authentication) app.UseHoneyBoxAdmin(); app.UseRouting(); +// Authorization must be between UseRouting and MapControllers +app.UseAuthorization(); + app.MapControllers(); // SPA fallback - serve index.html for non-API routes diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/menu.ts b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/menu.ts index 05dfb6fb..f68f3096 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/menu.ts +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/menu.ts @@ -17,6 +17,36 @@ export interface MenuTree { children: MenuTree[] } +// 创建菜单请求 +export interface CreateMenuRequest { + parentId: number + name: string + path?: string + component?: string + icon?: string + menuType: number // 1=目录, 2=菜单, 3=按钮 + permission?: string + sortOrder: number + status: number // 1=显示, 0=隐藏 + isExternal: boolean + isCache: boolean +} + +// 更新菜单请求 +export interface UpdateMenuRequest { + parentId: number + name: string + path?: string + component?: string + icon?: string + menuType: number // 1=目录, 2=菜单, 3=按钮 + permission?: string + sortOrder: number + status: number // 1=显示, 0=隐藏 + isExternal: boolean + isCache: boolean +} + // 获取用户菜单 export function getUserMenus(): Promise> { return request({ @@ -32,3 +62,37 @@ export function getMenuTree(): Promise> { method: 'get' }) } + +// 获取菜单详情 +export function getMenuById(id: number): Promise> { + return request({ + url: `/admin/menus/${id}`, + method: 'get' + }) +} + +// 创建菜单 +export function createMenu(data: CreateMenuRequest): Promise> { + return request({ + url: '/admin/menus', + method: 'post', + data + }) +} + +// 更新菜单 +export function updateMenu(id: number, data: UpdateMenuRequest): Promise> { + return request({ + url: `/admin/menus/${id}`, + method: 'put', + data + }) +} + +// 删除菜单 +export function deleteMenu(id: number): Promise> { + return request({ + url: `/admin/menus/${id}`, + method: 'delete' + }) +} diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/router/index.ts b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/router/index.ts index 008b1613..ec4aa781 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/router/index.ts +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/router/index.ts @@ -16,6 +16,32 @@ const constantRoutes: RouteRecordRaw[] = [ name: 'NotFound', component: () => import('@/views/error/404.vue'), meta: { title: '404' } + }, + { + path: '/profile', + name: 'Profile', + component: () => import('@/layout/index.vue'), + meta: { title: '个人中心' }, + children: [ + { + path: '', + component: () => import('@/views/profile/index.vue'), + meta: { title: '个人中心' } + } + ] + }, + { + path: '/password', + name: 'Password', + component: () => import('@/layout/index.vue'), + meta: { title: '修改密码' }, + children: [ + { + path: '', + component: () => import('@/views/password/index.vue'), + meta: { title: '修改密码' } + } + ] } ] diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/password/index.vue b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/password/index.vue new file mode 100644 index 00000000..7c6e37fd --- /dev/null +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/password/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/profile/index.vue b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/profile/index.vue new file mode 100644 index 00000000..e4990ea9 --- /dev/null +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/profile/index.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/system/menu/index.vue b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/system/menu/index.vue index 6f2ee57f..264ee446 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/system/menu/index.vue +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/system/menu/index.vue @@ -49,14 +49,77 @@ + + + + + + + + + + 目录 + 菜单 + 按钮 + + + + + + + + + + + + + + + + + + + + + + + + + + 显示 + 隐藏 + + + + + + + + + + + +