|
|
@@ -0,0 +1,259 @@
|
|
|
+<!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>
|