| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>Cockpit Map Snapshot Demo</title>
- <style>
- body { margin:0; min-height:100vh; background:#08131f; color:#e8f1f8; font:15px/1.5 "Microsoft YaHei",sans-serif; }
- main { width:min(1220px,calc(100vw - 32px)); margin:28px auto; display:grid; gap:16px; }
- header, section { background:#0e1e2d; border:1px solid #21465f; padding:20px; }
- h1 { margin:0 0 6px; font-size:22px; } .endpoint { color:#78a2b8; font-family:Consolas,monospace; margin:0; }
- .layout { display:grid; grid-template-columns:minmax(0,2fr) minmax(260px,1fr); gap:16px; }
- .toolbar { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:14px; }
- button { border:1px solid #2f7098; background:#123247; color:#dff2ff; padding:8px 14px; cursor:pointer; }
- button:disabled { opacity:.45; cursor:not-allowed; }
- .map { position:relative; height:480px; overflow:hidden; background:linear-gradient(135deg,#0b1a28,#102b3b); border:1px solid #275b74; }
- .map::before { content:""; position:absolute; inset:0; background-image:linear-gradient(rgba(60,130,160,.12) 1px,transparent 1px),linear-gradient(90deg,rgba(60,130,160,.12) 1px,transparent 1px); background-size:48px 48px; }
- .marker { position:absolute; width:26px; height:26px; border-radius:50%; display:grid; place-items:center; font-size:11px; font-weight:700; border:2px solid rgba(255,255,255,.75); transform:translate(-50%,-50%); box-shadow:0 0 14px currentColor; }
- .point { background:#20a35d; color:#052; } .device { background:#3d87d8; color:#021c33; border-radius:6px; }
- .marker span { position:absolute; top:30px; left:50%; transform:translateX(-50%); white-space:nowrap; color:#cfe8f6; text-shadow:0 1px 3px #000; }
- .stats { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-bottom:12px; }
- .card { background:#0b1922; border:1px solid #275b74; padding:10px; } .card strong { display:block; font-size:20px; } .card span { color:#8fb3c7; }
- table { width:100%; border-collapse:collapse; } th,td { border:1px solid #275b74; padding:8px; text-align:left; } th { background:#123247; }
- .invalid { color:#f0ad4e; } .valid { color:#68d095; } .note { color:#8fb3c7; font-size:13px; }
- </style>
- </head>
- <body>
- <main>
- <header>
- <h1>驾驶舱地图快照分页演示</h1>
- <p class="endpoint">GET /api/cockpit/v1/map-snapshot?page=1&pageSize=2</p>
- </header>
- <div class="layout">
- <section>
- <div class="toolbar">
- <button id="load">开始加载第 1 页</button>
- <button id="reset">重新开始</button>
- <span id="status" class="note">地图框架已就绪,等待分页数据。</span>
- </div>
- <div class="stats">
- <div class="card"><strong id="loaded-count">0</strong><span>已加载对象</span></div>
- <div class="card"><strong id="renderable-count">0</strong><span>可渲染对象</span></div>
- <div class="card"><strong id="blocked-count">0</strong><span>坐标禁渲染</span></div>
- <div class="card"><strong id="complete">否</strong><span>加载完成</span></div>
- </div>
- <div id="map" class="map" aria-label="一张图地图框架"></div>
- </section>
- <section>
- <h2>不可渲染对象</h2>
- <table>
- <thead><tr><th>编码</th><th>状态</th><th>原因</th></tr></thead>
- <tbody id="blocked"></tbody>
- </table>
- <p class="note">已关联设备的点位由监测点承载,不重复生成独立设备点位。</p>
- </section>
- </div>
- </main>
- <script>
- const snapshotAt='2026-09-02T10:00:00+08:00';
- const pages=[
- {page:1,pageSize:2,snapshotAt,total:7,batchUpdatedAt:snapshotAt,complete:false,monitoringPoints:[
- {pointCode:'MP-001',pointName:'供水干管监测点',pointStatus:'NORMAL',statusReasons:[],longitude:110.141,latitude:28.452,coordinateValidation:{state:'VALID',reason:null,renderable:true,crs:'WGS84'}},
- {pointCode:'MP-002',pointName:'燃气调压站监测点',pointStatus:'ALARM',statusReasons:['EQ-ASSOC-001 报警'],longitude:110.142,latitude:28.453,coordinateValidation:{state:'VALID',reason:null,renderable:true,crs:'WGS84'}}
- ],unassociatedEquipment:[]},
- {page:2,pageSize:2,snapshotAt,total:7,batchUpdatedAt:snapshotAt,complete:false,monitoringPoints:[
- {pointCode:'MP-003',pointName:'污水排口监测点',pointStatus:'WARNING',statusReasons:['EQ-FREE-003 预警'],longitude:110.143,latitude:28.454,coordinateValidation:{state:'UNVERIFIED',reason:'UNVERIFIED_COORDINATE',renderable:false,crs:'WGS84'}}
- ],unassociatedEquipment:[
- {deviceCode:'EQ-FREE-001',deviceName:'供水压力计-001',longitude:181,latitude:28.455,coordinateValidation:{state:'INVALID',reason:'LONGITUDE_OUT_OF_RANGE',renderable:false,crs:'WGS84'}}
- ]},
- {page:3,pageSize:2,snapshotAt,total:7,batchUpdatedAt:snapshotAt,complete:false,monitoringPoints:[],unassociatedEquipment:[
- {deviceCode:'EQ-FREE-002',deviceName:'智能井盖-002',longitude:110.145,latitude:28.456,coordinateValidation:{state:'VALID',reason:null,renderable:true,crs:'WGS84'}},
- {deviceCode:'EQ-FREE-003',deviceName:'污水环境监测仪-003',longitude:110.146,latitude:28.457,coordinateValidation:{state:'UNVERIFIED',reason:'UNVERIFIED_COORDINATE',renderable:false,crs:'WGS84'}}
- ]},
- {page:4,pageSize:2,snapshotAt,total:7,batchUpdatedAt:snapshotAt,complete:true,monitoringPoints:[],unassociatedEquipment:[
- {deviceCode:'EQ-FREE-004',deviceName:'零坐标设备-004',longitude:0,latitude:0,coordinateValidation:{state:'INVALID',reason:'ZERO_COORDINATE',renderable:false,crs:'WGS84'}}
- ]}
- ];
- let pageIndex=0, loaded=0, renderable=0, blocked=0;
- const map=document.getElementById('map'), statusText=document.getElementById('status'), loadButton=document.getElementById('load');
- function percent(value,min,max){ return ((value-min)/(max-min)*82+9).toFixed(2)+'%'; }
- function marker(item,isPoint){
- const marker=document.createElement('div');
- marker.className='marker '+(isPoint?'point':'device');
- marker.style.left=percent(item.longitude,110.135,110.152);
- marker.style.top=percent(item.latitude,28.445,28.462);
- marker.textContent=isPoint?'点':'设';
- const label=document.createElement('span'); label.textContent=isPoint?item.pointName:item.deviceName; marker.appendChild(label);
- map.appendChild(marker);
- }
- function blockedRow(item,isPoint){
- const row=document.createElement('tr'); row.className='invalid';
- row.innerHTML=`<td>${isPoint?item.pointCode:item.deviceCode}</td><td>${item.coordinateValidation.state}</td><td>${item.coordinateValidation.reason}</td>`;
- document.getElementById('blocked').appendChild(row);
- }
- function loadPage(){
- if(pageIndex>=pages.length) return;
- const page=pages[pageIndex++];
- page.monitoringPoints.forEach(item=>add(item,true));
- page.unassociatedEquipment.forEach(item=>add(item,false));
- loaded+=page.monitoringPoints.length+page.unassociatedEquipment.length;
- document.getElementById('loaded-count').textContent=loaded;
- document.getElementById('renderable-count').textContent=renderable;
- document.getElementById('blocked-count').textContent=blocked;
- document.getElementById('complete').textContent=page.complete?'是':'否';
- statusText.textContent=page.complete?`快照 ${snapshotAt} 已加载完成。`:`已加载第 ${page.page} 页,快照 ${snapshotAt} 保持一致。`;
- loadButton.disabled=page.complete;
- loadButton.textContent=page.complete?'加载完成':`加载第 ${page.page+1} 页`;
- }
- function add(item,isPoint){
- if(item.coordinateValidation.renderable){ marker(item,isPoint); renderable++; }
- else { blockedRow(item,isPoint); blocked++; }
- }
- loadButton.addEventListener('click',loadPage);
- document.getElementById('reset').addEventListener('click',()=>{
- pageIndex=0; loaded=0; renderable=0; blocked=0;
- map.querySelectorAll('.marker').forEach(x=>x.remove());
- document.getElementById('blocked').innerHTML='';
- document.getElementById('loaded-count').textContent='0'; document.getElementById('renderable-count').textContent='0';
- document.getElementById('blocked-count').textContent='0'; document.getElementById('complete').textContent='否';
- statusText.textContent='地图框架已就绪,等待分页数据。'; loadButton.disabled=false; loadButton.textContent='开始加载第 1 页';
- });
- </script>
- </body>
- </html>
|