bc-model-viewer 1.7.21 → 1.7.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bc-model-viewer.min.js +1 -1
- package/examples/LayerManagerDemo.html +737 -0
- package/examples/index.html +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>图层管理演示 - Bc-model-viewer</title>
|
|
8
|
+
<link rel="stylesheet" href="common-styles.css">
|
|
9
|
+
<style>
|
|
10
|
+
/* 图层管理特殊样式 */
|
|
11
|
+
.layer-list {
|
|
12
|
+
max-height: 400px;
|
|
13
|
+
overflow-y: auto;
|
|
14
|
+
margin: 15px 0;
|
|
15
|
+
padding: 10px;
|
|
16
|
+
background-color: #f9f9f9;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
border: 1px solid #e0e0e0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.layer-item {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
padding: 10px;
|
|
25
|
+
margin-bottom: 8px;
|
|
26
|
+
background: white;
|
|
27
|
+
border-radius: 6px;
|
|
28
|
+
border: 1px solid #e0e0e0;
|
|
29
|
+
transition: all 0.2s ease;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.layer-item:hover {
|
|
33
|
+
background-color: #f5f5f5;
|
|
34
|
+
border-color: #667eea;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.layer-item.hidden {
|
|
38
|
+
opacity: 0.5;
|
|
39
|
+
background-color: #f0f0f0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.layer-checkbox {
|
|
43
|
+
width: 18px;
|
|
44
|
+
height: 18px;
|
|
45
|
+
margin-right: 12px;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
accent-color: #667eea;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.layer-info {
|
|
51
|
+
flex: 1;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.layer-name {
|
|
56
|
+
font-weight: 500;
|
|
57
|
+
color: #2c3e50;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
margin-bottom: 4px;
|
|
60
|
+
word-break: break-word;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.layer-details {
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
color: #666;
|
|
66
|
+
display: flex;
|
|
67
|
+
gap: 12px;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.layer-detail-item {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 4px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.layer-index {
|
|
78
|
+
display: inline-block;
|
|
79
|
+
padding: 2px 6px;
|
|
80
|
+
background-color: #667eea;
|
|
81
|
+
color: white;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.layer-folder {
|
|
88
|
+
color: #999;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.layer-color {
|
|
92
|
+
display: inline-block;
|
|
93
|
+
width: 16px;
|
|
94
|
+
height: 16px;
|
|
95
|
+
border-radius: 3px;
|
|
96
|
+
border: 1px solid #ddd;
|
|
97
|
+
vertical-align: middle;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.action-buttons {
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-wrap: wrap;
|
|
103
|
+
gap: 8px;
|
|
104
|
+
margin: 15px 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.action-buttons button {
|
|
108
|
+
flex: 1;
|
|
109
|
+
min-width: 120px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.layer-stats {
|
|
113
|
+
margin-top: 15px;
|
|
114
|
+
padding: 10px;
|
|
115
|
+
background-color: #e8f4f8;
|
|
116
|
+
border-radius: 6px;
|
|
117
|
+
font-size: 13px;
|
|
118
|
+
color: #2c3e50;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.layer-stats strong {
|
|
122
|
+
color: #667eea;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.search-box {
|
|
126
|
+
width: 100%;
|
|
127
|
+
padding: 8px 12px;
|
|
128
|
+
margin-bottom: 10px;
|
|
129
|
+
border: 1px solid #ddd;
|
|
130
|
+
border-radius: 6px;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.search-box:focus {
|
|
135
|
+
outline: none;
|
|
136
|
+
border-color: #667eea;
|
|
137
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.empty-state {
|
|
141
|
+
text-align: center;
|
|
142
|
+
padding: 40px 20px;
|
|
143
|
+
color: #999;
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.empty-state::before {
|
|
148
|
+
content: '📦';
|
|
149
|
+
display: block;
|
|
150
|
+
font-size: 48px;
|
|
151
|
+
margin-bottom: 10px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* 树形结构样式 */
|
|
155
|
+
.tree-node {
|
|
156
|
+
margin-left: 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tree-node-children {
|
|
160
|
+
margin-left: 24px;
|
|
161
|
+
border-left: 2px solid #e0e0e0;
|
|
162
|
+
padding-left: 8px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.tree-folder {
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
padding: 8px 10px;
|
|
169
|
+
margin: 4px 0;
|
|
170
|
+
background: #f8f9fa;
|
|
171
|
+
border-radius: 6px;
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
user-select: none;
|
|
174
|
+
transition: all 0.2s ease;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.tree-folder:hover {
|
|
178
|
+
background: #e9ecef;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.tree-folder-icon {
|
|
182
|
+
margin-right: 8px;
|
|
183
|
+
font-size: 14px;
|
|
184
|
+
transition: transform 0.2s ease;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.tree-folder-icon.collapsed {
|
|
188
|
+
transform: rotate(-90deg);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.tree-folder-name {
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
color: #495057;
|
|
194
|
+
font-size: 14px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.tree-layer-item {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
padding: 8px 10px;
|
|
201
|
+
margin: 4px 0;
|
|
202
|
+
background: white;
|
|
203
|
+
border-radius: 6px;
|
|
204
|
+
border: 1px solid #e0e0e0;
|
|
205
|
+
transition: all 0.2s ease;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.tree-layer-item:hover {
|
|
209
|
+
background-color: #f5f5f5;
|
|
210
|
+
border-color: #667eea;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.tree-layer-item.hidden {
|
|
214
|
+
opacity: 0.5;
|
|
215
|
+
background-color: #f0f0f0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.tree-indent {
|
|
219
|
+
display: inline-block;
|
|
220
|
+
width: 20px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.view-toggle {
|
|
224
|
+
margin-bottom: 10px;
|
|
225
|
+
display: flex;
|
|
226
|
+
gap: 8px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.view-toggle button {
|
|
230
|
+
padding: 6px 12px;
|
|
231
|
+
font-size: 12px;
|
|
232
|
+
min-width: auto;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.view-toggle button.active {
|
|
236
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
237
|
+
color: white;
|
|
238
|
+
}
|
|
239
|
+
</style>
|
|
240
|
+
</head>
|
|
241
|
+
|
|
242
|
+
<body>
|
|
243
|
+
<div id="container"></div>
|
|
244
|
+
<a href="index.html" class="back-button">返回示例列表</a>
|
|
245
|
+
|
|
246
|
+
<div class="control-panel">
|
|
247
|
+
<h2>📚 图层管理演示</h2>
|
|
248
|
+
<p>此示例演示如何使用 <code>viewer.layerManager</code> API 来管理模型的图层可见性。</p>
|
|
249
|
+
|
|
250
|
+
<div class="action-buttons">
|
|
251
|
+
<button onclick="showAllLayers()">显示所有</button>
|
|
252
|
+
<button onclick="hideAllLayers()" class="secondary">隐藏所有</button>
|
|
253
|
+
<button onclick="toggleAllLayers()">切换所有</button>
|
|
254
|
+
<button onclick="logLayerInfo()" class="secondary">打印图层信息</button>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="layer-stats" id="layerStats">
|
|
258
|
+
<strong>图层统计:</strong> 加载中...
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div class="view-toggle">
|
|
262
|
+
<button id="treeViewBtn" class="active" onclick="switchView('tree')">树形视图</button>
|
|
263
|
+
<button id="listViewBtn" onclick="switchView('list')">列表视图</button>
|
|
264
|
+
<button id="dfcViewBtn" onclick="switchView('dfc')">DFC视图</button>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div id="dfcInputGroup" style="display: none; margin-bottom: 10px;">
|
|
268
|
+
<input type="text" class="search-box" id="dfcKeyInput" placeholder="输入 DFC key (如: dfc-architecture)"
|
|
269
|
+
style="margin-bottom: 8px;" onkeypress="if(event.key==='Enter') loadDfcTree()">
|
|
270
|
+
<button onclick="loadDfcTree()" style="width: 100%;">加载 DFC 树形结构</button>
|
|
271
|
+
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
|
272
|
+
提示:DFC key 格式为 "key1-key2",例如 "dfc-architecture"
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<input type="text" class="search-box" id="layerSearch" placeholder="搜索图层名称..." oninput="filterLayers()"
|
|
277
|
+
style="display: block;">
|
|
278
|
+
|
|
279
|
+
<div class="layer-list" id="layerList">
|
|
280
|
+
<div class="empty-state">加载模型中...</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<div class="control-group">
|
|
284
|
+
<h3>操作说明</h3>
|
|
285
|
+
<ul style="margin: 0; padding-left: 20px; font-size: 13px; color: #666; line-height: 1.8;">
|
|
286
|
+
<li>切换"树形视图"、"列表视图"和"DFC视图"查看图层</li>
|
|
287
|
+
<li>在树形视图中,点击文件夹图标可展开/折叠</li>
|
|
288
|
+
<li>DFC视图:输入 DFC key (如 "dfc-architecture") 查看特定类型的图层树形结构</li>
|
|
289
|
+
<li>勾选/取消勾选复选框来切换图层可见性</li>
|
|
290
|
+
<li>使用搜索框快速查找图层(支持文件夹路径搜索)</li>
|
|
291
|
+
<li>点击"显示所有"或"隐藏所有"批量操作</li>
|
|
292
|
+
<li>使用"打印图层信息"查看图层数据和树形结构</li>
|
|
293
|
+
</ul>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<script type="module">
|
|
298
|
+
import { Viewer } from '../../index.ts'
|
|
299
|
+
|
|
300
|
+
const container = document.getElementById('container')
|
|
301
|
+
|
|
302
|
+
// 实例化 Viewer 对象
|
|
303
|
+
const viewer = new Viewer(container, {
|
|
304
|
+
load: {
|
|
305
|
+
cache: {
|
|
306
|
+
enabled: false
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// 当前视图模式
|
|
312
|
+
let currentView = 'tree' // 'tree'、'list' 或 'dfc'
|
|
313
|
+
let expandedFolders = new Set() // 存储展开的文件夹路径
|
|
314
|
+
let currentDfcKey = '' // 当前 DFC key
|
|
315
|
+
|
|
316
|
+
// 加载测试模型
|
|
317
|
+
try {
|
|
318
|
+
await viewer.loadZipAsync('./dgz/ob.dgz')
|
|
319
|
+
console.log('模型加载成功')
|
|
320
|
+
|
|
321
|
+
// 模型加载完成后,初始化图层列表
|
|
322
|
+
initializeLayerList()
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('模型加载失败:', error)
|
|
325
|
+
document.getElementById('layerList').innerHTML =
|
|
326
|
+
'<div class="empty-state">模型加载失败,请检查模型文件路径</div>'
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 初始化图层列表
|
|
330
|
+
function initializeLayerList() {
|
|
331
|
+
const layerList = document.getElementById('layerList')
|
|
332
|
+
const layers = viewer.layerManager.layers
|
|
333
|
+
|
|
334
|
+
if (!layers || layers.length === 0) {
|
|
335
|
+
layerList.innerHTML = '<div class="empty-state">模型中未发现图层</div>'
|
|
336
|
+
updateLayerStats()
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 默认展开所有文件夹
|
|
341
|
+
const tree = viewer.layerManager.getTreeStructure()
|
|
342
|
+
expandAllFolders(tree)
|
|
343
|
+
|
|
344
|
+
renderLayerList()
|
|
345
|
+
updateLayerStats()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 展开所有文件夹
|
|
349
|
+
function expandAllFolders(nodes, parentPath = '') {
|
|
350
|
+
nodes.forEach(node => {
|
|
351
|
+
if (node.type === 'folder' && node.children) {
|
|
352
|
+
const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name
|
|
353
|
+
expandedFolders.add(currentPath)
|
|
354
|
+
expandAllFolders(node.children, currentPath)
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 渲染图层列表
|
|
360
|
+
function renderLayerList() {
|
|
361
|
+
const layerList = document.getElementById('layerList')
|
|
362
|
+
const filterText = document.getElementById('layerSearch').value.toLowerCase()
|
|
363
|
+
layerList.innerHTML = ''
|
|
364
|
+
|
|
365
|
+
if (currentView === 'dfc') {
|
|
366
|
+
// DFC 视图:显示 DFC 树形结构
|
|
367
|
+
if (!currentDfcKey) {
|
|
368
|
+
layerList.innerHTML = '<div class="empty-state">请输入 DFC key 并点击"加载 DFC 树形结构"</div>'
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
const dfcTree = viewer.layerManager.getDfcTreeStructure(currentDfcKey)
|
|
372
|
+
if (dfcTree.length === 0) {
|
|
373
|
+
layerList.innerHTML = `<div class="empty-state">未找到匹配的 DFC 图层 (key: ${currentDfcKey})</div>`
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
renderTreeView(dfcTree, filterText, '')
|
|
377
|
+
} else if (currentView === 'tree') {
|
|
378
|
+
const tree = viewer.layerManager.getTreeStructure()
|
|
379
|
+
if (tree.length === 0) {
|
|
380
|
+
layerList.innerHTML = '<div class="empty-state">模型中未发现图层</div>'
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
renderTreeView(tree, filterText, '')
|
|
384
|
+
} else {
|
|
385
|
+
const layers = viewer.layerManager.layers
|
|
386
|
+
const filteredLayers = filterText
|
|
387
|
+
? layers.filter(layer => {
|
|
388
|
+
const folderStr = Array.isArray(layer.folder)
|
|
389
|
+
? layer.folder.join('/')
|
|
390
|
+
: (layer.folder || '')
|
|
391
|
+
return layer.name?.toLowerCase().includes(filterText) ||
|
|
392
|
+
layer.display_name?.toLowerCase().includes(filterText) ||
|
|
393
|
+
folderStr.toLowerCase().includes(filterText)
|
|
394
|
+
})
|
|
395
|
+
: layers
|
|
396
|
+
|
|
397
|
+
if (filteredLayers.length === 0) {
|
|
398
|
+
layerList.innerHTML = '<div class="empty-state">未找到匹配的图层</div>'
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
renderListView(filteredLayers)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 渲染树形视图
|
|
407
|
+
function renderTreeView(nodes, filterText = '', parentPath = '', parentContainer = null) {
|
|
408
|
+
const container = parentContainer || document.getElementById('layerList')
|
|
409
|
+
|
|
410
|
+
nodes.forEach(node => {
|
|
411
|
+
const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name
|
|
412
|
+
const isExpanded = expandedFolders.has(currentPath)
|
|
413
|
+
|
|
414
|
+
if (node.type === 'folder') {
|
|
415
|
+
// 检查文件夹或其子节点是否匹配搜索条件
|
|
416
|
+
const hasMatch = filterText === '' || nodeMatchesFilter(node, filterText, true)
|
|
417
|
+
|
|
418
|
+
if (filterText === '' || hasMatch) {
|
|
419
|
+
const folderDiv = document.createElement('div')
|
|
420
|
+
folderDiv.className = 'tree-folder'
|
|
421
|
+
folderDiv.innerHTML = `
|
|
422
|
+
<span class="tree-folder-icon ${isExpanded ? '' : 'collapsed'}">▶</span>
|
|
423
|
+
<span class="tree-folder-name">📁 ${node.name}</span>
|
|
424
|
+
`
|
|
425
|
+
folderDiv.onclick = (e) => {
|
|
426
|
+
e.stopPropagation()
|
|
427
|
+
toggleFolder(currentPath)
|
|
428
|
+
}
|
|
429
|
+
container.appendChild(folderDiv)
|
|
430
|
+
|
|
431
|
+
// 渲染子节点
|
|
432
|
+
if (node.children && node.children.length > 0) {
|
|
433
|
+
const childrenDiv = document.createElement('div')
|
|
434
|
+
childrenDiv.className = 'tree-node-children'
|
|
435
|
+
childrenDiv.style.display = isExpanded ? 'block' : 'none'
|
|
436
|
+
childrenDiv.setAttribute('data-path', currentPath)
|
|
437
|
+
container.appendChild(childrenDiv)
|
|
438
|
+
|
|
439
|
+
// 递归渲染子节点
|
|
440
|
+
renderTreeView(node.children, filterText, currentPath, childrenDiv)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
} else if (node.type === 'layer') {
|
|
444
|
+
// 图层节点
|
|
445
|
+
if (filterText === '' || nodeMatchesFilter(node, filterText, false)) {
|
|
446
|
+
const layer = node.layer
|
|
447
|
+
const layerIndex = node.layerIndex
|
|
448
|
+
const colorStyle = layer.color
|
|
449
|
+
? `background-color: rgba(${layer.color[0]}, ${layer.color[1]}, ${layer.color[2]}, ${layer.color[3] || 1})`
|
|
450
|
+
: 'background-color: #ccc'
|
|
451
|
+
|
|
452
|
+
const layerItem = document.createElement('div')
|
|
453
|
+
layerItem.className = `tree-layer-item ${!layer.visible ? 'hidden' : ''}`
|
|
454
|
+
layerItem.setAttribute('data-layer-index', layerIndex)
|
|
455
|
+
layerItem.innerHTML = `
|
|
456
|
+
<input type="checkbox"
|
|
457
|
+
class="layer-checkbox"
|
|
458
|
+
${layer.visible ? 'checked' : ''}
|
|
459
|
+
onchange="toggleLayerVisibilityByIndex(${layerIndex}, this.checked)">
|
|
460
|
+
<div class="layer-info">
|
|
461
|
+
<div class="layer-name">${layer.display_name || layer.name || '未命名图层'}</div>
|
|
462
|
+
<div class="layer-details">
|
|
463
|
+
<span class="layer-detail-item">
|
|
464
|
+
<span class="layer-index">#${layerIndex}</span>
|
|
465
|
+
</span>
|
|
466
|
+
${layer.color ? `<span class="layer-color" style="${colorStyle}"></span>` : ''}
|
|
467
|
+
${layer.id ? `<span class="layer-detail-item" style="color: #999; font-size: 11px;">ID: ${layer.id}</span>` : ''}
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
`
|
|
471
|
+
container.appendChild(layerItem)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 检查节点是否匹配搜索条件
|
|
478
|
+
function nodeMatchesFilter(node, filterText, isFolder) {
|
|
479
|
+
if (isFolder && node.children) {
|
|
480
|
+
// 文件夹:检查名称或子节点是否匹配
|
|
481
|
+
if (node.name.toLowerCase().includes(filterText)) return true
|
|
482
|
+
return node.children.some(child => nodeMatchesFilter(child, filterText, child.type === 'folder'))
|
|
483
|
+
} else if (node.layer) {
|
|
484
|
+
// 图层:检查名称
|
|
485
|
+
const layer = node.layer
|
|
486
|
+
return layer.name?.toLowerCase().includes(filterText) ||
|
|
487
|
+
layer.display_name?.toLowerCase().includes(filterText)
|
|
488
|
+
}
|
|
489
|
+
return false
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 渲染列表视图
|
|
493
|
+
function renderListView(layers) {
|
|
494
|
+
const layerList = document.getElementById('layerList')
|
|
495
|
+
|
|
496
|
+
layers.forEach((layer, index) => {
|
|
497
|
+
const originalIndex = viewer.layerManager.layers.indexOf(layer)
|
|
498
|
+
const layerItem = document.createElement('div')
|
|
499
|
+
layerItem.className = `layer-item ${!layer.visible ? 'hidden' : ''}`
|
|
500
|
+
|
|
501
|
+
const folderStr = Array.isArray(layer.folder)
|
|
502
|
+
? layer.folder.join(' / ')
|
|
503
|
+
: (layer.folder || '')
|
|
504
|
+
|
|
505
|
+
const colorStyle = layer.color
|
|
506
|
+
? `background-color: rgba(${layer.color[0]}, ${layer.color[1]}, ${layer.color[2]}, ${layer.color[3] || 1})`
|
|
507
|
+
: 'background-color: #ccc'
|
|
508
|
+
|
|
509
|
+
layerItem.innerHTML = `
|
|
510
|
+
<input type="checkbox"
|
|
511
|
+
class="layer-checkbox"
|
|
512
|
+
${layer.visible ? 'checked' : ''}
|
|
513
|
+
onchange="toggleLayerVisibilityByIndex(${originalIndex}, this.checked)">
|
|
514
|
+
<div class="layer-info">
|
|
515
|
+
<div class="layer-name">${layer.display_name || layer.name || '未命名图层'}</div>
|
|
516
|
+
<div class="layer-details">
|
|
517
|
+
<span class="layer-detail-item">
|
|
518
|
+
<span class="layer-index">#${originalIndex}</span>
|
|
519
|
+
</span>
|
|
520
|
+
${folderStr ? `<span class="layer-detail-item layer-folder">📁 ${folderStr}</span>` : ''}
|
|
521
|
+
${layer.color ? `<span class="layer-color" style="${colorStyle}"></span>` : ''}
|
|
522
|
+
${layer.id ? `<span class="layer-detail-item" style="color: #999; font-size: 11px;">ID: ${layer.id}</span>` : ''}
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
`
|
|
526
|
+
layerList.appendChild(layerItem)
|
|
527
|
+
})
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 切换文件夹展开/折叠
|
|
531
|
+
function toggleFolder(path) {
|
|
532
|
+
if (expandedFolders.has(path)) {
|
|
533
|
+
expandedFolders.delete(path)
|
|
534
|
+
} else {
|
|
535
|
+
expandedFolders.add(path)
|
|
536
|
+
}
|
|
537
|
+
renderLayerList()
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 切换视图模式
|
|
541
|
+
window.switchView = function (view) {
|
|
542
|
+
currentView = view
|
|
543
|
+
document.getElementById('treeViewBtn').classList.toggle('active', view === 'tree')
|
|
544
|
+
document.getElementById('listViewBtn').classList.toggle('active', view === 'list')
|
|
545
|
+
document.getElementById('dfcViewBtn').classList.toggle('active', view === 'dfc')
|
|
546
|
+
|
|
547
|
+
// 显示/隐藏 DFC 输入框
|
|
548
|
+
const dfcInputGroup = document.getElementById('dfcInputGroup')
|
|
549
|
+
const layerSearch = document.getElementById('layerSearch')
|
|
550
|
+
if (view === 'dfc') {
|
|
551
|
+
dfcInputGroup.style.display = 'block'
|
|
552
|
+
layerSearch.style.display = 'none'
|
|
553
|
+
} else {
|
|
554
|
+
dfcInputGroup.style.display = 'none'
|
|
555
|
+
layerSearch.style.display = 'block'
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
renderLayerList()
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// 加载 DFC 树形结构
|
|
562
|
+
window.loadDfcTree = function () {
|
|
563
|
+
const dfcKeyInput = document.getElementById('dfcKeyInput')
|
|
564
|
+
const key = dfcKeyInput.value.trim()
|
|
565
|
+
|
|
566
|
+
if (!key) {
|
|
567
|
+
alert('请输入 DFC key')
|
|
568
|
+
return
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
currentDfcKey = key
|
|
572
|
+
renderLayerList()
|
|
573
|
+
updateLayerStats()
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 更新图层统计信息
|
|
577
|
+
function updateLayerStats() {
|
|
578
|
+
const layers = viewer.layerManager.layers
|
|
579
|
+
if (!layers || layers.length === 0) {
|
|
580
|
+
document.getElementById('layerStats').innerHTML =
|
|
581
|
+
'<strong>图层统计:</strong> 无图层数据'
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let statsHtml = ''
|
|
586
|
+
|
|
587
|
+
if (currentView === 'dfc' && currentDfcKey) {
|
|
588
|
+
// DFC 视图:显示匹配的图层统计
|
|
589
|
+
const dfcTree = viewer.layerManager.getDfcTreeStructure(currentDfcKey)
|
|
590
|
+
const dfcLayers = getAllLayersFromTree(dfcTree)
|
|
591
|
+
const total = dfcLayers.length
|
|
592
|
+
const visible = dfcLayers.filter(l => l.visible !== false).length
|
|
593
|
+
const hidden = total - visible
|
|
594
|
+
|
|
595
|
+
statsHtml = `
|
|
596
|
+
<strong>DFC 图层统计 (key: ${currentDfcKey}):</strong>
|
|
597
|
+
匹配 ${total} 个图层 |
|
|
598
|
+
可见 ${visible} 个 |
|
|
599
|
+
隐藏 ${hidden} 个
|
|
600
|
+
`
|
|
601
|
+
} else {
|
|
602
|
+
// 普通视图:显示所有图层统计
|
|
603
|
+
const total = layers.length
|
|
604
|
+
const visible = layers.filter(l => l.visible !== false).length
|
|
605
|
+
const hidden = total - visible
|
|
606
|
+
const hiddenIndices = viewer.layerManager.getHiddenIndices()
|
|
607
|
+
|
|
608
|
+
statsHtml = `
|
|
609
|
+
<strong>图层统计:</strong>
|
|
610
|
+
总计 ${total} 个图层 |
|
|
611
|
+
可见 ${visible} 个 |
|
|
612
|
+
隐藏 ${hidden} 个
|
|
613
|
+
${hiddenIndices.length > 0 ? `| 隐藏索引: [${hiddenIndices.join(', ')}]` : ''}
|
|
614
|
+
`
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
document.getElementById('layerStats').innerHTML = statsHtml
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// 从树形结构中获取所有图层
|
|
621
|
+
function getAllLayersFromTree(nodes) {
|
|
622
|
+
const layers = []
|
|
623
|
+
nodes.forEach(node => {
|
|
624
|
+
if (node.type === 'layer' && node.layer) {
|
|
625
|
+
layers.push(node.layer)
|
|
626
|
+
} else if (node.type === 'folder' && node.children) {
|
|
627
|
+
layers.push(...getAllLayersFromTree(node.children))
|
|
628
|
+
}
|
|
629
|
+
})
|
|
630
|
+
return layers
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// 切换图层可见性(通过索引)
|
|
634
|
+
window.toggleLayerVisibilityByIndex = function (index, visible) {
|
|
635
|
+
const layers = viewer.layerManager.layers
|
|
636
|
+
if (index >= 0 && index < layers.length) {
|
|
637
|
+
layers[index].visible = visible
|
|
638
|
+
viewer.updateMeshVisibleByLayer()
|
|
639
|
+
updateLayerStats()
|
|
640
|
+
|
|
641
|
+
// 更新UI状态
|
|
642
|
+
const layerItems = document.querySelectorAll(`[data-layer-index="${index}"]`)
|
|
643
|
+
layerItems.forEach(item => {
|
|
644
|
+
item.classList.toggle('hidden', !visible)
|
|
645
|
+
const checkbox = item.querySelector('input[type="checkbox"]')
|
|
646
|
+
if (checkbox) checkbox.checked = visible
|
|
647
|
+
})
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 兼容旧版本的函数
|
|
652
|
+
window.toggleLayerVisibility = function (index, visible) {
|
|
653
|
+
toggleLayerVisibilityByIndex(index, visible)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 显示所有图层
|
|
657
|
+
window.showAllLayers = function () {
|
|
658
|
+
const layers = viewer.layerManager.layers
|
|
659
|
+
layers.forEach(layer => {
|
|
660
|
+
layer.visible = true
|
|
661
|
+
})
|
|
662
|
+
viewer.updateMeshVisibleByLayer()
|
|
663
|
+
renderLayerList()
|
|
664
|
+
updateLayerStats()
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 隐藏所有图层
|
|
668
|
+
window.hideAllLayers = function () {
|
|
669
|
+
const layers = viewer.layerManager.layers
|
|
670
|
+
layers.forEach(layer => {
|
|
671
|
+
layer.visible = false
|
|
672
|
+
})
|
|
673
|
+
viewer.updateMeshVisibleByLayer()
|
|
674
|
+
renderLayerList()
|
|
675
|
+
updateLayerStats()
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// 切换所有图层
|
|
679
|
+
window.toggleAllLayers = function () {
|
|
680
|
+
const layers = viewer.layerManager.layers
|
|
681
|
+
const allVisible = layers.every(layer => layer.visible !== false)
|
|
682
|
+
layers.forEach(layer => {
|
|
683
|
+
layer.visible = !allVisible
|
|
684
|
+
})
|
|
685
|
+
viewer.updateMeshVisibleByLayer()
|
|
686
|
+
renderLayerList()
|
|
687
|
+
updateLayerStats()
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// 过滤图层
|
|
691
|
+
window.filterLayers = function () {
|
|
692
|
+
renderLayerList()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// 打印图层信息
|
|
696
|
+
window.logLayerInfo = function () {
|
|
697
|
+
console.log('=== 图层信息 ===')
|
|
698
|
+
console.log('图层总数:', viewer.layerManager.layers.length)
|
|
699
|
+
console.log('所有图层:', viewer.layerManager.layers)
|
|
700
|
+
console.log('隐藏的图层索引:', viewer.layerManager.getHiddenIndices())
|
|
701
|
+
console.log('可见图层:', viewer.layerManager.layers.filter(l => l.visible !== false))
|
|
702
|
+
console.log('隐藏图层:', viewer.layerManager.layers.filter(l => l.visible === false))
|
|
703
|
+
console.log('树形结构:', viewer.layerManager.getTreeStructure())
|
|
704
|
+
|
|
705
|
+
// 如果有 DFC key,也打印 DFC 树形结构
|
|
706
|
+
if (currentDfcKey) {
|
|
707
|
+
console.log(`=== DFC 树形结构 (key: ${currentDfcKey}) ===`)
|
|
708
|
+
console.log(viewer.layerManager.getDfcTreeStructure(currentDfcKey))
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// 打印所有有 path 属性的图层(用于查找可用的 DFC key)
|
|
712
|
+
const layersWithPath = viewer.layerManager.layers.filter(l =>
|
|
713
|
+
l.path && Array.isArray(l.path) && l.path.length >= 2
|
|
714
|
+
)
|
|
715
|
+
if (layersWithPath.length > 0) {
|
|
716
|
+
console.log('=== 有 path 属性的图层(可用于 DFC) ===')
|
|
717
|
+
const pathKeys = new Set()
|
|
718
|
+
layersWithPath.forEach(layer => {
|
|
719
|
+
const path = Array.isArray(layer.path) ? layer.path : []
|
|
720
|
+
if (path.length >= 2) {
|
|
721
|
+
const key = path.slice(0, 2).join('-')
|
|
722
|
+
pathKeys.add(key)
|
|
723
|
+
}
|
|
724
|
+
})
|
|
725
|
+
console.log('可用的 DFC keys:', Array.from(pathKeys))
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// 暴露 viewer 实例到 window 对象,方便调试
|
|
730
|
+
window.viewer = viewer
|
|
731
|
+
|
|
732
|
+
console.log('图层管理演示已加载')
|
|
733
|
+
</script>
|
|
734
|
+
|
|
735
|
+
</body>
|
|
736
|
+
|
|
737
|
+
</html>
|