| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>Cockpit API Foundation Demo</title>
- <style>
- body {
- margin: 0;
- min-height: 100vh;
- display: grid;
- place-items: center;
- background: #0b1622;
- color: #e8f1f8;
- font: 15px/1.5 "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
- }
- main {
- width: min(920px, calc(100vw - 32px));
- padding: 24px;
- border: 1px solid #21465f;
- background: #102030;
- }
- h1 {
- margin: 0 0 4px;
- font-size: 22px;
- }
- .endpoint {
- margin: 0 0 18px;
- color: #78a2b8;
- font-family: Consolas, monospace;
- }
- .toolbar {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- margin-bottom: 18px;
- }
- button {
- border: 1px solid #35708d;
- border-radius: 4px;
- padding: 7px 12px;
- color: #dff0fa;
- background: #173a4d;
- cursor: pointer;
- }
- button:hover {
- background: #1d4d67;
- }
- .state {
- display: none;
- border: 1px solid #2a5a75;
- padding: 16px;
- background: #0d1922;
- }
- .state.active {
- display: block;
- }
- .state h2 {
- margin: 0 0 10px;
- font-size: 18px;
- }
- .state.success h2 {
- color: #61d095;
- }
- .state.unauthorized h2 {
- color: #f0ad4e;
- }
- .state.server-error h2 {
- color: #ff6b6b;
- }
- dl {
- margin: 0 0 12px;
- display: grid;
- grid-template-columns: 140px 1fr;
- gap: 4px 12px;
- }
- dt {
- color: #8fb3c7;
- }
- dd {
- margin: 0;
- overflow-wrap: anywhere;
- }
- pre {
- margin: 0;
- padding: 12px;
- max-height: 320px;
- overflow: auto;
- background: #07111a;
- color: #c9e7f7;
- font-size: 13px;
- }
- </style>
- </head>
- <body>
- <main>
- <h1>城市生命线驾驶舱基础契约演示</h1>
- <p class="endpoint">GET /api/cockpit/v1/foundation</p>
- <div class="toolbar">
- <button type="button" data-mode="success">成功空地图</button>
- <button type="button" data-mode="unauthorized">未认证 401</button>
- <button type="button" data-mode="server-error">服务端错误 500</button>
- <button type="button" data-mode="live">请求真实接口</button>
- </div>
- <section id="state" class="state">
- <h2 id="state-title"></h2>
- <dl id="state-fields"></dl>
- <pre id="state-payload"></pre>
- </section>
- </main>
- <script>
- var states = {
- 'success': {
- title: '成功:空地图帧',
- className: 'state success',
- payload: {
- code: 200,
- msg: '操作成功',
- data: {
- requestId: 'demo-foundation-empty',
- apiVersion: 'v1',
- readOnly: true,
- mapFrame: { state: 'EMPTY', renderable: false, reason: '驾驶舱基础数据为空' },
- freshness: {
- status: 'FRESH',
- serverTime: '2026-09-02T10:00:00+08:00',
- dataTime: '2026-09-02T10:00:00+08:00'
- }
- }
- }
- },
- 'unauthorized': {
- title: '失败:驾驶舱身份未认证',
- className: 'state unauthorized',
- payload: {
- code: 401,
- msg: '驾驶舱接口需要认证',
- data: {
- category: 'UNAUTHORIZED',
- message: '驾驶舱接口需要认证',
- requestId: 'demo-foundation-unauthorized',
- serverTime: '2026-09-02T10:00:01+08:00'
- }
- }
- },
- 'server-error': {
- title: '失败:基础数据服务暂不可用',
- className: 'state server-error',
- payload: {
- code: 500,
- msg: '驾驶舱基础数据暂不可用',
- data: {
- category: 'SERVER_ERROR',
- message: '驾驶舱基础数据暂不可用',
- requestId: 'demo-foundation-server-error',
- serverTime: '2026-09-02T10:00:02+08:00'
- }
- }
- }
- };
- function render(mode, payload, title, className) {
- var section = document.getElementById('state');
- var response = payload || states[mode].payload;
- var fields = {
- 'HTTP 状态': String(response.code),
- '消息': response.msg,
- 'requestId': response.data && response.data.requestId,
- 'readOnly': response.data && response.data.readOnly,
- '地图帧': response.data && response.data.mapFrame && response.data.mapFrame.state,
- '数据新鲜度': response.data && response.data.freshness && response.data.freshness.status,
- '错误类型': response.data && response.data.category
- };
- var list = document.getElementById('state-fields');
- list.innerHTML = '';
- Object.keys(fields).forEach(function (key) {
- if (fields[key] === undefined || fields[key] === null) {
- return;
- }
- var term = document.createElement('dt');
- term.textContent = key;
- var value = document.createElement('dd');
- value.textContent = String(fields[key]);
- list.appendChild(term);
- list.appendChild(value);
- });
- document.getElementById('state-title').textContent =
- title || (states[mode] && states[mode].title) || '接口返回';
- section.className = className || (states[mode] && states[mode].className) || 'state';
- document.getElementById('state-payload').textContent = JSON.stringify(response, null, 2);
- section.classList.add('active');
- }
- function loadLive() {
- fetch('/api/cockpit/v1/foundation', { headers: { 'X-Request-Id': 'demo-foundation-live' } })
- .then(function (response) {
- return response.json().then(function (payload) {
- render('success', payload,
- response.ok ? '真实接口:' + payload.msg : '真实接口 HTTP ' + response.status,
- response.ok ? 'state success' : (response.status === 401 ? 'state unauthorized' : 'state server-error'));
- });
- })
- .catch(function (error) {
- render('server-error', {
- code: 500,
- msg: '驾驶舱基础数据暂不可用',
- data: {
- category: 'SERVER_ERROR',
- message: String(error),
- requestId: 'demo-foundation-live',
- serverTime: ''
- }
- });
- });
- }
- document.querySelectorAll('button[data-mode]').forEach(function (button) {
- button.addEventListener('click', function () {
- var mode = button.getAttribute('data-mode');
- if (mode === 'live') {
- loadLive();
- } else {
- render(mode);
- }
- });
- });
- var initialMode = location.search.indexOf('mode=unauthorized') >= 0 ? 'unauthorized'
- : location.search.indexOf('mode=server-error') >= 0 ? 'server-error'
- : location.search.indexOf('mode=live') >= 0 ? 'live'
- : 'success';
- if (initialMode === 'live') {
- loadLive();
- } else {
- render(initialMode);
- }
- </script>
- </body>
- </html>
|