mesScreen3.html 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980
  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>MES综合大屏</title>
  8. <link rel="stylesheet" href="${ctxStatic}/bootstrap/css/bootstrap.min.css">
  9. <link rel="stylesheet" href="${ctxStatic}/screen/style.css">
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. overflow: hidden;
  18. }
  19. .mes3-wrapper {
  20. position: relative;
  21. width: 1920px;
  22. height: 1080px;
  23. background-color: #00091A;
  24. background-image: url("${ctxStatic}/screen/imgs/ljrSreen.png");
  25. background-size: 100% 100%;
  26. background-position: center;
  27. overflow: hidden;
  28. margin: 0 auto;
  29. }
  30. /* ===== 顶部标题栏 ===== */
  31. .mes3-header {
  32. position: absolute;
  33. top: -13px;
  34. left: 0;
  35. width: 100%;
  36. height: 87px;
  37. z-index: 10;
  38. display: flex;
  39. align-items: center;
  40. justify-content: space-between;
  41. padding: 0 20px;
  42. }
  43. .mes3-logo {
  44. height: 50px;
  45. width: auto;
  46. vertical-align: middle;
  47. }
  48. .mes3-header-title {
  49. position: absolute;
  50. left: 50%;
  51. top: -13px;
  52. transform: translateX(-50%);
  53. width: 1833px;
  54. height: 162px;
  55. display: flex;
  56. align-items: center;
  57. justify-content: center;
  58. font-size: 32px;
  59. font-weight: bold;
  60. color: #FFFFFF;
  61. font-family: "Microsoft YaHei", sans-serif;
  62. letter-spacing: 2px;
  63. text-shadow: 0 0 10px rgba(77, 201, 245, 0.5);
  64. background-image: url("${ctxStatic}/screen/imgs/ljrBt.png");
  65. background-size: contain;
  66. background-position: center;
  67. background-repeat: no-repeat;
  68. }
  69. .mes3-bt {
  70. position: absolute;
  71. left: 50%;
  72. top: 50%;
  73. transform: translate(-50%, -50%);
  74. height: 45px;
  75. width: auto;
  76. vertical-align: middle;
  77. }
  78. .mes3-header-right {
  79. text-align: right;
  80. font-family: "Microsoft YaHei", sans-serif;
  81. margin-right: 88px;
  82. }
  83. .mes3-header-right .mes3-date {
  84. font-size: 16px;
  85. line-height: 24px;
  86. color: #4DC9F5;
  87. }
  88. .mes3-header-right .mes3-time {
  89. font-size: 24px;
  90. line-height: 32px;
  91. font-weight: bold;
  92. color: #FFFFFF;
  93. }
  94. .mes-fullscreen-btn {
  95. position: absolute;
  96. top: 22px;
  97. right: 18px;
  98. z-index: 30;
  99. padding: 6px 14px;
  100. font-size: 14px;
  101. line-height: 1.2;
  102. color: #4DC9F5;
  103. background: rgba(0, 20, 50, 0.65);
  104. border: 1px solid #4DC9F5;
  105. border-radius: 4px;
  106. cursor: pointer;
  107. font-family: "Microsoft YaHei", sans-serif;
  108. }
  109. .mes-fullscreen-btn:hover {
  110. color: #FFFFFF;
  111. background: rgba(77, 201, 245, 0.28);
  112. }
  113. html:fullscreen .mes-fullscreen-btn,
  114. html:-webkit-full-screen .mes-fullscreen-btn {
  115. display: none !important;
  116. }
  117. /* ===== 中间主体区域 ===== */
  118. .mes3-body {
  119. position: absolute;
  120. top: 87px;
  121. left: 0;
  122. right: 0;
  123. bottom: 0;
  124. }
  125. /* ===== 左侧三列面板(容器铺满主体,便于按名称重排到与 screen3/4/5 对齐) ===== */
  126. .mes3-left {
  127. position: absolute;
  128. left: 0;
  129. top: 0;
  130. right: 0;
  131. bottom: 0;
  132. width: auto;
  133. pointer-events: none;
  134. }
  135. .mes3-left .mes3-panel {
  136. pointer-events: auto;
  137. }
  138. /* 通用面板样式 */
  139. .mes3-panel {
  140. position: absolute;
  141. left: 0;
  142. right: auto;
  143. background-image: url("${ctxStatic}/screen/imgs/ljrZ1.png");
  144. background-size: 100% 100%;
  145. background-repeat: no-repeat;
  146. }
  147. .mes3-panel .panel-header {
  148. height: 45px;
  149. line-height: 45px;
  150. text-align: center;
  151. font-size: 18px;
  152. font-weight: bold;
  153. color: #4DC9F5;
  154. font-family: "Microsoft YaHei", sans-serif;
  155. }
  156. .mes3-panel .panel-body {
  157. position: absolute;
  158. top: 45px;
  159. left: 0;
  160. right: 0;
  161. bottom: 0;
  162. padding: 10px;
  163. overflow: hidden;
  164. }
  165. /* 近七日产量折线图 -> 右上(对齐前屏「总览」高度区) */
  166. .mes3-panel-left1 {
  167. left: 1450px;
  168. top: 58px;
  169. width: 440px;
  170. height: 290px;
  171. background-image: url("${ctxStatic}/screen/imgs/ljrY1.png");
  172. }
  173. .mes3-panel-left1 .panel-body {
  174. display: block;
  175. padding: 8px 10px;
  176. }
  177. .mes3-panel-left1 .panel-body img {
  178. width: 70px;
  179. height: auto;
  180. }
  181. /* 近七日产品抽检/质检 -> 左下(对齐前屏质检区) */
  182. .mes3-panel-left2 {
  183. left: 30px;
  184. top: 508px;
  185. width: 440px;
  186. height: 460px;
  187. }
  188. .mes3-panel-left2 .panel-body {
  189. display: block;
  190. padding: 8px 10px;
  191. }
  192. .mes3-panel-left2 .panel-body img {
  193. width: 70px;
  194. height: auto;
  195. }
  196. /* 不良类型柱状图:隐藏(左边第三个) */
  197. .mes3-panel-left3 {
  198. display: none;
  199. }
  200. .mes3-panel-left3 .panel-body {
  201. padding: 5px;
  202. }
  203. /* ===== 中间区域(中下表隐藏后加高) ===== */
  204. .mes3-center {
  205. position: absolute;
  206. left: 490px;
  207. top: 58px;
  208. width: 940px;
  209. bottom: 15px;
  210. height: auto;
  211. }
  212. /* 中间Logo区域 */
  213. .mes3-center-logo {
  214. position: absolute;
  215. top: 50%;
  216. left: 50%;
  217. transform: translate(-50%, -50%);
  218. width: 600px;
  219. height: 400px;
  220. display: flex;
  221. align-items: center;
  222. justify-content: center;
  223. z-index: 1;
  224. }
  225. .mes3-center-logo-img {
  226. max-width: 100%;
  227. max-height: 100%;
  228. object-fit: contain;
  229. }
  230. /* 中间数据卡片区域 - 环绕Logo布局 */
  231. .mes3-center-cards {
  232. position: relative;
  233. width: 100%;
  234. height: 100%;
  235. z-index: 2;
  236. }
  237. .mes3-data-card {
  238. position: absolute;
  239. width: 120px;
  240. height: 90px;
  241. background-image: url("${ctxStatic}/screen/imgs/ljrZHOxiao.png");
  242. background-size: 100% 100%;
  243. background-repeat: no-repeat;
  244. display: flex;
  245. flex-direction: column;
  246. align-items: center;
  247. justify-content: center;
  248. padding: 8px;
  249. }
  250. .mes3-data-card .card-label {
  251. font-size: 14px;
  252. color: #4DC9F5;
  253. font-family: "Microsoft YaHei", sans-serif;
  254. margin-bottom: 5px;
  255. }
  256. .mes3-data-card .card-value {
  257. font-size: 28px;
  258. font-weight: bold;
  259. color: #00F7FF;
  260. font-family: "Microsoft YaHei", sans-serif;
  261. }
  262. /* 数据卡片位置 - 左侧 */
  263. .mes3-data-card:nth-child(1) { left: 50px; top: 180px; } /* 生产人数 */
  264. .mes3-data-card:nth-child(2) { left: 180px; top: 80px; } /* 设备数量 */
  265. /* 数据卡片位置 - 顶部 */
  266. .mes3-data-card:nth-child(3) { left: 320px; top: 20px; } /* 计划产量 */
  267. .mes3-data-card:nth-child(4) { left: 500px; top: 20px; } /* 实际产量 */
  268. /* 数据卡片位置 - 右侧 */
  269. .mes3-data-card:nth-child(5) { left: 640px; top: 80px; } /* 近修数量 */
  270. .mes3-data-card:nth-child(6) { left: 770px; top: 180px; } /* 报废数量 */
  271. /* 移除中间流程图 */
  272. .mes3-center-flow {
  273. display: none;
  274. }
  275. /* ===== 中间下方大面板 - 设备故障统计分析:隐藏(中间第二个) ===== */
  276. .mes3-panel-center-bottom {
  277. display: none;
  278. }
  279. .mes3-panel-center-bottom .panel-header {
  280. height: 45px;
  281. line-height: 45px;
  282. text-align: center;
  283. font-size: 18px;
  284. font-weight: bold;
  285. color: #4DC9F5;
  286. font-family: "Microsoft YaHei", sans-serif;
  287. }
  288. .mes3-panel-center-bottom .panel-body {
  289. position: absolute;
  290. top: 45px;
  291. left: 0;
  292. right: 0;
  293. bottom: 0;
  294. padding: 10px;
  295. }
  296. /* ===== 右侧面板区域 ===== */
  297. /* 达成率数据分析:隐藏(右边第一个) */
  298. .mes3-panel-right1 {
  299. display: none;
  300. }
  301. .mes3-panel-right1 .panel-header {
  302. height: 45px;
  303. line-height: 45px;
  304. text-align: center;
  305. font-size: 18px;
  306. font-weight: bold;
  307. color: #4DC9F5;
  308. font-family: "Microsoft YaHei", sans-serif;
  309. }
  310. .mes3-panel-right1 .panel-body {
  311. position: absolute;
  312. top: 45px;
  313. left: 0;
  314. right: 0;
  315. bottom: 0;
  316. padding: 10px;
  317. }
  318. /* 产量统计内容 */
  319. .mes3-stat-grid {
  320. display: flex;
  321. flex-direction: column;
  322. height: 100%;
  323. gap: 10px;
  324. }
  325. .mes3-stat-row {
  326. flex: 1;
  327. display: flex;
  328. gap: 10px;
  329. }
  330. .mes3-stat-cell {
  331. flex: 1;
  332. display: flex;
  333. flex-direction: column;
  334. align-items: center;
  335. justify-content: center;
  336. }
  337. .mes3-stat-label {
  338. font-size: 16px;
  339. font-weight: bold;
  340. color: #fff;
  341. font-family: "Microsoft YaHei", sans-serif;
  342. margin-bottom: 8px;
  343. }
  344. .mes3-stat-value {
  345. font-size: 36px;
  346. font-weight: bold;
  347. color: #00F7FF;
  348. font-family: "Microsoft YaHei", sans-serif;
  349. }
  350. .mes3-stat-value.green { color: #00FF88; }
  351. .mes3-stat-value.yellow { color: #FFDD00; }
  352. .mes3-stat-unit {
  353. font-size: 16px;
  354. color: #fff;
  355. font-family: "Microsoft YaHei", sans-serif;
  356. margin-left: 5px;
  357. }
  358. /* 每小时上下线 -> 右下(对齐前屏同名表) */
  359. .mes3-panel-right2 {
  360. position: absolute;
  361. left: 1450px;
  362. top: 508px;
  363. width: 440px;
  364. height: 460px;
  365. background-image: url("${ctxStatic}/screen/imgs/ljrY2.png");
  366. background-size: 100% 100%;
  367. background-repeat: no-repeat;
  368. }
  369. .mes3-panel-right2 .panel-header {
  370. height: 45px;
  371. line-height: 45px;
  372. text-align: center;
  373. font-size: 18px;
  374. font-weight: bold;
  375. color: #4DC9F5;
  376. font-family: "Microsoft YaHei", sans-serif;
  377. }
  378. .mes3-panel-right2 .panel-body {
  379. position: absolute;
  380. top: 45px;
  381. left: 0;
  382. right: 0;
  383. bottom: 0;
  384. padding: 10px;
  385. }
  386. /* 设备报警 -> 左上(对齐前屏报警信息区) */
  387. .mes3-panel-right3 {
  388. position: absolute;
  389. left: 30px;
  390. top: 58px;
  391. width: 440px;
  392. height: 430px;
  393. background-image: url("${ctxStatic}/screen/imgs/ljrZ1.png");
  394. background-size: 100% 100%;
  395. background-repeat: no-repeat;
  396. }
  397. .mes3-panel-right3 .panel-header {
  398. height: 45px;
  399. line-height: 45px;
  400. text-align: center;
  401. font-size: 18px;
  402. font-weight: bold;
  403. color: #4DC9F5;
  404. font-family: "Microsoft YaHei", sans-serif;
  405. }
  406. .mes3-panel-right3 .panel-body {
  407. position: absolute;
  408. top: 45px;
  409. left: 0;
  410. right: 0;
  411. bottom: 0;
  412. padding: 10px;
  413. }
  414. /* 图表 */
  415. .mes3-chart {
  416. width: 100%;
  417. height: 100%;
  418. }
  419. .mes3-chart-grid-line {
  420. position: absolute;
  421. width: 100%;
  422. height: 1px;
  423. background-color: rgba(0, 143, 253, 0.15);
  424. }
  425. /* ===== 达成率数据分析 ===== */
  426. .mes3-rate-cards {
  427. display: flex;
  428. gap: 12px;
  429. padding: 5px 0 10px;
  430. }
  431. .mes3-rate-card {
  432. flex: 1;
  433. height: 70px;
  434. border: 1px solid rgba(0, 200, 255, 0.4);
  435. background: linear-gradient(to right, rgba(0, 100, 180, 0.3), rgba(0, 50, 120, 0.2));
  436. display: flex;
  437. align-items: center;
  438. padding: 0 10px;
  439. gap: 8px;
  440. }
  441. .mes3-rate-card .rate-icon {
  442. flex-shrink: 0;
  443. }
  444. .mes3-rate-card .rate-label {
  445. font-size: 13px;
  446. color: #ffffff;
  447. font-family: "Microsoft YaHei", sans-serif;
  448. margin-bottom: 2px;
  449. }
  450. .mes3-rate-card .rate-value {
  451. font-size: 26px;
  452. font-weight: bold;
  453. color: #00F7FF;
  454. font-family: "Microsoft YaHei", sans-serif;
  455. line-height: 1;
  456. }
  457. .mes3-rate-card .rate-unit {
  458. font-size: 14px;
  459. color: #00F7FF;
  460. margin-left: 2px;
  461. }
  462. .mes3-rate-chart-title {
  463. display: flex;
  464. justify-content: space-between;
  465. align-items: center;
  466. color: #4DC9F5;
  467. font-size: 13px;
  468. font-family: "Microsoft YaHei", sans-serif;
  469. padding: 6px 0 4px;
  470. }
  471. .mes3-rate-chart-title .legend {
  472. color: #ffffff;
  473. font-size: 12px;
  474. }
  475. .mes3-rate-chart {
  476. position: relative;
  477. height: 180px;
  478. }
  479. .legend-dot {
  480. display: inline-block;
  481. width: 10px;
  482. height: 10px;
  483. border-radius: 50%;
  484. margin-right: 4px;
  485. vertical-align: middle;
  486. }
  487. /* ===== 设备故障统计分析 ===== */
  488. .mes3-fault-rank {
  489. width: 42%;
  490. padding-right: 15px;
  491. }
  492. .mes3-fault-rank .fault-rank-title {
  493. color: #fff;
  494. font-size: 14px;
  495. font-weight: bold;
  496. padding-bottom: 10px;
  497. font-family: "Microsoft YaHei", sans-serif;
  498. }
  499. .mes3-fault-item {
  500. display: flex;
  501. align-items: center;
  502. height: 32px;
  503. margin-bottom: 4px;
  504. font-family: "Microsoft YaHei", sans-serif;
  505. font-size: 13px;
  506. }
  507. .mes3-fault-item .fault-rank-no {
  508. width: 50px;
  509. color: #4DC9F5;
  510. }
  511. .mes3-fault-item.top1 .fault-rank-no { color: #FF4444; }
  512. .mes3-fault-item.top2 .fault-rank-no { color: #FF9900; }
  513. .mes3-fault-item .fault-name {
  514. width: 100px;
  515. color: #ffffff;
  516. }
  517. .mes3-fault-item .fault-bar-wrap {
  518. flex: 1;
  519. height: 8px;
  520. background: rgba(0, 100, 180, 0.3);
  521. margin: 0 10px;
  522. position: relative;
  523. }
  524. .mes3-fault-item .fault-bar {
  525. height: 100%;
  526. background: linear-gradient(to right, #00C8FF, #00FF88);
  527. }
  528. .mes3-fault-item .fault-count {
  529. width: 30px;
  530. text-align: right;
  531. color: #00F7FF;
  532. font-weight: bold;
  533. }
  534. .mes3-fault-chart {
  535. flex: 1;
  536. position: relative;
  537. }
  538. .mes3-fault-chart .fault-chart-title {
  539. display: flex;
  540. justify-content: space-between;
  541. align-items: center;
  542. color: #fff;
  543. font-size: 14px;
  544. font-weight: bold;
  545. padding-bottom: 6px;
  546. font-family: "Microsoft YaHei", sans-serif;
  547. }
  548. .mes3-fault-chart .legend {
  549. color: #ffffff;
  550. font-size: 12px;
  551. font-weight: normal;
  552. }
  553. /* ===== 报警信息 ===== */
  554. .mes3-alert-list {
  555. padding-top: 5px;
  556. padding-right: 4px;
  557. height: 100%;
  558. overflow-y: auto;
  559. scrollbar-width: thin;
  560. scrollbar-color: rgba(0, 200, 255, 0.45) rgba(0, 40, 80, 0.25);
  561. }
  562. .mes3-alert-list::-webkit-scrollbar {
  563. width: 6px;
  564. }
  565. .mes3-alert-list::-webkit-scrollbar-track {
  566. background: rgba(0, 40, 80, 0.25);
  567. border-radius: 3px;
  568. }
  569. .mes3-alert-list::-webkit-scrollbar-thumb {
  570. background: linear-gradient(180deg, rgba(0, 200, 255, 0.55), rgba(77, 201, 245, 0.35));
  571. border-radius: 3px;
  572. }
  573. .mes3-alert-list::-webkit-scrollbar-thumb:hover {
  574. background: rgba(0, 230, 255, 0.7);
  575. }
  576. .mes3-alert-list::-webkit-scrollbar-button {
  577. display: none;
  578. width: 0;
  579. height: 0;
  580. }
  581. .mes3-alert-list::-webkit-scrollbar-corner {
  582. background: transparent;
  583. }
  584. .mes3-alert-item {
  585. display: flex;
  586. align-items: center;
  587. height: 32px;
  588. padding: 0 8px;
  589. margin-bottom: 4px;
  590. font-family: "Microsoft YaHei", sans-serif;
  591. font-size: 14px;
  592. border-bottom: 1px dashed rgba(77, 201, 245, 0.2);
  593. }
  594. .mes3-alert-item .alert-no {
  595. width: 30px;
  596. color: #4DC9F5;
  597. font-weight: bold;
  598. }
  599. .mes3-alert-item .alert-text {
  600. flex: 1;
  601. color: #ffffff;
  602. }
  603. /* ===== 告警 ===== */
  604. .mes3-alert {
  605. position: absolute;
  606. top: 80px;
  607. left: 15px;
  608. right: 15px;
  609. height: 0;
  610. overflow: hidden;
  611. background-color: rgba(255, 0, 0, 0.08);
  612. border: 2px solid #FF0000;
  613. border-left-width: 6px;
  614. line-height: 48px;
  615. text-indent: 20px;
  616. font-size: 20px;
  617. color: #FF4444;
  618. font-weight: bold;
  619. font-family: "Microsoft YaHei", sans-serif;
  620. z-index: 100;
  621. transition: height 0.3s, opacity 0.3s;
  622. opacity: 0;
  623. }
  624. .mes3-alert.show {
  625. height: 48px;
  626. opacity: 1;
  627. }
  628. </style>
  629. </head>
  630. <body>
  631. <div class="mes3-wrapper" id="app">
  632. <!-- 顶部标题栏 -->
  633. <div class="mes3-header">
  634. <img class="mes3-logo" src="${ctxStatic}/screen/imgs/ljrLogo.png" alt="logo">
  635. <div class="mes3-header-title">MES系统综合数据大屏</div>
  636. <div class="mes3-header-right">
  637. <div class="mes3-date" id="mes3Date"></div>
  638. <div class="mes3-time" id="mes3Time"></div>
  639. </div>
  640. <button type="button" class="mes-fullscreen-btn" id="btnFullscreen" title="全屏">全屏</button>
  641. </div>
  642. <!-- 中间主体 -->
  643. <div class="mes3-body">
  644. <!-- 左侧三列面板 -->
  645. <div class="mes3-left">
  646. <!-- 面板1: 近七日产量折线图 -->
  647. <div class="mes3-panel mes3-panel-left1">
  648. <div class="panel-header">近七日产量分析</div>
  649. <div class="panel-body">
  650. <canvas id="chartLeft1" class="mes3-chart"></canvas>
  651. </div>
  652. </div>
  653. <!-- 面板2: 近七日产品质检(对齐前屏同名逻辑) -->
  654. <div class="mes3-panel mes3-panel-left2">
  655. <div class="panel-header">近七日产品质检数据分析</div>
  656. <div class="panel-body">
  657. <canvas id="chartLeft2" class="mes3-chart"></canvas>
  658. </div>
  659. </div>
  660. <!-- 面板3: 不良类型柱状图 -->
  661. <div class="mes3-panel mes3-panel-left3">
  662. <div class="panel-header">不良类型柱状图</div>
  663. <div class="panel-body">
  664. <canvas id="chartLeft3" class="mes3-chart"></canvas>
  665. </div>
  666. </div>
  667. </div>
  668. <!-- 中间区域 -->
  669. <div class="mes3-center">
  670. <!-- 中间数据卡片 - 环绕Logo -->
  671. <div class="mes3-center-cards">
  672. <div class="mes3-data-card">
  673. <div class="card-label">生产人数</div>
  674. <div class="card-value" id="productionPeople">48</div>
  675. </div>
  676. <div class="mes3-data-card">
  677. <div class="card-label">设备数量</div>
  678. <div class="card-value" id="deviceCount">56</div>
  679. </div>
  680. <div class="mes3-data-card">
  681. <div class="card-label">计划产量</div>
  682. <div class="card-value" id="planProduction">221</div>
  683. </div>
  684. <div class="mes3-data-card">
  685. <div class="card-label">实际产量</div>
  686. <div class="card-value" id="actualProduction">225</div>
  687. </div>
  688. <div class="mes3-data-card">
  689. <div class="card-label">返修数量</div>
  690. <div class="card-value" id="repairCount">11</div>
  691. </div>
  692. <div class="mes3-data-card">
  693. <div class="card-label">报废数量</div>
  694. <div class="card-value" id="scrapCount">10</div>
  695. </div>
  696. </div>
  697. <!-- 中间Logo -->
  698. <div class="mes3-center-logo">
  699. <img class="mes3-center-logo-img" src="${ctxStatic}/screen/imgs/ljrZho.png" alt="LJR">
  700. </div>
  701. </div>
  702. <!-- 右侧面板1: 达成率数据分析(右上角独立小面板)-->
  703. <div class="mes3-panel-right1">
  704. <div class="panel-header">达成率数据分析</div>
  705. <div class="panel-body">
  706. <!-- 上方两个达成率指标卡 -->
  707. <div class="mes3-rate-cards">
  708. <div class="mes3-rate-card">
  709. <div class="rate-icon">
  710. <svg width="40" height="40" viewBox="0 0 40 40"><circle cx="20" cy="20" r="18" fill="none" stroke="#00C8FF" stroke-width="2"/><path d="M 13 20 L 18 25 L 27 14" stroke="#00C8FF" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>
  711. </div>
  712. <div class="rate-text">
  713. <div class="rate-label">今日达成率</div>
  714. <div class="rate-value"><span id="todayRate">80</span><span class="rate-unit">%</span></div>
  715. </div>
  716. </div>
  717. <div class="mes3-rate-card">
  718. <div class="rate-icon">
  719. <svg width="40" height="40" viewBox="0 0 40 40"><rect x="8" y="10" width="24" height="22" fill="none" stroke="#00C8FF" stroke-width="2" rx="2"/><line x1="14" y1="6" x2="14" y2="14" stroke="#00C8FF" stroke-width="2"/><line x1="26" y1="6" x2="26" y2="14" stroke="#00C8FF" stroke-width="2"/><line x1="8" y1="18" x2="32" y2="18" stroke="#00C8FF" stroke-width="1.5"/></svg>
  720. </div>
  721. <div class="rate-text">
  722. <div class="rate-label">当月达成率</div>
  723. <div class="rate-value"><span id="monthRate">70</span><span class="rate-unit">%</span></div>
  724. </div>
  725. </div>
  726. </div>
  727. <!-- 下方折线图 -->
  728. <div class="mes3-rate-chart-title">
  729. <span>近七日达成率数据分析</span>
  730. <span class="legend"><i class="legend-dot" style="background:#FFDD00"></i>目标 <i class="legend-dot" style="background:#00C8FF;margin-left:10px"></i>实际</span>
  731. </div>
  732. <div class="mes3-rate-chart">
  733. <canvas id="chartRight1" class="mes3-chart"></canvas>
  734. </div>
  735. </div>
  736. </div>
  737. <!-- 中间下方大面板: 设备故障统计分析 -->
  738. <div class="mes3-panel-center-bottom">
  739. <div class="panel-header">设备故障统计分析</div>
  740. <div class="panel-body" style="display:flex;padding: 15px;">
  741. <!-- 左侧故障类型排名 -->
  742. <div class="mes3-fault-rank">
  743. <div class="fault-rank-title">故障类型排名</div>
  744. <div id="faultRankList"></div>
  745. </div>
  746. <!-- 右侧近七日设备故障分析图 -->
  747. <div class="mes3-fault-chart">
  748. <div class="fault-chart-title">
  749. <span>近七日设备故障分析</span>
  750. <span class="legend"><i class="legend-dot" style="background:#00C8FF"></i>故障时间 <i class="legend-dot" style="background:#00FF88;margin-left:10px"></i>故障率</span>
  751. </div>
  752. <div style="position:absolute;top:40px;left:10px;right:10px;bottom:10px;">
  753. <canvas id="chartCenterBottom" class="mes3-chart"></canvas>
  754. </div>
  755. </div>
  756. </div>
  757. </div>
  758. <!-- 右侧面板2: 每小时上下线数据分析 -->
  759. <div class="mes3-panel-right2">
  760. <div class="panel-header">每小时上下线数据分析</div>
  761. <div class="panel-body">
  762. <canvas id="chartRight2" class="mes3-chart"></canvas>
  763. </div>
  764. </div>
  765. <!-- 右侧面板3: 报警信息 -->
  766. <div class="mes3-panel-right3">
  767. <div class="panel-header">产线阶段报警信息</div>
  768. <div class="panel-body" style="padding: 0 15px;">
  769. <div id="alertList" class="mes3-alert-list"></div>
  770. </div>
  771. </div>
  772. </div>
  773. <!-- 告警 -->
  774. <div class="mes3-alert" id="mes3Alert"></div>
  775. </div>
  776. <script src="${ctxStatic}/jquery-1.11.3.min.js"></script>
  777. <script>
  778. (function() {
  779. var __screenFail = 0, __screenReloadAt = 0;
  780. $(document).ajaxError(function(e, xhr) {
  781. if (!xhr) return;
  782. var text = xhr.responseText || '';
  783. if (xhr.status === 0 || xhr.status === 401 || xhr.status === 403 || xhr.status === 502 || xhr.status === 503
  784. || text.indexOf('login') >= 0 || text.indexOf('登录') >= 0) {
  785. __screenFail++;
  786. if (__screenFail >= 3 && Date.now() - __screenReloadAt > 60000) {
  787. __screenReloadAt = Date.now();
  788. location.reload();
  789. }
  790. }
  791. });
  792. $(document).ajaxSuccess(function() { __screenFail = 0; });
  793. function pad(n) { return n < 10 ? '0' + n : '' + n; }
  794. function updateClock() {
  795. var now = new Date();
  796. var y = now.getFullYear();
  797. var m = now.getMonth() + 1;
  798. var d = now.getDate();
  799. var w = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'][now.getDay()];
  800. document.getElementById('mes3Date').textContent = y + '年' + pad(m) + '月' + pad(d) + '日 ' + w;
  801. var h = now.getHours(), mi = now.getMinutes(), s = now.getSeconds();
  802. document.getElementById('mes3Time').textContent = pad(h) + ':' + pad(mi) + ':' + pad(s);
  803. }
  804. // ====== 各窗口测试数据 ======
  805. var mockData = {};
  806. /**
  807. * 总览页:全产线汇总,上线 OP060 / 下线 OP400(lineSn 空=不过滤产线)
  808. */
  809. var HOURLY_API_CFG = { lineSn: '', onlineOprno: 'OP060', offlineOprno: 'OP400' };
  810. var OP_ONLINE = 'OP060';
  811. var OP_OFFLINE = 'OP400';
  812. var QUALITY_LINE_LIST = ['MP', 'XT', 'ZP'];
  813. function buildHourLabels24() {
  814. var hourLabels = [];
  815. for (var i = 1; i <= 24; i++) {
  816. hourLabels.push(pad(i));
  817. }
  818. return hourLabels;
  819. }
  820. function updateHourlyUpDownByApi() {
  821. $.ajax({
  822. url: '${ctx}/mes/mesProduct/hourlyUpDownOkData',
  823. type: 'POST',
  824. dataType: 'json',
  825. data: {
  826. lineSn: HOURLY_API_CFG.lineSn,
  827. onlineOprno: HOURLY_API_CFG.onlineOprno,
  828. offlineOprno: HOURLY_API_CFG.offlineOprno
  829. },
  830. success: function(resp) {
  831. if (resp && resp.result === 'true' && resp.data) {
  832. var d = resp.data;
  833. if (!d.up || !d.down || d.up.length < 24 || d.down.length < 24) {
  834. return;
  835. }
  836. mockData.hourlyData = {
  837. labels: buildHourLabels24(),
  838. up: d.up,
  839. down: d.down,
  840. max: typeof d.max === 'number' ? d.max : 100
  841. };
  842. drawAllCharts();
  843. }
  844. }
  845. });
  846. }
  847. function generateMockData() {
  848. // 近7日日期标签
  849. var now = new Date();
  850. var weekLabels = [];
  851. for (var i = 6; i >= 0; i--) {
  852. var d = new Date(now);
  853. d.setDate(d.getDate() - i);
  854. weekLabels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
  855. }
  856. // 1. 近七日产量折线图 (左1)
  857. mockData.productTrend = {
  858. labels: weekLabels,
  859. data: [1100, 1100, 1200, 1125, 1125, 1180, 1150],
  860. max: 1300, min: 1000
  861. };
  862. // 2. 近七日产品检验盘点 (左2) - 产出/不良 双柱状图
  863. mockData.qualityInspect = {
  864. labels: weekLabels,
  865. produced: [400, 321, 354, 164, 241, 342, 231],
  866. defect: [345, 256, 312, 123, 214, 286, 189],
  867. max: 500
  868. };
  869. // 3. 不良类型柱状图 (左3)
  870. mockData.defectType = {
  871. labels: ['故障A', '故障B', '电阻设备', '焊接', '铸锭', '故障C', '故障D'],
  872. data: [480, 380, 220, 180, 60, 200, 80],
  873. max: 500
  874. };
  875. // 4. 达成率数据分析 - 近七日达成率 (右1)
  876. mockData.achieveRate = {
  877. labels: weekLabels,
  878. target: [1150, 1160, 1180, 1165, 1180, 1180, 1170],
  879. actual: [1150, 1160, 1180, 1165, 1180, 1200, 1170],
  880. max: 1300, min: 1000
  881. };
  882. mockData.todayRate = 80;
  883. mockData.monthRate = 70;
  884. // 5. 每小时上下线数据分析 (右2) - 24小时
  885. var hourLabels = [];
  886. var upLine = [], downLine = [];
  887. for (var i = 1; i <= 24; i++) {
  888. hourLabels.push(pad(i));
  889. upLine.push(Math.round(60 + Math.random() * 40));
  890. downLine.push(Math.round(50 + Math.random() * 40));
  891. }
  892. mockData.hourlyData = {
  893. labels: hourLabels,
  894. up: upLine,
  895. down: downLine,
  896. max: 120
  897. };
  898. // 6. 报警信息由 updateDeviceAlarmAlertsByApi 拉取 mesDeviceAlarm/listData,展示 type
  899. mockData.alerts = [];
  900. // 7. 设备故障统计分析 - 故障类型排名
  901. mockData.faultRank = [
  902. { rank: 'No.1', name: '机构故障', count: 10, percent: 100, top: 1 },
  903. { rank: 'No.2', name: '电控故障', count: 8, percent: 80, top: 2 },
  904. { rank: 'No.3', name: '辅助设备故障', count: 6, percent: 60 },
  905. { rank: 'No.4', name: '突发故障', count: 4, percent: 40 },
  906. { rank: 'No.5', name: '故障类型1', count: 2, percent: 20 }
  907. ];
  908. // 近七日设备故障分析
  909. mockData.faultAnalysis = {
  910. labels: weekLabels,
  911. faultTime: [25, 35, 45, 35, 30, 40, 43],
  912. faultRate: [40, 60, 100, 55, 55, 60, 65],
  913. maxTime: 60, maxRate: 120
  914. };
  915. // 6个数据卡片
  916. mockData.cards = {
  917. productionPeople: 48,
  918. deviceCount: 56,
  919. planProduction: 221,
  920. actualProduction: 225,
  921. repairCount: 11,
  922. scrapCount: 10
  923. };
  924. }
  925. // 通用:准备 canvas 上下文
  926. function prepareCanvas(canvasId) {
  927. var canvas = document.getElementById(canvasId);
  928. if (!canvas) return null;
  929. var parent = canvas.parentElement;
  930. var pw = parent.offsetWidth;
  931. var ph = parent.offsetHeight;
  932. if (canvas.width !== pw || canvas.height !== ph) {
  933. canvas.width = pw;
  934. canvas.height = ph;
  935. }
  936. return { ctx: canvas.getContext('2d'), w: pw, h: ph };
  937. }
  938. // 通用:Y 轴刻度和网格
  939. function drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal) {
  940. minVal = minVal || 0;
  941. ctx.fillStyle = 'rgba(77, 201, 245, 0.8)';
  942. ctx.font = '11px Microsoft YaHei';
  943. ctx.textAlign = 'right';
  944. ctx.strokeStyle = 'rgba(0, 143, 253, 0.15)';
  945. ctx.lineWidth = 1;
  946. for (var i = 0; i <= 4; i++) {
  947. var val = Math.round(minVal + (maxVal - minVal) * (4 - i) / 4);
  948. var y = padT + ch * i / 4;
  949. ctx.fillText(val, padL - 5, y + 4);
  950. ctx.beginPath();
  951. ctx.moveTo(padL, y);
  952. ctx.lineTo(padL + cw, y);
  953. ctx.stroke();
  954. }
  955. }
  956. // 折线图(单线,平滑,带面积)
  957. function drawLineChart(canvasId, data, labels, maxVal, minVal, color) {
  958. var p = prepareCanvas(canvasId);
  959. if (!p) return;
  960. var ctx = p.ctx, w = p.w, h = p.h;
  961. var padL = 42, padR = 12, padT = 15, padB = 25;
  962. var cw = w - padL - padR, ch = h - padT - padB;
  963. color = color || '#00C8FF';
  964. minVal = minVal || 0;
  965. ctx.clearRect(0, 0, w, h);
  966. drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal);
  967. if (!data || data.length < 2) return;
  968. var stepX = cw / (data.length - 1);
  969. var range = maxVal - minVal;
  970. // 渐变面积
  971. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  972. grad.addColorStop(0, hexToRgba(color, 0.4));
  973. grad.addColorStop(1, hexToRgba(color, 0.02));
  974. ctx.beginPath();
  975. ctx.moveTo(padL, padT + ch);
  976. for (var i = 0; i < data.length; i++) {
  977. var x = padL + i * stepX;
  978. var y = padT + ch - ((data[i] - minVal) / range) * ch;
  979. if (i === 0) ctx.lineTo(x, y);
  980. else {
  981. var px = padL + (i - 1) * stepX;
  982. var py = padT + ch - ((data[i - 1] - minVal) / range) * ch;
  983. var cpx = (px + x) / 2;
  984. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  985. }
  986. }
  987. ctx.lineTo(padL + (data.length - 1) * stepX, padT + ch);
  988. ctx.closePath();
  989. ctx.fillStyle = grad;
  990. ctx.fill();
  991. // 折线
  992. ctx.beginPath();
  993. for (var i = 0; i < data.length; i++) {
  994. var x = padL + i * stepX;
  995. var y = padT + ch - ((data[i] - minVal) / range) * ch;
  996. if (i === 0) ctx.moveTo(x, y);
  997. else {
  998. var px = padL + (i - 1) * stepX;
  999. var py = padT + ch - ((data[i - 1] - minVal) / range) * ch;
  1000. var cpx = (px + x) / 2;
  1001. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  1002. }
  1003. }
  1004. ctx.strokeStyle = color;
  1005. ctx.lineWidth = 2;
  1006. ctx.stroke();
  1007. // 数据点和标签
  1008. ctx.fillStyle = '#ffffff';
  1009. ctx.font = 'bold 11px Microsoft YaHei';
  1010. ctx.textAlign = 'center';
  1011. for (var i = 0; i < data.length; i++) {
  1012. var x = padL + i * stepX;
  1013. var y = padT + ch - ((data[i] - minVal) / range) * ch;
  1014. ctx.beginPath();
  1015. ctx.arc(x, y, 3, 0, Math.PI * 2);
  1016. ctx.fillStyle = color;
  1017. ctx.fill();
  1018. ctx.strokeStyle = '#fff';
  1019. ctx.lineWidth = 1.5;
  1020. ctx.stroke();
  1021. ctx.fillStyle = '#ffffff';
  1022. ctx.fillText(data[i], x, y - 8);
  1023. }
  1024. // X 轴标签
  1025. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1026. ctx.font = '11px Microsoft YaHei';
  1027. ctx.textAlign = 'center';
  1028. for (var i = 0; i < labels.length; i++) {
  1029. var x = padL + i * stepX;
  1030. ctx.fillText(labels[i], x, h - 6);
  1031. }
  1032. }
  1033. // 双折线图(目标+实际)
  1034. function drawDualLineChart(canvasId, target, actual, labels, maxVal, minVal) {
  1035. var p = prepareCanvas(canvasId);
  1036. if (!p) return;
  1037. var ctx = p.ctx, w = p.w, h = p.h;
  1038. var padL = 42, padR = 12, padT = 12, padB = 25;
  1039. var cw = w - padL - padR, ch = h - padT - padB;
  1040. minVal = minVal || 0;
  1041. var range = maxVal - minVal;
  1042. ctx.clearRect(0, 0, w, h);
  1043. drawYAxis(ctx, padL, padT, cw, ch, maxVal, minVal);
  1044. if (!target || target.length < 2) return;
  1045. var stepX = cw / (target.length - 1);
  1046. function drawLine(arr, color) {
  1047. // 面积
  1048. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  1049. grad.addColorStop(0, hexToRgba(color, 0.3));
  1050. grad.addColorStop(1, hexToRgba(color, 0.02));
  1051. ctx.beginPath();
  1052. ctx.moveTo(padL, padT + ch);
  1053. for (var i = 0; i < arr.length; i++) {
  1054. var x = padL + i * stepX;
  1055. var y = padT + ch - ((arr[i] - minVal) / range) * ch;
  1056. if (i === 0) ctx.lineTo(x, y);
  1057. else {
  1058. var px = padL + (i - 1) * stepX;
  1059. var py = padT + ch - ((arr[i - 1] - minVal) / range) * ch;
  1060. var cpx = (px + x) / 2;
  1061. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  1062. }
  1063. }
  1064. ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
  1065. ctx.closePath();
  1066. ctx.fillStyle = grad;
  1067. ctx.fill();
  1068. // 线
  1069. ctx.beginPath();
  1070. for (var i = 0; i < arr.length; i++) {
  1071. var x = padL + i * stepX;
  1072. var y = padT + ch - ((arr[i] - minVal) / range) * ch;
  1073. if (i === 0) ctx.moveTo(x, y);
  1074. else {
  1075. var px = padL + (i - 1) * stepX;
  1076. var py = padT + ch - ((arr[i - 1] - minVal) / range) * ch;
  1077. var cpx = (px + x) / 2;
  1078. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  1079. }
  1080. }
  1081. ctx.strokeStyle = color;
  1082. ctx.lineWidth = 2;
  1083. ctx.stroke();
  1084. // 点
  1085. for (var i = 0; i < arr.length; i++) {
  1086. var x = padL + i * stepX;
  1087. var y = padT + ch - ((arr[i] - minVal) / range) * ch;
  1088. ctx.beginPath();
  1089. ctx.arc(x, y, 3, 0, Math.PI * 2);
  1090. ctx.fillStyle = color;
  1091. ctx.fill();
  1092. }
  1093. }
  1094. drawLine(target, '#FFDD00');
  1095. drawLine(actual, '#00C8FF');
  1096. // 数值
  1097. ctx.fillStyle = '#ffffff';
  1098. ctx.font = '10px Microsoft YaHei';
  1099. ctx.textAlign = 'center';
  1100. for (var i = 0; i < actual.length; i++) {
  1101. var x = padL + i * stepX;
  1102. var y = padT + ch - ((actual[i] - minVal) / range) * ch;
  1103. ctx.fillText(actual[i], x, y - 6);
  1104. }
  1105. // X 轴
  1106. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1107. ctx.font = '10px Microsoft YaHei';
  1108. for (var i = 0; i < labels.length; i++) {
  1109. var x = padL + i * stepX;
  1110. ctx.fillText(labels[i], x, h - 6);
  1111. }
  1112. }
  1113. // 双柱状图 (产出/不良)
  1114. function drawDoubleBarChart(canvasId, produced, defect, labels, maxVal) {
  1115. var p = prepareCanvas(canvasId);
  1116. if (!p) return;
  1117. var ctx = p.ctx, w = p.w, h = p.h;
  1118. var padL = 42, padR = 12, padT = 30, padB = 25;
  1119. var cw = w - padL - padR, ch = h - padT - padB;
  1120. ctx.clearRect(0, 0, w, h);
  1121. drawYAxis(ctx, padL, padT, cw, ch, maxVal);
  1122. // 图例
  1123. ctx.fillStyle = '#00FF88';
  1124. ctx.fillRect(padL + 40, 8, 12, 12);
  1125. ctx.fillStyle = '#ffffff';
  1126. ctx.font = '12px Microsoft YaHei';
  1127. ctx.textAlign = 'left';
  1128. ctx.fillText('合格', padL + 58, 18);
  1129. ctx.fillStyle = '#00C8FF';
  1130. ctx.fillRect(padL + 110, 8, 12, 12);
  1131. ctx.fillStyle = '#ffffff';
  1132. ctx.fillText('不良', padL + 128, 18);
  1133. if (!produced || produced.length < 1) return;
  1134. var groupW = cw / produced.length;
  1135. var barW = groupW * 0.3;
  1136. for (var i = 0; i < produced.length; i++) {
  1137. var cx = padL + i * groupW + groupW / 2;
  1138. // 产出
  1139. var h1 = (produced[i] / maxVal) * ch;
  1140. var g1 = ctx.createLinearGradient(0, padT + ch - h1, 0, padT + ch);
  1141. g1.addColorStop(0, '#00FF88');
  1142. g1.addColorStop(1, 'rgba(0, 255, 136, 0.2)');
  1143. ctx.fillStyle = g1;
  1144. ctx.fillRect(cx - barW - 2, padT + ch - h1, barW, h1);
  1145. // 不良
  1146. var h2 = (defect[i] / maxVal) * ch;
  1147. var g2 = ctx.createLinearGradient(0, padT + ch - h2, 0, padT + ch);
  1148. g2.addColorStop(0, '#00C8FF');
  1149. g2.addColorStop(1, 'rgba(0, 200, 255, 0.2)');
  1150. ctx.fillStyle = g2;
  1151. ctx.fillRect(cx + 2, padT + ch - h2, barW, h2);
  1152. // 数值
  1153. ctx.fillStyle = '#00FF88';
  1154. ctx.font = 'bold 10px Microsoft YaHei';
  1155. ctx.textAlign = 'center';
  1156. ctx.fillText(produced[i], cx - barW / 2 - 2, padT + ch - h1 - 4);
  1157. ctx.fillStyle = '#00C8FF';
  1158. ctx.fillText(defect[i], cx + barW / 2 + 2, padT + ch - h2 - 4);
  1159. // X 轴
  1160. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1161. ctx.font = '11px Microsoft YaHei';
  1162. ctx.fillText(labels[i], cx, h - 6);
  1163. }
  1164. }
  1165. // 单柱状图 (不良类型)
  1166. function drawBarChart(canvasId, data, labels, maxVal) {
  1167. var p = prepareCanvas(canvasId);
  1168. if (!p) return;
  1169. var ctx = p.ctx, w = p.w, h = p.h;
  1170. var padL = 42, padR = 12, padT = 15, padB = 25;
  1171. var cw = w - padL - padR, ch = h - padT - padB;
  1172. ctx.clearRect(0, 0, w, h);
  1173. drawYAxis(ctx, padL, padT, cw, ch, maxVal);
  1174. if (!data || data.length < 1) return;
  1175. var groupW = cw / data.length;
  1176. var barW = groupW * 0.5;
  1177. for (var i = 0; i < data.length; i++) {
  1178. var cx = padL + i * groupW + groupW / 2;
  1179. var barH = (data[i] / maxVal) * ch;
  1180. var grad = ctx.createLinearGradient(0, padT + ch - barH, 0, padT + ch);
  1181. grad.addColorStop(0, '#00C8FF');
  1182. grad.addColorStop(1, 'rgba(0, 200, 255, 0.1)');
  1183. ctx.fillStyle = grad;
  1184. ctx.fillRect(cx - barW / 2, padT + ch - barH, barW, barH);
  1185. // X 轴
  1186. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1187. ctx.font = '11px Microsoft YaHei';
  1188. ctx.textAlign = 'center';
  1189. ctx.fillText(labels[i], cx, h - 6);
  1190. }
  1191. }
  1192. // 混合柱+折线 (设备故障分析)
  1193. function drawBarLineChart(canvasId, barData, lineData, labels, barMax, lineMax) {
  1194. var p = prepareCanvas(canvasId);
  1195. if (!p) return;
  1196. var ctx = p.ctx, w = p.w, h = p.h;
  1197. var padL = 42, padR = 42, padT = 15, padB = 25;
  1198. var cw = w - padL - padR, ch = h - padT - padB;
  1199. ctx.clearRect(0, 0, w, h);
  1200. // 左 Y 轴:柱状图刻度
  1201. drawYAxis(ctx, padL, padT, cw, ch, barMax);
  1202. // 右 Y 轴:折线图刻度 (%)
  1203. ctx.fillStyle = 'rgba(0, 255, 136, 0.9)';
  1204. ctx.font = '11px Microsoft YaHei';
  1205. ctx.textAlign = 'left';
  1206. for (var i = 0; i <= 4; i++) {
  1207. var val = Math.round(lineMax * (4 - i) / 4);
  1208. var y = padT + ch * i / 4;
  1209. ctx.fillText(val + '%', padL + cw + 5, y + 4);
  1210. }
  1211. if (!barData || barData.length < 1) return;
  1212. var groupW = cw / barData.length;
  1213. var barW = groupW * 0.4;
  1214. // 柱
  1215. for (var i = 0; i < barData.length; i++) {
  1216. var cx = padL + i * groupW + groupW / 2;
  1217. var barH = (barData[i] / barMax) * ch;
  1218. var grad = ctx.createLinearGradient(0, padT + ch - barH, 0, padT + ch);
  1219. grad.addColorStop(0, '#00C8FF');
  1220. grad.addColorStop(1, 'rgba(0, 200, 255, 0.2)');
  1221. ctx.fillStyle = grad;
  1222. ctx.fillRect(cx - barW / 2, padT + ch - barH, barW, barH);
  1223. // 数值
  1224. ctx.fillStyle = '#00C8FF';
  1225. ctx.font = 'bold 11px Microsoft YaHei';
  1226. ctx.textAlign = 'center';
  1227. ctx.fillText(barData[i], cx, padT + ch - barH - 4);
  1228. // X 轴
  1229. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1230. ctx.font = '11px Microsoft YaHei';
  1231. ctx.fillText(labels[i], cx, h - 6);
  1232. }
  1233. // 折线
  1234. var stepX = groupW;
  1235. ctx.beginPath();
  1236. for (var i = 0; i < lineData.length; i++) {
  1237. var cx = padL + i * groupW + groupW / 2;
  1238. var cy = padT + ch - (lineData[i] / lineMax) * ch;
  1239. if (i === 0) ctx.moveTo(cx, cy);
  1240. else ctx.lineTo(cx, cy);
  1241. }
  1242. ctx.strokeStyle = '#00FF88';
  1243. ctx.lineWidth = 2;
  1244. ctx.stroke();
  1245. // 点
  1246. for (var i = 0; i < lineData.length; i++) {
  1247. var cx = padL + i * groupW + groupW / 2;
  1248. var cy = padT + ch - (lineData[i] / lineMax) * ch;
  1249. ctx.beginPath();
  1250. ctx.arc(cx, cy, 3, 0, Math.PI * 2);
  1251. ctx.fillStyle = '#00FF88';
  1252. ctx.fill();
  1253. }
  1254. }
  1255. // 双面积折线 (每小时上下线)
  1256. function drawAreaLineChart(canvasId, upData, downData, labels, maxVal) {
  1257. var p = prepareCanvas(canvasId);
  1258. if (!p) return;
  1259. var ctx = p.ctx, w = p.w, h = p.h;
  1260. var padL = 35, padR = 12, padT = 30, padB = 25;
  1261. var cw = w - padL - padR, ch = h - padT - padB;
  1262. ctx.clearRect(0, 0, w, h);
  1263. drawYAxis(ctx, padL, padT, cw, ch, maxVal);
  1264. // 图例
  1265. ctx.fillStyle = '#00FF88';
  1266. ctx.fillRect(padL + 60, 8, 12, 12);
  1267. ctx.fillStyle = '#ffffff';
  1268. ctx.font = '12px Microsoft YaHei';
  1269. ctx.textAlign = 'left';
  1270. ctx.fillText('上线', padL + 78, 18);
  1271. ctx.fillStyle = '#00C8FF';
  1272. ctx.fillRect(padL + 130, 8, 12, 12);
  1273. ctx.fillStyle = '#ffffff';
  1274. ctx.fillText('下线', padL + 148, 18);
  1275. if (!upData || upData.length < 2) return;
  1276. var stepX = cw / (upData.length - 1);
  1277. function drawOne(arr, color) {
  1278. var grad = ctx.createLinearGradient(0, padT, 0, padT + ch);
  1279. grad.addColorStop(0, hexToRgba(color, 0.5));
  1280. grad.addColorStop(1, hexToRgba(color, 0.05));
  1281. ctx.beginPath();
  1282. ctx.moveTo(padL, padT + ch);
  1283. for (var i = 0; i < arr.length; i++) {
  1284. var x = padL + i * stepX;
  1285. var y = padT + ch - (arr[i] / maxVal) * ch;
  1286. if (i === 0) ctx.lineTo(x, y);
  1287. else {
  1288. var px = padL + (i - 1) * stepX;
  1289. var py = padT + ch - (arr[i - 1] / maxVal) * ch;
  1290. var cpx = (px + x) / 2;
  1291. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  1292. }
  1293. }
  1294. ctx.lineTo(padL + (arr.length - 1) * stepX, padT + ch);
  1295. ctx.closePath();
  1296. ctx.fillStyle = grad;
  1297. ctx.fill();
  1298. ctx.beginPath();
  1299. for (var i = 0; i < arr.length; i++) {
  1300. var x = padL + i * stepX;
  1301. var y = padT + ch - (arr[i] / maxVal) * ch;
  1302. if (i === 0) ctx.moveTo(x, y);
  1303. else {
  1304. var px = padL + (i - 1) * stepX;
  1305. var py = padT + ch - (arr[i - 1] / maxVal) * ch;
  1306. var cpx = (px + x) / 2;
  1307. ctx.bezierCurveTo(cpx, py, cpx, y, x, y);
  1308. }
  1309. }
  1310. ctx.strokeStyle = color;
  1311. ctx.lineWidth = 1.5;
  1312. ctx.stroke();
  1313. }
  1314. drawOne(upData, '#00FF88');
  1315. drawOne(downData, '#00C8FF');
  1316. // X 轴
  1317. ctx.fillStyle = 'rgba(77, 201, 245, 0.9)';
  1318. ctx.font = '9px Microsoft YaHei';
  1319. ctx.textAlign = 'center';
  1320. for (var i = 0; i < labels.length; i++) {
  1321. if (i % 2 === 0) {
  1322. var x = padL + i * stepX;
  1323. ctx.fillText(labels[i], x, h - 6);
  1324. }
  1325. }
  1326. }
  1327. // 工具:hex 转 rgba
  1328. function hexToRgba(hex, alpha) {
  1329. var r = parseInt(hex.slice(1, 3), 16);
  1330. var g = parseInt(hex.slice(3, 5), 16);
  1331. var b = parseInt(hex.slice(5, 7), 16);
  1332. return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
  1333. }
  1334. // 渲染故障类型排名列表
  1335. function renderFaultRank() {
  1336. var list = mockData.faultRank || [];
  1337. var html = '';
  1338. for (var i = 0; i < list.length; i++) {
  1339. var it = list[i];
  1340. var topCls = it.top === 1 ? 'top1' : (it.top === 2 ? 'top2' : '');
  1341. html += '<div class="mes3-fault-item ' + topCls + '">';
  1342. html += '<div class="fault-rank-no">' + it.rank + '</div>';
  1343. html += '<div class="fault-name">' + it.name + '</div>';
  1344. html += '<div class="fault-bar-wrap"><div class="fault-bar" style="width:' + it.percent + '%"></div></div>';
  1345. html += '<div class="fault-count">' + it.count + '</div>';
  1346. html += '</div>';
  1347. }
  1348. var el = document.getElementById('faultRankList');
  1349. if (el) el.innerHTML = html;
  1350. }
  1351. function escapeHtml(s) {
  1352. if (s == null || s === '') return '';
  1353. return String(s)
  1354. .replace(/&/g, '&amp;')
  1355. .replace(/</g, '&lt;')
  1356. .replace(/>/g, '&gt;')
  1357. .replace(/"/g, '&quot;');
  1358. }
  1359. // 渲染报警信息列表(text 为设备报警记录的 type 字段)
  1360. function renderAlerts() {
  1361. var list = mockData.alerts || [];
  1362. var html = '';
  1363. for (var i = 0; i < list.length; i++) {
  1364. html += '<div class="mes3-alert-item">';
  1365. html += '<div class="alert-no">' + list[i].no + '</div>';
  1366. html += '<div class="alert-text">' + escapeHtml(list[i].text) + '</div>';
  1367. html += '</div>';
  1368. }
  1369. var el = document.getElementById('alertList');
  1370. if (el) el.innerHTML = html;
  1371. }
  1372. // 渲染数据卡片和达成率数字
  1373. function renderTextData() {
  1374. var c = mockData.cards || {};
  1375. var map = {
  1376. productionPeople: c.productionPeople,
  1377. deviceCount: c.deviceCount,
  1378. planProduction: c.planProduction,
  1379. actualProduction: c.actualProduction,
  1380. repairCount: c.repairCount,
  1381. scrapCount: c.scrapCount,
  1382. todayRate: mockData.todayRate,
  1383. monthRate: mockData.monthRate
  1384. };
  1385. for (var k in map) {
  1386. var el = document.getElementById(k);
  1387. if (el && map[k] != null) el.textContent = map[k];
  1388. }
  1389. }
  1390. function extractList(resp) {
  1391. if (!resp) return [];
  1392. if (Object.prototype.toString.call(resp.list) === '[object Array]') return resp.list;
  1393. if (Object.prototype.toString.call(resp.rows) === '[object Array]') return resp.rows;
  1394. if (resp.data) {
  1395. if (Object.prototype.toString.call(resp.data.list) === '[object Array]') return resp.data.list;
  1396. if (Object.prototype.toString.call(resp.data.rows) === '[object Array]') return resp.data.rows;
  1397. }
  1398. return [];
  1399. }
  1400. function parseDeviceSnParts(deviceSn) {
  1401. var s = String(deviceSn || '').trim().toUpperCase();
  1402. var parts = s.split('_').filter(function(p) { return !!p; });
  1403. var line = parts.length > 0 ? parts[0] : '';
  1404. var oprno = '';
  1405. for (var i = 0; i < parts.length; i++) {
  1406. if (/^OP\d+$/.test(parts[i])) {
  1407. oprno = parts[i];
  1408. break;
  1409. }
  1410. }
  1411. if (!oprno) {
  1412. var m = s.match(/OP\d+/);
  1413. if (m) oprno = m[0];
  1414. }
  1415. return { line: line, oprno: oprno };
  1416. }
  1417. function updateDeviceAlarmAlertsByApi() {
  1418. $.ajax({
  1419. url: '${ctx}/mes/mesProductScreen/alertList',
  1420. type: 'POST',
  1421. dataType: 'json',
  1422. data: {
  1423. pageNo: 1,
  1424. pageSize: 200,
  1425. state: '0'
  1426. },
  1427. success: function(resp) {
  1428. var rows = extractList(resp);
  1429. var alerts = [];
  1430. for (var i = 0; i < rows.length; i++) {
  1431. var r = rows[i] || {};
  1432. if (String(r.state) !== '0') continue;
  1433. var sn = r.deviceSn || r.device_sn || r.deviceCode || r.device_code || '';
  1434. var parts = parseDeviceSnParts(sn);
  1435. var oprno = String(r.oprno || parts.oprno || '').toUpperCase();
  1436. var typ = (r.type != null && r.type !== undefined) ? String(r.type) : '';
  1437. var text = oprno
  1438. ? (oprno + (typ ? (' | ' + typ) : ''))
  1439. : typ;
  1440. if (!text) continue;
  1441. alerts.push({ no: alerts.length + 1, text: text, oprno: oprno || '' });
  1442. }
  1443. mockData.alerts = alerts;
  1444. renderAlerts();
  1445. },
  1446. error: function() {
  1447. mockData.alerts = [];
  1448. renderAlerts();
  1449. }
  1450. });
  1451. }
  1452. /** 总览三卡:生产人数 / 设备数量 / 计划产量 <- mesProductScreen 最新配置 */
  1453. function updateScreenConfigByApi() {
  1454. $.ajax({
  1455. url: '${ctx}/mes/mesProductScreen/latestConfig',
  1456. type: 'POST',
  1457. dataType: 'json',
  1458. success: function(resp) {
  1459. if (!resp || resp.result !== 'true' || !resp.data) return;
  1460. var d = resp.data;
  1461. mockData.cards = mockData.cards || {};
  1462. mockData.cards.productionPeople = Number(d.peopleNums) || 0;
  1463. mockData.cards.deviceCount = Number(d.deviceNums) || 0;
  1464. mockData.cards.planProduction = Number(d.planNums) || 0;
  1465. var elPeople = document.getElementById('productionPeople');
  1466. var elDevice = document.getElementById('deviceCount');
  1467. var elPlan = document.getElementById('planProduction');
  1468. if (elPeople) elPeople.textContent = mockData.cards.productionPeople;
  1469. if (elDevice) elDevice.textContent = mockData.cards.deviceCount;
  1470. if (elPlan) elPlan.textContent = mockData.cards.planProduction;
  1471. syncAchieveRateWithPlan();
  1472. }
  1473. });
  1474. }
  1475. function calcRatePercent(actual, plan) {
  1476. actual = Number(actual) || 0;
  1477. plan = Number(plan) || 0;
  1478. if (plan <= 0) {
  1479. return 0;
  1480. }
  1481. return Math.round(actual * 100 / plan);
  1482. }
  1483. function syncAchieveRateWithPlan() {
  1484. mockData.cards = mockData.cards || {};
  1485. mockData.achieveRate = mockData.achieveRate || { labels: [], actual: [] };
  1486. var planNums = Number(mockData.cards.planProduction) || 0;
  1487. var actualProduction = Number(mockData.cards.actualProduction) || 0;
  1488. var labels = mockData.achieveRate.labels || [];
  1489. var actualSeries = mockData.achieveRate.actual || [];
  1490. var targetSeries = [];
  1491. for (var i = 0; i < labels.length; i++) {
  1492. targetSeries.push(planNums);
  1493. }
  1494. var maxVal = Math.max.apply(null, targetSeries.concat(actualSeries).concat([10]));
  1495. mockData.achieveRate.target = targetSeries;
  1496. mockData.achieveRate.max = maxVal <= 0 ? 10 : Math.ceil(maxVal * 1.1);
  1497. mockData.achieveRate.min = 0;
  1498. mockData.todayRate = calcRatePercent(actualProduction, planNums);
  1499. mockData.monthRate = calcRatePercent(actualProduction, planNums);
  1500. renderTextData();
  1501. drawAllCharts();
  1502. }
  1503. function isOprOfflineFamily(oprno) {
  1504. var op = (oprno || '').replace(/\s/g, '').toUpperCase();
  1505. var prefix = OP_OFFLINE;
  1506. if (op.length < prefix.length || op.indexOf(prefix) !== 0) return false;
  1507. if (op.length === prefix.length) return op === prefix;
  1508. return new RegExp('^' + prefix + '[A-Z0-9_-]+$').test(op);
  1509. }
  1510. function isRecordContentOk(content) {
  1511. var c = (content || '').replace(/\s/g, '').toUpperCase();
  1512. return c === 'OK';
  1513. }
  1514. function getCalendarDayUpdateDateRange() {
  1515. var now = new Date();
  1516. var p = function(n) { return n < 10 ? '0' + n : '' + n; };
  1517. var dateStr = now.getFullYear() + '-' + p(now.getMonth() + 1) + '-' + p(now.getDate());
  1518. return {
  1519. gte: dateStr + ' 00:00:00',
  1520. lte: dateStr + ' 23:59:59',
  1521. startMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0).getTime(),
  1522. endMs: new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999).getTime()
  1523. };
  1524. }
  1525. function parseRecordDateMs(val) {
  1526. if (val == null || val === '') return NaN;
  1527. if (typeof val === 'number' && !isNaN(val)) return val;
  1528. var s = String(val).trim().replace(/-/g, '/');
  1529. var t = Date.parse(s);
  1530. return isNaN(t) ? NaN : t;
  1531. }
  1532. function isRecordWithinToday(r, day) {
  1533. var t = parseRecordDateMs(r.updateDate || r.update_date);
  1534. if (isNaN(t)) return false;
  1535. return t >= day.startMs && t <= day.endMs;
  1536. }
  1537. function extractPageCount(resp) {
  1538. if (!resp) return 0;
  1539. if (typeof resp.count === 'number') return resp.count;
  1540. if (resp.data && typeof resp.data.count === 'number') return resp.data.count;
  1541. var list = extractList(resp);
  1542. return list.length;
  1543. }
  1544. // 中间卡:返修/报废数量 <- mesProductScreen/repairScrapCount
  1545. function updateRepairScrapCountByApi() {
  1546. $.ajax({
  1547. url: '${ctx}/mes/mesProductScreen/repairScrapCount',
  1548. type: 'POST',
  1549. dataType: 'json',
  1550. success: function(resp) {
  1551. if (!resp || resp.result !== 'true' || !resp.data) return;
  1552. mockData.cards = mockData.cards || {};
  1553. mockData.cards.repairCount = Number(resp.data.repairCount) || 0;
  1554. mockData.cards.scrapCount = Number(resp.data.scrapCount) || 0;
  1555. renderTextData();
  1556. }
  1557. });
  1558. }
  1559. // 不良类型柱状图:按返工白名单(item_id->title)分组统计当天返工次数
  1560. function updateDefectTypeByApi() {
  1561. $.ajax({
  1562. url: '${ctx}/mes/mesProduct/defectTypeData',
  1563. type: 'POST',
  1564. dataType: 'json',
  1565. success: function(resp) {
  1566. if (!resp || resp.result !== 'true' || !resp.data) return;
  1567. var d = resp.data;
  1568. mockData.defectType = {
  1569. labels: d.labels || [],
  1570. data: d.data || [],
  1571. max: Number(d.max) || 10
  1572. };
  1573. drawAllCharts();
  1574. }
  1575. });
  1576. }
  1577. // 近七日产品质检:产出=OP400 OK,不良=返工 OP070-OP400,按产线汇总
  1578. function updateQualityInspectByApi() {
  1579. var labels = null;
  1580. var producedSum = null;
  1581. var defectSum = null;
  1582. var total = QUALITY_LINE_LIST.length;
  1583. var finished = 0;
  1584. function finishOne() {
  1585. finished++;
  1586. if (finished < total) return;
  1587. if (!producedSum || !defectSum) return;
  1588. var maxV = 10;
  1589. var i;
  1590. for (i = 0; i < producedSum.length; i++) {
  1591. if (Number(producedSum[i]) > maxV) maxV = Number(producedSum[i]);
  1592. }
  1593. for (i = 0; i < defectSum.length; i++) {
  1594. if (Number(defectSum[i]) > maxV) maxV = Number(defectSum[i]);
  1595. }
  1596. mockData.qualityInspect = {
  1597. labels: labels || [],
  1598. produced: producedSum,
  1599. defect: defectSum,
  1600. max: Math.ceil(maxV * 1.15) || 10
  1601. };
  1602. drawAllCharts();
  1603. }
  1604. for (var li = 0; li < QUALITY_LINE_LIST.length; li++) {
  1605. (function(lineSn) {
  1606. $.ajax({
  1607. url: '${ctx}/mes/mesProductRecord/weekQualityData',
  1608. type: 'POST',
  1609. dataType: 'json',
  1610. data: {
  1611. lineSn: lineSn,
  1612. producedOprno: OP_OFFLINE,
  1613. defectOprnoFrom: 'OP070',
  1614. defectOprnoTo: OP_OFFLINE
  1615. },
  1616. success: function(resp) {
  1617. if (resp && resp.result === 'true' && resp.data) {
  1618. var d = resp.data;
  1619. if (!labels) labels = d.labels || [];
  1620. var p = d.produced || [];
  1621. var df = d.defect || [];
  1622. if (!producedSum) {
  1623. producedSum = [];
  1624. defectSum = [];
  1625. for (var i = 0; i < 7; i++) {
  1626. producedSum[i] = 0;
  1627. defectSum[i] = 0;
  1628. }
  1629. }
  1630. for (var j = 0; j < 7; j++) {
  1631. producedSum[j] += Number(p[j]) || 0;
  1632. defectSum[j] += Number(df[j]) || 0;
  1633. }
  1634. }
  1635. finishOne();
  1636. },
  1637. error: function() {
  1638. finishOne();
  1639. }
  1640. });
  1641. })(QUALITY_LINE_LIST[li]);
  1642. }
  1643. }
  1644. /** 实际产量:OP400 当天完成(OK)数 */
  1645. function updateActualProductionByApi() {
  1646. var dayRange = getCalendarDayUpdateDateRange();
  1647. var pageSize = 3000;
  1648. var maxPages = 200;
  1649. var pageNo = 1;
  1650. var totalOk = 0;
  1651. function applyActualProductionCount(n) {
  1652. mockData.cards = mockData.cards || {};
  1653. mockData.cards.actualProduction = Number(n) || 0;
  1654. syncAchieveRateWithPlan();
  1655. renderTextData();
  1656. }
  1657. function fetchPage() {
  1658. $.ajax({
  1659. url: '${ctx}/mes/mesProductScreen/recordList',
  1660. type: 'POST',
  1661. dataType: 'json',
  1662. data: {
  1663. pageNo: pageNo,
  1664. pageSize: pageSize,
  1665. oprno: OP_OFFLINE,
  1666. content: 'OK',
  1667. updateDate_gte: dayRange.gte,
  1668. updateDate_lte: dayRange.lte
  1669. },
  1670. success: function(resp) {
  1671. var list = extractList(resp);
  1672. for (var i = 0; i < list.length; i++) {
  1673. var r = list[i] || {};
  1674. if (isOprOfflineFamily(r.oprno) && isRecordContentOk(r.content) && isRecordWithinToday(r, dayRange)) {
  1675. totalOk++;
  1676. }
  1677. }
  1678. if (list.length === pageSize && pageNo < maxPages) {
  1679. pageNo++;
  1680. fetchPage();
  1681. } else {
  1682. applyActualProductionCount(totalOk);
  1683. }
  1684. },
  1685. error: function() {
  1686. applyActualProductionCount(totalOk);
  1687. }
  1688. });
  1689. }
  1690. fetchPage();
  1691. }
  1692. function matchesOfflineOkRecord(r) {
  1693. var t = parseRecordDateMs(r.updateDate || r.update_date);
  1694. if (isNaN(t)) return false;
  1695. if (!isOprOfflineFamily(r.oprno) || !isRecordContentOk(r.content)) return false;
  1696. return true;
  1697. }
  1698. function dateKeyFromMs(ms, padFn) {
  1699. var d = new Date(ms);
  1700. return d.getFullYear() + '-' + padFn(d.getMonth() + 1) + '-' + padFn(d.getDate());
  1701. }
  1702. function updateProductTrend7DaysByApi() {
  1703. var now = new Date();
  1704. var padFn = function(n) { return n < 10 ? '0' + n : '' + n; };
  1705. var labels = [];
  1706. var dayKeys = [];
  1707. var i;
  1708. for (i = 6; i >= 0; i--) {
  1709. var d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);
  1710. labels.push((d.getMonth() + 1) + '月' + d.getDate() + '日');
  1711. dayKeys.push(d.getFullYear() + '-' + padFn(d.getMonth() + 1) + '-' + padFn(d.getDate()));
  1712. }
  1713. var oldest = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
  1714. var gte = oldest.getFullYear() + '-' + padFn(oldest.getMonth() + 1) + '-' + padFn(oldest.getDate()) + ' 00:00:00';
  1715. var lte = now.getFullYear() + '-' + padFn(now.getMonth() + 1) + '-' + padFn(now.getDate()) + ' 23:59:59';
  1716. var counts = {};
  1717. for (i = 0; i < dayKeys.length; i++) {
  1718. counts[dayKeys[i]] = 0;
  1719. }
  1720. var pageSize = 3000;
  1721. var maxPages = 500;
  1722. var pageNo = 1;
  1723. function fetchPage() {
  1724. $.ajax({
  1725. url: '${ctx}/mes/mesProductScreen/recordList',
  1726. type: 'POST',
  1727. dataType: 'json',
  1728. data: {
  1729. pageNo: pageNo,
  1730. pageSize: pageSize,
  1731. oprno: OP_OFFLINE,
  1732. content: 'OK',
  1733. updateDate_gte: gte,
  1734. updateDate_lte: lte
  1735. },
  1736. success: function(resp) {
  1737. var list = extractList(resp);
  1738. var j;
  1739. for (j = 0; j < list.length; j++) {
  1740. var r = list[j] || {};
  1741. if (!matchesOfflineOkRecord(r)) continue;
  1742. var t = parseRecordDateMs(r.updateDate || r.update_date);
  1743. var key = dateKeyFromMs(t, padFn);
  1744. if (Object.prototype.hasOwnProperty.call(counts, key)) {
  1745. counts[key]++;
  1746. }
  1747. }
  1748. if (list.length === pageSize && pageNo < maxPages) {
  1749. pageNo++;
  1750. fetchPage();
  1751. } else {
  1752. var data = [];
  1753. for (j = 0; j < dayKeys.length; j++) {
  1754. data.push(counts[dayKeys[j]] || 0);
  1755. }
  1756. var mx = Math.max.apply(null, data.concat([0]));
  1757. var maxV = mx <= 0 ? 10 : Math.ceil(mx * 1.15);
  1758. mockData.productTrend = {
  1759. labels: labels,
  1760. data: data,
  1761. max: maxV,
  1762. min: 0
  1763. };
  1764. drawAllCharts();
  1765. }
  1766. },
  1767. error: function() {
  1768. drawAllCharts();
  1769. }
  1770. });
  1771. }
  1772. fetchPage();
  1773. }
  1774. function refreshData() {
  1775. generateMockData();
  1776. renderTextData();
  1777. updateScreenConfigByApi();
  1778. updateActualProductionByApi();
  1779. updateProductTrend7DaysByApi();
  1780. updateDeviceAlarmAlertsByApi();
  1781. updateRepairScrapCountByApi();
  1782. updateDefectTypeByApi();
  1783. updateQualityInspectByApi();
  1784. renderFaultRank();
  1785. renderAlerts();
  1786. updateHourlyUpDownByApi();
  1787. drawAllCharts();
  1788. }
  1789. function changeZoom() {
  1790. var wb = $(window).width() / 1920;
  1791. var hb = $(window).height() / 1080;
  1792. var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement);
  1793. var $app = $('#app');
  1794. if (isFs) {
  1795. $('html, body').css({ 'zoom': 1, 'width': '100%', 'height': '100%', 'overflow': 'hidden', 'margin': 0, 'background': '#00091A' });
  1796. $app.css({
  1797. 'transform': 'scale(' + wb + ', ' + hb + ')',
  1798. 'transform-origin': 'left top',
  1799. 'margin': 0,
  1800. 'position': 'absolute',
  1801. 'left': 0,
  1802. 'top': 0
  1803. });
  1804. } else {
  1805. $('html').css({ 'width': '', 'height': '', 'overflow': '', 'margin': '', 'background': '', 'zoom': '' });
  1806. $app.css({ 'transform': 'none', 'transform-origin': '', 'margin': '0 auto', 'position': 'relative', 'left': '', 'top': '' });
  1807. $('body').css({ 'zoom': wb >= hb ? hb : wb, 'width': '', 'height': '', 'overflow': 'hidden', 'margin': '', 'background': '' });
  1808. }
  1809. }
  1810. function drawAllCharts() {
  1811. // 近七日产量(右上)
  1812. var d1 = mockData.productTrend;
  1813. if (d1) drawLineChart('chartLeft1', d1.data, d1.labels, d1.max, d1.min, '#00C8FF');
  1814. // 近七日产品质检(左下)
  1815. var d2 = mockData.qualityInspect;
  1816. if (d2) drawDoubleBarChart('chartLeft2', d2.produced, d2.defect, d2.labels, d2.max);
  1817. // 每小时上下线(右下)
  1818. var d6 = mockData.hourlyData;
  1819. if (d6) drawAreaLineChart('chartRight2', d6.up, d6.down, d6.labels, d6.max);
  1820. }
  1821. $(window).resize(function() {
  1822. changeZoom();
  1823. drawAllCharts();
  1824. });
  1825. function initFullscreenBtn() {
  1826. var btn = document.getElementById('btnFullscreen');
  1827. if (!btn) return;
  1828. btn.addEventListener('click', function () {
  1829. var el = document.documentElement;
  1830. if (el.requestFullscreen) {
  1831. el.requestFullscreen();
  1832. } else if (el.webkitRequestFullscreen) {
  1833. el.webkitRequestFullscreen();
  1834. }
  1835. });
  1836. function syncFullscreenBtn() {
  1837. var fs = document.fullscreenElement || document.webkitFullscreenElement;
  1838. btn.style.display = fs ? 'none' : '';
  1839. changeZoom();
  1840. drawAllCharts();
  1841. }
  1842. document.addEventListener('fullscreenchange', syncFullscreenBtn);
  1843. document.addEventListener('webkitfullscreenchange', syncFullscreenBtn);
  1844. }
  1845. $(function() {
  1846. changeZoom();
  1847. updateClock();
  1848. setInterval(updateClock, 1000);
  1849. refreshData();
  1850. initFullscreenBtn();
  1851. // 每2分钟刷新一次数据
  1852. setInterval(refreshData, 1000 * 60 * 2);
  1853. // 每秒重绘一次(应对 resize 或 zoom 变化)
  1854. setInterval(drawAllCharts, 1000);
  1855. });
  1856. })();
  1857. </script>
  1858. </body>
  1859. </html>