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

feat(dashboard): 首页排版优化方案B深色数据驾驶舱 + 移除模块饼图全宽趋势图

- 重构 Dashboard.vue 为深色 Hero 横幅(通过率 conic-gradient 环形,≥90绿/≥70橙/<70红)
- 快捷操作上浮至第二位(4 图标色块卡片,hover 上浮),统计指标并入横幅
- 移除模块用例分布饼图,执行趋势折线图 span=24 全宽展示(260px,切换保留)
- 保留全宽最近执行 7 列表格;loadStats 仅并发 overview+trend
- PRD 需求/计划执行文档同步去饼图方案并校验通过;补记 HANDOFF 会话68
- 前端 build 零报错(vue-tsc + vite)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 522ab2f6
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页排版优化 — 效果图对比</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: #e8ecf1; padding: 32px 24px 80px;
}
h1 { text-align: center; font-size: 22px; color: #1f2d3d; margin-bottom: 6px; }
.sub { text-align: center; color: #7a8ba0; font-size: 13px; margin-bottom: 36px; }
.scheme {
max-width: 1180px; margin: 0 auto 56px; background: #fff; border-radius: 12px;
box-shadow: 0 4px 24px rgba(31,45,61,.08); overflow: hidden;
}
.scheme-title {
padding: 16px 24px; font-size: 16px; font-weight: 700; color: #1f2d3d;
background: #f7f9fb; border-bottom: 1px solid #e6ebf5; display: flex; align-items: center; gap: 10px;
}
.badge {
font-size: 11px; font-weight: 600; padding: 2px 10px; border-radius: 20px;
background: #ecf5ff; color: #409eff; border: 1px solid #d9ecff;
}
.scheme-desc { padding: 14px 24px; font-size: 13px; color: #606266; line-height: 1.7; border-bottom: 1px dashed #e6ebf5; background: #fffdf5; }
.scheme-desc b { color: #d48806; }
.canvas { padding: 20px 24px 28px; background: #f5f7fa; }
/* ---------- 布局元素通用 ---------- */
.row { display: flex; gap: 16px; margin-bottom: 16px; }
.card {
background: #fff; border-radius: 8px; padding: 16px;
box-shadow: 0 1px 4px rgba(31,45,61,.06); position: relative;
}
.card-h { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.card-h .t { font-size: 14px; font-weight: 600; color: #303133; }
.card-h .more { font-size: 12px; color: #409eff; cursor: pointer; }
/* ====== 方案A:现状 ====== */
.a-stats .card { flex: 1; display: flex; flex-direction: column; }
.a-stat-top { display: flex; align-items: center; gap: 12px; }
.a-icon { width: 44px; height: 44px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 22px; color: #fff; }
.a-val { font-size: 26px; font-weight: 700; color: #303133; line-height: 1.1; }
.a-lab { font-size: 12px; color: #909399; margin-top: 2px; }
.a-foot { border-top: 1px solid #ebeef5; margin-top: 12px; padding-top: 8px; font-size: 12px; color: #909399; }
.chart { border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #a0aec0; font-size: 12px; }
.trend-a { height: 180px; flex: 2; background: linear-gradient(180deg,#eef5ff, #f8fbff); border: 1px solid #e3edf8; }
.pie-a { height: 180px; flex: 1; background: linear-gradient(180deg,#f4f7fb,#fafcff); border: 1px solid #e8eef5; }
table.recent { width: 100%; border-collapse: collapse; font-size: 12px; }
table.recent th { text-align: left; color: #909399; font-weight: 600; padding: 8px 10px; background: #fafbfc; border-bottom: 1px solid #ebeef5; }
table.recent td { padding: 9px 10px; color: #606266; border-bottom: 1px solid #f2f4f7; }
.tag { display: inline-block; padding: 1px 8px; border-radius: 3px; font-size: 11px; }
.tag.g { background: #f0f9eb; color: #67c23a; } .tag.r { background: #fef0f0; color: #f56c6c; }
.quick { flex: 1; text-align: center; padding: 18px 8px; background: #f5f7fa; border-radius: 8px; cursor: pointer; }
.quick:hover { background: #e6ebf5; }
.quick .qi { font-size: 24px; } .quick .qt { font-size: 12px; color: #606266; margin-top: 6px; display: block; }
/* ====== 方案B ====== */
.hero {
background: linear-gradient(120deg, #1f3a5f 0%, #2b5a8c 55%, #3a7bd5 100%);
border-radius: 10px; color: #fff; padding: 22px 24px; margin-bottom: 16px;
display: flex; align-items: center; justify-content: space-between; gap: 24px;
}
.hero-l .h1 { font-size: 20px; font-weight: 700; letter-spacing: .5px; }
.hero-l .h2 { font-size: 12px; opacity: .75; margin-top: 6px; }
.hero-stats { display: flex; gap: 0; }
.hstat { padding: 0 26px; text-align: center; border-right: 1px solid rgba(255,255,255,.18); }
.hstat:last-child { border-right: none; }
.hstat .v { font-size: 26px; font-weight: 700; }
.hstat .v small { font-size: 13px; font-weight: 400; opacity: .8; }
.hstat .k { font-size: 12px; opacity: .7; margin-top: 3px; }
.hstat .d { font-size: 11px; margin-top: 3px; opacity: .9; }
.rate-ring {
width: 92px; height: 92px; border-radius: 50%; flex-shrink: 0;
background: conic-gradient(#7ee2a8 0 72%, rgba(255,255,255,.15) 72% 100%);
display: flex; align-items: center; justify-content: center; position: relative;
}
.rate-ring::before { content: ''; position: absolute; width: 70px; height: 70px; border-radius: 50%; background: #2b5a8c; }
.rate-ring .rv { position: relative; font-size: 18px; font-weight: 700; }
.b-chart-t { height: 190px; flex: 2; background: linear-gradient(180deg,#f0f6ff,#fbfdff); border: 1px solid #e3edf8; }
.b-chart-r { height: 190px; flex: 1; background: linear-gradient(180deg,#f6f8fc,#fcfdff); border: 1px solid #e8eef5; }
.quick-b { flex: 1; display: flex; align-items: center; gap: 10px; padding: 14px 16px; background: #fff; border: 1px solid #e6ebf5; border-radius: 8px; cursor: pointer; transition: all .2s; }
.quick-b:hover { border-color: #409eff; box-shadow: 0 4px 12px rgba(64,158,255,.15); transform: translateY(-2px); }
.quick-b .qi { width: 38px; height: 38px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #fff; flex-shrink: 0; }
.quick-b .qt { font-size: 13px; font-weight: 600; color: #303133; display: block; }
.quick-b .qd { font-size: 11px; color: #909399; margin-top: 2px; display: block; }
/* ====== 方案C ====== */
.c-wrap { display: flex; gap: 16px; }
.c-main { flex: 3; min-width: 0; }
.c-side { flex: 1.15; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.kpi-band { display: flex; background: #fff; border-radius: 8px; box-shadow: 0 1px 4px rgba(31,45,61,.06); margin-bottom: 16px; overflow: hidden; }
.kpi { flex: 1; padding: 18px 20px; border-right: 1px solid #f0f2f5; position: relative; }
.kpi:last-child { border-right: none; }
.kpi::after { content: ''; position: absolute; left: 20px; bottom: 0; width: 28px; height: 3px; border-radius: 2px; }
.kpi.g::after { background: #67c23a; } .kpi.r::after { background: #f56c6c; }
.kpi.b::after { background: #409eff; } .kpi.o::after { background: #e6a23c; }
.kpi .v { font-size: 28px; font-weight: 700; color: #303133; }
.kpi .v small { font-size: 12px; color: #c0c4cc; font-weight: 400; }
.kpi .k { font-size: 12px; color: #909399; margin-top: 4px; }
.kpi .d { font-size: 11px; margin-top: 6px; }
.c-trend { height: 200px; background: linear-gradient(180deg,#f0f6ff,#fbfdff); border: 1px solid #e3edf8; }
.side-card { flex: 1; }
.mini-list { font-size: 12px; }
.mini-item { display: flex; align-items: center; gap: 8px; padding: 7px 0; border-bottom: 1px dashed #f0f2f5; }
.mini-item:last-child { border-bottom: none; }
.mini-item .nm { color: #606266; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-item .tm { color: #c0c4cc; font-size: 11px; flex-shrink: 0; }
.bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; font-size: 12px; }
.bar-row:last-child { margin-bottom: 0; }
.bar-row .bn { width: 76px; color: #606266; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 0; }
.bar-track { flex: 1; height: 8px; background: #f0f2f5; border-radius: 4px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 4px; }
.bar-row .bv { width: 30px; text-align: right; color: #909399; font-size: 11px; flex-shrink: 0; }
.ring-c { display: flex; align-items: center; justify-content: center; height: 120px; color: #a0aec0; font-size: 12px; }
.legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.pro { font-size: 12px; color: #606266; line-height: 1.9; padding: 12px 24px 18px; }
.pro b { color: #303133; }
.pro .plus { color: #67c23a; font-weight: 600; } .pro .minus { color: #f56c6c; font-weight: 600; }
</style>
</head>
<body>
<h1>首页(测试总览)排版布局优化 — 效果图</h1>
<p class="sub">方案 A 为现状还原 · 方案 B / C 为优化提案 · 数值为示意数据</p>
<!-- ================= 方案 A 现状 ================= -->
<div class="scheme">
<div class="scheme-title">方案 A — 现状还原(对照基准)<span class="badge">当前线上</span></div>
<div class="scheme-desc">四张独立统计卡片 → 图表区(趋势 16 + 饼图 8)→ 最近执行表格 → 快捷操作。<b>问题</b>:卡片重复感强、页脚信息弱关联;快捷操作沉底几乎不可见;页面纵向过长;数字与图表割裂。</div>
<div class="canvas">
<div class="row a-stats">
<div class="card"><div class="a-stat-top"><div class="a-icon" style="background:#67c23a"></div><div><div class="a-val">1,842</div><div class="a-lab">本月通过</div></div></div><div class="a-foot">执行 356 次</div></div>
<div class="card"><div class="a-stat-top"><div class="a-icon" style="background:#f56c6c"></div><div><div class="a-val">213</div><div class="a-lab">本月失败</div></div></div><div class="a-foot">跳过 41 次</div></div>
<div class="card"><div class="a-stat-top"><div class="a-icon" style="background:#409eff">📈</div><div><div class="a-val">89.6%</div><div class="a-lab">平均通过率</div></div></div><div class="a-foot">状态良好</div></div>
<div class="card"><div class="a-stat-top"><div class="a-icon" style="background:#e6a23c">🗂</div><div><div class="a-val">427</div><div class="a-lab">总用例数</div></div></div><div class="a-foot">36 个模块</div></div>
</div>
<div class="row">
<div class="card" style="flex:2"><div class="card-h"><span class="t">执行趋势(近30天)</span><span style="font-size:11px;color:#909399;border:1px solid #dcdfe6;border-radius:4px;padding:2px 8px">执行次数 | 通过率</span></div><div class="chart trend-a">折线图(近 30 天)</div></div>
<div class="card" style="flex:1"><div class="card-h"><span class="t">模块用例分布</span></div><div class="chart pie-a">环形饼图</div></div>
</div>
<div class="row" style="margin-bottom:0">
<div class="card" style="flex:1">
<div class="card-h"><span class="t">最近执行记录</span><span class="more">查看全部</span></div>
<table class="recent">
<tr><th>执行名称</th><th>状态</th><th>通过率</th><th>耗时</th><th>触发人</th><th>时间</th><th>操作</th></tr>
<tr><td>每日定时执行自动化测试-0910</td><td><span class="tag g">完成</span></td><td>92.4%</td><td>4521s</td><td>调度器</td><td>09-10 04:39</td><td>详情</td></tr>
<tr><td>每日定时执行自动化测试-0909</td><td><span class="tag r">失败</span></td><td>15.2%</td><td></td><td>调度器</td><td>09-09 11:21</td><td>详情</td></tr>
<tr><td>全量测试-手动回归</td><td><span class="tag g">完成</span></td><td>88.1%</td><td>398s</td><td>czj</td><td>09-08 16:20</td><td>详情</td></tr>
</table>
</div>
</div>
<div class="row" style="margin-bottom:0; margin-top:16px">
<div class="quick"><div class="qi">🎥</div><span class="qt">录制用例</span></div>
<div class="quick"><div class="qi">▶️</div><span class="qt">执行全部测试</span></div>
<div class="quick"><div class="qi">📁</div><span class="qt">管理模块</span></div>
<div class="quick"><div class="qi">📊</div><span class="qt">查看报告</span></div>
</div>
</div>
<div class="pro"><b>现状痛点:</b>① 4 卡片各占 1/4 行,数字之间无层次,页脚关联信息弱 &nbsp;② 快捷操作在页面最底部,首屏之外基本无人点 &nbsp;③ 表格 7 列全宽,但信息密度低 &nbsp;④ 纵向四段式,滚动 2-3 屏才能看完</div>
</div>
<!-- ================= 方案 B ================= -->
<div class="scheme">
<div class="scheme-title">方案 B — 深色数据驾驶舱<span class="badge">推荐</span></div>
<div class="scheme-desc">首屏改为「<b>欢迎横幅 + 指标内嵌</b>」:4 个核心指标合并进深色 Hero 横幅(右侧通过率环形),快捷操作上浮到第二位(图标色块化),图表区压缩高度,最近执行保持全宽表格。<b>一屏容纳全部核心信息,动线:看数据 → 快捷行动 → 查明细。</b></div>
<div class="canvas">
<div class="hero">
<div class="hero-l">
<div class="h1">测试总览</div>
<div class="h2">2026-09-10 · 近 30 天数据 · 每日定时任务正常调度中</div>
</div>
<div class="hero-stats">
<div class="hstat"><div class="v">1,842</div><div class="k">本月通过</div><div class="d">执行 356 次</div></div>
<div class="hstat"><div class="v">213</div><div class="k">本月失败</div><div class="d">跳过 41 次</div></div>
<div class="hstat"><div class="v">427</div><div class="k">总用例数</div><div class="d">36 个模块</div></div>
</div>
<div class="rate-ring"><div class="rv">89.6%</div></div>
</div>
<div class="row">
<div class="quick-b"><div class="qi" style="background:#409eff">🎥</div><div><span class="qt">录制用例</span><span class="qd">可视化录制回放</span></div></div>
<div class="quick-b"><div class="qi" style="background:#67c23a"></div><div><span class="qt">执行全部测试</span><span class="qd">一键全量回归</span></div></div>
<div class="quick-b"><div class="qi" style="background:#e6a23c">📁</div><div><span class="qt">管理模块</span><span class="qd">用例组织维护</span></div></div>
<div class="quick-b"><div class="qi" style="background:#f56c6c">📊</div><div><span class="qt">查看报告</span><span class="qd">历史执行报告</span></div></div>
</div>
<div class="row">
<div class="card" style="flex:2"><div class="card-h"><span class="t">执行趋势(近30天)</span><span style="font-size:11px;color:#409eff">执行次数 | 通过率</span></div><div class="chart b-chart-t">折线图(高度 180→190,网格更紧凑)</div></div>
<div class="card" style="flex:1"><div class="card-h"><span class="t">模块用例分布</span></div><div class="chart b-chart-r">环形饼图</div></div>
</div>
<div class="row" style="margin-bottom:0">
<div class="card" style="flex:1">
<div class="card-h"><span class="t">最近执行记录</span><span class="more">查看全部</span></div>
<table class="recent">
<tr><th>执行名称</th><th>状态</th><th>通过率</th><th>耗时</th><th>触发人</th><th>时间</th><th>操作</th></tr>
<tr><td>每日定时执行自动化测试-0910</td><td><span class="tag g">完成</span></td><td>92.4%</td><td>4521s</td><td>调度器</td><td>09-10 04:39</td><td>详情</td></tr>
<tr><td>每日定时执行自动化测试-0909</td><td><span class="tag r">失败</span></td><td>15.2%</td><td></td><td>调度器</td><td>09-09 11:21</td><td>详情</td></tr>
</table>
</div>
</div>
</div>
<div class="pro"><b>收益:</b><span class="plus"></span>首屏即全部核心指标(纵向省 ~200px),<span class="plus"></span>快捷操作从页底提升到第二位、点击率大幅提升,<span class="plus"></span>深色横幅区分「总览数字」与「明细内容」两个层次<br><b>代价:</b><span class="minus"></span>页面出现深色区块,与现有浅色风格形成对比(也可做浅色渐变横幅变体)</div>
</div>
<!-- ================= 方案 C ================= -->
<div class="scheme">
<div class="scheme-title">方案 C — 浅色仪表盘 + 左右双栏</div>
<div class="scheme-desc">保持浅色风格:顶部 4 指标改为<b>一体化 KPI 带状卡</b>(分隔线 + 底部彩条,去独立卡片感);下方改为<b>左主右辅双栏</b>——左侧趋势图 + 最近执行,右侧「今日待关注(失败/中断)」+ 模块用例 Top5 横向条形图(饼图 → 条形,小空间下更可读)。<b>信息密度最高,适合长期盯数据。</b></div>
<div class="canvas">
<div class="kpi-band">
<div class="kpi g"><div class="v">1,842</div><div class="k">本月通过</div><div class="d" style="color:#909399">执行 356 次</div></div>
<div class="kpi r"><div class="v">213</div><div class="k">本月失败</div><div class="d" style="color:#909399">跳过 41 次</div></div>
<div class="kpi b"><div class="v">89.6<small>%</small></div><div class="k">平均通过率</div><div class="d" style="color:#67c23a">▲ 状态良好</div></div>
<div class="kpi o"><div class="v">427</div><div class="k">总用例数</div><div class="d" style="color:#909399">36 个模块</div></div>
</div>
<div class="c-wrap">
<div class="c-main">
<div class="card" style="margin-bottom:16px"><div class="card-h"><span class="t">执行趋势(近30天)</span><span style="font-size:11px;color:#409eff">执行次数 | 通过率</span></div><div class="chart c-trend">折线图</div></div>
<div class="card">
<div class="card-h"><span class="t">最近执行记录</span><span class="more">查看全部</span></div>
<table class="recent">
<tr><th>执行名称</th><th>状态</th><th>通过率</th><th>时间</th><th>操作</th></tr>
<tr><td>每日定时执行-0910</td><td><span class="tag g">完成</span></td><td>92.4%</td><td>09-10 04:39</td><td>详情</td></tr>
<tr><td>每日定时执行-0909</td><td><span class="tag r">失败</span></td><td>15.2%</td><td>09-09 11:21</td><td>详情</td></tr>
<tr><td>全量测试-手动回归</td><td><span class="tag g">完成</span></td><td>88.1%</td><td>09-08 16:20</td><td>详情</td></tr>
</table>
</div>
</div>
<div class="c-side">
<div class="card side-card">
<div class="card-h"><span class="t">⚠ 需要关注</span><span class="more">全部</span></div>
<div class="mini-list">
<div class="mini-item"><span class="legend-dot" style="background:#f56c6c"></span><span class="nm">每日定时执行-0909 · 被取消</span><span class="tm">11:21</span></div>
<div class="mini-item"><span class="legend-dot" style="background:#e6a23c"></span><span class="nm">5.202 执行 107 例 · 通过率 35.5%</span><span class="tm">09:36</span></div>
<div class="mini-item"><span class="legend-dot" style="background:#e6a23c"></span><span class="nm">今日定时任务 09:00 被部署中断</span><span class="tm">09:00</span></div>
</div>
</div>
<div class="card side-card">
<div class="card-h"><span class="t">模块用例 Top5</span></div>
<div>
<div class="bar-row"><span class="bn">会议运维</span><div class="bar-track"><div class="bar-fill" style="width:92%;background:#409eff"></div></div><span class="bv">96</span></div>
<div class="bar-row"><span class="bn">资产设备</span><div class="bar-track"><div class="bar-fill" style="width:74%;background:#67c23a"></div></div><span class="bv">78</span></div>
<div class="bar-row"><span class="bn">安全测试</span><div class="bar-track"><div class="bar-fill" style="width:61%;background:#e6a23c"></div></div><span class="bv">64</span></div>
<div class="bar-row"><span class="bn">通知公告</span><div class="bar-track"><div class="bar-fill" style="width:45%;background:#909399"></div></div><span class="bv">47</span></div>
<div class="bar-row"><span class="bn">登录验证</span><div class="bar-track"><div class="bar-fill" style="width:32%;background:#c0c4cc"></div></div><span class="bv">33</span></div>
</div>
</div>
<div class="card side-card">
<div class="card-h"><span class="t">快捷操作</span></div>
<div class="mini-list">
<div class="mini-item"><span class="legend-dot" style="background:#409eff"></span><span class="nm">🎥 录制用例</span></div>
<div class="mini-item"><span class="legend-dot" style="background:#67c23a"></span><span class="nm">▶ 执行全部测试</span></div>
<div class="mini-item"><span class="legend-dot" style="background:#e6a23c"></span><span class="nm">📁 管理模块</span></div>
<div class="mini-item"><span class="legend-dot" style="background:#f56c6c"></span><span class="nm">📊 查看报告</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="pro"><b>收益:</b><span class="plus"></span>KPI 带状卡去卡片重复感、色条强化指标归属,<span class="plus"></span>右侧「需要关注」直击痛点(失败/中断一眼可见),<span class="plus"></span>饼图改条形图在侧栏小空间更可读<br><b>代价:</b><span class="minus"></span>改动幅度最大(新增「需要关注」聚合逻辑,需后端 stats 接口补充数据),<span class="minus"></span>最近执行表格列数精简,触发人/耗时列移到详情页</div>
</div>
</body>
</html>
# PRD 需求文档 — 首页排版优化(数据驾驶舱)
> **文档类型**: PRD 需求文档
> **创建日期**: 2026-09-10
> **作者**: czj
> **优先级**: P0
> **状态**: 已实现
> **关联文档**: `_PRD_首页排版优化_数据驾驶舱_计划执行.md`
---
## 一、需求背景
### 1.1 当前问题
当前「测试总览」首页(`Dashboard.vue`,路由 `/`)采用纵向四段式排版:
1. **统计卡片行**:4 张独立卡片(本月通过 / 本月失败 / 平均通过率 / 总用例数),每张卡片含图标 + 大数字 + 页脚明细
2. **图表区**:左侧执行趋势折线图(span 16)+ 右侧模块用例分布环形饼图(span 8)
3. **最近执行记录**:全宽 7 列表格
4. **快捷操作**:4 个色块入口(录制用例 / 执行全部测试 / 管理模块 / 查看报告)
### 1.2 现有痛点
| # | 痛点 | 说明 |
|---|------|------|
| 1 | **卡片重复感强** | 4 张卡片结构相同,数字之间无主次层次,页脚关联信息弱 |
| 2 | **快捷操作沉底** | 位于页面最底部(首屏之外),实际使用中几乎无人点击 |
| 3 | **页面纵向过长** | 四段式结构,需滚动 2~3 屏才能看完核心信息 |
| 4 | **数字与图表割裂** | 统计卡片与趋势图/饼图分区展示,缺少一体化的驾驶舱观感 |
| 5 | **核心指标无视觉焦点** | 通过率作为最关键指标,与其它指标同等权重,无突出呈现 |
### 1.3 需求目标
将首页改造成「数据驾驶舱」风格:首屏即容纳全部核心指标,动线为「看数据 → 快捷行动 → 查明细」,强化通过率指标的视觉焦点,提升快捷操作的可达性。图表区移除视觉杂乱的模块饼图,保留全宽执行趋势折线图,与下方全宽表格形成统一视觉流。
---
## 二、功能需求
### 2.1 方案概述(方案 B:深色数据驾驶舱)
首屏改为**深色 Hero 横幅 + 指标内嵌**,将 4 个核心指标合并进横幅(右侧通过率环形图),快捷操作上浮到第二位(图标色块化),图表区改为全宽折线图(span 24,移除模块分布饼图),最近执行保持全宽表格。
**布局结构(从上到下)**
```
┌────────────────────────────────────────────────────┐
│ 深色 Hero 横幅(欢迎语 ┃ 本月通过/失败/用例数 ┃ 通过率环形)│
├────────────────────────────────────────────────────┤
│ 快捷操作:录制用例 │ 执行全部测试 │ 管理模块 │ 查看报告 │
│ (图标色块卡片,带副标题描述) │
├────────────────────────────────────────────────────┤
│ 图表区:执行趋势折线图(span 24 全宽展示) │
├────────────────────────────────────────────────────┤
│ 最近执行记录(保留全宽 7 列表格) │
└────────────────────────────────────────────────────┘
```
### 2.2 详细功能点
#### 2.2.1 深色 Hero 横幅(`header` 区)
| 区块 | 内容 | 说明 |
|------|------|------|
| 左侧 | 标题「测试总览」+ 副标题(当前日期 + 统计周期 + 系统状态) | `2026-09-10 · 近 30 天数据 · 定时任务持续调度中` |
| 中部 | 3 个内嵌指标:本月通过 / 本月失败 / 总用例数 | 数值 + 标签 + 页脚关联信息,以竖分隔线分隔 |
| 右侧 | 通过率环形进度环 | 环形展示通过率百分比(如 89.6%),环色随通过率区间变化(≥90 绿 / ≥70 橙 / <70 红) |
**样式要点**
- 背景采用深蓝渐变(`linear-gradient(120deg, #1f3a5f, #2b5a8c, #3a7bd5)`
- 白字、内嵌指标竖线分隔(`border-right: 1px solid rgba(255,255,255,.18)`
- 环形图用 CSS `conic-gradient` 实现,中心为深色底色 + 通过率数值
- 副标题透明度 0.75
#### 2.2.2 快捷操作区(上浮到第二位)
原页底 4 个方块改为**图标色块卡片**
| 操作 | 图标色 | 主标题 | 副标题 |
|------|--------|--------|--------|
| 录制用例 | 蓝 #409eff | 录制用例 | 可视化录制回放 |
| 执行全部测试 | 绿 #67c23a | 执行全部测试 | 一键全量回归 |
| 管理模块 | 橙 #e6a23c | 管理模块 | 用例组织维护 |
| 查看报告 | 红 #f56c6c | 查看报告 | 历史执行报告 |
**交互**:hover 上浮 + 蓝色边框 + 阴影;点击跳转路径不变(`/recorder` `/execution` `/modules` `/reports`)。
#### 2.2.3 图表区
- 移除模块分布饼图,执行趋势图全宽展开(span 24),高度 260px
- 折线图顶部「执行次数 / 通过率」切换保留
- 图表本身(ECharts 配置、数据源)保持近 30 天趋势数据
#### 2.2.4 最近执行记录
- **保留全宽 7 列表格**(执行名称 / 状态 / 通过率 / 耗时 / 触发人 / 时间 / 操作),列不变
- 仅位置调整到图表区下方,样式不变
### 2.3 数据来源
| 接口 | 用途 |
|------|------|
| `GET /api/stats/overview?days=30` | 统计卡片 + 最近执行记录 |
| `GET /api/stats/trend?days=30` | 执行趋势折线图 |
---
## 三、非功能需求
### 3.1 兼容性
- 仅改前端 `frontend/src/views/Dashboard.vue`,后端/API 无改动
- 采用 Element Plus + ECharts 现有技术栈,不引入新依赖
- 兼容现有分辨率:Hero 横幅可伸缩,指标区在大屏下自动居中
### 3.2 性能
- 无新增网络请求,仍为 `Promise.all` 并发拉取 3 个接口
- 环形图使用纯 CSS `conic-gradient`,零 ECharts 额外实例开销
### 3.3 可访问性
- 通过率环形/数值文字对比度满足 WCAG AA
- 快捷操作卡片 hover 有明确视觉反馈
---
## 四、验收标准
### F01 布局结构
- [x] 页面从上到下依次为:Hero 横幅 → 快捷操作 → 图表区 → 最近执行表格
### F02 Hero 横幅数据
- [x] 横幅展示标题「测试总览」+ 副标题(日期 + 周期 + 状态)
- [x] 中部 3 个内嵌指标(通过/失败/用例数)数值与 `/api/stats/overview` 一致
- [x] 右侧通过率环形环色随通过率区间变化(≥90 绿 / ≥70 橙 / <70 红)
- [x] 通过率数值同接口数据一致
### F03 快捷操作
- [x] 快捷操作位于 Hero 下方,4 个色块卡片
- [x] 每卡片含图标 + 主标题 + 副标题
- [x] 点击录制用例 → `/recorder`;执行全部测试 → 启动后跳 `/execution`;管理模块 → `/modules`;查看报告 → `/reports`
### F04 图表区
- [x] 趋势折线图正常渲染,全宽展开(span 24),高度 260px
- [x] 移除模块分布饼图,页面视觉更加清爽聚焦
- [x] 折线图「执行次数/通过率」切换可用
### F05 最近执行记录
- [x] 全宽表格,7 列不变,数据正确
### F06 加载与错误态
- [x] 加载失败时 `ElMessage.error` 提示
- [x] 数据为 0 时横幅正常显示(如指标为 0 显示 0)
---
## 五、涉及范围
| 项 | 说明 |
|----|------|
| 涉及端 | 前端(`frontend/src/views/Dashboard.vue`) |
| 涉及后端 | 无 |
| 涉及路由 | 无 |
| 涉及数据库 | 无 |
| 涉及新依赖 | 无 |
---
## 六、参考
- 效果图:`Docs/PRD/排版优化/效果图/首页排版优化_效果图.html`(方案 B)
\ No newline at end of file
# PRD 计划执行文档 — 首页排版优化(数据驾驶舱)
> **文档类型**: 计划执行文档
> **创建日期**: 2026-09-10
> **作者**: czj
> **优先级**: P0
> **状态**: 已完成
> **关联文档**: `_PRD_首页排版优化_数据驾驶舱.md`
---
## 一、任务概述
对「测试总览」首页(`Dashboard.vue`)进行排版优化,采用**方案 B:深色数据驾驶舱**,并根据用户反馈移除模块分布饼图,趋势折线图全宽展示。
- **改动代码文件**`frontend/src/views/Dashboard.vue`
- **后端**:无改动
- **部署验证**:已重新构建并 SFTP 部署至 192.168.5.60
- **效果图**`Docs/PRD/排版优化/效果图/首页_新布局_无饼图.png`
---
## 二、变更清单
| # | 文件 | 变更 |
|---|------|------|
| 1 | `frontend/src/views/Dashboard.vue` | 模板重构(Hero 横幅 + 快捷操作上浮 + 趋势图 span=24 全宽 + 移除模块饼图)+ 样式重写 |
---
## 三、实施步骤
### Phase 1:模板重构(`Dashboard.vue` template)
1. **删除**`stats-row` 四卡片 `el-row`/`el-col`
2. **新增** `hero-banner` 区块:
- 左侧:标题「测试总览」+ 副标题(动态日期 + 周期 + 状态文案)
- 中部:3 个内嵌指标(本月通过 / 本月失败 / 总用例数),各含 副标题/页脚
- 右侧:通过率环形 `rate-ring`(CSS `conic-gradient` + 中心数值)
3. **快捷操作区上浮**`quick-actions` 卡片移到图表区之前,内部改为 4 个 `quick-item` 色块卡片(图标 + 主标题 + 副标题)
4. **图表区**:移除模块用例分布饼图,保留执行趋势图并全宽展示(`el-col :span="24"`),高度 260px
5. **最近执行记录**:位置移到图表区之后,表格 7 列保持不变
### Phase 2:Script 调整(`Dashboard.vue` script)
- 新增 `heroStats` 计算属性:从 `stats.value` 派生横幅内嵌指标(通过 / 失败 / 用例数)
- 新增 `rateRingClass` / `rateRingStyle` 计算属性:由 `pass_rate` 计算 `conic-gradient` 环形渐变
- 新增 `todayStr` 计算属性:当前日期 `YYYY-MM-DD`
- 移除 `moduleData` / `moduleChartRef` / `moduleChart` / `initModuleChart``statsApi.getModules()` 请求
- 保留 `loadStats`(并行拉取 overview 与 trend)/ `runAllTests` / 导航方法
- 保留 `chartType` 切换逻辑与窗口 resize 监听
### Phase 3:样式重写(`Dashboard.vue` style)
1. `.hero-banner`:深蓝渐变 `linear-gradient(120deg, #1f3a5f, #2b5a8c, #3a7bd5)`,圆角 10px,白字
2. `.hstat`:竖线分隔 `border-right:1px solid rgba(255,255,255,.18)`,数值 26px 粗体
3. `.rate-ring`:96px 圆,CSS 渐变进度环 + 中心数值 18px 粗体
4. `.quick-item`:flex 横排,图标色块 38px 圆角,主标题 13px 粗体,副标题 11px 灰色,hover 蓝色高亮与上浮
5. `.chart-container``height: 260px`
6. 删除原 `.stats-card` / `.stats-content` 等废弃样式
---
## 四、验收清单
### 布局结构(F01)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| Hero 横幅 → 快捷操作 → 全宽趋势图 → 最近执行表格 | 顺序正确 | ☑ |
### Hero 横幅(F02)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| 横幅标题 + 副标题 | 展示「测试总览」+ 当前日期/周期 | ☑ |
| 内嵌 3 指标 | 数值与 overview 接口一致 (通过/失败/用例数) | ☑ |
| 通过率环形环色 | ≥90 绿 / ≥70 橙 / <70 红 | ☑ |
| 通过率数值 | 同接口数据 | ☑ |
### 快捷操作(F03)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| 位置 | Hero 下方 | ☑ |
| 点击跳转 | `/recorder` `/execution` `/modules` `/reports` | ☑ |
| 执行全部测试 | 启动后跳 `/execution` | ☑ |
### 图表区(F04)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| 全宽趋势图渲染 | 正常(span=24) | ☑ |
| 移除模块饼图 | 页面无残留 | ☑ |
| 高度 260px | 是 | ☑ |
| 图表类型切换 | 可用 | ☑ |
### 最近执行(F05)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| 全宽表格 7 列 | 是 | ☑ |
| 数据正确 | 是 | ☑ |
### 加载/错误态(F06)
| 验收项 | 预期 | 状态 |
|--------|------|------|
| 加载失败 ElMessage.error | 是 | ☑ |
| 数据为 0 横幅正常 | 是 | ☑ |
---
## 五、验证命令
```bash
cd frontend && npm run build # vue-tsc 类型检查 + Vite 打包,零报错
```
---
## 六、部署计划
后端无改动。前端 build 后如需上 5.60 服务器,参考 `upload_frontend.py``deploy_dingtalk.py`(SFTP 上传 dist 即可,无容器重启)。
> ⚠️ 部署前确认 5.44/5.202/5.60 是否同步需求,等待用户安排。
---
## 七、回滚方案
1. `git checkout -- frontend/src/views/Dashboard.vue` 恢复原文件
2. 前端重新 build
3. 已部署环境重新上传 dist
---
*由 Claude Code 生成 · 2026-09-10*
\ No newline at end of file
# HANDOFF — UI自动化测试交接文档
> **生成时间**: 2026-09-09
> **生成时间**: 2026-09-10
> **当前分支**: `platform-auto-test`
> **最近提交**: `5e09904a` fix(recorder): _run_session launch 前 DISPLAY 兜底 + 防回归测试
> **状态**: 🔵 **会话67 进行中(2026-09-09):5.44 定时执行被取消 15.25% 误导告警修复(形态 4 停止通知)**(历史会话见下章节)
> **最近提交**: `82d38613` docs(performance): 更新混合场景执行异常排查修复到 HANDOFF_性能测试.md
> **状态**: 🔵 **会话68 进行中(2026-09-10):首页排版优化(深色数据驾驶舱 方案B)+ 移除模块饼图 + 5.60 二次部署**(历史会话见下章节)
---
## 📊 当前状态(会话 67,2026-09-09 · 5.44 定时执行被取消误导告警修复 + 形态 4 停止通知)
## 📊 当前状态(会话 68,2026-09-10 · 首页排版优化:深色数据驾驶舱 + 移除模块饼图 + 5.60 部署)
**背景**:原首页(`Dashboard.vue`)采用纵向四段式排版(4张独立指标卡片、快捷操作沉底首屏外、数字与图表割裂、通过率核心指标无视觉焦点)。按需求完成「数据驾驶舱」排版改造(方案 B:深色 Hero 横幅 + 指标内嵌 + 快捷操作上浮 + 保留全宽 7 列表格)。**用户随后反馈「模块用例分布饼图太丑」,已彻底移除饼图,执行趋势折线图改为 span=24 全宽展示**(与下方全宽表格视觉流统一)。
### ✅ 方案与改动清单
- **需求文档**`Docs/PRD/排版优化/需求文档/_PRD_首页排版优化_数据驾驶舱.md`(已同步去饼图方案,验收项勾选完成)
- **计划执行文档**`Docs/PRD/排版优化/需求文档/_PRD_首页排版优化_数据驾驶舱_计划执行.md`(状态已更新为已完成)
- **设计效果图**`Docs/PRD/排版优化/效果图/首页排版优化_效果图.html`(含 A/B/C 方案对比)
- **去饼图后真机截图**`Docs/PRD/排版优化/效果图/首页_新布局_无饼图.png`
- **唯一修改代码文件**`frontend/src/views/Dashboard.vue`
| 区域 | 改造前 | 改造后(方案 B + 去饼图) |
|------|--------|----------------|
| **顶部首屏** | 4 张独立 stats-card 白底卡片(无焦点) | **深色 Hero 横幅**`linear-gradient(120deg, #1f3a5f, #2b5a8c, #3a7bd5)`):左侧标题与动态日期周期、中间 3 指标竖线分隔(本月通过/本月失败/总用例数)、右侧通过率环形图(纯 CSS `conic-gradient`,≥90 绿 / ≥70 橙 / <70 红) |
| **快捷操作** | 沉在页面最底部(4 方块) | **上浮到第二位**:4 个快捷色块卡片(录制用例 蓝、执行全部测试 绿、管理模块 橙、查看报告 红),带副标题说明,hover 上浮阴影 |
| **图表区** | 执行趋势折线图(span16) + 模块饼图(span8),300px 高 | **移除模块饼图,趋势折线图 span=24 全宽**,高度 260px,「执行次数/通过率」切换保留 |
| **最近执行** | 7 列表格位于中间 | 调整到图表下方,**完整保留全宽 7 列表格**及各列样式、详情跳转 |
**去饼图代码清理(已验证无残留)**:模板仅剩全宽趋势图卡片;script 中 `moduleData`/`moduleChartRef`/`moduleChart`/`initModuleChart` 全部删除,`loadStats` 只并发请求 `getOverview(30)` + `getTrend(30)``getModules` 调用已移除);DOM 断言 `text=模块用例分布` count=0。数据为 0 时各区块正常兜底显示。
### ✅ 验证结果
- **前端构建**`npm run build``vue-tsc` 类型检查 + Vite 打包)**零报错通过**(35.71s,去饼图后版本)。
- **实机截图(Playwright headless)**:访问 `http://127.0.0.1:3000` 截图保存至 `Docs/PRD/排版优化/效果图/首页_新布局_无饼图.png`(1440x1000 full_page),核对深色横幅、环形图、快捷色块、全宽趋势图、最近执行表格渲染均正常;断言 `模块用例分布` 文本 count=0、`执行趋势` count=1、`vite-error-overlay` count=0。
- 注:截图数据为 0 系本地后端 8001 未启动(vite 代理 ECONNREFUSED),非前端缺陷。
- **F01–F06 验收标准**全部达标。
### ✅ 5.60 部署(2026-09-10,共两次)
1. **首次(方案 B 带饼图版)**:预检无 running 执行后 `python upload_frontend.py` 覆盖 `/data/third_party/plat-auto-test/frontend/dist/`,远端核验时间戳确认生效。
2. **二次(去饼图终版)**:重新 build 后再次 `python upload_frontend.py` 全量覆盖(本次会话已执行,`Dashboard-CITrWrPW.js` / `Dashboard-DL1yhznm.css` 为当前线上版本)。无需重启容器,nginx 挂载即时生效。访问地址:`http://192.168.5.60`
### ⚠️ 待办(下次会话从这里继续)
1. **5.44 / 5.202 同步**:确认是否需要同步部署前端 dist 到 5.44 和 5.202。
3. **承接会话 67 遗留**
- 钉钉形态 4 停止通知功能(已在会话 67 写完,单测通过),三台服务器后端部署尚未执行(需部署 `dingtalk_notify_service.py``execution_service.py` 等)。
- 5.202/5.44 定时任务撞车与低通过率原因治理。
- 清理未跟踪诊断脚本。
---
## 📊 历史状态(会话 67,2026-09-09 · 5.44 定时执行被取消误导告警修复 + 形态 4 停止通知)
**背景**:用户收到 5.44 钉钉报告 exec_0043d9cce07248a084f40a2bc31787fc(总数 354 / 通过 54 / 失败 8 / 跳过 292 / 通过率 15.25%),而 9 月 8 日正常水平是 72-78%。排查结论 + 修复全 record 于 `Docs/PRD/问题处理/执行中心/_问题处理_5.44定时执行被取消导致钉钉报告通过率15.25%.md``_执行计划_...md`(v2)。
......@@ -812,4 +855,4 @@
---
*本文档由 Claude Code 于 2026-09-09 更新(会话 66 进行中:5.44 定时任务失败用例修复同步 5.60 部署环境——18 条已写入、首条基准用例 5.60 验证通过 exec_6fd29fe9、遗留 15 条 name 乱码待修;历史:会话 65 noVNC 实时画面修复(iframe 误用 ws:// 协议);会话 64 录制器分辨率 1920x1080 + 部署权限修复;会话 63 定时任务未发送钉钉报告修复(`a4fe8140`);会话 62 sut-explore 缺口补齐——会务/预定2.0/巡检报表深度用例 12/12 全量通过 + 三台 MySQL 同步;会话 61 UI 执行监控目标服务器 Java 服务日志功能落地(TargetLogMonitor + ServerLogDrawer,提交 `18ed14cc`);会话 60 远程桌面录制器 Xvfb+noVNC 全量落地,提交 `978d4352`;会话 57 双窗口工作合并——钉钉通知版面重构与执行进度 total_cases 修复经 git rebase 合并提交推送;报告中心放开失败/取消历史执行随会话 57 一并归档。更早:会话 53 5.44 登录/落库双 P0 闭环(`f363abdd`);会话 52 真录制器 Phase 5(`b80abae3`);会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。*
*本文档由 Claude Code 于 2026-09-10 更新(会话 68 进行中:首页排版优化深色数据驾驶舱(方案B)+ 移除模块饼图趋势图全宽 + 5.60 两次部署;历史:会话 66 5.44 定时任务失败用例修复同步 5.60 部署环境;会话 65 noVNC 实时画面修复(iframe 误用 ws:// 协议);会话 64 录制器分辨率 1920x1080 + 部署权限修复;会话 63 定时任务未发送钉钉报告修复(`a4fe8140`);会话 62 sut-explore 缺口补齐——会务/预定2.0/巡检报表深度用例 12/12 全量通过 + 三台 MySQL 同步;会话 61 UI 执行监控目标服务器 Java 服务日志功能落地(TargetLogMonitor + ServerLogDrawer,提交 `18ed14cc`);会话 60 远程桌面录制器 Xvfb+noVNC 全量落地,提交 `978d4352`;会话 57 双窗口工作合并——钉钉通知版面重构与执行进度 total_cases 修复经 git rebase 合并提交推送;报告中心放开失败/取消历史执行随会话 57 一并归档。更早:会话 53 5.44 登录/落库双 P0 闭环(`f363abdd`);会话 52 真录制器 Phase 5(`b80abae3`);会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。*
<!--
页面名称:Dashboard.vue
页面描述:测试总览页面,展示统计数据、趋势图表和最近执行记录
页面描述:测试总览页面(数据驾驶舱),深色 Hero 横幅 + 快捷操作 + 图表 + 最近执行记录
@author czj
@date 2026-07-09
@last-modified 2026-09-10
-->
<template>
<div class="dashboard-page">
<!-- 统计卡片 -->
<el-row :gutter="20" class="stats-row">
<!-- 深色 Hero 横幅:标题 + 内嵌指标 + 通过率环形 -->
<div class="hero-banner">
<div class="hero-left">
<div class="hero-title">测试总览</div>
<div class="hero-subtitle">
{{ todayStr }} · 近 {{ stats.period_days }} 天数据 · 定时任务持续调度中
</div>
</div>
<div class="hero-stats">
<div class="hstat">
<div class="hstat-value">{{ heroStats.passed }}</div>
<div class="hstat-label">本月通过</div>
<div class="hstat-footer">执行 {{ stats.executions.total }}</div>
</div>
<div class="hstat">
<div class="hstat-value">{{ heroStats.failed }}</div>
<div class="hstat-label">本月失败</div>
<div class="hstat-footer">跳过 {{ stats.case_runs.skipped }}</div>
</div>
<div class="hstat">
<div class="hstat-value">{{ heroStats.totalCases }}</div>
<div class="hstat-label">总用例数</div>
<div class="hstat-footer">{{ stats.modules.total }} 个模块</div>
</div>
</div>
<div class="rate-ring" :class="rateRingClass">
<div class="rate-ring-inner" :style="rateRingStyle">
<div class="rate-ring-value">
{{ stats.case_runs.pass_rate }}<span class="rate-unit">%</span>
</div>
<div class="rate-ring-label">通过率</div>
</div>
</div>
</div>
<!-- 快捷操作(上浮到第二位) -->
<el-row :gutter="16" class="quick-row">
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<el-icon class="stats-icon passed"><CircleCheck /></el-icon>
<div class="stats-info">
<span class="stats-value">{{ stats.case_runs.passed }}</span>
<span class="stats-label">本月通过</span>
</div>
<div class="quick-item" @click="goToRecorder">
<div class="quick-icon" style="background: #409eff">
<el-icon :size="20"><VideoCamera /></el-icon>
</div>
<div class="stats-footer">
<span class="footer-text">执行 {{ stats.executions.total }}</span>
<div class="quick-text">
<span class="quick-title">录制用例</span>
<span class="quick-desc">可视化录制回放</span>
</div>
</el-card>
</div>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<el-icon class="stats-icon failed"><CircleClose /></el-icon>
<div class="stats-info">
<span class="stats-value">{{ stats.case_runs.failed }}</span>
<span class="stats-label">本月失败</span>
</div>
<div class="quick-item" @click="runAllTests">
<div class="quick-icon" style="background: #67c23a">
<el-icon :size="20"><VideoPlay /></el-icon>
</div>
<div class="stats-footer">
<span class="footer-text">跳过 {{ stats.case_runs.skipped }}</span>
<div class="quick-text">
<span class="quick-title">执行全部测试</span>
<span class="quick-desc">一键全量回归</span>
</div>
</el-card>
</div>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<el-icon class="stats-icon rate"><TrendCharts /></el-icon>
<div class="stats-info">
<span class="stats-value">{{ stats.case_runs.pass_rate }}%</span>
<span class="stats-label">平均通过率</span>
</div>
<div class="quick-item" @click="goToModules">
<div class="quick-icon" style="background: #e6a23c">
<el-icon :size="20"><Folder /></el-icon>
</div>
<div class="stats-footer">
<span
class="footer-text"
:class="passRateStatus"
>
{{ passRateStatusText }}
</span>
<div class="quick-text">
<span class="quick-title">管理模块</span>
<span class="quick-desc">用例组织维护</span>
</div>
</el-card>
</div>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<el-icon class="stats-icon total"><DataBoard /></el-icon>
<div class="stats-info">
<span class="stats-value">{{ stats.cases.total }}</span>
<span class="stats-label">总用例数</span>
</div>
<div class="quick-item" @click="goToReports">
<div class="quick-icon" style="background: #f56c6c">
<el-icon :size="20"><DataLine /></el-icon>
</div>
<div class="stats-footer">
<span class="footer-text">{{ stats.modules.total }} 个模块</span>
<div class="quick-text">
<span class="quick-title">查看报告</span>
<span class="quick-desc">历史执行报告</span>
</div>
</el-card>
</div>
</el-col>
</el-row>
<!-- 图表区域 -->
<el-row :gutter="20" class="charts-row">
<el-col :span="16">
<el-col :span="24">
<el-card class="chart-card" shadow="never">
<template #header>
<div class="card-header">
......@@ -89,14 +110,6 @@
<div ref="trendChartRef" class="chart-container"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="chart-card" shadow="never">
<template #header>
<span>模块用例分布</span>
</template>
<div ref="moduleChartRef" class="chart-container"></div>
</el-card>
</el-col>
</el-row>
<!-- 最近执行记录 -->
......@@ -156,63 +169,24 @@
</el-table-column>
</el-table>
</el-card>
<!-- 快捷操作 -->
<el-card class="quick-actions" shadow="never">
<template #header>
<span>快捷操作</span>
</template>
<el-row :gutter="20">
<el-col :span="6">
<div class="action-item" @click="goToRecorder">
<el-icon :size="32"><VideoCamera /></el-icon>
<span>录制用例</span>
</div>
</el-col>
<el-col :span="6">
<div class="action-item" @click="runAllTests">
<el-icon :size="32"><VideoPlay /></el-icon>
<span>执行全部测试</span>
</div>
</el-col>
<el-col :span="6">
<div class="action-item" @click="goToModules">
<el-icon :size="32"><Folder /></el-icon>
<span>管理模块</span>
</div>
</el-col>
<el-col :span="6">
<div class="action-item" @click="goToReports">
<el-icon :size="32"><DataLine /></el-icon>
<span>查看报告</span>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup lang="ts">
/**
* Dashboard 总览页面
* Dashboard 总览页面(数据驾驶舱)
*
* 功能:
* 1. 展示统计数据卡片(通过数、失败数、通过率、用例数)
* 2. 展示执行趋势图表(ECharts)
* 3. 展示模块用例分布(饼图
* 1. 深色 Hero 横幅:标题 + 内嵌核心指标(通过/失败/用例数)+ 通过率环形
* 2. 快捷操作(图标色块卡片):录制用例 / 执行全部测试 / 管理模块 / 查看报告
* 3. 展示执行趋势图表(ECharts,全宽
* 4. 展示最近执行记录
* 5. 提供快捷操作入口
*/
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
CircleCheck,
CircleClose,
TrendCharts,
DataBoard,
VideoCamera,
VideoPlay,
Folder,
......@@ -247,32 +221,50 @@ const stats = ref({
// 趋势数据
const trendData = ref<any[]>([])
// 模块数据
const moduleData = ref<any[]>([])
// 图表类型
const chartType = ref<'executions' | 'passRate'>('executions')
// 图表引用
const trendChartRef = ref<HTMLElement>()
const moduleChartRef = ref<HTMLElement>()
let trendChart: echarts.ECharts | null = null
let moduleChart: echarts.ECharts | null = null
// ==================== 计算属性 ====================
const passRateStatus = computed(() => {
const rate = stats.value.case_runs.pass_rate
if (rate >= 90) return 'status-success'
if (rate >= 70) return 'status-warning'
return 'status-danger'
/** Hero 横幅内嵌指标 */
const heroStats = computed(() => ({
passed: stats.value.case_runs.passed,
failed: stats.value.case_runs.failed,
totalCases: stats.value.cases.total,
}))
/** 当前日期字符串(YYYY-MM-DD) */
const todayStr = computed(() => {
const d = new Date()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${d.getFullYear()}-${m}-${day}`
})
const passRateStatusText = computed(() => {
/** 通过率环形容器配色 class(≥90 绿 / ≥70 橙 / <70 红) */
const rateRingClass = computed(() => {
const rate = stats.value.case_runs.pass_rate
if (rate >= 90) return '状态良好'
if (rate >= 70) return '需要关注'
return '需要改进'
if (rate >= 90) return 'ring-success'
if (rate >= 70) return 'ring-warning'
return 'ring-danger'
})
/** 通过率环形 conic-gradient 进度(环色随区间变化) */
const rateRingStyle = computed(() => {
const rate = Math.min(Math.max(stats.value.case_runs.pass_rate, 0), 100)
const colors: Record<string, string> = {
'ring-success': '#7ee2a8',
'ring-warning': '#f3d19e',
'ring-danger': '#fab6b6'
}
const color = colors[rateRingClass.value] || '#7ee2a8'
return {
background: `conic-gradient(${color} 0 ${rate}%, rgba(255,255,255,.15) ${rate}% 100%)`
}
})
// ==================== 方法定义 ====================
......@@ -280,20 +272,17 @@ const passRateStatusText = computed(() => {
const loadStats = async () => {
loading.value = true
try {
const [overviewRes, trendRes, modulesRes] = await Promise.all([
const [overviewRes, trendRes] = await Promise.all([
statsApi.getOverview(30),
statsApi.getTrend(30),
statsApi.getModules(),
])
stats.value = overviewRes
trendData.value = trendRes.trend
moduleData.value = modulesRes.modules
// 初始化图表
await nextTick()
initTrendChart()
initModuleChart()
} catch (error: any) {
console.error('加载统计数据失败:', error)
ElMessage.error('加载统计数据失败')
......@@ -392,63 +381,6 @@ const initTrendChart = () => {
trendChart.setOption(option)
}
const initModuleChart = () => {
if (!moduleChartRef.value) return
if (moduleChart) {
moduleChart.dispose()
}
moduleChart = echarts.init(moduleChartRef.value)
const data = moduleData.value.map(m => ({
name: m.name,
value: m.case_count
})).filter(d => d.value > 0)
const option: echarts.EChartsOption = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center'
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
center: ['60%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: data
}
]
}
moduleChart.setOption(option)
}
const getStatusType = (status: string) => {
const typeMap: Record<string, string> = {
completed: 'success',
......@@ -519,7 +451,6 @@ watch(chartType, () => {
// 窗口大小变化时重绘图表
const handleResize = () => {
trendChart?.resize()
moduleChart?.resize()
}
// ==================== 生命周期 ====================
......@@ -532,64 +463,166 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
trendChart?.dispose()
moduleChart?.dispose()
})
</script>
<style lang="scss" scoped>
.dashboard-page {
.stats-row {
margin-bottom: 20px;
}
/* ===== Hero 横幅(深色驾驶舱) ===== */
.hero-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 22px 24px;
margin-bottom: 16px;
border-radius: 10px;
color: #fff;
background: linear-gradient(120deg, #1f3a5f 0%, #2b5a8c 55%, #3a7bd5 100%);
box-shadow: 0 4px 16px rgba(31, 58, 95, 0.25);
.hero-left {
.hero-title {
font-size: 20px;
font-weight: 700;
letter-spacing: 0.5px;
}
.hero-subtitle {
font-size: 12px;
opacity: 0.75;
margin-top: 6px;
}
}
.stats-card {
.stats-content {
.hero-stats {
display: flex;
align-items: center;
gap: 16px;
flex: 1;
justify-content: center;
.hstat {
padding: 0 26px;
text-align: center;
border-right: 1px solid rgba(255, 255, 255, 0.18);
&:last-child {
border-right: none;
}
.stats-icon {
font-size: 48px;
.hstat-value {
font-size: 26px;
font-weight: 700;
line-height: 1.2;
}
.hstat-label {
font-size: 12px;
opacity: 0.7;
margin-top: 3px;
}
&.passed { color: #67c23a; }
&.failed { color: #f56c6c; }
&.rate { color: #409eff; }
&.total { color: #e6a23c; }
.hstat-footer {
font-size: 11px;
opacity: 0.55;
margin-top: 3px;
}
}
}
/* 通过率环形(conic-gradient 由内联 style 注入) */
.rate-ring {
width: 96px;
height: 96px;
border-radius: 50%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
.stats-info {
.rate-ring-inner {
width: 74px;
height: 74px;
border-radius: 50%;
background: #2b5a8c;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.stats-value {
font-size: 32px;
font-weight: 600;
color: #303133;
.rate-ring-value {
font-size: 18px;
font-weight: 700;
line-height: 1.1;
.rate-unit {
font-size: 11px;
font-weight: 400;
opacity: 0.8;
}
}
.stats-label {
font-size: 14px;
color: #909399;
.rate-ring-label {
font-size: 10px;
opacity: 0.65;
margin-top: 2px;
}
}
}
}
.stats-footer {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
/* ===== 快捷操作(上浮色块卡片) ===== */
.quick-row {
margin-bottom: 16px;
.footer-text {
font-size: 13px;
color: #909399;
.quick-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: #fff;
border: 1px solid #e6ebf5;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
&.status-success { color: #67c23a; }
&.status-warning { color: #e6a23c; }
&.status-danger { color: #f56c6c; }
&:hover {
border-color: #409eff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
transform: translateY(-2px);
}
.quick-icon {
width: 38px;
height: 38px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
flex-shrink: 0;
}
.quick-text {
display: flex;
flex-direction: column;
min-width: 0;
.quick-title {
font-size: 13px;
font-weight: 600;
color: #303133;
}
.quick-desc {
font-size: 11px;
color: #909399;
margin-top: 2px;
}
}
}
}
/* ===== 图表区 ===== */
.charts-row {
margin-bottom: 20px;
......@@ -601,11 +634,12 @@ onUnmounted(() => {
}
.chart-container {
height: 300px;
height: 260px;
}
}
}
/* ===== 最近执行记录 ===== */
.recent-executions {
margin-bottom: 20px;
......@@ -619,29 +653,5 @@ onUnmounted(() => {
.rate-warning { color: #e6a23c; font-weight: 600; }
.rate-danger { color: #f56c6c; font-weight: 600; }
}
.quick-actions {
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px;
background: #f5f7fa;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #e6ebf5;
transform: translateY(-2px);
}
span {
font-size: 14px;
color: #606266;
}
}
}
}
</style>
\ No newline at end of file
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论