mesScreen4.html 39 KB

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