修复仪表盘三个bug:采集状态精确区分5种+暂停恢复、产量结算保留实时值、车间平均产量排除停用机床

main
haoliang 3 hours ago
parent 724183997b
commit 4366cf9347

@ -33,7 +33,7 @@
<div class="stat-card"> <div class="stat-card">
<div class="stat-label"> <div class="stat-label">
采集服务 采集服务
<el-tooltip content="数据采集服务的运行状态。系统根据心跳表判断服务是否存活,心跳超时则显示异常。" placement="top"> <el-tooltip content="数据采集服务的运行状态。点击暂停可暂停采集引擎,需手动启动/停止Windows服务。" placement="top">
<span class="info-icon"></span> <span class="info-icon"></span>
</el-tooltip> </el-tooltip>
</div> </div>
@ -42,13 +42,14 @@
{{ collectorStatusText }} {{ collectorStatusText }}
</el-tag> </el-tag>
</div> </div>
<div class="stat-sub" v-if="collectorStatus.serviceStatus === 'Running' && collectorStatus.status === 'running'"> {{ formatUptime(collectorStatus.uptimeSeconds) }}</div> <div class="stat-sub" v-if="collectorStatus.status === 'running'"> {{ formatUptime(collectorStatus.uptimeSeconds) }}</div>
</div> </div>
<div class="collector-actions"> <div class="collector-actions">
<el-button v-if="collectorStatus.serviceStatus !== 'Running'" size="small" type="success" :loading="startLoading" @click="startCollector"></el-button> <el-button v-if="collectorStatus.status === 'stopped' || collectorStatus.status === 'not_installed'" size="small" type="success" disabled>需手动启动服务</el-button>
<el-button v-if="collectorStatus.status === 'running'" size="small" type="danger" :loading="stopLoading" @click="stopCollector"></el-button> <el-button v-if="collectorStatus.status === 'paused' || collectorStatus.status === 'timeout'" size="small" type="success" :loading="startLoading" @click="startCollector"></el-button>
<el-button size="small" type="warning" :loading="refreshLoading" @click="refreshCollectorConfig"></el-button> <el-button v-if="collectorStatus.status === 'running'" size="small" type="danger" :loading="stopLoading" @click="stopCollector"></el-button>
</div> <el-button size="small" type="warning" :loading="refreshLoading" @click="refreshCollectorConfig"></el-button>
</div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
@ -402,25 +403,25 @@ function formatNumber(val: number | undefined | null): string {
return Number(val).toFixed(2) return Number(val).toFixed(2)
} }
// + Windows // 5
const collectorTagType = computed(() => { const collectorTagType = computed(() => {
const { serviceStatus, status } = collectorStatus.value const { status } = collectorStatus.value
if (serviceStatus === 'Running' && status === 'running') return 'success' if (status === 'running') return 'success'
if (serviceStatus === 'Running' && status !== 'running') return 'warning' // if (status === 'paused') return 'info'
if (serviceStatus === 'NotInstalled') return 'danger' if (status === 'timeout') return 'warning'
if (serviceStatus === 'StartFailed') return 'danger' if (status === 'stopped') return 'info'
if (status === 'not_installed') return 'danger'
return 'warning' return 'warning'
}) })
const collectorStatusText = computed(() => { const collectorStatusText = computed(() => {
const { serviceStatus, status } = collectorStatus.value const { status } = collectorStatus.value
if (serviceStatus === 'Running' && status === 'running') return '运行中' if (status === 'running') return '运行中'
if (serviceStatus === 'Running' && status !== 'running') return '心跳超时' if (status === 'paused') return '已暂停'
if (serviceStatus === 'NotInstalled') return '未安装' if (status === 'timeout') return '心跳超时'
if (serviceStatus === 'Stopped') return '已停止' if (status === 'stopped') return '已停止'
if (serviceStatus === 'Starting') return '启动中' if (status === 'not_installed') return '未安装'
if (serviceStatus === 'StartFailed') return '启动失败' return status || '-'
return serviceStatus || '-'
}) })
function alertTypeTag(type: string): string { function alertTypeTag(type: string): string {
@ -443,7 +444,7 @@ function initWorkshopChart() {
trigger: 'axis', trigger: 'axis',
formatter: (params: any) => { formatter: (params: any) => {
const d = workshopData.value[params[0].dataIndex] const d = workshopData.value[params[0].dataIndex]
return `${d.workshopName}<br/>${unitLabel}产量: ${params[0].value} ${unit}<br/>总产量: ${d.quantity} 件<br/>机床数: ${d.machineCount}` return `${d.workshopName}<br/>${unitLabel}产量: ${Number(params[0].value).toFixed(2)} ${unit}<br/>总产量: ${d.quantity} 件<br/>机床数: ${d.machineCount}`
}, },
}, },
grid: { left: 60, right: 20, top: 20, bottom: 30 }, grid: { left: 60, right: 20, top: 20, bottom: 30 },
@ -452,7 +453,7 @@ function initWorkshopChart() {
series: [{ series: [{
type: 'bar', data: workshopData.value.map(i => i.avgQuantity), type: 'bar', data: workshopData.value.map(i => i.avgQuantity),
itemStyle: { color: '#67C23A', borderRadius: [4, 4, 0, 0] }, barWidth: '40%', 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 },
}], }],
}) })
} }

@ -119,8 +119,11 @@ namespace CncCollector.Core
using (var conn = new MySqlConnection(_connectionString)) using (var conn = new MySqlConnection(_connectionString))
{ {
// 结账所有活跃段is_settled=0 且 end_time IS NULL // 结账所有活跃段is_settled=0 且 end_time IS NULL
// 正确计算quantity保留当前end_part_count用end-start计算产量
conn.Execute(@"UPDATE cnc_production_segment 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() close_reason = @Reason, is_settled = 1, updated_at = NOW()
WHERE is_settled = 0 AND end_time IS NULL", WHERE is_settled = 0 AND end_time IS NULL",
new { Reason = SegmentCloseReason.ServiceStop }); new { Reason = SegmentCloseReason.ServiceStop });

@ -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) ELSE COALESCE(SUM(ad.day_quantity), 0) / (DATEDIFF(@EndDate, @StartDate) + 1) / NULLIF(COUNT(DISTINCT m.id), 0)
END AS AvgQuantity END AS AvgQuantity
FROM cnc_workshop w 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 ( LEFT JOIN (
SELECT machine_id, production_date, day_quantity FROM ( SELECT machine_id, production_date, day_quantity FROM (
SELECT dp.machine_id, dp.production_date, SUM(dp.total_quantity) AS day_quantity SELECT dp.machine_id, dp.production_date, SUM(dp.total_quantity) AS day_quantity

@ -111,8 +111,30 @@ namespace CncService.Impl
serviceStatusText = svc.ToString(); serviceStatusText = svc.ToString();
} }
// 组合状态NotInstalled -> 停止,其他根据心跳决定 // 组合状态精确区分5种情况
string status = (serviceStatusText == "NotInstalled") ? "stopped" : (heartbeatRunning ? "running" : "stopped"); 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 { return new {
status, status,

@ -137,15 +137,21 @@ namespace CncWebApi.Controllers
try try
{ {
dynamic statusObj = _dashboardService.GetCollectorStatus(); dynamic statusObj = _dashboardService.GetCollectorStatus();
string status = statusObj?.status as string;
string serviceStatus = statusObj?.serviceStatus as string; string serviceStatus = statusObj?.serviceStatus as string;
if (!string.IsNullOrEmpty(serviceStatus) && string.Equals(serviceStatus, "NotInstalled", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(serviceStatus) && string.Equals(serviceStatus, "NotInstalled", StringComparison.OrdinalIgnoreCase))
{ {
return Ok(ApiResponse<object>.Fail(40001, "采集服务未安装,请先在服务器上运行 install.ps1 安装服务")); return Ok(ApiResponse<object>.Fail(40001, "采集服务未安装,请先在服务器上运行 install.ps1 安装服务"));
} }
if (!string.IsNullOrEmpty(serviceStatus) && string.Equals(serviceStatus, "Running", StringComparison.OrdinalIgnoreCase)) if (status == "running")
{ {
return Ok(ApiResponse<object>.Fail(40002, "采集服务已在运行中,无需再次启动")); return Ok(ApiResponse<object>.Fail(40002, "采集服务已在运行中,无需再次启动"));
} }
if (status == "stopped")
{
return Ok(ApiResponse<object>.Fail(40003, "采集服务已停止请手动启动Windows服务"));
}
// paused 或 timeout 状态转发到CncCollector恢复引擎
} }
catch { /* ignore status fetch errors and fallback to forwarding */ } catch { /* ignore status fetch errors and fallback to forwarding */ }

Loading…
Cancel
Save