monitoring-point-detail-demo.html 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <title>Cockpit Monitoring Point Detail Demo</title>
  7. <style>
  8. body { margin:0; min-height:100vh; background:#08131f; color:#e8f1f8; font:15px/1.5 "Microsoft YaHei",sans-serif; }
  9. main { width:min(1180px,calc(100vw - 32px)); margin:28px auto; display:grid; gap:16px; }
  10. header, section { background:#0e1e2d; border:1px solid #21465f; padding:20px; }
  11. h1,h2 { margin:0 0 10px; } h1 { font-size:22px; } h2 { font-size:17px; }
  12. .endpoint { color:#78a2b8; font-family:Consolas,monospace; margin:0; }
  13. .toolbar { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
  14. button { border:1px solid #2f7098; background:#123247; color:#dff2ff; padding:8px 14px; cursor:pointer; }
  15. button.active { outline:2px solid #61c2ff; }
  16. .summary { display:grid; grid-template-columns:repeat(6,1fr); gap:8px; margin-bottom:14px; }
  17. .summary div { background:#0b1922; border:1px solid #275b74; padding:10px; text-align:center; }
  18. .summary strong { display:block; font-size:20px; }
  19. .summary span { color:#8fb3c7; font-size:12px; }
  20. .status { display:inline-block; padding:3px 9px; border-radius:12px; background:#20415a; }
  21. .alarm { background:#8b2020; } .warning { background:#8a6510; } .work-order { background:#1f5e91; }
  22. .normal { background:#197343; } .offline { color:#a9b7c0; background:#38424a; } .empty { background:#48515a; }
  23. table { width:100%; border-collapse:collapse; }
  24. th,td { border:1px solid #275b74; padding:8px; text-align:left; }
  25. th { background:#123247; } td.mono { font-family:Consolas,monospace; }
  26. .reasons { margin:0; padding-left:18px; } .note { color:#8fb3c7; font-size:13px; }
  27. pre { max-height:260px; overflow:auto; background:#071019; border:1px solid #275b74; padding:12px; }
  28. </style>
  29. </head>
  30. <body>
  31. <main>
  32. <header>
  33. <h1>驾驶舱监测点状态与详情演示</h1>
  34. <p class="endpoint">GET /api/cockpit/v1/monitoring-points/{monitoringPointCode}</p>
  35. </header>
  36. <section>
  37. <div class="toolbar">
  38. <button type="button" data-mode="alarm" class="active">报警优先</button>
  39. <button type="button" data-mode="workOrder">工单处置</button>
  40. <button type="button" data-mode="offline">离线</button>
  41. <button type="button" data-mode="empty">无设备</button>
  42. <button type="button" data-mode="notFound">404</button>
  43. </div>
  44. <div id="detail">
  45. <div class="summary" id="statistics"></div>
  46. <h2>设备卡片(后端排序)</h2>
  47. <table>
  48. <thead><tr><th>设备编码</th><th>设备名称</th><th>类型</th><th>状态</th><th>状态时间</th></tr></thead>
  49. <tbody id="devices"></tbody>
  50. </table>
  51. </div>
  52. <div id="error" hidden>
  53. <h2 id="error-title"></h2>
  54. <pre id="error-payload"></pre>
  55. </div>
  56. </section>
  57. <section>
  58. <h2>状态原因与状态字典</h2>
  59. <p class="note">监测点状态由后端按 ALARM &gt; WARNING &gt; WORK_ORDER &gt; NORMAL &gt; OFFLINE &gt; EMPTY 解析。</p>
  60. <ul id="reasons" class="reasons"></ul>
  61. <pre id="payload"></pre>
  62. </section>
  63. </main>
  64. <script>
  65. var statusClass = {
  66. ALARM: 'alarm', WARNING: 'warning', WORK_ORDER: 'work-order',
  67. NORMAL: 'normal', OFFLINE: 'offline', EMPTY: 'empty'
  68. };
  69. var responses = {
  70. alarm: response('MP-001', '供水干管监测点', 'ALARM', [
  71. device('EQ-001', '供水压力计-001', '压力计', 'ALARM', '2026-09-02T09:59:00+08:00', ['压力超高']),
  72. device('EQ-003', '燃气浓度计-003', '气体检测仪', 'ALARM', '2026-09-02T09:59:10+08:00', ['浓度超限']),
  73. device('EQ-005', '污水环境监测仪-005', '环境监测仪', 'WARNING', '2026-09-02T09:59:20+08:00', ['氨氮波动']),
  74. device('EQ-002', '智能井盖-002', '智能井盖', 'NORMAL', '2026-09-02T09:59:30+08:00', []),
  75. device('EQ-004', '供水流量计-004', '流量计', 'OFFLINE', '2026-09-02T09:59:40+08:00', [])
  76. ]),
  77. workOrder: response('MP-WORK', '泵站监测点', 'WORK_ORDER', [
  78. device('EQ-WORK', '泵站振动监测器', '振动监测器', 'WORK_ORDER', '2026-09-02T09:58:00+08:00', ['维修处置中'])
  79. ]),
  80. offline: response('MP-OFFLINE', '排口监测点', 'OFFLINE', [
  81. device('EQ-OFFLINE', '污水液位计', '液位计', 'OFFLINE', '2026-09-02T09:57:00+08:00', ['连接超时'])
  82. ]),
  83. empty: response('MP-EMPTY', '预留监测点', 'EMPTY', []),
  84. notFound: {
  85. code: 404, msg: '监测点不存在',
  86. data: { category: 'NOT_FOUND', message: '监测点不存在', requestId: 'point-detail-demo-404', serverTime: '2026-09-02T10:00:00+08:00' }
  87. }
  88. };
  89. function device(code, name, type, status, time, reasons) {
  90. return { deviceCode: code, deviceName: name, deviceTypeName: type, deviceStatus: status,
  91. statusLabel: label(status), statusReasons: reasons, statusTime: time };
  92. }
  93. function label(status) {
  94. return { ALARM:'报警', WARNING:'预警', WORK_ORDER:'工单处置', NORMAL:'正常', OFFLINE:'离线', EMPTY:'无设备' }[status];
  95. }
  96. function response(code, name, status, devices) {
  97. return {
  98. code: 200, msg: '操作成功',
  99. data: {
  100. requestId: 'point-detail-demo-' + status.toLowerCase(), apiVersion: 'v1', readOnly: true,
  101. monitoringPointCode: code, monitoringPointName: name, pointStatus: status, statusLabel: label(status),
  102. statusReasons: devices.filter(function (item) { return item.deviceStatus === status; })
  103. .flatMap(function (item) { return item.statusReasons.map(function (reason) { return { status: status, deviceCode: item.deviceCode, reason: reason }; }); }),
  104. deviceStatistics: {
  105. totalDeviceCount: devices.length,
  106. alarmDeviceCount: count(devices, 'ALARM'), warningDeviceCount: count(devices, 'WARNING'),
  107. workOrderDeviceCount: count(devices, 'WORK_ORDER'), normalDeviceCount: count(devices, 'NORMAL'),
  108. offlineDeviceCount: count(devices, 'OFFLINE')
  109. },
  110. devices: devices,
  111. coordinateValidation: { state: 'VALID', reason: null, renderable: true, crs: 'WGS84' },
  112. statusDictionary: ['ALARM','WARNING','WORK_ORDER','NORMAL','OFFLINE','EMPTY'].map(function (item, index) {
  113. return { status: item, label: label(item), priority: index + 1 };
  114. })
  115. }
  116. };
  117. }
  118. function count(devices, status) { return devices.filter(function (item) { return item.deviceStatus === status; }).length; }
  119. function render(mode) {
  120. document.querySelectorAll('button[data-mode]').forEach(function (button) { button.classList.toggle('active', button.dataset.mode === mode); });
  121. var payload = responses[mode];
  122. var success = payload.code === 200;
  123. document.getElementById('detail').hidden = !success;
  124. document.getElementById('error').hidden = success;
  125. if (success) {
  126. var data = payload.data;
  127. var stats = data.deviceStatistics;
  128. document.getElementById('statistics').innerHTML = [
  129. ['总设备', stats.totalDeviceCount], ['报警', stats.alarmDeviceCount], ['预警', stats.warningDeviceCount],
  130. ['工单', stats.workOrderDeviceCount], ['正常', stats.normalDeviceCount], ['离线', stats.offlineDeviceCount]
  131. ].map(function (item) { return '<div><strong>' + item[1] + '</strong><span>' + item[0] + '</span></div>'; }).join('');
  132. document.getElementById('devices').innerHTML = data.devices.map(function (item) {
  133. return '<tr><td class="mono">' + item.deviceCode + '</td><td>' + item.deviceName + '</td><td>' + item.deviceTypeName +
  134. '</td><td><span class="status ' + statusClass[item.deviceStatus] + '">' + item.statusLabel + '</span></td><td class="mono">' + item.statusTime + '</td></tr>';
  135. }).join('');
  136. document.getElementById('reasons').innerHTML = data.statusReasons.length
  137. ? data.statusReasons.map(function (item) { return '<li><strong>' + item.deviceCode + '</strong>:' + item.reason + '</li>'; }).join('')
  138. : '<li>无对应状态原因</li>';
  139. } else {
  140. document.getElementById('error-title').textContent = 'HTTP ' + payload.code + ':' + payload.msg;
  141. document.getElementById('error-payload').textContent = JSON.stringify(payload, null, 2);
  142. }
  143. document.getElementById('payload').textContent = JSON.stringify(payload, null, 2);
  144. }
  145. document.querySelectorAll('button[data-mode]').forEach(function (button) {
  146. button.addEventListener('click', function () { render(button.dataset.mode); });
  147. });
  148. render('alarm');
  149. </script>
  150. </body>
  151. </html>