water-design-const-app/src/static/map.html

1278 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<script type="text/javascript"
src="https://api.map.baidu.com/api?v=3.0&&type=webgl&ak=iqyZkSZPurf61MhFV7hesbDukHdMBEEb"></script>
<title>百度地图</title>
<style>
#map-container {
width: 100vw;
height: 100vh;
position: relative;
}
/** 去除百度地图的水印和logo */
.BMap_cpyCtrl,
.anchorBL {
display: none;
}
/* 模型预览面板 */
.model-preview-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
pointer-events: none; /* 允许点击穿透到地图 */
align-items: flex-start; /* 顶部对齐 */
}
.model-preview-tree {
width: 300px;
height: auto;
max-height: 80vh;
margin-top: 2%;
margin-left: 2%;
background: rgba(30, 30, 30, 0.55);
border-right: 1px solid rgba(51, 51, 51, 0.5);
border-radius: 8px;
overflow-y: auto;
overflow-x: hidden;
padding: 20px;
pointer-events: auto; /* 列表区域可以交互 */
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
}
/* 自定义滚动条样式 */
.model-preview-tree::-webkit-scrollbar {
width: 6px;
}
.model-preview-tree::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.model-preview-tree::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.model-preview-tree::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.tree-title {
font-size: 18px;
font-weight: bold;
color: #fff;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #002db6;
}
.tree-node {
margin-bottom: 4px;
}
.tree-node-item {
display: flex;
align-items: center;
padding: 6px 8px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
min-height: 28px;
}
.tree-node-item:hover {
background-color: #323232;
}
.tree-node-checkbox {
width: 18px;
height: 18px;
margin-right: 8px;
cursor: pointer;
}
.tree-node-label {
flex: 1;
font-size: 14px;
color: #fff;
user-select: none;
}
.tree-expand-icon {
display: inline-block;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.tree-expand-icon:hover {
opacity: 0.7;
}
.tree-children {
margin-top: 4px;
}
.model-preview-close {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(221, 221, 221, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
transition: all 0.2s;
z-index: 1001;
pointer-events: auto; /* 关闭按钮可以点击 */
}
.model-preview-close:hover {
background: #f5f5f5;
transform: scale(1.1);
}
.model-preview-close::before,
.model-preview-close::after {
content: '';
position: absolute;
width: 2px;
height: 20px;
background: #666;
transform: rotate(45deg);
}
.model-preview-close::after {
transform: rotate(-45deg);
}
/* 点击菜单样式 */
.action-menu {
background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 45, 182, 0.12),
0 4px 16px rgba(0, 0, 0, 0.08),
0 2px 8px rgba(0, 0, 0, 0.04);
width: 100%;
height: 100%;
overflow: hidden;
border: 1px solid rgba(0, 45, 182, 0.08);
backdrop-filter: blur(10px);
padding: 0;
display: flex;
flex-direction: column;
justify-content: stretch;
align-items: stretch;
box-sizing: border-box;
}
.action-menu-item {
padding: 0 20px;
margin: 0;
cursor: pointer;
font-size: 15px;
color: #1a1a1a;
border-radius: 0;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: flex-start;
position: relative;
font-weight: 500;
letter-spacing: 0.2px;
flex: 1;
box-sizing: border-box;
text-align: left;
border-bottom: 1px solid rgba(0, 45, 182, 0.06);
}
.action-menu-item:first-child {
border-radius: 16px 16px 0 0;
}
.action-menu-item:last-child {
border-bottom: none;
border-radius: 0 0 16px 16px;
}
.action-menu-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 0;
background: linear-gradient(180deg, #002db6 0%, #0056e6 100%);
border-radius: 0 2px 2px 0;
opacity: 0;
transition: all 0.25s ease;
}
.action-menu-item:hover {
background: linear-gradient(135deg, #f0f4ff 0%, #e6edff 100%);
color: #002db6;
padding-left: 24px;
transform: translateX(2px);
box-shadow: inset 0 0 0 1px rgba(0, 45, 182, 0.1);
}
.action-menu-item:hover::before {
opacity: 1;
height: 60%;
}
.action-menu-item span {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin-right: 12px;
width: 24px;
height: 24px;
flex-shrink: 0;
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.action-menu-item:hover span {
transform: scale(1.15) rotate(5deg);
}
</style>
</head>
<body>
<div id="map-container"></div>
<!-- 模型预览面板 -->
<div id="model-preview-panel" class="model-preview-panel" style="display: none;">
<div class="model-preview-tree">
<div class="tree-title">模型列表</div>
<div id="tree-container"></div>
</div>
<div class="model-preview-close" id="close-preview-btn"></div>
</div>
</body>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function () {
// 全局变量
let map = null;
let allOverlays = []; // 保存所有覆盖物
let currentClickedProject = null; // 当前点击的项目信息
let modelList = []; // 模型列表(原始数据)
let projectTreeData = []; // 树形结构数据
let checkedNodeIds = []; // 已勾选的节点ID
let modelOverlays = []; // 模型覆盖物(用于清除)
let apiBaseUrl = '/api'; // API 基础地址
let apiToken = ''; // API token
// 兼容的 URL 参数解析函数(替代 URLSearchParams
function parseUrlParams(queryString) {
const params = {};
if (!queryString) return params;
// 移除开头的 ?
const cleanQuery = queryString.startsWith('?') ? queryString.substring(1) : queryString;
if (!cleanQuery) return params;
const pairs = cleanQuery.split('&');
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i].split('=');
if (pair.length === 2) {
const key = decodeURIComponent(pair[0]);
const value = decodeURIComponent(pair[1]);
params[key] = value;
}
}
return params;
}
// 获取 URL 参数(兼容方式)
function getUrlParam(name) {
const queryString = window.location.search;
const params = parseUrlParams(queryString);
return params[name] || null;
}
function getUrlParams() {
const projectInfoStr = getUrlParam('projectInfo');
if (!projectInfoStr) return [];
try {
const projectInfo = JSON.parse(projectInfoStr);
return projectInfo;
} catch (e) {
console.error('解析 projectInfo 失败:', e);
return [];
}
}
const projectInfo = getUrlParams()
// 检查 URL 参数中是否有模型预览请求(使用兼容方式)
const urlParams = parseUrlParams(window.location.search);
const modelListParam = urlParams['modelList'] || null;
const actionParam = urlParams['action'] || null;
const clickedProjectParam = urlParams['clickedProject'] || null;
// 获取 API 配置信息(从 URL 参数中获取)
apiBaseUrl = urlParams['apiBaseUrl'] || '/api';
apiToken = urlParams['token'] || '';
// 如果 URL 中没有 token尝试从 localStorage 获取(备用方案)
if (!apiToken) {
try {
apiToken = localStorage.getItem('token') || '';
} catch (e) {
// localStorage 可能不可用
}
}
// 保存点击的项目信息
if (clickedProjectParam) {
try {
currentClickedProject = JSON.parse(decodeURIComponent(clickedProjectParam));
} catch (e) {
console.error('解析点击项目信息失败:', e);
}
}
// 先初始化地图
initMap(projectInfo)
// 如果 URL 中有模型预览请求(通过 URL 参数触发的情况)
if (actionParam === 'modelPreview' && clickedProjectParam) {
// 通过 postMessage 通知 Vue 组件获取模型列表
const triggerModelPreview = () => {
try {
const projectInfo = JSON.parse(decodeURIComponent(clickedProjectParam));
// 尝试多种方式发送消息
const messageData = {
data: {
action: 'modelPreview',
projectInfo: projectInfo
}
};
// 方案1: uni.postMessage
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
uni.postMessage(messageData);
return;
}
// 方案2: window.parent.postMessage
if (window.parent && window.parent !== window) {
window.parent.postMessage(messageData, '*');
return;
}
} catch (e) {
console.error('触发模型预览失败:', e);
}
};
// 等待 UniAppJSBridgeReady 后再触发
setTimeout(triggerModelPreview, 500);
}
// 如果 URL 中有模型预览请求(已有模型列表的情况),在页面加载完成后显示
if (actionParam === 'showPreview' && modelListParam) {
try {
const models = JSON.parse(decodeURIComponent(modelListParam));
// 定义一个函数来显示预览
const showPreviewWhenReady = () => {
if (map && typeof window.showModelPreview === 'function') {
window.showModelPreview(models, currentClickedProject);
} else {
setTimeout(showPreviewWhenReady, 200);
}
};
// 等待地图初始化完成后再显示预览
setTimeout(showPreviewWhenReady, 500);
} catch (e) {
console.error('解析模型列表失败:', e);
}
}
// 处理模型数据响应(通过 URL 参数,备用方案)
const modelDataParam = urlParams['modelData'] || null;
if (actionParam === 'loadModelData' && modelDataParam) {
try {
const modelData = JSON.parse(decodeURIComponent(modelDataParam));
const loadModelDataWhenReady = () => {
if (map && typeof window.drawModel === 'function') {
window.drawModel(modelData);
} else {
setTimeout(loadModelDataWhenReady, 200);
}
};
setTimeout(loadModelDataWhenReady, 500);
} catch (e) {
console.error('解析模型数据失败:', e);
}
}
function initMap(proInfo) {
map = new BMapGL.Map('map-container') // 创建地图实例
let point = new BMapGL.Point(proInfo[0].lng, proInfo[0].lat) // 创建点坐标
map.centerAndZoom(point, 12) // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true) // 启用滚轮放大缩小
projectInfo.forEach(item => {
handleProjectInfoOnMap(map, item)
})
}
function handleProjectInfoOnMap(map, projectInfo) {
if (projectInfo.lng && projectInfo.lat) {
const projectPoint = new BMapGL.Point(projectInfo.lng, projectInfo.lat);
const icon = new BMapGL.Icon('./image/location.png', new BMapGL.Size(36, 36), {
anchor: new BMapGL.Size(12, 24),
})
const marker = new BMapGL.Marker(projectPoint, {
icon: icon,
})
const label = new BMapGL.Label(`${projectInfo.proName} (${projectInfo.declination})`, {
position: projectPoint,
offset: new BMapGL.Size(0, 0),
})
label.setStyle({
color: '#002db6',
backgroundColor: 'transparent',
border: 'none',
textAlign: 'center',
padding: '5px',
whiteSpace: 'nowrap',
fontSize: '18px',
fontWeight: 'bold',
transform: 'translateX(-45%)'
});
map.addOverlay(marker)
map.addOverlay(label)
// 保存覆盖物引用
allOverlays.push({
marker: marker,
label: label,
projectInfo: projectInfo,
point: projectPoint
});
marker.addEventListener('click', function () {
currentClickedProject = projectInfo;
showActionMenu(projectPoint, projectInfo);
});
}
}
// 显示操作菜单
function showActionMenu(point, projectInfo) {
const menuHtml = `
<div class="action-menu" style="overflow: hidden !important; padding: 0 !important; margin: 0 !important; box-sizing: border-box !important; width: 100% !important; height: 100% !important;">
<div class="action-menu-item" data-action="preview">
<span>🗺️</span>模型预览
</div>
<div class="action-menu-item" data-action="survey">
<span>📋</span>勘察
</div>
</div>
`;
const infoWindow = new BMapGL.InfoWindow(menuHtml, {
width: 170,
height: 130,
title: '',
enableMessage: false,
offset: new BMapGL.Size(0, -20),
enableAutoPan: true
});
map.openInfoWindow(infoWindow, point);
setTimeout(() => {
const menuContainer = document.querySelector('.action-menu');
if (menuContainer) {
// 处理点击和触摸事件(兼容安卓)
const handleAction = function(e) {
// 阻止默认行为和事件冒泡
e.preventDefault();
e.stopPropagation();
const target = e.target.closest('.action-menu-item');
if (!target) return;
const action = target.getAttribute('data-action');
map.closeInfoWindow();
if (action === 'survey') {
// 勘察 - 原来的逻辑
const surveyMessage = {
data: {
action: 'navigateToProject',
projectInfo: projectInfo
}
};
// 尝试多种方式发送消息
try {
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
uni.postMessage(surveyMessage);
} else if (window.parent && window.parent !== window) {
window.parent.postMessage(surveyMessage, '*');
}
} catch (error) {
console.error('发送勘察消息失败:', error);
}
} else if (action === 'preview') {
// 模型预览
sendModelPreviewMessage(projectInfo);
}
};
// 同时监听 click 和 touchstart 事件(安卓兼容)
menuContainer.addEventListener('click', handleAction);
menuContainer.addEventListener('touchend', function(e) {
// 触摸事件需要立即处理,避免延迟
handleAction(e);
});
// 防止触摸时触发点击(避免重复触发)
let touchStartTime = 0;
menuContainer.addEventListener('touchstart', function(e) {
touchStartTime = Date.now();
});
menuContainer.addEventListener('click', function(e) {
// 如果刚刚有触摸事件,忽略点击事件(避免重复)
if (Date.now() - touchStartTime < 300) {
e.preventDefault();
e.stopPropagation();
}
}, true);
}
}, 200);
}
// 发送模型预览消息的辅助函数
function sendModelPreviewMessage(projectInfo) {
// 根据 uni-app 文档,使用 data 对象格式(不是数组)
const messageData = {
data: {
action: 'modelPreview',
projectInfo: projectInfo
}
};
// 方案1: 尝试使用 uni.postMessageiOS 和部分安卓)
let messageSent = false;
try {
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
uni.postMessage(messageData);
messageSent = true;
}
} catch (error) {
// 静默失败,尝试下一个方案
}
// 方案2: 尝试使用 window.parent.postMessage安卓备选方案
if (!messageSent) {
try {
if (window.parent && window.parent !== window) {
window.parent.postMessage(messageData, '*');
messageSent = true;
}
} catch (error) {
// 静默失败,尝试下一个方案
}
}
// 方案3: 尝试使用 window.webkit.messageHandlersiOS WebView
if (!messageSent) {
try {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.uni) {
window.webkit.messageHandlers.uni.postMessage(messageData);
messageSent = true;
}
} catch (error) {
// 静默失败,尝试下一个方案
}
}
// 方案4: 通过 URL 参数方式触发(最可靠的安卓方案,作为最后备选)
if (!messageSent) {
try {
// 获取当前 URL 参数(使用兼容方式)
const currentUrlParams = parseUrlParams(window.location.search);
const currentProjectInfo = currentUrlParams['projectInfo'] || '';
// 构建新的 URL添加模型预览触发参数
const clickedProjectJson = encodeURIComponent(JSON.stringify(projectInfo));
const timestamp = Date.now();
// 构建完整的新 URL保留 apiBaseUrl 和 token
let newUrl = window.location.pathname;
const newParams = [];
if (currentProjectInfo) {
newParams.push('projectInfo=' + encodeURIComponent(currentProjectInfo));
}
newParams.push('clickedProject=' + clickedProjectJson);
newParams.push('action=modelPreview');
if (apiBaseUrl) {
newParams.push('apiBaseUrl=' + encodeURIComponent(apiBaseUrl));
}
if (apiToken) {
newParams.push('token=' + encodeURIComponent(apiToken));
}
newParams.push('t=' + timestamp);
newUrl += '?' + newParams.join('&');
// 使用 location.href 触发页面重新加载
// 注意:这会触发页面重新加载,但可以通过 URL 参数恢复状态
window.location.href = newUrl;
return;
} catch (error) {
console.error('发送模型预览消息失败:', error);
alert('无法发送模型预览请求,请重试');
}
}
}
// 清空所有覆盖物
function clearAllOverlays() {
allOverlays.forEach(overlay => {
map.removeOverlay(overlay.marker);
map.removeOverlay(overlay.label);
});
}
// 清空模型覆盖物
function clearModelOverlays() {
if (map && modelOverlays.length > 0) {
modelOverlays.forEach(overlay => {
map.removeOverlay(overlay);
});
modelOverlays = [];
}
}
// 递归构建树形结构(参考 test.vue 的 onBuildTree
function buildTree(data) {
console.log('data',JSON.stringify(data));
// 获取所有节点的 id 集合,用于判断是否为顶级节点
const allIds = new Set(data.map(item => item.id));
// 筛选顶级节点nodelevel为"2" 且 parentId 不在数据id列表中parentId 是项目ID
const topNodes = data.filter((item) => {
return item.nodelevel == '2' && !allIds.has(item.parentId);
});
return topNodes.map((node) => ({
...node,
expanded: true, // 默认展开
children: findChildren(node, data)
}));
}
// 查找子节点(参考 test.vue 的 findChildren
function findChildren(node, data) {
// 寻找 parentId 等于当前节点 id 的所有项,作为子节点
const children = data.filter((item) => item.parentId == node.id);
if (children.length > 0) {
return children.map((child) => ({
...child,
expanded: true, // 默认展开
children: findChildren(child, data)
}));
}
return [];
}
// 递归获取节点下所有子节点ID参考 test.vue 的 getAllChildNodeIds
function getAllChildNodeIds(node) {
let nodeIds = [];
nodeIds.push(node.id);
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
nodeIds = nodeIds.concat(getAllChildNodeIds(child));
});
}
return nodeIds;
}
// 渲染树形节点(递归)
function renderTreeNode(node, container, level = 0) {
const nodeDiv = document.createElement('div');
nodeDiv.className = 'tree-node';
// 根据层级设置缩进
nodeDiv.style.paddingLeft = (level * 20) + 'px';
nodeDiv.style.marginLeft = '0';
const itemDiv = document.createElement('div');
itemDiv.className = 'tree-node-item';
// 判断是否为末级节点(使用节点的 isLeaf 属性,如果没有则计算)
const isLeaf = node.isLeaf !== undefined ? node.isLeaf : (Number(node.nodelevel) === Number(node.nodeCount));
const hasChildren = node.children && node.children.length > 0;
// 所有节点都显示复选框
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'tree-node-checkbox';
checkbox.checked = checkedNodeIds.includes(node.id);
checkbox.dataset.nodeId = node.id;
checkbox.addEventListener('change', function(e) {
handleNodeCheckChange(node, e.target.checked);
});
itemDiv.appendChild(checkbox);
// 如果有子节点,显示展开/折叠图标
if (hasChildren) {
const expandIcon = document.createElement('span');
expandIcon.className = 'tree-expand-icon';
// 默认展开
const isExpanded = node.expanded !== undefined ? node.expanded : true;
expandIcon.textContent = isExpanded ? '▼' : '▶';
expandIcon.style.marginRight = '6px';
expandIcon.style.cursor = 'pointer';
expandIcon.style.width = '12px';
expandIcon.style.display = 'inline-block';
expandIcon.style.fontSize = '10px';
expandIcon.style.color = '#fff';
expandIcon.addEventListener('click', function(e) {
e.stopPropagation();
// 切换展开状态
const currentExpanded = node.expanded !== undefined ? node.expanded : true;
node.expanded = !currentExpanded;
const childrenContainer = nodeDiv.querySelector('.tree-children');
if (childrenContainer) {
childrenContainer.style.display = node.expanded ? 'block' : 'none';
expandIcon.textContent = node.expanded ? '▼' : '▶';
}
});
itemDiv.insertBefore(expandIcon, checkbox);
}
const label = document.createElement('span');
label.className = 'tree-node-label';
label.textContent = node.nodeName || node.name || `节点 ${node.id}`;
itemDiv.appendChild(label);
nodeDiv.appendChild(itemDiv);
// 如果有子节点,递归渲染
if (node.children && node.children.length > 0) {
const childrenContainer = document.createElement('div');
childrenContainer.className = 'tree-children';
// 默认展开expanded 默认为 true
childrenContainer.style.display = (node.expanded !== false) ? 'block' : 'none';
node.children.forEach(child => {
renderTreeNode(child, childrenContainer, level + 1);
});
nodeDiv.appendChild(childrenContainer);
}
container.appendChild(nodeDiv);
}
// 节点勾选状态变化事件(参考 test.vue 的 handleNodeCheckChange
function handleNodeCheckChange(node, checked) {
// 递归获取当前节点下所有子节点ID
const allChildIds = getAllChildNodeIds(node);
// 更新业务数组
if (checked) {
allChildIds.forEach(id => {
if (!checkedNodeIds.includes(id)) {
checkedNodeIds.push(id);
}
});
} else {
checkedNodeIds = checkedNodeIds.filter(id => !allChildIds.includes(id));
}
// 更新所有复选框状态
updateCheckboxStates();
// 加载模型数据
loadBatchCadData();
}
// 更新所有复选框状态
function updateCheckboxStates() {
const checkboxes = document.querySelectorAll('.tree-node-checkbox');
checkboxes.forEach(checkbox => {
const nodeId = checkbox.dataset.nodeId;
checkbox.checked = checkedNodeIds.includes(nodeId);
});
}
// 批量加载勾选节点的CAD数据直接调用 API
async function loadBatchCadData() {
// 清空旧模型覆盖物
clearModelOverlays();
if (checkedNodeIds.length === 0) {
return;
}
// 检查必要的配置
if (!apiBaseUrl) {
console.error('API baseURL 未配置');
return;
}
try {
// 构建 API URL确保 URL 格式正确)
let apiUrl = apiBaseUrl;
if (!apiUrl.endsWith('/')) {
apiUrl += '/';
}
apiUrl += 'model/openView';
// 如果 apiBaseUrl 是相对路径,需要转换为绝对路径
if (!apiUrl.startsWith('http')) {
// 相对路径,使用当前域名
const protocol = window.location.protocol;
const host = window.location.host;
apiUrl = protocol + '//' + host + (apiUrl.startsWith('/') ? '' : '/') + apiUrl.replace(/^\//, '');
}
// 批量请求模型数据
const promises = checkedNodeIds.map(nodeId => {
// 使用 XMLHttpRequest 作为 fetch 的备选方案(更好的兼容性)
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
if (apiToken) {
xhr.setRequestHeader('Authorization', apiToken);
xhr.setRequestHeader('Token', apiToken);
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const result = JSON.parse(xhr.responseText);
if (result.code >= 200 && result.code < 300) {
resolve(result.data || []);
} else {
console.error('API 返回错误:', result);
resolve([]);
}
} catch (e) {
console.error('解析响应数据失败:', e);
resolve([]);
}
} else {
console.error('请求失败 (nodeId: ' + nodeId + ', status: ' + xhr.status + ')');
resolve([]);
}
}
};
xhr.onerror = function() {
console.error('请求错误 (nodeId: ' + nodeId + ')');
resolve([]);
};
xhr.send(JSON.stringify({ id: nodeId }));
});
});
// 等待所有请求完成
const results = await Promise.all(promises);
// 合并所有模型数据
const modelData = results.reduce((total, data) => {
if (Array.isArray(data)) {
return [...total, ...data];
}
return total;
}, []);
// 绘制模型
if (modelData.length > 0) {
drawModel(modelData);
}
} catch (error) {
console.error('批量加载模型数据失败:', error);
}
}
// 绘制模型覆盖物(参考 test.vue 的 drawModel- 暴露到全局
window.drawModel = function(modelInfoList) {
// 清除现有模型覆盖物
clearModelOverlays();
if (!modelInfoList || modelInfoList.length === 0) {
return;
}
for (const item of modelInfoList) {
try {
// 处理 LWPOLYLINE 数据结构
if (item.entityType === 'LWPOLYLINE') {
const pointList = JSON.parse(item.geometry);
const newPointList = pointList?.segments;
if (newPointList) {
newPointList.forEach((segment) => {
if (segment.type === 'line') {
const line = new BMapGL.Polyline(
[
new BMapGL.Point(segment.start[0], segment.start[1]),
new BMapGL.Point(segment.end[0], segment.end[1]),
],
{
strokeColor: 'red',
strokeWeight: 2,
strokeOpacity: 0.8,
}
);
map.addOverlay(line);
modelOverlays.push(line);
} else if (segment.type === 'arc') {
if (segment.start_point && segment.end_point && segment.center && segment.radius) {
const line = new BMapGL.Polyline(
[
new BMapGL.Point(segment.start_point[0], segment.start_point[1]),
new BMapGL.Point(segment.end_point[0], segment.end_point[1]),
],
{
strokeColor: 'red',
strokeWeight: 2,
strokeOpacity: 0.8,
}
);
map.addOverlay(line);
modelOverlays.push(line);
} else if (segment.center && segment.radius) {
const circle = new BMapGL.Circle(
new BMapGL.Point(segment.center[0], segment.center[1]),
segment.radius,
{
strokeColor: 'red',
strokeWeight: 2,
fillColor: 'rgba(0,0,255,0.3)',
}
);
map.addOverlay(circle);
modelOverlays.push(circle);
}
}
});
}
} else if (item.entityType === 'LINE') {
const geometry = JSON.parse(item.geometry);
const start = geometry.start;
const end = geometry.end;
const polyline = new BMapGL.Polyline(
[new BMapGL.Point(start[0], start[1]), new BMapGL.Point(end[0], end[1])],
{ strokeColor: 'red', strokeWeight: 2, strokeOpacity: 0.8 }
);
map.addOverlay(polyline);
modelOverlays.push(polyline);
} else if (item.entityType === 'CIRCLE') {
const geometry = JSON.parse(item.geometry);
const center = geometry.center;
const radius = geometry.radius;
if (center && center.length > 0) {
const circle = new BMapGL.Circle(
new BMapGL.Point(center[0], center[1]),
radius,
{
strokeColor: 'blue',
strokeWeight: 2,
fillColor: 'rgba(0,0,255,0.3)',
}
);
map.addOverlay(circle);
modelOverlays.push(circle);
}
} else {
// 处理多边形或其他图形
const pointList = JSON.parse(item.geometry);
if (pointList && pointList.points && pointList.points.length > 0) {
const path = pointList.points.map((p) => {
const [lng, lat, , , angle] = p;
return {
point: new BMapGL.Point(lng, lat),
angle: angle || 0
};
});
let overlay = null;
if (path.length === 1) {
const { point, angle } = path[0];
const arrowIcon = new BMapGL.Icon(
'//api.map.baidu.com/img/markers.png',
new BMapGL.Size(20, 34),
{
anchor: new BMapGL.Size(10, 34),
imageOffset: new BMapGL.Size(0, 0),
}
);
overlay = new BMapGL.Marker(point, {
icon: arrowIcon,
rotation: (angle * 180) / Math.PI,
});
} else if (path.length === 2) {
const linePoints = path.map((p) => p.point);
const endAngle = (path[1].angle * 180) / Math.PI;
overlay = new BMapGL.Polyline(linePoints, {
strokeColor: 'green',
strokeWeight: 2,
});
const arrowMarker = new BMapGL.Marker(linePoints[1], {
icon: new BMapGL.Icon(
'//api.map.baidu.com/img/markers.png',
new BMapGL.Size(20, 34),
{ anchor: new BMapGL.Size(10, 34) }
),
rotation: endAngle,
});
map.addOverlay(arrowMarker);
modelOverlays.push(arrowMarker);
} else {
const polygonPoints = path.map((p) => p.point);
overlay = new BMapGL.Polygon(polygonPoints, {
strokeColor: '#3388ff',
strokeWeight: 2,
fillColor: 'rgba(51,136,255,0.2)',
});
}
if (overlay) {
map.addOverlay(overlay);
modelOverlays.push(overlay);
}
}
}
} catch (error) {
console.error('绘制图元失败:', item, error);
}
}
// 调整地图视图以显示所有覆盖物
const points = modelOverlays
.map((overlay) => {
if (overlay instanceof BMapGL.Marker) {
return overlay.getPosition();
} else if (overlay instanceof BMapGL.Polyline || overlay instanceof BMapGL.Polygon) {
return overlay.getPath();
} else if (overlay instanceof BMapGL.Circle) {
return overlay.getCenter();
}
})
.flat()
.filter(Boolean);
if (points.length > 0) {
const adjustedZoom = Math.min(16, map.getMaxZoom());
map.setViewport(points, {
zoom: adjustedZoom,
padding: [50, 50, 50, 50],
});
}
}
// 恢复所有覆盖物
function restoreAllOverlays() {
allOverlays.forEach(overlay => {
map.addOverlay(overlay.marker);
map.addOverlay(overlay.label);
});
}
// 定位到指定坐标
function locateToPoint(point, zoom = 15) {
map.centerAndZoom(point, zoom);
}
// 显示模型预览面板(暴露到全局作用域)
window.showModelPreview = function(models, clickedProject) {
// 如果传入了点击的项目信息,保存它
if (clickedProject) {
currentClickedProject = clickedProject;
}
if (!map) {
// 如果地图未初始化,等待一下再试
setTimeout(() => {
if (map) {
window.showModelPreview(models, clickedProject);
} else {
console.error('地图初始化超时');
}
}, 500);
return;
}
modelList = models || [];
const panel = document.getElementById('model-preview-panel');
const treeContainer = document.getElementById('tree-container');
if (!panel) {
console.error('找不到预览面板元素 #model-preview-panel');
return;
}
if (!treeContainer) {
console.error('找不到树容器元素 #tree-container');
return;
}
// 清空所有覆盖物和模型覆盖物
clearAllOverlays();
clearModelOverlays();
// 清空已勾选节点
checkedNodeIds = [];
// 加载项目树形数据(参考 test.vue 的 loadProjectTree
// 检查数据是否为平铺结构(没有 children 属性)
const hasChildren = modelList.some(item => item.children && Array.isArray(item.children) && item.children.length > 0);
let treeData;
if (hasChildren) {
// 如果已经是树形结构,直接使用
treeData = modelList;
} else {
// 如果是平铺数据,转换为树形结构
treeData = buildTree(modelList);
}
// 为每个节点添加 isLeaf 属性和 expanded 属性(参考 test.vue 的 loadProjectTree
function processNode(node) {
const isLeaf = Number(node.nodelevel) === Number(node.nodeCount);
return {
...node,
isLeaf: isLeaf,
expanded: node.expanded !== undefined ? node.expanded : true, // 默认展开
children: node.children && node.children.length > 0
? node.children.map(child => processNode(child))
: []
};
}
projectTreeData = treeData.map(node => processNode(node));
// 渲染树形结构
treeContainer.innerHTML = '';
if (projectTreeData.length === 0) {
treeContainer.innerHTML = '<div style="color: #aaa; text-align: center; padding: 20px;">暂无模型数据</div>';
} else {
projectTreeData.forEach(node => {
renderTreeNode(node, treeContainer, 0);
});
}
// 显示面板
panel.style.display = 'flex';
};
// 带数据的预览函数(用于直接调用)
window.showModelPreviewWithData = function(models, clickedProject) {
window.showModelPreview(models, clickedProject);
};
// 监听 postMessage 消息
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'showModelPreview') {
window.showModelPreview(event.data.modelList);
}
});
// 关闭模型预览面板
function closeModelPreview() {
const panel = document.getElementById('model-preview-panel');
panel.style.display = 'none';
// 清除模型覆盖物
clearModelOverlays();
// 清空已勾选的节点
checkedNodeIds = [];
// 更新所有复选框状态
updateCheckboxStates();
// 恢复所有覆盖物
restoreAllOverlays();
// 定位到点击的坐标
if (currentClickedProject && currentClickedProject.lng && currentClickedProject.lat) {
const point = new BMapGL.Point(currentClickedProject.lng, currentClickedProject.lat);
locateToPoint(point, 12);
}
}
// 绑定关闭按钮事件
document.getElementById('close-preview-btn').addEventListener('click', closeModelPreview);
// 监听来自父组件的消息(用于接收模型列表)
// 注意uni-app 的 web-view 消息传递机制
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'showModelPreview') {
const models = event.data.modelList;
const clickedProject = event.data.clickedProject;
window.showModelPreview(models, clickedProject);
}
});
// 也监听 uni 的消息(如果支持)
if (typeof uni !== 'undefined' && uni.on) {
uni.on('modelPreviewData', function(data) {
if (data && data.modelList) {
window.showModelPreview(data.modelList, data.clickedProject);
}
});
}
// 监听来自 Vue 组件的模型数据响应
window.addEventListener('message', function(event) {
// 处理模型数据响应
if (event.data && event.data.action === 'modelDataResponse') {
const modelData = event.data.modelData || [];
if (modelData.length > 0) {
drawModel(modelData);
}
}
});
})
</script>
</html>