|
|
@@ -300,7 +300,7 @@
|
|
|
<div class="mes4-panel p-alert">
|
|
|
<div class="mes4-ptitle"> 产线阶段报警信息 </div>
|
|
|
<div class="mes4-pbody" style="padding:0">
|
|
|
- <div class="alert-head"><div>序号</div><div>报警类型</div></div>
|
|
|
+ <div class="alert-head"><div>序号</div><div>工位 / 报警类型</div></div>
|
|
|
<div id="alertList"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -331,6 +331,7 @@
|
|
|
<th>小时产能</th>
|
|
|
<th>实际产能</th>
|
|
|
<th>当班产能(12小时内)</th>
|
|
|
+ <th>上班产量</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody id="opTableBody"></tbody>
|
|
|
@@ -386,7 +387,7 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="mes4-panel p-hourly">
|
|
|
- <div class="mes4-ptitle"> 每小时上下线数据分析 </div>
|
|
|
+ <div class="mes4-ptitle"> 近12小时上下线数据分析 </div>
|
|
|
<div class="legend-inline">
|
|
|
<span><i class="dot" style="background:#00FF88"></i>上线</span>
|
|
|
<span><i class="dot" style="background:#00C8FF"></i>下线</span>
|
|
|
@@ -424,7 +425,7 @@
|
|
|
function padOpTableRowsToPageSize(html, rowCount) {
|
|
|
for (var j = rowCount; j < OP_TABLE_PAGE_SIZE; j++) {
|
|
|
var alt = (j % 2 === 1) ? ' op-row-alt' : '';
|
|
|
- html += '<tr class="op-row-empty' + alt + '"><td colspan="6"> </td></tr>';
|
|
|
+ html += '<tr class="op-row-empty' + alt + '"><td colspan="7"> </td></tr>';
|
|
|
}
|
|
|
return html;
|
|
|
}
|
|
|
@@ -476,10 +477,11 @@
|
|
|
var mockData = {};
|
|
|
/** 每小时上下线:mesProduct/hourlyUpDownOkData(本页 lineSn=ZP,上线 OP190 / 下线 OP250) */
|
|
|
var HOURLY_API_CFG = { lineSn: 'ZP', onlineOprno: 'OP190', offlineOprno: 'OP250' };
|
|
|
- function buildHourLabels24() {
|
|
|
+ function buildRecentHourLabels12() {
|
|
|
var labels = [];
|
|
|
- for (var i = 1; i <= 24; i++) {
|
|
|
- labels.push(pad(i));
|
|
|
+ var nowH = new Date().getHours();
|
|
|
+ for (var i = 11; i >= 0; i--) {
|
|
|
+ labels.push(String((nowH - i + 24) % 24));
|
|
|
}
|
|
|
return labels;
|
|
|
}
|
|
|
@@ -491,19 +493,20 @@
|
|
|
data: {
|
|
|
lineSn: HOURLY_API_CFG.lineSn,
|
|
|
onlineOprno: HOURLY_API_CFG.onlineOprno,
|
|
|
- offlineOprno: HOURLY_API_CFG.offlineOprno
|
|
|
+ offlineOprno: HOURLY_API_CFG.offlineOprno,
|
|
|
+ recentHours: 12
|
|
|
},
|
|
|
success: function(resp) {
|
|
|
if (resp && resp.result === 'true' && resp.data) {
|
|
|
var d = resp.data;
|
|
|
- if (!d.up || !d.down || d.up.length < 24 || d.down.length < 24) {
|
|
|
+ if (!d.up || !d.down || d.up.length < 1) {
|
|
|
return;
|
|
|
}
|
|
|
mockData.hourly = {
|
|
|
- labels: buildHourLabels24(),
|
|
|
+ labels: d.labels && d.labels.length ? d.labels : buildRecentHourLabels12(),
|
|
|
up: d.up,
|
|
|
down: d.down,
|
|
|
- max: typeof d.max === 'number' ? d.max : 100
|
|
|
+ max: 4
|
|
|
};
|
|
|
drawAllCharts();
|
|
|
}
|
|
|
@@ -525,18 +528,18 @@
|
|
|
|
|
|
// OP 表格
|
|
|
mockData.opTable = [
|
|
|
- { code: 'OP10', name: '上料+打码', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP20', name: '底板组件-正面FSW', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP25', name: '底板组件-打磨', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP30', name: '底板组件-反面FSW', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP40', name: '底板组件-CNC', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP50', name: '干冰清洁', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP55', name: '模组梁+封板预装', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP60', name: '封板正面FSW', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP65', name: '打磨', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP70', name: '封板反面FSW', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP75', name: '下线检测', plan: 50, hour: 50, real: 50, shift: 50 },
|
|
|
- { code: 'OP80', name: '装箱', plan: 50, hour: 50, real: 50, shift: 50 }
|
|
|
+ { code: 'OP10', name: '上料+打码', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP20', name: '底板组件-正面FSW', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP25', name: '底板组件-打磨', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP30', name: '底板组件-反面FSW', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP40', name: '底板组件-CNC', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP50', name: '干冰清洁', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP55', name: '模组梁+封板预装', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP60', name: '封板正面FSW', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP65', name: '打磨', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP70', name: '封板反面FSW', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP75', name: '下线检测', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 },
|
|
|
+ { code: 'OP80', name: '装箱', plan: 50, hour: 50, real: 50, shift: 50, prevShift: 50 }
|
|
|
];
|
|
|
|
|
|
// 数据卡
|
|
|
@@ -571,14 +574,13 @@
|
|
|
max: 1300, min: 1000
|
|
|
};
|
|
|
|
|
|
- // 每小时上下线 (24 小时)
|
|
|
- var hourLabels = [], up = [], down = [];
|
|
|
- for (var i = 1; i <= 24; i++) {
|
|
|
- hourLabels.push(pad(i));
|
|
|
- up.push(Math.round(60 + Math.random() * 30));
|
|
|
- down.push(Math.round(50 + Math.random() * 30));
|
|
|
+ // 近12小时上下线
|
|
|
+ var hourLabels = buildRecentHourLabels12(), up = [], down = [];
|
|
|
+ for (var i = 0; i < 12; i++) {
|
|
|
+ up.push(Math.round(Math.random() * 4));
|
|
|
+ down.push(Math.round(Math.random() * 4));
|
|
|
}
|
|
|
- mockData.hourly = { labels: hourLabels, up: up, down: down, max: 100 };
|
|
|
+ mockData.hourly = { labels: hourLabels, up: up, down: down, max: 4 };
|
|
|
|
|
|
// 各月产量
|
|
|
mockData.monthly = {
|
|
|
@@ -621,7 +623,7 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 双柱状图 - 近七日产品质检 (产出/不良)
|
|
|
+ // 双柱状图 - 近七日产品质检 (产出/不良) / 每小时上下线
|
|
|
function drawDoubleBar(id, a, b, labels, maxV) {
|
|
|
var p = prepareCanvas(id); if (!p) return;
|
|
|
var ctx = p.ctx, w = p.w, h = p.h;
|
|
|
@@ -630,24 +632,80 @@
|
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
drawYAxis(ctx, padL, padT, cw, ch, maxV);
|
|
|
if (!a || a.length < 1) return;
|
|
|
- var gw = cw / a.length, bw = gw * 0.3;
|
|
|
- for (var i = 0; i < a.length; i++) {
|
|
|
+ var n = a.length;
|
|
|
+ var dense = n > 12;
|
|
|
+ var gw = cw / n, bw = gw * (dense ? 0.32 : 0.3);
|
|
|
+ var labelStep = dense ? 2 : 1;
|
|
|
+ var valFont = dense ? 'bold 9px Microsoft YaHei' : 'bold 10px Microsoft YaHei';
|
|
|
+ var lblFont = dense ? '10px Microsoft YaHei' : '11px Microsoft YaHei';
|
|
|
+ for (var i = 0; i < n; i++) {
|
|
|
var cx = padL + i * gw + gw / 2;
|
|
|
var h1 = (a[i] / maxV) * ch;
|
|
|
var g1 = ctx.createLinearGradient(0, padT + ch - h1, 0, padT + ch);
|
|
|
g1.addColorStop(0, '#00FF88'); g1.addColorStop(1, 'rgba(0,255,136,0.15)');
|
|
|
ctx.fillStyle = g1;
|
|
|
ctx.fillRect(cx - bw - 2, padT + ch - h1, bw, h1);
|
|
|
- var h2 = (b[i] / maxV) * ch;
|
|
|
+ var h2 = ((b && b[i] != null ? b[i] : 0) / maxV) * ch;
|
|
|
var g2 = ctx.createLinearGradient(0, padT + ch - h2, 0, padT + ch);
|
|
|
g2.addColorStop(0, '#00C8FF'); g2.addColorStop(1, 'rgba(0,200,255,0.15)');
|
|
|
ctx.fillStyle = g2;
|
|
|
ctx.fillRect(cx + 2, padT + ch - h2, bw, h2);
|
|
|
- ctx.fillStyle = '#00FF88'; ctx.font = 'bold 10px Microsoft YaHei';
|
|
|
- ctx.textAlign = 'center'; ctx.fillText(a[i], cx - bw / 2 - 2, padT + ch - h1 - 4);
|
|
|
- ctx.fillStyle = '#00C8FF'; ctx.fillText(b[i], cx + bw / 2 + 2, padT + ch - h2 - 4);
|
|
|
- ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = '11px Microsoft YaHei';
|
|
|
- ctx.fillText(labels[i], cx, h - 6);
|
|
|
+ if (!dense || a[i] > 0) {
|
|
|
+ ctx.fillStyle = '#00FF88'; ctx.font = valFont;
|
|
|
+ ctx.textAlign = 'center'; ctx.fillText(a[i], cx - bw / 2 - 2, padT + ch - h1 - 4);
|
|
|
+ }
|
|
|
+ if (!dense || (b && b[i] > 0)) {
|
|
|
+ ctx.fillStyle = '#00C8FF'; ctx.font = valFont;
|
|
|
+ ctx.textAlign = 'center';
|
|
|
+ ctx.fillText(b && b[i] != null ? b[i] : 0, cx + bw / 2 + 2, padT + ch - h2 - 4);
|
|
|
+ }
|
|
|
+ if (i % labelStep === 0) {
|
|
|
+ ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = lblFont;
|
|
|
+ ctx.textAlign = 'center';
|
|
|
+ ctx.fillText(labels[i], cx, h - 6);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 近12小时上下线双柱:纵轴 0–4,横坐标/柱顶数值与柱组中心对齐
|
|
|
+ function drawHourlyDoubleBar(id, a, b, labels) {
|
|
|
+ var p = prepareCanvas(id); if (!p) return;
|
|
|
+ var ctx = p.ctx, w = p.w, h = p.h;
|
|
|
+ var maxV = 4;
|
|
|
+ var padL = 32, padR = 8, padT = 16, padB = 26;
|
|
|
+ var cw = w - padL - padR, ch = h - padT - padB;
|
|
|
+ ctx.clearRect(0, 0, w, h);
|
|
|
+ drawYAxis(ctx, padL, padT, cw, ch, maxV);
|
|
|
+ if (!a || a.length < 1) return;
|
|
|
+ var n = a.length;
|
|
|
+ var gw = cw / n;
|
|
|
+ var bw = Math.max(3, Math.min(14, gw * 0.28));
|
|
|
+ var gap = 2;
|
|
|
+ ctx.textAlign = 'center';
|
|
|
+ for (var i = 0; i < n; i++) {
|
|
|
+ var cx = padL + (i + 0.5) * gw;
|
|
|
+ var v1 = Number(a[i]) || 0;
|
|
|
+ var v2 = Number(b && b[i] != null ? b[i] : 0) || 0;
|
|
|
+ var h1 = (Math.min(v1, maxV) / maxV) * ch;
|
|
|
+ var h2 = (Math.min(v2, maxV) / maxV) * ch;
|
|
|
+ var x1 = cx - gap / 2 - bw;
|
|
|
+ var x2 = cx + gap / 2;
|
|
|
+ var g1 = ctx.createLinearGradient(0, padT + ch - h1, 0, padT + ch);
|
|
|
+ g1.addColorStop(0, '#00FF88'); g1.addColorStop(1, 'rgba(0,255,136,0.15)');
|
|
|
+ ctx.fillStyle = g1;
|
|
|
+ ctx.fillRect(x1, padT + ch - h1, bw, h1);
|
|
|
+ var g2 = ctx.createLinearGradient(0, padT + ch - h2, 0, padT + ch);
|
|
|
+ g2.addColorStop(0, '#00C8FF'); g2.addColorStop(1, 'rgba(0,200,255,0.15)');
|
|
|
+ ctx.fillStyle = g2;
|
|
|
+ ctx.fillRect(x2, padT + ch - h2, bw, h2);
|
|
|
+ ctx.font = 'bold 11px Microsoft YaHei';
|
|
|
+ ctx.fillStyle = '#00FF88';
|
|
|
+ ctx.fillText(String(v1), x1 + bw / 2, padT + ch - h1 - 4);
|
|
|
+ ctx.fillStyle = '#00C8FF';
|
|
|
+ ctx.fillText(String(v2), x2 + bw / 2, padT + ch - h2 - 4);
|
|
|
+ ctx.fillStyle = 'rgba(77,201,245,0.95)';
|
|
|
+ ctx.font = '12px Microsoft YaHei';
|
|
|
+ ctx.fillText(labels[i] != null ? String(labels[i]) : String(i), cx, h - 6);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -927,6 +985,7 @@
|
|
|
html += '<td class="op-val">' + r.hour + '</td>';
|
|
|
html += '<td class="op-val">' + r.real + '</td>';
|
|
|
html += '<td class="op-val">' + r.shift + '</td>';
|
|
|
+ html += '<td class="op-val">' + (r.prevShift || 0) + '</td>';
|
|
|
html += '</tr>';
|
|
|
}
|
|
|
html = padOpTableRowsToPageSize(html, slice.length);
|
|
|
@@ -1004,7 +1063,8 @@
|
|
|
plan: (mockData.cards && mockData.cards.planProduction) || 0,
|
|
|
hour: 0,
|
|
|
real: 0,
|
|
|
- shift: 0
|
|
|
+ shift: 0,
|
|
|
+ prevShift: 0
|
|
|
});
|
|
|
}
|
|
|
if (out.length > 0) {
|
|
|
@@ -1013,6 +1073,7 @@
|
|
|
renderTable();
|
|
|
startOpTablePageRotate();
|
|
|
updateHourlyCapacityByApi();
|
|
|
+ updateDeviceAlarmAlertsByApi();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
@@ -1035,9 +1096,10 @@
|
|
|
fetchCapacityInto('hourlyCapacityData', 'ZP', oprnos, 'hour');
|
|
|
fetchCapacityInto('dayCapacityData', 'ZP', oprnos, 'real');
|
|
|
fetchCapacityInto('shiftCapacityData', 'ZP', oprnos, 'shift');
|
|
|
+ fetchCapacityInto('prevShiftCapacityData', 'ZP', oprnos, 'prevShift');
|
|
|
}
|
|
|
|
|
|
- // 通用:拉取产能数据并写入表格指定字段(hour=小时产能, real=实际/当天产能, shift=当班产能)
|
|
|
+ // 通用:拉取产能数据并写入表格指定字段(hour=小时产能, real=实际/当天产能, shift=当班产能, prevShift=上班产量)
|
|
|
function fetchCapacityInto(action, lineSn, oprnos, field) {
|
|
|
$.ajax({
|
|
|
url: '${ctx}/mes/mesProductRecord/' + action,
|
|
|
@@ -1179,6 +1241,54 @@
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
+ function parseDeviceSnParts(deviceSn) {
|
|
|
+ var s = String(deviceSn || '').trim().toUpperCase();
|
|
|
+ var parts = s.split('_').filter(function(p) { return !!p; });
|
|
|
+ var line = parts.length > 0 ? parts[0] : '';
|
|
|
+ var oprno = '';
|
|
|
+ for (var i = 0; i < parts.length; i++) {
|
|
|
+ if (/^OP\d+$/.test(parts[i])) {
|
|
|
+ oprno = parts[i];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!oprno) {
|
|
|
+ var m = s.match(/OP\d+/);
|
|
|
+ if (m) oprno = m[0];
|
|
|
+ }
|
|
|
+ return { line: line, oprno: oprno };
|
|
|
+ }
|
|
|
+ function parseOprnoNum(code) {
|
|
|
+ var m = String(code || '').toUpperCase().match(/OP(\d+)/);
|
|
|
+ return m ? parseInt(m[1], 10) : -1;
|
|
|
+ }
|
|
|
+ function isAlertOprnoOnZpLine(oprno) {
|
|
|
+ var num = parseOprnoNum(oprno);
|
|
|
+ var list = mockData.opTable || [];
|
|
|
+ if (!list.length) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ var c = String((list[i] && list[i].code) || '').toUpperCase();
|
|
|
+ if (!c) continue;
|
|
|
+ if (parseOprnoNum(c) === num && num >= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ function resolveAlertProcessName(oprno) {
|
|
|
+ var num = parseOprnoNum(oprno);
|
|
|
+ var list = mockData.opTable || [];
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ var c = String((list[i] && list[i].code) || '').toUpperCase();
|
|
|
+ if (!c) continue;
|
|
|
+ if (parseOprnoNum(c) === num && num >= 0) {
|
|
|
+ return list[i].name || '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ }
|
|
|
function updateDeviceAlarmAlertsByApi() {
|
|
|
$.ajax({
|
|
|
url: '${ctx}/mes/mesProductScreen/alertList',
|
|
|
@@ -1186,17 +1296,24 @@
|
|
|
dataType: 'json',
|
|
|
data: {
|
|
|
pageNo: 1,
|
|
|
- pageSize: 50,
|
|
|
- state: '0' // 只显示待处理的报警
|
|
|
+ pageSize: 200,
|
|
|
+ state: '0'
|
|
|
},
|
|
|
success: function(resp) {
|
|
|
var rows = extractUserList(resp);
|
|
|
var alerts = [];
|
|
|
for (var i = 0; i < rows.length; i++) {
|
|
|
var r = rows[i] || {};
|
|
|
- if (String(r.state) !== '0') continue; // 双保险:跳过非待处理
|
|
|
+ if (String(r.state) !== '0') continue;
|
|
|
+ var sn = r.deviceSn || r.device_sn || r.deviceCode || r.device_code || '';
|
|
|
+ var parts = parseDeviceSnParts(sn);
|
|
|
+ var oprno = String(r.oprno || parts.oprno || '').toUpperCase();
|
|
|
+ if (!oprno) continue;
|
|
|
+ if (!isAlertOprnoOnZpLine(oprno)) continue;
|
|
|
var typ = (r.type != null && r.type !== undefined) ? String(r.type) : '';
|
|
|
- alerts.push({ no: alerts.length + 1, text: typ });
|
|
|
+ var pname = resolveAlertProcessName(oprno);
|
|
|
+ var text = oprno + (pname ? (' ' + pname) : '') + (typ ? (' | ' + typ) : '');
|
|
|
+ alerts.push({ no: alerts.length + 1, text: text, oprno: oprno });
|
|
|
}
|
|
|
mockData.alerts = alerts;
|
|
|
renderAlerts();
|
|
|
@@ -1345,7 +1462,7 @@
|
|
|
var a = mockData.achieveRate;
|
|
|
if (a) drawDualLine('chartAchieve', a.target, a.actual, a.labels, a.max, a.min);
|
|
|
var hr = mockData.hourly;
|
|
|
- if (hr) drawAreaLine('chartHourly', hr.up, hr.down, hr.labels, hr.max);
|
|
|
+ if (hr) drawHourlyDoubleBar('chartHourly', hr.up, hr.down, hr.labels);
|
|
|
var mo = mockData.monthly;
|
|
|
if (mo) drawMonthBar('chartMonth', mo.data, mo.labels, mo.max);
|
|
|
drawGauge('gaugeRate', mockData.rate || 0);
|