3333
This commit is contained in:
parent
e4505e0370
commit
e0a4bc4ba2
188
.kiro/specs/menu-crud/design.md
Normal file
188
.kiro/specs/menu-crud/design.md
Normal file
|
|
@ -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<ApiResponse<number>>
|
||||
export function updateMenu(id: number, data: UpdateMenuRequest): Promise<ApiResponse<void>>
|
||||
export function deleteMenu(id: number): Promise<ApiResponse<void>>
|
||||
export function getMenuById(id: number): Promise<ApiResponse<MenuTree>>
|
||||
```
|
||||
|
||||
### 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. **边界测试**:测试空数据、特殊字符等边界情况
|
||||
84
.kiro/specs/menu-crud/requirements.md
Normal file
84
.kiro/specs/menu-crud/requirements.md
Normal file
|
|
@ -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
|
||||
84
.kiro/specs/menu-crud/tasks.md
Normal file
84
.kiro/specs/menu-crud/tasks.md
Normal file
|
|
@ -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)显示权限标识
|
||||
|
|
@ -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
|
|||
/// <param name="request">创建请求</param>
|
||||
/// <returns>新管理员ID</returns>
|
||||
[HttpPost]
|
||||
[OperationLog("管理员管理", "创建管理员")]
|
||||
public async Task<ApiResponse<long>> Create([FromBody] CreateAdminUserRequest request)
|
||||
{
|
||||
var createdBy = GetCurrentUserId();
|
||||
|
|
@ -67,6 +69,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">更新请求</param>
|
||||
[HttpPut("{id:long}")]
|
||||
[OperationLog("管理员管理", "更新管理员")]
|
||||
public async Task<ApiResponse> Update(long id, [FromBody] UpdateAdminUserRequest request)
|
||||
{
|
||||
await _adminUserService.UpdateAsync(id, request);
|
||||
|
|
@ -78,6 +81,7 @@ public class AdminUserController : ControllerBase
|
|||
/// </summary>
|
||||
/// <param name="id">管理员ID</param>
|
||||
[HttpDelete("{id:long}")]
|
||||
[OperationLog("管理员管理", "删除管理员")]
|
||||
public async Task<ApiResponse> Delete(long id)
|
||||
{
|
||||
await _adminUserService.DeleteAsync(id);
|
||||
|
|
@ -102,6 +106,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/roles")]
|
||||
[OperationLog("管理员管理", "分配角色")]
|
||||
public async Task<ApiResponse> AssignRoles(long id, [FromBody] AssignRolesRequest request)
|
||||
{
|
||||
await _adminUserService.AssignRolesAsync(id, request.RoleIds);
|
||||
|
|
@ -126,6 +131,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/menus")]
|
||||
[OperationLog("管理员管理", "分配专属菜单")]
|
||||
public async Task<ApiResponse> AssignMenus(long id, [FromBody] AssignUserMenusRequest request)
|
||||
{
|
||||
await _adminUserService.AssignMenusAsync(id, request.MenuIds);
|
||||
|
|
@ -138,6 +144,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/department")]
|
||||
[OperationLog("管理员管理", "分配部门")]
|
||||
public async Task<ApiResponse> AssignDepartment(long id, [FromBody] AssignDepartmentRequest request)
|
||||
{
|
||||
await _adminUserService.AssignDepartmentAsync(id, request.DepartmentId);
|
||||
|
|
@ -150,6 +157,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">状态请求</param>
|
||||
[HttpPut("{id:long}/status")]
|
||||
[OperationLog("管理员管理", "设置状态")]
|
||||
public async Task<ApiResponse> SetStatus(long id, [FromBody] SetStatusRequest request)
|
||||
{
|
||||
await _adminUserService.SetStatusAsync(id, request.Status == 1);
|
||||
|
|
@ -162,6 +170,7 @@ public class AdminUserController : ControllerBase
|
|||
/// <param name="id">管理员ID</param>
|
||||
/// <param name="request">重置密码请求</param>
|
||||
[HttpPut("{id:long}/reset-password")]
|
||||
[OperationLog("管理员管理", "重置密码")]
|
||||
public async Task<ApiResponse> ResetPassword(long id, [FromBody] ResetPasswordRequest request)
|
||||
{
|
||||
await _adminUserService.ResetPasswordAsync(id, request.NewPassword);
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <returns>登录响应</returns>
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
[OperationLog("认证", "登录")]
|
||||
public async Task<ApiResponse<LoginResponse>> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
var ipAddress = GetClientIpAddress();
|
||||
|
|
@ -56,6 +58,7 @@ public class AuthController : ControllerBase
|
|||
/// <param name="request">修改密码请求</param>
|
||||
[HttpPut("password")]
|
||||
[Authorize]
|
||||
[OperationLog("认证", "修改密码")]
|
||||
public async Task<ApiResponse> ChangePassword([FromBody] ChangePasswordRequest request)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
|
@ -68,6 +71,7 @@ public class AuthController : ControllerBase
|
|||
/// </summary>
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
[OperationLog("认证", "退出登录")]
|
||||
public async Task<ApiResponse> Logout()
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <param name="request">创建请求</param>
|
||||
/// <returns>新部门ID</returns>
|
||||
[HttpPost]
|
||||
[OperationLog("部门管理", "创建部门")]
|
||||
public async Task<ApiResponse<long>> Create([FromBody] CreateDepartmentRequest request)
|
||||
{
|
||||
var id = await _departmentService.CreateAsync(request);
|
||||
|
|
@ -65,6 +67,7 @@ public class DepartmentController : ControllerBase
|
|||
/// <param name="id">部门ID</param>
|
||||
/// <param name="request">更新请求</param>
|
||||
[HttpPut("{id:long}")]
|
||||
[OperationLog("部门管理", "更新部门")]
|
||||
public async Task<ApiResponse> Update(long id, [FromBody] UpdateDepartmentRequest request)
|
||||
{
|
||||
await _departmentService.UpdateAsync(id, request);
|
||||
|
|
@ -76,6 +79,7 @@ public class DepartmentController : ControllerBase
|
|||
/// </summary>
|
||||
/// <param name="id">部门ID</param>
|
||||
[HttpDelete("{id:long}")]
|
||||
[OperationLog("部门管理", "删除部门")]
|
||||
public async Task<ApiResponse> Delete(long id)
|
||||
{
|
||||
await _departmentService.DeleteAsync(id);
|
||||
|
|
@ -88,6 +92,7 @@ public class DepartmentController : ControllerBase
|
|||
/// <param name="id">部门ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/menus")]
|
||||
[OperationLog("部门管理", "分配菜单")]
|
||||
public async Task<ApiResponse> AssignMenus(long id, [FromBody] AssignDepartmentMenusRequest request)
|
||||
{
|
||||
await _departmentService.AssignMenusAsync(id, request.MenuIds);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public class MenuController : ControllerBase
|
|||
/// <param name="request">创建请求</param>
|
||||
/// <returns>新菜单ID</returns>
|
||||
[HttpPost]
|
||||
[OperationLog("菜单管理", "创建菜单")]
|
||||
public async Task<ApiResponse<long>> Create([FromBody] CreateMenuRequest request)
|
||||
{
|
||||
var id = await _menuService.CreateAsync(request);
|
||||
|
|
@ -66,6 +67,7 @@ public class MenuController : ControllerBase
|
|||
/// <param name="id">菜单ID</param>
|
||||
/// <param name="request">更新请求</param>
|
||||
[HttpPut("{id:long}")]
|
||||
[OperationLog("菜单管理", "更新菜单")]
|
||||
public async Task<ApiResponse> Update(long id, [FromBody] UpdateMenuRequest request)
|
||||
{
|
||||
await _menuService.UpdateAsync(id, request);
|
||||
|
|
@ -77,6 +79,7 @@ public class MenuController : ControllerBase
|
|||
/// </summary>
|
||||
/// <param name="id">菜单ID</param>
|
||||
[HttpDelete("{id:long}")]
|
||||
[OperationLog("菜单管理", "删除菜单")]
|
||||
public async Task<ApiResponse> Delete(long id)
|
||||
{
|
||||
await _menuService.DeleteAsync(id);
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <param name="request">创建请求</param>
|
||||
/// <returns>新角色ID</returns>
|
||||
[HttpPost]
|
||||
[OperationLog("角色管理", "创建角色")]
|
||||
public async Task<ApiResponse<long>> Create([FromBody] CreateRoleRequest request)
|
||||
{
|
||||
var id = await _roleService.CreateAsync(request);
|
||||
|
|
@ -76,6 +78,7 @@ public class RoleController : ControllerBase
|
|||
/// <param name="id">角色ID</param>
|
||||
/// <param name="request">更新请求</param>
|
||||
[HttpPut("{id:long}")]
|
||||
[OperationLog("角色管理", "更新角色")]
|
||||
public async Task<ApiResponse> Update(long id, [FromBody] UpdateRoleRequest request)
|
||||
{
|
||||
await _roleService.UpdateAsync(id, request);
|
||||
|
|
@ -87,6 +90,7 @@ public class RoleController : ControllerBase
|
|||
/// </summary>
|
||||
/// <param name="id">角色ID</param>
|
||||
[HttpDelete("{id:long}")]
|
||||
[OperationLog("角色管理", "删除角色")]
|
||||
public async Task<ApiResponse> Delete(long id)
|
||||
{
|
||||
await _roleService.DeleteAsync(id);
|
||||
|
|
@ -111,6 +115,7 @@ public class RoleController : ControllerBase
|
|||
/// <param name="id">角色ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/menus")]
|
||||
[OperationLog("角色管理", "分配菜单")]
|
||||
public async Task<ApiResponse> AssignMenus(long id, [FromBody] AssignMenusRequest request)
|
||||
{
|
||||
await _roleService.AssignMenusAsync(id, request.MenuIds);
|
||||
|
|
@ -135,6 +140,7 @@ public class RoleController : ControllerBase
|
|||
/// <param name="id">角色ID</param>
|
||||
/// <param name="request">分配请求</param>
|
||||
[HttpPut("{id:long}/permissions")]
|
||||
[OperationLog("角色管理", "分配权限")]
|
||||
public async Task<ApiResponse> AssignPermissions(long id, [FromBody] AssignPermissionsRequest request)
|
||||
{
|
||||
await _roleService.AssignPermissionsAsync(id, request.PermissionCodes);
|
||||
|
|
|
|||
|
|
@ -74,9 +74,8 @@ public static class ServiceCollectionExtensions
|
|||
// 启用静态文件服务
|
||||
app.UseStaticFiles();
|
||||
|
||||
// 启用认证和授权
|
||||
// 启用认证 (Authorization 需要在 UseRouting 之后调用,所以不在这里调用)
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<ApiResponse<MenuTree[]>> {
|
||||
return request({
|
||||
|
|
@ -32,3 +62,37 @@ export function getMenuTree(): Promise<ApiResponse<MenuTree[]>> {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取菜单详情
|
||||
export function getMenuById(id: number): Promise<ApiResponse<MenuTree>> {
|
||||
return request({
|
||||
url: `/admin/menus/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建菜单
|
||||
export function createMenu(data: CreateMenuRequest): Promise<ApiResponse<number>> {
|
||||
return request({
|
||||
url: '/admin/menus',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新菜单
|
||||
export function updateMenu(id: number, data: UpdateMenuRequest): Promise<ApiResponse<null>> {
|
||||
return request({
|
||||
url: `/admin/menus/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除菜单
|
||||
export function deleteMenu(id: number): Promise<ApiResponse<null>> {
|
||||
return request({
|
||||
url: `/admin/menus/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: '修改密码' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>修改密码</span>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
style="max-width: 400px"
|
||||
>
|
||||
<el-form-item label="原密码" prop="oldPassword">
|
||||
<el-input
|
||||
v-model="formData.oldPassword"
|
||||
type="password"
|
||||
placeholder="请输入原密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input
|
||||
v-model="formData.newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="formData.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">
|
||||
确认修改
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { changePassword } from '@/api/auth'
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const formRules: FormRules = {
|
||||
oldPassword: [
|
||||
{ required: true, message: '请输入原密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (value !== formData.newPassword) {
|
||||
callback(new Error('两次输入密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await changePassword({
|
||||
oldPassword: formData.oldPassword,
|
||||
newPassword: formData.newPassword
|
||||
})
|
||||
ElMessage.success('密码修改成功')
|
||||
// 清空表单
|
||||
formData.oldPassword = ''
|
||||
formData.newPassword = ''
|
||||
formData.confirmPassword = ''
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '修改失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>个人中心</span>
|
||||
</template>
|
||||
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="用户名">{{ userInfo?.username }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ userInfo?.realName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ userInfo?.phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ userInfo?.email || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门">{{ userInfo?.departmentName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">
|
||||
<el-tag v-for="role in userInfo?.roles" :key="role" size="small" style="margin-right: 4px">
|
||||
{{ role }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -49,14 +49,77 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="550px" @close="resetForm">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
|
||||
<el-form-item label="上级菜单" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="formData.parentId"
|
||||
:data="menuTreeForSelect"
|
||||
:props="{ label: 'name', value: 'id', children: 'children' }"
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
placeholder="请选择上级菜单"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单类型" prop="menuType">
|
||||
<el-radio-group v-model="formData.menuType">
|
||||
<el-radio :value="1">目录</el-radio>
|
||||
<el-radio :value="2">菜单</el-radio>
|
||||
<el-radio :value="3">按钮</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入菜单名称" />
|
||||
</el-form-item>
|
||||
<!-- 目录和菜单显示图标 -->
|
||||
<el-form-item v-if="formData.menuType !== 3" label="图标" prop="icon">
|
||||
<el-input v-model="formData.icon" placeholder="请输入图标名称,如 Setting" />
|
||||
</el-form-item>
|
||||
<!-- 菜单类型显示路由路径和组件路径 -->
|
||||
<el-form-item v-if="formData.menuType === 2" label="路由路径" prop="path">
|
||||
<el-input v-model="formData.path" placeholder="请输入路由路径,如 /system/menu" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.menuType === 2" label="组件路径" prop="component">
|
||||
<el-input v-model="formData.component" placeholder="请输入组件路径,如 system/menu/index" />
|
||||
</el-form-item>
|
||||
<!-- 按钮类型显示权限标识 -->
|
||||
<el-form-item v-if="formData.menuType === 3" label="权限标识" prop="permission">
|
||||
<el-input v-model="formData.permission" placeholder="请输入权限标识,如 menu:create" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="formData.sortOrder" :min="0" :max="9999" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">显示</el-radio>
|
||||
<el-radio :value="0">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 菜单类型显示外链和缓存选项 -->
|
||||
<el-form-item v-if="formData.menuType === 2" label="是否外链" prop="isExternal">
|
||||
<el-switch v-model="formData.isExternal" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.menuType === 2" label="是否缓存" prop="isCache">
|
||||
<el-switch v-model="formData.isCache" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getMenuTree, type MenuTree } from '@/api/menu'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { getMenuTree, createMenu, updateMenu, deleteMenu, type MenuTree, type CreateMenuRequest, type UpdateMenuRequest } from '@/api/menu'
|
||||
|
||||
const loading = ref(false)
|
||||
const menuTree = ref<MenuTree[]>([])
|
||||
|
|
@ -67,6 +130,141 @@ const menuTypeMap: Record<number, { label: string; type: string }> = {
|
|||
3: { label: '按钮', type: 'warning' }
|
||||
}
|
||||
|
||||
// 表单相关状态
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const dialogTitle = computed(() => isEdit.value ? '编辑菜单' : '新增菜单')
|
||||
const formRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
parentId: 0,
|
||||
name: '',
|
||||
path: '',
|
||||
component: '',
|
||||
icon: '',
|
||||
menuType: 2,
|
||||
permission: '',
|
||||
sortOrder: 0,
|
||||
status: 1,
|
||||
isExternal: false,
|
||||
isCache: true
|
||||
})
|
||||
|
||||
// 用于选择父菜单的树(编辑时排除当前菜单及其子菜单)
|
||||
const menuTreeForSelect = computed(() => {
|
||||
if (!isEdit.value) {
|
||||
return [{ id: 0, name: '顶级菜单', children: menuTree.value }]
|
||||
}
|
||||
// 编辑时过滤掉自己及子菜单
|
||||
const filterTree = (nodes: MenuTree[]): MenuTree[] => {
|
||||
return nodes
|
||||
.filter(n => n.id !== formData.id)
|
||||
.map(n => ({ ...n, children: filterTree(n.children || []) }))
|
||||
}
|
||||
return [{ id: 0, name: '顶级菜单', children: filterTree(menuTree.value) }]
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules: FormRules = {
|
||||
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
|
||||
menuType: [{ required: true, message: '请选择菜单类型', trigger: 'change' }],
|
||||
path: [{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (formData.menuType === 2 && !value) {
|
||||
callback(new Error('菜单类型必须填写路由路径'))
|
||||
} else if (value && !formData.isExternal && !value.startsWith('/')) {
|
||||
callback(new Error('内部路由路径必须以 / 开头'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
component: [{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (formData.menuType === 2 && !value) {
|
||||
callback(new Error('菜单类型必须填写组件路径'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
permission: [{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (formData.menuType === 3 && !value) {
|
||||
callback(new Error('按钮类型必须填写权限标识'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
sortOrder: [{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: '排序值必须为非负整数',
|
||||
trigger: 'blur'
|
||||
}]
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
formData.id = 0
|
||||
formData.parentId = 0
|
||||
formData.name = ''
|
||||
formData.path = ''
|
||||
formData.component = ''
|
||||
formData.icon = ''
|
||||
formData.menuType = 2
|
||||
formData.permission = ''
|
||||
formData.sortOrder = 0
|
||||
formData.status = 1
|
||||
formData.isExternal = false
|
||||
formData.isCache = true
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const requestData: CreateMenuRequest | UpdateMenuRequest = {
|
||||
parentId: formData.parentId,
|
||||
name: formData.name,
|
||||
path: formData.path || undefined,
|
||||
component: formData.component || undefined,
|
||||
icon: formData.icon || undefined,
|
||||
menuType: formData.menuType,
|
||||
permission: formData.permission || undefined,
|
||||
sortOrder: formData.sortOrder,
|
||||
status: formData.status,
|
||||
isExternal: formData.isExternal,
|
||||
isCache: formData.isCache
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
await updateMenu(formData.id, requestData)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await createMenu(requestData)
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchData()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
|
|
@ -77,26 +275,65 @@ const fetchData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 新增菜单
|
||||
const handleAdd = () => {
|
||||
ElMessage.info('新增菜单功能待实现')
|
||||
resetForm()
|
||||
isEdit.value = false
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 添加子菜单
|
||||
const handleAddChild = (row: MenuTree) => {
|
||||
ElMessage.info(`添加 ${row.name} 的子菜单功能待实现`)
|
||||
resetForm()
|
||||
isEdit.value = false
|
||||
formData.parentId = row.id
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑菜单
|
||||
const handleEdit = (row: MenuTree) => {
|
||||
ElMessage.info(`编辑 ${row.name} 功能待实现`)
|
||||
resetForm()
|
||||
isEdit.value = true
|
||||
// 填充表单数据
|
||||
formData.id = row.id
|
||||
formData.parentId = row.parentId
|
||||
formData.name = row.name
|
||||
formData.path = row.path || ''
|
||||
formData.component = row.component || ''
|
||||
formData.icon = row.icon || ''
|
||||
formData.menuType = row.menuType
|
||||
formData.permission = row.permission || ''
|
||||
formData.sortOrder = row.sortOrder
|
||||
formData.status = row.status
|
||||
formData.isExternal = row.isExternal
|
||||
formData.isCache = row.isCache
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (row: MenuTree) => {
|
||||
// 构建确认消息,如果有子菜单则警告
|
||||
const hasChildren = row.children && row.children.length > 0
|
||||
const confirmMessage = hasChildren
|
||||
? `菜单 "${row.name}" 下有 ${row.children.length} 个子菜单,删除后子菜单也将被删除。确定要删除吗?`
|
||||
: `确定要删除菜单 "${row.name}" 吗?`
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要删除菜单 "${row.name}" 吗?`, '提示', {
|
||||
type: 'warning'
|
||||
await ElMessageBox.confirm(confirmMessage, '删除确认', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
ElMessage.info('删除功能待实现')
|
||||
} catch {
|
||||
// 取消删除
|
||||
|
||||
// 调用删除 API
|
||||
await deleteMenu(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
// 刷新数据
|
||||
fetchData()
|
||||
} catch (error: any) {
|
||||
// 用户取消删除时 error 为 'cancel'
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.message || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default defineConfig({
|
|||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5238',
|
||||
target: 'http://localhost:5000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user