From b332137dc3a3ae1bb3bed15a6d076454a10768cd Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Wed, 14 Jan 2026 10:21:20 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BE=E5=BA=A6=E5=9C=B0=E5=9B=BE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/static/map.html | 299 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 297 insertions(+), 2 deletions(-) diff --git a/src/static/map.html b/src/static/map.html index 9b38fb0..b9ce340 100644 --- a/src/static/map.html +++ b/src/static/map.html @@ -65,6 +65,79 @@ .tree-children { margin-top: 2px; display: none; } .tree-children.expanded { display: block; } + /* 全选/全不选按钮 */ + .tree-select-all { + display: flex; + gap: 10px; + padding: 10px 0; + margin-bottom: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .tree-select-btn { + flex: 1; + padding: 6px 12px; + font-size: 13px; + background: rgba(0, 45, 182, 0.8); + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; + } + .tree-select-btn:hover { + background: rgba(0, 45, 182, 1); + } + + /* 工程列表面板 */ + .project-list-panel { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; + z-index: 999; display: flex; pointer-events: none; align-items: flex-start; + } + + .project-list-container { + width: 300px; height: auto; max-height: 80vh; + margin: 5% 0 0 2%; + background: rgba(30, 30, 30, 0.85); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + overflow-y: auto; overflow-x: hidden; + padding: 20px; + pointer-events: auto; + backdrop-filter: blur(5px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + color: #fff; + } + + .project-list-container::-webkit-scrollbar { width: 6px; } + .project-list-container::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 3px; } + .project-list-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 3px; } + .project-list-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } + + .project-list-item { + display: flex; align-items: center; padding: 6px 8px; + cursor: pointer; border-radius: 4px; transition: background-color 0.2s; + min-height: 28px; margin-bottom: 2px; + } + .project-list-item:hover { background-color: rgba(255, 255, 255, 0.1); } + + .project-list-radio { + width: 16px; height: 16px; margin-right: 8px; cursor: pointer; + accent-color: #002db6; + } + + .project-list-label { + flex: 1; font-size: 14px; user-select: none; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + + .project-list-enter-btn { + padding: 4px 12px; font-size: 12px; background: rgba(0, 45, 182, 0.8); + color: #fff; border: none; border-radius: 4px; cursor: pointer; + transition: background-color 0.2s; margin-left: 8px; + } + .project-list-enter-btn:hover { background: rgba(0, 45, 182, 1); } + .model-preview-close { position: absolute; top: 50px; right: 20px; width: 40px; height: 40px; @@ -79,6 +152,22 @@ } .model-preview-close::after { transform: rotate(-45deg); } + /* AR 按钮 */ + .model-preview-ar { + position: absolute; top: 100px; right: 20px; + width: 40px; height: 40px; + background: rgba(0, 45, 182, 0.95); + border-radius: 50%; display: flex; align-items: center; justify-content: center; + cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + z-index: 1001; pointer-events: auto; + font-size: 20px; + color: #fff; + font-weight: bold; + } + .model-preview-ar:hover { + background: rgba(0, 45, 182, 1); + } + /* 地图 Label 样式 - 提取至 CSS */ .map-project-label { color: #002db6 !important; @@ -123,11 +212,23 @@
+ +
+
+
+
+
+ @@ -263,12 +364,14 @@ projectInfo: [], currentClickedProject: null, modelList: [], - checkedNodeIds: [] + checkedNodeIds: [], + selectedProjectId: null }, elements: { panel: null, tree: null, - closeBtn: null + closeBtn: null, + projectListContainer: null }, init() { @@ -287,6 +390,9 @@ // 初始化地图 this.initMap(this.state.projectInfo); + // 渲染工程列表 + this.renderProjectList(); + // 处理 URL Action this.handleUrlActions(params); }, @@ -295,6 +401,10 @@ this.elements.panel = document.getElementById('model-preview-panel'); this.elements.tree = document.getElementById('tree-container'); this.elements.closeBtn = document.getElementById('close-preview-btn'); + this.elements.projectListContainer = document.getElementById('project-list-container'); + this.elements.selectAllBtn = document.getElementById('select-all-btn'); + this.elements.deselectAllBtn = document.getElementById('deselect-all-btn'); + this.elements.arBtn = document.getElementById('ar-btn'); }, bindEvents() { @@ -302,6 +412,21 @@ this.elements.closeBtn.addEventListener('click', () => this.closePreview()); } + // AR 按钮 + if (this.elements.arBtn) { + this.elements.arBtn.addEventListener('click', () => this.navigateToAR()); + } + + // 全选按钮 + if (this.elements.selectAllBtn) { + this.elements.selectAllBtn.addEventListener('click', () => this.selectAllNodes()); + } + + // 全不选按钮 + if (this.elements.deselectAllBtn) { + this.elements.deselectAllBtn.addEventListener('click', () => this.deselectAllNodes()); + } + // 监听 postMessage window.addEventListener('message', (e) => { const data = e.data; @@ -360,6 +485,111 @@ }); }, + renderProjectList() { + if (!this.elements.projectListContainer) return; + + const projectInfo = this.state.projectInfo || []; + if (projectInfo.length === 0) { + this.elements.projectListContainer.innerHTML = '
暂无工程数据
'; + return; + } + + const fragment = document.createDocumentFragment(); + projectInfo.forEach((project, index) => { + const projectId = project.id || `project-${index}`; + const itemDiv = document.createElement('div'); + itemDiv.className = 'project-list-item'; + itemDiv.dataset.projectId = projectId; + + // 单选框 + const radio = document.createElement('input'); + radio.type = 'radio'; + radio.name = 'project-radio'; + radio.className = 'project-list-radio'; + radio.checked = this.state.selectedProjectId === projectId; + + // 工程名称标签 + const label = document.createElement('span'); + label.className = 'project-list-label'; + label.textContent = `${project.proName || '未命名工程'} (${project.declination || ''})`; + + // 进入按钮 + const enterBtn = document.createElement('button'); + enterBtn.className = 'project-list-enter-btn'; + enterBtn.textContent = '预览'; + // 只有选中时才显示进入按钮 + enterBtn.style.display = radio.checked ? 'block' : 'none'; + + itemDiv.appendChild(radio); + itemDiv.appendChild(label); + itemDiv.appendChild(enterBtn); + + // 点击工程项:选中单选框并定位 + itemDiv.addEventListener('click', (e) => { + // 如果点击的是按钮,不触发定位 + if (e.target === enterBtn) return; + + // 选中单选框 + const projectId = project.id || `project-${index}`; + radio.checked = true; + this.state.selectedProjectId = projectId; + + // 更新所有单选框状态和进入按钮显示状态 + this.updateProjectRadioStates(); + this.updateEnterButtonsVisibility(); + + // 定位到该工程坐标 + if (project.lng && project.lat) { + const point = new BMapGL.Point(project.lng, project.lat); + this.map.centerAndZoom(point, 15); + } + }); + + // 单选框变化事件 + radio.addEventListener('change', () => { + if (radio.checked) { + this.state.selectedProjectId = projectId; + this.updateProjectRadioStates(); + this.updateEnterButtonsVisibility(); + } + }); + + // 点击跳转按钮 - 直接进入模型预览 + enterBtn.addEventListener('click', (e) => { + e.stopPropagation(); + // 设置当前点击的工程 + this.state.currentClickedProject = project; + // 直接触发模型预览 + this.triggerPreview(project); + }); + + fragment.appendChild(itemDiv); + }); + + this.elements.projectListContainer.innerHTML = ''; + this.elements.projectListContainer.appendChild(fragment); + }, + + updateProjectRadioStates() { + const radios = this.elements.projectListContainer.querySelectorAll('.project-list-radio'); + radios.forEach(radio => { + const item = radio.closest('.project-list-item'); + const projectId = item ? item.dataset.projectId : null; + radio.checked = this.state.selectedProjectId === projectId; + }); + }, + + updateEnterButtonsVisibility() { + const items = this.elements.projectListContainer.querySelectorAll('.project-list-item'); + items.forEach(item => { + const radio = item.querySelector('.project-list-radio'); + const enterBtn = item.querySelector('.project-list-enter-btn'); + if (radio && enterBtn) { + enterBtn.style.display = radio.checked ? 'block' : 'none'; + } + }); + }, + openActionMenu(point, info) { const content = `
@@ -455,11 +685,24 @@ const fragment = document.createDocumentFragment(); treeData.forEach(node => this.renderTreeNode(node, fragment, 0)); this.elements.tree.appendChild(fragment); + + // 默认全选 + this.selectAllNodes(); } this.elements.panel.style.display = 'flex'; }, + navigateToAR() { + // 跳转到勘察逻辑 + const projectInfo = this.state.currentClickedProject; + if (projectInfo) { + Bridge.sendMessage('navigateToProject', { projectInfo: projectInfo }); + } else { + console.warn('没有选中的工程信息,无法跳转到勘察页面'); + } + }, + closePreview() { this.elements.panel.style.display = 'none'; this.clearModels(); @@ -569,6 +812,7 @@ const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'tree-node-checkbox'; + checkbox.dataset.id = node.id; // 添加data-id用于全选/全不选时更新UI checkbox.checked = this.state.checkedNodeIds.includes(node.id); checkbox.addEventListener('change', (e) => this.handleCheck(node, e.target.checked)); itemDiv.appendChild(checkbox); @@ -621,6 +865,57 @@ this.loadBatchData(); }, + // 获取所有节点ID(递归) + getAllNodeIds(nodes) { + let ids = []; + nodes.forEach(node => { + ids.push(node.id); + if (node.children && node.children.length > 0) { + ids = ids.concat(this.getAllNodeIds(node.children)); + } + }); + return ids; + }, + + // 全选 + selectAllNodes() { + const treeData = this.buildTreeData(this.state.modelList); + const allIds = this.getAllNodeIds(treeData); + this.state.checkedNodeIds = [...new Set(allIds)]; // 去重 + + // 更新所有复选框UI + this.updateAllCheckboxes(); + + // 触发数据加载 + this.loadBatchData(); + }, + + // 全不选 + deselectAllNodes() { + this.state.checkedNodeIds = []; + + // 更新所有复选框UI + this.updateAllCheckboxes(); + + // 清除模型 + this.clearModels(); + }, + + // 更新所有复选框状态 + updateAllCheckboxes() { + const checkboxes = this.elements.tree.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + // 从checkbox的data-id获取节点ID + const nodeId = checkbox.dataset.id; + if (nodeId !== undefined) { + // 确保ID类型一致(都转为字符串或数字进行比较) + const idStr = String(nodeId); + const checkedIdsStr = this.state.checkedNodeIds.map(id => String(id)); + checkbox.checked = checkedIdsStr.includes(idStr); + } + }); + }, + syncCheckboxUI(rootNode, checked) { // 简易实现:重新渲染虽然 heavy,但在没有 VDOM 的情况下保证一致性。 // 更好的方式是在 render 时给 checkbox 加 data-id