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

688 lines
25 KiB
HTML
Raw Normal View History

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 15:17:19 +08:00
width: 200px;
height: auto;
max-height: 80%;
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;
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
}
.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 {
margin-bottom: 8px;
}
.tree-node-item {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.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;
}
.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; // 当前点击的项目信息
let modelList = []; // 模型列表
2025-08-01 17:02:21 +08:00
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
2025-09-23 16:16:46 +08:00
const projectInfo = JSON.parse(params.get('projectInfo'))
return projectInfo
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 13:35:59 +08:00
// 检查 URL 参数中是否有模型预览请求
const urlParams = new URLSearchParams(window.location.search);
const modelListParam = urlParams.get('modelList');
const actionParam = urlParams.get('action');
const clickedProjectParam = urlParams.get('clickedProject');
// 保存点击的项目信息
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
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.postMessageiOS 和部分安卓)
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.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 urlParams = new URLSearchParams(window.location.search);
const currentProjectInfo = urlParams.get('projectInfo');
// 构建新的 URL添加模型预览触发参数
const clickedProjectJson = encodeURIComponent(JSON.stringify(projectInfo));
const timestamp = Date.now();
// 构建完整的新 URL
let newUrl = window.location.pathname;
if (currentProjectInfo) {
newUrl += '?projectInfo=' + encodeURIComponent(currentProjectInfo);
}
newUrl += (currentProjectInfo ? '&' : '?') + 'clickedProject=' + clickedProjectJson;
newUrl += '&action=modelPreview&t=' + timestamp;
// 使用 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);
});
}
// 恢复所有覆盖物
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();
// 渲染树形结构
treeContainer.innerHTML = '';
if (modelList.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 15:17:19 +08:00
console.log('modelList',JSON.stringify(modelList));
2026-01-12 13:35:59 +08:00
modelList.forEach((model, index) => {
2026-01-12 15:17:19 +08:00
2026-01-12 13:35:59 +08:00
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);
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';
// 恢复所有覆盖物
restoreAllOverlays();
// 定位到点击的坐标
if (currentClickedProject && currentClickedProject.lng && currentClickedProject.lat) {
const point = new BMapGL.Point(currentClickedProject.lng, currentClickedProject.lat);
locateToPoint(point, 15);
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);
}
});
}
})
2025-07-31 01:33:54 +08:00
</script>
2025-07-29 09:25:24 +08:00
</html>