gis地图模型渲染
This commit is contained in:
parent
9f94393090
commit
14c2cd78cf
|
|
@ -1,18 +1,17 @@
|
|||
<!-- 在你的 Vue 组件中 -->
|
||||
<template>
|
||||
<view class="container">
|
||||
<web-view ref="baiduMap" :src="webViewUrl" :key="webViewKey" @message="handleWebViewMessage" @load="handleWebViewLoad"></web-view>
|
||||
<web-view :src="webViewUrl" :key="webViewKey" @message="handleWebViewMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useMemberStore } from '@/stores/modules/member'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getAllProjectListApi, getProjectModelListApi } from '@/services/projectSelect'
|
||||
import { getAllProjectListApi, getProjectModelListApi, getModelListByProjectIdApi } from '@/services/projectSelect'
|
||||
import { useGeomagnetism } from '@/hooks/useGeomagnetism';
|
||||
|
||||
const baiduMap = ref(null)
|
||||
const projectList = ref([])
|
||||
const webViewUrl = ref('')
|
||||
const webViewKey = ref(0) // 用于强制重新渲染 web-view
|
||||
|
|
@ -24,15 +23,10 @@ const getProjectList = async () => {
|
|||
projectList.value = res || []
|
||||
}
|
||||
|
||||
// 处理 web-view 加载完成事件
|
||||
const handleWebViewLoad = (event) => {
|
||||
// 当页面重新加载时,HTML 中的代码会自动检测 URL 参数并发送消息
|
||||
}
|
||||
|
||||
const handleWebViewMessage = (event) => {
|
||||
// 尝试多种数据格式
|
||||
let action, projectInfo;
|
||||
|
||||
|
||||
// 格式1: event.detail.data 是对象
|
||||
if (event.detail && event.detail.data) {
|
||||
if (event.detail.data.action) {
|
||||
|
|
@ -45,7 +39,7 @@ const handleWebViewMessage = (event) => {
|
|||
projectInfo = event.detail.data[0]?.projectInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 格式3: 直接访问 event.detail
|
||||
if (!action && event.detail) {
|
||||
if (event.detail.action) {
|
||||
|
|
@ -53,7 +47,7 @@ const handleWebViewMessage = (event) => {
|
|||
projectInfo = event.detail.projectInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 格式4: 安卓环境下可能的数据格式
|
||||
if (!action && event.detail && typeof event.detail === 'object') {
|
||||
if (event.detail.action) {
|
||||
|
|
@ -61,7 +55,7 @@ const handleWebViewMessage = (event) => {
|
|||
projectInfo = event.detail.projectInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!action) {
|
||||
console.warn('无法解析 action,消息格式可能不正确');
|
||||
return;
|
||||
|
|
@ -69,28 +63,32 @@ const handleWebViewMessage = (event) => {
|
|||
|
||||
if (action === 'modelPreview') {
|
||||
// 模型预览逻辑
|
||||
getProjectModelListApi({ projectId: projectInfo.proId }).then((res) => {
|
||||
getModelListByProjectIdApi({ projectId: projectInfo.proId }).then((res) => {
|
||||
if (res?.data?.length > 0) {
|
||||
// 使用 URL 参数方式传递数据(最可靠的方式)
|
||||
const modelListJson = JSON.stringify(res.data);
|
||||
const clickedProjectJson = JSON.stringify(projectInfo);
|
||||
|
||||
|
||||
// 重新构建项目信息
|
||||
const allProjectInfo = rebuildProjectInfo();
|
||||
|
||||
const allProjectInfo = buildProjectInfo(projectList.value);
|
||||
|
||||
// 构建新的 URL
|
||||
const projectInfoParam = encodeURIComponent(JSON.stringify(allProjectInfo));
|
||||
const modelListParam = encodeURIComponent(modelListJson);
|
||||
const clickedProjectParam = encodeURIComponent(clickedProjectJson);
|
||||
const timestamp = Date.now();
|
||||
|
||||
const newUrl = `/static/map.html?projectInfo=${projectInfoParam}&modelList=${modelListParam}&clickedProject=${clickedProjectParam}&action=showPreview&t=${timestamp}`;
|
||||
|
||||
|
||||
// 获取 API baseURL 和 token,传递给 HTML
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || '/api';
|
||||
const token = memberStore.token || '';
|
||||
|
||||
const newUrl = `/static/map.html?projectInfo=${projectInfoParam}&modelList=${modelListParam}&clickedProject=${clickedProjectParam}&action=showPreview&apiBaseUrl=${encodeURIComponent(apiBaseUrl)}&token=${encodeURIComponent(token)}&t=${timestamp}`;
|
||||
|
||||
// 强制重新渲染 web-view
|
||||
webViewKey.value += 1;
|
||||
// 先清空,再设置新值,确保触发更新
|
||||
webViewUrl.value = '';
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
webViewUrl.value = newUrl;
|
||||
}, 100);
|
||||
|
|
@ -112,7 +110,7 @@ const handleWebViewMessage = (event) => {
|
|||
projectId: projectInfo.proId,
|
||||
token: memberStore.token,
|
||||
},
|
||||
(result) => {},
|
||||
(result) => { },
|
||||
)
|
||||
} else {
|
||||
uni.$u.toast('该工程暂无模型数据')
|
||||
|
|
@ -124,9 +122,9 @@ const handleWebViewMessage = (event) => {
|
|||
// 计算磁偏角
|
||||
const { calculate } = useGeomagnetism();
|
||||
|
||||
// 重新构建项目信息的辅助函数
|
||||
const rebuildProjectInfo = () => {
|
||||
return projectList.value.map((item) => {
|
||||
// 构建项目信息(包含磁偏角)
|
||||
const buildProjectInfo = (items) => {
|
||||
return items.map((item) => {
|
||||
const res = calculate(item.latitude, item.longitude);
|
||||
const declVal = res?.declinationFormatted || (res?.declination !== undefined ? `${res.declination.toFixed(2)}°` : '0° 0\'');
|
||||
return {
|
||||
|
|
@ -144,22 +142,13 @@ const rebuildProjectInfo = () => {
|
|||
onLoad(() => {
|
||||
getProjectList().then(() => {
|
||||
if (projectList.value.length > 0) {
|
||||
const projectInfo = projectList.value.map((item) => {
|
||||
const res = calculate(item.latitude, item.longitude);
|
||||
// 使用格式化后的度分格式,如果没有则使用原始数值
|
||||
const declVal = res?.declinationFormatted || (res?.declination !== undefined ? `${res.declination.toFixed(2)}°` : '0° 0\'');
|
||||
return {
|
||||
lat: item.latitude,
|
||||
declination: declVal,
|
||||
lng: item.longitude,
|
||||
proName: item.proName,
|
||||
chargePerson: item.chargePerson,
|
||||
location: item.location,
|
||||
proId: item.proId,
|
||||
}
|
||||
})
|
||||
const projectInfo = buildProjectInfo(projectList.value);
|
||||
// 获取 API baseURL 和 token,传递给 HTML
|
||||
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || '/api';
|
||||
const token = memberStore.token || '';
|
||||
|
||||
const projectInfoJson = JSON.stringify(projectInfo)
|
||||
webViewUrl.value = `/static/map.html?projectInfo=${projectInfoJson}`
|
||||
webViewUrl.value = `/static/map.html?projectInfo=${projectInfoJson}&apiBaseUrl=${encodeURIComponent(apiBaseUrl)}&token=${encodeURIComponent(token)}`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -25,3 +25,12 @@ export const getProjectModelListApi = (data) => {
|
|||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 根据工程id获取模型列表
|
||||
export const getModelListByProjectIdApi = (data) => {
|
||||
return http({
|
||||
method: 'GET',
|
||||
url: `/model/listSelect`,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,19 +37,39 @@
|
|||
}
|
||||
|
||||
.model-preview-tree {
|
||||
width: 200px;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
max-height: 80%;
|
||||
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;
|
||||
|
|
@ -61,18 +81,20 @@
|
|||
}
|
||||
|
||||
.tree-node {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
|
||||
.tree-node-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
|
||||
.tree-node-item:hover {
|
||||
background-color: #323232;
|
||||
}
|
||||
|
|
@ -91,6 +113,21 @@
|
|||
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;
|
||||
|
|
@ -241,21 +278,73 @@
|
|||
let map = null;
|
||||
let allOverlays = []; // 保存所有覆盖物
|
||||
let currentClickedProject = null; // 当前点击的项目信息
|
||||
let modelList = []; // 模型列表
|
||||
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 params = new URLSearchParams(window.location.search);
|
||||
const projectInfo = JSON.parse(params.get('projectInfo'))
|
||||
return projectInfo
|
||||
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 = new URLSearchParams(window.location.search);
|
||||
const modelListParam = urlParams.get('modelList');
|
||||
const actionParam = urlParams.get('action');
|
||||
const clickedProjectParam = urlParams.get('clickedProject');
|
||||
// 检查 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) {
|
||||
|
|
@ -325,6 +414,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 处理模型数据响应(通过 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) // 创建点坐标
|
||||
|
|
@ -517,21 +624,31 @@
|
|||
// 方案4: 通过 URL 参数方式触发(最可靠的安卓方案,作为最后备选)
|
||||
if (!messageSent) {
|
||||
try {
|
||||
// 获取当前 URL 参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const currentProjectInfo = urlParams.get('projectInfo');
|
||||
// 获取当前 URL 参数(使用兼容方式)
|
||||
const currentUrlParams = parseUrlParams(window.location.search);
|
||||
const currentProjectInfo = currentUrlParams['projectInfo'] || '';
|
||||
|
||||
// 构建新的 URL,添加模型预览触发参数
|
||||
const clickedProjectJson = encodeURIComponent(JSON.stringify(projectInfo));
|
||||
const timestamp = Date.now();
|
||||
|
||||
// 构建完整的新 URL
|
||||
// 构建完整的新 URL(保留 apiBaseUrl 和 token)
|
||||
let newUrl = window.location.pathname;
|
||||
const newParams = [];
|
||||
if (currentProjectInfo) {
|
||||
newUrl += '?projectInfo=' + encodeURIComponent(currentProjectInfo);
|
||||
newParams.push('projectInfo=' + encodeURIComponent(currentProjectInfo));
|
||||
}
|
||||
newUrl += (currentProjectInfo ? '&' : '?') + 'clickedProject=' + clickedProjectJson;
|
||||
newUrl += '&action=modelPreview&t=' + timestamp;
|
||||
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 参数恢复状态
|
||||
|
|
@ -552,6 +669,446 @@
|
|||
});
|
||||
}
|
||||
|
||||
// 清空模型覆盖物
|
||||
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 => {
|
||||
|
|
@ -598,35 +1155,48 @@
|
|||
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 (modelList.length === 0) {
|
||||
if (projectTreeData.length === 0) {
|
||||
treeContainer.innerHTML = '<div style="color: #aaa; text-align: center; padding: 20px;">暂无模型数据</div>';
|
||||
} else {
|
||||
console.log('modelList',JSON.stringify(modelList));
|
||||
modelList.forEach((model, index) => {
|
||||
|
||||
const nodeDiv = document.createElement('div');
|
||||
nodeDiv.className = 'tree-node';
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'tree-node-item';
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'tree-node-checkbox';
|
||||
checkbox.checked = false;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'tree-node-label';
|
||||
label.textContent = model.name || model.modelName || model.label || `模型 ${index + 1}`;
|
||||
|
||||
itemDiv.appendChild(checkbox);
|
||||
itemDiv.appendChild(label);
|
||||
nodeDiv.appendChild(itemDiv);
|
||||
treeContainer.appendChild(nodeDiv);
|
||||
projectTreeData.forEach(node => {
|
||||
renderTreeNode(node, treeContainer, 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -651,6 +1221,15 @@
|
|||
const panel = document.getElementById('model-preview-panel');
|
||||
panel.style.display = 'none';
|
||||
|
||||
// 清除模型覆盖物
|
||||
clearModelOverlays();
|
||||
|
||||
// 清空已勾选的节点
|
||||
checkedNodeIds = [];
|
||||
|
||||
// 更新所有复选框状态
|
||||
updateCheckboxStates();
|
||||
|
||||
// 恢复所有覆盖物
|
||||
restoreAllOverlays();
|
||||
|
||||
|
|
@ -682,6 +1261,17 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听来自 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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue