287 lines
23 KiB
HTML
287 lines
23 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block content %}
|
||
<section id="ksp-location-board" aria-label="太阳系地点板">
|
||
<header class="location-toolbar">
|
||
<nav class="location-breadcrumbs" id="location-breadcrumbs" aria-label="地图层级"></nav>
|
||
<div class="location-summary">
|
||
<label class="transfer-toggle" for="transfer-toggle">
|
||
<input id="transfer-toggle" type="checkbox">
|
||
<span>全部转移轨迹</span>
|
||
</label>
|
||
<span id="scope-count">0 assets</span>
|
||
<span class="live-status"><i></i>实时星历</span>
|
||
<button class="location-reset" id="location-reset" type="button">重置</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="location-layout">
|
||
<main class="location-map" id="location-map">
|
||
<svg id="space-map" viewBox="0 0 1000 620" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="map-title map-desc">
|
||
<title id="map-title">太阳系资产位置地图</title>
|
||
<desc id="map-desc">从太阳系全局钻取到行星系统、行星和卫星,并查看实际转移航路。</desc>
|
||
<g id="orbit-layer"></g>
|
||
<g id="route-layer"></g>
|
||
<g id="asset-layer"></g>
|
||
</svg>
|
||
<div class="body-layer" id="body-layer"></div>
|
||
<div class="map-caption"><span id="view-mode">SOLAR SYSTEM</span><b id="map-caption">选择天体或转移资产</b></div>
|
||
<div class="map-legend" aria-label="图例">
|
||
<span><i class="asset-dot"></i>资产位置</span>
|
||
<span><i class="progress-line"></i>已完成航程</span>
|
||
<span><i class="remaining-line"></i>剩余航程</span>
|
||
</div>
|
||
</main>
|
||
|
||
<aside class="asset-panel">
|
||
<div class="asset-panel-head">
|
||
<div><span>ASSETS IN SCOPE</span><h2 id="scope-title">Solar System</h2></div>
|
||
<b id="panel-count">0</b>
|
||
</div>
|
||
<div class="asset-list" id="asset-list"></div>
|
||
</aside>
|
||
|
||
<aside class="asset-drawer" id="asset-drawer" aria-hidden="true">
|
||
<button class="drawer-close" id="drawer-close" type="button" aria-label="关闭详情">×</button>
|
||
<span class="drawer-eyebrow">ASSET DETAIL</span>
|
||
<h2 id="drawer-name"></h2>
|
||
<div class="drawer-fields" id="drawer-fields"></div>
|
||
<a class="drawer-link" id="drawer-link" href="#">打开资产详情页</a>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
|
||
<script id="location-assets-data" type="application/json">{{ asset_positions|tojson }}</script>
|
||
|
||
<style>
|
||
#ksp-location-board{position:absolute;inset:0;display:grid;grid-template-rows:auto minmax(0,1fr);overflow:hidden;background:var(--bg);color:var(--text);font-size:clamp(12px,.28vw + 8px,22px)}
|
||
#ksp-location-board button,#ksp-location-board a{font:inherit}
|
||
.location-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:48px;padding:8px clamp(14px,1vw,30px);border-bottom:1px solid var(--border);background:var(--surface)}
|
||
.location-breadcrumbs,.location-summary{display:flex;align-items:center;gap:9px;min-width:0}
|
||
.location-breadcrumbs button{padding:0;border:0;background:transparent;color:var(--muted);cursor:pointer}
|
||
.location-breadcrumbs button.current{color:var(--text);font-weight:600}.location-breadcrumbs .sep{color:var(--muted)}
|
||
.location-summary{color:var(--muted);white-space:nowrap}.live-status{display:flex;align-items:center;gap:5px}.live-status i{width:7px;height:7px;border-radius:50%;background:var(--amber)}
|
||
.transfer-toggle{display:flex;align-items:center;gap:7px;cursor:pointer;color:var(--text)}.transfer-toggle input{accent-color:var(--accent)}
|
||
.location-reset{padding:4px 9px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--muted);cursor:pointer}.location-reset:hover{color:var(--text);background:var(--surface2)}
|
||
.location-layout{position:relative;display:grid;grid-template-columns:minmax(0,1fr) clamp(270px,20vw,540px);min-width:0;min-height:0}
|
||
.location-map{position:relative;min-width:0;min-height:0;overflow:hidden;background:radial-gradient(circle at 50% 50%,rgba(59,130,246,.09),transparent 44%),var(--bg)}
|
||
#space-map{display:block;width:100%;height:100%}.body-layer{position:absolute;inset:0;pointer-events:none}
|
||
.asset-panel{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;padding:clamp(14px,1vw,28px);border-left:1px solid var(--border);background:var(--surface)}
|
||
.asset-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding-bottom:10px;border-bottom:1px solid var(--border)}
|
||
.asset-panel-head span,.drawer-eyebrow{color:var(--muted);font-family:var(--font-data);font-size:.7em;letter-spacing:.08em}.asset-panel-head h2{margin-top:4px;font-size:1.2em}.asset-panel-head b{padding:4px 8px;background:rgba(59,130,246,.12);color:var(--accent);font-family:var(--font-data)}
|
||
.asset-list{min-height:0;overflow-y:auto}.asset-button{display:flex;align-items:center;gap:9px;width:100%;padding:clamp(9px,.55vw,18px) 3px;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);text-align:left;cursor:pointer}.asset-button::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--amber);flex:0 0 auto}.asset-button.planned::before{background:var(--purple)}.asset-button:hover,.asset-button.selected{background:rgba(59,130,246,.08)}
|
||
.asset-empty{padding:18px 2px;color:var(--muted)}
|
||
.body-button{--planet-size:16px;position:absolute;transform:translate(-50%,-50%);display:block;width:var(--planet-size);height:var(--planet-size);padding:0;border:0;background:transparent;color:var(--text);pointer-events:auto;cursor:pointer;overflow:visible}
|
||
.planet-disc{display:block;width:100%;height:100%;border:1px solid var(--muted);border-radius:50%;background:var(--surface2)}.body-button.sun .planet-disc{border-color:var(--text);background:#74b4ec;box-shadow:0 0 26px rgba(116,180,236,.42)}.body-button.minor .planet-disc{border-style:dashed;background:rgba(148,163,184,.12)}.body-button:hover .planet-disc,.body-button:focus-visible .planet-disc{outline:2px solid var(--accent);outline-offset:3px}
|
||
.body-label{position:absolute;top:calc(100% + 5px);left:50%;transform:translateX(-50%);color:var(--muted);white-space:nowrap;pointer-events:none}
|
||
.map-caption{position:absolute;top:16px;left:18px;display:flex;align-items:center;gap:9px;max-width:70%;pointer-events:none}.map-caption span{padding:5px 8px;background:rgba(59,130,246,.12);color:#7ab8f6;font-family:var(--font-data)}.map-caption b{font-weight:500}
|
||
.map-legend{position:absolute;bottom:16px;left:18px;display:flex;gap:14px;padding:8px 10px;border:1px solid var(--border);background:rgba(19,19,24,.88);color:var(--text)}.map-legend span{display:flex;align-items:center;gap:6px}.asset-dot{width:7px;height:7px;border-radius:50%;background:var(--amber)}.progress-line,.remaining-line{display:inline-block;width:24px;border-top:2px solid #72b4f4}.remaining-line{border-top:1px dashed var(--muted)}
|
||
.orbit{fill:none;stroke:rgba(148,163,184,.18);stroke-width:1.1}.orbit.strong{stroke:rgba(248,250,252,.48);stroke-width:1.35}.orbit.minor{stroke-dasharray:3 7;opacity:.65}
|
||
.transfer-track{fill:none;stroke:rgba(148,163,184,.38);stroke-width:1.25;stroke-dasharray:4 8}.transfer-progress{fill:none;stroke:#72b4f4;stroke-width:2.25}.route-endpoint{fill:var(--bg);stroke:#72b4f4;stroke-width:1.6}.route-vessel-dot{fill:var(--bg);stroke:#72b4f4;stroke-width:2.5}.route-vessel-heading{stroke:#72b4f4;stroke-width:2;stroke-linecap:round}.route-label-leader{stroke:var(--muted);stroke-width:1;opacity:.7}.route-label-title,.route-label-meta{font-family:var(--font-ui);paint-order:stroke;stroke:var(--bg);stroke-linejoin:round}.route-label-title{fill:var(--text);font-size:12px;font-weight:600;stroke-width:3}.route-label-meta{fill:var(--muted);font-size:10px;stroke-width:3}.route-group.hidden{display:none}
|
||
.map-asset{fill:var(--amber);stroke:var(--bg);stroke-width:2}.map-asset-label{fill:var(--text);font-size:11px;font-family:var(--font-ui);paint-order:stroke;stroke:var(--bg);stroke-width:3;stroke-linejoin:round}
|
||
.asset-drawer{--panel-width:clamp(270px,20vw,540px);position:absolute;z-index:8;top:0;right:var(--panel-width);bottom:0;width:clamp(320px,23vw,620px);display:flex;flex-direction:column;gap:clamp(13px,.8vw,24px);padding:clamp(18px,1.2vw,36px);border-left:1px solid var(--border);background:var(--surface2);transform:translateX(calc(100% + var(--panel-width)));transition:transform .18s ease;pointer-events:none}.asset-drawer.open{transform:translateX(0);pointer-events:auto}.drawer-close{align-self:flex-end;width:30px;height:30px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--muted);font-size:20px;cursor:pointer}.asset-drawer h2{font-size:1.4em}.drawer-fields{display:grid;gap:10px}.drawer-field{padding-top:9px;border-top:1px solid var(--border)}.drawer-field span,.drawer-field strong{display:block}.drawer-field span{margin-bottom:3px;color:var(--muted);font-size:.78em}.drawer-field strong{font-weight:500;overflow-wrap:anywhere}.drawer-link{margin-top:auto;padding:9px 12px;border-radius:4px;background:var(--accent);color:#fff;text-align:center}.drawer-link:hover{text-decoration:none;filter:brightness(1.08)}
|
||
@media(max-width:900px){.location-layout{grid-template-columns:minmax(0,1fr) 230px}.asset-drawer{--panel-width:230px;right:230px;width:300px}.live-status{display:none}.map-legend{display:none}}
|
||
@media(max-width:680px){.location-toolbar{align-items:flex-start;flex-direction:column}.location-summary{width:100%;overflow-x:auto}.location-layout{grid-template-columns:minmax(0,1fr) 180px}.asset-drawer{--panel-width:180px;right:180px;width:280px}.map-caption{max-width:90%}}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
(() => {
|
||
const root = document.getElementById('ksp-location-board');
|
||
const assets = JSON.parse(document.getElementById('location-assets-data').textContent);
|
||
const ns = 'http://www.w3.org/2000/svg';
|
||
const layers = {
|
||
orbit: root.querySelector('#orbit-layer'),
|
||
route: root.querySelector('#route-layer'),
|
||
asset: root.querySelector('#asset-layer')
|
||
};
|
||
const systems = {
|
||
mercury:{name:'Mercury',moons:[]}, venus:{name:'Venus',moons:[]}, earth:{name:'Earth',moons:['moon']},
|
||
mars:{name:'Mars',moons:['phobos','deimos']}, ceres:{name:'Ceres',moons:[]}, vesta:{name:'Vesta',moons:[]},
|
||
jupiter:{name:'Jupiter',moons:['io','europa','ganymede','callisto']},
|
||
saturn:{name:'Saturn',moons:['mimas','enceladus','tethys','dione','rhea','titan','iapetus']},
|
||
uranus:{name:'Uranus',moons:['miranda','ariel','umbriel','titania','oberon']},
|
||
neptune:{name:'Neptune',moons:['triton']}, pluto:{name:'Pluto',moons:['charon']}
|
||
};
|
||
const bodyNames = {sun:'Sun',mercury:'Mercury',venus:'Venus',earth:'Earth',moon:'Moon',mars:'Mars',phobos:'Phobos',deimos:'Deimos',ceres:'Ceres',vesta:'Vesta',jupiter:'Jupiter',io:'Io',europa:'Europa',ganymede:'Ganymede',callisto:'Callisto',saturn:'Saturn',mimas:'Mimas',enceladus:'Enceladus',tethys:'Tethys',dione:'Dione',rhea:'Rhea',titan:'Titan',iapetus:'Iapetus',uranus:'Uranus',miranda:'Miranda',ariel:'Ariel',umbriel:'Umbriel',titania:'Titania',oberon:'Oberon',neptune:'Neptune',triton:'Triton',pluto:'Pluto',charon:'Charon',unknown:'Unknown'};
|
||
const moonSystems = {moon:'earth',phobos:'mars',deimos:'mars',io:'jupiter',europa:'jupiter',ganymede:'jupiter',callisto:'jupiter',mimas:'saturn',enceladus:'saturn',tethys:'saturn',dione:'saturn',rhea:'saturn',titan:'saturn',iapetus:'saturn',miranda:'uranus',ariel:'uranus',umbriel:'uranus',titania:'uranus',oberon:'uranus',triton:'neptune',charon:'pluto'};
|
||
const planets = [
|
||
{id:'mercury',angle:-35,size:9,orbit:50},{id:'venus',angle:60,size:12,orbit:78},{id:'earth',angle:-22,size:13,orbit:110},{id:'mars',angle:125,size:11,orbit:140},
|
||
{id:'vesta',angle:-135,size:7,orbit:160,kind:'minor'},{id:'ceres',angle:150,size:8,orbit:176,kind:'minor'},{id:'jupiter',angle:32,size:25,orbit:215},
|
||
{id:'saturn',angle:210,size:22,orbit:260},{id:'uranus',angle:-28,size:17,orbit:310},{id:'neptune',angle:145,size:17,orbit:360},{id:'pluto',angle:210,size:10,orbit:420,kind:'minor'}
|
||
];
|
||
let view = {level:'solar',system:null,body:null};
|
||
let selectedRoute = null;
|
||
let showAllRoutes = false;
|
||
let bodyPoints = {};
|
||
|
||
function svgEl(tag, attrs, text) {
|
||
const node = document.createElementNS(ns, tag);
|
||
Object.entries(attrs || {}).forEach(([key, value]) => node.setAttribute(key, String(value)));
|
||
if (text !== undefined) node.textContent = text;
|
||
return node;
|
||
}
|
||
function clearMap() {
|
||
Object.values(layers).forEach(layer => layer.replaceChildren());
|
||
root.querySelector('#body-layer').replaceChildren();
|
||
bodyPoints = {};
|
||
}
|
||
function orbit(cx, cy, rx, ry, strong=false, kind='') {
|
||
layers.orbit.append(svgEl('ellipse', {cx,cy,rx,ry,class:'orbit '+(strong?'strong ':'')+(kind==='minor'?'minor':'')}));
|
||
}
|
||
function planetPoint(planet) {
|
||
const angle = planet.angle * Math.PI / 180;
|
||
return {x:500 + Math.cos(angle) * planet.orbit, y:310 + Math.sin(angle) * planet.orbit * .6};
|
||
}
|
||
function mapPoint(x,y) {
|
||
const stage = root.querySelector('#location-map');
|
||
const width = stage.clientWidth || 1000;
|
||
const height = stage.clientHeight || 620;
|
||
const scale = Math.min(width/1000,height/620);
|
||
return {x:(width-1000*scale)/2+x*scale,y:(height-620*scale)/2+y*scale};
|
||
}
|
||
function bodyButton(id, name, x, y, size, sun=false, kind='') {
|
||
bodyPoints[id] = {x,y};
|
||
const point = mapPoint(x,y);
|
||
const button = document.createElement('button');
|
||
button.type = 'button';
|
||
button.className = 'body-button '+(sun?'sun ':'')+(kind==='minor'?'minor':'');
|
||
button.style.left = point.x+'px';
|
||
button.style.top = point.y+'px';
|
||
button.style.setProperty('--planet-size','clamp('+size+'px,'+(size/10).toFixed(2)+'vw,'+(size*2)+'px)');
|
||
button.setAttribute('aria-label','打开 '+name);
|
||
const disc = document.createElement('span'); disc.className = 'planet-disc';
|
||
const label = document.createElement('span'); label.className = 'body-label'; label.textContent = name;
|
||
button.append(disc,label);
|
||
button.addEventListener('click',()=>openBody(id));
|
||
root.querySelector('#body-layer').append(button);
|
||
}
|
||
function systemForBody(body) { return moonSystems[body] || body; }
|
||
function visibleTransfer(asset) {
|
||
if (!asset.transfer) return false;
|
||
if (view.level === 'system') return asset.scope === view.system;
|
||
return view.level === 'solar' || view.system === 'sun' ? asset.scope === 'sun' : false;
|
||
}
|
||
function transferPoint(body) {
|
||
const system = systemForBody(body);
|
||
return bodyPoints[body] || bodyPoints[system] || null;
|
||
}
|
||
function drawTransfer(asset, index) {
|
||
if (!visibleTransfer(asset)) return;
|
||
const from = transferPoint(asset.transfer.from);
|
||
const to = transferPoint(asset.transfer.to);
|
||
if (!from || !to) return;
|
||
const bend = index % 2 ? -1 : 1;
|
||
const mx = (from.x + to.x) / 2;
|
||
const my = (from.y + to.y) / 2;
|
||
const dx = to.x-from.x, dy = to.y-from.y;
|
||
const length = Math.max(1,Math.hypot(dx,dy));
|
||
const offset = Math.min(85,length*.24)*bend;
|
||
const cx = mx-dy/length*offset, cy = my+dx/length*offset;
|
||
const d = `M${from.x} ${from.y} Q${cx} ${cy} ${to.x} ${to.y}`;
|
||
const group = svgEl('g',{class:'route-group'+((showAllRoutes||selectedRoute===asset.id)?'':' hidden')});
|
||
layers.route.append(group);
|
||
const track = svgEl('path',{d,class:'transfer-track',pathLength:100}); group.append(track);
|
||
group.append(svgEl('path',{d,class:'transfer-progress',pathLength:100,'stroke-dasharray':asset.transfer.progress+' '+(100-asset.transfer.progress)}));
|
||
const total = track.getTotalLength();
|
||
const start = track.getPointAtLength(0), end = track.getPointAtLength(total);
|
||
const point = track.getPointAtLength(total*asset.transfer.progress/100);
|
||
const before = track.getPointAtLength(Math.max(0,total*asset.transfer.progress/100-2));
|
||
const angle = Math.atan2(point.y-before.y,point.x-before.x)*180/Math.PI;
|
||
group.append(svgEl('circle',{cx:start.x,cy:start.y,r:4,class:'route-endpoint'}));
|
||
group.append(svgEl('circle',{cx:end.x,cy:end.y,r:4,class:'route-endpoint'}));
|
||
const vessel = svgEl('g',{transform:`translate(${point.x} ${point.y}) rotate(${angle})`});
|
||
vessel.append(svgEl('circle',{cx:0,cy:0,r:6,class:'route-vessel-dot'}));
|
||
vessel.append(svgEl('line',{x1:5,y1:0,x2:15,y2:0,class:'route-vessel-heading'}));
|
||
group.append(vessel);
|
||
const side = point.x > 760 ? -1 : 1;
|
||
const labelX = point.x+side*24, labelY = point.y>340?point.y-20:point.y+27;
|
||
const anchor = side<0?'end':'start';
|
||
group.append(svgEl('line',{x1:point.x+side*8,y1:point.y-2,x2:labelX-side*4,y2:labelY-5,class:'route-label-leader'}));
|
||
group.append(svgEl('text',{x:labelX,y:labelY,'text-anchor':anchor,class:'route-label-title'},asset.name));
|
||
group.append(svgEl('text',{x:labelX,y:labelY+14,'text-anchor':anchor,class:'route-label-meta'},`${bodyNames[asset.transfer.from]||asset.transfer.from} → ${bodyNames[asset.transfer.to]||asset.transfer.to} · ${asset.transfer.progress}%`));
|
||
}
|
||
function drawAsset(x,y,label) {
|
||
layers.asset.append(svgEl('circle',{cx:x,cy:y,r:5,class:'map-asset'}));
|
||
layers.asset.append(svgEl('text',{x:x+10,y:y+4,class:'map-asset-label'},label));
|
||
}
|
||
function drawSolar() {
|
||
planets.forEach(p=>orbit(500,310,p.orbit,p.orbit*.6,p.id==='earth'||p.id==='mars',p.kind));
|
||
bodyButton('sun','Sun',500,310,34,true);
|
||
planets.forEach(p=>{const point=planetPoint(p);bodyButton(p.id,systems[p.id].name,point.x,point.y,p.size,false,p.kind)});
|
||
currentAssets().filter(a=>!a.transfer&&a.scope==='sun').slice(0,12).forEach((a,i)=>{const angle=.55+i*1.15;const radius=48+(i%4)*28;drawAsset(500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.6,a.name)});
|
||
assets.filter(a=>a.transfer).forEach(drawTransfer);
|
||
}
|
||
function drawSystem(systemId) {
|
||
const system = systems[systemId]; if (!system) return;
|
||
bodyButton(systemId,system.name,500,310,48);
|
||
const step = system.moons.length>5?42:system.moons.length>3?48:58;
|
||
system.moons.forEach((moon,index)=>{const radius=105+index*step;orbit(500,310,radius,radius*.7,index===0);const angle=.7+index*1.45;bodyButton(moon,bodyNames[moon],500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.7,12+(index%3)*2)});
|
||
assets.filter(a=>a.transfer).forEach(drawTransfer);
|
||
}
|
||
function drawLocal(bodyId) {
|
||
bodyButton(bodyId,systems[bodyId]?.name||bodyNames[bodyId]||bodyId,500,310,68,bodyId==='sun');
|
||
orbit(500,310,115,72,true); orbit(500,310,180,112); orbit(500,310,255,158);
|
||
currentAssets().filter(a=>!a.transfer).slice(0,20).forEach((a,index)=>{const angle=.4+index*.78;const radius=115+(index%3)*65;drawAsset(500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.62,a.name)});
|
||
}
|
||
function openBody(id) {
|
||
selectedRoute = null;
|
||
if (view.level==='solar') view = id==='sun'?{level:'local',system:'sun',body:'sun'}:{level:'system',system:id,body:null};
|
||
else if (view.level==='system') view = {level:'local',system:view.system,body:id};
|
||
else if (view.system==='sun'&&id!=='sun') view = {level:'system',system:id,body:null};
|
||
render();
|
||
}
|
||
function currentAssets() {
|
||
if (view.level==='solar') return assets;
|
||
if (view.system==='sun') return assets.filter(a=>a.scope==='sun');
|
||
if (view.level==='system') return assets.filter(a=>a.scope===view.system);
|
||
return assets.filter(a=>a.body===view.body);
|
||
}
|
||
function scopeName() {
|
||
if (view.level==='solar') return 'Solar System';
|
||
if (view.system==='sun') return 'Heliocentric Assets';
|
||
if (view.level==='system') return systems[view.system].name+' System';
|
||
return systems[view.body]?.name||bodyNames[view.body]||view.body;
|
||
}
|
||
function renderBreadcrumbs() {
|
||
const wrap = root.querySelector('#location-breadcrumbs'); wrap.replaceChildren();
|
||
const items = [{label:'Solar System',action:()=>{view={level:'solar',system:null,body:null};selectedRoute=null;render()}}];
|
||
if (view.level!=='solar'&&view.system!=='sun') items.push({label:systems[view.system].name+' System',action:()=>{view={level:'system',system:view.system,body:null};selectedRoute=null;render()}});
|
||
if (view.system==='sun') items.push({label:'Heliocentric Assets'}); else if (view.level==='local') items.push({label:scopeName()});
|
||
items.forEach((item,index)=>{if(index){const sep=document.createElement('span');sep.className='sep';sep.textContent='›';wrap.append(sep)}const button=document.createElement('button');button.type='button';button.textContent=item.label;button.className=index===items.length-1?'current':'';if(item.action)button.addEventListener('click',item.action);wrap.append(button)});
|
||
}
|
||
function renderList() {
|
||
const rows = currentAssets();
|
||
root.querySelector('#scope-title').textContent=scopeName(); root.querySelector('#scope-count').textContent=rows.length+' assets'; root.querySelector('#panel-count').textContent=rows.length;
|
||
const list=root.querySelector('#asset-list'); list.replaceChildren();
|
||
if(!rows.length){const empty=document.createElement('div');empty.className='asset-empty';empty.textContent='当前范围内没有资产';list.append(empty);return}
|
||
rows.forEach(asset=>{const button=document.createElement('button');button.type='button';button.className='asset-button '+(String(asset.state).toLowerCase().includes('planned')?'planned ':'')+(selectedRoute===asset.id?'selected':'');button.textContent=asset.name;button.addEventListener('click',()=>{selectedRoute=visibleTransfer(asset)?asset.id:null;render();openDrawer(asset)});list.append(button)});
|
||
}
|
||
function openDrawer(asset) {
|
||
root.querySelector('#drawer-name').textContent=asset.name;
|
||
const fields=root.querySelector('#drawer-fields');fields.replaceChildren();
|
||
[['类型',asset.asset_type],['状态',asset.state],['位置',asset.location],['任务',asset.mission],['下一事件',asset.next_event]].forEach(([label,value])=>{const row=document.createElement('div');row.className='drawer-field';const key=document.createElement('span');key.textContent=label;const val=document.createElement('strong');val.textContent=value||'—';row.append(key,val);fields.append(row)});
|
||
root.querySelector('#drawer-link').href=asset.detail_url;
|
||
root.querySelector('#asset-drawer').classList.add('open'); root.querySelector('#asset-drawer').setAttribute('aria-hidden','false');
|
||
}
|
||
function closeDrawer(){root.querySelector('#asset-drawer').classList.remove('open');root.querySelector('#asset-drawer').setAttribute('aria-hidden','true')}
|
||
function render() {
|
||
closeDrawer(); clearMap();
|
||
if(view.level==='solar'||view.system==='sun')drawSolar();else if(view.level==='system')drawSystem(view.system);else drawLocal(view.body);
|
||
const hasRoutes=currentAssets().some(visibleTransfer);
|
||
const selected=assets.find(a=>a.id===selectedRoute);
|
||
root.querySelector('#view-mode').textContent=view.level==='solar'?'SOLAR SYSTEM':view.system==='sun'?'HELIOCENTRIC':view.level==='system'?'PLANETARY SYSTEM':'LOCAL ORBITS';
|
||
root.querySelector('#map-caption').textContent=selected?'已选择转移轨迹 · '+selected.name:showAllRoutes&&hasRoutes?'正在显示全部转移轨迹':view.level==='system'?'选择行星、卫星或转移资产':'选择天体或转移资产';
|
||
const toggle=root.querySelector('#transfer-toggle');toggle.checked=showAllRoutes&&hasRoutes;toggle.disabled=!hasRoutes;
|
||
renderBreadcrumbs();renderList();
|
||
}
|
||
root.querySelector('#drawer-close').addEventListener('click',closeDrawer);
|
||
root.querySelector('#transfer-toggle').addEventListener('change',event=>{showAllRoutes=event.target.checked;render()});
|
||
root.querySelector('#location-reset').addEventListener('click',()=>{view={level:'solar',system:null,body:null};selectedRoute=null;showAllRoutes=false;render()});
|
||
let resizeFrame=0;window.addEventListener('resize',()=>{cancelAnimationFrame(resizeFrame);resizeFrame=requestAnimationFrame(render)});
|
||
render();
|
||
})();
|
||
</script>
|
||
{% endblock %}
|