mesScreen5.html 39 KB

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