2026/8/25 22:13:40

KingFusion驾驶舱开发实战:转运信息模块(AGV状态+任务列表+完成率+转运次数)

KingFusion驾驶舱开发实战:转运信息模块(AGV状态+任务列表+完成率+转运次数) 2026-08-07 | 雷工笔记 | MES系列合集一、背景驾驶舱是MES系统的仪表盘也是工厂管理层最关注的功能模块。之前我分享了产能信息和设备信息模块今天继续完善转运信息区域。转运信息模块的核心是AGV小车的运行状态和任务数据展示。现场有5台AGV小车由AGV调度系统统一管理通过API接口将AGV状态实时上报给MES系统MES收到后更新到数据库驾驶舱再从数据库读取数据展示给用户。整体数据链路AGV调度系统 → API上报 → MES接口 → PM_AGVStatusDetail表 → 计算工程 → 驾驶舱前端 PM_AGVTaskDetail表 → (case 8~11) → 图表/变量本文聚焦计算工程后端脚本SQL查询和前端客户端页面变量绑定的具体实现不带任何概念包装全是代码和踩坑记录。二、涉及的表结构2.1 PM_AGVStatusDetailAGV状态详情这个表由AGV调度系统通过API实时更新写入5台AGV各有一条记录字段如下CREATE TABLE ”public”.”PM_AGVStatusDetail” ( ”id” int4 NOT NULL, ”c_AGVCode” int4 NOT NULL, -- AGV设备编码1~5 ”c_AGVStatus” int4 NOT NULL, -- 状态0未定义,1停止,2运行,3空闲,4读卡停止,5管制 ”c_AGVBattery” int4, -- 电池电量 ”c_AGVAlarm” int4, -- 报警状态 ”c_AGVPosition” varchar(50), -- 当前位置 ”c_FinchTime” timestamp -- 更新时间 ); ALTER TABLE ”PM_AGVStatusDetail” ADD CONSTRAINT ”AGV” UNIQUE (”c_AGVCode”);2.2 PM_AGVTaskDetailAGV任务详情每一条记录代表一次AGV转运任务字段如下CREATE TABLE ”public”.”PM_AGVTaskDetail” ( ”ID” int4 NOT NULL, ”c_TaskID” varchar(100), -- 调度AGV任务编码 ”c_AGVCode” int4, -- AGV设备编码 ”c_LocationCode” varchar(50), -- 当前位置 ”c_AGVTaskStatus” varchar(20), -- 任务状态创建/执行中/已完成 ”c_CreateTime” timestamp, -- 任务创建时间 ”c_FinchTime” timestamp, -- 完成时间 ”c_UpdateTime” timestamp -- 更新时间 );三、功能实现的四个步骤3.1 AGV状态更新case 8需求从PM_AGVStatusDetail表读取5台AGV的状态码赋值给5个工程变量 $System.状态_AGV01 ~ $System.状态_AGV05图形模型根据变量值显示不同颜色的AGV图标。后端计算工程脚本PM_cockpit请求式计算case 8://驾驶舱-转运信息-AGV状态 var sql SELECT ”c_AGVCode”, ”c_AGVStatus” FROM ”PM_AGVStatusDetail” ORDER BY ”c_AGVCode”; var res[]; var ret await SQLQuery(RFM_PM, sql, res); OutParam.recordsres; OutParam.errorCoderet; break;前端客户端驾驶舱页面变量触发事件var M { type: 8 }; requestCalculationPost($System.RFM_PM, PM_cockpit, M, function (res) { if (res.Code 0 res.errorCode 0) { var resdata res.records; for (var i 0; i resdata.length; i) { var code resdata[i].c_AGVCode; var status resdata[i].c_AGVStatus; if (code 1) $System.状态_AGV01 status; else if (code 2) $System.状态_AGV02 status; else if (code 3) $System.状态_AGV03 status; else if (code 4) $System.状态_AGV04 status; else if (code 5) $System.状态_AGV05 status; } } });状态码 → 颜色映射状态码含义图标颜色0未定义灰色1停止红色2运行绿色3空闲黄色4读卡停止橙色5管制紫色3.2 AGV任务列表case 9需求在Datagrid中展示最近10条AGV任务记录5个字段任务编码、AGV编号、当前位置、任务状态、创建时间。后端case 9://驾驶舱-转运信息-AGV任务列表 var sql SELECT ”c_TaskID”, ”c_AGVCode”, ”c_LocationCode”, ”c_AGVTaskStatus”, ”c_CreateTime” FROM ”PM_AGVTaskDetail” ORDER BY ”c_UpdateTime” DESC NULLS LAST LIMIT 10; var res[]; var ret await SQLQuery(RFM_PM, sql, res); OutParam.recordsres; OutParam.errorCoderet; break;关键细节ORDER BY c_UpdateTime DESC NULLS LAST。为什么加 NULLS LAST因为未完成的任务 c_UpdateTime 为NULL不处理的话NULL值会排在最前面导致取到的10条记录全是NULL。加上 NULLS LAST 确保真正有更新时间的记录排前面。前端绑定Datagridvar M { type: 9 }; requestCalculationPost($System.RFM_PM, PM_cockpit, M, function (res) { if (res.Code 0 res.errorCode 0) { var resdata res.records; if (resdata.length 0) { $Function.tip(info, 无AGV任务数据); } else { var datagrid1 []; for (var i 0; i resdata.length; i) { var data2 {}; data2.number i 1; data2.c_TaskID resdata[i].c_TaskID; data2.c_AGVCode resdata[i].c_AGVCode; data2.c_LocationCode resdata[i].c_LocationCode || ; data2.c_AGVTaskStatus resdata[i].c_AGVTaskStatus; data2.c_CreateTime resdata[i].c_CreateTime; datagrid1.push(data2); } $(DatagridX.id).datagrid({ data: datagrid1 }); } } });3.3 近7日转运完成率case 10需求折线图展示近7天每天AGV转运任务的完成率已完成任务数 / 总任务数 × 100%。后端case 10://驾驶舱-转运信息-近七日转运完成率 var sql SELECT ”c_CreateTime”::date AS ”date”, ROUND(COUNT(*) FILTER (WHERE ”c_AGVTaskStatus” 已完成) * 100.0 / NULLIF(COUNT(*), 0), 1) AS ”rate” FROM ”PM_AGVTaskDetail” WHERE ”c_CreateTime” CURRENT_DATE - 7 GROUP BY ”c_CreateTime”::date ORDER BY ”c_CreateTime”::date; var res[]; var ret await SQLQuery(RFM_PM, sql, res); OutParam.recordsres; OutParam.errorCoderet; break;技术要点NULLIF(COUNT(*), 0)防止当天没有任务时除零报错FILTER (WHERE ...)是PostgreSQL的聚合过滤语法比CASE WHEN更简洁::date是PostgreSQL的类型转换语法将timestamp转为date以按天分组前端ECharts折线图CustomCharts52var dates getLastSevenDays(); // 生成最近7天日期数组 var M { type: 10 }; requestCalculationPost($System.RFM_PM, PM_cockpit, M, function (res) { if (res.Code 0 res.errorCode 0) { var resdata res.records; // 构建日期→完成率映射 var rateMap {}; for (var i 0; i resdata.length; i) { var d resdata[i].date; var parts d.split(-); rateMap[parts[1] - parts[2]] Number(resdata[i].rate); } // 按7天顺序填充数据无数据的天填0 var chartData []; for (var j 0; j dates.length; j) { chartData.push(rateMap[dates[j]] || 0); } option { title: { text: ”近七日转运完成率”, ... }, xAxis: { data: dates, ... }, yAxis: { name: 单位(100%), ... }, series: [{ data: chartData, type: line, lineStyle: { color: DeepSkyBlue, width: 5 }, ... }] }; CustomCharts52.$Children.echart.setOption(option, true); } });3.4 近7日转运次数case 11需求柱状图展示近7天每天AGV转运任务的总数量。后端case 11://驾驶舱-转运信息-近七日转运次数 var sql SELECT ”c_CreateTime”::date AS ”date”, COUNT(*) AS ”cnt” FROM ”PM_AGVTaskDetail” WHERE ”c_CreateTime” CURRENT_DATE - 7 GROUP BY ”c_CreateTime”::date ORDER BY ”c_CreateTime”::date; var res[]; var ret await SQLQuery(RFM_PM, sql, res); OutParam.recordsres; OutParam.errorCoderet; break;前端的绑定逻辑与完成率类似只是后端返回的是 cnt数量而非 rate百分比图表改为柱状图CustomCharts53使用渐变色柱状条。四、遇到的坑和解决坑1PostgreSQL 的列名必须加双引号KingFusion 连接的是 PostgreSQL 数据库PG 对大小写敏感KingFusion 建立表时字段名带了双引号如 c_AGVCode后续查询必须原样加上双引号否则报错字段不存在。错误写法var sql SELECT c_AGVCode, c_AGVStatus FROM PM_AGVStatusDetail;正确写法var sql SELECT ”c_AGVCode”, ”c_AGVStatus” FROM ”PM_AGVStatusDetail”;坑2NULL值排序问题ORDER BY c_UpdateTime DESC 时NULL值在PostgreSQL中默认排在最前面DESC排序时导致 LIMIT 10 取到的都是没有更新时间的记录真正需要的最新记录反而取不到。解决加 NULLS LAST让NULL值排到最后。坑3静态数据到动态数据的过渡之前驾驶舱的图表数据是静态写死的比如完成率 [95, 96, 95, 97, 98, 95, 97]转运次数 [50, 45, 42, 36, 38, 31, 50]。改动态数据后需要前端构建 日期→数据 映射然后按7天固定顺序填充某天无数据时填0避免图表数据错位。坑4刷新机制设计AGV状态需要实时更新但又不希望频繁调用后端。最终采用页面变量取反触发的方式页面创建时取反变量 → 触发一次查询如需定时刷新在定时器位置取反同一变量即可这样刷新的频率和触发时机完全可配置不用修改查询逻辑五、总结转运信息模块的开发核心就两件事后端写SQL、前端绑定变量。但细节决定成败数据库设计决定了能做什么功能PM_AGVStatusDetail 和 PM_AGVTaskDetail 两张表分别支撑了状态展示和任务统计SQL查询要考虑PG的特殊语法和NULL值处理前端架构用页面变量触发取代直接调用刷新机制更灵活代码管理每次测试通过后代码备份到知识库并在驾驶舱页面创建Excalidraw逻辑流程图避免时间久了忘记相关文章KingFusion驾驶舱开发实战产能信息模块KingFusion驾驶舱开发实战设备信息模块代码备份本文所有代码已归档到个人知识库持续更新。