work-order-seven-states-demo.html 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 Work Order Seven States 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(1280px,calc(100vw - 32px)); margin:28px auto; display:grid; gap:16px; }
  10. header, section { background:#0e1e2d; border:1px solid #21465f; padding:20px; }
  11. h1,h2,h3 { margin:0 0 10px; } h1 { font-size:22px; } h2 { font-size:17px; } h3 { font-size:15px; }
  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. .orders { display:grid; gap:12px; }
  18. .order { border:1px solid #275b74; background:#0b1922; padding:12px; }
  19. .order.alarm { border-color:#ff7a7a; } .order.warning { border-color:#f2c94c; }
  20. .title { display:flex; justify-content:space-between; gap:8px; align-items:flex-start; }
  21. .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. .active { color:#68d095; border-color:#68d095; } .cancelled { color:#ff7a7a; border-color:#ff7a7a; } .finished { color:#a9b7c0; border-color:#a9b7c0; }
  24. .timeline { margin-top:10px; display:grid; gap:8px; }
  25. .entry { border-left:3px solid #61c2ff; background:#0e1e2d; padding:8px 10px; }
  26. .entry.cancelled { border-left-color:#ff7a7a; } .entry.finished { border-left-color:#a9b7c0; }
  27. .meta { color:#8fb3c7; font-size:13px; margin:6px 0 0; overflow-wrap:anywhere; }
  28. .dictionary { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:8px; }
  29. .state { border:1px solid #275b74; background:#0b1922; padding:8px; } .state.cancelled { border-color:#ff7a7a; }
  30. .mono { font-family:Consolas,monospace; overflow-wrap:anywhere; } pre { max-height:340px; overflow:auto; background:#071019; border:1px solid #275b74; padding:12px; }
  31. .note { color:#8fb3c7; font-size:13px; }
  32. </style>
  33. </head>
  34. <body>
  35. <main>
  36. <header>
  37. <h1>驾驶舱事件关联工单七态演示</h1>
  38. <p class="endpoint">GET /api/cockpit/v1/equipments/{deviceCode}/work-orders</p>
  39. <p class="endpoint">GET /api/cockpit/v1/events/{eventSource}/{eventCode}/work-orders</p>
  40. <p class="endpoint">GET /api/cockpit/v1/work-orders/{workOrderCode}/timeline</p>
  41. </header>
  42. <section>
  43. <div class="toolbar">
  44. <button type="button" data-mode="all" class="active">七态总览</button>
  45. <button type="button" data-mode="multi">同设备多类型报警</button>
  46. </div>
  47. <div class="layout">
  48. <div>
  49. <h2>工单列表</h2>
  50. <div id="orders" class="orders"></div>
  51. </div>
  52. <div>
  53. <h2>状态字典</h2>
  54. <div id="dictionary" class="dictionary"></div>
  55. </div>
  56. </div>
  57. </section>
  58. <section>
  59. <h2>响应契约</h2>
  60. <p class="note">状态字典采用 ADR-0008 新七态:1 待派单、2 待接单、3 已接单、4 处理中、5 待验收、6 已结案、7 已取消;已废止状态不再输出。</p>
  61. <pre id="payload" class="mono"></pre>
  62. </section>
  63. </main>
  64. <script>
  65. const stateDictionary = [
  66. {code:1,name:'待派单'}, {code:2,name:'待接单'}, {code:3,name:'已接单'},
  67. {code:4,name:'处理中'}, {code:5,name:'待验收'}, {code:6,name:'已结案'}, {code:7,name:'已取消'}
  68. ];
  69. const summary = (code, state, source, eventCode, desc) => {
  70. const stateName = stateDictionary[state - 1].name;
  71. return {
  72. workOrderCode:code, deviceCode:'TC-PS-WS-202609020012', orderType:1, orderTypeName:'故障维修',
  73. orderLevel:2, orderDesc:desc,
  74. stateCode:state, stateName,
  75. createTime:'2026-09-02T09:00:00+08:00', updateTime:'2026-09-02T10:00:00+08:00',
  76. planFinishTime:'2026-09-03T09:00:00+08:00', realFinishTime:state === 6 ? '2026-09-02T10:00:00+08:00' : null,
  77. eventAssociation:{ eventSource:source, eventCode, associatedAt:'2026-09-02T10:00:00+08:00' }
  78. };
  79. };
  80. const allOrders = stateDictionary.map(state => summary(
  81. 'WO-202609020000' + state.code, state.code, 'ALARM', 'ALM-20260902-00' + state.code, '演示第 ' + state.code + ' 态'));
  82. const multiOrders = [
  83. summary('WO-2026090200101', 4, 'ALARM', 'ALM-WS_ENV_HIGH-001', '污水COD超标维修'),
  84. summary('WO-2026090200102', 5, 'ALARM', 'ALM-WS_ENV_PH-002', '污水pH越界维修')
  85. ];
  86. const timelines = {
  87. 'WO-2026090200101': ['创建:待派单','派单:待接单','开始处置:处理中'],
  88. 'WO-2026090200102': ['创建:待派单','派单:待接单','提交验收:待验收']
  89. };
  90. function badgeClass(order) {
  91. if (order.stateCode === 6) return 'finished';
  92. if (order.stateCode === 7) return 'cancelled';
  93. return 'active';
  94. }
  95. function renderOrder(order) {
  96. const timeline = timelines[order.workOrderCode] || ['创建:' + order.stateName];
  97. return `<article class="order ${order.eventAssociation.eventSource === 'ALARM' ? 'alarm' : 'warning'}">
  98. <div class="title">
  99. <div><h3>${order.orderDesc}</h3><div class="code">${order.workOrderCode}</div></div>
  100. <span class="badge ${badgeClass(order)}">${order.stateCode} ${order.stateName}</span>
  101. </div>
  102. <p class="meta">事件:${order.eventAssociation.eventSource} ${order.eventAssociation.eventCode}</p>
  103. <div class="timeline">${timeline.map(item => `<div class="entry ${badgeClass(order)}">${item}</div>`).join('')}</div>
  104. </article>`;
  105. }
  106. function render(mode) {
  107. document.querySelectorAll('button[data-mode]').forEach(button => button.classList.toggle('active', button.dataset.mode === mode));
  108. const orders = mode === 'all' ? allOrders : multiOrders;
  109. document.getElementById('orders').innerHTML = orders.map(renderOrder).join('');
  110. document.getElementById('dictionary').innerHTML = stateDictionary.map(state => `
  111. <div class="state ${state.code === 7 ? 'cancelled' : ''}"><strong>${state.code} ${state.name}</strong>
  112. <p class="meta">${state.code === 7 ? '终态' : '中间态'}</p>
  113. </div>`).join('');
  114. document.getElementById('payload').textContent = JSON.stringify({
  115. code:200, msg:'操作成功', data:{
  116. requestId:'work-order-demo', apiVersion:'v1', readOnly:true,
  117. deviceCode:'TC-PS-WS-202609020012', eventSource:mode === 'all' ? null : 'ALARM',
  118. eventCode:mode === 'all' ? null : 'WS_ENV_MULTI', state:'READY', stateReason:'READY',
  119. stateDictionary, workOrders:orders
  120. }
  121. }, null, 2);
  122. }
  123. document.querySelectorAll('button[data-mode]').forEach(button => button.addEventListener('click', () => render(button.dataset.mode)));
  124. render('all');
  125. </script>
  126. </body>
  127. </html>