189 lines
6.2 KiB
Markdown
189 lines
6.2 KiB
Markdown
# 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. **边界测试**:测试空数据、特殊字符等边界情况
|