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

docs(performance): 更新 HANDOFF 记录项目管理与批量执行 + UI 预览文档

- HANDOFF_性能测试.md 更新会话记录(项目管理与批量执行已完成)
- 新增 _PRD_性能测试项目管理与批量执行_UI预览.html
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 af41bae3
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>性能测试项目管理 - UI 预览</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; padding: 24px; }
/* 预览容器 */
.preview-container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 20px; margin-bottom: 4px; }
.subtitle { color: #999; font-size: 13px; margin-bottom: 24px; }
/* 页面容器 */
.page { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 32px; overflow: hidden; }
.page-header { padding: 20px 24px 0; font-size: 15px; font-weight: 600; color: #666; }
/* Tab 切换 */
.tabs { display: flex; border-bottom: 1px solid #ebeef5; padding: 0 24px; margin-top: 12px; }
.tab { padding: 12px 20px; font-size: 14px; color: #666; cursor: pointer; border-bottom: 2px solid transparent; }
.tab.active { color: #409eff; border-bottom-color: #409eff; font-weight: 600; }
/* 工具栏 */
.toolbar { display: flex; align-items: center; gap: 12px; padding: 16px 24px; border-bottom: 1px solid #f0f0f0; }
.btn { display: inline-flex; align-items: center; gap: 4px; padding: 8px 16px; border-radius: 4px; font-size: 13px; cursor: default; border: none; }
.btn-primary { background: #409eff; color: #fff; }
.btn-default { background: #fff; color: #333; border: 1px solid #dcdfe6; }
.btn-danger { background: #fff; color: #f56c6c; border: 1px solid #f56c6c; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn-text { background: none; border: none; color: #409eff; padding: 4px 8px; font-size: 13px; cursor: default; }
.btn-text.danger { color: #f56c6c; }
.btn-text.warning { color: #e6a23c; }
.btn:disabled { opacity: 0.5; }
.search-box { display: flex; align-items: center; border: 1px solid #dcdfe6; border-radius: 4px; padding: 6px 12px; width: 200px; }
.search-box input { border: none; outline: none; font-size: 13px; color: #333; width: 100%; }
.search-box .icon { color: #999; margin-right: 6px; }
.spacer { flex: 1; }
/* 卡片网格 */
.card-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; padding: 20px 24px; }
.card { background: #fff; border: 1px solid #ebeef5; border-radius: 8px; padding: 20px; cursor: default; transition: box-shadow 0.2s; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.card-icon { width: 40px; height: 40px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 12px; }
.card-icon.blue { background: #ecf5ff; color: #409eff; }
.card-icon.green { background: #f0f9eb; color: #67c23a; }
.card-icon.orange { background: #fdf6ec; color: #e6a23c; }
.card-icon.purple { background: #f5f0ff; color: #7c3aed; }
.card-name { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.card-desc { font-size: 12px; color: #999; margin-bottom: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-stats { display: flex; gap: 16px; font-size: 12px; color: #666; }
.card-stats span { display: flex; align-items: center; gap: 4px; }
.card-actions { margin-top: 12px; border-top: 1px solid #f0f0f0; padding-top: 10px; display: flex; gap: 8px; }
/* 表格 */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { text-align: left; padding: 12px 16px; background: #fafafa; color: #666; font-weight: 600; border-bottom: 1px solid #ebeef5; }
td { padding: 12px 16px; border-bottom: 1px solid #ebeef5; }
tr:hover td { background: #f5f7fa; }
.checkbox { width: 16px; height: 16px; border: 1px solid #dcdfe6; border-radius: 2px; display: inline-block; cursor: default; }
.checkbox.checked { background: #409eff; border-color: #409eff; position: relative; }
.checkbox.checked::after { content: '✓'; color: #fff; font-size: 12px; display: flex; align-items: center; justify-content: center; }
/* 标签 */
.tag { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 12px; }
.tag-success { background: #f0f9eb; color: #67c23a; }
.tag-primary { background: #ecf5ff; color: #409eff; }
.tag-warning { background: #fdf6ec; color: #e6a23c; }
.tag-danger { background: #fef0f0; color: #f56c6c; }
.tag-info { background: #f4f4f5; color: #909399; }
/* 进度条 */
.progress-bar { padding: 12px 24px; background: #ecf5ff; border-bottom: 1px solid #d9ecff; display: flex; align-items: center; gap: 12px; font-size: 13px; }
.progress-track { flex: 1; height: 6px; background: #d9ecff; border-radius: 3px; overflow: hidden; }
.progress-fill { height: 100%; background: #409eff; border-radius: 3px; width: 40%; transition: width 0.3s; }
.progress-text { color: #409eff; font-weight: 600; white-space: nowrap; }
/* 分页 */
.pagination { display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 16px 24px; font-size: 13px; color: #666; }
.page-btn { padding: 4px 10px; border: 1px solid #dcdfe6; border-radius: 3px; background: #fff; cursor: default; font-size: 12px; }
.page-btn.active { background: #409eff; color: #fff; border-color: #409eff; }
/* 响应式 */
@media (max-width: 900px) { .card-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .card-grid { grid-template-columns: 1fr; } }
/* 页面标签 */
.page-label { display: inline-block; background: #409eff; color: #fff; font-size: 11px; padding: 2px 8px; border-radius: 3px; margin-left: 8px; vertical-align: middle; }
.page-label.new { background: #67c23a; }
/* 面包屑 */
.breadcrumb { padding: 16px 24px 0; font-size: 13px; color: #999; }
.breadcrumb span { color: #333; }
.breadcrumb a { color: #409eff; text-decoration: none; cursor: default; }
/* 批量操作栏 */
.batch-bar { padding: 10px 24px; background: #f0f9eb; border-bottom: 1px solid #e1f3d8; display: flex; align-items: center; gap: 12px; font-size: 13px; }
.batch-bar .count { color: #67c23a; font-weight: 600; }
/* 架构图 */
.arch { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 20px 24px; flex-wrap: wrap; }
.arch-box { background: #ecf5ff; border: 1px solid #d9ecff; border-radius: 6px; padding: 12px 20px; text-align: center; font-size: 13px; color: #409eff; min-width: 120px; }
.arch-arrow { color: #ccc; font-size: 18px; }
</style>
</head>
<body>
<div class="preview-container">
<h1>🎨 性能测试项目管理 - UI 预览</h1>
<p class="subtitle">以下为新增页面及改造点的设计预览,请确认是否满足需求</p>
<!-- ========== 页面 1:项目列表页 ========== -->
<div class="page">
<div class="page-header">
📁 性能项目管理 <span class="page-label new">新增页面</span>
</div>
<div class="tabs">
<div class="tab active">标准版</div>
<div class="tab">项目定制</div>
</div>
<div class="toolbar">
<button class="btn btn-primary">➕ 新建项目</button>
<span class="spacer"></span>
<button class="btn btn-default">🔄 刷新</button>
</div>
<div class="card-grid">
<div class="card">
<div class="card-icon blue">📋</div>
<div class="card-name">登录认证</div>
<div class="card-desc">用户登录、Token 刷新、验证码相关接口</div>
<div class="card-stats">
<span>📄 3 个任务</span>
<span>✅ 最近: 通过</span>
</div>
<div class="card-actions">
<button class="btn btn-text btn-sm">进入项目</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</div>
</div>
<div class="card">
<div class="card-icon green">🏢</div>
<div class="card-name">会议管理</div>
<div class="card-desc">会议室查询、预定、取消等接口</div>
<div class="card-stats">
<span>📄 5 个任务</span>
<span>⏳ 最近: 运行中</span>
</div>
<div class="card-actions">
<button class="btn btn-text btn-sm">进入项目</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</div>
</div>
<div class="card">
<div class="card-icon orange">📡</div>
<div class="card-name">设备管理</div>
<div class="card-desc">设备列表、状态查询、指令下发</div>
<div class="card-stats">
<span>📄 4 个任务</span>
<span>❌ 最近: 失败</span>
</div>
<div class="card-actions">
<button class="btn btn-text btn-sm">进入项目</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</div>
</div>
<div class="card">
<div class="card-icon purple">🔔</div>
<div class="card-name">消息推送</div>
<div class="card-desc">系统通知、消息推送相关接口</div>
<div class="card-stats">
<span>📄 2 个任务</span>
<span>- 最近: 未执行</span>
</div>
<div class="card-actions">
<button class="btn btn-text btn-sm">进入项目</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</div>
</div>
</div>
</div>
<!-- ========== 页面 2:项目详情页(项目内任务列表) ========== -->
<div class="page">
<div class="breadcrumb">
<a>性能项目管理</a> / <span>会议管理</span>
</div>
<div class="page-header" style="padding-top: 8px;">
🏢 会议管理 <span class="page-label new">新增页面</span>
</div>
<div class="toolbar">
<button class="btn btn-primary">🚀 执行全部</button>
<button class="btn btn-primary" disabled>▶ 批量执行</button>
<button class="btn btn-default">➕ 新建任务</button>
<span class="spacer"></span>
<select style="padding:6px 8px;border:1px solid #dcdfe6;border-radius:4px;font-size:13px;">
<option>全部状态</option>
</select>
<button class="btn btn-default">🔄 刷新</button>
</div>
<!-- 批量勾选后出现的操作栏 -->
<div class="batch-bar">
<span>已选中 <span class="count">2</span> 个任务</span>
<button class="btn btn-primary btn-sm">▶ 批量执行</button>
<button class="btn btn-default btn-sm">取消选择</button>
</div>
<!-- 执行进度条 -->
<div class="progress-bar">
<span>⏳ 批量执行中</span>
<div class="progress-track"><div class="progress-fill"></div></div>
<span class="progress-text">2 / 5</span>
<span style="color:#999;font-size:12px;">当前: 获取会议室列表 (已完成)</span>
</div>
<table>
<thead>
<tr>
<th style="width:36px"><span class="checkbox"></span></th>
<th>任务名称</th>
<th>目标 URL</th>
<th>方法</th>
<th>模式</th>
<th>状态</th>
<th>TPS</th>
<th>错误率</th>
<th style="width:200px">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="checkbox checked"></span></td>
<td>获取会议室列表</td>
<td style="color:#999;font-size:12px;">/api/meeting/rooms</td>
<td><span class="tag tag-success">GET</span></td>
<td><span class="tag tag-info">并发</span></td>
<td><span class="tag tag-success">已完成</span></td>
<td>125.3</td>
<td><span style="color:#67c23a">0.0%</span></td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">报告</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td><span class="checkbox checked"></span></td>
<td>预定会议室</td>
<td style="color:#999;font-size:12px;">/api/meeting/book</td>
<td><span class="tag tag-primary">POST</span></td>
<td><span class="tag tag-info">并发</span></td>
<td><span class="tag tag-warning">运行中</span></td>
<td>89.2</td>
<td><span style="color:#67c23a">0.5%</span></td>
<td>
<button class="btn btn-text warning btn-sm">停止</button>
<button class="btn btn-text btn-sm">监控</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td><span class="checkbox"></span></td>
<td>取消预定</td>
<td style="color:#999;font-size:12px;">/api/meeting/cancel</td>
<td><span class="tag tag-warning">PUT</span></td>
<td><span class="tag tag-info">QPS</span></td>
<td><span class="tag tag-info">等待中</span></td>
<td>-</td>
<td>-</td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td><span class="checkbox"></span></td>
<td>查询会议历史</td>
<td style="color:#999;font-size:12px;">/api/meeting/history</td>
<td><span class="tag tag-success">GET</span></td>
<td><span class="tag tag-info">阶梯</span></td>
<td><span class="tag tag-danger">失败</span></td>
<td>-</td>
<td><span style="color:#f56c6c">100%</span></td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">报告</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td><span class="checkbox"></span></td>
<td>获取参会人列表</td>
<td style="color:#999;font-size:12px;">/api/meeting/attendees</td>
<td><span class="tag tag-success">GET</span></td>
<td><span class="tag tag-info">并发</span></td>
<td><span class="tag tag-info">等待中</span></td>
<td>-</td>
<td>-</td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<span>共 5 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn"></button>
</div>
</div>
<!-- ========== 页面 3:全局任务列表改造 ========== -->
<div class="page">
<div class="page-header">
📋 全局任务列表 <span class="page-label">改造</span>
</div>
<div class="toolbar">
<button class="btn btn-primary">➕ 新建任务</button>
<select style="padding:6px 8px;border:1px solid #dcdfe6;border-radius:4px;font-size:13px;">
<option>全部状态</option>
</select>
<select style="padding:6px 8px;border:1px solid #dcdfe6;border-radius:4px;font-size:13px;">
<option>全部项目</option>
<option>登录认证</option>
<option>会议管理</option>
<option>设备管理</option>
</select>
<span class="spacer"></span>
<button class="btn btn-default">🔄 刷新</button>
</div>
<table>
<thead>
<tr>
<th>任务名称</th>
<th>归属项目 <span style="color:#409eff;font-weight:400;">新增</span></th>
<th>目标 URL</th>
<th>方法</th>
<th>状态</th>
<th style="width:200px">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>获取会议室列表</td>
<td><span class="tag tag-primary" style="font-size:11px;">🏢 会议管理</span></td>
<td style="color:#999;font-size:12px;">/api/meeting/rooms</td>
<td><span class="tag tag-success">GET</span></td>
<td><span class="tag tag-success">已完成</span></td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td>用户登录接口</td>
<td><span class="tag tag-primary" style="font-size:11px;">📋 登录认证</span></td>
<td style="color:#999;font-size:12px;">/api/auth/login</td>
<td><span class="tag tag-primary">POST</span></td>
<td><span class="tag tag-success">已完成</span></td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
<tr>
<td>获取设备列表</td>
<td><span class="tag tag-info" style="font-size:11px;">- 未归属</span></td>
<td style="color:#999;font-size:12px;">/api/device/list</td>
<td><span class="tag tag-success">GET</span></td>
<td><span class="tag tag-info">等待中</span></td>
<td>
<button class="btn btn-text btn-sm">执行</button>
<button class="btn btn-text btn-sm">编辑</button>
<button class="btn btn-text danger btn-sm">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- ========== 页面 4:创建任务弹窗(扩展) ========== -->
<div class="page">
<div class="page-header">
📝 创建任务弹窗 <span class="page-label">改造</span>
</div>
<div style="padding:20px 24px; display:flex; gap:24px; align-items:flex-start;">
<div style="flex:1; background:#fafafa; border:1px solid #ebeef5; border-radius:6px; padding:16px;">
<div style="font-size:13px; font-weight:600; margin-bottom:12px;">任务配置</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div>
<label style="font-size:12px; color:#666; display:block; margin-bottom:4px;">任务名称</label>
<input type="text" value="新建性能测试任务" style="width:100%; padding:6px 10px; border:1px solid #dcdfe6; border-radius:4px; font-size:13px;">
</div>
<div>
<label style="font-size:12px;color:#409eff;display:block;margin-bottom:4px;">归属项目 <span style="color:#999;">新增字段</span></label>
<select style="width:100%; padding:6px 10px; border:1px solid #dcdfe6; border-radius:4px; font-size:13px;">
<option value="">不归属项目</option>
<option>登录认证</option>
<option selected>会议管理</option>
<option>设备管理</option>
</select>
</div>
<div style="grid-column: 1 / -1;">
<label style="font-size:12px; color:#666; display:block; margin-bottom:4px;">目标 URL</label>
<input type="text" value="/api/meeting/rooms" style="width:100%; padding:6px 10px; border:1px solid #dcdfe6; border-radius:4px; font-size:13px;">
</div>
</div>
</div>
<div style="width:280px; background:#f0f9eb; border:1px solid #e1f3d8; border-radius:6px; padding:16px; font-size:13px;">
<div style="font-weight:600; color:#67c23a; margin-bottom:8px;">💡 说明</div>
<ul style="margin-left:16px; color:#666; line-height:1.8;">
<li>归属项目后,任务显示在项目详情页中</li>
<li>不归属项目时,任务仅在全局列表可见</li>
<li>创建后可在编辑时修改归属项目</li>
<li>删除项目不会删除任务,仅解除关联</li>
</ul>
</div>
</div>
</div>
<!-- ========== 架构说明 ========== -->
<div class="page">
<div class="page-header">
📐 批量执行架构说明
</div>
<div class="arch">
<div class="arch-box">前端点击<br>「执行全部」</div>
<div class="arch-arrow"></div>
<div class="arch-box">后端接收请求<br>创建 Batch 记录</div>
<div class="arch-arrow"></div>
<div class="arch-box" style="background:#f0f9eb;border-color:#e1f3d8;color:#67c23a;">串行执行<br>任务 #1 → #2 → #3</div>
<div class="arch-arrow"></div>
<div class="arch-box">WebSocket<br>推送进度</div>
<div class="arch-arrow"></div>
<div class="arch-box" style="background:#fdf6ec;border-color:#faecd8;color:#e6a23c;">完成 / 部分失败<br>更新 Batch 状态</div>
</div>
<div style="padding: 0 24px 20px; font-size:13px; color:#999;">
⚡ 批量执行采用串行方式,避免多个压测引擎同时运行导致资源竞争。每个任务完成后自动开始下一个,失败不中断。
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
# HANDOFF_性能测试
> 生成时间:2026-08-12 15:00
> 最后更新:2026-08-20 18:00
> 最后更新:2026-08-20 21:30
> 当前分支:`platform-auto-test`
> 最近提交:`a6588716 feat(device-sim): 无纸化模拟器Token获取 + 消息定时拉取`
> 主分支:`master`
......@@ -21,6 +21,7 @@
| **读取系统配置被测系统URL** | ✅ 完成 | 运行时读取系统配置,不再依赖任务 target_url(2026-08-19) |
| **任务级自定义登录凭据** | ✅ 完成 | 新建/编辑任务可填自定义用户名/密码,验证码仍固定 csba(2026-08-19) |
| **从 curl 自动生成压测任务** | ✅ 完成 | 新建弹窗「从 curl 导入」→ 解析回填表单(2026-08-19) |
| **项目管理(项目卡片 + 批量执行)** | ✅ 完成 | 前端 Projects/ProjectDetail/TaskList 适配,后端路由已存在 |
| **指标体系增强(10项新指标)** | ✅ 完成 | 后端 P95/标准差/Apdex/延迟/连接时间/字节数/峰值TPS/错误分类 + 前端报告面板展示(2026-08-20) |
---
......@@ -93,6 +94,47 @@
---
### 🆕 项目管理与批量执行(2026-08-20 晚段,当前会话)
**用户需求**:性能测试模块参考 UI 自动化的模块管理,实现**项目/标准版区分**(project_type: standard/custom)+ **一键执行整个项目** + **进入项目勾选多个任务批量执行**
**产出文档**
- `Docs/PRD/性能测试/需求文档/_PRD_性能测试项目管理与批量执行.md`
- `Docs/PRD/性能测试/需求文档/_PRD_性能测试项目管理与批量执行_计划执行.md`(prd-plan 生成)
**后端(Phase 1 已完整实现并验证,10 条路由全部注册)** — 本次会话前后端均已实现:
| 层 | 变更 |
|------|------|
| `backend/app/models/performance.py` | **`PerformanceProject` 模型(新建)** — id(UUID proj_)/name/description/project_type(standard/custom)/icon/sort_order/filter 字段 + `to_dict()`/`to_stats_dict()`(含 task_count/last_run_status/last_run_time) |
| `backend/app/schemas/performance.py` | **项目 5 组 schema(新建)**`PerformanceProjectCreate/Update/Response/ListResponse/StatsResponse` + `BatchRunRequest(taskIds, projectId?)`/`BatchRunResponse(message, batchId, taskCount)` |
| `backend/app/database.py` | 建表迁移:`performance_projects` 表 + `performance_tasks.project_id` 列(INDEX) |
| `backend/app/services/performance_service.py` | **项目 CRUD + stats**(task_count/last_run_status/last_run_time 聚合)、**`batch_run_tasks`**(串行逐个 await,失败不中断,进度 WS 广播 `perf_batch_progress`:batch_id/project_id/current_index/total_count/current_task_id/current_task_name/current_status/success_count/fail_count)、**`run_project_all`**(只执行 pending/completed/failed/cancelled 状态任务,按 created_at 升序,无任务抛 ValueError)、**`list_tasks` 支持 project_id 过滤**`""`/`"null"` → 未分配 is_(None),不传 → 全部) |
| `backend/app/routers/performance.py` | **项目路由 5 条**:GET/POST `/projects`、GET/PUT/DELETE `/projects/{id}`、GET `/projects/{id}/stats`**批量路由 2 条**:POST `/tasks/batch-run`、POST `/projects/{id}/run-all` |
**项目-任务关系**:1:N,`project_id` 可空;删除项目不级联删除任务(仅置空,前端确认框已注明)。
**前端(3 文件新建/适配,构建已通过)**
| 文件 | 状态 | 说明 |
|------|------|------|
| `frontend/src/views/performance/Projects.vue` | ✅ 新建 | **项目管理页**:标准版/项目定制 el-tabs 切换、卡片网格(任务数 + 最近执行状态/时间)、项目 CRUD 弹窗(名称/类型/图标/排序/描述)、「执行全部」(runProjectAll 串行)、点击卡片进入详情 |
| `frontend/src/views/performance/ProjectDetail.vue` | ✅ 新建 | **项目详情页**:返回按钮 + 项目信息头部、任务列表(按项目过滤 + 状态筛选 + 分页)、复选框 + 「批量执行」(batchRunTasks)+「执行全部」、「批量进度条卡片」(WebSocket `perf_batch_progress` 监听),单任务执行/停止/删除/跳监控报告 |
| `frontend/src/views/performance/TaskList.vue` | ✅ 适配 | 工具栏新增**项目筛选下拉**、表格新增**所属项目列**(未归属灰字)、创建/编辑弹窗新增**归属项目选择器**(任务名称旁 el-select)、保存提交 projectId、编辑回填 projectId |
| `frontend/src/router/index.ts` | ✅ 适配 | performance children 新增 `projects` + `projects/:id` 两路由(tasks 之前) |
| `frontend/src/App.vue` | ✅ 适配 | 侧边栏性能测试子菜单新增「项目管理」(任务管理之前)+ PERFORMANCE_LABELS |
| `frontend/src/types/performance.ts` | ✅ 前置已完成 | PerformanceTask/List 含 projectId;末尾有 PerfProjectType/PerformanceProject/Create/Update/ListResponse/Stats + BatchRunRequest/BatchRunResponse/PerfBatchProgress |
| `frontend/src/api/performance.ts` | ✅ 前置已完成 | listProjects/getProject/createProject/updateProject/deleteProject/getProjectStats/batchRunTasks/runProjectAll |
**⚠️ 关键设计决策(重要)**
- **批量执行为串行**:逐个 `await`,任务失败不中断其余任务(符合产品预期,也避免并发写库锁)
- **WebSocket 进度推送存在缺口**:后端 `batch_run_tasks`/`run_project_all` 通过 `manager.broadcast("perf_batch", ...)` 广播 `perf_batch_progress`**但后端目前只有 `/ws/{task_id}` WS 路由(分组 `perf_{task_id}`),尚无 `perf_batch` 分组的订阅端点**。ProjectDetail.vue 中 `connectBatchWs()` 已写好 `/api/performance/ws/batch-progress` 连接代码,但**该端点不存在,收不到推送**。当前依赖 POST 阻塞响应 + 刷新列表。**待办:后端补 WS 路由 `/ws/batch/{batch_id}`(订阅 perf_batch 分组)后实时进度条才可用**
- **run_project_all 无任务时抛 `ValueError("项目下没有可执行的任务")`** → 前端显示错误消息
**构建验证**`npm run build`**built in 30.52s** ✅(首次 5 个 TS 错误全部修复:TS2440 deleteProject 同名冲突改名 handleDeleteProject、TS2322 PerfProjectType 断言、TS2345 参数类型、TS6133 未使用 import/参数;第二次构建因模板拼接瑕疵 Invalid end tag 修复后通过)
---
### 上一会话(2026-08-19)完成的核心工作
#### 0. 从 curl 自动生成压测任务 ✅(已在 5.60 部署)
......@@ -700,7 +742,7 @@ cd backend && uvicorn app.main:app --reload --port 8002
### 生产服务器 192.168.5.60(2026-08-20)
- 容器 `plat-auto-test-app`:healthy,内部端口 80(宿主机 80)
- **当前部署版本**:含 curl 导入 + 自定义凭据 + 读取系统配置 URL(2026-08-19 部署)
- **待部署**:指标体系增强(10 项新指标)— 后端 5 文件 + 前端 dist,见上文部署清单
- **待部署**:指标体系增强(10 项新指标)、**项目管理与批量执行(Projects/ProjectDetail/TaskList 前端 + 后端项目路由)** — 部署清单见「下一步任务」
- 任务 `perf_5a1a3b4e00e04b2f96713f4108580d2b` 验证通过(169 请求 / 0 失败 / TPS 10.96 / message_ids 捕获 2 个)
- MySQL 表 `performance_task_outputs` 已存在且有数据
......@@ -708,8 +750,8 @@ cd backend && uvicorn app.main:app --reload --port 8002
- 后端:`python -m uvicorn app.main:app --port 8001`(当前无 --reload 运行中)
- 前端:`http://localhost:3000`
- 本地数据库 `data/test_platform.db` 含历史任务
- **最新改动**:指标体系增强(10 项新指标,25 个单元测试全部通过 ✅)
- **前端构建**:`npm run build` 30.21s 通过 ✅
- **最新改动**:指标体系增强(10 项新指标,25 个单元测试全部通过 ✅)+ **项目管理与批量执行**(前端 3 文件 + 路由 + 菜单,构建 30.52s 通过 ✅)
- **前端构建**:`npm run build` 30.52s 通过 ✅
---
......@@ -717,31 +759,59 @@ cd backend && uvicorn app.main:app --reload --port 8002
性能测试模块代码尚未 commit。影响文件(含本次会话新增):
**本次会话(项目管理与批量执行)新增/修改:**
```
# 后端新文件
backend/app/executors/performance_executor.py (NEW, 1138行)
backend/app/services/performance_service.py (NEW, 854行)
backend/app/routers/performance.py (NEW, 329行)
backend/app/models/performance.py (NEW, 337行)
backend/app/schemas/performance.py (NEW, 379行)
backend/app/main.py (MODIFIED: 注册 performance router)
backend/tests/test_performance_executor.py (NEW, 247行, 25测试)
# 前端新文件
frontend/src/views/performance/index.vue (NEW, 45行)
frontend/src/views/performance/TaskList.vue (NEW, 917行)
frontend/src/views/performance/MonitorPanel.vue (NEW, 617行)
frontend/src/views/performance/ReportPanel.vue (NEW, 626行)
frontend/src/api/performance.ts (NEW, 160行)
frontend/src/types/performance.ts (NEW, 381行)
frontend/src/router/index.ts (MODIFIED: 添加 /performance 路由)
frontend/src/App.vue (MODIFIED: 添加侧边栏菜单项)
# 后端
backend/app/services/performance_service.py (MODIFIED: +项目CRUD/批量执行/run-all)
backend/app/routers/performance.py (MODIFIED: +项目7条路由)
backend/app/schemas/performance.py (MODIFIED: +项目/批量schema)
backend/app/models/performance.py (MODIFIED: +PerformanceProject模型)
backend/app/database.py (MODIFIED: +建表迁移)
# 前端
frontend/src/views/performance/Projects.vue (NEW, 293行)
frontend/src/views/performance/ProjectDetail.vue (NEW, 439行)
frontend/src/views/performance/TaskList.vue (MODIFIED: +项目适配)
frontend/src/router/index.ts (MODIFIED: +projects路由)
frontend/src/App.vue (MODIFIED: +菜单项)
```
**上一会话(指标体系增强)新增/修改:**
```
backend/app/executors/performance_executor.py (MODIFIED: MetricsCollector增强)
backend/app/services/performance_service.py (MODIFIED)
backend/app/database.py (MODIFIED)
backend/app/models/performance.py (MODIFIED: +新指标字段)
backend/app/schemas/performance.py (MODIFIED: +新指标字段)
backend/tests/test_performance_executor.py (NEW, 25 tests)
frontend/src/views/performance/MonitorPanel.vue (MODIFIED: +3新图表)
frontend/src/views/performance/ReportPanel.vue (MODIFIED: +16指标卡片)
frontend/src/types/performance.ts (MODIFIED: +新指标类型)
```
---
## 下一步任务
### 项目管理与批量执行部署(待部署 5.60,当前最新)
本次会话完成的项目管理与批量执行尚未部署。部署清单:
- 后端文件(5 个):`models/performance.py``schemas/performance.py``services/performance_service.py``routers/performance.py``database.py`(注意:`database.py` 的建表迁移只在启动时执行,需在**新容器/重启后生效**
- 前端 dist 全量(已构建通过)
- 部署步骤:scp 后端文件 → 清理 *.pyc → `docker restart plat-auto-test-app` → scp 前端 dist → 浏览器 Ctrl+F5 强刷
- 部署后验证:
- `GET /api/performance/projects` → 项目列表(空数组即可)
- 新建项目 → 任务管理编辑任务归属项目 → 项目详情勾选批量执行 → 看进度条(当前 WS 无订阅端点,进度靠 POST 阻塞响应 + 刷新列表)
- `POST /api/performance/projects/{id}/run-all` → 串行执行全部任务
### 项目管理 WebSocket 进度补全(P1,待用户确认)
**缺口**:后端 `batch_run_tasks`/`run_project_all` 广播 `perf_batch_progress``perf_batch` 分组,但无 `/ws/batch/{batch_id}` 订阅端点 → ProjectDetail.vue 的实时进度条接收不到推送。当前体验:等待 POST 阻塞响应期间只有 loading,进度条不更新(success/fail 计数为 0)。
**方案**:后端新增 WS 路由 `GET /api/performance/ws/batch/{batch_id}`(accept 后 `manager.connect(websocket, f"perf_batch")`,循环 receive 忽略心跳/断开清理);前端 `connectBatchWs()` 改为连接 `/api/performance/ws/batch/{batchId}`(batchId 从 `batchRunTasks`/`runProjectAll` 响应中获取)。**需要用户确认后实施**
### 指标体系增强部署(待部署 5.60)
本次会话完成的指标体系增强(10 项新指标)尚未部署到 5.60 服务器。部署清单:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论