2025-07-29 09:25:24 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html>
|
|
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2025-07-31 01:33:54 +08:00
|
|
|
|
|
|
|
|
|
|
<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"
|
2025-10-28 10:18:47 +08:00
|
|
|
|
src="https://api.map.baidu.com/api?v=3.0&&type=webgl&ak=iqyZkSZPurf61MhFV7hesbDukHdMBEEb"></script>
|
2025-07-31 01:33:54 +08:00
|
|
|
|
|
2025-07-29 09:25:24 +08:00
|
|
|
|
<title>百度地图</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
#map-container {
|
|
|
|
|
|
width: 100vw;
|
|
|
|
|
|
height: 100vh;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
position: relative;
|
2025-07-29 09:25:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 去除百度地图的水印和logo */
|
|
|
|
|
|
.BMap_cpyCtrl,
|
|
|
|
|
|
.anchorBL {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
/* 模型预览面板 */
|
|
|
|
|
|
.model-preview-panel {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
display: flex;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
pointer-events: none; /* 允许点击穿透到地图 */
|
|
|
|
|
|
align-items: flex-start; /* 顶部对齐 */
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.model-preview-tree {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
width: 300px;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
height: auto;
|
2026-01-12 17:42:45 +08:00
|
|
|
|
max-height: 80vh;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
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;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
overflow-y: auto;
|
2026-01-12 17:42:45 +08:00
|
|
|
|
overflow-x: hidden;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
padding: 20px;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
pointer-events: auto; /* 列表区域可以交互 */
|
|
|
|
|
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
2026-01-12 17:42:45 +08:00
|
|
|
|
|
|
|
|
|
|
/* 自定义滚动条样式 */
|
|
|
|
|
|
.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);
|
|
|
|
|
|
}
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
.tree-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: bold;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
color: #fff;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
|
border-bottom: 2px solid #002db6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tree-node {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
margin-bottom: 4px;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
2026-01-12 17:42:45 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
.tree-node-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-01-12 17:42:45 +08:00
|
|
|
|
padding: 6px 8px;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
transition: background-color 0.2s;
|
2026-01-12 17:42:45 +08:00
|
|
|
|
min-height: 28px;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
.tree-node-item:hover {
|
2026-01-12 15:17:19 +08:00
|
|
|
|
background-color: #323232;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tree-node-checkbox {
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
margin-right: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tree-node-label {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 14px;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
color: #fff;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
.model-preview-close {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 20px;
|
|
|
|
|
|
right: 20px;
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
border: 1px solid rgba(221, 221, 221, 0.5);
|
2026-01-12 13:35:59 +08:00
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
cursor: pointer;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
2026-01-12 13:35:59 +08:00
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
z-index: 1001;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
pointer-events: auto; /* 关闭按钮可以点击 */
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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);
|
|
|
|
|
|
}
|
2025-07-29 09:25:24 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div id="map-container"></div>
|
2026-01-12 13:35:59 +08:00
|
|
|
|
<!-- 模型预览面板 -->
|
|
|
|
|
|
<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>
|
2025-07-31 01:33:54 +08:00
|
|
|
|
</body>
|
2025-07-29 09:25:24 +08:00
|
|
|
|
|
2025-07-31 01:33:54 +08:00
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
|
document.addEventListener('UniAppJSBridgeReady', function () {
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 全局变量
|
|
|
|
|
|
let map = null;
|
|
|
|
|
|
let allOverlays = []; // 保存所有覆盖物
|
|
|
|
|
|
let currentClickedProject = null; // 当前点击的项目信息
|
2026-01-12 17:42:45 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-08-01 17:02:21 +08:00
|
|
|
|
|
|
|
|
|
|
function getUrlParams() {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
const projectInfoStr = getUrlParam('projectInfo');
|
|
|
|
|
|
if (!projectInfoStr) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
const projectInfo = JSON.parse(projectInfoStr);
|
|
|
|
|
|
return projectInfo;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('解析 projectInfo 失败:', e);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
2025-08-01 17:02:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 16:16:46 +08:00
|
|
|
|
const projectInfo = getUrlParams()
|
2025-08-01 17:02:21 +08:00
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 检查 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 可能不可用
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存点击的项目信息
|
|
|
|
|
|
if (clickedProjectParam) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
currentClickedProject = JSON.parse(decodeURIComponent(clickedProjectParam));
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('解析点击项目信息失败:', e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 先初始化地图
|
2025-09-23 16:16:46 +08:00
|
|
|
|
initMap(projectInfo)
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
2026-01-12 15:17:19 +08:00
|
|
|
|
// 如果 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 中有模型预览请求(已有模型列表的情况),在页面加载完成后显示
|
2026-01-12 13:35:59 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-01 17:02:21 +08:00
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 处理模型数据响应(通过 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 17:02:21 +08:00
|
|
|
|
function initMap(proInfo) {
|
2026-01-12 13:35:59 +08:00
|
|
|
|
map = new BMapGL.Map('map-container') // 创建地图实例
|
2025-09-23 16:16:46 +08:00
|
|
|
|
let point = new BMapGL.Point(proInfo[0].lng, proInfo[0].lat) // 创建点坐标
|
2025-08-01 17:02:21 +08:00
|
|
|
|
map.centerAndZoom(point, 12) // 初始化地图,设置中心点坐标和地图级别
|
|
|
|
|
|
map.enableScrollWheelZoom(true) // 启用滚轮放大缩小
|
2025-09-23 16:16:46 +08:00
|
|
|
|
projectInfo.forEach(item => {
|
|
|
|
|
|
handleProjectInfoOnMap(map, item)
|
|
|
|
|
|
})
|
2025-08-01 17:02:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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), {
|
2026-01-12 13:35:59 +08:00
|
|
|
|
anchor: new BMapGL.Size(12, 24),
|
2025-08-01 17:02:21 +08:00
|
|
|
|
})
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
2025-08-01 17:02:21 +08:00
|
|
|
|
const marker = new BMapGL.Marker(projectPoint, {
|
|
|
|
|
|
icon: icon,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-12 10:13:52 +08:00
|
|
|
|
const label = new BMapGL.Label(`${projectInfo.proName} (${projectInfo.declination})`, {
|
2025-08-01 17:02:21 +08:00
|
|
|
|
position: projectPoint,
|
2026-01-12 13:35:59 +08:00
|
|
|
|
offset: new BMapGL.Size(0, 0),
|
2025-08-01 17:02:21 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
label.setStyle({
|
|
|
|
|
|
color: '#002db6',
|
|
|
|
|
|
backgroundColor: 'transparent',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
padding: '5px',
|
|
|
|
|
|
whiteSpace: 'nowrap',
|
|
|
|
|
|
fontSize: '18px',
|
|
|
|
|
|
fontWeight: 'bold',
|
2026-01-12 13:35:59 +08:00
|
|
|
|
transform: 'translateX(-45%)'
|
2025-08-01 17:02:21 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
map.addOverlay(marker)
|
2025-08-01 17:02:21 +08:00
|
|
|
|
map.addOverlay(label)
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存覆盖物引用
|
|
|
|
|
|
allOverlays.push({
|
|
|
|
|
|
marker: marker,
|
|
|
|
|
|
label: label,
|
|
|
|
|
|
projectInfo: projectInfo,
|
|
|
|
|
|
point: projectPoint
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-01 17:02:21 +08:00
|
|
|
|
marker.addEventListener('click', function () {
|
2026-01-12 13:35:59 +08:00
|
|
|
|
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>
|
|
|
|
|
|
`;
|
2025-09-23 16:16:46 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
const infoWindow = new BMapGL.InfoWindow(menuHtml, {
|
|
|
|
|
|
width: 170,
|
|
|
|
|
|
height: 130,
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
enableMessage: false,
|
|
|
|
|
|
offset: new BMapGL.Size(0, -20),
|
|
|
|
|
|
enableAutoPan: true
|
|
|
|
|
|
});
|
2025-09-23 16:16:46 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
map.openInfoWindow(infoWindow, point);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const menuContainer = document.querySelector('.action-menu');
|
|
|
|
|
|
if (menuContainer) {
|
2026-01-12 15:17:19 +08:00
|
|
|
|
// 处理点击和触摸事件(兼容安卓)
|
|
|
|
|
|
const handleAction = function(e) {
|
|
|
|
|
|
// 阻止默认行为和事件冒泡
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
const target = e.target.closest('.action-menu-item');
|
|
|
|
|
|
if (!target) return;
|
|
|
|
|
|
|
|
|
|
|
|
const action = target.getAttribute('data-action');
|
|
|
|
|
|
map.closeInfoWindow();
|
|
|
|
|
|
|
|
|
|
|
|
if (action === 'survey') {
|
|
|
|
|
|
// 勘察 - 原来的逻辑
|
2026-01-12 15:17:19 +08:00
|
|
|
|
const surveyMessage = {
|
|
|
|
|
|
data: {
|
|
|
|
|
|
action: 'navigateToProject',
|
|
|
|
|
|
projectInfo: projectInfo
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
2026-01-12 15:17:19 +08:00
|
|
|
|
// 尝试多种方式发送消息
|
2026-01-12 13:35:59 +08:00
|
|
|
|
try {
|
2026-01-12 15:17:19 +08:00
|
|
|
|
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
|
|
|
|
|
|
uni.postMessage(surveyMessage);
|
|
|
|
|
|
} else if (window.parent && window.parent !== window) {
|
|
|
|
|
|
window.parent.postMessage(surveyMessage, '*');
|
|
|
|
|
|
}
|
2026-01-12 13:35:59 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('发送勘察消息失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (action === 'preview') {
|
|
|
|
|
|
// 模型预览
|
2026-01-12 15:17:19 +08:00
|
|
|
|
sendModelPreviewMessage(projectInfo);
|
2025-09-23 16:16:46 +08:00
|
|
|
|
}
|
2026-01-12 15:17:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 同时监听 click 和 touchstart 事件(安卓兼容)
|
|
|
|
|
|
menuContainer.addEventListener('click', handleAction);
|
|
|
|
|
|
menuContainer.addEventListener('touchend', function(e) {
|
|
|
|
|
|
// 触摸事件需要立即处理,避免延迟
|
|
|
|
|
|
handleAction(e);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 防止触摸时触发点击(避免重复触发)
|
|
|
|
|
|
let touchStartTime = 0;
|
|
|
|
|
|
menuContainer.addEventListener('touchstart', function(e) {
|
|
|
|
|
|
touchStartTime = Date.now();
|
2025-09-23 16:16:46 +08:00
|
|
|
|
});
|
2026-01-12 15:17:19 +08:00
|
|
|
|
|
|
|
|
|
|
menuContainer.addEventListener('click', function(e) {
|
|
|
|
|
|
// 如果刚刚有触摸事件,忽略点击事件(避免重复)
|
|
|
|
|
|
if (Date.now() - touchStartTime < 300) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, true);
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}, 200);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送模型预览消息的辅助函数
|
|
|
|
|
|
function sendModelPreviewMessage(projectInfo) {
|
|
|
|
|
|
// 根据 uni-app 文档,使用 data 对象格式(不是数组)
|
|
|
|
|
|
const messageData = {
|
|
|
|
|
|
data: {
|
|
|
|
|
|
action: 'modelPreview',
|
|
|
|
|
|
projectInfo: projectInfo
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-12 15:17:19 +08:00
|
|
|
|
// 方案1: 尝试使用 uni.postMessage(iOS 和部分安卓)
|
|
|
|
|
|
let messageSent = false;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
try {
|
2026-01-12 15:17:19 +08:00
|
|
|
|
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
|
|
|
|
|
|
uni.postMessage(messageData);
|
|
|
|
|
|
messageSent = true;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
2026-01-12 15:17:19 +08:00
|
|
|
|
} 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.messageHandlers(iOS 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 {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 获取当前 URL 参数(使用兼容方式)
|
|
|
|
|
|
const currentUrlParams = parseUrlParams(window.location.search);
|
|
|
|
|
|
const currentProjectInfo = currentUrlParams['projectInfo'] || '';
|
2026-01-12 15:17:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建新的 URL,添加模型预览触发参数
|
|
|
|
|
|
const clickedProjectJson = encodeURIComponent(JSON.stringify(projectInfo));
|
|
|
|
|
|
const timestamp = Date.now();
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 构建完整的新 URL(保留 apiBaseUrl 和 token)
|
2026-01-12 15:17:19 +08:00
|
|
|
|
let newUrl = window.location.pathname;
|
2026-01-12 17:42:45 +08:00
|
|
|
|
const newParams = [];
|
2026-01-12 15:17:19 +08:00
|
|
|
|
if (currentProjectInfo) {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
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));
|
2026-01-12 15:17:19 +08:00
|
|
|
|
}
|
2026-01-12 17:42:45 +08:00
|
|
|
|
newParams.push('t=' + timestamp);
|
|
|
|
|
|
|
|
|
|
|
|
newUrl += '?' + newParams.join('&');
|
2026-01-12 15:17:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用 location.href 触发页面重新加载
|
|
|
|
|
|
// 注意:这会触发页面重新加载,但可以通过 URL 参数恢复状态
|
|
|
|
|
|
window.location.href = newUrl;
|
2026-01-12 13:35:59 +08:00
|
|
|
|
return;
|
2026-01-12 15:17:19 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('发送模型预览消息失败:', error);
|
|
|
|
|
|
alert('无法发送模型预览请求,请重试');
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空所有覆盖物
|
|
|
|
|
|
function clearAllOverlays() {
|
|
|
|
|
|
allOverlays.forEach(overlay => {
|
|
|
|
|
|
map.removeOverlay(overlay.marker);
|
|
|
|
|
|
map.removeOverlay(overlay.label);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 清空模型覆盖物
|
|
|
|
|
|
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],
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 恢复所有覆盖物
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 清空所有覆盖物和模型覆盖物
|
2026-01-12 13:35:59 +08:00
|
|
|
|
clearAllOverlays();
|
2026-01-12 17:42:45 +08:00
|
|
|
|
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));
|
2026-01-12 13:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 渲染树形结构
|
|
|
|
|
|
treeContainer.innerHTML = '';
|
2026-01-12 17:42:45 +08:00
|
|
|
|
if (projectTreeData.length === 0) {
|
2026-01-12 15:17:19 +08:00
|
|
|
|
treeContainer.innerHTML = '<div style="color: #aaa; text-align: center; padding: 20px;">暂无模型数据</div>';
|
2026-01-12 13:35:59 +08:00
|
|
|
|
} else {
|
2026-01-12 17:42:45 +08:00
|
|
|
|
projectTreeData.forEach(node => {
|
|
|
|
|
|
renderTreeNode(node, treeContainer, 0);
|
2025-08-01 17:02:21 +08:00
|
|
|
|
});
|
2026-01-12 13:35:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示面板
|
|
|
|
|
|
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';
|
|
|
|
|
|
|
2026-01-12 17:42:45 +08:00
|
|
|
|
// 清除模型覆盖物
|
|
|
|
|
|
clearModelOverlays();
|
|
|
|
|
|
|
|
|
|
|
|
// 清空已勾选的节点
|
|
|
|
|
|
checkedNodeIds = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 更新所有复选框状态
|
|
|
|
|
|
updateCheckboxStates();
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 恢复所有覆盖物
|
|
|
|
|
|
restoreAllOverlays();
|
|
|
|
|
|
|
|
|
|
|
|
// 定位到点击的坐标
|
|
|
|
|
|
if (currentClickedProject && currentClickedProject.lng && currentClickedProject.lat) {
|
|
|
|
|
|
const point = new BMapGL.Point(currentClickedProject.lng, currentClickedProject.lat);
|
2026-01-12 17:47:07 +08:00
|
|
|
|
locateToPoint(point, 12);
|
2025-08-01 17:02:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 绑定关闭按钮事件
|
|
|
|
|
|
document.getElementById('close-preview-btn').addEventListener('click', closeModelPreview);
|
2025-07-31 01:33:54 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 监听来自父组件的消息(用于接收模型列表)
|
|
|
|
|
|
// 注意: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);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-08-01 17:02:21 +08:00
|
|
|
|
|
2026-01-12 13:35:59 +08:00
|
|
|
|
// 也监听 uni 的消息(如果支持)
|
|
|
|
|
|
if (typeof uni !== 'undefined' && uni.on) {
|
|
|
|
|
|
uni.on('modelPreviewData', function(data) {
|
|
|
|
|
|
if (data && data.modelList) {
|
|
|
|
|
|
window.showModelPreview(data.modelList, data.clickedProject);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-01-12 17:42:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 监听来自 Vue 组件的模型数据响应
|
|
|
|
|
|
window.addEventListener('message', function(event) {
|
|
|
|
|
|
// 处理模型数据响应
|
|
|
|
|
|
if (event.data && event.data.action === 'modelDataResponse') {
|
|
|
|
|
|
const modelData = event.data.modelData || [];
|
|
|
|
|
|
if (modelData.length > 0) {
|
|
|
|
|
|
drawModel(modelData);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-01-12 13:35:59 +08:00
|
|
|
|
})
|
2025-07-31 01:33:54 +08:00
|
|
|
|
</script>
|
2025-07-29 09:25:24 +08:00
|
|
|
|
|
|
|
|
|
|
</html>
|