提交 13a044a6 authored 作者: 陈泽健's avatar 陈泽健

feat(ui-test): 前端适配模块父子层级结构-树形选择器

- TypeScript 类型定义增加 parentId/children 字段
- API 层新增 buildModuleTree() 树形构建函数
- Cases.vue 筛选区和编辑弹窗模块选择器改为 el-tree-select
- Recorder.vue 模块选择器改为 el-tree-select
- Modules.vue 编辑弹窗支持选择父模块,含循环引用防护
- 新增 PRD 需求文档和执行计划文档
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 b9ba786b
# PRD 需求文档 - 前端适配模块父子层级结构
> **文档类型**: PRD 需求文档
> **创建日期**: 2026-07-22
> **作者**: czj
> **优先级**: P2
> **状态**: 待开发
---
## 一、需求背景
### 1.1 问题描述
后端已完成模块表 `parent_id` 字段的完整实现,支持模块的父子层级结构:
- **模型层**`Module` 模型新增 `parent_id` 字段和自引用关系(`parent`/`children`
- **Schema层**`ModuleCreate`/`ModuleUpdate`/`ModuleResponse` 增加 `parent_id`/`children` 字段
- **Service层**`create`/`update` 支持 `parent_id` 参数
- **API层**:响应中包含 `parentId` 字段(camelCase 转换)
但前端尚未适配,存在以下问题:
| 问题 | 影响 |
|------|------|
| TypeScript 类型定义缺少 `parentId`/`children` | 类型检查不完整,IDE 提示缺失 |
| 模块选择器是扁平下拉列表 | 无法直观展示模块层级关系 |
| Modules.vue 编辑弹窗不支持父模块选择 | 无法在 UI 层面管理模块层级 |
### 1.2 目标用户
- **测试人员**:需要按模块层级筛选和管理用例
- **管理员**:需要配置和维护模块父子层级关系
### 1.3 预期效果
1. 模块选择器展示层级结构,用户可快速定位模块
2. 编辑模块时可选择父模块,建立层级关系
3. 类型定义完整,代码可维护性提升
---
## 二、功能需求
### 2.1 需求清单
| 功能 | 描述 | 优先级 |
|------|------|--------|
| TypeScript 类型定义 | Module 接口增加 parentId/children 字段 | P0 |
| 模块选择器树形化 | Cases.vue、Recorder.vue 使用 el-tree-select | P0 |
| 编辑弹窗父模块选择 | Modules.vue 编辑弹窗支持选择父模块 | P0 |
| 循环引用防护 | 编辑时排除当前模块及其子模块 | P1 |
### 2.2 模块选择器树形化
**涉及页面**
| 页面 | 位置 | 说明 |
|------|------|------|
| Cases.vue | 筛选区模块下拉 | 筛选用例列表 |
| Cases.vue | 编辑弹窗模块选择 | 新建/编辑用例时选择所属模块 |
| Recorder.vue | 模块选择下拉 | 录制用例时选择所属模块 |
**UI 规范**
- 使用 Element Plus `el-tree-select` 组件
- props 配置:`{ label: 'name', value: 'id', children: 'children' }`
- `check-strictly`:允许选择任意层级(父级/子级均可)
- `clearable`:支持清空选择
- 树形展开,层级缩进显示
### 2.3 编辑弹窗父模块选择
**Modules.vue 编辑弹窗新增字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| 父模块 | el-tree-select | 可选,选择父模块建立层级关系 |
**循环引用防护**
- 编辑模块时,父模块选择器需排除:
1. 当前模块自身
2. 当前模块的所有子模块(递归)
防止形成循环引用:A → B → A 或 A → B → C → A
### 2.4 TypeScript 类型定义
**Module 接口扩展**
```typescript
export interface Module {
id: string
name: string
description: string
icon: string
order: number
moduleType?: string
parentId?: string | null // 新增:父模块ID
children?: Module[] // 新增:子模块列表
caseCount?: number
passRate?: number
createdAt: string
updatedAt: string
}
```
**CreateModuleRequest / UpdateModuleRequest 扩展**
```typescript
parent_id?: string | null // 新增:父模块ID
```
---
## 三、技术方案
### 3.1 数据流
```
后端 API (/api/modules)
↓ 返回扁平列表(含 parentId)
前端 API 层 (buildTree 函数)
↓ 构建树形结构
页面组件 (el-tree-select)
↓ 展示层级
```
### 3.2 树形数据构建
```typescript
/**
* 将扁平模块列表构建为树形结构
* @param items 扁平模块列表
* @returns 树形结构数据
*/
function buildTree(items: Module[]): Module[] {
const map = new Map<string, Module>()
const roots: Module[] = []
// 建立映射
items.forEach(item => map.set(item.id, { ...item, children: [] }))
// 构建树
items.forEach(item => {
const node = map.get(item.id)!
if (item.parentId && map.has(item.parentId)) {
const parent = map.get(item.parentId)!
if (!parent.children) parent.children = []
parent.children.push(node)
} else {
roots.push(node)
}
})
return roots
}
```
### 3.3 前端文件改动
| 文件 | 改动 |
|------|------|
| `frontend/src/types/module.ts` | 增加 parentId/children 字段 |
| `frontend/src/api/modules.ts` | 新增 buildTree 辅助函数并导出 |
| `frontend/src/views/Cases.vue` | el-select → el-tree-select(2处) |
| `frontend/src/views/Recorder.vue` | el-select → el-tree-select(1处) |
| `frontend/src/views/Modules.vue` | 编辑弹窗增加父模块选择字段 |
---
## 四、界面设计
### 4.1 模块选择器(树形)
```
┌─────────────────────────────────────┐
│ 选择模块 ▼ │
├─────────────────────────────────────┤
│ ▼ 预定2.0 │
│ └── 会议管理 │
│ ▼ 数据统计 │
│ ├── 数据分析 │
│ ├── 管理看板 │
│ └── 运维管理 │
│ ▼ 集控控制 │
│ ├── 设备列表 │
│ └── 远程控制 │
│ ... │
└─────────────────────────────────────┘
```
### 4.2 模块编辑弹窗(新增父模块字段)
```
┌─────────────────────────────────────┐
│ 编辑模块 ✕ │
├─────────────────────────────────────┤
│ 模块名称: [会议管理 ] │
│ 模块类型: [标准模块 ▼ ] │
│ 父模块: [预定2.0 ▼ ] │ ← 新增
│ 模块描述: [ ] │
│ [ ] │
│ 模块图标: [folder ] │
├─────────────────────────────────────┤
│ [取消] [确定] │
└─────────────────────────────────────┘
```
---
## 五、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| TypeScript 编译 | `npm run build` 无类型错误 |
| Cases.vue 筛选模块选择器 | 展示树形层级,可筛选 |
| Cases.vue 编辑弹窗模块选择器 | 展示树形层级,可选择任意模块 |
| Recorder.vue 模块选择器 | 展示树形层级 |
| Modules.vue 编辑弹窗父模块选择 | 可选择父模块,保存后层级正确 |
| 循环引用防护 | 编辑时不能选择自己或子模块作为父模块 |
| 无父模块的叶子模块 | 正常显示在选择器中 |
| 清空父模块 | 可清空,模块变为顶级模块 |
---
## 六、相关文档
- `backend/app/models/module.py` - 模块模型(已实现 parent_id)
- `backend/app/schemas/module.py` - 模块 Schema(已实现 parentId/children)
- `frontend/src/types/module.ts` - 前端类型定义(待更新)
- `frontend/src/views/Cases.vue` - 用例管理页面
- `frontend/src/views/Recorder.vue` - 用例录制器页面
- `frontend/src/views/Modules.vue` - 模块管理页面
---
*本文档由 Claude Code 生成,遵循项目 PRD 文档规范。*
\ No newline at end of file
# 执行计划 - 前端适配模块父子层级结构
> **文档类型**: 执行计划文档
> **来源文档**: `_PRD_需求文档_前端适配模块父子层级.md`
> **创建日期**: 2026-07-22
> **作者**: czj
> **预计工期**: 2 小时
> **实施状态**: ⏳ 待实施
---
## 一、执行概述
### 1.1 目标
前端适配后端已实现的模块父子层级结构,包括类型定义、树形选择器、编辑弹窗父模块选择。
### 1.2 改动范围
| 层级 | 文件数 | 改动类型 |
|------|--------|----------|
| 类型定义 | 1 | Module 接口增加字段 |
| API 层 | 1 | 新增 buildTree 函数 |
| 页面组件 | 3 | el-select → el-tree-select |
| 新增代码 | - | buildTree 辅助函数 |
---
## 二、执行步骤
### Phase 1: TypeScript 类型定义 (预计 10 分钟)
#### Step 1.1: 更新 Module 接口
**文件**: `frontend/src/types/module.ts`
```typescript
/** 测试模块接口 */
export interface Module {
/** 模块唯一标识 */
id: string
/** 模块名称 */
name: string
/** 模块描述 */
description: string
/** 模块图标标识 */
icon: string
/** 排序序号 */
order: number
/** 模块类型 standard/custom */
moduleType?: string
/** 父模块ID(支持层级结构) */
parentId?: string | null
/** 子模块列表(树形结构) */
children?: Module[]
/** 用例数量(扩展字段) */
caseCount?: number
/** 通过率(扩展字段) */
passRate?: number
/** 创建时间 */
createdAt: string
/** 更新时间 */
updatedAt: string
}
/** 创建模块请求参数 */
export interface CreateModuleRequest {
/** 模块名称 */
name: string
/** 模块描述 */
description?: string
/** 模块图标 */
icon?: string
/** 模块类型 standard/custom */
module_type?: string
/** 父模块ID */
parent_id?: string | null
}
/** 更新模块请求参数 */
export interface UpdateModuleRequest {
name?: string
description?: string
icon?: string
order?: number
module_type?: string
/** 父模块ID(空字符串表示清除父模块) */
parent_id?: string | null
}
```
---
### Phase 2: API 层增加 buildTree 函数 (预计 10 分钟)
#### Step 2.1: 新增并导出 buildTree 函数
**文件**: `frontend/src/api/modules.ts`
在文件末尾添加:
```typescript
/**
* 将扁平模块列表构建为树形结构
*
* @param items 扁平模块列表
* @returns 树形结构数据
*/
export function buildModuleTree(items: Module[]): Module[] {
const map = new Map<string, Module>()
const roots: Module[] = []
// 建立映射:先复制并初始化 children
items.forEach(item => {
map.set(item.id, { ...item, children: [] })
})
// 构建树:根据 parentId 归类到父模块的 children 中
items.forEach(item => {
const node = map.get(item.id)!
if (item.parentId && map.has(item.parentId)) {
const parent = map.get(item.parentId)!
if (!parent.children) {
parent.children = []
}
parent.children.push(node)
} else {
// 无父模块或父模块不存在,作为根节点
roots.push(node)
}
})
return roots
}
```
---
### Phase 3: Cases.vue 模块选择器树形化 (预计 20 分钟)
#### Step 3.1: 筛选区模块选择器
**文件**: `frontend/src/views/Cases.vue`
**原代码**(约第14-27行):
```vue
<el-select
v-model="filterModuleId"
placeholder="选择模块"
clearable
style="width: 200px"
@change="loadCases"
>
<el-option
v-for="m in modules"
:key="m.id"
:label="m.name"
:value="m.id"
/>
</el-select>
```
**改为**
```vue
<el-tree-select
v-model="filterModuleId"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
clearable
check-strictly
style="width: 200px"
@change="loadCases"
/>
```
#### Step 3.2: 编辑弹窗模块选择器
**原代码**(约第230-237行):
```vue
<el-form-item label="所属模块" prop="module_id">
<el-select v-model="editForm.module_id" placeholder="选择模块" style="width: 100%">
<el-option
v-for="m in modules"
:key="m.id"
:label="m.name"
:value="m.id"
/>
</el-select>
</el-form-item>
```
**改为**
```vue
<el-form-item label="所属模块" prop="module_id">
<el-tree-select
v-model="editForm.module_id"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
check-strictly
style="width: 100%"
/>
</el-form-item>
```
#### Step 3.3: 增加 moduleTreeData 计算属性
`<script setup>` 中增加:
```typescript
import { buildModuleTree } from '@/api/modules'
// 模块树形数据(用于 el-tree-select)
const moduleTreeData = computed(() => buildModuleTree(modules.value))
```
---
### Phase 4: Recorder.vue 模块选择器树形化 (预计 15 分钟)
#### Step 4.1: 模块选择器
**文件**: `frontend/src/views/Recorder.vue`
找到模块选择器(约第31-44行),将 `el-select` 改为 `el-tree-select`
```vue
<el-tree-select
v-model="recorderForm.moduleId"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
check-strictly
style="width: 100%"
/>
```
#### Step 4.2: 增加 moduleTreeData 计算属性
```typescript
import { buildModuleTree } from '@/api/modules'
const moduleTreeData = computed(() => buildModuleTree(modules.value))
```
---
### Phase 5: Modules.vue 编辑弹窗支持父模块选择 (预计 25 分钟)
#### Step 5.1: formData 增加 parent_id 字段
**文件**: `frontend/src/views/Modules.vue`
`formData` 定义中增加:
```typescript
const formData = ref<CreateModuleRequest>({
name: '',
description: '',
icon: 'folder',
module_type: 'standard',
parent_id: null // 新增
})
```
#### Step 5.2: 编辑弹窗增加父模块字段
在编辑弹窗的表单中增加:
```vue
<el-form-item label="父模块">
<el-tree-select
v-model="formData.parent_id"
:data="parentModuleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择父模块(可选)"
clearable
check-strictly
style="width: 100%"
/>
</el-form-item>
```
#### Step 5.3: 增加 parentModuleTreeData 计算属性(排除当前模块及其子模块)
```typescript
import { buildModuleTree } from '@/api/modules'
// 编辑时排除当前模块及其子模块(防循环引用)
const parentModuleTreeData = computed(() => {
if (!isEdit.value || !editingId.value) {
return buildModuleTree(modules.value)
}
// 递归获取所有子模块ID
const getChildIds = (parentId: string): string[] => {
const children = modules.value.filter(m => m.parentId === parentId)
return children.flatMap(c => [c.id, ...getChildIds(c.id)])
}
const excludeIds = new Set([editingId.value, ...getChildIds(editingId.value)])
const filteredModules = modules.value.filter(m => !excludeIds.has(m.id))
return buildModuleTree(filteredModules)
})
```
#### Step 5.4: showEditDialog 填充 parent_id
```typescript
const showEditDialog = (module: Module) => {
isEdit.value = true
editingId.value = module.id
formData.value = {
name: module.name,
description: module.description,
icon: module.icon,
module_type: module.moduleType || 'standard',
parent_id: module.parentId || null // 新增
}
dialogVisible.value = true
}
```
#### Step 5.5: showCreateDialog 初始化 parent_id
```typescript
const showCreateDialog = () => {
isEdit.value = false
editingId.value = ''
formData.value = {
name: '',
description: '',
icon: 'folder',
module_type: currentType.value,
parent_id: null // 新增
}
dialogVisible.value = true
}
```
---
### Phase 6: 验证 (预计 10 分钟)
#### Step 6.1: TypeScript 编译检查
```bash
cd frontend
npm run build
```
预期:无类型错误。
#### Step 6.2: 功能验证清单
| 测试项 | 操作 | 预期结果 |
|--------|------|----------|
| Cases.vue 筛选模块 | 打开用例管理,点击模块筛选下拉 | 展示树形层级 |
| Cases.vue 编辑用例 | 编辑用例,查看模块选择器 | 展示树形层级 |
| Recorder.vue 模块选择 | 打开录制器,查看模块选择器 | 展示树形层级 |
| Modules.vue 编辑父模块 | 编辑模块,选择父模块,保存 | 保存成功,刷新后层级正确 |
| 循环引用防护 | 编辑模块,查看父模块选择器 | 不包含自己及子模块 |
| 清空父模块 | 编辑模块,清空父模块,保存 | 模块变为顶级模块 |
---
## 三、风险评估
| 风险项 | 可能性 | 影响 | 缓解措施 |
|--------|--------|------|----------|
| el-tree-select 兼容性 | 低 | 中 | Element Plus 2.2+ 支持,项目已满足 |
| 大量模块时性能 | 低 | 低 | 虚拟滚动 + 层级折叠 |
| 循环引用遗漏 | 中 | 高 | edit 时严格排除当前模块及其子模块 |
| parentId 字段名不一致 | 低 | 中 | 后端返回 camelCase(parentId),前端直接使用 |
---
## 四、验收清单
- [ ] TypeScript 类型定义更新
- [ ] buildModuleTree 函数实现并导出
- [ ] Cases.vue 筛选区模块选择器树形化
- [ ] Cases.vue 编辑弹窗模块选择器树形化
- [ ] Recorder.vue 模块选择器树形化
- [ ] Modules.vue 编辑弹窗增加父模块选择
- [ ] 循环引用防护实现
- [ ] TypeScript 编译通过
- [ ] 功能验证通过
---
## 五、相关文件
| 文件 | 用途 |
|------|------|
| `frontend/src/types/module.ts` | TypeScript 类型定义 |
| `frontend/src/api/modules.ts` | API 层 + buildModuleTree |
| `frontend/src/views/Cases.vue` | 用例管理页面 |
| `frontend/src/views/Recorder.vue` | 用例录制器页面 |
| `frontend/src/views/Modules.vue` | 模块管理页面 |
---
*本文档由 Claude Code 生成,遵循项目执行计划文档规范。*
\ No newline at end of file
......@@ -63,3 +63,43 @@ export const moduleApi = {
return response.data
}
}
/**
* 将扁平模块列表构建为树形结构
*
* 用于 el-tree-select 组件的数据源,将后端返回的扁平模块列表
* 根据 parentId 字段构建为父子层级结构。
*
* @param items 扁平模块列表
* @returns 树形结构数据
*
* @example
* const tree = buildModuleTree(modules)
* // tree: [{ id: '1', name: '预定2.0', children: [{ id: '2', name: '会议管理', children: [] }] }]
*/
export function buildModuleTree(items: Module[]): Module[] {
const map = new Map<string, Module>()
const roots: Module[] = []
// 建立映射:先复制并初始化 children
items.forEach(item => {
map.set(item.id, { ...item, children: [] })
})
// 构建树:根据 parentId 归类到父模块的 children 中
items.forEach(item => {
const node = map.get(item.id)!
if (item.parentId && map.has(item.parentId)) {
const parent = map.get(item.parentId)!
if (!parent.children) {
parent.children = []
}
parent.children.push(node)
} else {
// 无父模块或父模块不存在,作为根节点
roots.push(node)
}
})
return roots
}
\ No newline at end of file
......@@ -20,6 +20,10 @@ export interface Module {
order: number
/** 模块类型 standard/custom */
moduleType?: string
/** 父模块ID(支持层级结构) */
parentId?: string | null
/** 子模块列表(树形结构) */
children?: Module[]
/** 用例数量(扩展字段) */
caseCount?: number
/** 通过率(扩展字段) */
......@@ -40,6 +44,8 @@ export interface CreateModuleRequest {
icon?: string
/** 模块类型 standard/custom */
module_type?: string
/** 父模块ID */
parent_id?: string | null
}
/** 更新模块请求参数 */
......@@ -49,6 +55,8 @@ export interface UpdateModuleRequest {
icon?: string
order?: number
module_type?: string
/** 父模块ID(空字符串表示清除父模块) */
parent_id?: string | null
}
/** 模块列表响应 */
......
......@@ -11,20 +11,16 @@
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<el-select
<el-tree-select
v-model="filterModuleId"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
clearable
check-strictly
style="width: 200px"
@change="loadCases"
>
<el-option
v-for="m in modules"
:key="m.id"
:label="m.name"
:value="m.id"
/>
</el-select>
<el-input
v-model="keyword"
placeholder="搜索用例名称"
......@@ -228,14 +224,14 @@
<el-input v-model="editForm.name" placeholder="请输入用例名称" />
</el-form-item>
<el-form-item label="所属模块" prop="module_id">
<el-select v-model="editForm.module_id" placeholder="选择模块" style="width: 100%">
<el-option
v-for="m in modules"
:key="m.id"
:label="m.name"
:value="m.id"
<el-tree-select
v-model="editForm.module_id"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
check-strictly
style="width: 100%"
/>
</el-select>
</el-form-item>
<el-form-item label="用例类型">
<el-select v-model="editForm.case_type" style="width: 100%">
......@@ -465,7 +461,7 @@ import {
Search, Plus, Upload, Download, VideoPlay, Document
} from '@element-plus/icons-vue'
import { caseApi } from '@/api/cases'
import { moduleApi } from '@/api/modules'
import { moduleApi, buildModuleTree } from '@/api/modules'
import { executionApi } from '@/api/executions'
import CaseStepEditor from '@/components/CaseStepEditor.vue'
......@@ -505,6 +501,9 @@ const selectedCases = ref<any[]>([])
// 模块列表
const modules = ref<any[]>([])
// 模块树形数据(用于 el-tree-select)
const moduleTreeData = computed(() => buildModuleTree(modules.value))
// 编辑弹窗
const editDialogVisible = ref(false)
const editMode = ref<'create' | 'edit'>('create')
......
......@@ -75,6 +75,17 @@
<el-option label="项目定制模块" value="custom" />
</el-select>
</el-form-item>
<el-form-item label="父模块">
<el-tree-select
v-model="formData.parent_id"
:data="parentModuleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择父模块(可选)"
clearable
check-strictly
style="width: 100%"
/>
</el-form-item>
<el-form-item label="模块描述">
<el-input
v-model="formData.description"
......@@ -109,7 +120,7 @@ import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Folder, Document, TrendCharts } from '@element-plus/icons-vue'
import type { Module, CreateModuleRequest, UpdateModuleRequest } from '@/types/module'
import { moduleApi } from '@/api/modules'
import { moduleApi, buildModuleTree } from '@/api/modules'
const router = useRouter()
const route = useRoute()
......@@ -125,11 +136,30 @@ const editingId = ref('')
/** 当前模块类型(来自路由参数,默认 standard) */
const currentType = computed(() => (route.params.type as string) || 'standard')
/** 父模块树形数据(编辑时排除当前模块及其子模块,防循环引用) */
const parentModuleTreeData = computed(() => {
if (!isEdit.value || !editingId.value) {
return buildModuleTree(modules.value)
}
// 递归获取所有子模块ID
const getChildIds = (parentId: string): string[] => {
const children = modules.value.filter(m => m.parentId === parentId)
return children.flatMap(c => [c.id, ...getChildIds(c.id)])
}
const excludeIds = new Set([editingId.value, ...getChildIds(editingId.value)])
const filteredModules = modules.value.filter(m => !excludeIds.has(m.id))
return buildModuleTree(filteredModules)
})
const formData = ref<CreateModuleRequest>({
name: '',
description: '',
icon: 'folder',
module_type: 'standard'
module_type: 'standard',
parent_id: null
})
// ==================== 方法定义 ====================
......@@ -150,7 +180,7 @@ const loadModules = async () => {
const showCreateDialog = () => {
isEdit.value = false
editingId.value = ''
formData.value = { name: '', description: '', icon: 'folder', module_type: currentType.value }
formData.value = { name: '', description: '', icon: 'folder', module_type: currentType.value, parent_id: null }
dialogVisible.value = true
}
......@@ -162,7 +192,8 @@ const showEditDialog = (module: Module) => {
name: module.name,
description: module.description,
icon: module.icon,
module_type: module.moduleType || 'standard'
module_type: module.moduleType || 'standard',
parent_id: module.parentId || null
}
dialogVisible.value = true
}
......
......@@ -29,19 +29,15 @@
/>
</el-form-item>
<el-form-item label="所属模块" required>
<el-select
<el-tree-select
v-model="recorderForm.moduleId"
:data="moduleTreeData"
:props="{ label: 'name', value: 'id', children: 'children' }"
placeholder="选择模块"
check-strictly
style="width: 200px"
:disabled="isRecording"
>
<el-option
v-for="m in modules"
:key="m.id"
:label="m.name"
:value="m.id"
/>
</el-select>
</el-form-item>
<el-form-item label="用例名称" required>
<el-input
......@@ -256,7 +252,7 @@
* 6. 保存为测试用例
*/
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
......@@ -266,7 +262,7 @@ import {
FolderChecked,
} from '@element-plus/icons-vue'
import type { Module } from '@/types/module'
import { moduleApi } from '@/api/modules'
import { moduleApi, buildModuleTree } from '@/api/modules'
import { recorderApi } from '@/api/recorder'
const router = useRouter()
......@@ -284,6 +280,9 @@ const recorderForm = ref({
/** 模块列表 */
const modules = ref<Module[]>([])
/** 模块树形数据(用于 el-tree-select) */
const moduleTreeData = computed(() => buildModuleTree(modules.value))
/** 是否录制中 */
const isRecording = ref(false)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论