|
|
@@ -214,6 +214,15 @@
|
|
|
.left-top .chart {
|
|
|
height: 178px;
|
|
|
}
|
|
|
+ .left-bottom #warehouseChart {
|
|
|
+ position: absolute;
|
|
|
+ left: 20px;
|
|
|
+ right: 20px;
|
|
|
+ top: 130px;
|
|
|
+ bottom: 18px;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ }
|
|
|
.chart-with-metrics {
|
|
|
position: absolute;
|
|
|
left: 20px;
|
|
|
@@ -221,23 +230,6 @@
|
|
|
top: 126px;
|
|
|
bottom: 16px;
|
|
|
}
|
|
|
- .plan-badge {
|
|
|
- position: absolute;
|
|
|
- right: 28px;
|
|
|
- top: 92px;
|
|
|
- width: 92px;
|
|
|
- height: 92px;
|
|
|
- border-radius: 50%;
|
|
|
- background: linear-gradient(160deg, #ff4271, #ff2366);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- color: #fff;
|
|
|
- font-size: 24px;
|
|
|
- font-weight: 900;
|
|
|
- box-shadow: 0 0 18px rgba(255, 48, 105, .7);
|
|
|
- z-index: 4;
|
|
|
- }
|
|
|
.table-title {
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
@@ -266,7 +258,7 @@
|
|
|
.jph-table td {
|
|
|
height: 52px;
|
|
|
text-align: center;
|
|
|
- font-size: 20px;
|
|
|
+ font-size: 18px;
|
|
|
font-weight: 800;
|
|
|
border-bottom: 1px solid rgba(73, 193, 255, .12);
|
|
|
}
|
|
|
@@ -279,7 +271,11 @@
|
|
|
.jph-table td.name {
|
|
|
text-align: left;
|
|
|
padding-left: 14px;
|
|
|
- font-size: 18px;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ .jph-table td.line {
|
|
|
+ color: #65f2ff;
|
|
|
+ font-size: 16px;
|
|
|
}
|
|
|
.ng-table {
|
|
|
width: 100%;
|
|
|
@@ -305,6 +301,24 @@
|
|
|
.ng-table tbody tr:nth-child(odd) td {
|
|
|
background: rgba(10, 88, 153, .36);
|
|
|
}
|
|
|
+ .jph-table .empty-row td,
|
|
|
+ .ng-table .empty-row td {
|
|
|
+ color: rgba(210, 242, 255, .68);
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-align: center;
|
|
|
+ background: rgba(8, 80, 145, .26);
|
|
|
+ }
|
|
|
+ .ng-table .empty-row td {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .jph-table tbody,
|
|
|
+ .ng-table tbody {
|
|
|
+ transition: opacity .28s ease;
|
|
|
+ }
|
|
|
+ .table-switching {
|
|
|
+ opacity: .18;
|
|
|
+ }
|
|
|
.mini-stat {
|
|
|
position: absolute;
|
|
|
left: 24px;
|
|
|
@@ -376,6 +390,11 @@
|
|
|
}
|
|
|
.daily-legend .online::before { background: #10f2b4; }
|
|
|
.daily-legend .offline::before { background: #18b8ff; }
|
|
|
+ @media (max-width: 900px), (max-height: 520px) {
|
|
|
+ body {
|
|
|
+ background: #020b20;
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
@@ -405,9 +424,8 @@
|
|
|
<div class="panel-title">生产计划交付率</div>
|
|
|
<div class="mini-stat">
|
|
|
<img src="${ctxStatic}/screen/imgs/liuan10.png" alt="">
|
|
|
- <div><span>生产计划交付率</span><strong id="deliveryRateText">98%</strong></div>
|
|
|
+ <div><span>生产计划交付率</span><strong id="deliveryRateText">0%</strong></div>
|
|
|
</div>
|
|
|
- <div class="plan-badge" id="deliveryBadge">98%</div>
|
|
|
<div class="legend"><span><i class="line-yellow"></i>目标</span><span><i class="line-green"></i>实际</span></div>
|
|
|
<div class="chart-with-metrics" id="deliveryChart"></div>
|
|
|
</section>
|
|
|
@@ -428,16 +446,17 @@
|
|
|
</section>
|
|
|
|
|
|
<section class="panel center-main">
|
|
|
- <div class="table-title">HEV各工位实际JPH</div>
|
|
|
+ <div class="table-title">各产线工位实际JPH</div>
|
|
|
<table class="jph-table">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
- <th style="width:110px;">工位</th>
|
|
|
- <th style="width:200px;">工位名称</th>
|
|
|
- <th style="width:100px;">计划产能</th>
|
|
|
- <th style="width:100px;">小时产能</th>
|
|
|
- <th style="width:100px;">实际产能</th>
|
|
|
- <th style="width:100px;">当班产能(12小时内)</th>
|
|
|
+ <th style="width:110px;">产线</th>
|
|
|
+ <th style="width:95px;">工位</th>
|
|
|
+ <th style="width:190px;">工位名称</th>
|
|
|
+ <th style="width:95px;">计划产能</th>
|
|
|
+ <th style="width:95px;">小时产能</th>
|
|
|
+ <th style="width:95px;">实际产能</th>
|
|
|
+ <th style="width:120px;">当班产能(12小时内)</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody id="jphTable"></tbody>
|
|
|
@@ -464,9 +483,9 @@
|
|
|
<div class="panel-title">IQC检测合格率</div>
|
|
|
<div class="mini-stat">
|
|
|
<img src="${ctxStatic}/screen/imgs/liuan13.png" alt="">
|
|
|
- <div><span>IQC检测合格率</span><strong id="iqcRateText">98%</strong></div>
|
|
|
+ <div><span>IQC检测合格率</span><strong id="iqcRateText">0%</strong></div>
|
|
|
</div>
|
|
|
- <div class="rate-number" id="iqcRate">98%</div>
|
|
|
+ <div class="rate-number" id="iqcRate">0%</div>
|
|
|
<div class="legend"><span><i class="line-yellow"></i>不合格数量</span><span><i class="line-green"></i>合格率</span></div>
|
|
|
<div class="chart-with-metrics" id="iqcChart"></div>
|
|
|
</section>
|
|
|
@@ -481,7 +500,7 @@
|
|
|
<div class="panel-title">生产计划达成率</div>
|
|
|
<div class="mini-stat">
|
|
|
<img src="${ctxStatic}/screen/imgs/liuan10.png" alt="">
|
|
|
- <div><span>今日达成率</span><strong id="todayPlanRate">90%</strong></div>
|
|
|
+ <div><span>今日达成率</span><strong id="todayPlanRate">0%</strong></div>
|
|
|
</div>
|
|
|
<div class="legend"><span><i class="line-yellow"></i>目标</span><span><i class="line-green"></i>实际</span></div>
|
|
|
<div class="chart-with-metrics" id="planRateChart"></div>
|
|
|
@@ -491,7 +510,7 @@
|
|
|
<div class="panel-title">生产计划完成率</div>
|
|
|
<div class="mini-stat">
|
|
|
<img src="${ctxStatic}/screen/imgs/liuan13.png" alt="">
|
|
|
- <div><span>生产计划完成率</span><strong id="finishRate">98%</strong></div>
|
|
|
+ <div><span>生产计划完成率</span><strong id="finishRate">0%</strong></div>
|
|
|
</div>
|
|
|
<div class="legend"><span><i class="line-yellow"></i>目标</span><span><i class="line-green"></i>实际</span></div>
|
|
|
<div class="chart-with-metrics" id="finishRateChart"></div>
|
|
|
@@ -502,57 +521,23 @@
|
|
|
<script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
|
|
|
<script src="${ctxStatic}/echarts/4.2/echarts.min.js"></script>
|
|
|
<script>
|
|
|
- // screenData 保存 /mes/mesProduct/screenData4 返回的综合大屏数据:
|
|
|
- // 产量、计划数、近七日数据、质量数据、告警数据、工位数据等都从这里取。
|
|
|
var screenData = {};
|
|
|
-
|
|
|
- // hourlyData 保存 /mes/mesProduct/screenDataHourly 返回的小时上下线数据。
|
|
|
- // 当前接口按产线编码分组,例如 hevxt、b13xt,每组包含 hours、online、offline。
|
|
|
var hourlyData = {};
|
|
|
-
|
|
|
- // ECharts 实例缓存。重复刷新数据时复用实例,避免每次 setOption 都重新创建 canvas。
|
|
|
var chartMap = {};
|
|
|
- var jphPageTimer = null;
|
|
|
- var jphPageIndex = 0;
|
|
|
var jphRows = [];
|
|
|
- var jphPageSize = 9;
|
|
|
-
|
|
|
- // 当后台接口暂时没有返回对应数组时,页面用这些兜底值保持版面完整。
|
|
|
- // 后续如果后台补齐精确接口,只需要保证字段有值,兜底数据会自动失效。
|
|
|
- var fallbackDates = ['12月1日', '12月2日', '12月3日', '12月4日', '12月5日', '12月6日', '12月7日'];
|
|
|
-
|
|
|
-
|
|
|
- // 大屏按蓝湖稿 1920*1080 制作;这里根据浏览器窗口等比缩放并居中显示。
|
|
|
- // Runtime data used by the Liuan screen. Keep these declarations separate from
|
|
|
- // generated comments so the browser never treats them as commented-out code.
|
|
|
- var screenData = {};
|
|
|
- var hourlyData = {};
|
|
|
- var chartMap = {};
|
|
|
- var jphPageTimer = null;
|
|
|
+ var ngRows = [];
|
|
|
var jphPageIndex = 0;
|
|
|
- var jphRows = [];
|
|
|
+ var ngPageIndex = 0;
|
|
|
var jphPageSize = 9;
|
|
|
- var fallbackDates = ['12月1日', '12月2日', '12月3日', '12月4日', '12月5日', '12月6日', '12月7日'];
|
|
|
- var stationRows = [
|
|
|
- ['OP10', '上料+打码'],
|
|
|
- ['OP20', '底板组件-正面FSW'],
|
|
|
- ['OP25', '底板组件-打磨'],
|
|
|
- ['OP30', '底板组件-反面FSW'],
|
|
|
- ['OP40', '底板组件-CNC'],
|
|
|
- ['OP50', '干冰清洁'],
|
|
|
- ['OP55', '模组架+封板预装'],
|
|
|
- ['OP60', '封板正面FSW'],
|
|
|
- ['OP65', '打磨'],
|
|
|
- ['OP70', '封板反面FSW']
|
|
|
- ];
|
|
|
+ var ngPageSize = 6;
|
|
|
+ var jphPageTimer = null;
|
|
|
+ var ngPageTimer = null;
|
|
|
|
|
|
function fitScreen() {
|
|
|
var scale = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
|
|
|
var left = (window.innerWidth - 1920 * scale) / 2;
|
|
|
var top = (window.innerHeight - 1080 * scale) / 2;
|
|
|
- $('#screen').css({
|
|
|
- transform: 'translate(' + left + 'px,' + top + 'px) scale(' + scale + ')'
|
|
|
- });
|
|
|
+ $('#screen').css({ transform: 'translate(' + left + 'px,' + top + 'px) scale(' + scale + ')' });
|
|
|
}
|
|
|
|
|
|
function pad(n) {
|
|
|
@@ -565,20 +550,61 @@
|
|
|
$('#clock').text(now.getFullYear() + '年' + pad(now.getMonth() + 1) + '月' + pad(now.getDate()) + '日 ' + weeks[now.getDay()] + ' ' + pad(now.getHours()) + ':' + pad(now.getMinutes()));
|
|
|
}
|
|
|
|
|
|
- // 接口数据为空时统一回退到 fallback,避免某个图表因为空数组直接不显示。
|
|
|
- function getArray(value, fallback) {
|
|
|
- return value && value.length ? value : fallback;
|
|
|
+ function recentDates(days) {
|
|
|
+ var list = [];
|
|
|
+ var now = new Date();
|
|
|
+ for (var i = days - 1; i >= 0; i--) {
|
|
|
+ var d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);
|
|
|
+ list.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ function getArray(value) {
|
|
|
+ return $.isArray(value) ? value : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ function numberValue(value) {
|
|
|
+ var n = Number(value);
|
|
|
+ return isNaN(n) ? 0 : n;
|
|
|
+ }
|
|
|
+
|
|
|
+ function alignNumbers(list, length) {
|
|
|
+ var source = getArray(list);
|
|
|
+ var result = [];
|
|
|
+ for (var i = 0; i < length; i++) {
|
|
|
+ result.push(numberValue(source[i]));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
function sum(list) {
|
|
|
- list = list || [];
|
|
|
var total = 0;
|
|
|
+ list = getArray(list);
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
- total += Number(list[i]) || 0;
|
|
|
+ total += numberValue(list[i]);
|
|
|
}
|
|
|
return total;
|
|
|
}
|
|
|
|
|
|
+ function percent(numerator, denominator) {
|
|
|
+ numerator = numberValue(numerator);
|
|
|
+ denominator = numberValue(denominator);
|
|
|
+ if (denominator <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return Math.min(100, Math.max(0, Math.round(numerator * 100 / denominator)));
|
|
|
+ }
|
|
|
+
|
|
|
+ function escapeHtml(value) {
|
|
|
+ return String(value == null ? '' : value)
|
|
|
+ .replace(/&/g, '&')
|
|
|
+ .replace(/</g, '<')
|
|
|
+ .replace(/>/g, '>')
|
|
|
+ .replace(/"/g, '"')
|
|
|
+ .replace(/'/g, ''');
|
|
|
+ }
|
|
|
+
|
|
|
function chart(id, option) {
|
|
|
var el = document.getElementById(id);
|
|
|
if (!el) return;
|
|
|
@@ -588,18 +614,10 @@
|
|
|
chartMap[id].setOption(option, true);
|
|
|
}
|
|
|
|
|
|
- // 图表通用网格配置,保证不同图表的坐标轴留白一致。
|
|
|
function baseGrid(bottom) {
|
|
|
- return {
|
|
|
- top: 18,
|
|
|
- left: 35,
|
|
|
- right: 16,
|
|
|
- bottom: bottom || 28,
|
|
|
- containLabel: true
|
|
|
- };
|
|
|
+ return { top: 18, left: 35, right: 16, bottom: bottom || 28, containLabel: true };
|
|
|
}
|
|
|
|
|
|
- // 图表通用坐标轴样式,匹配蓝湖稿的蓝色科技风。
|
|
|
function axisStyle() {
|
|
|
return {
|
|
|
axisLine: { lineStyle: { color: 'rgba(97, 199, 255, .35)' } },
|
|
|
@@ -609,10 +627,9 @@
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 渲染柱状图:用于工件数据统计、出库数据统计等只需要一组数值的面板。
|
|
|
- function renderBars(id, dates, values, color1, color2) {
|
|
|
+ function renderBars(id, dates, values, color1, color2, grid) {
|
|
|
chart(id, {
|
|
|
- grid: baseGrid(22),
|
|
|
+ grid: grid || baseGrid(22),
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
xAxis: Object.assign({ type: 'category', data: dates }, axisStyle()),
|
|
|
yAxis: Object.assign({ type: 'value' }, axisStyle()),
|
|
|
@@ -631,7 +648,6 @@
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 渲染“实际/目标”双线面积图:用于计划交付率、计划达成率、计划完成率。
|
|
|
function renderLineArea(id, dates, values, target) {
|
|
|
chart(id, {
|
|
|
grid: baseGrid(22),
|
|
|
@@ -659,7 +675,7 @@
|
|
|
name: '目标',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
- data: target || values.map(function () { return 1100; }),
|
|
|
+ data: target || [],
|
|
|
symbol: 'none',
|
|
|
lineStyle: { color: '#ffcf30', width: 2 }
|
|
|
}
|
|
|
@@ -667,32 +683,41 @@
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 渲染百分比折线图。当前页面未直接调用,保留给后续接入纯百分比指标时复用。
|
|
|
- function renderPercent(id, dates, values) {
|
|
|
+ function renderPercent(id, dates, values, targetValue) {
|
|
|
chart(id, {
|
|
|
grid: baseGrid(22),
|
|
|
tooltip: { trigger: 'axis' },
|
|
|
xAxis: Object.assign({ type: 'category', boundaryGap: false, data: dates }, axisStyle()),
|
|
|
yAxis: Object.assign({ type: 'value', min: 0, max: 100, axisLabel: { color: '#bdeeff', fontSize: 10, formatter: '{value}%' } }, axisStyle()),
|
|
|
- series: [{
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- data: values,
|
|
|
- symbol: 'circle',
|
|
|
- symbolSize: 6,
|
|
|
- lineStyle: { color: '#19f0c2', width: 3 },
|
|
|
- itemStyle: { color: '#19f0c2' },
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(25, 240, 194, .45)' },
|
|
|
- { offset: 1, color: 'rgba(24, 184, 255, .05)' }
|
|
|
- ])
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '实际',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: values,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 6,
|
|
|
+ lineStyle: { color: '#19f0c2', width: 3 },
|
|
|
+ itemStyle: { color: '#19f0c2' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(25, 240, 194, .45)' },
|
|
|
+ { offset: 1, color: 'rgba(24, 184, 255, .05)' }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '目标',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: dates.map(function () { return targetValue == null ? 100 : targetValue; }),
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: { color: '#ffcf30', width: 2 }
|
|
|
}
|
|
|
- }]
|
|
|
+ ]
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 渲染 IQC 合格率:柱子表示不合格数量,折线表示合格率。
|
|
|
function renderIqc(dates, bad, rates) {
|
|
|
chart('iqcChart', {
|
|
|
grid: baseGrid(24),
|
|
|
@@ -729,8 +754,6 @@
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 渲染每日上下线统计。数据来自 screenDataHourly:
|
|
|
- // hLine.hours 为横轴小时,hLine.online 为上线数,hLine.offline 为下线数。
|
|
|
function renderDaily(dates, online, offline) {
|
|
|
chart('dailyChart', {
|
|
|
grid: baseGrid(20),
|
|
|
@@ -760,25 +783,39 @@
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // 渲染中间 JPH 表格。rows 优先取 screenData.oprnoData,
|
|
|
- // 若旧接口没有该字段,则兼容使用 screenData.workStationData。
|
|
|
- function renderJphTable(rows) {
|
|
|
- jphRows = [];
|
|
|
- var sourceRows = rows && rows.length ? rows : [];
|
|
|
- var maxLength = sourceRows.length;
|
|
|
+ function calculateTablePages() {
|
|
|
+ jphPageSize = Math.max(6, Math.floor(($('.center-main').height() - 124) / 52));
|
|
|
+ ngPageSize = Math.max(4, Math.floor(($('.right-top').height() - 104) / 30));
|
|
|
+ }
|
|
|
|
|
|
- for (var i = 0; i < maxLength; i++) {
|
|
|
+ function normalizeJphRows(rows) {
|
|
|
+ var sourceRows = getArray(rows);
|
|
|
+ var result = [];
|
|
|
+ for (var i = 0; i < sourceRows.length; i++) {
|
|
|
var item = sourceRows[i] || {};
|
|
|
- jphRows.push([
|
|
|
- item[0],
|
|
|
- item[1],
|
|
|
- item[2],
|
|
|
- item[3],
|
|
|
- item[4],
|
|
|
- item[5]
|
|
|
- ]);
|
|
|
- }
|
|
|
+ if ($.isArray(item)) {
|
|
|
+ if (item.length >= 7) {
|
|
|
+ result.push([item[0], item[1], item[2], item[3], item[4], item[5], item[6]]);
|
|
|
+ } else {
|
|
|
+ result.push(['', item[0], item[1], item[2], item[3], item[4], item[5]]);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ result.push([
|
|
|
+ item.lineTitle || item.lineSn || '',
|
|
|
+ item.oprno || '',
|
|
|
+ item.title || item.oprnoTitle || item.sn || '',
|
|
|
+ item.planCapacity || 0,
|
|
|
+ item.hourCapacity || 0,
|
|
|
+ item.actualCapacity || item.dayCount || 0,
|
|
|
+ item.shiftCapacity || item.shiftCount || 0
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
+ function renderJphTable(rows) {
|
|
|
+ jphRows = normalizeJphRows(rows);
|
|
|
jphPageIndex = 0;
|
|
|
renderJphTablePage();
|
|
|
startJphPageTimer();
|
|
|
@@ -788,16 +825,20 @@
|
|
|
var html = '';
|
|
|
var start = jphPageIndex * jphPageSize;
|
|
|
var pageRows = jphRows.slice(start, start + jphPageSize);
|
|
|
-
|
|
|
+ if (!pageRows.length) {
|
|
|
+ $('#jphTable').html('<tr class="empty-row"><td colspan="7">暂无工位 JPH 数据</td></tr>');
|
|
|
+ return;
|
|
|
+ }
|
|
|
for (var i = 0; i < pageRows.length; i++) {
|
|
|
var row = pageRows[i];
|
|
|
html += '<tr>' +
|
|
|
- '<td>' + row[0] + '</td>' +
|
|
|
- '<td class="name">' + row[1] + '</td>' +
|
|
|
- '<td>' + row[2] + '</td>' +
|
|
|
- '<td>' + row[3] + '</td>' +
|
|
|
- '<td>' + row[4] + '</td>' +
|
|
|
- '<td>' + row[5] + '</td>' +
|
|
|
+ '<td class="line">' + escapeHtml(row[0]) + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row[1]) + '</td>' +
|
|
|
+ '<td class="name">' + escapeHtml(row[2]) + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row[3]) + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row[4]) + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row[5]) + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row[6]) + '</td>' +
|
|
|
'</tr>';
|
|
|
}
|
|
|
$('#jphTable').html(html);
|
|
|
@@ -808,105 +849,121 @@
|
|
|
clearInterval(jphPageTimer);
|
|
|
jphPageTimer = null;
|
|
|
}
|
|
|
-
|
|
|
var pageCount = Math.ceil(jphRows.length / jphPageSize);
|
|
|
- if (pageCount <= 1) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
+ if (pageCount <= 1) return;
|
|
|
jphPageTimer = setInterval(function () {
|
|
|
jphPageIndex = (jphPageIndex + 1) % pageCount;
|
|
|
- renderJphTablePage();
|
|
|
- }, 5000);
|
|
|
+ switchTablePage('#jphTable', renderJphTablePage);
|
|
|
+ }, 4500);
|
|
|
}
|
|
|
|
|
|
- // 渲染右上 NG 预警表。当前复用 screenData.alarms;
|
|
|
- // 如果后续后台提供专门的“超48小时未处理 NG 记录”接口,可在 loadData 中替换数据源。
|
|
|
function renderNgTable(items) {
|
|
|
- var source = getArray(items, [
|
|
|
- { sn: '20012549...', oprno: 'OP40', title: '工位结果', result: 'NG', time: '2026-05-29 10:07' },
|
|
|
- { sn: '20012549...', oprno: 'OP40', title: '工位结果', result: 'NG', time: '2026-05-29 10:07' },
|
|
|
- { sn: '20012549...', oprno: 'OP40', title: '工位结果', result: 'NG', time: '2026-05-29 10:07' },
|
|
|
- { sn: '20012549...', oprno: 'OP40', title: '工位结果', result: 'NG', time: '2026-05-29 10:07' },
|
|
|
- { sn: '20012549...', oprno: 'OP40', title: '工位结果', result: 'NG', time: '2026-05-29 10:07' }
|
|
|
- ]);
|
|
|
+ ngRows = getArray(items);
|
|
|
+ ngPageIndex = 0;
|
|
|
+ renderNgTablePage();
|
|
|
+ startNgPageTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderNgTablePage() {
|
|
|
var html = '';
|
|
|
- for (var i = 0; i < Math.min(source.length, 5); i++) {
|
|
|
- var row = source[i];
|
|
|
+ var start = ngPageIndex * ngPageSize;
|
|
|
+ var pageRows = ngRows.slice(start, start + ngPageSize);
|
|
|
+ if (!pageRows.length) {
|
|
|
+ $('#ngTable').html('<tr class="empty-row"><td colspan="5">暂无超 48 小时 NG 预警</td></tr>');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (var i = 0; i < pageRows.length; i++) {
|
|
|
+ var row = pageRows[i] || {};
|
|
|
html += '<tr>' +
|
|
|
- '<td>' + (row.sn || row.msg || '') + '</td>' +
|
|
|
- '<td>' + (row.oprno || 'OP40') + '</td>' +
|
|
|
- '<td>' + (row.title || '工位结果') + '</td>' +
|
|
|
- '<td>' + (row.result || 'NG') + '</td>' +
|
|
|
- '<td>' + (row.time || row.createDate || '') + '</td>' +
|
|
|
+ '<td title="' + escapeHtml(row.sn || row.msg || '') + '">' + escapeHtml(row.sn || row.msg || '') + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row.oprno || '') + '</td>' +
|
|
|
+ '<td title="' + escapeHtml(row.title || '') + '">' + escapeHtml(row.title || '') + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row.result || row.content || '') + '</td>' +
|
|
|
+ '<td>' + escapeHtml(row.time || row.createDate || row.updateDate || '') + '</td>' +
|
|
|
'</tr>';
|
|
|
}
|
|
|
$('#ngTable').html(html);
|
|
|
}
|
|
|
|
|
|
- // 大屏核心格式化入口:
|
|
|
- // 1. 从 screenData/hourlyData 取接口数据;
|
|
|
- // 2. 对缺失字段做兜底和派生计算;
|
|
|
- // 3. 写入顶部指标、表格和各个 ECharts 面板。
|
|
|
+ function startNgPageTimer() {
|
|
|
+ if (ngPageTimer) {
|
|
|
+ clearInterval(ngPageTimer);
|
|
|
+ ngPageTimer = null;
|
|
|
+ }
|
|
|
+ var pageCount = Math.ceil(ngRows.length / ngPageSize);
|
|
|
+ if (pageCount <= 1) return;
|
|
|
+ ngPageTimer = setInterval(function () {
|
|
|
+ ngPageIndex = (ngPageIndex + 1) % pageCount;
|
|
|
+ switchTablePage('#ngTable', renderNgTablePage);
|
|
|
+ }, 4200);
|
|
|
+ }
|
|
|
+
|
|
|
+ function switchTablePage(selector, renderer) {
|
|
|
+ var $body = $(selector);
|
|
|
+ $body.addClass('table-switching');
|
|
|
+ setTimeout(function () {
|
|
|
+ renderer();
|
|
|
+ $body.removeClass('table-switching');
|
|
|
+ }, 180);
|
|
|
+ }
|
|
|
+
|
|
|
function formatData() {
|
|
|
- // screenData.date/output/quality1/quality2 均来自 screenData4。
|
|
|
- // date 是近七日横轴,output 是近七日产量,quality1/quality2 用于推算合格/不合格。
|
|
|
- var dates = getArray(screenData.date, fallbackDates);
|
|
|
- var output = getArray(screenData.output, [400, 354, 256, 164, 241, 342, 286]);
|
|
|
- var quality1 = getArray(screenData.quality1, output.map(function (v) { return Math.round(v * .96); }));
|
|
|
- var quality2 = getArray(screenData.quality2, output.map(function (v) { return Math.max(1, Math.round(v * .04)); }));
|
|
|
-
|
|
|
- // 目前 screenData4 没有直接返回交付率/完成率曲线,因此先用产量数据派生展示。
|
|
|
- // 后续若后台返回 deliveryRateList、finishRateList 等字段,可在这里直接替换 values。
|
|
|
- var target = output.map(function () { return 1100; });
|
|
|
- var actual = output.map(function (v) { return Math.max(980, v + 800); });
|
|
|
- var plan = Number(screenData.plan) || 1200;
|
|
|
- var today = Number(screenData.actual) || output[output.length - 1] || 0;
|
|
|
- var month = sum(output) + today;
|
|
|
- var finishRate = plan ? Math.min(100, Math.round(today * 100 / plan)) : 0;
|
|
|
- var deliveryRate = Math.max(80, Math.min(99, finishRate || 98));
|
|
|
- var iqcRate = Math.max(88, Math.min(99, 100 - Math.round(sum(quality2) * 100 / Math.max(1, sum(output)))));
|
|
|
-
|
|
|
- // 写入大数字指标。today/month 等值来自接口或由近七日产量派生。
|
|
|
+ calculateTablePages();
|
|
|
+ var dates = getArray(screenData.date);
|
|
|
+ if (!dates.length) {
|
|
|
+ dates = recentDates(7);
|
|
|
+ }
|
|
|
+ var output = alignNumbers(screenData.output, dates.length);
|
|
|
+ var quality1 = alignNumbers(screenData.quality1, dates.length);
|
|
|
+ var quality2 = alignNumbers(screenData.quality2, dates.length);
|
|
|
+ var warehouseOut = alignNumbers(screenData.warehouseOut, dates.length);
|
|
|
+ var plan = numberValue(screenData.plan);
|
|
|
+ var today = numberValue(screenData.todayOutput || output[output.length - 1]);
|
|
|
+ var month = numberValue(screenData.monthOutput || sum(output));
|
|
|
+ var todayOut = numberValue(screenData.todayOut || warehouseOut[warehouseOut.length - 1]);
|
|
|
+ var monthOut = numberValue(screenData.monthOut || sum(warehouseOut));
|
|
|
+ var planTarget = dates.map(function () { return plan; });
|
|
|
+ var planRateValues = output.map(function (value) { return percent(value, plan); });
|
|
|
+ var finishRate = percent(today, plan);
|
|
|
+ var deliveryRate = percent(todayOut, today || plan);
|
|
|
+ var iqcRateValues = dates.map(function (_, i) { return percent(quality1[i], quality1[i] + quality2[i]); });
|
|
|
+ var iqcRate = iqcRateValues.length ? iqcRateValues[iqcRateValues.length - 1] : 0;
|
|
|
+
|
|
|
$('#todayTotal').text(today);
|
|
|
$('#monthTotal').text(month);
|
|
|
- $('#todayOut').text(Math.max(0, today - 2));
|
|
|
- $('#monthOut').text(Math.max(0, month - 8));
|
|
|
- $('#deliveryRateText,#deliveryBadge').text(deliveryRate + '%');
|
|
|
- $('#todayPlanRate').text(deliveryRate + '%');
|
|
|
- $('#finishRate').text((finishRate || 98) + '%');
|
|
|
+ $('#todayOut').text(todayOut);
|
|
|
+ $('#monthOut').text(monthOut);
|
|
|
+ $('#deliveryRateText').text(deliveryRate + '%');
|
|
|
+ $('#todayPlanRate').text(finishRate + '%');
|
|
|
+ $('#finishRate').text(finishRate + '%');
|
|
|
$('#iqcRate,#iqcRateText').text(iqcRate + '%');
|
|
|
|
|
|
renderBars('workpieceChart', dates, output, '#83ff64', '#14a9ff');
|
|
|
- renderLineArea('deliveryChart', dates, actual, target);
|
|
|
- renderBars('warehouseChart', dates, output.map(function (v) { return Math.max(0, Math.round(v * .92)); }), '#86ff62', '#13a4ff');
|
|
|
- renderLineArea('planRateChart', dates, actual, target);
|
|
|
- renderLineArea('finishRateChart', dates, actual, target);
|
|
|
- renderIqc(dates, quality2, dates.map(function (_, i) { return Math.max(88, Math.min(99, iqcRate - 3 + i)); }));
|
|
|
-
|
|
|
- // 小时上下线接口返回多个产线分组,这里优先展示 HEVXT,没有则展示 B13XT。
|
|
|
- // 如果要切换指定产线,调整这里的取值顺序即可。
|
|
|
+ renderLineArea('deliveryChart', dates, warehouseOut, output);
|
|
|
+ renderBars('warehouseChart', dates, warehouseOut, '#86ff62', '#13a4ff', {
|
|
|
+ top: 12,
|
|
|
+ left: 28,
|
|
|
+ right: 14,
|
|
|
+ bottom: 34,
|
|
|
+ containLabel: true
|
|
|
+ });
|
|
|
+ renderPercent('planRateChart', dates, planRateValues, 100);
|
|
|
+ renderLineArea('finishRateChart', dates, output, planTarget);
|
|
|
+ renderIqc(dates, quality2, iqcRateValues);
|
|
|
+
|
|
|
var hLine = hourlyData.hevxt || hourlyData.b13xt || {};
|
|
|
- renderDaily(getArray(hLine.hours, ['08:00','09:00','10:00','11:00','12:00','13:00','14:00']),
|
|
|
- getArray(hLine.online, [70, 72, 68, 75, 78, 74, 80]),
|
|
|
- getArray(hLine.offline, [63, 68, 70, 73, 71, 69, 72]));
|
|
|
+ var hours = getArray(hLine.hours);
|
|
|
+ renderDaily(hours, alignNumbers(hLine.online, hours.length), alignNumbers(hLine.offline, hours.length));
|
|
|
|
|
|
- // 表格类数据:JPH 表格取工位数据,NG 表格临时复用 alarms。
|
|
|
renderJphTable(screenData.oprnoData || screenData.workStationData);
|
|
|
- renderNgTable(screenData.alarms);
|
|
|
+ renderNgTable(screenData.ngWarnings || screenData.alarms);
|
|
|
}
|
|
|
|
|
|
- // 前端数据获取入口。
|
|
|
- // screenData4:综合数据接口,返回生产人数、设备数、计划/实际产量、近七日统计、质量、告警、工位数据等。
|
|
|
- // screenDataHourly:小时维度上下线接口,返回每条产线的小时、上线数、下线数。
|
|
|
- // 两个接口是异步返回的,所以每个接口成功后都调用 formatData(),谁先回来谁先渲染;
|
|
|
- // 另一个接口回来后会再次刷新相关图表。
|
|
|
function loadData() {
|
|
|
$.post('/js/a/mes/mesProduct/screenData4', function (ret) {
|
|
|
screenData = ret && ret.data ? ret.data : {};
|
|
|
formatData();
|
|
|
}).fail(function () {
|
|
|
- // 接口失败时也渲染一次兜底数据,避免大屏白屏。
|
|
|
screenData = {};
|
|
|
formatData();
|
|
|
});
|
|
|
@@ -914,6 +971,9 @@
|
|
|
$.post('/js/a/mes/mesProduct/screenDataHourly', function (ret) {
|
|
|
hourlyData = ret && ret.data ? ret.data : {};
|
|
|
formatData();
|
|
|
+ }).fail(function () {
|
|
|
+ hourlyData = {};
|
|
|
+ formatData();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -921,14 +981,13 @@
|
|
|
fitScreen();
|
|
|
updateClock();
|
|
|
loadData();
|
|
|
-
|
|
|
- // 时间只展示到分钟,20 秒刷新一次足够;业务数据每 2 分钟刷新一次。
|
|
|
setInterval(updateClock, 1000 * 20);
|
|
|
setInterval(loadData, 1000 * 60 * 2);
|
|
|
-
|
|
|
- // 窗口尺寸变化时,重新缩放画布并通知 ECharts 重新计算尺寸。
|
|
|
$(window).on('resize', function () {
|
|
|
fitScreen();
|
|
|
+ calculateTablePages();
|
|
|
+ renderJphTablePage();
|
|
|
+ renderNgTablePage();
|
|
|
Object.keys(chartMap).forEach(function (key) {
|
|
|
chartMap[key].resize();
|
|
|
});
|