index.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <!-- 引入 ECharts 文件 -->
  6. <script type="text/javascript" src="static/jquery-3.3.1.min.js"></script>
  7. <script src="static/echarts.min.js"></script>
  8. <script src="static/plug-in.min.js"></script>
  9. <script src="static/jquery.liMarquee.js"></script>
  10. <link rel="stylesheet" href="static/index.css" media="all" />
  11. </head>
  12. <body>
  13. <div class="positon">
  14. <!-- <button class="back-btn" onclick="goBack()">&larr; 返回</button> -->
  15. 底图:<select id="mapName">
  16. </select>
  17. </div>
  18. <div class="title-name">设备运行状态一览图</div>
  19. <div class="title-icon">
  20. <label class="legend-item legend-normal"><input type="checkbox" id="filterNormal" checked> 正常设备</label>
  21. <label class="legend-item legend-yellow"><input type="checkbox" id="filterPlanned" checked> 问题设备</label>
  22. <label class="legend-item legend-red"><input type="checkbox" id="filterStopped" checked> 停机设备</label>
  23. </div>
  24. <div style="display:flex;width:1920px;justify-content:space-between;align-items:center;">
  25. <div id="echart" class="echart"></div>
  26. <div class="data">
  27. <!-- 右上:当前问题设备(有进行中报修单的设备) -->
  28. <div class="panel panel-problem">
  29. <div class="panel-name">当前问题设备</div>
  30. <div class="item title">
  31. <span>设备编号</span>
  32. <span>设备名称</span>
  33. <span>维修状态</span>
  34. </div>
  35. <div id="problemList" class="scrollDiv str_vertical"></div>
  36. </div>
  37. <!-- 右下:近期保养计划 -->
  38. <div class="panel panel-plan">
  39. <div class="panel-name">近期保养计划</div>
  40. <div class="bar-title">本月完成/计划</div>
  41. <div class="sum-line">
  42. <span id="monthNum">暂无</span>
  43. <span id="waitNum">暂无</span>
  44. <span id="rate">暂无</span>
  45. </div>
  46. <div class="item title">
  47. <span>设备</span>
  48. <span>周期</span>
  49. <span>计划日期</span>
  50. </div>
  51. <div id="planList" class="scrollDiv str_vertical"></div>
  52. </div>
  53. </div>
  54. </div>
  55. </body>
  56. <style>
  57. /*Plugin CSS*/
  58. .str_wrap {
  59. overflow: hidden;
  60. width: 100%;
  61. position: relative;
  62. -moz-user-select: none;
  63. -khtml-user-select: none;
  64. user-select: none;
  65. white-space: nowrap;
  66. }
  67. /* 保养计划列表:设备名称列加宽,周期列变窄 */
  68. .data .panel-plan .item > span:nth-child(1) {
  69. flex: 4;
  70. }
  71. .data .panel-plan .item > span:nth-child(2) {
  72. flex: 2;
  73. padding: 0;
  74. }
  75. .positon {
  76. position: absolute;
  77. top: 20px;
  78. left: 30%;
  79. z-index: 1000;
  80. }
  81. #mapName {
  82. width: 120px;
  83. }
  84. .str_move {
  85. white-space: nowrap;
  86. position: absolute;
  87. top: 0;
  88. left: 0;
  89. cursor: move;
  90. }
  91. .str_move_clone {
  92. display: inline-block;
  93. vertical-align: top;
  94. position: absolute;
  95. left: 100%;
  96. top: 0;
  97. }
  98. .str_vertical .str_move_clone {
  99. left: 0;
  100. top: 100%;
  101. }
  102. .str_down .str_move_clone {
  103. left: 0;
  104. bottom: 100%;
  105. }
  106. .title-name {
  107. position: absolute;
  108. top: 87px;
  109. left: 26%;
  110. font-size: 34px;
  111. color: black;
  112. z-index: 10000;
  113. }
  114. .title-icon {
  115. position: absolute;
  116. top: 130px;
  117. left: 30%;
  118. z-index: 10000;
  119. }
  120. .title-icon label.legend-item {
  121. display: inline-flex;
  122. align-items: center;
  123. margin-right: 24px;
  124. font-size: 14px;
  125. color: #333;
  126. cursor: pointer;
  127. user-select: none;
  128. }
  129. .title-icon label.legend-item::before {
  130. content: '';
  131. display: inline-block;
  132. width: 12px;
  133. height: 12px;
  134. border-radius: 50%;
  135. margin-right: 8px;
  136. }
  137. .title-icon label.legend-normal::before {
  138. background-color: #174FBF;
  139. box-shadow: 0 0 6px #174FBF;
  140. }
  141. .title-icon label.legend-yellow::before {
  142. background-color: yellow;
  143. box-shadow: 0 0 6px yellow;
  144. }
  145. .title-icon label.legend-red::before {
  146. background-color: red;
  147. box-shadow: 0 0 6px red;
  148. }
  149. .title-icon input[type="checkbox"] {
  150. margin-right: 5px;
  151. cursor: pointer;
  152. transform: scale(1.2);
  153. }
  154. </style>
  155. <script>
  156. function getBase64(image) {
  157. var canvas = document.createElement("canvas");
  158. canvas.width = image.width;
  159. canvas.height = image.height;
  160. var context = canvas.getContext("2d");
  161. context.drawImage(image, 0, 0, image.width, image.height);
  162. // 将canvas的内容转换为base64编码的字符串
  163. var base64 = canvas.toDataURL("image/png");// 可以根据需要更改为其他格式,如'image/jpeg'等
  164. return base64;
  165. }
  166. const ACCESS_TOKEN = 'Access-Token'
  167. const VUE_STORE_BASE = 'pro__'
  168. let mapName, mapList, dataList
  169. var tooltip_message = ''
  170. // 基于准备好的dom,初始化echarts实例
  171. var myChart = echarts.init(document.getElementById('echart'))
  172. var img = new Image()
  173. var canvas = document.createElement('canvas')
  174. var ctx = canvas.getContext('2d')
  175. canvas.width = myChart.getWidth() * window.devicePixelRatio
  176. canvas.height = myChart.getHeight() * window.devicePixelRatio
  177. var fullImage = new Image()
  178. img.onload = function () {
  179. ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
  180. fullImage.src = canvas.toDataURL()
  181. setTimeout(function () {
  182. myChart.resize()
  183. }, 100)
  184. }
  185. img.src = './static/03_04.png'
  186. var option = {
  187. // 背景
  188. backgroundColor: {
  189. repeat: 'no-repeat',
  190. image: fullImage
  191. },
  192. // 地图
  193. geo: {
  194. map: 'world',
  195. silent: true,
  196. top: 0,
  197. left: 0,
  198. right: 0,
  199. bottom: 0,
  200. itemStyle: {
  201. normal: {
  202. opacity: 0.8,
  203. borderWidth: 0,
  204. color: '#a53d13'
  205. }
  206. }
  207. },
  208. title: {
  209. text: '',
  210. },
  211. // 开启工具栏
  212. tooltip: {
  213. "trigger": "item",
  214. "confine": true,
  215. "formatter": (params, ticket, callback) => {
  216. // params.data [20, 204, "L-012"],水平位置,垂直位置,设备编号
  217. let sbNo = params.data[2];
  218. getSbDetail(sbNo);
  219. return tooltip_message;
  220. }
  221. },
  222. series: [{
  223. name: '正常点',
  224. type: 'effectScatter',
  225. coordinateSystem: 'geo',
  226. data: [
  227. ],
  228. showEffectOn: 'render',
  229. rippleEffect: {
  230. brushType: 'stroke'
  231. },
  232. hoverAnimation: true,
  233. label: {
  234. normal: {
  235. formatter: '{@value}',
  236. position: 'left',
  237. show: false
  238. }
  239. },
  240. itemStyle: {
  241. normal: {
  242. color: '#174FBF',
  243. shadowBlur: 200,
  244. shadowColor: '#174FBF'
  245. }
  246. },
  247. zlevel: 1
  248. }, {
  249. name: '正常点编号',
  250. type: 'scatter',
  251. coordinateSystem: 'geo',
  252. symbol: 'pin', // 气泡
  253. symbolSize: 50,
  254. label: {
  255. normal: {
  256. show: true,
  257. formatter: '{@value}',
  258. textStyle: {
  259. color: '#000',
  260. fontSize: 11
  261. }
  262. }
  263. },
  264. itemStyle: {
  265. normal: {
  266. color: '#174FBF'
  267. }
  268. },
  269. zlevel: 6,
  270. data: [
  271. ]
  272. }, {
  273. name: '红色停机设备异常点',
  274. type: 'effectScatter',
  275. coordinateSystem: 'geo',
  276. data: [
  277. ],
  278. showEffectOn: 'render',
  279. rippleEffect: {
  280. brushType: 'stroke'
  281. },
  282. hoverAnimation: true,
  283. label: {
  284. normal: {
  285. formatter: '{@value}',
  286. position: 'left',
  287. show: false
  288. }
  289. },
  290. itemStyle: {
  291. normal: {
  292. color: 'red',
  293. shadowBlur: 200,
  294. shadowColor: 'red'
  295. }
  296. },
  297. zlevel: 1
  298. }, {
  299. name: '红色停机设备异常点编号',
  300. type: 'scatter',
  301. coordinateSystem: 'geo',
  302. symbol: 'pin', // 气泡
  303. symbolSize: 50,
  304. label: {
  305. normal: {
  306. show: true,
  307. formatter: '{@value}',
  308. textStyle: {
  309. color: '#000',
  310. fontSize: 11
  311. }
  312. }
  313. },
  314. itemStyle: {
  315. normal: {
  316. color: 'red'
  317. }
  318. },
  319. zlevel: 6,
  320. data: [
  321. ]
  322. }, {
  323. name: '黄色报修中异常点',
  324. type: 'effectScatter',
  325. coordinateSystem: 'geo',
  326. data: [
  327. ],
  328. showEffectOn: 'render',
  329. rippleEffect: {
  330. brushType: 'stroke'
  331. },
  332. hoverAnimation: true,
  333. label: {
  334. normal: {
  335. formatter: '{@value}',
  336. position: 'left',
  337. show: false
  338. }
  339. },
  340. itemStyle: {
  341. normal: {
  342. color: 'yellow',
  343. shadowBlur: 200,
  344. shadowColor: 'yellow'
  345. }
  346. },
  347. zlevel: 1
  348. }, {
  349. name: '黄色报修中异常点编号',
  350. type: 'scatter',
  351. coordinateSystem: 'geo',
  352. symbol: 'pin', // 气泡
  353. symbolSize: 50,
  354. label: {
  355. normal: {
  356. show: true,
  357. formatter: '{@value}',
  358. textStyle: {
  359. color: '#000',
  360. fontSize: 11
  361. }
  362. }
  363. },
  364. itemStyle: {
  365. normal: {
  366. color: 'yellow'
  367. }
  368. },
  369. zlevel: 6,
  370. data: [
  371. ]
  372. }]
  373. }
  374. function getMapList() {
  375. $('#mapName').on('change', function (e) {
  376. mapName = e.delegateTarget.value
  377. img.src = mapList.find(item => item.targetId == mapName).url
  378. init()
  379. });
  380. $.ajax({
  381. type: "GET",
  382. url: "/api/upms/files?type=56",
  383. headers: getHeader(),
  384. success: function (res) {
  385. var data = res.data
  386. mapList = data
  387. for (var i = 0; i < data.length; i++) {
  388. var option = '<option value="' + data[i].targetId + '">' + data[i].targetId + '</option>'
  389. $('#mapName').append(option)
  390. }
  391. mapName = mapList[0].targetId
  392. img.src = mapList.find(item => item.targetId == mapName).url
  393. $('#mapName').val(mapName);
  394. }
  395. })
  396. }
  397. getMapList()
  398. //初始化加载数据,定时器只能在定时到了才发生
  399. getData();
  400. getScreenJobData();
  401. getScreenProblemData();
  402. // 使用刚指定的配置项和数据显示图表
  403. myChart.setOption(option);
  404. // 设备详情
  405. function getSbDetail(sbNo) {
  406. $.ajax({
  407. type: "get",
  408. url: "/api/sb/infos/no/" + sbNo,
  409. headers: getHeader(),
  410. success: function (response) {
  411. tooltip_message = '<div>设备编号:' + response.data.no +
  412. '<br/>设备名称:' + response.data.name +
  413. '<br/>设备型号:' + response.data.model +
  414. '<br/>使用位置:' + response.data.cph +
  415. '<br/>设备维修员:' + response.data.repairUserName + '</div>';
  416. },
  417. error: function (xhr, textStatus, errorThrown) {
  418. error(xhr, textStatus, errorThrown);
  419. }
  420. });
  421. }
  422. // 近期保养计划:设备、周期、计划日期
  423. function getScreenJobData() {
  424. $.ajax({
  425. type: "get",
  426. url: "/api/check/jobs/screen",
  427. headers: getHeader(),
  428. success: function (response) {
  429. let data = response.data || {};
  430. $('#monthNum').text((data.finishNum || 0) + "/" + (data.monthNum || 0));
  431. $('#waitNum').text("待完成:" + (data.waitNum || 0));
  432. $('#rate').text("完成率:" + (data.rate || 0) + "%");
  433. let planHtml = '';
  434. (data.planList || []).forEach(item => {
  435. planHtml += buildRow(item.sbName || item.sbNo, item.periodText || '', item.startTime,
  436. '设备编号:' + (item.sbNo || '') + ';计划完成:' + (item.endTime || ''));
  437. })
  438. renderMarquee('planList', planHtml);
  439. },
  440. error: function (xhr, textStatus, errorThrown) {
  441. error(xhr, textStatus, errorThrown);
  442. }
  443. });
  444. }
  445. // 当前问题设备:有进行中报修单(待接收/维修中/转派审批中/委外维修中)的设备
  446. function getScreenProblemData() {
  447. $.ajax({
  448. type: "get",
  449. url: "/api/sb/infos/screen/problem-list",
  450. headers: getHeader(),
  451. success: function (response) {
  452. let problemHtml = '';
  453. (response.data || []).forEach(item => {
  454. problemHtml += buildRow(item.no, item.name, item.repairStatusLabel,
  455. '报修单:' + (item.repairNo || '') + ';报修时间:' + formatTime(item.applyTime));
  456. })
  457. renderMarquee('problemList', problemHtml);
  458. },
  459. error: function (xhr, textStatus, errorThrown) {
  460. error(xhr, textStatus, errorThrown);
  461. }
  462. });
  463. }
  464. // 列表行:三列内容 + 鼠标悬停提示
  465. function buildRow(col1, col2, col3, tip) {
  466. return '<div class="item"' + (tip ? ' title="' + tip + '"' : '') + '>' +
  467. '<span>' + (col1 || '') + '</span>' +
  468. '<span>' + (col2 || '') + '</span>' +
  469. '<span>' + (col3 || '') + '</span>' +
  470. '</div>'
  471. }
  472. // 报修时间只展示到分钟,避免提示过长
  473. function formatTime(time) {
  474. if (!time) return ''
  475. return String(time).replace('T', ' ').substring(0, 16)
  476. }
  477. // 渲染滚动列表:刷新前先停掉上一轮动画并移除插件生成的节点,避免克隆节点与定时器叠加
  478. function renderMarquee(id, html) {
  479. const $box = $('#' + id)
  480. const moveId = $box.data('moveId')
  481. if (moveId) {
  482. clearTimeout(moveId)
  483. $box.data('moveId', null)
  484. }
  485. $box.find('.str_move').stop(true).remove()
  486. $box.empty().append(html || '')
  487. $box.liMarquee({
  488. direction: 'up',//向上滚动
  489. runshort: false,//内容不足时不滚动
  490. scrollamount: 20//速度
  491. })
  492. }
  493. // setInterval(mockData(), 10000);
  494. setInterval(function () { getData() }, 60000);
  495. // 右侧两个列表变化不频繁,5 分钟刷新一次
  496. setInterval(function () { getScreenJobData(); getScreenProblemData() }, 300000);
  497. function mockData() {
  498. var data = [
  499. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常'],
  500. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常'],
  501. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常'],
  502. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常'],
  503. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常'],
  504. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '正常']
  505. ];
  506. var data2 = [
  507. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道'],
  508. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道'],
  509. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道'],
  510. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道'],
  511. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道'],
  512. [Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000), '不知道', '不知道']
  513. ];
  514. option.series[0].data = data;// 正常数据
  515. option.series[1].data = data2;// 异常的红色内容名称
  516. option.series[2].data = data2;// 异常黄点
  517. console.log(data);
  518. console.log(data2);
  519. myChart.setOption(option, true)
  520. }
  521. function getData() {
  522. $.ajax({
  523. type: "get",
  524. url: "/api/sb/infos/screen",
  525. headers: getHeader(),
  526. success: function (response) {
  527. dataList = response.data;
  528. init()
  529. },
  530. error: function (xhr, textStatus, errorThrown) {
  531. error(xhr, textStatus, errorThrown);
  532. }
  533. });
  534. }
  535. function init() {
  536. data = dataList.find(item => item.mapName === mapName)
  537. const normalSbInfoList = [];
  538. const repairSbInfoListRed = [];
  539. const repairSbInfoListYellow = [];
  540. if (data.normalSbInfoList != null) {
  541. for (var i = 0; i < data.normalSbInfoList.length; i++) {
  542. normalSbInfoList.push([data.normalSbInfoList[i].zjm, data.normalSbInfoList[i].jbdh,
  543. data.normalSbInfoList[i].no])
  544. }
  545. }
  546. if (data.repairSbInfoListRed != null) {
  547. for (var i = 0; i < data.repairSbInfoListRed.length; i++) {
  548. repairSbInfoListRed.push([data.repairSbInfoListRed[i].zjm, data.repairSbInfoListRed[i].jbdh,
  549. data.repairSbInfoListRed[i].no])
  550. }
  551. }
  552. if (data.repairSbInfoListYellow != null) {
  553. for (var i = 0; i < data.repairSbInfoListYellow.length; i++) {
  554. repairSbInfoListYellow.push([data.repairSbInfoListYellow[i].zjm, data.repairSbInfoListYellow[i].jbdh,
  555. data.repairSbInfoListYellow[i].no])
  556. }
  557. }
  558. // 保存原始数据副本
  559. option._originalData = {
  560. normal: normalSbInfoList.slice(),
  561. red: repairSbInfoListRed.slice(),
  562. yellow: repairSbInfoListYellow.slice()
  563. };
  564. option.series[0].data = normalSbInfoList;// 正常数据:
  565. option.series[1].data = normalSbInfoList;// 正常数据的编号
  566. option.series[2].data = repairSbInfoListRed;// 停机设备 红色编号
  567. option.series[3].data = repairSbInfoListRed;// 停机设备 红色
  568. option.series[4].data = repairSbInfoListYellow;// 有进行中报修单 黃色编号
  569. option.series[5].data = repairSbInfoListYellow;// 有进行中报修单 黃色
  570. applyFilter();
  571. }
  572. function applyFilter() {
  573. const showNormal = $('#filterNormal').is(':checked');
  574. const showPlanned = $('#filterPlanned').is(':checked');
  575. const showStopped = $('#filterStopped').is(':checked');
  576. const orig = option._originalData || { normal: [], red: [], yellow: [] };
  577. option.series[0].data = showNormal ? orig.normal : [];
  578. option.series[1].data = showNormal ? orig.normal : [];
  579. option.series[2].data = showStopped ? orig.red : [];
  580. option.series[3].data = showStopped ? orig.red : [];
  581. option.series[4].data = showPlanned ? orig.yellow : [];
  582. option.series[5].data = showPlanned ? orig.yellow : [];
  583. myChart.setOption(option, true);
  584. }
  585. $('#filterNormal, #filterPlanned, #filterStopped').on('change', function () {
  586. applyFilter();
  587. });
  588. function getHeader() {
  589. return {
  590. Authorization: getToken(),
  591. "Cache-Control": "no-cache",
  592. accept: "application/json; charset=utf-8",
  593. 'Content-Type': 'application/json;charset=UTF-8'
  594. // accept: "application/json; charset=utf-8",
  595. // "Access-Control-Allow-Origin": "http://localhost:5000",
  596. // "Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept"
  597. }
  598. }
  599. function getToken() {
  600. const storeTokenJson = JSON.parse(localStorage.getItem(VUE_STORE_BASE + ACCESS_TOKEN))
  601. return 'Bearer ' + storeTokenJson.value
  602. }
  603. function goBack() {
  604. // 优先尝试让父级 Vue 路由跳转到首页
  605. try {
  606. if (window.parent && window.parent !== window) {
  607. // 通知父级 Vue 跳转首页
  608. window.parent.postMessage({ action: 'navigateHome' }, '*')
  609. // 作为兜底:让父级回退
  610. setTimeout(function () {
  611. if (window.parent.history.length > 1) {
  612. window.parent.history.back()
  613. }
  614. }, 100)
  615. } else {
  616. if (window.history.length > 1) {
  617. window.history.back()
  618. }
  619. }
  620. } catch (e) {
  621. if (window.history.length > 1) {
  622. window.history.back()
  623. }
  624. }
  625. }
  626. </script>
  627. </html>