| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>Cockpit Warning/Alarm Read Model Demo</title>
- <style>
- body { margin:0; min-height:100vh; background:#08131f; color:#e8f1f8; font:15px/1.5 "Microsoft YaHei",sans-serif; }
- main { width:min(1240px,calc(100vw - 32px)); margin:28px auto; display:grid; gap:16px; }
- header, section { background:#0e1e2d; border:1px solid #21465f; padding:20px; }
- h1,h2 { margin:0 0 10px; } h1 { font-size:22px; } h2 { font-size:17px; }
- .endpoint { color:#78a2b8; font-family:Consolas,monospace; margin:0; }
- .toolbar { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
- button { border:1px solid #2f7098; background:#123247; color:#dff2ff; padding:8px 14px; cursor:pointer; }
- button.active { outline:2px solid #61c2ff; }
- .layout { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:16px; align-items:start; }
- .family { border:1px solid #275b74; background:#0b1922; padding:14px; }
- .family.warning { border-color:#f2c94c; } .family.alarm { border-color:#ff7a7a; }
- .event { border:1px solid #2f7098; background:#0e1e2d; padding:12px; margin-top:10px; }
- .title { display:flex; justify-content:space-between; gap:8px; align-items:flex-start; }
- .name { font-weight:700; } .code { color:#78a2b8; font-family:Consolas,monospace; font-size:12px; overflow-wrap:anywhere; }
- .badge { font-size:12px; padding:3px 8px; border:1px solid; white-space:nowrap; }
- .RED { color:#ff7a7a; border-color:#ff7a7a; } .YELLOW { color:#f2c94c; border-color:#f2c94c; }
- .BLUE { color:#61c2ff; border-color:#61c2ff; } .GREEN { color:#68d095; border-color:#68d095; } .UNKNOWN { color:#a9b7c0; border-color:#a9b7c0; }
- .meta { margin:8px 0 0; color:#8fb3c7; font-size:13px; } .value { font-size:22px; font-weight:700; margin-top:6px; }
- .value small { font-size:13px; color:#8fb3c7; font-weight:400; margin-left:4px; }
- .relation { margin-top:12px; border:1px dashed #f2c94c; background:#101a24; padding:12px; }
- .empty { color:#a9b7c0; margin-top:10px; } .mono { font-family:Consolas,monospace; overflow-wrap:anywhere; }
- pre { max-height:340px; overflow:auto; background:#071019; border:1px solid #275b74; padding:12px; }
- .note { color:#8fb3c7; font-size:13px; }
- </style>
- </head>
- <body>
- <main>
- <header>
- <h1>驾驶舱预警 / 报警只读模型演示</h1>
- <p class="endpoint">GET /api/cockpit/v1/equipments/{deviceCode}/events/current</p>
- <p class="endpoint">GET /api/cockpit/v1/equipments/{deviceCode}/events/history?startTime=&endTime=&page=1&pageSize=100</p>
- </header>
- <section>
- <div class="toolbar">
- <button type="button" data-mode="warning" class="active">独立预警</button>
- <button type="button" data-mode="alarm">独立报警</button>
- <button type="button" data-mode="upgraded">预警升级为报警</button>
- </div>
- <div class="layout">
- <div id="warning-family" class="family warning">
- <h2>预警区域</h2><div id="warnings"></div>
- </div>
- <div id="alarm-family" class="family alarm">
- <h2>报警区域</h2><div id="alarms"></div>
- </div>
- </div>
- <div id="relation-panel"></div>
- </section>
- <section>
- <h2>响应契约</h2>
- <p class="note">预警与报警始终是两个事件族;alarmWarningRelations 只显示后端关联表给出的权威关系,前端不做真伪判断或时间戳推断。</p>
- <pre id="payload" class="mono"></pre>
- </section>
- </main>
- <script>
- const response = (warnings, alarms, relations) => ({
- code:200, msg:'操作成功', data:{
- requestId:'event-read-demo', apiVersion:'v1', readOnly:true,
- deviceCode:'TC-PS-WS-202609020012', deviceType:'ws_sewage_environment', deviceTypeName:'污水环境监测设备',
- state: warnings.length || alarms.length ? 'READY' : 'EMPTY', stateReason: warnings.length || alarms.length ? 'READY' : 'NO_EVENTS',
- warnings, alarms, alarmWarningRelations:relations, startTime:null, endTime:null,
- timezone:'Asia/Shanghai', page:1, pageSize:0, total:warnings.length + alarms.length
- }
- });
- const warning = (no, level, color, status, value, time) => ({ warningNo:no, warningName:'污水COD预警', warningType:'WS_ENV',
- level, levelColor:color, status, eventTime:time, value, unit:'mg/L', deviceCode:'TC-PS-WS-202609020012',
- deviceName:'污水环境监测设备', location:'太常片区' });
- const alarm = (id, level, color, status, value, time) => ({ alarmId:id, warningCode:'WS_ENV_HIGH', warningName:'污水COD报警',
- warningType:'WS_ENV', level, levelColor:color, status, eventTime:time, value, thresholdMinValue:40, thresholdMaxValue:50,
- unit:'mg/L', deviceCode:'TC-PS-WS-202609020012', deviceName:'污水环境监测设备', location:'太常片区' });
- const relation = (warningNo, alarmId) => ({ relationId:'REL-' + warningNo, warningNo, alarmId, relatedAt:'2026-09-02T10:00:00+08:00' });
- const responses = {
- warning: response([warning('WRN-20260902-001', 2, 'YELLOW', 'PROCESSING', 18.2, '2026-09-02T09:59:30+08:00')], [], []),
- alarm: response([], [alarm('ALM-20260902-001', 1, 'RED', 'UNHANDLED', 42, '2026-09-02T10:00:00+08:00')], []),
- upgraded: response(
- [warning('WRN-20260902-001', 2, 'YELLOW', 'PROCESSING', 18.2, '2026-09-02T09:59:30+08:00')],
- [alarm('ALM-20260902-001', 1, 'RED', 'UNHANDLED', 42, '2026-09-02T10:00:00+08:00')],
- [relation('WRN-20260902-001', 'ALM-20260902-001')])
- };
- function renderEvent(item, codeField) {
- return `<article class="event">
- <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>
- <div class="value">${item.value ?? '—'}${item.unit ? '<small>' + item.unit + '</small>' : ''}</div>
- <p class="meta">状态:${item.status}<br />时间:${item.eventTime}<br />设备:${item.deviceCode}</p>
- </article>`;
- }
- function render(mode) {
- document.querySelectorAll('button[data-mode]').forEach(button => button.classList.toggle('active', button.dataset.mode === mode));
- const payload = responses[mode], data = payload.data;
- document.getElementById('warnings').innerHTML = data.warnings.length
- ? data.warnings.map(item => renderEvent(item, 'warningNo')).join('')
- : '<p class="empty">本状态下没有预警事件。</p>';
- document.getElementById('alarms').innerHTML = data.alarms.length
- ? data.alarms.map(item => renderEvent(item, 'alarmId')).join('')
- : '<p class="empty">本状态下没有报警事件。</p>';
- document.getElementById('relation-panel').innerHTML = data.alarmWarningRelations.length
- ? data.alarmWarningRelations.map(item => `<div class="relation"><strong>权威关联:</strong> ${item.warningNo} ⇄ ${item.alarmId}<br /><span class="note">${item.relationId} · ${item.relatedAt}</span></div>`).join('')
- : '';
- document.getElementById('payload').textContent = JSON.stringify(payload, null, 2);
- }
- document.querySelectorAll('button[data-mode]').forEach(button => button.addEventListener('click', () => render(button.dataset.mode)));
- render('warning');
- </script>
- </body>
- </html>
|