提交 3f368601 authored 作者: 陈泽健's avatar 陈泽健

docs: 同步各模块HANDOFF文档 & 性能测试文档迁移至新目录

- 功能测试报告 HANDOFF:更新模板优化(T1-T5)状态、部署记录、待办
- 安全测试 HANDOFF:更新 task-preview 接口实测通过状态
- V2 部署升级 HANDOFF:新增离线部署包验证(5.69 KylinOS)与实施记录
- 功能测试报告路由:新增 FileResponse/HTMLResponse 导入
- 性能测试文档迁移:从 Docs/PRD/需求文档/性能测试/ 迁移至 Docs/PRD/性能测试/
- 新增功能测试报告模板优化 + Excel 适配 PRD 文档(4 份)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 f3035587
......@@ -3,8 +3,9 @@
> **生成时间**: 2026-08-17
> **当前分支**: `platform-auto-test`
> **最近提交**: `ce2c75de` feat: 功能测试报告新增模板配置功能(多模板管理)
> **状态**: ✅ 功能测试报告模块全部完成(后端服务 + API + 前端页面 + ERP 上传 + ERP 配置持久化 + **报告模板配置多模板管理** + **Excel 格式适配**)
> **部署状态**: ✅ 已部署至 192.168.5.60(Docker 容器 plat-auto-test-app,端口 80),前后端均已更新,**模板创建 500 错误已修复并验证通过**
> **未提交变更**: 报告模板优化(T1-T5)+ Excel 格式适配的代码与文档尚未 git commit,部署是直接 scp 到 5.60 的(本地改动脉冲式同步,需尽快补提交)
> **状态**: ✅ 功能测试报告模块全部完成(后端服务 + API + 前端页面 + ERP 上传 + ERP 配置持久化 + **报告模板配置多模板管理** + **Excel 格式适配** + **报告模板优化(章节编号动态化 + 描述字段 + 复制功能)** + **下载/预览 500 已修复**)
> **部署状态**: ✅ 已部署至 192.168.5.60(Docker 容器 plat-auto-test-app,端口 80),前后端均已更新,**模板创建 500 错误已修复**,**模板优化(编号动态化/描述/复制)已验证通过**,**报告下载/预览 500 已修复并部署**
---
......@@ -141,6 +142,40 @@ PASS 3A-3F: description 创建/更新/列表持久化 + 复制(_副本/_副本
```
前端 `vue-tsc` 类型检查:修改文件无错误 ✅
**🚀 已部署至 5.60 服务器**(2026-08-17,paramiko scp + docker restart):
| 部署项 | 说明 |
|--------|------|
| 后端 6 文件 | scp 覆盖到 `/data/third_party/plat-auto-test/backend/app/...` |
| 前端 dist/ | 本地 `npx vite build`(跳过 vue-tsc,因 ApiCaseList.vue 预存类型错误与本任务无关)→ 全量上传 |
| 容器重启 | `docker restart plat-auto-test-app`,重启后 `_ensure_columns()` 自动补齐 `report_templates.description` 列 |
| 服务器 API 实测 | 创建带描述 / 复制(`_副本`、`_副本2`、描述继承、is_default=False)/ 更新描述 / 列表含描述 / 旧模板回退 `""` — 全部通过 |
| 验证后状态 | 2 个模板(精简模板 id=2、默认模板 id=1 默认),测试模板已清理 |
**⚠️ 部署注意**:`npm run build` 会因 `ApiCaseList.vue` / `Execution.vue` 的预存类型错误(TS2339/TS2345 等,非本模块代码)而失败,需用 `npx vite build` 直接构建。这些类型错误待 API 测试模块窗口修复。
### 任务 6:报告下载/预览"内部服务器错误"修复 — 2026-08-17
**现象**:前端点击「下载报告」/「预览报告」提示"内部服务器错误"(HTTP 500)。
**根因**:`backend/app/routers/functional_report.py` 使用了 `FileResponse`(download 端点)和 `HTMLResponse`(preview 端点),但**未在文件顶部导入**。本地开发环境因 Starlette 顶层重导出"碰巧能跑",但 5.60 服务器 Docker 环境的 FastAPI 版本不导出这两个类 → `NameError` → 500。
**修复**:import 区增加一行:
```python
from fastapi.responses import FileResponse, HTMLResponse
```
**验证**(5.60 服务器实测):
- `import app.routers.functional_report` 本地通过 ✅
- scp 覆盖 + `docker restart plat-auto-test-app` ✅
- 下载/预览不存在会话 → 返回 404(不再 500)✅
- 容器日志无 NameError / Error ✅
**⚠️ 同类风险排查**:全局 grep 确认其他 router(`document.py` / `reports.py` / `security.py` / `device_sim.py`)均已正确从 `fastapi.responses` 导入或函数内局部导入,无同类问题。
**详细问题处理文档**:`Docs/PRD/功能测试/问题处理/BUG-2026-08-17-001_下载文件内部服务器错误.md`
### 参考工具 bug 修复记录
| # | 问题 | 根因 | 修复 |
......@@ -234,15 +269,16 @@ GET /api/functional-report/config → 获取 ERP 配置(DB 优先,
PUT /api/functional-report/config → 更新 ERP 配置(持久化到数据库)
```
### 报告模板配置端点(2026-08-13 新增,前缀 `/api/functional-report/templates`)
### 报告模板配置端点(2026-08-13 新增,2026-08-17 扩展 description + duplicate,前缀 `/api/functional-report/templates`)
```
GET /templates → 获取模板列表(含默认标记,不含 config)
GET /templates → 获取模板列表(含默认标记 + description,不含 config)
GET /templates/{id} → 获取单个模板完整配置
POST /templates → 创建模板(首个模板自动设为默认)
PUT /templates/{id} → 更新模板(name/config/is_default 可独立更新)
POST /templates → 创建模板(首个模板自动设为默认;支持 description ≤200 字符
PUT /templates/{id} → 更新模板(name/description/config/is_default 可独立更新)
DELETE /templates/{id} → 删除模板(不允许删除最后一个)
PUT /templates/{id}/default → 设为默认模板(全局唯一,自动切换)
POST /templates/{id}/duplicate → 复制模板(2026-08-17 新增;名称自动 _副本/_副本N,description 与 config 继承,is_default=False)
```
**config JSON 结构**(创建/更新时与默认配置合并,`merge_template_config` 保证结构完整):
......@@ -521,6 +557,7 @@ normalize_test_result("功能验证") → '通过' ✅
| F12 | **容器启动失败 ImportError** | 服务器后端缺少本地已有的 `performance_output.py`/`api_preset.py` 及 `api_preset` schema/service(routers/__init__.py 不 export,靠 main.py 直接导入) | 补传缺失文件后再 `docker restart`,成功恢复 healthy |
| F13 | **创建模板 500 `TypeError: dict can not be used as parameter`** | MySQL 的 aiomysql 驱动不接受 Python dict 作为 SQL 参数:`_validate_config()` 返回 dict,直接赋给 `config` Text 列。SQLite 不校验类型所以本地从未报错 | 赋模型前 `json.dumps(config, ensure_ascii=False)` 序列化为 JSON 字符串(create 和 update 两处都要改),读侧 `to_dict()` 已 `json.loads` 无需改 |
| F14 | **本地 main.py 未注册 report_template 路由** | 之前只改了服务器上的 main.py(已注册),本地 `main.py` 和 `models/__init__.py` 忘记同步注册 | 本地/服务器都要注册:main.py import + include_router,models/__init__.py import + __all__ |
| F15 | **下载/预览报告 500 `NameError: FileResponse`** | `functional_report.py` 使用 `FileResponse`/`HTMLResponse` 但未导入;本地因 Starlette 顶层重导出碰巧能跑,服务器 FastAPI 版本严格不导出 | `from fastapi.responses import FileResponse, HTMLResponse` 显式导入(已修复 + 部署 + 排查其他 router 无同类问题) |
---
......@@ -627,15 +664,37 @@ frontend/src/views/functional-report/FunctionalReport.vue — 2026-08-13 新增
frontend/src/views/functional-report/index.vue — 2026-08-13 注释更新
```
### 文档(4 个文件)
### 文档(6 个文件)
```
Docs/PRD/功能测试/需求文档/_PRD_功能测试报告模块.md
Docs/PRD/功能测试/需求文档/_PRD_功能测试报告模块_计划执行.md
Docs/PRD/功能测试/需求文档/_PRD_功能测试用例Excel适配.md — 2026-08-17 新增
Docs/PRD/功能测试/需求文档/_PRD_功能测试用例Excel适配_计划执行.md — 2026-08-17 新增
Docs/PRD/功能测试/需求文档/_PRD_功能测试报告模板优化.md — 2026-08-17 新增(T1-T5,P2/P3 待办项见文内 backlog)
Docs/PRD/功能测试/需求文档/_PRD_功能测试报告模板优化_计划执行.md — 2026-08-17 新增
```
---
## 七、待办与 Backlog
### 7.1 模板优化 P2/P3 待办(本轮未实施,见 PRD backlog)
| # | 优先级 | 内容 |
|---|--------|------|
| T6 | P2 | Word 字体下拉框(当前为文本输入,改为系统字体枚举) |
| T7 | P2 | 一键恢复默认配置按钮 |
| T8 | P3 | 图表颜色标签显示(当前仅颜色选择器,无语义标签预览) |
| T9 | P3 | 模板导出/导入(JSON 文件) |
| T10 | P3 | 报告生成页模板下拉显示章节摘要(如"精简模板(8/10 章)") |
### 7.2 其他待办
- **P3 大亚湾真实 Excel 报告验证**:新格式用例文件(`临时目录/大亚湾一体化会议系统测试用例-260811.xlsx`)的测试结果列(C13)大部分为公式未缓存值(353/487 空),需业务方填写实际结果后再上传生成完整报告验证
- **前端类型错误**`ApiCaseList.vue` / `Execution.vue` 存在预存 TS 类型错误(TS2339/TS2345),导致 `npm run build` 失败,当前部署用 `npx vite build` 绕过;待 API 测试模块窗口修复
- **临时脚本清理**`临时目录/` 下有 `_fix_md_subs.py``_fix_md_gen.py``_fix_md_gen2.py`(markdown_generator 修复脚本,已完成使命)、`_verify_numbering.py`(验证脚本,可保留供回归)可择机清理
---
*本文档由功能测试报告开发窗口生成,供下一次会话快速恢复上下文。其他窗口的交接见 `HANDOFF.md`(主文档)、`HANDOFF_安全测试.md`、`HANDOFF_设备模拟.md` 等。*
\ No newline at end of file
# 功能测试报告模板优化 · 需求文档(PRD)
> **文档状态**:已定稿 · **版本**:v1.0 · **最后更新**:2026-08-17
> **维护者**:czj · **所属模块**:报告中心-功能测试
> **对应计划执行**:`_PRD_功能测试报告模板优化_计划执行.md`
---
## 1. 背景与目标
### 1.1 背景
功能测试报告模块已于 2026-08-13 完成模板配置功能(多模板管理:章节开关、Word 样式、图表样式、报告默认值)。在代码审查中发现以下问题:
| 问题 | 严重性 | 说明 |
|------|--------|------|
| **章节编号硬编码** | 🔴 P0 | `word_generator.py` 中 10 个章节标题编号(一、二、三…十)为硬编码字符串;`markdown_generator.py``_title()` 中中文数字同样硬编码。**当用户关闭前置章节后,后续章节编号错误**(如关闭"报告基本信息"后,下一章仍显示"二、测试执行摘要",实际应为"一、") |
| **子章节编号硬编码** | 🔴 P0 | 子章节(如 `1.1 报告标识``2.1 测试结果统计`)的前缀数字硬编码,与主章节编号脱节,关闭章节后同样错乱 |
| **模板无描述** | 🟡 P1 | 模板列表只显示名称,多模板(5+)时难以区分用途 |
| **模板无法复制** | 🟡 P1 | 基于现有模板创建变体需手动新建再逐一配置,效率低 |
### 1.2 目标
1. **P0 章节编号动态化**:Word 与 Markdown 报告的主章节、子章节编号均随启用章节动态生成,关闭任意章节后编号连续正确
2. **P1 模板描述字段**:模板支持可选描述(≤200 字),列表与表单均可维护
3. **P1 模板复制功能**:一键复制现有模板为新模板(名称自动加"副本"后缀),配置完整继承
### 1.3 参考文件
| 文件 | 说明 |
|------|------|
| `backend/app/services/functional/word_generator.py` | Word 报告生成器(编号硬编码,需改造) |
| `backend/app/services/functional/markdown_generator.py` | Markdown 报告生成器(编号部分动态,需改造) |
| `backend/app/models/report_template.py` | 模板 ORM 模型(需新增 description 字段) |
| `backend/app/schemas/report_template.py` | 模板 Pydantic 模型(需新增 description) |
| `backend/app/routers/report_template.py` | 模板 API 路由(需新增复制端点) |
| `frontend/src/views/functional-report/TemplateConfig.vue` | 模板配置页面(需新增描述输入与复制按钮) |
| `frontend/src/api/reportTemplate.ts` | 模板 API 封装(需新增 description 与 duplicate) |
---
## 2. 功能需求
### 2.1 功能清单
| 编号 | 功能 | 说明 | 优先级 |
|------|------|------|--------|
| T1 | 主章节编号动态化 | Word/Markdown 主章节编号(一、二…十)按启用顺序动态生成 | **P0** |
| T2 | 子章节编号动态化 | 子章节前缀数字(`{ch_idx}.1`)与主章节编号联动 | **P0** |
| T3 | 模板描述字段 | 模板新增 `description` 可选字段(≤200 字),列表副标题展示 | **P1** |
| T4 | 模板复制功能 | 后端新增复制端点 + 前端"复制"按钮 | **P1** |
| T5 | 向后兼容 | 开启全部章节时,报告编号与现状完全一致;已有模板数据处理兼容 | P0 |
### 2.2 功能详细说明
#### T1. 主章节编号动态化
**现状**
- `word_generator.py``_add_title_local("一、报告基本信息", level=2)` 硬编码,虽有 `ch_idx` 计数器但未使用
- `markdown_generator.py``_title(ch_idx, "一", "报告基本信息")` 中第一个参数 `ch_idx` 已动态,但中文数字 `"一"` 硬编码
**改造**
- 两文件中新增 `_zh_num(n)` 工具函数:整数 → 中文数字(1→一、2→二…10→十),>10 回退为阿拉伯数字
- 主章节标题统一为 `_add_title_local(f"{_zh_num(ch_idx)}、报告基本信息")` / `_title(ch_idx, _zh_num(ch_idx), "报告基本信息")`
**结果**:关闭"报告基本信息"后,"测试执行摘要"自动变为"一、测试执行摘要"。
#### T2. 子章节编号动态化
**现状**:子章节标题硬编码为 `1.1 报告标识``2.1 测试结果统计`…与主章节编号脱节。
**改造**:子章节标题改为 `f"{ch_idx}.1 {子章节名}"`,其中 `ch_idx` 为当前主章节序号(注意:需在 `ch_idx += 1` 之后取值)。
**结果**:关闭章节后主章节编号变化,子章节前缀自动跟随(如原"四、测试用例执行详情"变为"三、"后,其子章节 4.1→3.1、4.2→3.2、4.3→3.3)。
#### T3. 模板描述字段
**后端**
- `models/report_template.py`:新增 `description: Mapped[str] = mapped_column(String(200), nullable=True, default="")``to_dict()` / `to_list_item()` 增加 `description`
- `schemas/report_template.py`:Create / Update / ListItem / Response 增加 `description: Optional[str]`
- 路由 create/update 处理 `description`
**前端**
- `reportTemplate.ts`:接口类型增加 `description`
- `TemplateConfig.vue`:列表中显示描述副标题(截断省略),表单基本信息区新增"模板描述"输入框(`el-input type="textarea"`,maxlength=200)
**数据库迁移**`report_templates` 表为 SQLite/MySQL 通用,需新增一列。提供迁移 SQL / 启动时自动校验(`ALTER TABLE ... ADD COLUMN`,幂等)。
#### T4. 模板复制功能
**后端**:新增端点 `POST /templates/{template_id}/duplicate`
- 逻辑:读取原模板 config + description → 新建模板,name = `原名称_副本`(若已存在同名追加序号),is_default=False,config 深拷贝
- 返回:新模板完整信息
**前端**
- `reportTemplate.ts`:新增 `duplicate(id)` 方法
- `TemplateConfig.vue`:模板列表操作区新增"复制"按钮(CopyDocument 图标),点击后调接口,成功后刷新列表并选中新模板
#### T5. 向后兼容
- 全部章节开启时,动态生成的编号与现状完全一致(一、二…十 / 1.1、2.1…)
- 已有模板 config 无 `description` 字段:读取时默认 `""`,不报错
- 数据库新增列采用幂等 ALTER,已存在列不重复添加
---
## 3. 非功能需求
| 项 | 说明 |
|----|------|
| 性能 | 编号动态化仅影响标题字符串生成,无额外 IO |
| 兼容 | Word/Markdown 两种格式同步改造,行为一致 |
| 回归 | 全章节开启报告与改造前输出一致(仅编号生成方式变化,内容不变) |
| 数据库 | 迁移幂等,SQLite/MySQL 均适用 |
---
## 4. 验收标准
| # | 验收点 | 预期结果 |
|---|--------|----------|
| 1 | 全章节开启生成报告 | 主章节编号一~十、子章节 1.1/2.1…10.4 与改造前一致 |
| 2 | 关闭"报告基本信息"生成报告 | 原"二、测试执行摘要"显示为"一、",其子章节 2.1→1.1、2.2→1.2、2.3→1.3 |
| 3 | 关闭中间任意章节(如"图表分析") | 后续章节编号自动前移,无跳号/重号 |
| 4 | Word 与 Markdown 编号行为一致 | 同一模板配置下两种格式章节编号相同 |
| 5 | 创建模板带描述 | 列表展示描述,编辑可修改,保存后持久化 |
| 6 | 复制模板 | 新模板名称带"副本",config/description 完整继承,不改变原模板 |
| 7 | 旧模板兼容 | 无 description 的旧模板读取正常,列表 description 显示为空 |
---
## 5. 潜在风险与回退
| 风险 | 概率 | 应对 |
|------|------|------|
| 数据库 ALTER 在 MySQL 上失败 | 低 | 仅 ADD COLUMN 单列,幂等检查 |
| 子章节前缀遗漏遗漏某些硬编码处 | 低 | 逐处 grep 核对(`^\d+\.` 模式) |
| 编号改造引入内容变化 | 低 | 全章节开启时与旧输出逐条比对 |
回退方案:`git checkout -- backend/app/services/functional/word_generator.py backend/app/services/functional/markdown_generator.py backend/app/models/report_template.py backend/app/schemas/report_template.py backend/app/routers/report_template.py`
---
## 6. 后续优化(本次不做,列入 backlog)
| 编号 | 说明 | 优先级 |
|------|------|--------|
| T6 | Word 字体下拉选择器(宋体/微软雅黑/黑体等常见字体) | P2 |
| T7 | 一键恢复系统默认配置按钮 | P2 |
| T8 | 图表颜色标签与 BUG 等级对齐(BUG-严重→1级-致命 等) | P2 |
| T9 | 模板配置导出/导入(跨环境迁移) | P2 |
| T10 | 生成报告页模板下拉显示章节摘要 | P3 |
---
*本 PRD 由 Claude Code 编写,供 prd-plan / prd-code 流程使用。*
\ No newline at end of file
# 功能测试用例 Excel 格式适配 · 需求文档(PRD)
> **文档状态**:已定稿 · **版本**:v1.0 · **最后更新**:2026-08-17
> **维护者**:czj · **所属模块**:报告中心-功能测试
---
## 1. 背景与目标
### 1.1 背景
功能测试报告模块当前**只读取 Excel 的 active sheet**(Sheet 1),且测试结果归一化映射表覆盖的枚举值有限:
| 现状 | 说明 |
|------|------|
| 单 Sheet 读取 | `read_test_cases()` 使用 `wb.active`,只读第一个 Sheet |
| 测试结果映射不全 | `pass_aliases` 未包含"功能验证"等新枚举值 |
近期收到新的测试用例文件 **「大亚湾一体化会议系统测试用例-260811.xlsx」**,其格式与现有模板格式存在差异,当前代码无法正确处理:
1. **多 Sheet 结构**:文件包含 3 个 Sheet(预约会议 / 会议看板 / 会议室管理),每个 Sheet 都是完整独立的测试用例表,需要全部读取合并
2. **新增测试结果枚举**:Sheet 中出现 `"功能验证"` 值(语义 = 通过),当前映射表未识别,会作为原始字符串返回导致统计错误
3. **其他格式要素与现有模板一致**:列结构(17 列)、表头行(Row 3)、数据起始行(Row 4)均与现有模板相同
### 1.2 目标
使功能测试报告模块能够**无缝适配新格式**的测试用例 Excel 文件:
```
读取全部 Sheet → 合并所有测试用例 → 识别新增测试结果枚举 → 正常统计分析 → 生成报告
```
具体目标:
1. **多 Sheet 读取**`read_test_cases()` 遍历工作簿所有 Sheet,合并读取所有测试用例
2. **新增结果映射**`normalize_test_result()` 增加 `"功能验证"``通过` 映射
3. **兼容性**:不破坏现有单 Sheet 格式文件的读取行为
### 1.3 参考文件
| 文件 | 说明 |
|------|------|
| `E:\GithubData\ubains-module-test\platform-auto-test\临时目录\大亚湾一体化会议系统测试用例-260811.xlsx` | 新格式样例(3 Sheet / 280 行数据) |
| `backend/app/services/functional/excel_reader.py` | 现有读取逻辑(单 Sheet) |
| `backend/app/services/functional/config.py` | 现有常量与映射配置 |
---
## 2. 功能需求
### 2.1 功能清单
| 编号 | 功能 | 说明 | 优先级 |
|------|------|------|--------|
| E1 | 多 Sheet 读取 | `read_test_cases()` 遍历所有 Sheet 合并读取 | **P0** |
| E2 | 新增"功能验证"映射 | `normalize_test_result()` 将"功能验证"归一化为"通过" | **P0** |
| E3 | 向后兼容 | 单 Sheet 旧格式文件读取行为不改变 | P0 |
| E4 | 数据验证 | 对 3 个 Sheet 均执行现有行跳过逻辑(序号为空跳过) | P1 |
### 2.2 功能详细说明
#### E1. 多 Sheet 读取
**现状**`read_test_cases()``ws = wb.active` 只读取第一个 Sheet。
**改造**:遍历 `wb.sheetnames` 中所有 Sheet,对每个 Sheet 执行相同的数据行读取逻辑(`min_row=CASE_DATA_START_ROW`,序号为 0 跳过),按 Sheet 顺序拼接所有测试用例。
**结果**:3 个 Sheet 合计 277 条数据全部读入,`module` 字段自动分组统计(报告按模块分布统计不受影响)。
#### E2. 新增测试结果映射
**现状**`pass_aliases` 包含 `{"通过", "Pass", "PASS", "pass", "成功", "P", "√", "✓", "是", "yes", "Yes", "YES"}`
**改造**:新增 `"功能验证"` 别名。
**结果**`"功能验证"``TEST_RESULT_PASS`("通过"),通过率统计正确。
#### E3. 向后兼容
- 单 Sheet 文件:遍历只有一个 Sheet,行为与现在完全一致
- 多参数 `skip` 逻辑不变:仅对 `serial_number` 为 0 的行跳过
#### E4. 数据验证
- 每个 Sheet 独立判断数据起始行(Row 4)
- 遇空行继续向下(不做提前 break,避免 Sheet 间数据遗漏)
- 用例编号生成规则不变(如 `HYKB-001``YYHY-001`),供 BUG 关联使用
---
## 3. 非功能需求
| 项 | 说明 |
|----|------|
| 性能 | 多 Sheet 合并后仍为单次读取,无额外 IO |
| 兼容 | 不影响 BUG 列表 Excel 读取(仍为单 Sheet) |
| 回归 | 现有 17 列模板文件(`data/input/用例模板.xlsx`)可正常读取 |
---
## 4. 验收标准
| # | 验收点 | 预期结果 |
|---|--------|----------|
| 1 | 上传新格式文件(3 Sheet) | 后端成功解析,读取全部 277 条用例 |
| 2 | 数据统计 | 通过/失败/未验证/未开发四类统计正确,"功能验证"计入通过 |
| 3 | 生成报告 | 10 章节报告正常生成,模块分布覆盖 3 个 Sheet 的模块 |
| 4 | 旧格式文件回归 | 原单 Sheet 模板文件读取与统计结果不变 |
---
*本 PRD 由 Claude Code 编写,供 prd-plan / prd-code 流程使用。*
\ No newline at end of file
# 功能测试用例 Excel 格式适配 · 计划执行文档
> **文档状态**:已定稿 · **版本**:v1.0 · **最后更新**:2026-08-17
> **维护者**:czj · **所属模块**:报告中心-功能测试
> **对应 PRD**:`_PRD_功能测试用例Excel适配.md`
---
## 一、执行计划总览
| 阶段 | 内容 | 预估工时 | 产出物 |
|------|------|----------|--------|
| Phase 1 | 后端:excel_reader.py 多 Sheet 遍历改造 | 15min | `excel_reader.py` 修改 |
| Phase 2 | 后端:config.py 新增测试结果映射 | 5min | `config.py` 修改 |
| Phase 3 | 验证:用新格式 Excel 文件测试 | 10min | 控制台验证输出 |
**总预估工时:30 分钟**
---
## 二、Phase 1:excel_reader.py 多 Sheet 遍历改造
### 修改文件:`backend/app/services/functional/excel_reader.py`
**改动点**`read_test_cases()` 函数中,将 `ws = wb.active` 改为遍历 `wb.sheetnames` 所有 Sheet。
### 当前代码(第 164-165 行)
```python
def read_test_cases(file_path: str) -> List[TestCase]:
wb = openpyxl.load_workbook(file_path, data_only=True)
ws = wb.active
```
### 目标代码
```python
def read_test_cases(file_path: str) -> List[TestCase]:
wb = openpyxl.load_workbook(file_path, data_only=True)
test_cases = []
for sheet_name in wb.sheetnames: # ← 遍历所有 Sheet
ws = wb[sheet_name]
for row in ws.iter_rows(min_row=CASE_DATA_START_ROW):
serial_number = _get_int_cell_value(row, CASE_COL_SERIAL_NUMBER)
if serial_number == 0:
continue
case = TestCase(
serial_number=serial_number,
# ... 其余字段不变 ...
)
test_cases.append(case)
wb.close()
return test_cases
```
### 不变的部分
- `CASE_DATA_START_ROW` 常量(仍为 4)
- 所有列索引常量(`CASE_COL_*`
- 数据行的`serial_number == 0` 跳过逻辑
- `_get_cell_value()` / `_get_int_cell_value()` 工具函数
- `read_bug_list()` 函数(BUG 列表仍为单 Sheet,无需修改)
---
## 三、Phase 2:excel_reader.py 新增测试结果映射
### 修改文件:`backend/app/services/functional/excel_reader.py`
> 注:`normalize_test_result()` 及其 `pass_aliases` 定义位于 `excel_reader.py`(非 config.py),已按实际位置修改。
**改动点**:在 `pass_aliases` 集合中新增 `"功能验证"`
### 当前代码(第 308 行)
```python
pass_aliases = {"通过", "Pass", "PASS", "pass", "成功", "P", "√", "✓", "是", "yes", "Yes", "YES"}
```
### 目标代码
```python
pass_aliases = {"通过", "Pass", "PASS", "pass", "成功", "P", "√", "✓", "是", "yes", "Yes", "YES", "功能验证"}
```
---
## 四、Phase 3:验证
### 验证脚本
```python
from app.services.functional.excel_reader import read_test_cases, normalize_test_result
# 验证 1:多 Sheet 读取
cases = read_test_cases("临时目录/大亚湾一体化会议系统测试用例-260811.xlsx")
assert len(cases) == 277, f"预期 277 条,实际 {len(cases)}"
# 验证 2:测试结果统计
from collections import Counter
result_counter = Counter(c.test_result for c in cases)
print("测试结果分布:", dict(result_counter))
# 验证 3:模块分布
modules = set(c.module for c in cases)
print(f"模块数: {len(modules)}")
print("模块列表:", sorted(modules))
# 验证 4:旧格式兼容
old_cases = read_test_cases("data/input/用例模板.xlsx")
assert len(old_cases) > 0, "旧格式读取失败"
```
### 人工验证点
| 验证项 | 检查方法 | 预期 |
|--------|---------|------|
| 总用例数 | 统计 len(cases) | 277 |
| "功能验证"映射 | 筛选 `test_result="通过"` 的用例 | 包含原"功能验证"的用例 |
| 模块分组 | 统计 module 字段 | 覆盖 3 个 Sheet 的模块 |
| 旧格式回归 | 读取原模板文件 | 数据量不变 |
---
## 五、潜在风险与回退方案
| 风险 | 概率 | 应对 |
|------|------|------|
| 某 Sheet 有额外的合并单元格导致读取异常 | 低 | openpyxl 对合并单元格会返回 None,`_get_cell_value()` 已处理 |
| 某 Sheet 列顺序不一致 | 低 | 检查确认 3 个 Sheet 列结构完全一致 |
| 旧格式文件有不可见 Sheet | 低 | 不影响,旧文件只有 1 个 Sheet |
回退方案:`git checkout -- backend/app/services/functional/excel_reader.py` 恢复单 Sheet 读取。
---
*本文档由 Claude Code 生成,供 prd-code 流程使用。*
\ No newline at end of file
此差异已折叠。
# 执行计划 - 修复报告查看TPS趋势和响应时间趋势图表不显示
> **文档类型**: 执行计划文档
> **创建日期**: 2026-08-13
> **作者**: czj
> **关联文档**: `_问题处理_报告查看TPS趋势图表不显示.md`
> **状态**: 待执行
---
## 一、改动总览
| # | 改动项 | 文件 | 说明 |
|---|--------|------|------|
| 1 | 调整 `loadReport()``loading` 状态设置时机 | `frontend/src/views/performance/ReportPanel.vue` | 数据到达后先设 `loading=false` 再初始化图表,确保图表 div 已在 DOM 中 |
---
## 二、详细执行步骤
### Step 1:修复 `loadReport()` 中的 loading 时序
**文件**`frontend/src/views/performance/ReportPanel.vue`(第 247-261 行)
**问题**`loading.value = true` 在 API 调用期间保持,导致 `v-if="loading"` 骨架屏优先渲染,图表 div 不在 DOM 中,`initCharts()` 获取不到 ref。
**修复方案**:在 `report.value = res` 赋值后,立即将 `loading` 设为 `false`,使 Vue 将 DOM 切换为图表区块,再调用 `initCharts()``updateCharts()`
**改动前**
```typescript
async function loadReport() {
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(taskId.value)
report.value = res
await nextTick()
initCharts() // ❌ loading=true,图表 div 不在 DOM 中
updateCharts() // ❌ 操作未初始化的图表实例
} catch (e: any) {
ElMessage.error('加载报告失败: ' + (e.response?.data?.detail || e.message || ''))
} finally {
loading.value = false
}
}
```
**改动后**
```typescript
async function loadReport() {
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(taskId.value)
report.value = res
loading.value = false // ✅ 先让图表 div 渲染到 DOM
await nextTick() // ✅ 等待 Vue DOM 更新
initCharts() // ✅ 此时 ref 有效
updateCharts() // ✅ 图表正确渲染
} catch (e: any) {
ElMessage.error('加载报告失败: ' + (e.response?.data?.detail || e.message || ''))
loading.value = false
}
// 注意:不再有 finally 中的 loading=false
}
```
**具体改动**(第 252 行后插入 `loading.value = false`,移除 `finally` 块中的 `loading.value = false`):
```diff
async function loadReport() {
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(taskId.value)
report.value = res
+ loading.value = false
await nextTick()
initCharts()
updateCharts()
} catch (e: any) {
ElMessage.error('加载报告失败: ' + (e.response?.data?.detail || e.message || ''))
+ loading.value = false
- } finally {
- loading.value = false
}
}
```
### Step 2:验证前端构建
`frontend/` 目录下执行构建验证:
```bash
cd frontend && npm run build
```
预期结果:无 TypeScript 错误,构建成功。
### Step 3:部署到服务器
将前端构建产物上传到 192.168.5.60 并重启容器:
```bash
# 构建前端
cd frontend && npm run build
# 上传 dist 到服务器
scp -r frontend/dist/* ubains@192.168.5.60:/home/ubains/app/frontend/
# 重启容器
ssh ubains@192.168.5.60 "docker restart app"
```
---
## 三、验证步骤
| 步骤 | 操作 | 预期结果 |
|------|------|----------|
| 1 | 访问 http://localhost:3000,进入性能测试 → 报告查看 | 页面正常加载,显示任务选择器 |
| 2 | 选择一个已完成的任务,点击"查看报告" | 骨架屏短暂显示 → 摘要、指标卡片正常显示 |
| 3 | 滚动到趋势图区域 | **TPS 趋势图**显示折线面积图,**响应时间趋势图**显示多条折线 |
| 4 | 点击"刷新报告" | 图表重新加载,保持不变 |
| 5 | 切换任务 | 图表跟随新任务数据更新 |
| 6 | 进入性能测试 → 执行监控 | 实时监控面板图表不受影响(回归测试) |
---
## 四、回滚方案
如果修复后出现异常(如骨架屏闪烁、图表加载失败等),撤销 Step 1 的代码改动即可:
```bash
git checkout -- frontend/src/views/performance/ReportPanel.vue
```
---
*本文档由 Claude Code 生成,遵循项目执行计划文档规范。*
\ No newline at end of file
# 问题处理文档 - 报告查看TPS趋势和响应时间趋势图表不显示
> **文档类型**: 问题处理文档
> **创建日期**: 2026-08-13
> **作者**: czj
> **优先级**: P0
> **状态**: 待修复
---
## 一、问题描述
### 1.1 现象
性能测试任务执行完成后,通过左侧菜单「性能测试 → 报告查看」进入报告面板,摘要信息、核心指标卡片、响应时间摘要、状态码分布均正常显示,但**TPS趋势图和响应时间趋势图两个ECharts图表区域为空白**,没有渲染出任何折线或面积图。
![图表空白](图片占位 - 实际表现为两个图表卡片内无内容)
### 1.2 复现步骤
1. 执行一个性能测试任务(任意模式),等待完成
2. 通过左侧菜单「性能测试 → 报告查看」进入
3. 在任务选择器中选择刚完成的任务 → 点击"查看报告"
4. 观察:摘要和指标正常,但两个趋势图空白
5. 点击"刷新报告"按钮 → 现象不变
### 1.3 影响范围
- 所有已完成/失败的性能测试任务均受影响
- 报告面板的趋势图功能完全不可用
- 用户无法通过趋势图直观观察压测过程中TPS和响应时间的变化趋势
---
## 二、根因分析
### 2.1 代码定位
**文件**`frontend/src/views/performance/ReportPanel.vue`
**关键代码片段**——模板结构(第 41-163 行):
```html
<template v-else>
<!-- 刷新/导出按钮 -->
<el-skeleton v-if="loading" :rows="8" animated />
<template v-else-if="report">
<!-- 摘要信息、指标卡片、状态码表格 -->
<!-- TPS 趋势图 (ref="tpsChartRef") -->
<!-- 响应时间趋势图 (ref="rtChartRef") -->
</template>
<el-empty v-else description="暂无报告数据" />
</template>
```
**关键代码片段**——`loadReport()` 函数(第 247-261 行):
```typescript
async function loadReport() {
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(taskId.value)
report.value = res
await nextTick()
initCharts() // ← 此时 loading 仍为 true,图表 DOM 不存在
updateCharts() // ← 操作未初始化的图表实例
} catch (e: any) {
ElMessage.error('加载报告失败: ' + ...)
} finally {
loading.value = false // ← 图表初始化完成后才隐藏骨架
}
}
```
**`initCharts()` 函数**(第 276-283 行):
```typescript
function initCharts() {
if (tpsChartRef.value && !tpsChart) {
tpsChart = echarts.init(tpsChartRef.value)
}
if (rtChartRef.value && !rtChart) {
rtChart = echarts.init(rtChartRef.value)
}
}
```
### 2.2 根因详解
**Vue 条件渲染冲突**
1. 模板使用 `v-if="loading"`(骨架屏)和 `v-else-if="report"`(图表内容)构成互斥条件分支
2. `loadReport()` 的调用时序:
-`loading.value = true` → 骨架屏渲染
-`report.value = res` → 数据赋值
-`await nextTick()` → 等待 Vue 完成一次 DOM 更新
-**`initCharts()`** → 此时 `loading` 仍为 `true`,所以 DOM 中渲染的是骨架屏,**不是图表区块**
-`updateCharts()` → 操作未初始化的 `tpsChart``rtChart`(均为 `null`
-`finally { loading.value = false }` → 骨架屏隐藏,图表区块渲染,但此时 `initCharts()` 已过
3. 结果:`tpsChartRef.value``rtChartRef.value``undefined``echarts.init(undefined)` 静默失败,图表实例未创建,后续 `setOption()` 无效果
### 2.3 对比分析:MonitorPanel 为何正常
**文件**`frontend/src/views/performance/MonitorPanel.vue`
MonitorPanel 的图表在模板中**不在条件渲染分支内**——只要 `taskId` 存在,图表 div 始终在 DOM 中:
```html
<template v-else>
<!-- TPS 趋势图 -->
<el-card><div ref="tpsChartRef" class="chart" /></el-card>
<!-- 响应时间趋势图 -->
<el-card><div ref="rtChartRef" class="chart" /></el-card>
<!-- 状态码分布 -->
<el-card><div ref="statusChartRef" class="chart" /></el-card>
</template>
```
`initCharts()``onMounted``watch` 中调用,此时图表 div 已在 DOM 中,`ref` 值有效,`echarts.init()` 成功。
### 2.4 数据流验证
已通过实际 API 请求验证后端数据正常:
| 验证项 | 结果 |
|--------|------|
| API 返回快照数量 | 241 条 |
| 数据字段格式 | camelCase(`p50ResponseTime``avgResponseTime``tps` 等) |
| 字段值 | 非零有效值(如 TPS 22~64,响应时间 201~818ms) |
| 前端类型定义 | `PerformanceSnapshot` 接口字段名与 API 返回一致 |
| Pydantic 序列化 | `PerformanceSnapshotResponse` 使用 `populate_by_name=True`+`alias_generator=to_camel`,正确输出 camelCase |
**结论:后端 API 数据完全正确,问题纯属前端渲染时序 Bug。**
---
## 三、修复方案
### 3.1 方案 A(推荐):调整 loading 状态设置时机
**改动最小**:在 `loadReport()` 中,设置 `report.value = res` 后立即将 `loading` 设为 `false`,使图表 div 渲染到 DOM 中,再调用 `initCharts()`
```typescript
async function loadReport() {
if (!taskId.value) return
loading.value = true
try {
const res = await getReport(taskId.value)
report.value = res
loading.value = false // ← 先让图表 div 渲染到 DOM
await nextTick() // ← 等待 DOM 更新
initCharts() // ← 此时 ref 有效
updateCharts() // ← 图表正确渲染
} catch (e: any) {
ElMessage.error('加载报告失败: ' + ...)
loading.value = false
}
// 移除 finally 中的 loading=false
}
```
**优点**:改动仅 1 行,逻辑清晰,不影响骨架屏的加载体验。
**缺点**:无。
### 3.2 方案 B:使用 `watch` 监听 `report` 变化
利用 Vue 的 `watch``report` 值变化后自动初始化图表:
```typescript
watch(report, (newVal) => {
if (newVal) {
nextTick(() => {
initCharts()
updateCharts()
})
}
})
```
**优点**:关注点分离,`loadReport()` 只负责数据加载。
**缺点**:增加额外 watch,需要处理组件卸载时 dispose。
### 3.3 方案选择
**推荐方案 A**,原因:
- 改动最小(1 行代码)
- 修复逻辑直观
- 不引入新的 watch 或生命周期复杂度
- 不影响骨架屏的首次加载体验(API 调用期间骨架屏正常显示)
---
## 四、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 报告查看 - TPS 趋势图 | 显示折线面积图,x 轴为时间(秒),y 轴为 TPS 值 |
| 报告查看 - 响应时间趋势图 | 显示多条折线(平均/P50/P90/P99),x 轴为时间,y 轴为 ms |
| 报告查看 - 刷新报告 | 点击刷新后图表重新加载,保持显示 |
| 报告查看 - 首次加载 | 骨架屏在 API 调用期间正常显示,数据到达后平滑切换为图表 |
| 报告查看 - 无数据任务 | 显示"暂无报告数据"空状态,图表不渲染 |
| 报告查看 - 无任务 | 显示任务选择器(回归测试) |
| MonitorPanel 回归 | 实时监控面板图表不受影响 |
---
## 五、附录
### 5.1 涉及文件
| 文件 | 说明 |
|------|------|
| `frontend/src/views/performance/ReportPanel.vue` | 需要修改的主文件(第 252 行后增加 `loading.value = false`) |
### 5.2 相关技术栈
- Vue 3 Composition API(`v-if`/`v-else-if` 条件渲染)
- ECharts(`echarts.init()` 需要 DOM 元素已存在)
- Element Plus `el-skeleton`(骨架屏组件)
### 5.3 已知风险
- 无。修复逻辑简单,且已通过 MonitorPanel 的实现验证了正确模式。
---
*本文档由 Claude Code 生成,遵循项目问题处理文档规范。*
\ No newline at end of file
此差异已折叠。
......@@ -4,8 +4,8 @@
> **最后更新**: 2026-08-17
> **当前分支**: `platform-auto-test`
> **开发窗口**: 安全测试模块
> **最近提交**: `236b3f3b` feat(security): 安全测试 ERP 配置/上传/任务创建全链路对接
> **状态**: ✅ 安全测试 P1 全部完成 + P2 执行验证完成 + 菜单升级 + ERP 配置子菜单 + ERP 上传流程对接完成 + ERP 任务创建对接完成
> **最近提交**: `5b5b35de` docs(security): 同步安全测试 HANDOFF 提交状态
> **状态**: ✅ 安全测试 P1 全部完成 + P2 执行验证完成 + 菜单升级 + ERP 配置子菜单 + ERP 上传流程对接完成 + ERP 任务创建对接完成,全部**已提交并部署 5.60**
---
......@@ -71,7 +71,7 @@
**背景**:参考 `develop/AuxiliaryTool/ScriptTool/ApiSecurityTest` 工具,其除报告上传外还对接了 **ERP 任务创建(指派跟踪)**,用于形成「扫描 → 修复 → 回归验证」闭环。本次实现将该能力集成到平台安全测试模块。
**流程**:需求文档 → 计划执行文档 → 后端 → 前端 → 构建验证 全链路完成(遵循项目工作流)
**流程**:需求文档 → 计划执行文档 → 后端 → 前端 → 构建验证 → Git 提交推送 → 部署 5.60 服务器 全链路完成
**核心成果**
1. ✅ 按规范输出两份文档:
......@@ -88,7 +88,13 @@
3.**前端**
- `frontend/src/api/security.ts`(新增 `getSecurityTaskPreview` / `createSecurityTask``as any` 返回类型)
- `frontend/src/views/Reports.vue`(安全报告弹窗新增「创建ERP任务」按钮 + 对话框:报告摘要只读折叠区 + 任务配置表单(类型/状态/责任人/创建人/紧急程度/期望工时/截止天数/关联需求))
4.**构建验证通过**`npm run build` 无 TS 错误(修复了 `request.get`/`request.post` 返回类型推断问题,统一 `as any`);后端 5 文件语法检查通过;FastAPI 路由注册验证通过
4.**构建验证 + 提交推送**`npm run build` 无 TS 错误;`236b3f3b` 提交推送 origin;`5b5b35de` 补充 HANDOFF 状态同步提交推送
5.**部署至 5.60 服务器**:7 个后端文件上传 + main.py 路由补丁(CRLF 适配) + 前端 dist 上传 + 容器重启
6.**task-preview 接口实测通过**:用服务器真实执行 ID `exec_66d76c33e6184906903ca13d24acf983` 测试,返回正确数据
- `server_ip: 192.168.5.44``task_name: 192.168.5.44接口安全测试及漏洞修复_20260817`
- `level: 5`(最高优先级,因有高危漏洞)
- ERP 基础数据正常:5 种任务类型 + 130 名人员
7.**create-task 待用户在界面实测**(ERP 端生成任务确认)
**默认任务配置**(对齐参考实现 config.yaml):
- type_id=3(后端开发)、status_id=1(新建)、level=按漏洞自动计算(高危→5/中危→4/低危→3/信息→2/无→1)
......@@ -314,7 +320,7 @@ GET /api/security/executions/{id}/report/download → 下载 .md 文件
|--------|------|------|
| **P2.5** | ✅ ~~前端报告类型标签修复~~ | 报告类型显示"UI自动化"应为"安全测试" |
| **P3** | ✅ ~~安全报告上传 ERP 流程对接~~ | ERP 配置入口已完成,ERP 上传流程已完成(2026-08-17) |
| **P3.5** | ✅ ~~安全测试 ERP 任务创建对接~~ | 任务创建预览 + 创建接口 + 前端对话框已完成(2026-08-17),待下次会话实测 |
| **P3.5** | ✅ ~~安全测试 ERP 任务创建对接~~ | 任务创建预览 + 创建接口 + 前端对话框已完成(2026-08-17)**已部署 5.60 并实测 task-preview 通过**(真实执行 ID 返回正确的任务名/紧急程度/ERP 基础数据),create-task 待用户在界面实测 |
---
......@@ -398,7 +404,7 @@ PYTHONIOENCODING=utf-8 python scripts/create_security_cases.py
- `frontend/src/api/security.ts`(新增 `getSecurityTaskPreview` + `createSecurityTask` 函数)
- `frontend/src/views/Reports.vue`(安全报告弹窗新增「创建ERP任务」按钮 + 对话框 UI)
- 文档:`Docs/PRD/需求文档/安全测试/` 下两份 ERP 任务创建文档(新增)
- **已提交并推送**待下次会话**实测**任务创建端到端流程
- **已提交并推送****已部署至 5.60**,task-preview 接口实测通过(真实执行 ID 返回正确数据),create-task 待用户在界面端到端实测
---
......
......@@ -16,6 +16,7 @@ from pathlib import Path
from typing import Optional
from fastapi import APIRouter, HTTPException, Query, UploadFile, File, Form, Depends
from fastapi.responses import FileResponse, HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论