foundation-demo.html 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 API Foundation Demo</title>
  7. <style>
  8. body {
  9. margin: 0;
  10. min-height: 100vh;
  11. display: grid;
  12. place-items: center;
  13. background: #0b1622;
  14. color: #e8f1f8;
  15. font: 15px/1.5 "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
  16. }
  17. main {
  18. width: min(920px, calc(100vw - 32px));
  19. padding: 24px;
  20. border: 1px solid #21465f;
  21. background: #102030;
  22. }
  23. h1 {
  24. margin: 0 0 4px;
  25. font-size: 22px;
  26. }
  27. .endpoint {
  28. margin: 0 0 18px;
  29. color: #78a2b8;
  30. font-family: Consolas, monospace;
  31. }
  32. .toolbar {
  33. display: flex;
  34. gap: 8px;
  35. flex-wrap: wrap;
  36. margin-bottom: 18px;
  37. }
  38. button {
  39. border: 1px solid #35708d;
  40. border-radius: 4px;
  41. padding: 7px 12px;
  42. color: #dff0fa;
  43. background: #173a4d;
  44. cursor: pointer;
  45. }
  46. button:hover {
  47. background: #1d4d67;
  48. }
  49. .state {
  50. display: none;
  51. border: 1px solid #2a5a75;
  52. padding: 16px;
  53. background: #0d1922;
  54. }
  55. .state.active {
  56. display: block;
  57. }
  58. .state h2 {
  59. margin: 0 0 10px;
  60. font-size: 18px;
  61. }
  62. .state.success h2 {
  63. color: #61d095;
  64. }
  65. .state.unauthorized h2 {
  66. color: #f0ad4e;
  67. }
  68. .state.server-error h2 {
  69. color: #ff6b6b;
  70. }
  71. dl {
  72. margin: 0 0 12px;
  73. display: grid;
  74. grid-template-columns: 140px 1fr;
  75. gap: 4px 12px;
  76. }
  77. dt {
  78. color: #8fb3c7;
  79. }
  80. dd {
  81. margin: 0;
  82. overflow-wrap: anywhere;
  83. }
  84. pre {
  85. margin: 0;
  86. padding: 12px;
  87. max-height: 320px;
  88. overflow: auto;
  89. background: #07111a;
  90. color: #c9e7f7;
  91. font-size: 13px;
  92. }
  93. </style>
  94. </head>
  95. <body>
  96. <main>
  97. <h1>城市生命线驾驶舱基础契约演示</h1>
  98. <p class="endpoint">GET /api/cockpit/v1/foundation</p>
  99. <div class="toolbar">
  100. <button type="button" data-mode="success">成功空地图</button>
  101. <button type="button" data-mode="unauthorized">未认证 401</button>
  102. <button type="button" data-mode="server-error">服务端错误 500</button>
  103. <button type="button" data-mode="live">请求真实接口</button>
  104. </div>
  105. <section id="state" class="state">
  106. <h2 id="state-title"></h2>
  107. <dl id="state-fields"></dl>
  108. <pre id="state-payload"></pre>
  109. </section>
  110. </main>
  111. <script>
  112. var states = {
  113. 'success': {
  114. title: '成功:空地图帧',
  115. className: 'state success',
  116. payload: {
  117. code: 200,
  118. msg: '操作成功',
  119. data: {
  120. requestId: 'demo-foundation-empty',
  121. apiVersion: 'v1',
  122. readOnly: true,
  123. mapFrame: { state: 'EMPTY', renderable: false, reason: '驾驶舱基础数据为空' },
  124. freshness: {
  125. status: 'FRESH',
  126. serverTime: '2026-09-02T10:00:00+08:00',
  127. dataTime: '2026-09-02T10:00:00+08:00'
  128. }
  129. }
  130. }
  131. },
  132. 'unauthorized': {
  133. title: '失败:驾驶舱身份未认证',
  134. className: 'state unauthorized',
  135. payload: {
  136. code: 401,
  137. msg: '驾驶舱接口需要认证',
  138. data: {
  139. category: 'UNAUTHORIZED',
  140. message: '驾驶舱接口需要认证',
  141. requestId: 'demo-foundation-unauthorized',
  142. serverTime: '2026-09-02T10:00:01+08:00'
  143. }
  144. }
  145. },
  146. 'server-error': {
  147. title: '失败:基础数据服务暂不可用',
  148. className: 'state server-error',
  149. payload: {
  150. code: 500,
  151. msg: '驾驶舱基础数据暂不可用',
  152. data: {
  153. category: 'SERVER_ERROR',
  154. message: '驾驶舱基础数据暂不可用',
  155. requestId: 'demo-foundation-server-error',
  156. serverTime: '2026-09-02T10:00:02+08:00'
  157. }
  158. }
  159. }
  160. };
  161. function render(mode, payload, title, className) {
  162. var section = document.getElementById('state');
  163. var response = payload || states[mode].payload;
  164. var fields = {
  165. 'HTTP 状态': String(response.code),
  166. '消息': response.msg,
  167. 'requestId': response.data && response.data.requestId,
  168. 'readOnly': response.data && response.data.readOnly,
  169. '地图帧': response.data && response.data.mapFrame && response.data.mapFrame.state,
  170. '数据新鲜度': response.data && response.data.freshness && response.data.freshness.status,
  171. '错误类型': response.data && response.data.category
  172. };
  173. var list = document.getElementById('state-fields');
  174. list.innerHTML = '';
  175. Object.keys(fields).forEach(function (key) {
  176. if (fields[key] === undefined || fields[key] === null) {
  177. return;
  178. }
  179. var term = document.createElement('dt');
  180. term.textContent = key;
  181. var value = document.createElement('dd');
  182. value.textContent = String(fields[key]);
  183. list.appendChild(term);
  184. list.appendChild(value);
  185. });
  186. document.getElementById('state-title').textContent =
  187. title || (states[mode] && states[mode].title) || '接口返回';
  188. section.className = className || (states[mode] && states[mode].className) || 'state';
  189. document.getElementById('state-payload').textContent = JSON.stringify(response, null, 2);
  190. section.classList.add('active');
  191. }
  192. function loadLive() {
  193. fetch('/api/cockpit/v1/foundation', { headers: { 'X-Request-Id': 'demo-foundation-live' } })
  194. .then(function (response) {
  195. return response.json().then(function (payload) {
  196. render('success', payload,
  197. response.ok ? '真实接口:' + payload.msg : '真实接口 HTTP ' + response.status,
  198. response.ok ? 'state success' : (response.status === 401 ? 'state unauthorized' : 'state server-error'));
  199. });
  200. })
  201. .catch(function (error) {
  202. render('server-error', {
  203. code: 500,
  204. msg: '驾驶舱基础数据暂不可用',
  205. data: {
  206. category: 'SERVER_ERROR',
  207. message: String(error),
  208. requestId: 'demo-foundation-live',
  209. serverTime: ''
  210. }
  211. });
  212. });
  213. }
  214. document.querySelectorAll('button[data-mode]').forEach(function (button) {
  215. button.addEventListener('click', function () {
  216. var mode = button.getAttribute('data-mode');
  217. if (mode === 'live') {
  218. loadLive();
  219. } else {
  220. render(mode);
  221. }
  222. });
  223. });
  224. var initialMode = location.search.indexOf('mode=unauthorized') >= 0 ? 'unauthorized'
  225. : location.search.indexOf('mode=server-error') >= 0 ? 'server-error'
  226. : location.search.indexOf('mode=live') >= 0 ? 'live'
  227. : 'success';
  228. if (initialMode === 'live') {
  229. loadLive();
  230. } else {
  231. render(initialMode);
  232. }
  233. </script>
  234. </body>
  235. </html>