|
|
@@ -0,0 +1,939 @@
|
|
|
+<!doctype html>
|
|
|
+<html lang="zh-CN">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
|
+ <title>MES生产管理模块数据大屏</title>
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ html,
|
|
|
+ body {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ margin: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #020b20;
|
|
|
+ font-family: "Microsoft YaHei", Arial, sans-serif;
|
|
|
+ }
|
|
|
+ .liuan-screen {
|
|
|
+ position: relative;
|
|
|
+ width: 1920px;
|
|
|
+ height: 1080px;
|
|
|
+ overflow: hidden;
|
|
|
+ color: #fff;
|
|
|
+ transform-origin: left top;
|
|
|
+ background: #04122c url("${ctxStatic}/screen/imgs/liuan7.png") center / 100% 100% no-repeat;
|
|
|
+ }
|
|
|
+ .top-frame {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ width: 1920px;
|
|
|
+ height: 84px;
|
|
|
+ background: url("${ctxStatic}/screen/imgs/liuan6.png") center / 100% 100% no-repeat;
|
|
|
+ z-index: 3;
|
|
|
+ }
|
|
|
+ .brand {
|
|
|
+ position: absolute;
|
|
|
+ left: 70px;
|
|
|
+ top: 22px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 23px;
|
|
|
+ font-weight: 700;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+ .brand-mark {
|
|
|
+ width: 42px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px solid rgba(101, 222, 255, .8);
|
|
|
+ transform: rotate(-24deg);
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+ .brand-mark::before,
|
|
|
+ .brand-mark::after {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ .brand-mark::before {
|
|
|
+ width: 9px;
|
|
|
+ height: 9px;
|
|
|
+ left: 16px;
|
|
|
+ top: -4px;
|
|
|
+ background: #ffce35;
|
|
|
+ box-shadow: 13px 9px 0 #ff4058;
|
|
|
+ }
|
|
|
+ .brand-mark::after {
|
|
|
+ width: 32px;
|
|
|
+ height: 2px;
|
|
|
+ left: 5px;
|
|
|
+ top: 10px;
|
|
|
+ background: linear-gradient(90deg, transparent, #66ffe6, transparent);
|
|
|
+ }
|
|
|
+ .title {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 17px;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ color: #f2fbff;
|
|
|
+ font-size: 29px;
|
|
|
+ line-height: 36px;
|
|
|
+ font-weight: 800;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ text-shadow: 0 0 14px rgba(75, 198, 255, .9);
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+ .clock {
|
|
|
+ position: absolute;
|
|
|
+ right: 72px;
|
|
|
+ top: 26px;
|
|
|
+ font-size: 18px;
|
|
|
+ color: rgba(255, 255, 255, .92);
|
|
|
+ letter-spacing: 1px;
|
|
|
+ z-index: 5;
|
|
|
+ }
|
|
|
+ .content {
|
|
|
+ position: absolute;
|
|
|
+ left: 48px;
|
|
|
+ right: 48px;
|
|
|
+ top: 98px;
|
|
|
+ bottom: 30px;
|
|
|
+ }
|
|
|
+ .panel {
|
|
|
+ position: absolute;
|
|
|
+ color: #e9fbff;
|
|
|
+ }
|
|
|
+ .panel-sm {
|
|
|
+ width: 390px;
|
|
|
+ height: 290px;
|
|
|
+ padding: 58px 25px 22px;
|
|
|
+ }
|
|
|
+ .panel-sm.bg-1 {
|
|
|
+ background: url("${ctxStatic}/screen/imgs/liuan1.png") top center / 390px 76px no-repeat,
|
|
|
+ linear-gradient(180deg, rgba(11, 77, 143, .22), rgba(3, 28, 71, .62));
|
|
|
+ border: 1px solid rgba(0, 146, 255, .45);
|
|
|
+ box-shadow: inset 0 0 35px rgba(0, 135, 255, .17);
|
|
|
+ }
|
|
|
+ .panel-sm.bg-2 {
|
|
|
+ background: url("${ctxStatic}/screen/imgs/liuan2.png") top center / 390px 76px no-repeat,
|
|
|
+ linear-gradient(180deg, rgba(11, 77, 143, .22), rgba(3, 28, 71, .62));
|
|
|
+ border: 1px solid rgba(0, 146, 255, .45);
|
|
|
+ box-shadow: inset 0 0 35px rgba(0, 135, 255, .17);
|
|
|
+ }
|
|
|
+ .panel-sm.bg-5 {
|
|
|
+ background: url("${ctxStatic}/screen/imgs/liuan5.png") top center / 390px 76px no-repeat,
|
|
|
+ linear-gradient(180deg, rgba(11, 77, 143, .22), rgba(3, 28, 71, .62));
|
|
|
+ border: 1px solid rgba(0, 146, 255, .45);
|
|
|
+ box-shadow: inset 0 0 35px rgba(0, 135, 255, .17);
|
|
|
+ }
|
|
|
+ .panel-title {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ top: 11px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #ecfeff;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ text-shadow: 0 0 10px rgba(0, 222, 255, .8);
|
|
|
+ }
|
|
|
+ .panel-title small {
|
|
|
+ display: block;
|
|
|
+ margin-top: 3px;
|
|
|
+ color: rgba(255, 255, 255, .75);
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .left-top { left: 0; top: 0; }
|
|
|
+ .left-mid { left: 0; top: 315px; height: 285px; }
|
|
|
+ .left-bottom { left: 0; bottom: 0; height: 290px; }
|
|
|
+ .right-top { right: 0; top: 0; width: 454px; height: 310px; padding: 54px 24px 20px; background: url("${ctxStatic}/screen/imgs/liuan14.png") center / 100% 100% no-repeat; }
|
|
|
+ .right-mid { right: 0; top: 335px; height: 285px; }
|
|
|
+ .right-bottom { right: 0; bottom: 0; height: 290px; }
|
|
|
+ .center-main {
|
|
|
+ left: 472px;
|
|
|
+ top: 18px;
|
|
|
+ width: 905px;
|
|
|
+ height: 630px;
|
|
|
+ padding: 76px 38px 28px;
|
|
|
+ background: url("${ctxStatic}/screen/imgs/liuan15.png") center / 100% 100% no-repeat;
|
|
|
+ }
|
|
|
+ .bottom-card {
|
|
|
+ width: 438px;
|
|
|
+ height: 258px;
|
|
|
+ bottom: 0;
|
|
|
+ padding: 54px 22px 18px;
|
|
|
+ background: linear-gradient(180deg, rgba(11, 77, 143, .22), rgba(3, 28, 71, .62));
|
|
|
+ border: 1px solid rgba(0, 146, 255, .45);
|
|
|
+ box-shadow: inset 0 0 35px rgba(0, 135, 255, .17);
|
|
|
+ }
|
|
|
+ .bottom-1 { left: 430px; }
|
|
|
+ .bottom-2 { left: 902px; }
|
|
|
+ .metric-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 16px;
|
|
|
+ height: 72px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+ .metric {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 56px 1fr;
|
|
|
+ align-items: center;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+ .metric img {
|
|
|
+ width: 52px;
|
|
|
+ height: 52px;
|
|
|
+ }
|
|
|
+ .metric-label {
|
|
|
+ color: #d9faff;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+ .metric-value {
|
|
|
+ color: #20f4ff;
|
|
|
+ font-size: 20px;
|
|
|
+ line-height: 26px;
|
|
|
+ font-weight: 800;
|
|
|
+ text-shadow: 0 0 10px rgba(37, 235, 255, .85);
|
|
|
+ }
|
|
|
+ .chart {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ .left-top .chart {
|
|
|
+ height: 178px;
|
|
|
+ }
|
|
|
+ .chart-with-metrics {
|
|
|
+ position: absolute;
|
|
|
+ left: 20px;
|
|
|
+ right: 20px;
|
|
|
+ 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;
|
|
|
+ right: 0;
|
|
|
+ top: 18px;
|
|
|
+ text-align: center;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 900;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ }
|
|
|
+ .jph-table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ color: #f3fcff;
|
|
|
+ table-layout: fixed;
|
|
|
+ }
|
|
|
+ .jph-table th {
|
|
|
+ height: 47px;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 800;
|
|
|
+ text-align: center;
|
|
|
+ background: rgba(8, 102, 173, .35);
|
|
|
+ }
|
|
|
+ .jph-table td {
|
|
|
+ height: 52px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 800;
|
|
|
+ border-bottom: 1px solid rgba(73, 193, 255, .12);
|
|
|
+ }
|
|
|
+ .jph-table tbody tr:nth-child(odd) td {
|
|
|
+ background: rgba(3, 75, 139, .42);
|
|
|
+ }
|
|
|
+ .jph-table tbody tr:nth-child(even) td {
|
|
|
+ background: rgba(2, 44, 102, .45);
|
|
|
+ }
|
|
|
+ .jph-table td.name {
|
|
|
+ text-align: left;
|
|
|
+ padding-left: 14px;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+ .ng-table {
|
|
|
+ width: 100%;
|
|
|
+ table-layout: fixed;
|
|
|
+ border-collapse: collapse;
|
|
|
+ }
|
|
|
+ .ng-table th {
|
|
|
+ color: #c8efff;
|
|
|
+ font-size: 12px;
|
|
|
+ height: 28px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ .ng-table td {
|
|
|
+ height: 30px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ text-align: center;
|
|
|
+ border-bottom: 1px solid rgba(61, 177, 255, .12);
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ .ng-table tbody tr:nth-child(odd) td {
|
|
|
+ background: rgba(10, 88, 153, .36);
|
|
|
+ }
|
|
|
+ .mini-stat {
|
|
|
+ position: absolute;
|
|
|
+ left: 24px;
|
|
|
+ top: 74px;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 54px 1fr;
|
|
|
+ gap: 10px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ .mini-stat img {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ }
|
|
|
+ .mini-stat span {
|
|
|
+ display: block;
|
|
|
+ color: #dffcff;
|
|
|
+ font-size: 15px;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+ .mini-stat strong {
|
|
|
+ color: #20f4ff;
|
|
|
+ font-size: 22px;
|
|
|
+ }
|
|
|
+ .legend {
|
|
|
+ position: absolute;
|
|
|
+ right: 32px;
|
|
|
+ top: 96px;
|
|
|
+ display: flex;
|
|
|
+ gap: 14px;
|
|
|
+ color: #dcefff;
|
|
|
+ font-size: 11px;
|
|
|
+ }
|
|
|
+ .legend i {
|
|
|
+ display: inline-block;
|
|
|
+ width: 12px;
|
|
|
+ height: 6px;
|
|
|
+ margin-right: 4px;
|
|
|
+ border-radius: 4px;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+ .line-green { background: #10f2b4; }
|
|
|
+ .line-yellow { background: #ffc82e; }
|
|
|
+ .line-blue { background: #18b8ff; }
|
|
|
+ .rate-number {
|
|
|
+ position: absolute;
|
|
|
+ right: 42px;
|
|
|
+ top: 85px;
|
|
|
+ color: #23f8ff;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 900;
|
|
|
+ }
|
|
|
+ .daily-legend {
|
|
|
+ position: absolute;
|
|
|
+ right: 34px;
|
|
|
+ top: 72px;
|
|
|
+ color: #dcefff;
|
|
|
+ font-size: 12px;
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+ .daily-legend span::before {
|
|
|
+ content: "";
|
|
|
+ display: inline-block;
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ margin-right: 5px;
|
|
|
+ border-radius: 50%;
|
|
|
+ vertical-align: -1px;
|
|
|
+ }
|
|
|
+ .daily-legend .online::before { background: #10f2b4; }
|
|
|
+ .daily-legend .offline::before { background: #18b8ff; }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div class="liuan-screen" id="screen">
|
|
|
+ <div class="top-frame"></div>
|
|
|
+ <div class="brand"><span class="brand-mark"></span><span>大洋MES</span></div>
|
|
|
+ <div class="title">MES生产管理模块数据大屏</div>
|
|
|
+ <div class="clock" id="clock"></div>
|
|
|
+
|
|
|
+ <div class="content">
|
|
|
+ <section class="panel panel-sm bg-1 left-top">
|
|
|
+ <div class="panel-title">工件数据统计</div>
|
|
|
+ <div class="metric-row">
|
|
|
+ <div class="metric">
|
|
|
+ <img src="${ctxStatic}/screen/imgs/liuan8.png" alt="">
|
|
|
+ <div><div class="metric-label">今日总产量</div><div class="metric-value" id="todayTotal">0</div></div>
|
|
|
+ </div>
|
|
|
+ <div class="metric">
|
|
|
+ <img src="${ctxStatic}/screen/imgs/liuan9.png" alt="">
|
|
|
+ <div><div class="metric-label">本月总产量</div><div class="metric-value" id="monthTotal">0</div></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart" id="workpieceChart"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel panel-sm bg-2 left-mid">
|
|
|
+ <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>
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <section class="panel panel-sm bg-5 left-bottom">
|
|
|
+ <div class="panel-title">出库数据统计</div>
|
|
|
+ <div class="metric-row">
|
|
|
+ <div class="metric">
|
|
|
+ <img src="${ctxStatic}/screen/imgs/liuan11.png" alt="">
|
|
|
+ <div><div class="metric-label">今日出库</div><div class="metric-value" id="todayOut">0</div></div>
|
|
|
+ </div>
|
|
|
+ <div class="metric">
|
|
|
+ <img src="${ctxStatic}/screen/imgs/liuan12.png" alt="">
|
|
|
+ <div><div class="metric-label">本月出库</div><div class="metric-value" id="monthOut">0</div></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart" id="warehouseChart"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel center-main">
|
|
|
+ <div class="table-title">HEV各工位实际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>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="jphTable"></tbody>
|
|
|
+ </table>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel right-top">
|
|
|
+ <div class="panel-title">NG预警记录<small>(超48小时未处理记录)</small></div>
|
|
|
+ <table class="ng-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>工件号</th>
|
|
|
+ <th>工位号</th>
|
|
|
+ <th>提交名称</th>
|
|
|
+ <th>结果</th>
|
|
|
+ <th>预警时间</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="ngTable"></tbody>
|
|
|
+ </table>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel panel-sm bg-2 right-mid">
|
|
|
+ <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>
|
|
|
+ <div class="rate-number" id="iqcRate">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="iqcChart"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel panel-sm bg-5 right-bottom">
|
|
|
+ <div class="panel-title">每日上下线统计</div>
|
|
|
+ <div class="daily-legend"><span class="online">上线</span><span class="offline">下线</span></div>
|
|
|
+ <div class="chart" id="dailyChart"></div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel bottom-card bottom-1">
|
|
|
+ <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>
|
|
|
+ <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>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel bottom-card bottom-2">
|
|
|
+ <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>
|
|
|
+ <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>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<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 jphPageIndex = 0;
|
|
|
+ var jphRows = [];
|
|
|
+ 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']
|
|
|
+ ];
|
|
|
+
|
|
|
+ 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 + ')'
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function pad(n) {
|
|
|
+ return n < 10 ? '0' + n : '' + n;
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateClock() {
|
|
|
+ var now = new Date();
|
|
|
+ var weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
|
|
+ $('#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 sum(list) {
|
|
|
+ list = list || [];
|
|
|
+ var total = 0;
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ total += Number(list[i]) || 0;
|
|
|
+ }
|
|
|
+ return total;
|
|
|
+ }
|
|
|
+
|
|
|
+ function chart(id, option) {
|
|
|
+ var el = document.getElementById(id);
|
|
|
+ if (!el) return;
|
|
|
+ if (!chartMap[id]) {
|
|
|
+ chartMap[id] = echarts.init(el);
|
|
|
+ }
|
|
|
+ chartMap[id].setOption(option, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图表通用网格配置,保证不同图表的坐标轴留白一致。
|
|
|
+ function baseGrid(bottom) {
|
|
|
+ return {
|
|
|
+ top: 18,
|
|
|
+ left: 35,
|
|
|
+ right: 16,
|
|
|
+ bottom: bottom || 28,
|
|
|
+ containLabel: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图表通用坐标轴样式,匹配蓝湖稿的蓝色科技风。
|
|
|
+ function axisStyle() {
|
|
|
+ return {
|
|
|
+ axisLine: { lineStyle: { color: 'rgba(97, 199, 255, .35)' } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { color: '#bdeeff', fontSize: 10 },
|
|
|
+ splitLine: { lineStyle: { color: 'rgba(75, 164, 255, .12)' } }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染柱状图:用于工件数据统计、出库数据统计等只需要一组数值的面板。
|
|
|
+ function renderBars(id, dates, values, color1, color2) {
|
|
|
+ chart(id, {
|
|
|
+ grid: baseGrid(22),
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ xAxis: Object.assign({ type: 'category', data: dates }, axisStyle()),
|
|
|
+ yAxis: Object.assign({ type: 'value' }, axisStyle()),
|
|
|
+ series: [{
|
|
|
+ type: 'bar',
|
|
|
+ data: values,
|
|
|
+ barWidth: 14,
|
|
|
+ itemStyle: {
|
|
|
+ barBorderRadius: [8, 8, 0, 0],
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: color1 },
|
|
|
+ { offset: 1, color: color2 }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染“实际/目标”双线面积图:用于计划交付率、计划达成率、计划完成率。
|
|
|
+ function renderLineArea(id, dates, values, target) {
|
|
|
+ chart(id, {
|
|
|
+ grid: baseGrid(22),
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ xAxis: Object.assign({ type: 'category', boundaryGap: false, data: dates }, axisStyle()),
|
|
|
+ yAxis: Object.assign({ type: 'value' }, axisStyle()),
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '实际',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: values,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 7,
|
|
|
+ lineStyle: { color: '#15f2b8', width: 3 },
|
|
|
+ itemStyle: { color: '#15f2b8' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(21,242,184,.42)' },
|
|
|
+ { offset: 1, color: 'rgba(21,242,184,.02)' }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '目标',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: target || values.map(function () { return 1100; }),
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: { color: '#ffcf30', width: 2 }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染百分比折线图。当前页面未直接调用,保留给后续接入纯百分比指标时复用。
|
|
|
+ function renderPercent(id, dates, values) {
|
|
|
+ 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)' }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染 IQC 合格率:柱子表示不合格数量,折线表示合格率。
|
|
|
+ function renderIqc(dates, bad, rates) {
|
|
|
+ chart('iqcChart', {
|
|
|
+ grid: baseGrid(24),
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ xAxis: Object.assign({ type: 'category', data: dates }, axisStyle()),
|
|
|
+ yAxis: [
|
|
|
+ Object.assign({ type: 'value' }, axisStyle()),
|
|
|
+ Object.assign({ type: 'value', min: 0, max: 100, axisLabel: { color: '#bdeeff', formatter: '{value}%' } }, axisStyle())
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'bar',
|
|
|
+ name: '不合格数量',
|
|
|
+ data: bad,
|
|
|
+ barWidth: 12,
|
|
|
+ itemStyle: {
|
|
|
+ barBorderRadius: [8, 8, 0, 0],
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: '#7cff55' },
|
|
|
+ { offset: 1, color: '#0ea4ff' }
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'line',
|
|
|
+ name: '合格率',
|
|
|
+ yAxisIndex: 1,
|
|
|
+ smooth: true,
|
|
|
+ data: rates,
|
|
|
+ lineStyle: { color: '#ffc72e', width: 3 },
|
|
|
+ itemStyle: { color: '#ffc72e' }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染每日上下线统计。数据来自 screenDataHourly:
|
|
|
+ // hLine.hours 为横轴小时,hLine.online 为上线数,hLine.offline 为下线数。
|
|
|
+ function renderDaily(dates, online, offline) {
|
|
|
+ chart('dailyChart', {
|
|
|
+ grid: baseGrid(20),
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ xAxis: Object.assign({ type: 'category', boundaryGap: false, data: dates }, axisStyle()),
|
|
|
+ yAxis: Object.assign({ type: 'value' }, axisStyle()),
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '上线',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: online,
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: { color: '#13f0b2', width: 3 },
|
|
|
+ areaStyle: { color: 'rgba(19, 240, 178, .22)' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '下线',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: offline,
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: { color: '#1aa9ff', width: 3 },
|
|
|
+ areaStyle: { color: 'rgba(26, 169, 255, .20)' }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染中间 JPH 表格。rows 优先取 screenData.oprnoData,
|
|
|
+ // 若旧接口没有该字段,则兼容使用 screenData.workStationData。
|
|
|
+ function renderJphTable(rows) {
|
|
|
+ jphRows = [];
|
|
|
+ var sourceRows = rows && rows.length ? rows : [];
|
|
|
+ var maxLength = sourceRows.length;
|
|
|
+
|
|
|
+ for (var i = 0; i < maxLength; i++) {
|
|
|
+ var item = sourceRows[i] || {};
|
|
|
+ jphRows.push([
|
|
|
+ item[0],
|
|
|
+ item[1],
|
|
|
+ item[2],
|
|
|
+ item[3],
|
|
|
+ item[4],
|
|
|
+ item[5]
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ jphPageIndex = 0;
|
|
|
+ renderJphTablePage();
|
|
|
+ startJphPageTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderJphTablePage() {
|
|
|
+ var html = '';
|
|
|
+ var start = jphPageIndex * jphPageSize;
|
|
|
+ var pageRows = jphRows.slice(start, start + jphPageSize);
|
|
|
+
|
|
|
+ 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>' +
|
|
|
+ '</tr>';
|
|
|
+ }
|
|
|
+ $('#jphTable').html(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ function startJphPageTimer() {
|
|
|
+ if (jphPageTimer) {
|
|
|
+ clearInterval(jphPageTimer);
|
|
|
+ jphPageTimer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var pageCount = Math.ceil(jphRows.length / jphPageSize);
|
|
|
+ if (pageCount <= 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ jphPageTimer = setInterval(function () {
|
|
|
+ jphPageIndex = (jphPageIndex + 1) % pageCount;
|
|
|
+ renderJphTablePage();
|
|
|
+ }, 5000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染右上 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' }
|
|
|
+ ]);
|
|
|
+ var html = '';
|
|
|
+ for (var i = 0; i < Math.min(source.length, 5); i++) {
|
|
|
+ var row = source[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>' +
|
|
|
+ '</tr>';
|
|
|
+ }
|
|
|
+ $('#ngTable').html(html);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 大屏核心格式化入口:
|
|
|
+ // 1. 从 screenData/hourlyData 取接口数据;
|
|
|
+ // 2. 对缺失字段做兜底和派生计算;
|
|
|
+ // 3. 写入顶部指标、表格和各个 ECharts 面板。
|
|
|
+ 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 等值来自接口或由近七日产量派生。
|
|
|
+ $('#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) + '%');
|
|
|
+ $('#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。
|
|
|
+ // 如果要切换指定产线,调整这里的取值顺序即可。
|
|
|
+ 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]));
|
|
|
+
|
|
|
+ // 表格类数据:JPH 表格取工位数据,NG 表格临时复用 alarms。
|
|
|
+ renderJphTable(screenData.oprnoData || screenData.workStationData);
|
|
|
+ renderNgTable(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();
|
|
|
+ });
|
|
|
+
|
|
|
+ $.post('/js/a/mes/mesProduct/screenDataHourly', function (ret) {
|
|
|
+ hourlyData = ret && ret.data ? ret.data : {};
|
|
|
+ formatData();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ $(function () {
|
|
|
+ fitScreen();
|
|
|
+ updateClock();
|
|
|
+ loadData();
|
|
|
+
|
|
|
+ // 时间只展示到分钟,20 秒刷新一次足够;业务数据每 2 分钟刷新一次。
|
|
|
+ setInterval(updateClock, 1000 * 20);
|
|
|
+ setInterval(loadData, 1000 * 60 * 2);
|
|
|
+
|
|
|
+ // 窗口尺寸变化时,重新缩放画布并通知 ECharts 重新计算尺寸。
|
|
|
+ $(window).on('resize', function () {
|
|
|
+ fitScreen();
|
|
|
+ Object.keys(chartMap).forEach(function (key) {
|
|
|
+ chartMap[key].resize();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+</html>
|