event-read-demo.html 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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 Warning/Alarm Read Model 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(1240px,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. .layout { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:16px; align-items:start; }
  17. .family { border:1px solid #275b74; background:#0b1922; padding:14px; }
  18. .family.warning { border-color:#f2c94c; } .family.alarm { border-color:#ff7a7a; }
  19. .event { border:1px solid #2f7098; background:#0e1e2d; padding:12px; margin-top:10px; }
  20. .title { display:flex; justify-content:space-between; gap:8px; align-items:flex-start; }
  21. .name { font-weight:700; } .code { color:#78a2b8; font-family:Consolas,monospace; font-size:12px; overflow-wrap:anywhere; }
  22. .badge { font-size:12px; padding:3px 8px; border:1px solid; white-space:nowrap; }
  23. .RED { color:#ff7a7a; border-color:#ff7a7a; } .YELLOW { color:#f2c94c; border-color:#f2c94c; }
  24. .BLUE { color:#61c2ff; border-color:#61c2ff; } .GREEN { color:#68d095; border-color:#68d095; } .UNKNOWN { color:#a9b7c0; border-color:#a9b7c0; }
  25. .meta { margin:8px 0 0; color:#8fb3c7; font-size:13px; } .value { font-size:22px; font-weight:700; margin-top:6px; }
  26. .value small { font-size:13px; color:#8fb3c7; font-weight:400; margin-left:4px; }
  27. .relation { margin-top:12px; border:1px dashed #f2c94c; background:#101a24; padding:12px; }
  28. .empty { color:#a9b7c0; margin-top:10px; } .mono { font-family:Consolas,monospace; overflow-wrap:anywhere; }
  29. pre { max-height:340px; overflow:auto; background:#071019; border:1px solid #275b74; padding:12px; }
  30. .note { color:#8fb3c7; font-size:13px; }
  31. </style>
  32. </head>
  33. <body>
  34. <main>
  35. <header>
  36. <h1>驾驶舱预警 / 报警只读模型演示</h1>
  37. <p class="endpoint">GET /api/cockpit/v1/equipments/{deviceCode}/events/current</p>
  38. <p class="endpoint">GET /api/cockpit/v1/equipments/{deviceCode}/events/history?startTime=&amp;endTime=&amp;page=1&amp;pageSize=100</p>
  39. </header>
  40. <section>
  41. <div class="toolbar">
  42. <button type="button" data-mode="warning" class="active">独立预警</button>
  43. <button type="button" data-mode="alarm">独立报警</button>
  44. <button type="button" data-mode="upgraded">预警升级为报警</button>
  45. </div>
  46. <div class="layout">
  47. <div id="warning-family" class="family warning">
  48. <h2>预警区域</h2><div id="warnings"></div>
  49. </div>
  50. <div id="alarm-family" class="family alarm">
  51. <h2>报警区域</h2><div id="alarms"></div>
  52. </div>
  53. </div>
  54. <div id="relation-panel"></div>
  55. </section>
  56. <section>
  57. <h2>响应契约</h2>
  58. <p class="note">预警与报警始终是两个事件族;alarmWarningRelations 只显示后端关联表给出的权威关系,前端不做真伪判断或时间戳推断。</p>
  59. <pre id="payload" class="mono"></pre>
  60. </section>
  61. </main>
  62. <script>
  63. const response = (warnings, alarms, relations) => ({
  64. code:200, msg:'操作成功', data:{
  65. requestId:'event-read-demo', apiVersion:'v1', readOnly:true,
  66. deviceCode:'TC-PS-WS-202609020012', deviceType:'ws_sewage_environment', deviceTypeName:'污水环境监测设备',
  67. state: warnings.length || alarms.length ? 'READY' : 'EMPTY', stateReason: warnings.length || alarms.length ? 'READY' : 'NO_EVENTS',
  68. warnings, alarms, alarmWarningRelations:relations, startTime:null, endTime:null,
  69. timezone:'Asia/Shanghai', page:1, pageSize:0, total:warnings.length + alarms.length
  70. }
  71. });
  72. const warning = (no, level, color, status, value, time) => ({ warningNo:no, warningName:'污水COD预警', warningType:'WS_ENV',
  73. level, levelColor:color, status, eventTime:time, value, unit:'mg/L', deviceCode:'TC-PS-WS-202609020012',
  74. deviceName:'污水环境监测设备', location:'太常片区' });
  75. const alarm = (id, level, color, status, value, time) => ({ alarmId:id, warningCode:'WS_ENV_HIGH', warningName:'污水COD报警',
  76. warningType:'WS_ENV', level, levelColor:color, status, eventTime:time, value, thresholdMinValue:40, thresholdMaxValue:50,
  77. unit:'mg/L', deviceCode:'TC-PS-WS-202609020012', deviceName:'污水环境监测设备', location:'太常片区' });
  78. const relation = (warningNo, alarmId) => ({ relationId:'REL-' + warningNo, warningNo, alarmId, relatedAt:'2026-09-02T10:00:00+08:00' });
  79. const responses = {
  80. warning: response([warning('WRN-20260902-001', 2, 'YELLOW', 'PROCESSING', 18.2, '2026-09-02T09:59:30+08:00')], [], []),
  81. alarm: response([], [alarm('ALM-20260902-001', 1, 'RED', 'UNHANDLED', 42, '2026-09-02T10:00:00+08:00')], []),
  82. upgraded: response(
  83. [warning('WRN-20260902-001', 2, 'YELLOW', 'PROCESSING', 18.2, '2026-09-02T09:59:30+08:00')],
  84. [alarm('ALM-20260902-001', 1, 'RED', 'UNHANDLED', 42, '2026-09-02T10:00:00+08:00')],
  85. [relation('WRN-20260902-001', 'ALM-20260902-001')])
  86. };
  87. function renderEvent(item, codeField) {
  88. return `<article class="event">
  89. <div class="title"><div><div class="name">${item.warningName}</div><div class="code">${item[codeField]}</div></div><span class="badge ${item.levelColor}">${item.level} ${item.levelColor}</span></div>
  90. <div class="value">${item.value ?? '—'}${item.unit ? '<small>' + item.unit + '</small>' : ''}</div>
  91. <p class="meta">状态:${item.status}<br />时间:${item.eventTime}<br />设备:${item.deviceCode}</p>
  92. </article>`;
  93. }
  94. function render(mode) {
  95. document.querySelectorAll('button[data-mode]').forEach(button => button.classList.toggle('active', button.dataset.mode === mode));
  96. const payload = responses[mode], data = payload.data;
  97. document.getElementById('warnings').innerHTML = data.warnings.length
  98. ? data.warnings.map(item => renderEvent(item, 'warningNo')).join('')
  99. : '<p class="empty">本状态下没有预警事件。</p>';
  100. document.getElementById('alarms').innerHTML = data.alarms.length
  101. ? data.alarms.map(item => renderEvent(item, 'alarmId')).join('')
  102. : '<p class="empty">本状态下没有报警事件。</p>';
  103. document.getElementById('relation-panel').innerHTML = data.alarmWarningRelations.length
  104. ? data.alarmWarningRelations.map(item => `<div class="relation"><strong>权威关联:</strong> ${item.warningNo} ⇄ ${item.alarmId}<br /><span class="note">${item.relationId} · ${item.relatedAt}</span></div>`).join('')
  105. : '';
  106. document.getElementById('payload').textContent = JSON.stringify(payload, null, 2);
  107. }
  108. document.querySelectorAll('button[data-mode]').forEach(button => button.addEventListener('click', () => render(button.dataset.mode)));
  109. render('warning');
  110. </script>
  111. </body>
  112. </html>