修复仪表盘6个数据问题+补充测试规范图表维度

1. 最新告警:修复createdAt映射(snake_case→PascalCase),补齐alertType/machineName/title/isResolved字段
2. 机床状态分布:从原始group-by改为返回{online,offline,disabled}结构
3. 产量趋势:Controller包装为{items:[]}格式
4. 机床排行:补齐rank/program/status字段
5. 工人排行:补齐rank/machineCount/totalQuantity字段
6. 测试规范:新增维度3.1图表验证,扩展仪表盘速查表维度覆盖(1,2,3,3.1,5,6,9,12,17,20),新增反模式第14条
main
haoliang 4 days ago
parent 0acc2c7ced
commit 7354ae1641

@ -58,6 +58,7 @@
| 1 | 页面加载 | URL正确 + 关键元素可见 | 所有页面 | 只goto不等待 | | 1 | 页面加载 | URL正确 + 关键元素可见 | 所有页面 | 只goto不等待 |
| 2 | 默认数据 | 打开即有数据(行数>0 / 卡片有值) | 有数据展示的页面 | 不检查数据 | | 2 | 默认数据 | 打开即有数据(行数>0 / 卡片有值) | 有数据展示的页面 | 不检查数据 |
| 3 | 汇总卡片数值 | ≠ 空 ≠ `-``--``undefined``null` | 有统计卡片的页面 | 只检查可见 | | 3 | 汇总卡片数值 | ≠ 空 ≠ `-``--``undefined``null` | 有统计卡片的页面 | 只检查可见 |
| 3.1 | 图表有数据 | Canvas/SVG已渲染 + 有数据点/色块(非空白) | 有ECharts/Chart组件的页面 | 只检查容器可见 |
| 4 | 表格行数 | `.el-table__row` > 0 | 有表格的页面 | 只检查table可见 | | 4 | 表格行数 | `.el-table__row` > 0 | 有表格的页面 | 只检查table可见 |
| 5 | 表格每列有值 | 第一行逐列文本非空 | 有表格的页面 | 只检查前2-3列 | | 5 | 表格每列有值 | 第一行逐列文本非空 | 有表格的页面 | 只检查前2-3列 |
| 6 | 时间/数值列 | 有具体数值非空非NULL | 有时间/数值列的页面 | 跳过 | | 6 | 时间/数值列 | 有具体数值非空非NULL | 有时间/数值列的页面 | 跳过 |
@ -82,7 +83,7 @@
|----------|-------------| |----------|-------------|
| 列表页(表格+筛选) | 1,2,4,5,8,9,10,13,14,19 | | 列表页(表格+筛选) | 1,2,4,5,8,9,10,13,14,19 |
| 列表页(表格+筛选+卡片) | 1,2,3,4,5,6,7,8,9,10,13,14,17,19 | | 列表页(表格+筛选+卡片) | 1,2,3,4,5,6,7,8,9,10,13,14,17,19 |
| 仪表盘/概览页 | 1,2,3,20 | | 仪表盘/概览页 | 1,2,3,3.1,5,6,9,12,17,20 |
| 详情/表单页 | 1,15,16 | | 详情/表单页 | 1,15,16 |
| 所有页面 | 1,20 | | 所有页面 | 1,20 |
@ -104,6 +105,7 @@
| 元素 | 验证标准 | | 元素 | 验证标准 |
|------|----------| |------|----------|
| 汇总卡片 | 非空 ∧ ≠ `""` ∧ ≠ `"-"` ∧ ≠ `"--"` ∧ ≠ `"undefined"` ∧ ≠ `"null"` | | 汇总卡片 | 非空 ∧ ≠ `""` ∧ ≠ `"-"` ∧ ≠ `"--"` ∧ ≠ `"undefined"` ∧ ≠ `"null"` |
| 图表(ECharts等) | Canvas/SVG存在 + 非空白(有数据点/扇形/柱状) |
| 表格数据列 | 第一行每列有文本值 | | 表格数据列 | 第一行每列有文本值 |
| 状态标签 | 已知中文文案 | | 状态标签 | 已知中文文案 |
| 时间列 | 有值且格式正确 | | 时间列 | 有值且格式正确 |
@ -183,3 +185,4 @@ $headers = @{ Authorization = "Bearer $token" }
| 11 | 测试不按维度表逐项覆盖 | 对照5.0维度总表 | | 11 | 测试不按维度表逐项覆盖 | 对照5.0维度总表 |
| 12 | 测试只覆盖部分页面 | 每个改动涉及的页面都要覆盖 | | 12 | 测试只覆盖部分页面 | 每个改动涉及的页面都要覆盖 |
| 13 | 新增维度后不更新维度总表 | 新发现的问题必须补充为新维度 | | 13 | 新增维度后不更新维度总表 | 新发现的问题必须补充为新维度 |
| 14 | 图表只检查容器DOM存在 | 验证Canvas/SVG有实际渲染内容数据点/色块) |

@ -8,8 +8,15 @@ namespace CncModels.Dto.Dashboard
public class AlertListItem public class AlertListItem
{ {
public long Id { get; set; } public long Id { get; set; }
public string Message { get; set; } /// <summary>告警时间</summary>
public string Severity { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
/// <summary>告警类型</summary>
public string AlertType { get; set; }
/// <summary>机床名称</summary>
public string MachineName { get; set; }
/// <summary>告警标题</summary>
public string Title { get; set; }
/// <summary>是否已处理</summary>
public bool IsResolved { get; set; }
} }
} }

@ -7,8 +7,17 @@ namespace CncModels.Dto.Dashboard
/// </summary> /// </summary>
public class MachineRankResponse public class MachineRankResponse
{ {
/// <summary>排名</summary>
public int Rank { get; set; }
/// <summary>机床ID</summary>
public int MachineId { get; set; } public int MachineId { get; set; }
/// <summary>机床名称</summary>
public string MachineName { get; set; } public string MachineName { get; set; }
/// <summary>当前NC程序名</summary>
public string Program { get; set; }
/// <summary>产量</summary>
public int Quantity { get; set; } public int Quantity { get; set; }
/// <summary>机床状态1=在线 0=离线</summary>
public int Status { get; set; }
} }
} }

@ -7,7 +7,13 @@ namespace CncModels.Dto.Dashboard
/// </summary> /// </summary>
public class WorkerRankResponse public class WorkerRankResponse
{ {
/// <summary>排名</summary>
public int Rank { get; set; }
/// <summary>工人姓名</summary>
public string WorkerName { get; set; } public string WorkerName { get; set; }
public int Quantity { get; set; } /// <summary>绑定机床数</summary>
public int MachineCount { get; set; }
/// <summary>总产量</summary>
public int TotalQuantity { get; set; }
} }
} }

@ -137,7 +137,11 @@ namespace CncRepository.Impl.Dashboard
var sql = @" var sql = @"
SELECT m.id AS MachineId, SELECT m.id AS MachineId,
m.name AS MachineName, m.name AS MachineName,
COALESCE(SUM(ad.day_quantity), 0) AS Quantity COALESCE(SUM(ad.day_quantity), 0) AS Quantity,
CAST(m.is_online AS SIGNED) AS Status,
(SELECT seg.program_name FROM cnc_production_segment seg
WHERE seg.machine_id = m.id AND seg.production_date = CURDATE()
ORDER BY seg.id DESC LIMIT 1) AS Program
FROM cnc_machine m FROM cnc_machine m
LEFT JOIN ( LEFT JOIN (
SELECT machine_id, production_date, day_quantity FROM ( SELECT machine_id, production_date, day_quantity FROM (
@ -158,10 +162,13 @@ namespace CncRepository.Impl.Dashboard
) )
GROUP BY seg.machine_id, seg.production_date GROUP BY seg.machine_id, seg.production_date
) ad ON ad.machine_id = m.id ) ad ON ad.machine_id = m.id
GROUP BY m.id, m.name GROUP BY m.id, m.name, m.is_online
ORDER BY Quantity DESC ORDER BY Quantity DESC
LIMIT @Top"; LIMIT @Top";
return conn.Query<MachineRankResponse>(sql, new { StartDate = startDate, EndDate = endDate, Top = top }).ToList(); var rows = conn.Query<MachineRankResponse>(sql, new { StartDate = startDate, EndDate = endDate, Top = top }).ToList();
// 填充排名
for (int i = 0; i < rows.Count; i++) rows[i].Rank = i + 1;
return rows;
} }
} }
@ -172,7 +179,8 @@ namespace CncRepository.Impl.Dashboard
{ {
var sql = @" var sql = @"
SELECT w.name AS WorkerName, SELECT w.name AS WorkerName,
COALESCE(SUM(ad.day_quantity), 0) AS Quantity COUNT(DISTINCT wm.machine_id) AS MachineCount,
COALESCE(SUM(ad.day_quantity), 0) AS TotalQuantity
FROM cnc_worker w FROM cnc_worker w
LEFT JOIN cnc_worker_machine wm ON wm.worker_id = w.id LEFT JOIN cnc_worker_machine wm ON wm.worker_id = w.id
LEFT JOIN ( LEFT JOIN (
@ -195,9 +203,12 @@ namespace CncRepository.Impl.Dashboard
GROUP BY seg.machine_id, seg.production_date GROUP BY seg.machine_id, seg.production_date
) ad ON ad.machine_id = wm.machine_id ) ad ON ad.machine_id = wm.machine_id
GROUP BY w.id, w.name GROUP BY w.id, w.name
ORDER BY Quantity DESC ORDER BY TotalQuantity DESC
LIMIT @Top"; LIMIT @Top";
return conn.Query<WorkerRankResponse>(sql, new { StartDate = startDate, EndDate = endDate, Top = top }).ToList(); var rows = conn.Query<WorkerRankResponse>(sql, new { StartDate = startDate, EndDate = endDate, Top = top }).ToList();
// 填充排名
for (int i = 0; i < rows.Count; i++) rows[i].Rank = i + 1;
return rows;
} }
} }
@ -232,13 +243,15 @@ namespace CncRepository.Impl.Dashboard
} }
} }
/// <summary>机床状态分布(示意性实现,需要根据实际状态表结构调整)</summary> /// <summary>机床状态分布</summary>
public List<dynamic> GetMachineStatusDistribution() public object GetMachineStatusDistribution()
{ {
using (var conn = CreateConnection()) using (var conn = CreateConnection())
{ {
var sql = @"SELECT last_device_status AS Status, COUNT(1) AS Count FROM cnc_machine GROUP BY last_device_status"; var online = conn.ExecuteScalar<int>("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND is_online = 1");
return conn.Query(sql).ToList(); var offline = conn.ExecuteScalar<int>("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 1 AND is_online = 0");
var disabled = conn.ExecuteScalar<int>("SELECT COUNT(1) FROM cnc_machine WHERE is_enabled = 0");
return new { online, offline, disabled };
} }
} }
@ -247,8 +260,12 @@ namespace CncRepository.Impl.Dashboard
{ {
using (var conn = CreateConnection()) using (var conn = CreateConnection())
{ {
var sql = @"SELECT id, title AS Message, alert_type AS Severity, created_at FROM cnc_alert ORDER BY created_at DESC LIMIT @Count"; var sql = @"SELECT a.id AS Id, a.created_at AS CreatedAt, a.alert_type AS AlertType,
// 如果 cnc_alert 不存在 Title/Severity后续可调整为 Message/Level 等字段 COALESCE(m.name, '') AS MachineName, a.title AS Title,
CAST(a.is_resolved AS SIGNED) AS IsResolved
FROM cnc_alert a
LEFT JOIN cnc_machine m ON m.id = a.machine_id
ORDER BY a.created_at DESC LIMIT @Count";
return conn.Query<AlertListItem>(sql, new { Count = count }).ToList(); return conn.Query<AlertListItem>(sql, new { Count = count }).ToList();
} }
} }

@ -19,7 +19,7 @@ namespace CncRepository.Interface
List<dynamic> GetProductionTrend(int days); List<dynamic> GetProductionTrend(int days);
List<dynamic> GetMachineStatusDistribution(); object GetMachineStatusDistribution();
List<AlertListItem> GetRecentAlerts(int count); List<AlertListItem> GetRecentAlerts(int count);
} }

@ -85,8 +85,8 @@ namespace CncWebApi.Controllers
[Route("trend")] [Route("trend")]
public IHttpActionResult GetProductionTrend(int days = 7) public IHttpActionResult GetProductionTrend(int days = 7)
{ {
var result = _dashboardService.GetProductionTrend(days); var result = _dashboardService.GetProductionTrend();
return Ok(ApiResponse<object>.Success(result)); return Ok(ApiResponse<object>.Success(new { items = result }));
} }
/// <summary> /// <summary>

Loading…
Cancel
Save