mesScreen5.html 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>产线阶段统计大屏</title>
  8. <link rel="stylesheet" href="${ctxStatic}/bootstrap/css/bootstrap.min.css">
  9. <link rel="stylesheet" href="${ctxStatic}/screen/style.css">
  10. <style>
  11. * { margin: 0; padding: 0; box-sizing: border-box; }
  12. body { overflow: hidden; font-family: "Microsoft YaHei", sans-serif; }
  13. .mes4-wrapper {
  14. position: relative;
  15. width: 1920px;
  16. height: 1080px;
  17. background-color: #00091A;
  18. background-image: url("${ctxStatic}/screen/imgs/ljrSreen1.png");
  19. background-size: 100% 100%;
  20. overflow: hidden;
  21. margin: 0 auto;
  22. }
  23. /* ===== 顶部标题栏 ===== */
  24. .mes4-header {
  25. position: absolute;
  26. top: 0;
  27. left: 0;
  28. width: 100%;
  29. height: 90px;
  30. z-index: 10;
  31. }
  32. .mes4-logo {
  33. position: absolute;
  34. top: 20px;
  35. left: 40px;
  36. height: 50px;
  37. width: auto;
  38. }
  39. .mes4-title {
  40. position: absolute;
  41. left: 50%;
  42. top: 20px;
  43. transform: translateX(-50%);
  44. font-size: 38px;
  45. font-weight: bold;
  46. color: #FFFFFF;
  47. letter-spacing: 3px;
  48. text-shadow: 0 0 10px rgba(77, 201, 245, 0.6);
  49. white-space: nowrap;
  50. }
  51. .mes4-datetime {
  52. position: absolute;
  53. top: 32px;
  54. right: 110px;
  55. text-align: right;
  56. }
  57. .mes4-datetime .mes4-date {
  58. display: inline-block;
  59. font-size: 18px;
  60. color: #FFFFFF;
  61. line-height: 28px;
  62. }
  63. .mes4-datetime .mes4-time {
  64. display: inline-block;
  65. margin-left: 14px;
  66. font-size: 22px;
  67. color: #FFFFFF;
  68. font-weight: bold;
  69. line-height: 28px;
  70. }
  71. .mes-fullscreen-btn {
  72. position: absolute;
  73. top: 28px;
  74. right: 18px;
  75. z-index: 30;
  76. padding: 6px 14px;
  77. font-size: 14px;
  78. line-height: 1.2;
  79. color: #4DC9F5;
  80. background: rgba(0, 20, 50, 0.65);
  81. border: 1px solid #4DC9F5;
  82. border-radius: 4px;
  83. cursor: pointer;
  84. font-family: "Microsoft YaHei", sans-serif;
  85. }
  86. .mes-fullscreen-btn:hover {
  87. color: #FFFFFF;
  88. background: rgba(77, 201, 245, 0.28);
  89. }
  90. html:fullscreen .mes-fullscreen-btn,
  91. html:-webkit-full-screen .mes-fullscreen-btn {
  92. display: none !important;
  93. }
  94. /* ===== 面板通用 - 使用自定义类名避免与 Bootstrap 的 .panel 冲突 ===== */
  95. .mes4-panel {
  96. position: absolute;
  97. overflow: hidden;
  98. background: transparent;
  99. border: none;
  100. box-shadow: none;
  101. }
  102. .mes4-panel .mes4-ptitle {
  103. height: 42px;
  104. line-height: 42px;
  105. text-align: center;
  106. font-size: 18px;
  107. font-weight: bold;
  108. color: #4DC9F5;
  109. letter-spacing: 1px;
  110. background: transparent;
  111. }
  112. .mes4-panel .mes4-pbody {
  113. position: absolute;
  114. top: 42px;
  115. left: 0;
  116. right: 0;
  117. bottom: 0;
  118. padding: 5px 10px;
  119. background: transparent;
  120. }
  121. .chart { width: 100%; height: 100%; display: block; }
  122. /* ===== 位置:与 screen3 高度规则一致 ===== */
  123. .p-alert { left: 36px; top: 118px; width: 440px; height: 430px; }
  124. .p-quality { left: 36px; top: 568px; width: 440px; height: 460px; }
  125. .p-product { display: none; }
  126. .p-table { left: 496px; top: 118px; width: 928px; height: 560px; }
  127. .p-month { display: none; }
  128. .p-overview { right: 30px; top: 130px; width: 430px; height: 290px; }
  129. .p-achieve { display: none; }
  130. .p-hourly { right: 36px; top: 568px; width: 440px; height: 460px; }
  131. .p-alert .mes4-ptitle,
  132. .p-overview .mes4-ptitle,
  133. .p-quality .mes4-ptitle,
  134. .p-hourly .mes4-ptitle {
  135. height: 40px;
  136. line-height: 40px;
  137. font-size: 16px;
  138. color: #4DC9F5;
  139. font-weight: bold;
  140. letter-spacing: 1px;
  141. }
  142. .p-alert .mes4-pbody { top: 40px; padding: 0 6px 8px; }
  143. .p-overview .mes4-pbody { top: 40px; padding: 0; }
  144. .p-table .mes4-pbody { top: 8px; padding: 8px 28px 12px; }
  145. .p-quality .mes4-pbody,
  146. .p-hourly .mes4-pbody { top: 68px; }
  147. /* ===== 报警信息 ===== */
  148. .alert-head {
  149. display: flex;
  150. align-items: center;
  151. height: 36px;
  152. padding: 0 22px;
  153. color: #4DC9F5;
  154. font-size: 14px;
  155. font-weight: bold;
  156. border-bottom: 1px solid rgba(77, 201, 245, 0.3);
  157. }
  158. .alert-head > div:nth-child(1) { width: 70px; }
  159. .alert-head > div:nth-child(2) { flex: 1; }
  160. .alert-row {
  161. display: flex;
  162. align-items: center;
  163. height: 28px;
  164. padding: 0 22px;
  165. color: #ffffff;
  166. font-size: 13px;
  167. }
  168. .alert-row > div:nth-child(1) { width: 70px; color: #4DC9F5; font-weight: bold; }
  169. .alert-row > div:nth-child(2) { flex: 1; }
  170. /* ===== OP 表格(6 工位铺满,字号加大) ===== */
  171. .op-table {
  172. width: 100%;
  173. height: calc(100% - 28px);
  174. border-collapse: collapse;
  175. color: #ffffff;
  176. font-size: 20px;
  177. table-layout: fixed;
  178. }
  179. .op-table thead th {
  180. height: 56px;
  181. color: #4DC9F5;
  182. font-weight: bold;
  183. font-size: 20px;
  184. letter-spacing: 1px;
  185. background: rgba(0, 100, 180, 0.28);
  186. border-bottom: 2px solid rgba(77, 201, 245, 0.5);
  187. text-align: center;
  188. }
  189. .op-table tbody td {
  190. height: 76px;
  191. text-align: center;
  192. border-bottom: 1px solid rgba(77, 201, 245, 0.22);
  193. vertical-align: middle;
  194. }
  195. .op-table tbody tr:last-child td {
  196. border-bottom: 2px solid rgba(77, 201, 245, 0.35);
  197. }
  198. .op-table tbody tr:hover td { background: rgba(0, 200, 255, 0.06); }
  199. .op-table tbody tr.op-row-alt td {
  200. background: rgba(0, 80, 140, 0.14);
  201. }
  202. .op-table tbody tr.op-row-alt:hover td { background: rgba(0, 200, 255, 0.08); }
  203. .op-table tbody tr.op-row-empty td {
  204. height: 76px;
  205. color: transparent;
  206. border-bottom: 1px solid rgba(77, 201, 245, 0.12);
  207. user-select: none;
  208. }
  209. .op-table-page-hint {
  210. text-align: right;
  211. height: 26px;
  212. line-height: 26px;
  213. font-size: 15px;
  214. color: #4DC9F5;
  215. padding-right: 4px;
  216. margin-bottom: 2px;
  217. letter-spacing: 1px;
  218. }
  219. .op-table td.op-col { color: #00C8FF; font-weight: bold; font-size: 26px; }
  220. .op-table td.op-name { color: #ffffff; text-align: center; font-size: 22px; }
  221. .op-table td.op-val { color: #00F7FF; font-weight: bold; font-size: 28px; }
  222. /* ===== 产线阶段数据总览 (按模板排布) ===== */
  223. .overview-wrap {
  224. position: relative;
  225. height: 100%;
  226. padding: 14px 18px;
  227. }
  228. .overview-cards {
  229. display: grid;
  230. grid-template-columns: 1fr 1fr;
  231. gap: 14px 170px;
  232. height: 100%;
  233. }
  234. .overview-card {
  235. background: transparent;
  236. display: flex;
  237. flex-direction: column;
  238. align-items: center;
  239. justify-content: center;
  240. padding: 4px;
  241. padding-top: 19px;
  242. }
  243. .overview-card .oc-label {
  244. color: #4DC9F5;
  245. font-size: 14px;
  246. margin-bottom: 4px;
  247. }
  248. .overview-card .oc-value {
  249. color: #00F7FF;
  250. font-size: 24px;
  251. font-weight: bold;
  252. line-height: 1;
  253. }
  254. .overview-gauge-wrap {
  255. display: none;
  256. }
  257. .overview-gauge-wrap canvas { width: 100%; height: 100%; }
  258. .overview-gauge-wrap .gauge-txt {
  259. position: absolute;
  260. top: 50%;
  261. left: 50%;
  262. transform: translate(-50%, -50%);
  263. text-align: center;
  264. color: #00F7FF;
  265. pointer-events: none;
  266. }
  267. .overview-gauge-wrap .gauge-txt .val {
  268. font-size: 30px;
  269. font-weight: bold;
  270. line-height: 1;
  271. }
  272. .overview-gauge-wrap .gauge-txt .lbl {
  273. font-size: 13px;
  274. color: #ffffff;
  275. margin-top: 6px;
  276. }
  277. /* ===== 图例 ===== */
  278. .legend-inline {
  279. display: flex;
  280. align-items: center;
  281. justify-content: flex-end;
  282. padding: 4px 18px 0;
  283. font-size: 13px;
  284. color: #ffffff;
  285. gap: 18px;
  286. }
  287. .legend-inline .dot {
  288. display: inline-block;
  289. width: 12px;
  290. height: 12px;
  291. border-radius: 50%;
  292. margin-right: 5px;
  293. vertical-align: middle;
  294. }
  295. </style>
  296. </head>
  297. <body>
  298. <div class="mes4-wrapper" id="app">
  299. <div class="mes4-header">
  300. <img class="mes4-logo" src="${ctxStatic}/screen/imgs/ljrLogo.png" alt="logo">
  301. <div class="mes4-title">机加阶段统计大屏</div>
  302. <div class="mes4-datetime">
  303. <span class="mes4-date" id="mes4Date"></span>
  304. <span class="mes4-time" id="mes4Time"></span>
  305. </div>
  306. <button type="button" class="mes-fullscreen-btn" id="btnFullscreen" title="全屏">全屏</button>
  307. </div>
  308. <div class="mes4-panel p-alert">
  309. <div class="mes4-ptitle"> 产线阶段报警信息 </div>
  310. <div class="mes4-pbody">
  311. <div class="alert-head"><div>序号</div><div>报警类型</div></div>
  312. <div id="alertList"></div>
  313. </div>
  314. </div>
  315. <div class="mes4-panel p-quality">
  316. <div class="mes4-ptitle"> 近七日产品质检数据分析 </div>
  317. <div class="legend-inline">
  318. <span><i class="dot" style="background:#00FF88"></i>产出</span>
  319. <span><i class="dot" style="background:#00C8FF"></i>不良</span>
  320. </div>
  321. <div class="mes4-pbody"><canvas id="chartQuality" class="chart"></canvas></div>
  322. </div>
  323. <div class="mes4-panel p-product">
  324. <div class="mes4-ptitle"> 近七日产量分析 </div>
  325. <div class="mes4-pbody"><canvas id="chartProduct" class="chart"></canvas></div>
  326. </div>
  327. <div class="mes4-panel p-table">
  328. <div class="mes4-pbody">
  329. <div class="op-table-page-hint" id="opTablePageHint"></div>
  330. <table class="op-table">
  331. <thead>
  332. <tr>
  333. <th style="width:160px">工位</th>
  334. <th style="width:300px">工位名称</th>
  335. <th>实际产能</th>
  336. <th>当班产能</th>
  337. </tr>
  338. </thead>
  339. <tbody id="opTableBody"></tbody>
  340. </table>
  341. </div>
  342. </div>
  343. <div class="mes4-panel p-month">
  344. <div class="mes4-ptitle">各月产量统计图</div>
  345. <div class="mes4-pbody"><canvas id="chartMonth" class="chart"></canvas></div>
  346. </div>
  347. <div class="mes4-panel p-overview">
  348. <div class="mes4-ptitle"> 产线阶段数据总览 </div>
  349. <div class="mes4-pbody" style="padding:0">
  350. <div class="overview-wrap">
  351. <div class="overview-cards">
  352. <div class="overview-card">
  353. <div class="oc-label">生产人数</div>
  354. <div class="oc-value"><span id="productionPeople">48</span></div>
  355. </div>
  356. <div class="overview-card">
  357. <div class="oc-label">设备数量</div>
  358. <div class="oc-value"><span id="deviceCount">56</span></div>
  359. </div>
  360. <div class="overview-card">
  361. <div class="oc-label">计划产量</div>
  362. <div class="oc-value"><span id="planProduction">221</span></div>
  363. </div>
  364. <div class="overview-card">
  365. <div class="oc-label">实际产量</div>
  366. <div class="oc-value"><span id="actualProduction">201</span></div>
  367. </div>
  368. </div>
  369. </div>
  370. </div>
  371. </div>
  372. <div class="mes4-panel p-achieve">
  373. <div class="mes4-ptitle"> 近七日达成率数据分析 </div>
  374. <div class="legend-inline">
  375. <span><i class="dot" style="background:#FFDD00"></i>目标</span>
  376. <span><i class="dot" style="background:#00C8FF"></i>实际</span>
  377. </div>
  378. <div class="mes4-pbody" style="top:62px"><canvas id="chartAchieve" class="chart"></canvas></div>
  379. </div>
  380. <div class="mes4-panel p-hourly">
  381. <div class="mes4-ptitle"> 每小时上下线数据分析 </div>
  382. <div class="legend-inline">
  383. <span><i class="dot" style="background:#00FF88"></i>上线</span>
  384. <span><i class="dot" style="background:#00C8FF"></i>下线</span>
  385. </div>
  386. <div class="mes4-pbody"><canvas id="chartHourly" class="chart"></canvas></div>
  387. </div>
  388. </div>
  389. <script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
  390. <script>
  391. (function() {
  392. var __screenFail = 0, __screenReloadAt = 0;
  393. $(document).ajaxError(function(e, xhr) {
  394. if (!xhr) return;
  395. var text = xhr.responseText || '';
  396. if (xhr.status === 0 || xhr.status === 401 || xhr.status === 403 || xhr.status === 502 || xhr.status === 503
  397. || text.indexOf('login') >= 0 || text.indexOf('登录') >= 0) {
  398. __screenFail++;
  399. if (__screenFail >= 3 && Date.now() - __screenReloadAt > 60000) {
  400. __screenReloadAt = Date.now();
  401. location.reload();
  402. }
  403. }
  404. });
  405. $(document).ajaxSuccess(function() { __screenFail = 0; });
  406. function pad(n) { return n < 10 ? '0' + n : '' + n; }
  407. /** 工位表:每页条数与自动翻页间隔(毫秒) */
  408. var OP_TABLE_PAGE_SIZE = 6;
  409. var OP_TABLE_PAGE_ROTATE_MS = 8000;
  410. var opTablePageIndex = 0;
  411. var opTableRotateTimer = null;
  412. function padOpTableRowsToPageSize(html, rowCount) {
  413. for (var j = rowCount; j < OP_TABLE_PAGE_SIZE; j++) {
  414. var alt = (j % 2 === 1) ? ' op-row-alt' : '';
  415. html += '<tr class="op-row-empty' + alt + '"><td colspan="4">&nbsp;</td></tr>';
  416. }
  417. return html;
  418. }
  419. function getOpTablePageCount(list) {
  420. list = list || [];
  421. if (list.length === 0) {
  422. return 1;
  423. }
  424. return Math.ceil(list.length / OP_TABLE_PAGE_SIZE);
  425. }
  426. function stopOpTablePageRotate() {
  427. if (opTableRotateTimer) {
  428. clearInterval(opTableRotateTimer);
  429. opTableRotateTimer = null;
  430. }
  431. }
  432. function startOpTablePageRotate() {
  433. stopOpTablePageRotate();
  434. var listNow = mockData.opTable || [];
  435. if (getOpTablePageCount(listNow) <= 1) {
  436. return;
  437. }
  438. opTableRotateTimer = setInterval(function() {
  439. var list = mockData.opTable || [];
  440. var pageCount = getOpTablePageCount(list);
  441. if (pageCount <= 1) {
  442. return;
  443. }
  444. opTablePageIndex = (opTablePageIndex + 1) % pageCount;
  445. renderTable();
  446. }, OP_TABLE_PAGE_ROTATE_MS);
  447. }
  448. function updateClock() {
  449. var now = new Date();
  450. var y = now.getFullYear();
  451. var m = now.getMonth() + 1;
  452. var d = now.getDate();
  453. var w = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][now.getDay()];
  454. document.getElementById('mes4Date').textContent = y + '年' + pad(m) + '月' + pad(d) + '日 ' + w;
  455. var h = now.getHours(), mi = now.getMinutes(), s = now.getSeconds();
  456. document.getElementById('mes4Time').textContent = pad(h) + ':' + pad(mi) + ':' + pad(s);
  457. }
  458. // ====== 页面数据 ======
  459. var mockData = {};
  460. /** 本屏产线:XT(工位 OP110-OP160) */
  461. var LINE_SN = 'XT';
  462. var OP_FROM = 110;
  463. var OP_TO = 160;
  464. /** 每小时上下线:上线 OP110 / 下线 OP160 */
  465. var HOURLY_API_CFG = { lineSn: LINE_SN, onlineOprno: 'OP110', offlineOprno: 'OP160' };
  466. function buildHourLabels24() {
  467. var labels = [];
  468. for (var i = 1; i <= 24; i++) {
  469. labels.push(pad(i));
  470. }
  471. return labels;
  472. }
  473. function updateHourlyUpDownByApi() {
  474. $.ajax({
  475. url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
  476. type: 'POST',
  477. dataType: 'json',
  478. data: {
  479. lineSn: HOURLY_API_CFG.lineSn,
  480. onlineOprno: HOURLY_API_CFG.onlineOprno,
  481. offlineOprno: HOURLY_API_CFG.offlineOprno
  482. },
  483. success: function(resp) {
  484. if (resp && resp.result === 'true' && resp.data) {
  485. var d = resp.data;
  486. if (!d.up || !d.down || d.up.length < 24 || d.down.length < 24) {
  487. return;
  488. }
  489. mockData.hourly = {
  490. labels: buildHourLabels24(),
  491. up: d.up,
  492. down: d.down,
  493. max: typeof d.max === 'number' ? d.max : 100
  494. };
  495. drawAllCharts();
  496. }
  497. }
  498. });
  499. }
  500. function generateMockData() {
  501. // 最近7日日期
  502. var now = new Date();
  503. var weekLabels = [];
  504. for (var i = 6; i >= 0; i--) {
  505. var d = new Date(now);
  506. d.setDate(d.getDate() - i);
  507. weekLabels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
  508. }
  509. // 报警信息由 updateDeviceAlarmAlertsByApi 拉取
  510. mockData.alerts = [];
  511. // 工位表由 mesLineProcess(XT,OP110-OP160)拉取
  512. mockData.opTable = [];
  513. // 数据卡(总览固定展示)
  514. mockData.cards = {
  515. productionPeople: 48,
  516. deviceCount: 56,
  517. planProduction: 221,
  518. actualProduction: 201
  519. };
  520. mockData.rate = 70;
  521. // 近七日产品质检
  522. mockData.qualityInspect = {
  523. labels: weekLabels,
  524. produced: [400, 321, 354, 164, 241, 342, 231],
  525. defect: [345, 256, 312, 123, 214, 286, 189],
  526. max: 500
  527. };
  528. // 近七日达成率
  529. mockData.achieveRate = {
  530. labels: weekLabels,
  531. target: [1150, 1160, 1180, 1165, 1180, 1180, 1170],
  532. actual: [1150, 1160, 1180, 1165, 1200, 1200, 1170],
  533. max: 1300, min: 1100
  534. };
  535. // 近七日产量
  536. mockData.productTrend = {
  537. labels: weekLabels,
  538. data: [1100, 1100, 1200, 1125, 1125, 1180, 1150],
  539. max: 1300, min: 1000
  540. };
  541. // 每小时上下线 (24 小时)
  542. var hourLabels = [], up = [], down = [];
  543. for (var i = 1; i <= 24; i++) {
  544. hourLabels.push(pad(i));
  545. up.push(Math.round(60 + Math.random() * 30));
  546. down.push(Math.round(50 + Math.random() * 30));
  547. }
  548. mockData.hourly = { labels: hourLabels, up: up, down: down, max: 100 };
  549. // 各月产量
  550. mockData.monthly = {
  551. labels: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
  552. data: [ 350, 340, 480, 360, 350, 360, 420, 410, 380, 420, 470, 400 ],
  553. max: 500
  554. };
  555. }
  556. // ====== 工具函数 ======
  557. function hexToRgba(hex, a) {
  558. var r = parseInt(hex.slice(1, 3), 16),
  559. g = parseInt(hex.slice(3, 5), 16),
  560. b = parseInt(hex.slice(5, 7), 16);
  561. return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
  562. }
  563. function prepareCanvas(id) {
  564. var c = document.getElementById(id);
  565. if (!c) return null;
  566. var p = c.parentElement;
  567. var w = p.clientWidth, h = p.clientHeight;
  568. if (c.width !== w || c.height !== h) { c.width = w; c.height = h; }
  569. return { ctx: c.getContext('2d'), w: w, h: h };
  570. }
  571. function drawYAxis(ctx, padL, padT, cw, ch, maxV, minV) {
  572. minV = minV || 0;
  573. ctx.fillStyle = 'rgba(77,201,245,0.8)';
  574. ctx.font = '11px Microsoft YaHei';
  575. ctx.textAlign = 'right';
  576. ctx.strokeStyle = 'rgba(0,143,253,0.18)';
  577. ctx.lineWidth = 1;
  578. for (var i = 0; i <= 4; i++) {
  579. var val = Math.round(minV + (maxV - minV) * (4 - i) / 4);
  580. var y = padT + ch * i / 4;
  581. ctx.fillText(val, padL - 5, y + 4);
  582. ctx.beginPath();
  583. ctx.moveTo(padL, y);
  584. ctx.lineTo(padL + cw, y);
  585. ctx.stroke();
  586. }
  587. }
  588. // 双柱状图 - 近七日产品质检 (产出/不良)
  589. function drawDoubleBar(id, a, b, labels, maxV) {
  590. var p = prepareCanvas(id); if (!p) return;
  591. var ctx = p.ctx, w = p.w, h = p.h;
  592. var padL = 38, padR = 10, padT = 12, padB = 22;
  593. var cw = w - padL - padR, ch = h - padT - padB;
  594. ctx.clearRect(0, 0, w, h);
  595. drawYAxis(ctx, padL, padT, cw, ch, maxV);
  596. if (!a || a.length < 1) return;
  597. var gw = cw / a.length, bw = gw * 0.3;
  598. for (var i = 0; i < a.length; i++) {
  599. var cx = padL + i * gw + gw / 2;
  600. var h1 = (a[i] / maxV) * ch;
  601. var g1 = ctx.createLinearGradient(0, padT + ch - h1, 0, padT + ch);
  602. g1.addColorStop(0, '#00FF88'); g1.addColorStop(1, 'rgba(0,255,136,0.15)');
  603. ctx.fillStyle = g1;
  604. ctx.fillRect(cx - bw - 2, padT + ch - h1, bw, h1);
  605. var h2 = (b[i] / maxV) * ch;
  606. var g2 = ctx.createLinearGradient(0, padT + ch - h2, 0, padT + ch);
  607. g2.addColorStop(0, '#00C8FF'); g2.addColorStop(1, 'rgba(0,200,255,0.15)');
  608. ctx.fillStyle = g2;
  609. ctx.fillRect(cx + 2, padT + ch - h2, bw, h2);
  610. ctx.fillStyle = '#00FF88'; ctx.font = 'bold 10px Microsoft YaHei';
  611. ctx.textAlign = 'center'; ctx.fillText(a[i], cx - bw / 2 - 2, padT + ch - h1 - 4);
  612. ctx.fillStyle = '#00C8FF'; ctx.fillText(b[i], cx + bw / 2 + 2, padT + ch - h2 - 4);
  613. ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = '11px Microsoft YaHei';
  614. ctx.fillText(labels[i], cx, h - 6);
  615. }
  616. }
  617. // 单折线图 (近七日产量, 平滑+面积)
  618. function drawLineChart(id, data, labels, maxV, minV, color) {
  619. var p = prepareCanvas(id); if (!p) return;
  620. var ctx = p.ctx, w = p.w, h = p.h;
  621. var padL = 45, padR = 12, padT = 15, padB = 25;
  622. var cw = w - padL - padR, ch = h - padT - padB;
  623. color = color || '#00C8FF';
  624. minV = minV || 0;
  625. ctx.clearRect(0, 0, w, h);
  626. drawYAxis(ctx, padL, padT, cw, ch, maxV, minV);
  627. if (!data || data.length < 2) return;
  628. var stepX = cw / (data.length - 1);
  629. var range = maxV - minV;
  630. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  631. grad.addColorStop(0, hexToRgba(color, 0.4));
  632. grad.addColorStop(1, hexToRgba(color, 0.02));
  633. ctx.beginPath();
  634. ctx.moveTo(padL, padT + ch);
  635. for (var i = 0; i < data.length; i++) {
  636. var x = padL + i * stepX;
  637. var y = padT + ch - ((data[i] - minV) / range) * ch;
  638. if (i === 0) ctx.lineTo(x, y);
  639. else {
  640. var px = padL + (i - 1) * stepX;
  641. var py = padT + ch - ((data[i - 1] - minV) / range) * ch;
  642. var cpx = (px + x) / 2;
  643. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  644. }
  645. }
  646. ctx.lineTo(padL + (data.length - 1) * stepX, padT + ch);
  647. ctx.closePath();
  648. ctx.fillStyle = grad;
  649. ctx.fill();
  650. ctx.beginPath();
  651. for (var j = 0; j < data.length; j++) {
  652. var x2 = padL + j * stepX;
  653. var y2 = padT + ch - ((data[j] - minV) / range) * ch;
  654. if (j === 0) ctx.moveTo(x2, y2);
  655. else {
  656. var px2 = padL + (j - 1) * stepX;
  657. var py2 = padT + ch - ((data[j - 1] - minV) / range) * ch;
  658. var cpx2 = (px2 + x2) / 2;
  659. ctx.bezierCurveTo(cpx2, py2, cpx2, y2, x2, y2);
  660. }
  661. }
  662. ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke();
  663. ctx.font = 'bold 11px Microsoft YaHei'; ctx.textAlign = 'center';
  664. for (var k = 0; k < data.length; k++) {
  665. var x3 = padL + k * stepX;
  666. var y3 = padT + ch - ((data[k] - minV) / range) * ch;
  667. ctx.beginPath(); ctx.arc(x3, y3, 3, 0, Math.PI * 2);
  668. ctx.fillStyle = color; ctx.fill();
  669. ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
  670. ctx.fillStyle = '#ffffff';
  671. ctx.fillText(data[k], x3, y3 - 8);
  672. }
  673. ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = '11px Microsoft YaHei';
  674. for (var m = 0; m < labels.length; m++) {
  675. var xx = padL + m * stepX;
  676. ctx.fillText(labels[m], xx, h - 6);
  677. }
  678. }
  679. // 双折线图 (目标/实际)
  680. function drawDualLine(id, a, b, labels, maxV, minV) {
  681. var p = prepareCanvas(id); if (!p) return;
  682. var ctx = p.ctx, w = p.w, h = p.h;
  683. var padL = 45, padR = 12, padT = 12, padB = 22;
  684. var cw = w - padL - padR, ch = h - padT - padB;
  685. minV = minV || 0;
  686. var range = maxV - minV;
  687. ctx.clearRect(0, 0, w, h);
  688. drawYAxis(ctx, padL, padT, cw, ch, maxV, minV);
  689. if (!a || a.length < 2) return;
  690. var stepX = cw / (a.length - 1);
  691. function draw(arr, color) {
  692. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  693. grad.addColorStop(0, hexToRgba(color, 0.3));
  694. grad.addColorStop(1, hexToRgba(color, 0.02));
  695. ctx.beginPath(); ctx.moveTo(padL, padT + ch);
  696. for (var i = 0; i < arr.length; i++) {
  697. var x = padL + i * stepX;
  698. var y = padT + ch - ((arr[i] - minV) / range) * ch;
  699. if (i === 0) ctx.lineTo(x, y);
  700. else {
  701. var px = padL + (i - 1) * stepX;
  702. var py = padT + ch - ((arr[i - 1] - minV) / range) * ch;
  703. var cpx = (px + x) / 2;
  704. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  705. }
  706. }
  707. ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
  708. ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
  709. ctx.beginPath();
  710. for (var j = 0; j < arr.length; j++) {
  711. var x2 = padL + j * stepX;
  712. var y2 = padT + ch - ((arr[j] - minV) / range) * ch;
  713. if (j === 0) ctx.moveTo(x2, y2);
  714. else {
  715. var px2 = padL + (j - 1) * stepX;
  716. var py2 = padT + ch - ((arr[j - 1] - minV) / range) * ch;
  717. var cpx2 = (px2 + x2) / 2;
  718. ctx.bezierCurveTo(cpx2, py2, cpx2, y2, x2, y2);
  719. }
  720. }
  721. ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke();
  722. for (var k = 0; k < arr.length; k++) {
  723. var x3 = padL + k * stepX;
  724. var y3 = padT + ch - ((arr[k] - minV) / range) * ch;
  725. ctx.beginPath(); ctx.arc(x3, y3, 3, 0, Math.PI * 2);
  726. ctx.fillStyle = color; ctx.fill();
  727. }
  728. }
  729. draw(a, '#FFDD00');
  730. draw(b, '#00C8FF');
  731. ctx.fillStyle = '#ffffff'; ctx.font = '10px Microsoft YaHei'; ctx.textAlign = 'center';
  732. for (var i = 0; i < b.length; i++) {
  733. var x = padL + i * stepX;
  734. var y = padT + ch - ((b[i] - minV) / range) * ch;
  735. ctx.fillText(b[i], x, y - 6);
  736. }
  737. ctx.fillStyle = 'rgba(77,201,245,0.9)';
  738. for (var i2 = 0; i2 < labels.length; i2++) {
  739. var x2 = padL + i2 * stepX;
  740. ctx.fillText(labels[i2], x2, h - 6);
  741. }
  742. }
  743. // 双面积折线图 (每小时上下线)
  744. function drawAreaLine(id, up, down, labels, maxV) {
  745. var p = prepareCanvas(id); if (!p) return;
  746. var ctx = p.ctx, w = p.w, h = p.h;
  747. var padL = 30, padR = 12, padT = 12, padB = 22;
  748. var cw = w - padL - padR, ch = h - padT - padB;
  749. ctx.clearRect(0, 0, w, h);
  750. drawYAxis(ctx, padL, padT, cw, ch, maxV);
  751. if (!up || up.length < 2) return;
  752. var stepX = cw / (up.length - 1);
  753. function draw(arr, color) {
  754. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  755. grad.addColorStop(0, hexToRgba(color, 0.45));
  756. grad.addColorStop(1, hexToRgba(color, 0.05));
  757. ctx.beginPath(); ctx.moveTo(padL, padT + ch);
  758. for (var i = 0; i < arr.length; i++) {
  759. var x = padL + i * stepX;
  760. var y = padT + ch - (arr[i] / maxV) * ch;
  761. if (i === 0) ctx.lineTo(x, y);
  762. else {
  763. var px = padL + (i - 1) * stepX;
  764. var py = padT + ch - (arr[i - 1] / maxV) * ch;
  765. var cpx = (px + x) / 2;
  766. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  767. }
  768. }
  769. ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
  770. ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
  771. ctx.beginPath();
  772. for (var j = 0; j < arr.length; j++) {
  773. var x2 = padL + j * stepX;
  774. var y2 = padT + ch - (arr[j] / maxV) * ch;
  775. if (j === 0) ctx.moveTo(x2, y2);
  776. else {
  777. var px2 = padL + (j - 1) * stepX;
  778. var py2 = padT + ch - (arr[j - 1] / maxV) * ch;
  779. var cpx2 = (px2 + x2) / 2;
  780. ctx.bezierCurveTo(cpx2, py2, cpx2, y2, x2, y2);
  781. }
  782. }
  783. ctx.strokeStyle = color; ctx.lineWidth = 1.6; ctx.stroke();
  784. }
  785. draw(up, '#00FF88');
  786. draw(down, '#00C8FF');
  787. ctx.fillStyle = 'rgba(77,201,245,0.9)'; ctx.font = '10px Microsoft YaHei'; ctx.textAlign = 'center';
  788. for (var i = 0; i < labels.length; i++) {
  789. if (i % 2 === 0) {
  790. var x = padL + i * stepX;
  791. ctx.fillText(labels[i], x, h - 6);
  792. }
  793. }
  794. }
  795. // 月产量柱状图 (渐变条)
  796. function drawMonthBar(id, data, labels, maxV) {
  797. var p = prepareCanvas(id); if (!p) return;
  798. var ctx = p.ctx, w = p.w, h = p.h;
  799. var padL = 42, padR = 12, padT = 15, padB = 25;
  800. var cw = w - padL - padR, ch = h - padT - padB;
  801. ctx.clearRect(0, 0, w, h);
  802. drawYAxis(ctx, padL, padT, cw, ch, maxV);
  803. if (!data || data.length < 1) return;
  804. var gw = cw / data.length, bw = gw * 0.35;
  805. for (var i = 0; i < data.length; i++) {
  806. var cx = padL + i * gw + gw / 2;
  807. var bh = (data[i] / maxV) * ch;
  808. var grad = ctx.createLinearGradient(0, padT + ch - bh, 0, padT + ch);
  809. grad.addColorStop(0, '#00FF88');
  810. grad.addColorStop(1, 'rgba(0,200,255,0.15)');
  811. ctx.fillStyle = grad;
  812. ctx.fillRect(cx - bw / 2, padT + ch - bh, bw, bh);
  813. ctx.fillStyle = 'rgba(77,201,245,0.9)';
  814. ctx.font = '11px Microsoft YaHei';
  815. ctx.textAlign = 'center';
  816. ctx.fillText(labels[i], cx, h - 6);
  817. }
  818. }
  819. // 半环形仪表盘 (今日达成率)
  820. function drawGauge(id, percent) {
  821. var p = prepareCanvas(id); if (!p) return;
  822. var ctx = p.ctx, w = p.w, h = p.h;
  823. ctx.clearRect(0, 0, w, h);
  824. var cx = w / 2, cy = h / 2;
  825. var r = Math.min(w, h) / 2 - 8;
  826. // 底环
  827. ctx.beginPath();
  828. ctx.arc(cx, cy, r, 0, Math.PI * 2);
  829. ctx.lineWidth = 10;
  830. ctx.strokeStyle = 'rgba(0,143,253,0.25)';
  831. ctx.stroke();
  832. // 进度环
  833. var start = -Math.PI / 2;
  834. var end = start + Math.PI * 2 * (percent / 100);
  835. var grad = ctx.createLinearGradient(0, 0, w, h);
  836. grad.addColorStop(0, '#00C8FF');
  837. grad.addColorStop(1, '#00FF88');
  838. ctx.beginPath();
  839. ctx.arc(cx, cy, r, start, end, false);
  840. ctx.lineWidth = 10;
  841. ctx.strokeStyle = grad;
  842. ctx.lineCap = 'round';
  843. ctx.stroke();
  844. // 外环刻度
  845. ctx.beginPath();
  846. ctx.arc(cx, cy, r + 10, 0, Math.PI * 2);
  847. ctx.lineWidth = 1;
  848. ctx.strokeStyle = 'rgba(0,200,255,0.35)';
  849. ctx.stroke();
  850. }
  851. function escapeHtml(s) {
  852. if (s == null || s === '') return '';
  853. return String(s)
  854. .replace(/&/g, '&amp;')
  855. .replace(/</g, '&lt;')
  856. .replace(/>/g, '&gt;')
  857. .replace(/"/g, '&quot;');
  858. }
  859. // ====== 渲染列表(text 为设备报警记录的 type 字段)======
  860. function renderAlerts() {
  861. var html = '';
  862. var list = mockData.alerts || [];
  863. for (var i = 0; i < list.length; i++) {
  864. html += '<div class="alert-row"><div>' + list[i].no + '</div><div>' + escapeHtml(list[i].text) + '</div></div>';
  865. }
  866. var el = document.getElementById('alertList');
  867. if (el) el.innerHTML = html;
  868. }
  869. function renderTable() {
  870. var list = mockData.opTable || [];
  871. var pageCount = getOpTablePageCount(list);
  872. if (opTablePageIndex >= pageCount) {
  873. opTablePageIndex = 0;
  874. }
  875. var start = opTablePageIndex * OP_TABLE_PAGE_SIZE;
  876. var slice = list.slice(start, start + OP_TABLE_PAGE_SIZE);
  877. var html = '';
  878. for (var i = 0; i < slice.length; i++) {
  879. var r = slice[i];
  880. html += '<tr' + ((i % 2 === 1) ? ' class="op-row-alt"' : '') + '>';
  881. html += '<td class="op-col">' + r.code + '</td>';
  882. html += '<td class="op-name">' + r.name + '</td>';
  883. html += '<td class="op-val">' + r.real + '</td>';
  884. html += '<td class="op-val">' + r.shift + '</td>';
  885. html += '</tr>';
  886. }
  887. html = padOpTableRowsToPageSize(html, slice.length);
  888. var el = document.getElementById('opTableBody');
  889. if (el) el.innerHTML = html;
  890. var hint = document.getElementById('opTablePageHint');
  891. if (hint) {
  892. if (list.length === 0) {
  893. hint.textContent = '';
  894. } else if (pageCount > 1) {
  895. hint.textContent = '第 ' + (opTablePageIndex + 1) + ' / ' + pageCount + ' 页 / 每页 ' + OP_TABLE_PAGE_SIZE + ' 条 / 共 ' + list.length + ' 条';
  896. } else {
  897. hint.textContent = '共 ' + list.length + ' 条';
  898. }
  899. }
  900. }
  901. function applyLatestPlanNumsToTable(planNums) {}
  902. function syncAchieveRateWithPlan() {}
  903. function parseOprnoNum(code) {
  904. var m = String(code || '').toUpperCase().match(/^OP(\d+)/);
  905. return m ? parseInt(m[1], 10) : -1;
  906. }
  907. function isOprnoInRange(code, fromNum, toNum) {
  908. var n = parseOprnoNum(code);
  909. return n >= fromNum && n <= toNum;
  910. }
  911. function updateLineProcessTableByApi() {
  912. $.ajax({
  913. url: '${ctx}/mes/mesProductScreen/lineProcessList',
  914. type: 'POST',
  915. dataType: 'json',
  916. data: {
  917. pageNo: 1,
  918. pageSize: 5000,
  919. lineSn: LINE_SN
  920. },
  921. success: function(resp) {
  922. var rows = extractUserList(resp);
  923. var out = [];
  924. for (var i = 0; i < rows.length; i++) {
  925. var p = rows[i] || {};
  926. var ls = String(p.lineSn || p.line_sn || '').toUpperCase();
  927. if (ls !== LINE_SN) continue;
  928. var code = p.oprno || '';
  929. if (!isOprnoInRange(code, OP_FROM, OP_TO)) continue;
  930. out.push({
  931. code: code,
  932. name: p.title || '',
  933. real: 0,
  934. shift: 0,
  935. _opNum: parseOprnoNum(code)
  936. });
  937. }
  938. out.sort(function(a, b) { return a._opNum - b._opNum; });
  939. mockData.opTable = out;
  940. opTablePageIndex = 0;
  941. renderTable();
  942. startOpTablePageRotate();
  943. if (out.length > 0) {
  944. updateHourlyCapacityByApi();
  945. }
  946. }
  947. });
  948. }
  949. function updateHourlyCapacityByApi() {
  950. var list = mockData.opTable || [];
  951. if (!list.length) return;
  952. var oprnos = [];
  953. for (var i = 0; i < list.length; i++) {
  954. if (list[i] && list[i].code) oprnos.push(list[i].code);
  955. }
  956. if (!oprnos.length) return;
  957. fetchCapacityInto('dayCapacityData', LINE_SN, oprnos, 'real', {});
  958. fetchCapacityInto('shiftCapacityData', LINE_SN, oprnos, 'shift', { fixedTwoShift: 'true' });
  959. }
  960. function fetchCapacityInto(action, lineSn, oprnos, field, extraData) {
  961. var data = { lineSn: lineSn, oprnos: oprnos.join(',') };
  962. if (extraData) {
  963. for (var k in extraData) {
  964. if (extraData.hasOwnProperty(k)) data[k] = extraData[k];
  965. }
  966. }
  967. $.ajax({
  968. url: '${ctx}/mes/mesProductRecord/' + action,
  969. type: 'POST',
  970. dataType: 'json',
  971. data: data,
  972. success: function(resp) {
  973. if (!resp || resp.result !== 'true' || !resp.data) return;
  974. var map = resp.data || {};
  975. var list = mockData.opTable || [];
  976. for (var i = 0; i < list.length; i++) {
  977. var code = list[i].code || '';
  978. var formatted = code.length === 6 ? code.substring(0, 5) : code;
  979. list[i][field] = Number(map[code] != null ? map[code] : map[formatted]) || 0;
  980. }
  981. renderTable();
  982. }
  983. });
  984. }
  985. // 近七日质检:产出=OP160 OK,不良=返工 OP120-OP160
  986. function updateWeekQualityByApi() {
  987. $.ajax({
  988. url: '${ctx}/mes/mesProductRecord/weekQualityData',
  989. type: 'POST',
  990. dataType: 'json',
  991. data: {
  992. lineSn: LINE_SN,
  993. producedOprno: 'OP160',
  994. defectOprnoFrom: 'OP120',
  995. defectOprnoTo: 'OP160'
  996. },
  997. success: function(resp) {
  998. if (!resp || resp.result !== 'true' || !resp.data) return;
  999. var d = resp.data;
  1000. var produced = d.produced || [];
  1001. var defect = d.defect || [];
  1002. var maxV = 10;
  1003. for (var i = 0; i < produced.length; i++) {
  1004. if (Number(produced[i]) > maxV) maxV = Number(produced[i]);
  1005. }
  1006. for (var j = 0; j < defect.length; j++) {
  1007. if (Number(defect[j]) > maxV) maxV = Number(defect[j]);
  1008. }
  1009. mockData.qualityInspect = {
  1010. labels: d.labels || [],
  1011. produced: produced,
  1012. defect: defect,
  1013. max: Math.ceil(maxV * 1.15) || 10
  1014. };
  1015. drawAllCharts();
  1016. }
  1017. });
  1018. }
  1019. function updateMonthlyProductByApi() {}
  1020. function updateLatestPlanProductionByApi() {}
  1021. function renderCards() {}
  1022. function extractUserList(resp) {
  1023. if (!resp) return [];
  1024. if (Object.prototype.toString.call(resp.list) === '[object Array]') return resp.list;
  1025. if (Object.prototype.toString.call(resp.rows) === '[object Array]') return resp.rows;
  1026. if (resp.data) {
  1027. if (Object.prototype.toString.call(resp.data.list) === '[object Array]') return resp.data.list;
  1028. if (Object.prototype.toString.call(resp.data.rows) === '[object Array]') return resp.data.rows;
  1029. }
  1030. return [];
  1031. }
  1032. function updateDeviceAlarmAlertsByApi() {
  1033. $.ajax({
  1034. url: '${ctx}/mes/mesProductScreen/alertList',
  1035. type: 'POST',
  1036. dataType: 'json',
  1037. data: {
  1038. pageNo: 1,
  1039. pageSize: 50,
  1040. state: '0' // 只显示待处理的报警
  1041. },
  1042. success: function(resp) {
  1043. var rows = extractUserList(resp);
  1044. var alerts = [];
  1045. for (var i = 0; i < rows.length; i++) {
  1046. var r = rows[i] || {};
  1047. if (String(r.state) !== '0') continue; // 双保险:跳过非待处理
  1048. var typ = (r.type != null && r.type !== undefined) ? String(r.type) : '';
  1049. alerts.push({ no: alerts.length + 1, text: typ });
  1050. }
  1051. mockData.alerts = alerts;
  1052. renderAlerts();
  1053. },
  1054. error: function() {
  1055. mockData.alerts = [];
  1056. renderAlerts();
  1057. }
  1058. });
  1059. }
  1060. /** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
  1061. function updateScreenConfigByApi() {
  1062. $.ajax({
  1063. url: '${ctx}/mes/mesProductScreen/latestConfig',
  1064. type: 'POST',
  1065. dataType: 'json',
  1066. success: function(resp) {
  1067. if (!resp || resp.result !== 'true' || !resp.data) return;
  1068. var d = resp.data;
  1069. mockData.cards = mockData.cards || {};
  1070. mockData.cards.productionPeople = Number(d.peopleNums) || 0;
  1071. mockData.cards.deviceCount = Number(d.deviceNums) || 0;
  1072. mockData.cards.planProduction = Number(d.planNums) || 0;
  1073. var elPeople = document.getElementById('productionPeople');
  1074. var elDevice = document.getElementById('deviceCount');
  1075. var elPlan = document.getElementById('planProduction');
  1076. if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
  1077. if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
  1078. if (elPlan) elPlan.textContent = mockData.cards.planProduction;
  1079. }
  1080. });
  1081. }
  1082. function isOpr180Family(oprno) {
  1083. var op = (oprno || '').replace(/\s/g, '').toUpperCase();
  1084. if (op.length < 5 || op.indexOf('OP180') !== 0) {
  1085. return false;
  1086. }
  1087. if (op.length === 5) {
  1088. return op === 'OP180';
  1089. }
  1090. return /^OP180[A-Z0-9_-]+$/.test(op);
  1091. }
  1092. function isRecordContentOk(content) {
  1093. var c = (content || '').replace(/\s/g, '').toUpperCase();
  1094. return c === 'OK';
  1095. }
  1096. function applyActualProductionCount(n) {
  1097. mockData.cards = mockData.cards || {};
  1098. mockData.cards.actualProduction = Number(n) || 0;
  1099. var el = document.getElementById('actualProduction');
  1100. if (el) el.textContent = mockData.cards.actualProduction;
  1101. }
  1102. /** 实际产量:取本阶段末工位当天 OK 数(机加 OP160) */
  1103. function updateActualProductionByApi() {
  1104. var oprno = HOURLY_API_CFG.offlineOprno || 'OP160';
  1105. $.ajax({
  1106. url: '${ctx}/mes/mesProductRecord/dayCapacityData',
  1107. type: 'POST',
  1108. dataType: 'json',
  1109. data: {
  1110. lineSn: LINE_SN,
  1111. oprnos: oprno
  1112. },
  1113. success: function(resp) {
  1114. if (!resp || resp.result !== 'true' || !resp.data) {
  1115. applyActualProductionCount(0);
  1116. return;
  1117. }
  1118. var map = resp.data || {};
  1119. var formatted = oprno.length === 6 ? oprno.substring(0, 5) : oprno;
  1120. applyActualProductionCount(Number(map[oprno] != null ? map[oprno] : map[formatted]) || 0);
  1121. },
  1122. error: function() {
  1123. applyActualProductionCount(0);
  1124. }
  1125. });
  1126. }
  1127. function drawAllCharts() {
  1128. var q = mockData.qualityInspect;
  1129. if (q) drawDoubleBar('chartQuality', q.produced, q.defect, q.labels, q.max);
  1130. var hr = mockData.hourly;
  1131. if (hr) drawAreaLine('chartHourly', hr.up, hr.down, hr.labels, hr.max);
  1132. }
  1133. function refreshData() {
  1134. generateMockData();
  1135. opTablePageIndex = 0;
  1136. renderAlerts();
  1137. renderTable();
  1138. startOpTablePageRotate();
  1139. updateScreenConfigByApi();
  1140. updateActualProductionByApi();
  1141. updateLineProcessTableByApi();
  1142. updateDeviceAlarmAlertsByApi();
  1143. updateHourlyUpDownByApi();
  1144. updateWeekQualityByApi();
  1145. drawAllCharts();
  1146. }
  1147. function changeZoom() {
  1148. var wb = $(window).width() / 1920;
  1149. var hb = $(window).height() / 1080;
  1150. var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement);
  1151. var $app = $('#app');
  1152. if (isFs) {
  1153. $('html, body').css({ 'zoom': 1, 'width': '100%', 'height': '100%', 'overflow': 'hidden', 'margin': 0, 'background': '#00091A' });
  1154. $app.css({
  1155. 'transform': 'scale(' + wb + ', ' + hb + ')',
  1156. 'transform-origin': 'left top',
  1157. 'margin': 0,
  1158. 'position': 'absolute',
  1159. 'left': 0,
  1160. 'top': 0
  1161. });
  1162. } else {
  1163. $('html').css({ 'width': '', 'height': '', 'overflow': '', 'margin': '', 'background': '', 'zoom': '' });
  1164. $app.css({ 'transform': 'none', 'transform-origin': '', 'margin': '0 auto', 'position': 'relative', 'left': '', 'top': '' });
  1165. $('body').css({ 'zoom': wb >= hb ? hb : wb, 'width': '', 'height': '', 'overflow': 'hidden', 'margin': '', 'background': '' });
  1166. }
  1167. }
  1168. $(window).resize(function() {
  1169. changeZoom();
  1170. drawAllCharts();
  1171. });
  1172. function initFullscreenBtn() {
  1173. var btn = document.getElementById('btnFullscreen');
  1174. if (!btn) return;
  1175. btn.addEventListener('click', function () {
  1176. var el = document.documentElement;
  1177. if (el.requestFullscreen) {
  1178. el.requestFullscreen();
  1179. } else if (el.webkitRequestFullscreen) {
  1180. el.webkitRequestFullscreen();
  1181. }
  1182. });
  1183. function syncFullscreenBtn() {
  1184. var fs = document.fullscreenElement || document.webkitFullscreenElement;
  1185. btn.style.display = fs ? 'none' : '';
  1186. changeZoom();
  1187. drawAllCharts();
  1188. }
  1189. document.addEventListener('fullscreenchange', syncFullscreenBtn);
  1190. document.addEventListener('webkitfullscreenchange', syncFullscreenBtn);
  1191. }
  1192. $(function() {
  1193. changeZoom();
  1194. updateClock();
  1195. setInterval(updateClock, 1000);
  1196. refreshData();
  1197. initFullscreenBtn();
  1198. // 定期刷新数据 (2 分钟)
  1199. setInterval(refreshData, 1000 * 60 * 2);
  1200. // 每秒重绘 (应对 zoom / resize)
  1201. setInterval(drawAllCharts, 1000);
  1202. });
  1203. })();
  1204. </script>
  1205. </body>
  1206. </html>