|
|
@@ -40,34 +40,34 @@
|
|
|
.mes4-title {
|
|
|
position: absolute;
|
|
|
left: 50%;
|
|
|
- top: 20px;
|
|
|
+ top: 18px;
|
|
|
transform: translateX(-50%);
|
|
|
- font-size: 38px;
|
|
|
+ font-size: 42px;
|
|
|
font-weight: bold;
|
|
|
color: #FFFFFF;
|
|
|
- letter-spacing: 3px;
|
|
|
+ letter-spacing: 4px;
|
|
|
text-shadow: 0 0 10px rgba(77, 201, 245, 0.6);
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
.mes4-datetime {
|
|
|
position: absolute;
|
|
|
- top: 32px;
|
|
|
+ top: 30px;
|
|
|
right: 50px;
|
|
|
text-align: right;
|
|
|
}
|
|
|
.mes4-datetime .mes4-date {
|
|
|
display: inline-block;
|
|
|
- font-size: 18px;
|
|
|
+ font-size: 20px;
|
|
|
color: #FFFFFF;
|
|
|
- line-height: 28px;
|
|
|
+ line-height: 30px;
|
|
|
}
|
|
|
.mes4-datetime .mes4-time {
|
|
|
display: inline-block;
|
|
|
margin-left: 14px;
|
|
|
- font-size: 22px;
|
|
|
+ font-size: 26px;
|
|
|
color: #FFFFFF;
|
|
|
font-weight: bold;
|
|
|
- line-height: 28px;
|
|
|
+ line-height: 30px;
|
|
|
}
|
|
|
|
|
|
/* ===== 面板通用 - 使用自定义类名避免与 Bootstrap 的 .panel 冲突 ===== */
|
|
|
@@ -79,107 +79,111 @@
|
|
|
box-shadow: none;
|
|
|
}
|
|
|
.mes4-panel .mes4-ptitle {
|
|
|
- height: 42px;
|
|
|
- line-height: 42px;
|
|
|
+ height: 48px;
|
|
|
+ line-height: 48px;
|
|
|
text-align: center;
|
|
|
- font-size: 18px;
|
|
|
+ font-size: 22px;
|
|
|
font-weight: bold;
|
|
|
color: #4DC9F5;
|
|
|
- letter-spacing: 1px;
|
|
|
+ letter-spacing: 2px;
|
|
|
background: transparent;
|
|
|
}
|
|
|
.mes4-panel .mes4-pbody {
|
|
|
position: absolute;
|
|
|
- top: 42px;
|
|
|
+ top: 48px;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
- padding: 5px 10px;
|
|
|
+ padding: 8px 14px;
|
|
|
background: transparent;
|
|
|
}
|
|
|
.chart { width: 100%; height: 100%; display: block; }
|
|
|
|
|
|
- /* ===== 位置定义 (1920 × 1080 画布) ===== */
|
|
|
- /* 左列: x=30, 宽=430 */
|
|
|
- .p-alert { left: 40px; top: 130px; width: 430px; height: 290px; }
|
|
|
- .p-quality { left: 40px; top: 455px; width: 430px; height: 265px; }
|
|
|
- .p-product { left: 40px; top: 763px; width: 430px; height: 306px; }
|
|
|
+ /* ===== 位置:左右对齐,中间表按 5 行收短 ===== */
|
|
|
+ .p-alert { left: 36px; top: 118px; width: 440px; height: 430px; }
|
|
|
+ .p-quality { left: 36px; top: 568px; width: 440px; height: 460px; }
|
|
|
+ .p-product { display: none; }
|
|
|
|
|
|
- /* 中列: x=480, 宽=960 */
|
|
|
- .p-table { left: 480px; top: 130px; width: 960px; height: 575px; }
|
|
|
- .p-month { left: 480px; top: 725px; width: 960px; height: 306px; }
|
|
|
+ .p-table { left: 496px; top: 118px; width: 928px; height: 560px; }
|
|
|
+ .p-month { display: none; }
|
|
|
|
|
|
- /* 右列: right=30, 宽=430 */
|
|
|
.p-overview { right: 30px; top: 130px; width: 430px; height: 290px; }
|
|
|
- .p-achieve { right: 40px; top: 455px; width: 430px; height: 265px; }
|
|
|
- .p-hourly { right: 40px; top: 763px; width: 430px; height: 306px; }
|
|
|
+ .p-achieve { display: none; }
|
|
|
+ .p-hourly { right: 36px; top: 568px; width: 440px; height: 460px; }
|
|
|
|
|
|
- /* 阶段报警信息 - 按模板比例调整 */
|
|
|
- .p-alert .mes4-ptitle {
|
|
|
+ .p-alert .mes4-ptitle,
|
|
|
+ .p-overview .mes4-ptitle,
|
|
|
+ .p-quality .mes4-ptitle,
|
|
|
+ .p-hourly .mes4-ptitle {
|
|
|
height: 40px;
|
|
|
line-height: 40px;
|
|
|
font-size: 16px;
|
|
|
- color: #4DC9F5;
|
|
|
- font-weight: bold;
|
|
|
- letter-spacing: 1px;
|
|
|
}
|
|
|
- .p-alert .mes4-pbody { top: 40px; padding: 0; }
|
|
|
- /* 总览标题 */
|
|
|
- .p-overview .mes4-ptitle { height: 40px; line-height: 40px; font-size: 16px; }
|
|
|
- .p-overview .mes4-pbody { top: 40px; }
|
|
|
+ .p-alert .mes4-pbody { top: 48px; padding: 0 6px 8px; }
|
|
|
+ .p-overview .mes4-pbody { top: 40px; padding: 0; }
|
|
|
+ .p-table .mes4-pbody { top: 8px; padding: 8px 28px 12px; }
|
|
|
+ .p-quality .mes4-pbody,
|
|
|
+ .p-hourly .mes4-pbody { top: 72px; }
|
|
|
|
|
|
/* ===== 报警信息 ===== */
|
|
|
.alert-head {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- height: 36px;
|
|
|
- padding: 0 22px;
|
|
|
+ height: 42px;
|
|
|
+ padding: 0 20px;
|
|
|
color: #4DC9F5;
|
|
|
- font-size: 14px;
|
|
|
+ font-size: 16px;
|
|
|
font-weight: bold;
|
|
|
- border-bottom: 1px solid rgba(77, 201, 245, 0.3);
|
|
|
+ border-bottom: 2px solid rgba(77, 201, 245, 0.45);
|
|
|
}
|
|
|
- .alert-head > div:nth-child(1) { width: 70px; }
|
|
|
+ .alert-head > div:nth-child(1) { width: 72px; }
|
|
|
.alert-head > div:nth-child(2) { flex: 1; }
|
|
|
.alert-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- height: 28px;
|
|
|
- padding: 0 22px;
|
|
|
+ height: 32px;
|
|
|
+ padding: 0 20px;
|
|
|
color: #ffffff;
|
|
|
- font-size: 13px;
|
|
|
+ font-size: 15px;
|
|
|
+ border-bottom: 1px solid rgba(77, 201, 245, 0.12);
|
|
|
}
|
|
|
- .alert-row > div:nth-child(1) { width: 70px; color: #4DC9F5; font-weight: bold; }
|
|
|
- .alert-row > div:nth-child(2) { flex: 1; }
|
|
|
+ .alert-row > div:nth-child(1) { width: 72px; color: #4DC9F5; font-weight: bold; font-size: 16px; }
|
|
|
+ .alert-row > div:nth-child(2) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
|
- /* ===== OP 表格 ===== */
|
|
|
+ /* ===== OP 表格(5 行加高、字号加大) ===== */
|
|
|
.op-table {
|
|
|
width: 100%;
|
|
|
+ height: 100%;
|
|
|
border-collapse: collapse;
|
|
|
color: #ffffff;
|
|
|
- font-size: 16px;
|
|
|
+ font-size: 20px;
|
|
|
table-layout: fixed;
|
|
|
}
|
|
|
.op-table thead th {
|
|
|
- height: 46px;
|
|
|
+ height: 58px;
|
|
|
color: #4DC9F5;
|
|
|
- font-weight: normal;
|
|
|
- font-size: 16px;
|
|
|
- background: rgba(0, 100, 180, 0.25);
|
|
|
- border-bottom: 1px solid rgba(77, 201, 245, 0.35);
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 20px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ background: rgba(0, 100, 180, 0.28);
|
|
|
+ border-bottom: 2px solid rgba(77, 201, 245, 0.5);
|
|
|
text-align: center;
|
|
|
}
|
|
|
.op-table tbody td {
|
|
|
- height: 46px;
|
|
|
+ height: 88px;
|
|
|
text-align: center;
|
|
|
- border-bottom: 1px dashed rgba(77, 201, 245, 0.18);
|
|
|
+ border-bottom: 1px solid rgba(77, 201, 245, 0.22);
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ .op-table tbody tr:last-child td {
|
|
|
+ border-bottom: 2px solid rgba(77, 201, 245, 0.35);
|
|
|
}
|
|
|
.op-table tbody tr:hover td { background: rgba(0, 200, 255, 0.06); }
|
|
|
- .op-table td.op-col { color: #00C8FF; font-weight: bold; font-size: 18px; }
|
|
|
- .op-table td.op-name { color: #ffffff; text-align: center; }
|
|
|
- .op-table td.op-val { color: #00F7FF; font-weight: bold; font-size: 18px; }
|
|
|
+ .op-table td.op-col { color: #00C8FF; font-weight: bold; font-size: 26px; }
|
|
|
+ .op-table td.op-name { color: #ffffff; text-align: center; font-size: 22px; }
|
|
|
+ .op-table td.op-val { color: #00F7FF; font-weight: bold; font-size: 28px; }
|
|
|
|
|
|
- /* ===== 产线阶段数据总览 (按模板排布) ===== */
|
|
|
+ /* ===== 产线阶段数据总览(对齐原 UI 四宫格框) ===== */
|
|
|
.overview-wrap {
|
|
|
position: relative;
|
|
|
height: 100%;
|
|
|
@@ -211,11 +215,6 @@
|
|
|
font-weight: bold;
|
|
|
line-height: 1;
|
|
|
}
|
|
|
- .overview-card .oc-value .arrow {
|
|
|
- font-size: 13px;
|
|
|
- color: #00FF88;
|
|
|
- margin-left: 3px;
|
|
|
- }
|
|
|
.overview-gauge-wrap {
|
|
|
position: absolute;
|
|
|
top: 50%;
|
|
|
@@ -250,17 +249,17 @@
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: flex-end;
|
|
|
- padding: 4px 18px 0;
|
|
|
- font-size: 13px;
|
|
|
+ padding: 6px 22px 0;
|
|
|
+ font-size: 15px;
|
|
|
color: #ffffff;
|
|
|
- gap: 18px;
|
|
|
+ gap: 22px;
|
|
|
}
|
|
|
.legend-inline .dot {
|
|
|
display: inline-block;
|
|
|
width: 12px;
|
|
|
height: 12px;
|
|
|
border-radius: 50%;
|
|
|
- margin-right: 5px;
|
|
|
+ margin-right: 6px;
|
|
|
vertical-align: middle;
|
|
|
}
|
|
|
</style>
|
|
|
@@ -279,7 +278,7 @@
|
|
|
|
|
|
<div class="mes4-panel p-alert">
|
|
|
<div class="mes4-ptitle"> 产线阶段报警信息 </div>
|
|
|
- <div class="mes4-pbody" style="padding:0">
|
|
|
+ <div class="mes4-pbody">
|
|
|
<div class="alert-head"><div>序号</div><div>报警类型</div></div>
|
|
|
<div id="alertList"></div>
|
|
|
</div>
|
|
|
@@ -291,7 +290,7 @@
|
|
|
<span><i class="dot" style="background:#00FF88"></i>产出</span>
|
|
|
<span><i class="dot" style="background:#00C8FF"></i>不良</span>
|
|
|
</div>
|
|
|
- <div class="mes4-pbody" style="top:62px"><canvas id="chartQuality" class="chart"></canvas></div>
|
|
|
+ <div class="mes4-pbody"><canvas id="chartQuality" class="chart"></canvas></div>
|
|
|
</div>
|
|
|
|
|
|
<div class="mes4-panel p-product">
|
|
|
@@ -300,16 +299,14 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="mes4-panel p-table">
|
|
|
- <div class="mes4-pbody" style="top:5px;padding:0 20px">
|
|
|
+ <div class="mes4-pbody">
|
|
|
<table class="op-table">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
- <th style="width:110px">工位</th>
|
|
|
- <th style="width:200px">工位名称</th>
|
|
|
- <th>计划产能</th>
|
|
|
- <th>小时产能</th>
|
|
|
+ <th style="width:160px">工位</th>
|
|
|
+ <th style="width:300px">工位名称</th>
|
|
|
<th>实际产能</th>
|
|
|
- <th>当班产能(12小时内)</th>
|
|
|
+ <th>当班产能</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody id="opTableBody"></tbody>
|
|
|
@@ -329,26 +326,19 @@
|
|
|
<div class="overview-cards">
|
|
|
<div class="overview-card">
|
|
|
<div class="oc-label">生产人数</div>
|
|
|
- <div class="oc-value"><span id="productionPeople">48</span> <span class="arrow">↑</span></div>
|
|
|
+ <div class="oc-value"><span id="productionPeople">48</span></div>
|
|
|
</div>
|
|
|
<div class="overview-card">
|
|
|
<div class="oc-label">设备数量</div>
|
|
|
- <div class="oc-value"><span id="deviceCount">56</span> <span class="arrow">↑</span></div>
|
|
|
+ <div class="oc-value"><span id="deviceCount">56</span></div>
|
|
|
</div>
|
|
|
<div class="overview-card">
|
|
|
<div class="oc-label">计划产量</div>
|
|
|
- <div class="oc-value"><span id="planProduction">221</span> <span class="arrow">↑</span></div>
|
|
|
+ <div class="oc-value"><span id="planProduction">221</span></div>
|
|
|
</div>
|
|
|
<div class="overview-card">
|
|
|
<div class="oc-label">实际产量</div>
|
|
|
- <div class="oc-value"><span id="actualProduction">201</span> <span class="arrow">↑</span></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="overview-gauge-wrap">
|
|
|
- <canvas id="gaugeRate"></canvas>
|
|
|
- <div class="gauge-txt">
|
|
|
- <div class="val"><span id="gaugeVal">70</span>%</div>
|
|
|
- <div class="lbl">今日达成率</div>
|
|
|
+ <div class="oc-value"><span id="actualProduction">201</span></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -370,7 +360,7 @@
|
|
|
<span><i class="dot" style="background:#00FF88"></i>上线</span>
|
|
|
<span><i class="dot" style="background:#00C8FF"></i>下线</span>
|
|
|
</div>
|
|
|
- <div class="mes4-pbody" style="top:62px"><canvas id="chartHourly" class="chart"></canvas></div>
|
|
|
+ <div class="mes4-pbody"><canvas id="chartHourly" class="chart"></canvas></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -390,10 +380,12 @@
|
|
|
document.getElementById('mes4Time').textContent = pad(h) + ':' + pad(mi) + ':' + pad(s);
|
|
|
}
|
|
|
|
|
|
- // ====== 测试数据 ======
|
|
|
+ // ====== 页面数据 ======
|
|
|
var mockData = {};
|
|
|
- /** 每小时上下线:mesProduct/hourlyUpDownOkData(本页 lineSn=MP,上线 OP120 / 下线 OP190) */
|
|
|
- var HOURLY_API_CFG = { lineSn: 'MP', onlineOprno: 'OP120', offlineOprno: 'OP190' };
|
|
|
+ /** 本屏产线:XT(工位 OP060–OP100,与 mesLineProcess 一致) */
|
|
|
+ var LINE_SN = 'XT';
|
|
|
+ /** 每小时上下线:上线 OP060 / 下线 OP100 */
|
|
|
+ var HOURLY_API_CFG = { lineSn: LINE_SN, onlineOprno: 'OP060', offlineOprno: 'OP100' };
|
|
|
function buildHourLabels24() {
|
|
|
var hourLabels = [];
|
|
|
for (var i = 1; i <= 24; i++) {
|
|
|
@@ -438,24 +430,13 @@
|
|
|
weekLabels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
|
|
|
}
|
|
|
|
|
|
- // 报警信息由 updateDeviceAlarmAlertsByApi 拉取 mesDeviceAlarm/listData,展示 type
|
|
|
+ // 报警信息由 updateDeviceAlarmAlertsByApi 拉取
|
|
|
mockData.alerts = [];
|
|
|
|
|
|
- // 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 }
|
|
|
- ];
|
|
|
+ // 工位表由 mesLineProcess/listData(XT,OP060–OP100)拉取,不使用假数据
|
|
|
+ mockData.opTable = [];
|
|
|
|
|
|
- // 数据卡
|
|
|
+ // 数据卡(总览固定展示用)
|
|
|
mockData.cards = {
|
|
|
productionPeople: 48,
|
|
|
deviceCount: 56,
|
|
|
@@ -521,15 +502,15 @@
|
|
|
}
|
|
|
function drawYAxis(ctx, padL, padT, cw, ch, maxV, minV) {
|
|
|
minV = minV || 0;
|
|
|
- ctx.fillStyle = 'rgba(77,201,245,0.8)';
|
|
|
- ctx.font = '11px Microsoft YaHei';
|
|
|
+ ctx.fillStyle = 'rgba(77,201,245,0.9)';
|
|
|
+ ctx.font = '13px Microsoft YaHei';
|
|
|
ctx.textAlign = 'right';
|
|
|
- ctx.strokeStyle = 'rgba(0,143,253,0.18)';
|
|
|
+ ctx.strokeStyle = 'rgba(0,143,253,0.22)';
|
|
|
ctx.lineWidth = 1;
|
|
|
for (var i = 0; i <= 4; i++) {
|
|
|
var val = Math.round(minV + (maxV - minV) * (4 - i) / 4);
|
|
|
var y = padT + ch * i / 4;
|
|
|
- ctx.fillText(val, padL - 5, y + 4);
|
|
|
+ ctx.fillText(val, padL - 6, y + 4);
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(padL, y);
|
|
|
ctx.lineTo(padL + cw, y);
|
|
|
@@ -541,12 +522,12 @@
|
|
|
function drawDoubleBar(id, a, b, labels, maxV) {
|
|
|
var p = prepareCanvas(id); if (!p) return;
|
|
|
var ctx = p.ctx, w = p.w, h = p.h;
|
|
|
- var padL = 38, padR = 10, padT = 12, padB = 22;
|
|
|
+ var padL = 46, padR = 12, padT = 16, padB = 28;
|
|
|
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 gw = cw / a.length, bw = gw * 0.3;
|
|
|
+ var gw = cw / a.length, bw = gw * 0.28;
|
|
|
for (var i = 0; i < a.length; i++) {
|
|
|
var cx = padL + i * gw + gw / 2;
|
|
|
var h1 = (a[i] / maxV) * ch;
|
|
|
@@ -559,11 +540,11 @@
|
|
|
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.fillStyle = '#00FF88'; ctx.font = 'bold 12px 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);
|
|
|
+ ctx.fillStyle = 'rgba(77,201,245,0.95)'; ctx.font = '13px Microsoft YaHei';
|
|
|
+ ctx.fillText(labels[i], cx, h - 8);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -699,7 +680,7 @@
|
|
|
function drawAreaLine(id, up, down, labels, maxV) {
|
|
|
var p = prepareCanvas(id); if (!p) return;
|
|
|
var ctx = p.ctx, w = p.w, h = p.h;
|
|
|
- var padL = 30, padR = 12, padT = 12, padB = 22;
|
|
|
+ var padL = 42, padR = 14, padT = 16, padB = 28;
|
|
|
var cw = w - padL - padR, ch = h - padT - padB;
|
|
|
ctx.clearRect(0, 0, w, h);
|
|
|
drawYAxis(ctx, padL, padT, cw, ch, maxV);
|
|
|
@@ -735,15 +716,15 @@
|
|
|
ctx.bezierCurveTo(cpx2, py2, cpx2, y2, x2, y2);
|
|
|
}
|
|
|
}
|
|
|
- ctx.strokeStyle = color; ctx.lineWidth = 1.6; ctx.stroke();
|
|
|
+ ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke();
|
|
|
}
|
|
|
draw(up, '#00FF88');
|
|
|
draw(down, '#00C8FF');
|
|
|
- ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = '10px Microsoft YaHei'; ctx.textAlign = 'center';
|
|
|
+ ctx.fillStyle = 'rgba(77,201,245,0.95)'; ctx.font = '12px Microsoft YaHei'; ctx.textAlign = 'center';
|
|
|
for (var i = 0; i < labels.length; i++) {
|
|
|
if (i % 2 === 0) {
|
|
|
var x = padL + i * stepX;
|
|
|
- ctx.fillText(labels[i], x, h - 6);
|
|
|
+ ctx.fillText(labels[i], x, h - 8);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -833,8 +814,6 @@
|
|
|
html += '<tr>';
|
|
|
html += '<td class="op-col">' + r.code + '</td>';
|
|
|
html += '<td class="op-name">' + r.name + '</td>';
|
|
|
- html += '<td class="op-val">' + r.plan + '</td>';
|
|
|
- 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 += '</tr>';
|
|
|
@@ -843,42 +822,24 @@
|
|
|
if (el) el.innerHTML = html;
|
|
|
}
|
|
|
|
|
|
- function applyLatestPlanNumsToTable(planNums) {
|
|
|
- var list = mockData.opTable || [];
|
|
|
- for (var i = 0; i < list.length; i++) {
|
|
|
- list[i].plan = planNums;
|
|
|
- }
|
|
|
- renderTable();
|
|
|
+ function parseOprnoNum(code) {
|
|
|
+ var m = String(code || '').toUpperCase().match(/^OP(\d+)/);
|
|
|
+ return m ? parseInt(m[1], 10) : -1;
|
|
|
}
|
|
|
|
|
|
+ function isOprnoInRange(code, fromNum, toNum) {
|
|
|
+ var n = parseOprnoNum(code);
|
|
|
+ return n >= fromNum && n <= toNum;
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyLatestPlanNumsToTable(planNums) {}
|
|
|
function calcRatePercent(actual, plan) {
|
|
|
actual = Number(actual) || 0;
|
|
|
plan = Number(plan) || 0;
|
|
|
- if (plan <= 0) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
+ if (plan <= 0) return 0;
|
|
|
return Math.round(actual * 100 / plan);
|
|
|
}
|
|
|
-
|
|
|
- function syncAchieveRateWithPlan() {
|
|
|
- mockData.cards = mockData.cards || {};
|
|
|
- mockData.achieveRate = mockData.achieveRate || { labels: [], actual: [] };
|
|
|
- var planNums = Number(mockData.cards.planProduction) || 0;
|
|
|
- var actualProduction = Number(mockData.cards.actualProduction) || 0;
|
|
|
- var labels = mockData.achieveRate.labels || [];
|
|
|
- var actualSeries = mockData.achieveRate.actual || [];
|
|
|
- var targetSeries = [];
|
|
|
- for (var i = 0; i < labels.length; i++) {
|
|
|
- targetSeries.push(planNums);
|
|
|
- }
|
|
|
- var maxVal = Math.max.apply(null, targetSeries.concat(actualSeries).concat([10]));
|
|
|
- mockData.achieveRate.target = targetSeries;
|
|
|
- mockData.achieveRate.max = maxVal <= 0 ? 10 : Math.ceil(maxVal * 1.1);
|
|
|
- mockData.achieveRate.min = 0;
|
|
|
- mockData.rate = calcRatePercent(actualProduction, planNums);
|
|
|
- renderCards();
|
|
|
- drawAllCharts();
|
|
|
- }
|
|
|
+ function syncAchieveRateWithPlan() {}
|
|
|
|
|
|
function updateLineProcessTableByApi() {
|
|
|
$.ajax({
|
|
|
@@ -888,7 +849,7 @@
|
|
|
data: {
|
|
|
pageNo: 1,
|
|
|
pageSize: 5000,
|
|
|
- lineSn: 'MP'
|
|
|
+ lineSn: LINE_SN
|
|
|
},
|
|
|
success: function(resp) {
|
|
|
var rows = extractUserList(resp);
|
|
|
@@ -896,19 +857,21 @@
|
|
|
for (var i = 0; i < rows.length; i++) {
|
|
|
var p = rows[i] || {};
|
|
|
var ls = String(p.lineSn || p.line_sn || '').toUpperCase();
|
|
|
- if (ls !== 'MP') continue;
|
|
|
+ if (ls !== LINE_SN) continue;
|
|
|
+ var code = p.oprno || '';
|
|
|
+ if (!isOprnoInRange(code, 60, 100)) continue;
|
|
|
out.push({
|
|
|
- code: p.oprno || '',
|
|
|
+ code: code,
|
|
|
name: p.title || '',
|
|
|
- plan: (mockData.cards && mockData.cards.planProduction) || 0,
|
|
|
- hour: 0,
|
|
|
real: 0,
|
|
|
- shift: 0
|
|
|
+ shift: 0,
|
|
|
+ _opNum: parseOprnoNum(code)
|
|
|
});
|
|
|
}
|
|
|
+ out.sort(function(a, b) { return a._opNum - b._opNum; });
|
|
|
+ mockData.opTable = out;
|
|
|
+ renderTable();
|
|
|
if (out.length > 0) {
|
|
|
- mockData.opTable = out;
|
|
|
- renderTable();
|
|
|
updateHourlyCapacityByApi();
|
|
|
}
|
|
|
}
|
|
|
@@ -917,77 +880,56 @@
|
|
|
|
|
|
function updateHourlyCapacityByApi() {
|
|
|
var list = mockData.opTable || [];
|
|
|
- if (!list.length) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!list.length) return;
|
|
|
var oprnos = [];
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
- if (list[i] && list[i].code) {
|
|
|
- oprnos.push(list[i].code);
|
|
|
- }
|
|
|
- }
|
|
|
- if (!oprnos.length) {
|
|
|
- return;
|
|
|
+ if (list[i] && list[i].code) oprnos.push(list[i].code);
|
|
|
}
|
|
|
- fetchCapacityInto('hourlyCapacityData', 'MP', oprnos, 'hour');
|
|
|
- fetchCapacityInto('dayCapacityData', 'MP', oprnos, 'real');
|
|
|
- fetchCapacityInto('shiftCapacityData', 'MP', oprnos, 'shift');
|
|
|
+ if (!oprnos.length) return;
|
|
|
+ fetchCapacityInto('dayCapacityData', LINE_SN, oprnos, 'real', {});
|
|
|
+ fetchCapacityInto('shiftCapacityData', LINE_SN, oprnos, 'shift', { fixedTwoShift: 'true' });
|
|
|
}
|
|
|
|
|
|
- // 通用:拉取产能数据并写入表格指定字段(hour=小时产能, real=实际/当天产能, shift=当班产能)
|
|
|
- function fetchCapacityInto(action, lineSn, oprnos, field) {
|
|
|
+ function fetchCapacityInto(action, lineSn, oprnos, field, extraData) {
|
|
|
+ var data = { lineSn: lineSn, oprnos: oprnos.join(',') };
|
|
|
+ if (extraData) {
|
|
|
+ for (var k in extraData) {
|
|
|
+ if (extraData.hasOwnProperty(k)) data[k] = extraData[k];
|
|
|
+ }
|
|
|
+ }
|
|
|
$.ajax({
|
|
|
url: '${ctx}/mes/mesProductRecord/' + action,
|
|
|
type: 'POST',
|
|
|
dataType: 'json',
|
|
|
- data: {
|
|
|
- lineSn: lineSn,
|
|
|
- oprnos: oprnos.join(',')
|
|
|
- },
|
|
|
+ data: data,
|
|
|
success: function(resp) {
|
|
|
- if (!resp || resp.result !== 'true' || !resp.data) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!resp || resp.result !== 'true' || !resp.data) return;
|
|
|
var map = resp.data || {};
|
|
|
var list = mockData.opTable || [];
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
var code = list[i].code || '';
|
|
|
- list[i][field] = Number(map[code]) || 0;
|
|
|
+ var formatted = code.length === 6 ? code.substring(0, 5) : code;
|
|
|
+ list[i][field] = Number(map[code] != null ? map[code] : map[formatted]) || 0;
|
|
|
}
|
|
|
renderTable();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- function updateLatestPlanProductionByApi() {
|
|
|
- $.ajax({
|
|
|
- url: '${ctx}/mes/mesPlan/latestPlanNums',
|
|
|
- type: 'POST',
|
|
|
- dataType: 'json',
|
|
|
- success: function(resp) {
|
|
|
- if (!resp || resp.result !== 'true' || !resp.data) {
|
|
|
- return;
|
|
|
- }
|
|
|
- var planNums = Number(resp.data.nums) || 0;
|
|
|
- mockData.cards = mockData.cards || {};
|
|
|
- mockData.cards.planProduction = planNums;
|
|
|
- applyLatestPlanNumsToTable(planNums);
|
|
|
- syncAchieveRateWithPlan();
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // 近七日产品质检数据分析(产出=半成品入库,不良=返工记录),按产线 MP 统计
|
|
|
+ // 近七日产品质检:产出=OP100 OK,不良=返工 OP070–OP100
|
|
|
function updateWeekQualityByApi() {
|
|
|
$.ajax({
|
|
|
url: '${ctx}/mes/mesProductRecord/weekQualityData',
|
|
|
type: 'POST',
|
|
|
dataType: 'json',
|
|
|
- data: { lineSn: 'MP' },
|
|
|
+ data: {
|
|
|
+ lineSn: LINE_SN,
|
|
|
+ producedOprno: 'OP100',
|
|
|
+ defectOprnoFrom: 'OP070',
|
|
|
+ defectOprnoTo: 'OP100'
|
|
|
+ },
|
|
|
success: function(resp) {
|
|
|
- if (!resp || resp.result !== 'true' || !resp.data) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!resp || resp.result !== 'true' || !resp.data) return;
|
|
|
var d = resp.data;
|
|
|
var produced = d.produced || [];
|
|
|
var defect = d.defect || [];
|
|
|
@@ -1002,68 +944,16 @@
|
|
|
labels: d.labels || [],
|
|
|
produced: produced,
|
|
|
defect: defect,
|
|
|
- max: Math.ceil(maxV * 1.2)
|
|
|
- };
|
|
|
- // 近七日产量分析(折线图):产量取每日入库数量(半成品入库管理)
|
|
|
- var pMax = 0, pMin = Number.MAX_VALUE;
|
|
|
- for (var k = 0; k < produced.length; k++) {
|
|
|
- var v = Number(produced[k]) || 0;
|
|
|
- if (v > pMax) pMax = v;
|
|
|
- if (v < pMin) pMin = v;
|
|
|
- }
|
|
|
- if (pMin === Number.MAX_VALUE) { pMin = 0; }
|
|
|
- var pad = Math.max(1, Math.round((pMax - pMin) * 0.2));
|
|
|
- mockData.productTrend = {
|
|
|
- labels: d.labels || [],
|
|
|
- data: produced,
|
|
|
- max: pMax + pad,
|
|
|
- min: Math.max(0, pMin - pad)
|
|
|
+ max: Math.ceil(maxV * 1.15) || 10
|
|
|
};
|
|
|
drawAllCharts();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 各月产量统计图(半成品入库管理按月统计),按产线 MP
|
|
|
- function updateMonthlyProductByApi() {
|
|
|
- $.ajax({
|
|
|
- url: '${ctx}/mes/mesProductRecord/monthlyProductData',
|
|
|
- type: 'POST',
|
|
|
- dataType: 'json',
|
|
|
- data: { lineSn: 'MP' },
|
|
|
- success: function(resp) {
|
|
|
- if (!resp || resp.result !== 'true' || !resp.data) {
|
|
|
- return;
|
|
|
- }
|
|
|
- var d = resp.data;
|
|
|
- var data = d.data || [];
|
|
|
- var maxV = 10;
|
|
|
- for (var i = 0; i < data.length; i++) {
|
|
|
- if (Number(data[i]) > maxV) maxV = Number(data[i]);
|
|
|
- }
|
|
|
- mockData.monthly = {
|
|
|
- labels: d.labels || [],
|
|
|
- data: data,
|
|
|
- max: Math.ceil(maxV * 1.2)
|
|
|
- };
|
|
|
- drawAllCharts();
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- function renderCards() {
|
|
|
- var c = mockData.cards || {};
|
|
|
- var m = {
|
|
|
- productionPeople: c.productionPeople,
|
|
|
- deviceCount: c.deviceCount,
|
|
|
- planProduction: c.planProduction,
|
|
|
- actualProduction: c.actualProduction,
|
|
|
- gaugeVal: mockData.rate
|
|
|
- };
|
|
|
- for (var k in m) {
|
|
|
- var el = document.getElementById(k);
|
|
|
- if (el && m[k] != null) el.textContent = m[k];
|
|
|
- }
|
|
|
- }
|
|
|
+ function updateMonthlyProductByApi() {}
|
|
|
+
|
|
|
+ function renderCards() {}
|
|
|
|
|
|
function extractUserList(resp) {
|
|
|
if (!resp) return [];
|
|
|
@@ -1105,60 +995,25 @@
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- function updateProductionPeopleByApi() {
|
|
|
+ /** 总览三卡:生产人数 / 设备数量 / 计划产量 ← mesProductScreen 最新配置 */
|
|
|
+ function updateScreenConfigByApi() {
|
|
|
$.ajax({
|
|
|
- url: '${ctx}/sys/empUser/listData',
|
|
|
+ url: '${ctx}/mes/mesProductScreen/latestConfig',
|
|
|
type: 'POST',
|
|
|
dataType: 'json',
|
|
|
- data: {
|
|
|
- pageNo: 1,
|
|
|
- pageSize: 5000,
|
|
|
- 'employee.office.officeName': '生产部'
|
|
|
- },
|
|
|
success: function(resp) {
|
|
|
- var peopleCount = 0;
|
|
|
- var users = extractUserList(resp);
|
|
|
- for (var i = 0; i < users.length; i++) {
|
|
|
- var u = users[i] || {};
|
|
|
- var office = (u.employee && u.employee.office) ? u.employee.office : {};
|
|
|
- var officeName = office.officeName || '';
|
|
|
- var normalizedOfficeName = (officeName + '').replace(/\s/g, '');
|
|
|
- if (
|
|
|
- normalizedOfficeName === '生产部' ||
|
|
|
- /[/\\>]+生产部$/.test(normalizedOfficeName)
|
|
|
- ) {
|
|
|
- peopleCount++;
|
|
|
- }
|
|
|
- }
|
|
|
- mockData.cards = mockData.cards || {};
|
|
|
- mockData.cards.productionPeople = peopleCount;
|
|
|
- var target = document.getElementById('productionPeople');
|
|
|
- if (target) target.textContent = peopleCount;
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function updateDeviceCountByApi() {
|
|
|
- $.ajax({
|
|
|
- url: '${ctx}/mes/mesDevice/listData',
|
|
|
- type: 'POST',
|
|
|
- dataType: 'json',
|
|
|
- data: {
|
|
|
- pageNo: 1,
|
|
|
- pageSize: 1
|
|
|
- },
|
|
|
- success: function(resp) {
|
|
|
- var deviceCount = 0;
|
|
|
- if (resp && typeof resp.count === 'number') {
|
|
|
- deviceCount = resp.count;
|
|
|
- } else {
|
|
|
- var devices = extractUserList(resp);
|
|
|
- deviceCount = devices.length;
|
|
|
- }
|
|
|
+ if (!resp || resp.result !== 'true' || !resp.data) return;
|
|
|
+ var d = resp.data;
|
|
|
mockData.cards = mockData.cards || {};
|
|
|
- mockData.cards.deviceCount = deviceCount;
|
|
|
- var target = document.getElementById('deviceCount');
|
|
|
- if (target) target.textContent = deviceCount;
|
|
|
+ mockData.cards.productionPeople = Number(d.peopleNums) || 0;
|
|
|
+ mockData.cards.deviceCount = Number(d.deviceNums) || 0;
|
|
|
+ mockData.cards.planProduction = Number(d.planNums) || 0;
|
|
|
+ var elPeople = document.getElementById('productionPeople');
|
|
|
+ var elDevice = document.getElementById('deviceCount');
|
|
|
+ var elPlan = document.getElementById('planProduction');
|
|
|
+ if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
|
|
|
+ if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
|
|
|
+ if (elPlan) elPlan.textContent = mockData.cards.planProduction;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
@@ -1181,113 +1036,54 @@
|
|
|
|
|
|
function applyActualProductionCount(n) {
|
|
|
mockData.cards = mockData.cards || {};
|
|
|
- mockData.cards.actualProduction = n;
|
|
|
- syncAchieveRateWithPlan();
|
|
|
- }
|
|
|
-
|
|
|
- function getCalendarDayUpdateDateRange() {
|
|
|
- var now = new Date();
|
|
|
- var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
|
|
|
- var dateStr = now.getFullYear() + '-' + pad(now.getMonth() + 1) + '-' + pad(now.getDate());
|
|
|
- return {
|
|
|
- gte: dateStr + ' 00:00:00',
|
|
|
- lte: dateStr + ' 23:59:59',
|
|
|
- startMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime(),
|
|
|
- endMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999).getTime()
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- function parseRecordDateMs(val) {
|
|
|
- if (val == null || val === '') {
|
|
|
- return NaN;
|
|
|
- }
|
|
|
- if (typeof val === 'number' && !isNaN(val)) {
|
|
|
- return val;
|
|
|
- }
|
|
|
- var s = String(val).trim().replace(/-/g, '/');
|
|
|
- var t = Date.parse(s);
|
|
|
- return isNaN(t) ? NaN : t;
|
|
|
- }
|
|
|
-
|
|
|
- function isRecordWithinToday(r, day) {
|
|
|
- var t = parseRecordDateMs(r.updateDate || r.update_date);
|
|
|
- if (isNaN(t)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return t >= day.startMs && t <= day.endMs;
|
|
|
+ mockData.cards.actualProduction = Number(n) || 0;
|
|
|
+ var el = document.getElementById('actualProduction');
|
|
|
+ if (el) el.textContent = mockData.cards.actualProduction;
|
|
|
}
|
|
|
|
|
|
+ /** 实际产量:取本阶段末工位当天 OK 数(半成品 OP100) */
|
|
|
function updateActualProductionByApi() {
|
|
|
- var dayRange = getCalendarDayUpdateDateRange();
|
|
|
- var pageSize = 3000;
|
|
|
- var maxPages = 200;
|
|
|
- var pageNo = 1;
|
|
|
- var totalOk = 0;
|
|
|
- function fetchPage() {
|
|
|
- $.ajax({
|
|
|
- url: '${ctx}/mes/mesProductRecord/listData',
|
|
|
- type: 'POST',
|
|
|
- dataType: 'json',
|
|
|
- data: {
|
|
|
- pageNo: pageNo,
|
|
|
- pageSize: pageSize,
|
|
|
- oprno: 'OP190',
|
|
|
- content: 'OK',
|
|
|
- lineSn: 'MP',
|
|
|
- updateDate_gte: dayRange.gte,
|
|
|
- updateDate_lte: dayRange.lte
|
|
|
- },
|
|
|
- success: function(resp) {
|
|
|
- var list = extractUserList(resp);
|
|
|
- for (var i = 0; i < list.length; i++) {
|
|
|
- var r = list[i] || {};
|
|
|
- if (isOpr190Family(r.oprno) && isRecordContentOk(r.content) && isRecordWithinToday(r, dayRange) && String(r.lineSn || r.line_sn || '').toUpperCase() === 'MP') {
|
|
|
- totalOk++;
|
|
|
- }
|
|
|
- }
|
|
|
- if (list.length === pageSize && pageNo < maxPages) {
|
|
|
- pageNo++;
|
|
|
- fetchPage();
|
|
|
- } else {
|
|
|
- applyActualProductionCount(totalOk);
|
|
|
- }
|
|
|
- },
|
|
|
- error: function() {
|
|
|
- applyActualProductionCount(totalOk);
|
|
|
+ var oprno = HOURLY_API_CFG.offlineOprno || 'OP100';
|
|
|
+ $.ajax({
|
|
|
+ url: '${ctx}/mes/mesProductRecord/dayCapacityData',
|
|
|
+ type: 'POST',
|
|
|
+ dataType: 'json',
|
|
|
+ data: {
|
|
|
+ lineSn: LINE_SN,
|
|
|
+ oprnos: oprno
|
|
|
+ },
|
|
|
+ success: function(resp) {
|
|
|
+ if (!resp || resp.result !== 'true' || !resp.data) {
|
|
|
+ applyActualProductionCount(0);
|
|
|
+ return;
|
|
|
}
|
|
|
- });
|
|
|
- }
|
|
|
- fetchPage();
|
|
|
+ var map = resp.data || {};
|
|
|
+ var formatted = oprno.length === 6 ? oprno.substring(0, 5) : oprno;
|
|
|
+ applyActualProductionCount(Number(map[oprno] != null ? map[oprno] : map[formatted]) || 0);
|
|
|
+ },
|
|
|
+ error: function() {
|
|
|
+ applyActualProductionCount(0);
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
function drawAllCharts() {
|
|
|
var q = mockData.qualityInspect;
|
|
|
if (q) drawDoubleBar('chartQuality', q.produced, q.defect, q.labels, q.max);
|
|
|
- var t = mockData.productTrend;
|
|
|
- if (t) drawLineChart('chartProduct', t.data, t.labels, t.max, t.min, '#00C8FF');
|
|
|
- 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);
|
|
|
- var mo = mockData.monthly;
|
|
|
- if (mo) drawMonthBar('chartMonth', mo.data, mo.labels, mo.max);
|
|
|
- drawGauge('gaugeRate', mockData.rate || 0);
|
|
|
}
|
|
|
|
|
|
function refreshData() {
|
|
|
generateMockData();
|
|
|
renderAlerts();
|
|
|
renderTable();
|
|
|
- renderCards();
|
|
|
- updateLatestPlanProductionByApi();
|
|
|
- updateLineProcessTableByApi();
|
|
|
- updateProductionPeopleByApi();
|
|
|
- updateDeviceCountByApi();
|
|
|
+ updateScreenConfigByApi();
|
|
|
updateActualProductionByApi();
|
|
|
+ updateLineProcessTableByApi();
|
|
|
updateDeviceAlarmAlertsByApi();
|
|
|
updateHourlyUpDownByApi();
|
|
|
updateWeekQualityByApi();
|
|
|
- updateMonthlyProductByApi();
|
|
|
drawAllCharts();
|
|
|
}
|
|
|
|