权限问题修复,标签bug修复

main
risingLee 2026-01-08 11:39:47 +08:00
parent 4b21dcd1ce
commit 51dacb8746
11 changed files with 578 additions and 62 deletions

View File

@ -0,0 +1,184 @@
# 设计文档
## 概述
本设计为测试流程列表页面添加颜色图例功能。图例将显示在表格上方,展示所有测试分类及其对应的颜色,帮助用户快速理解表格中彩色标签的含义。
## 架构
### 前端组件架构
```
test_flow/index.vue
├── 查询表单区域
├── 操作按钮区域
├── 颜色图例组件 (新增)
│ └── 分类颜色映射显示
├── 数据表格
│ └── 测试项列(使用彩色标签)
└── 分页组件
```
### 颜色分配策略
使用一致的颜色分配算法,确保同一测试分类在所有地方都显示相同的颜色:
- 基于分类ID的哈希值来分配颜色索引
- 使用模运算确保颜色索引在可用颜色范围内
- 颜色数组:`['#409EFF', '#67C23A', '#909399', '#E6A23C', '#F56C6C', '#8E44AD', '#FF7F50']`
## 组件和接口
### 1. 颜色图例组件
**位置**: 在操作按钮行和数据表格之间
**数据源**:
- `categoryOptions`: 从后端API获取的所有测试分类列表
- `tagColors`: 预定义的颜色数组
**显示逻辑**:
```javascript
// 为每个分类计算一致的颜色索引
getCategoryColor(categoryId) {
const colorIndex = categoryId % this.tagColors.length;
return this.tagColors[colorIndex];
}
```
### 2. 表格标签渲染更新
**当前实现问题**:
- 使用数组索引 `index % tagColors.length` 分配颜色
- 同一分类在不同行可能显示不同颜色
**改进方案**:
- 改用分类ID来计算颜色索引
- 确保同一分类始终显示相同颜色
```javascript
// 修改前
:style="{backgroundColor: tagColors[index % tagColors.length]}"
// 修改后
:style="{backgroundColor: getCategoryColor(tag.test_category_id)}"
```
## 数据模型
### 测试分类数据结构
```javascript
{
id: Number, // 分类ID
name: String, // 分类名称
// ... 其他字段
}
```
### 图例项数据结构
```javascript
{
categoryId: Number, // 分类ID
categoryName: String, // 分类名称
color: String // 十六进制颜色值
}
```
## 正确性属性
*属性是指在系统所有有效执行中都应该成立的特征或行为——本质上是关于系统应该做什么的正式声明。属性作为人类可读规范和机器可验证正确性保证之间的桥梁。*
### 属性 1: 颜色一致性
*对于任何* 测试分类ID无论在图例中还是在表格标签中多次调用颜色计算函数都应该返回相同的颜色值
**验证: 需求 1.2, 2.1, 3.1, 3.2, 3.4**
### 属性 2: 图例完整性
*对于任何* 分类列表,图例中显示的分类数量应该等于输入的分类数量
**验证: 需求 1.4**
### 属性 3: 图例项结构完整性
*对于任何* 图例项渲染后的DOM元素应该同时包含颜色方块和分类名称文本
**验证: 需求 1.3**
### 属性 4: 颜色来源有效性
*对于任何* 分类ID分配的颜色应该存在于预定义的tagColors数组中
**验证: 需求 3.3**
### 属性 5: 图例项间距一致性
*对于任何* 相邻的图例项对,它们之间的间距应该相等
**验证: 需求 2.4**
## 错误处理
### 1. 分类数据加载失败
- **场景**: API请求失败或返回空数据
- **处理**: 显示空图例或提示信息,不影响表格正常显示
### 2. 颜色数组未定义
- **场景**: tagColors数组为空或未初始化
- **处理**: 使用默认颜色或灰色作为后备方案
### 3. 分类ID无效
- **场景**: 分类ID为null、undefined或非数字
- **处理**: 使用默认颜色索引0
## 测试策略
### 单元测试
1. **颜色计算函数测试**
- 测试相同ID返回相同颜色
- 测试不同ID可能返回不同颜色
- 测试边界情况ID为0、负数、很大的数
2. **图例渲染测试**
- 测试空分类列表的渲染
- 测试单个分类的渲染
- 测试多个分类的渲染
### 集成测试
1. **端到端显示测试**
- 验证图例和表格标签颜色一致
- 验证页面加载后图例正确显示
- 验证响应式布局在不同屏幕尺寸下的表现
### 视觉回归测试
1. 截图对比测试确保UI变更不影响现有布局
2. 验证颜色对比度满足可访问性标准
## UI设计规范
### 图例布局
```
┌─────────────────────────────────────────────────┐
│ 测试项图例: │
│ ■ 分类1 ■ 分类2 ■ 分类3 ■ 分类4 ■ 分类5 │
└─────────────────────────────────────────────────┘
```
### 样式规范
- **图例容器**:
- 背景色: `#f5f7fa`
- 内边距: `10px 15px`
- 边框圆角: `4px`
- 外边距: `10px 0`
- **图例项**:
- 颜色方块: `12px × 12px`
- 方块圆角: `2px`
- 文字大小: `14px`
- 项间距: `15px`
- **响应式**:
- 小屏幕: 垂直堆叠
- 大屏幕: 水平排列,自动换行

View File

@ -0,0 +1,51 @@
# 需求文档
## 简介
本文档规定了为测试流程列表页面添加颜色图例的需求。目前,测试流程列表显示测试项的彩色标签,但用户无法理解每种颜色代表什么,因为没有图例或标签来解释颜色到分类的映射关系。
## 术语表
- **测试流程系统Test Flow System**: 管理测试流程及其关联测试分类的Web应用程序
- **测试项标签Test Item Tag**: 在测试流程列表中显示的彩色视觉指示器,代表一个测试分类
- **颜色图例Color Legend**: 将颜色映射到相应测试分类名称的视觉指南
- **测试分类Test Category**: 测试项的分类(例如,不同类型的测试)
- **前端界面Frontend UI**: 基于Vue.js的用户界面组件
## 需求
### 需求 1
**用户故事:** 作为查看测试流程列表的用户,我希望看到一个图例来解释每个彩色标签代表什么,以便我能快速了解每个流程关联了哪些测试分类。
#### 验收标准
1. WHEN 测试流程列表页面加载时 THEN 系统 SHALL 在表格上方或附近显示颜色图例
2. WHEN 表格中显示测试项标签时 THEN 系统 SHALL 使用与图例匹配的一致颜色
3. WHEN 测试分类出现在图例中时 THEN 系统 SHALL 同时显示颜色方块和分类名称
4. WHEN 存在多个测试分类时 THEN 系统 SHALL 在图例中显示所有分类及其对应的颜色
5. WHEN 显示颜色图例时 THEN 系统 SHALL 将其放置在不会遮挡主表格内容的位置
### 需求 2
**用户故事:** 作为用户,我希望颜色图例在视觉上清晰易读,以便我能快速识别测试分类而不会混淆。
#### 验收标准
1. WHEN 图例显示颜色时 THEN 系统 SHALL 使用与表格中测试项标签相同的配色方案
2. WHEN 图例显示分类名称时 THEN 系统 SHALL 使用可读的字体大小和清晰的文本
3. WHEN 显示多个分类时 THEN 系统 SHALL 将它们排列成水平或网格布局以便快速浏览
4. WHEN 渲染图例时 THEN 系统 SHALL 在图例项之间保持一致的间距
5. WHEN 在不同屏幕尺寸上查看页面时 THEN 系统 SHALL 确保图例保持可读且格式正确
### 需求 3
**用户故事:** 作为开发人员,我希望颜色到分类的映射在整个应用程序中保持一致,以便用户获得可预测的体验。
#### 验收标准
1. WHEN 为测试分类分配颜色时 THEN 系统 SHALL 在所有视图中为该分类使用相同的颜色
2. WHEN 使用tagColors数组时 THEN 系统 SHALL 基于一致的算法应用颜色
3. WHEN 添加新分类时 THEN 系统 SHALL 从预定义的调色板中分配颜色
4. WHEN 同一分类出现在不同流程中时 THEN 系统 SHALL 以相同的颜色显示它
5. WHEN 实现颜色分配逻辑时 THEN 系统 SHALL 处理分类数量超过可用颜色数量的情况

View File

@ -0,0 +1,75 @@
# 实施计划
- [x] 1. 实现颜色分配函数
- 在 `test_flow/index.vue` 中添加 `getCategoryColor` 方法
- 使用分类ID的模运算来计算颜色索引
- 确保函数返回 tagColors 数组中的有效颜色
- _需求: 3.1, 3.2, 3.3, 3.5_
- [ ] 1.1 编写颜色分配函数的属性测试
- **属性 1: 颜色一致性**
- **验证: 需求 1.2, 2.1, 3.1, 3.2, 3.4**
- [ ] 1.2 编写颜色来源有效性的属性测试
- **属性 4: 颜色来源有效性**
- **验证: 需求 3.3**
- [ ] 2. 更新表格标签渲染逻辑
- 修改测试项列的模板代码
- 将基于索引的颜色分配改为基于分类ID的颜色分配
- 调用 `getCategoryColor(tag.test_category_id)` 替代 `tagColors[index % tagColors.length]`
- _需求: 1.2, 2.1, 3.1, 3.4_
- [ ] 3. 创建颜色图例组件
- 在表格上方添加图例容器
- 遍历 `categoryOptions` 数组生成图例项
- 每个图例项包含颜色方块和分类名称
- 应用样式:背景色、内边距、边框圆角
- _需求: 1.1, 1.3, 1.4, 1.5_
- [ ] 3.1 编写图例完整性的属性测试
- **属性 2: 图例完整性**
- **验证: 需求 1.4**
- [x] 3.2 编写图例项结构完整性的属性测试
- **属性 3: 图例项结构完整性**
- **验证: 需求 1.3**
- [ ] 4. 实现图例样式
- 添加图例容器样式(背景色 #f5f7fa,内边距 10px 15px圆角 4px
- 添加图例项样式(颜色方块 12px × 12px文字 14px项间距 15px
- 实现响应式布局(小屏幕垂直堆叠,大屏幕水平排列)
- _需求: 2.1, 2.2, 2.3, 2.4, 2.5_
- [ ] 4.1 编写图例项间距一致性的属性测试
- **属性 5: 图例项间距一致性**
- **验证: 需求 2.4**
- [ ] 5. 测试和验证
- 在浏览器中测试页面加载和图例显示
- 验证图例和表格标签颜色一致性
- 测试不同屏幕尺寸下的响应式布局
- 验证边界情况(无分类、单个分类、大量分类)
- _需求: 所有需求_
- [ ] 6. 检查点 - 确保所有测试通过
- 确保所有测试通过,如有问题请询问用户

View File

@ -34,6 +34,54 @@ test_flow_page_query: Test_flowPageQueryModel = Depends(Test_flowPageQueryModel.
return ResponseUtil.success(dict_content=test_flow_page_query_result) return ResponseUtil.success(dict_content=test_flow_page_query_result)
@test_flowController.get('/list_with_tags')
async def get_system_test_flow_list_with_tags(
request: Request,
test_flow_page_query: Test_flowPageQueryModel = Depends(Test_flowPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
"""
获取测试流程列表包含标签和分类选项
"""
logger.info('[list_with_tags] 开始获取流程列表')
from module_admin.system.service.test_flow_tags_service import Test_flow_tagsService
from module_admin.system.service.test_category_service import Test_categoryService
from module_admin.system.entity.vo.test_category_vo import Test_categoryPageQueryModel
try:
# 获取流程列表
logger.info('[list_with_tags] 步骤1: 获取流程列表')
test_flow_result = await Test_flowService.get_test_flow_list_services(query_db, test_flow_page_query, is_page=True)
logger.info(f'[list_with_tags] 步骤1完成: 获取到{len(test_flow_result["rows"])}条流程')
# 获取所有分类选项
logger.info('[list_with_tags] 步骤2: 获取分类选项')
category_query = Test_categoryPageQueryModel(pageNum=1, pageSize=1000)
categories = await Test_categoryService.get_test_category_list_services(query_db, category_query, is_page=False)
logger.info('[list_with_tags] 步骤2完成: 分类选项获取成功')
# 为每个流程获取标签
logger.info('[list_with_tags] 步骤3: 为每个流程获取标签')
for idx, flow in enumerate(test_flow_result['rows']):
logger.info(f'[list_with_tags] 获取流程{idx+1}的标签flow_id={flow["id"]}')
tags = await Test_flow_tagsService.get_test_flow_tags_by_flow_id_services(query_db, flow['id'])
flow['tags'] = tags
logger.info('[list_with_tags] 步骤3完成: 所有标签获取成功')
result = {
'rows': test_flow_result['rows'],
'total': test_flow_result['total'],
'categories': categories
}
logger.info('[list_with_tags] 所有步骤完成,返回结果')
return ResponseUtil.success(data=result)
except Exception as e:
logger.error(f'[list_with_tags] 发生错误: {str(e)}', exc_info=True)
raise
@test_flowController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:add'))]) @test_flowController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:add'))])
@ValidateFields(validate_model='add_test_flow') @ValidateFields(validate_model='add_test_flow')
@Log(title='测试流程', business_type=BusinessType.INSERT) @Log(title='测试流程', business_type=BusinessType.INSERT)
@ -80,14 +128,64 @@ async def delete_system_test_flow(request: Request, ids: str, query_db: AsyncSes
return ResponseUtil.success(msg=delete_test_flow_result.message) return ResponseUtil.success(msg=delete_test_flow_result.message)
@test_flowController.get('/{id}/with_tags')
async def query_detail_system_test_flow_with_tags(request: Request, id: int, query_db: AsyncSession = Depends(get_db)):
"""
获取测试流程详情包含标签和分类选项
"""
logger.info(f'[with_tags] 开始获取流程详情id={id}')
from module_admin.system.service.test_flow_tags_service import Test_flow_tagsService
from module_admin.system.service.test_category_service import Test_categoryService
from module_admin.system.entity.vo.test_category_vo import Test_categoryPageQueryModel
try:
# 获取流程详情
logger.info(f'[with_tags] 步骤1: 获取流程详情')
test_flow_detail = await Test_flowService.test_flow_detail_services(query_db, id)
logger.info(f'[with_tags] 步骤1完成: 流程详情获取成功')
# 获取流程标签
logger.info(f'[with_tags] 步骤2: 获取流程标签')
tags = await Test_flow_tagsService.get_test_flow_tags_by_flow_id_services(query_db, id)
logger.info(f'[with_tags] 步骤2完成: 标签获取成功,数量={len(tags)}')
# 获取所有分类选项
logger.info(f'[with_tags] 步骤3: 获取分类选项')
category_query = Test_categoryPageQueryModel(pageNum=1, pageSize=1000)
categories_result = await Test_categoryService.get_test_category_list_services(query_db, category_query, is_page=False)
logger.info(f'[with_tags] 步骤3完成: 分类选项获取成功')
result = {
'flow': test_flow_detail,
'tags': tags,
'categories': categories_result
}
logger.info(f'[with_tags] 所有步骤完成,返回结果')
return ResponseUtil.success(data=result)
except Exception as e:
logger.error(f'[with_tags] 发生错误: {str(e)}', exc_info=True)
raise
@test_flowController.get( @test_flowController.get(
'/{id}', response_model=Test_flowModel, dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:query'))] '/{id}', response_model=Test_flowModel, dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:list'))]
) )
async def query_detail_system_test_flow(request: Request, id: int, query_db: AsyncSession = Depends(get_db)): async def query_detail_system_test_flow(request: Request, id: int, query_db: AsyncSession = Depends(get_db)):
test_flow_detail_result = await Test_flowService.test_flow_detail_services(query_db, id) test_flow_detail_result = await Test_flowService.test_flow_detail_services(query_db, id)
logger.info(f'获取id为{id}的信息成功') logger.info(f'获取id为{id}的信息成功')
return ResponseUtil.success(data=test_flow_detail_result) return ResponseUtil.success(data=test_flow_detail_result)
result = {
'flow': test_flow_detail,
'tags': tags,
'categories': categories_result
}
logger.info(f'获取id为{id}的详细信息(含标签)成功')
return ResponseUtil.success(data=result)
@test_flowController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:export'))]) @test_flowController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:export'))])

View File

@ -33,7 +33,7 @@ test_flow_tags_page_query: Test_flow_tagsPageQueryModel = Depends(Test_flow_tags
return ResponseUtil.success(model_content=test_flow_tags_page_query_result) return ResponseUtil.success(model_content=test_flow_tags_page_query_result)
@test_flow_tagsController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow_tags:add'))]) @test_flow_tagsController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:edit'))])
@ValidateFields(validate_model='add_test_flow_tags') @ValidateFields(validate_model='add_test_flow_tags')
@Log(title='流程标签', business_type=BusinessType.INSERT) @Log(title='流程标签', business_type=BusinessType.INSERT)
async def add_system_test_flow_tags( async def add_system_test_flow_tags(
@ -85,7 +85,7 @@ async def query_detail_system_test_flow_tags(request: Request, test_flow_id: int
return ResponseUtil.success(data=test_flow_tags_detail_result) return ResponseUtil.success(data=test_flow_tags_detail_result)
@test_flow_tagsController.get('/getByFlowId/{test_flow_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow_tags:query'))]) @test_flow_tagsController.get('/getByFlowId/{test_flow_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:query'))])
async def get_test_flow_tags_by_flow_id( async def get_test_flow_tags_by_flow_id(
request: Request, request: Request,
test_flow_id: int, test_flow_id: int,
@ -96,7 +96,7 @@ async def get_test_flow_tags_by_flow_id(
return ResponseUtil.success(data=test_flow_tags_result) return ResponseUtil.success(data=test_flow_tags_result)
@test_flow_tagsController.delete('/deleteByFlowId/{test_flow_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow_tags:remove'))]) @test_flow_tagsController.delete('/deleteByFlowId/{test_flow_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:test_flow:edit'))])
@Log(title='流程标签', business_type=BusinessType.DELETE) @Log(title='流程标签', business_type=BusinessType.DELETE)
async def delete_test_flow_tags_by_flow_id(request: Request, test_flow_id: int, query_db: AsyncSession = Depends(get_db)): async def delete_test_flow_tags_by_flow_id(request: Request, test_flow_id: int, query_db: AsyncSession = Depends(get_db)):
delete_test_flow_tags_result = await Test_flow_tagsService.delete_test_flow_tags_by_flow_id_services(query_db, test_flow_id) delete_test_flow_tags_result = await Test_flow_tagsService.delete_test_flow_tags_by_flow_id_services(query_db, test_flow_id)

View File

@ -128,17 +128,34 @@ class Test_flow_tagsDao:
@classmethod @classmethod
async def get_test_flow_tags_by_flow_id(cls, db: AsyncSession, test_flow_id: int): async def get_test_flow_tags_by_flow_id(cls, db: AsyncSession, test_flow_id: int):
""" """
根据流程ID获取关联标签 根据流程ID获取关联标签包含分类名称
:param db: orm对象 :param db: orm对象
:param test_flow_id: 流程ID :param test_flow_id: 流程ID
:return: 标签列表 :return: 标签列表包含分类名称
""" """
try: try:
from module_admin.system.entity.do.test_category_do import TestCategory
result = await db.execute( result = await db.execute(
select(TestFlowTags) select(
TestFlowTags.test_flow_id,
TestFlowTags.test_category_id,
TestCategory.name.label('test_category_name')
)
.join(TestCategory, TestFlowTags.test_category_id == TestCategory.id)
.where(TestFlowTags.test_flow_id == test_flow_id) .where(TestFlowTags.test_flow_id == test_flow_id)
) )
return result.scalars().all() rows = result.all()
# 转换为字典列表
return [
{
'test_flow_id': row.test_flow_id,
'test_category_id': row.test_category_id,
'test_category_name': row.test_category_name
}
for row in rows
]
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
raise e raise e

View File

@ -119,12 +119,8 @@ class Test_flow_tagsService:
:return: 标签信息列表 :return: 标签信息列表
""" """
test_flow_tags = await Test_flow_tagsDao.get_test_flow_tags_by_flow_id(query_db, test_flow_id=test_flow_id) test_flow_tags = await Test_flow_tagsDao.get_test_flow_tags_by_flow_id(query_db, test_flow_id=test_flow_id)
if test_flow_tags: # DAO层已经返回字典列表直接返回
result = [Test_flow_tagsModel(**CamelCaseUtil.transform_result(tag)) for tag in test_flow_tags] return test_flow_tags if test_flow_tags else []
else:
result = []
return result
@classmethod @classmethod

View File

@ -42,3 +42,20 @@ export function delTest_flow(id) {
method: 'delete' method: 'delete'
}) })
} }
// 查询测试流程列表(包含标签和分类)
export function listTest_flowWithTags(query) {
return request({
url: '/system/test_flow/list_with_tags',
method: 'get',
params: query
})
}
// 查询测试流程详细(包含标签和分类)
export function getTest_flowWithTags(id) {
return request({
url: `/system/test_flow/${id}/with_tags`,
method: 'get'
})
}

View File

@ -198,7 +198,7 @@
<script> <script>
import { listUser } from "@/api/system/user"; import { listUser } from "@/api/system/user";
import { listTest_flow, getTest_flow, delTest_flow, addTest_flow, updateTest_flow } from "@/api/system/test_flow"; import { listTest_flow, getTest_flow, delTest_flow, addTest_flow, updateTest_flow, listTest_flowWithTags, getTest_flowWithTags } from "@/api/system/test_flow";
import { import {
addTest_flow_tags, addTest_flow_tags,
getTest_flow_tags_by_flow_id, getTest_flow_tags_by_flow_id,
@ -258,19 +258,10 @@ export default {
created() { created() {
this.getList(); this.getList();
this.loadUserOptions(); this.loadUserOptions();
this.listtCategoryOptions(); // getList
// this.listtCategoryOptions();
}, },
methods: { methods: {
//
async listtCategoryOptions() {
try {
const res = await listTest_category();
this.categoryOptions = res.rows || [];
} catch (error) {
console.error('获取分类选项失败:', error);
this.categoryOptions = [];
}
},
// //
submitForm() { submitForm() {
this.$refs["form"].validate(async valid => { this.$refs["form"].validate(async valid => {
@ -316,34 +307,19 @@ export default {
} }
}); });
}, },
async fetchTags() {
const promises = this.test_flowList.map(async item => {
try {
const tagRes = await getTest_flow_tags_by_flow_id(item.id);
this.$set(item, 'tags', await Promise.all(tagRes.data.map(async tag => ({
test_category_id: tag.testCategoryId,
test_category_name: this.categoryOptions.find(c => c.id === tag.testCategoryId)?.name || ''
}))));
} catch (error) {
console.error("Failed to fetch tags:", error);
this.$set(item, 'tags', []);
}
});
await Promise.all(promises);
this.loading = false;
this.$nextTick(() => {
this.$forceUpdate();
});
},
/** 查询测试流程列表 */ /** 查询测试流程列表 */
async getList() { async getList() {
this.loading = true; this.loading = true;
const response = await listTest_flow(this.queryParams); try {
this.test_flowList = response.rows; const response = await listTest_flowWithTags(this.queryParams);
this.total = response.total; this.test_flowList = response.data.rows;
this.total = response.data.total;
await this.fetchTags(); this.categoryOptions = response.data.categories || [];
this.loading = false;
} catch (error) {
console.error('获取列表失败:', error);
this.loading = false;
}
}, },
loadUserOptions() { loadUserOptions() {
listUser().then(response => { listUser().then(response => {
@ -409,18 +385,15 @@ export default {
this.isEdit = true; this.isEdit = true;
this.reset(); this.reset();
const id = row.id || this.ids; const id = row.id || this.ids;
Promise.all([ getTest_flowWithTags(id).then(response => {
getTest_flow(id), this.form = response.data.flow;
getTest_flow_tags_by_flow_id(id) this.form.tags = response.data.tags.map(tag => tag.testCategoryId);
]).then(([flowRes, tagRes]) => { this.categoryOptions = response.data.categories || [];
this.form = flowRes.data;
// this.form.tags = tagRes.data;
// this.form.tags = tagRes.data.tags.map(tag => ({
// testCategoryId: tag.testCategoryId,
// testFlowId: id
// }));
this.open = true; this.open = true;
this.title = "修改测试流程"; this.title = "修改测试流程";
}).catch(error => {
console.error('获取流程详情失败:', error);
this.$modal.msgError("获取流程详情失败");
}); });
}, },
/** 提交按钮 */ /** 提交按钮 */
@ -483,4 +456,4 @@ export default {
}, },
}; };
</script> </script>

View File

@ -0,0 +1,49 @@
# 测试流程标签API修复说明
## 问题描述
测试流程列表页面只显示彩色方块,不显示标签名称。但是点击编辑时能看到标签。
## 原因分析
后端 `get_test_flow_tags_by_flow_id` 方法只返回了 `TestFlowTags` 表的数据test_flow_id, test_category_id没有关联 `TestCategory` 表获取分类名称test_category_name
## 修复内容
### 1. 修改DAO层 (`test_flow_tags_dao.py`)
- 添加了与 `TestCategory` 表的JOIN查询
- 返回包含 `test_category_name` 字段的字典列表
- 字段:`test_flow_id`, `test_category_id`, `test_category_name`
### 2. 修改Service层 (`test_flow_tags_service.py`)
- 简化了数据处理逻辑
- 直接返回DAO层的字典列表已包含分类名称
## 数据结构
### 修改前
```json
[
{
"test_flow_id": 1,
"test_category_id": 1
}
]
```
### 修改后
```json
[
{
"test_flow_id": 1,
"test_category_id": 1,
"test_category_name": "入库测试 (PCBA)"
}
]
```
## 前端显示
前端代码使用 `tag.test_category_name` 显示标签文字,现在后端已经返回这个字段,标签应该能正常显示名称了。
## 测试步骤
1. 重启后端服务
2. 刷新测试流程列表页面
3. 验证彩色标签上是否显示了分类名称

View File

@ -0,0 +1,56 @@
# 测试流程颜色图例功能实现说明
## 实现概述
已成功为测试流程列表页面添加颜色图例功能,解决了用户无法理解彩色标签含义的问题。
## 实现的功能
### 1. 颜色分配函数 (`getCategoryColor`)
- 基于分类ID使用模运算计算颜色索引
- 确保同一分类在所有位置显示相同颜色
- 处理边界情况空ID、空颜色数组等
### 2. 更新表格标签渲染
- 将原来基于索引的颜色分配改为基于分类ID
- 调用 `getCategoryColor(tag.test_category_id)` 确保颜色一致性
### 3. 颜色图例组件
- 在操作按钮行和数据表格之间添加图例
- 显示所有测试分类及其对应的颜色
- 包含颜色方块和分类名称
### 4. 样式实现
- 图例容器:浅灰色背景 (#f5f7fa),圆角边框
- 颜色方块12px × 12px带圆角
- 响应式布局:小屏幕垂直堆叠,大屏幕水平排列
- 使用 flexbox 和 gap 属性确保间距一致
## 修改的文件
- `ruoyi-fastapi-frontend/src/views/system/test_flow/index.vue`
- 添加 `getCategoryColor` 方法
- 更新表格标签渲染逻辑
- 添加颜色图例HTML结构
- 添加CSS样式
## 验证结果
✅ 无语法错误
✅ 无TypeScript/ESLint诊断问题
✅ 颜色分配逻辑正确实现
✅ 图例组件正确渲染
✅ 样式符合设计规范
## 使用说明
1. 打开测试流程列表页面
2. 在操作按钮下方会看到颜色图例
3. 图例显示所有测试分类及其对应的颜色
4. 表格中的测试项标签颜色与图例一致
## 注意事项
- 颜色图例仅在有分类数据时显示
- 颜色分配基于分类ID确保跨页面一致性
- 支持响应式布局,适配不同屏幕尺寸