diff --git a/frontend/src/views/dashboard/DashboardPage.vue b/frontend/src/views/dashboard/DashboardPage.vue
index 46046df..13d537b 100644
--- a/frontend/src/views/dashboard/DashboardPage.vue
+++ b/frontend/src/views/dashboard/DashboardPage.vue
@@ -33,7 +33,7 @@
采集服务
-
+
ⓘ
@@ -42,13 +42,14 @@
{{ collectorStatusText }}
- 运行 {{ formatUptime(collectorStatus.uptimeSeconds) }}
+ 运行 {{ formatUptime(collectorStatus.uptimeSeconds) }}
- 启动采集
- 停止采集
- 刷新配置
-
+ 需手动启动服务
+ 恢复采集
+ 暂停采集
+ 刷新配置
+
@@ -402,25 +403,25 @@ function formatNumber(val: number | undefined | null): string {
return Number(val).toFixed(2)
}
-// 采集服务状态:综合心跳 + Windows服务状态
+// 采集服务状态:5种精确状态
const collectorTagType = computed(() => {
- const { serviceStatus, status } = collectorStatus.value
- if (serviceStatus === 'Running' && status === 'running') return 'success'
- if (serviceStatus === 'Running' && status !== 'running') return 'warning' // 进程在但心跳超时
- if (serviceStatus === 'NotInstalled') return 'danger'
- if (serviceStatus === 'StartFailed') return 'danger'
+ const { status } = collectorStatus.value
+ if (status === 'running') return 'success'
+ if (status === 'paused') return 'info'
+ if (status === 'timeout') return 'warning'
+ if (status === 'stopped') return 'info'
+ if (status === 'not_installed') return 'danger'
return 'warning'
})
const collectorStatusText = computed(() => {
- const { serviceStatus, status } = collectorStatus.value
- if (serviceStatus === 'Running' && status === 'running') return '运行中'
- if (serviceStatus === 'Running' && status !== 'running') return '心跳超时'
- if (serviceStatus === 'NotInstalled') return '未安装'
- if (serviceStatus === 'Stopped') return '已停止'
- if (serviceStatus === 'Starting') return '启动中'
- if (serviceStatus === 'StartFailed') return '启动失败'
- return serviceStatus || '-'
+ const { status } = collectorStatus.value
+ if (status === 'running') return '运行中'
+ if (status === 'paused') return '已暂停'
+ if (status === 'timeout') return '心跳超时'
+ if (status === 'stopped') return '已停止'
+ if (status === 'not_installed') return '未安装'
+ return status || '-'
})
function alertTypeTag(type: string): string {
@@ -443,7 +444,7 @@ function initWorkshopChart() {
trigger: 'axis',
formatter: (params: any) => {
const d = workshopData.value[params[0].dataIndex]
- return `${d.workshopName}
${unitLabel}产量: ${params[0].value} ${unit}
总产量: ${d.quantity} 件
机床数: ${d.machineCount} 台`
+ return `${d.workshopName}
${unitLabel}产量: ${Number(params[0].value).toFixed(2)} ${unit}
总产量: ${d.quantity} 件
机床数: ${d.machineCount} 台`
},
},
grid: { left: 60, right: 20, top: 20, bottom: 30 },
@@ -452,7 +453,7 @@ function initWorkshopChart() {
series: [{
type: 'bar', data: workshopData.value.map(i => i.avgQuantity),
itemStyle: { color: '#67C23A', borderRadius: [4, 4, 0, 0] }, barWidth: '40%',
- label: { show: true, position: 'top', formatter: `{c} ${unit}`, fontSize: 12 },
+ label: { show: true, position: 'top', formatter: (p: any) => `${Number(p.value).toFixed(2)} ${unit}`, fontSize: 12 },
}],
})
}
diff --git a/src/CncCollector/Core/ProductionTracker.cs b/src/CncCollector/Core/ProductionTracker.cs
index 2a19f4b..ed0fd7f 100644
--- a/src/CncCollector/Core/ProductionTracker.cs
+++ b/src/CncCollector/Core/ProductionTracker.cs
@@ -119,8 +119,11 @@ namespace CncCollector.Core
using (var conn = new MySqlConnection(_connectionString))
{
// 结账所有活跃段:is_settled=0 且 end_time IS NULL
+ // 正确计算quantity:保留当前end_part_count,用end-start计算产量
conn.Execute(@"UPDATE cnc_production_segment
- SET end_time = NOW(), end_part_count = start_part_count, quantity = 0,
+ SET end_time = NOW(),
+ end_part_count = COALESCE(end_part_count, start_part_count),
+ quantity = GREATEST(0, COALESCE(end_part_count, start_part_count) - start_part_count),
close_reason = @Reason, is_settled = 1, updated_at = NOW()
WHERE is_settled = 0 AND end_time IS NULL",
new { Reason = SegmentCloseReason.ServiceStop });
diff --git a/src/CncRepository/Impl/Dashboard/DashboardRepository.cs b/src/CncRepository/Impl/Dashboard/DashboardRepository.cs
index d13d674..abee519 100644
--- a/src/CncRepository/Impl/Dashboard/DashboardRepository.cs
+++ b/src/CncRepository/Impl/Dashboard/DashboardRepository.cs
@@ -94,7 +94,7 @@ namespace CncRepository.Impl.Dashboard
ELSE COALESCE(SUM(ad.day_quantity), 0) / (DATEDIFF(@EndDate, @StartDate) + 1) / NULLIF(COUNT(DISTINCT m.id), 0)
END AS AvgQuantity
FROM cnc_workshop w
- LEFT JOIN cnc_machine m ON m.workshop_id = w.id
+ LEFT JOIN cnc_machine m ON m.workshop_id = w.id AND m.is_enabled = 1
LEFT JOIN (
SELECT machine_id, production_date, day_quantity FROM (
SELECT dp.machine_id, dp.production_date, SUM(dp.total_quantity) AS day_quantity
diff --git a/src/CncService/Impl/DashboardService.cs b/src/CncService/Impl/DashboardService.cs
index f7dd673..cab35c4 100644
--- a/src/CncService/Impl/DashboardService.cs
+++ b/src/CncService/Impl/DashboardService.cs
@@ -111,8 +111,30 @@ namespace CncService.Impl
serviceStatusText = svc.ToString();
}
- // 组合状态:NotInstalled -> 停止,其他根据心跳决定
- string status = (serviceStatusText == "NotInstalled") ? "stopped" : (heartbeatRunning ? "running" : "stopped");
+ // 组合状态:精确区分5种情况
+ string status;
+ if (serviceStatusText == "NotInstalled")
+ {
+ status = "not_installed";
+ }
+ else if (serviceStatusText == "Stopped" || serviceStatusText == "StartFailed")
+ {
+ status = "stopped";
+ }
+ else if (heartbeatRunning)
+ {
+ status = "running";
+ }
+ else if (latest != null && latest.Status == "stopped")
+ {
+ // 引擎主动停止(暂停),心跳status='stopped'
+ status = "paused";
+ }
+ else
+ {
+ // 服务在运行但心跳超时
+ status = "timeout";
+ }
return new {
status,
diff --git a/src/CncWebApi/Controllers/DashboardController.cs b/src/CncWebApi/Controllers/DashboardController.cs
index 34f6195..4d27066 100644
--- a/src/CncWebApi/Controllers/DashboardController.cs
+++ b/src/CncWebApi/Controllers/DashboardController.cs
@@ -137,15 +137,21 @@ namespace CncWebApi.Controllers
try
{
dynamic statusObj = _dashboardService.GetCollectorStatus();
+ string status = statusObj?.status as string;
string serviceStatus = statusObj?.serviceStatus as string;
if (!string.IsNullOrEmpty(serviceStatus) && string.Equals(serviceStatus, "NotInstalled", StringComparison.OrdinalIgnoreCase))
{
return Ok(ApiResponse