Skip to content

Commit 7a866e4

Browse files
committed
refactor: extract operation, visualization, and interpretation panels into dedicated web components
- Replaced inline operation panel HTML with <rn-operation-panel> custom element - Replaced inline visualization panel HTML with <rn-visualization-panel> custom element - Replaced inline interpretation panel HTML with <rn-interpretation-panel> custom element - Moved all panel-specific HTML structure, controls, and UI elements into respective component files - Reduced index.html from ~600 lines to minimal
1 parent 228514e commit 7a866e4

12 files changed

Lines changed: 718 additions & 627 deletions

index.html

Lines changed: 3 additions & 553 deletions
Large diffs are not rendered by default.

src/core/workerManager.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ class WorkerManager {
164164
}
165165
break;
166166

167+
case 'WORKER_READY':
168+
// Worker 初始化完成 / 模块加载成功的通知,仅用于调试
169+
console.log('🧩 Worker reported ready:', e.data && e.data.message);
170+
break;
171+
167172
default:
168173
console.warn('Unknown message type from worker:', type);
169174
}

src/css/layout.css

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
overflow: hidden;
4040
}
4141

42+
/* 可视化面板根组件:作为 flex 容器撑满中栏高度 */
43+
.visualization-panel > rn-visualization-panel {
44+
display: flex;
45+
flex-direction: column;
46+
flex: 1 1 auto;
47+
min-height: 0;
48+
}
49+
4250
/* 右栏:解读面板 (Interpretation Panel) */
4351
.interpretation-panel {
4452
flex: 0 0 320px;
@@ -108,7 +116,9 @@
108116
}
109117

110118
/* 去除底部留白,画布可贴近底部 */
111-
.container { padding-bottom: 0; }
119+
.container {
120+
padding-bottom: 0;
121+
}
112122

113123
/* 显示移动端Tab */
114124
#mobile-tabs {
@@ -135,7 +145,7 @@
135145
cursor: pointer;
136146
}
137147

138-
#mobile-tabs .tab-btn[aria-selected="true"] {
148+
#mobile-tabs .tab-btn[aria-selected='true'] {
139149
background: var(--primary-color);
140150
color: var(--text-white);
141151
border-color: var(--primary-color);

src/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import exportManager from './utils/exportManager.js';
1414
import UIManager from './managers/UIManager.js';
1515
import AppEventManager from './managers/AppEventManager.js';
1616
import GenerationManager from './managers/GenerationManager.js';
17+
import './ui/components/OperationPanelElement.js';
18+
import './ui/components/VisualizationPanelElement.js';
19+
import './ui/components/InterpretationPanelElement.js';
1720

1821
// #TODO: 添加错误边界处理
1922
// #TODO: 添加性能监控
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export function zoomInCamera(renderer) {
2+
if (!renderer || !renderer.camera || !renderer.controls) return;
3+
renderer.camera.position.multiplyScalar(0.8);
4+
renderer.controls.update();
5+
}
6+
7+
export function zoomOutCamera(renderer) {
8+
if (!renderer || !renderer.camera || !renderer.controls) return;
9+
renderer.camera.position.multiplyScalar(1.2);
10+
renderer.controls.update();
11+
}
12+
13+
export function resetViewCamera(renderer) {
14+
if (!renderer || !renderer.sceneManager) return;
15+
renderer.sceneManager.setCameraPosition(100, 100, 100);
16+
renderer.sceneManager.setControlsTarget(0, 0, 0);
17+
}
18+
19+
export function getViewportRectForRenderer(renderer) {
20+
if (!renderer || !renderer.camera || !renderer.roadNetData) {
21+
return { x: 0, y: 0, width: 100, height: 100 };
22+
}
23+
24+
const aspect = renderer.camera.aspect;
25+
const fov = (renderer.camera.fov * Math.PI) / 180;
26+
const distance = renderer.camera.position.length();
27+
const target = renderer.controls.target;
28+
const height = 2 * Math.tan(fov / 2) * distance;
29+
const width = height * aspect;
30+
31+
return {
32+
x: target.x - width / 2,
33+
y: target.z - height / 2,
34+
width,
35+
height,
36+
};
37+
}
38+
39+
export function centerOnWorld(renderer, worldX, worldY) {
40+
if (!renderer || !renderer.sceneManager) return;
41+
42+
const centerX = (renderer.roadNetData?.metadata.width || 100) / 2;
43+
const centerY = (renderer.roadNetData?.metadata.height || 100) / 2;
44+
renderer.sceneManager.setControlsTarget(
45+
worldX - centerX,
46+
0,
47+
worldY - centerY,
48+
);
49+
window.dispatchEvent(new CustomEvent('renderer-viewport-changed'));
50+
}

src/renderer3d/managers/Renderer3DViewController.js

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,6 @@
11
// 相机、视口与楼层可见性控制逻辑
22

3-
export function zoomInCamera(renderer) {
4-
if (!renderer || !renderer.camera || !renderer.controls) return;
5-
renderer.camera.position.multiplyScalar(0.8);
6-
renderer.controls.update();
7-
}
8-
9-
export function zoomOutCamera(renderer) {
10-
if (!renderer || !renderer.camera || !renderer.controls) return;
11-
renderer.camera.position.multiplyScalar(1.2);
12-
renderer.controls.update();
13-
}
14-
15-
export function resetViewCamera(renderer) {
16-
if (!renderer || !renderer.sceneManager) return;
17-
renderer.sceneManager.setCameraPosition(100, 100, 100);
18-
renderer.sceneManager.setControlsTarget(0, 0, 0);
19-
}
20-
21-
export function getViewportRectForRenderer(renderer) {
22-
if (!renderer || !renderer.camera || !renderer.roadNetData) {
23-
return { x: 0, y: 0, width: 100, height: 100 };
24-
}
25-
26-
const aspect = renderer.camera.aspect;
27-
const fov = (renderer.camera.fov * Math.PI) / 180;
28-
const distance = renderer.camera.position.length();
29-
const target = renderer.controls.target;
30-
const height = 2 * Math.tan(fov / 2) * distance;
31-
const width = height * aspect;
32-
33-
return {
34-
x: target.x - width / 2,
35-
y: target.z - height / 2,
36-
width,
37-
height,
38-
};
39-
}
40-
41-
export function centerOnWorld(renderer, worldX, worldY) {
42-
if (!renderer || !renderer.sceneManager) return;
43-
44-
const centerX = (renderer.roadNetData?.metadata.width || 100) / 2;
45-
const centerY = (renderer.roadNetData?.metadata.height || 100) / 2;
46-
renderer.sceneManager.setControlsTarget(
47-
worldX - centerX,
48-
0,
49-
worldY - centerY,
50-
);
51-
window.dispatchEvent(new CustomEvent('renderer-viewport-changed'));
52-
}
3+
export * from './Renderer3DCameraController.js';
534

545
export function showLayerInScene(renderer, index) {
556
if (!renderer || !renderer.scene) return;
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
class InterpretationPanelElement extends HTMLElement {
2+
constructor() {
3+
super();
4+
this.initialized = false;
5+
}
6+
7+
connectedCallback() {
8+
if (this.initialized) return;
9+
this.initialized = true;
10+
this.render();
11+
}
12+
13+
render() {
14+
this.innerHTML = `
15+
<div class="interpretation-wrapper">
16+
<div class="panel-header">
17+
<h2>数据解读</h2>
18+
<p class="panel-subtitle">Interpretation</p>
19+
</div>
20+
21+
<div class="interpretation-card" id="layer-control-card">
22+
<div class="card-header-row">
23+
<h3>图层控制</h3>
24+
</div>
25+
<div id="layer-control-section" style="padding: 12px 0">
26+
</div>
27+
</div>
28+
29+
<div class="interpretation-card" id="legend-card">
30+
<h3>图例</h3>
31+
<div class="legend-grid">
32+
<div class="legend-item-new" data-layer="obstacles">
33+
<button
34+
class="legend-eye"
35+
aria-pressed="true"
36+
title="显示/隐藏"
37+
></button>
38+
<span
39+
class="legend-icon obstacle-icon"
40+
aria-hidden="true"
41+
></span>
42+
<span class="legend-label">障碍物</span>
43+
</div>
44+
<div class="legend-item-new" data-layer="networkNodes">
45+
<button
46+
class="legend-eye"
47+
aria-pressed="true"
48+
title="显示/隐藏"
49+
></button>
50+
<span class="legend-icon node-icon" aria-hidden="true"></span>
51+
<span class="legend-label">网络节点</span>
52+
</div>
53+
<div class="legend-item-new" data-layer="networkEdges">
54+
<button
55+
class="legend-eye"
56+
aria-pressed="true"
57+
title="显示/隐藏"
58+
></button>
59+
<span class="legend-icon edge-icon" aria-hidden="true"></span>
60+
<span class="legend-label">网络边</span>
61+
</div>
62+
<div class="legend-item-new" data-layer="baseTriangulation">
63+
<button
64+
class="legend-eye"
65+
aria-pressed="true"
66+
title="显示/隐藏"
67+
></button>
68+
<span class="legend-icon base-icon" aria-hidden="true"></span>
69+
<span class="legend-label">基础三角化</span>
70+
</div>
71+
<div class="legend-item-new" data-layer="voronoi">
72+
<button
73+
class="legend-eye"
74+
aria-pressed="true"
75+
title="显示/隐藏"
76+
></button>
77+
<span
78+
class="legend-icon voronoi-icon"
79+
aria-hidden="true"
80+
></span>
81+
<span class="legend-label">Voronoi 骨架</span>
82+
</div>
83+
<div class="legend-sep"></div>
84+
<div class="legend-item-new-wrap">
85+
<div class="legend-item-new" aria-hidden="true">
86+
<span class="legend-icon start-icon"></span>
87+
<span class="legend-label">起点</span>
88+
</div>
89+
<div class="legend-item-new" aria-hidden="true">
90+
<span class="legend-icon end-icon"></span>
91+
<span class="legend-label">终点</span>
92+
</div>
93+
<div class="legend-item-new" aria-hidden="true">
94+
<span class="legend-icon path-icon"></span>
95+
<span class="legend-label">路径</span>
96+
</div>
97+
</div>
98+
</div>
99+
</div>
100+
101+
<div class="interpretation-card" id="path-card">
102+
<div class="card-header-row">
103+
<h3>路径统计</h3>
104+
<div class="card-actions">
105+
<button
106+
id="path-refresh-btn"
107+
class="btn-secondary btn-compact"
108+
title="刷新上次路径"
109+
>
110+
刷新
111+
</button>
112+
<button
113+
id="path-clear-btn"
114+
class="btn-secondary btn-compact"
115+
title="清空当前路径"
116+
>
117+
清空
118+
</button>
119+
<button
120+
id="path-collapse-btn"
121+
class="btn-secondary btn-icon"
122+
title="折叠/展开"
123+
aria-expanded="true"
124+
>
125+
126+
</button>
127+
</div>
128+
</div>
129+
<div id="path-stats" class="stats-grid">
130+
<div class="stat-item">
131+
<span class="stat-label">当前路径</span>
132+
<span class="stat-value" id="path-status">未选择</span>
133+
</div>
134+
<div class="stat-item">
135+
<span class="stat-label">路径长度</span>
136+
<span class="stat-value" id="path-length">--</span>
137+
</div>
138+
<div class="stat-item">
139+
<span class="stat-label">节点数量</span>
140+
<span class="stat-value" id="path-nodes">--</span>
141+
</div>
142+
<div class="stat-item">
143+
<span class="stat-label">转折次数</span>
144+
<span class="stat-value" id="path-turns">--</span>
145+
</div>
146+
<div class="stat-item">
147+
<span class="stat-label">平滑耗时</span>
148+
<span class="stat-value" id="path-smooth-ms">-- ms</span>
149+
</div>
150+
</div>
151+
<div id="path-info" class="path-detail">
152+
<p>💡 点击画布上的节点选择起点和终点,系统将自动计算最短路径</p>
153+
</div>
154+
</div>
155+
156+
<div class="interpretation-card" id="perf-card">
157+
<div class="card-header-row">
158+
<h3>性能数据</h3>
159+
<span
160+
id="perf-status-dot"
161+
class="status-dot ok"
162+
title="系统状态"
163+
></span>
164+
</div>
165+
<div class="stats-grid">
166+
<div class="stat-item">
167+
<span class="stat-label">总节点数</span>
168+
<span class="stat-value" id="node-count">0</span>
169+
</div>
170+
<div class="stat-item">
171+
<span class="stat-label">总边数</span>
172+
<span class="stat-value" id="edge-count">0</span>
173+
</div>
174+
<div class="stat-item">
175+
<span class="stat-label">层数</span>
176+
<span class="stat-value" id="layer-count">0</span>
177+
</div>
178+
<div class="stat-item">
179+
<span class="stat-label">生成耗时</span>
180+
<span class="stat-value" id="gen-time">-- ms</span>
181+
</div>
182+
<div class="stat-item">
183+
<span class="stat-label">3D渲染</span>
184+
<span class="stat-value" id="render-time">-- ms</span>
185+
</div>
186+
<div class="stat-item">
187+
<span class="stat-label">数据体积</span>
188+
<span class="stat-value" id="data-size">-- KB</span>
189+
</div>
190+
</div>
191+
<div id="perf-info" class="perf-detail">
192+
<p>等待生成模型...</p>
193+
</div>
194+
</div>
195+
196+
<div class="interpretation-card">
197+
<h3>操作提示</h3>
198+
<div class="insights-list">
199+
<div class="insight-item">
200+
<span class="insight-icon">•</span>
201+
<span class="insight-text">
202+
悬停:显示白色十字星,定位最近节点
203+
</span>
204+
</div>
205+
<div class="insight-item">
206+
<span class="insight-icon">•</span>
207+
<span class="insight-text">
208+
点击:第一次选择<strong>起点</strong>,第二次选择<strong>终点</strong>
209+
</span>
210+
</div>
211+
<div class="insight-item">
212+
<span class="insight-icon">•</span>
213+
<span class="insight-text">
214+
自动寻路:使用 A* 算法计算最短路径
215+
</span>
216+
</div>
217+
<div class="insight-item">
218+
<span class="insight-icon">•</span>
219+
<span class="insight-text">
220+
鼠标滚轮:缩放画布;拖拽:平移视图
221+
</span>
222+
</div>
223+
</div>
224+
</div>
225+
</div>
226+
`;
227+
}
228+
}
229+
230+
customElements.define('rn-interpretation-panel', InterpretationPanelElement);

0 commit comments

Comments
 (0)