模型预览

This commit is contained in:
cwchen 2026-01-12 13:35:59 +08:00
parent f73966d6be
commit ae94928ea7
2 changed files with 649 additions and 52 deletions

View File

@ -1,7 +1,7 @@
<!-- 在你的 Vue 组件中 -->
<template>
<view class="container">
<web-view ref="baiduMap" :src="webViewUrl" @message="handleWebViewMessage"></web-view>
<web-view ref="baiduMap" :src="webViewUrl" :key="webViewKey" @message="handleWebViewMessage"></web-view>
</view>
</template>
@ -15,6 +15,7 @@ import { useGeomagnetism } from '@/hooks/useGeomagnetism';
const baiduMap = ref(null)
const projectList = ref([])
const webViewUrl = ref('')
const webViewKey = ref(0) // web-view
const memberStore = useMemberStore()
//
@ -24,26 +25,127 @@ const getProjectList = async () => {
}
const handleWebViewMessage = (event) => {
getProjectModelListApi({ projectId: event.detail.data[0].projectInfo.proId }).then((res) => {
if (res?.data?.length > 0) {
const ctx = uni.requireNativePlugin('bonus-textodule')
ctx.openNativePage(
{
modelInfoList: res.data,
projectId: event.detail.data[0].projectInfo.proId,
token: memberStore.token,
},
(result) => {},
)
} else {
uni.$u.toast('该工程暂无模型数据')
console.log('=== 收到 web-view 消息 ===');
console.log('完整事件对象:', event);
console.log('event.detail:', event.detail);
console.log('event.detail.data:', event.detail.data);
console.log('event.detail.data 类型:', Array.isArray(event.detail.data) ? '数组' : typeof event.detail.data);
//
let action, projectInfo;
// 1: event.detail.data
if (event.detail && event.detail.data) {
if (event.detail.data.action) {
action = event.detail.data.action;
projectInfo = event.detail.data.projectInfo;
console.log('使用对象格式解析');
}
})
// 2: event.detail.data
else if (Array.isArray(event.detail.data) && event.detail.data.length > 0) {
action = event.detail.data[0]?.action;
projectInfo = event.detail.data[0]?.projectInfo;
console.log('使用数组格式解析');
}
}
// 3: 访 event.detail
if (!action && event.detail) {
if (event.detail.action) {
action = event.detail.action;
projectInfo = event.detail.projectInfo;
console.log('使用直接格式解析');
}
}
console.log('解析后的 action:', action);
console.log('解析后的 projectInfo:', projectInfo);
if (!action) {
console.warn('无法解析 action消息格式可能不正确');
console.warn('请检查 event.detail 的结构');
return;
}
if (action === 'modelPreview') {
console.log('收到模型预览请求projectId:', projectInfo?.proId);
//
getProjectModelListApi({ projectId: projectInfo.proId }).then((res) => {
console.log('获取模型列表成功:', res);
if (res?.data?.length > 0) {
// 使 URL
const modelListJson = JSON.stringify(res.data);
const clickedProjectJson = JSON.stringify(projectInfo);
//
const allProjectInfo = rebuildProjectInfo();
// 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}`;
console.log('准备更新 webViewUrlURL 长度:', newUrl.length);
// web-view
webViewKey.value += 1;
//
webViewUrl.value = '';
setTimeout(() => {
webViewUrl.value = newUrl;
console.log('webViewUrl 已更新');
}, 100);
} else {
console.warn('该工程暂无模型数据');
uni.$u.toast('该工程暂无模型数据');
}
}).catch((err) => {
console.error('获取模型列表失败:', err);
uni.$u.toast('获取模型列表失败');
});
} else if (action === 'navigateToProject') {
//
getProjectModelListApi({ projectId: projectInfo.proId }).then((res) => {
if (res?.data?.length > 0) {
const ctx = uni.requireNativePlugin('bonus-textodule')
ctx.openNativePage(
{
modelInfoList: res.data,
projectId: projectInfo.proId,
token: memberStore.token,
},
(result) => {},
)
} else {
uni.$u.toast('该工程暂无模型数据')
}
})
}
}
//
const { calculate } = useGeomagnetism();
//
const rebuildProjectInfo = () => {
return 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,
};
});
};
onLoad(() => {
getProjectList().then(() => {

View File

@ -14,6 +14,7 @@
#map-container {
width: 100vw;
height: 100vh;
position: relative;
}
/** 去除百度地图的水印和logo */
@ -21,71 +22,300 @@
.anchorBL {
display: none;
}
/* 模型预览面板 */
.model-preview-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
background: rgba(0, 0, 0, 0.3);
}
.model-preview-tree {
width: 300px;
height: 100%;
background: #fff;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
padding: 20px;
}
.tree-title {
font-size: 18px;
font-weight: bold;
color: #333;
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 {
background-color: #f5f5f5;
}
.tree-node-checkbox {
width: 18px;
height: 18px;
margin-right: 8px;
cursor: pointer;
}
.tree-node-label {
flex: 1;
font-size: 14px;
color: #333;
user-select: none;
}
.model-preview-close {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background: #fff;
border: 1px solid #ddd;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: all 0.2s;
z-index: 1001;
}
.model-preview-close:hover {
background: #f5f5f5;
transform: scale(1.1);
}
.model-preview-close::before,
.model-preview-close::after {
content: '';
position: absolute;
width: 2px;
height: 20px;
background: #666;
transform: rotate(45deg);
}
.model-preview-close::after {
transform: rotate(-45deg);
}
/* 点击菜单样式 */
.action-menu {
background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 45, 182, 0.12),
0 4px 16px rgba(0, 0, 0, 0.08),
0 2px 8px rgba(0, 0, 0, 0.04);
width: 100%;
height: 100%;
overflow: hidden;
border: 1px solid rgba(0, 45, 182, 0.08);
backdrop-filter: blur(10px);
padding: 0;
display: flex;
flex-direction: column;
justify-content: stretch;
align-items: stretch;
box-sizing: border-box;
}
.action-menu-item {
padding: 0 20px;
margin: 0;
cursor: pointer;
font-size: 15px;
color: #1a1a1a;
border-radius: 0;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: flex-start;
position: relative;
font-weight: 500;
letter-spacing: 0.2px;
flex: 1;
box-sizing: border-box;
text-align: left;
border-bottom: 1px solid rgba(0, 45, 182, 0.06);
}
.action-menu-item:first-child {
border-radius: 16px 16px 0 0;
}
.action-menu-item:last-child {
border-bottom: none;
border-radius: 0 0 16px 16px;
}
.action-menu-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 0;
background: linear-gradient(180deg, #002db6 0%, #0056e6 100%);
border-radius: 0 2px 2px 0;
opacity: 0;
transition: all 0.25s ease;
}
.action-menu-item:hover {
background: linear-gradient(135deg, #f0f4ff 0%, #e6edff 100%);
color: #002db6;
padding-left: 24px;
transform: translateX(2px);
box-shadow: inset 0 0 0 1px rgba(0, 45, 182, 0.1);
}
.action-menu-item:hover::before {
opacity: 1;
height: 60%;
}
.action-menu-item span {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin-right: 12px;
width: 24px;
height: 24px;
flex-shrink: 0;
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.action-menu-item:hover span {
transform: scale(1.15) rotate(5deg);
}
</style>
</head>
<body>
<div id="map-container"></div>
<!-- 模型预览面板 -->
<div id="model-preview-panel" class="model-preview-panel" style="display: none;">
<div class="model-preview-tree">
<div class="tree-title">模型列表</div>
<div id="tree-container"></div>
</div>
<div class="model-preview-close" id="close-preview-btn"></div>
</div>
</body>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function () {
// // 1. 初始化地图
// 全局变量
let map = null;
let allOverlays = []; // 保存所有覆盖物
let currentClickedProject = null; // 当前点击的项目信息
let modelList = []; // 模型列表
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
const projectInfo = JSON.parse(params.get('projectInfo'))
return projectInfo
}
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');
// 保存点击的项目信息
if (clickedProjectParam) {
try {
currentClickedProject = JSON.parse(decodeURIComponent(clickedProjectParam));
console.log('保存点击的项目信息:', currentClickedProject);
} catch (e) {
console.error('解析点击项目信息失败:', e);
}
}
// 先初始化地图
initMap(projectInfo)
// 如果 URL 中有模型预览请求,在页面加载完成后显示
if (actionParam === 'showPreview' && modelListParam) {
console.log('检测到模型预览请求');
try {
const models = JSON.parse(decodeURIComponent(modelListParam));
console.log('从 URL 参数获取模型列表,数量:', models.length);
// 定义一个函数来显示预览
const showPreviewWhenReady = () => {
if (map && typeof window.showModelPreview === 'function') {
console.log('地图和函数都已准备好,显示预览');
window.showModelPreview(models, currentClickedProject);
} else {
console.log('等待地图和函数准备...', {
map: !!map,
showModelPreview: typeof window.showModelPreview
});
setTimeout(showPreviewWhenReady, 200);
}
};
// 等待地图初始化完成后再显示预览
setTimeout(showPreviewWhenReady, 500);
} catch (e) {
console.error('解析模型列表失败:', e);
}
}
function initMap(proInfo) {
const map = new BMapGL.Map('map-container') // 创建地图实例
map = new BMapGL.Map('map-container') // 创建地图实例
let point = new BMapGL.Point(proInfo[0].lng, proInfo[0].lat) // 创建点坐标
map.centerAndZoom(point, 12) // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(true) // 启用滚轮放大缩小
projectInfo.forEach(item => {
handleProjectInfoOnMap(map, item)
})
// handleProjectInfoOnMap(map, proInfo)
}
function handleProjectInfoOnMap(map, projectInfo) {
// 示例1: 如果项目信息包含经纬度,移动地图到该位置
if (projectInfo.lng && projectInfo.lat) {
const projectPoint = new BMapGL.Point(projectInfo.lng, projectInfo.lat);
map.centerAndZoom(projectPoint, 15); // 移动并放大到项目位置
const icon = new BMapGL.Icon('./image/location.png', new BMapGL.Size(36, 36), {
anchor: new BMapGL.Size(12, 24), // 图标锚点,使图标底部中心点与坐标点重合
anchor: new BMapGL.Size(12, 24),
})
// 创建marker并应用自定义图标
const marker = new BMapGL.Marker(projectPoint, {
icon: icon,
})
map.addOverlay(marker)
// const infoWindow = new BMapGL.InfoWindow(`
// <h3 style="color: #002db6; margin: 0 0 10px 0; font-size: 18px;">${projectInfo.proName}</h3>
// <p>负责人: ${projectInfo.chargePerson}</p>
// <p>所在地: ${projectInfo.location}</p>
// `);
const label = new BMapGL.Label(`${projectInfo.proName} (${projectInfo.declination})`, {
position: projectPoint,
offset: new BMapGL.Size(0, 0), // 调整偏移量使文字在marker正下方
offset: new BMapGL.Size(0, 0),
})
// 设置label样式
label.setStyle({
color: '#002db6',
backgroundColor: 'transparent',
@ -95,29 +325,294 @@
whiteSpace: 'nowrap',
fontSize: '18px',
fontWeight: 'bold',
transform: 'translateX(-45%)' // 关键让Label向左移动自身宽度的一半
transform: 'translateX(-45%)'
});
map.addOverlay(marker)
map.addOverlay(label)
// 保存覆盖物引用
allOverlays.push({
marker: marker,
label: label,
projectInfo: projectInfo,
point: projectPoint
});
marker.addEventListener('click', function () {
// map.openInfoWindow(infoWindow, projectPoint);
// 向父组件传参然后跳转相关页面
uni.postMessage({
data: {
action: 'navigateToProject',
projectInfo: projectInfo
}
});
currentClickedProject = projectInfo;
showActionMenu(projectPoint, projectInfo);
});
}
}
// 显示操作菜单
function showActionMenu(point, projectInfo) {
const menuHtml = `
<div class="action-menu" style="overflow: hidden !important; padding: 0 !important; margin: 0 !important; box-sizing: border-box !important; width: 100% !important; height: 100% !important;">
<div class="action-menu-item" data-action="preview">
<span>🗺️</span>模型预览
</div>
<div class="action-menu-item" data-action="survey">
<span>📋</span>勘察
</div>
</div>
`;
const infoWindow = new BMapGL.InfoWindow(menuHtml, {
width: 170,
height: 130,
title: '',
enableMessage: false,
offset: new BMapGL.Size(0, -20),
enableAutoPan: true
});
map.openInfoWindow(infoWindow, point);
setTimeout(() => {
const menuContainer = document.querySelector('.action-menu');
if (menuContainer) {
menuContainer.addEventListener('click', function(e) {
const target = e.target.closest('.action-menu-item');
if (!target) return;
const action = target.getAttribute('data-action');
map.closeInfoWindow();
if (action === 'survey') {
// 勘察 - 原来的逻辑
console.log('发送勘察消息:', projectInfo);
try {
uni.postMessage({
data: {
action: 'navigateToProject',
projectInfo: projectInfo
}
});
console.log('勘察消息已发送');
} catch (error) {
console.error('发送勘察消息失败:', error);
}
} else if (action === 'preview') {
// 模型预览
console.log('准备发送模型预览消息:---', projectInfo);
console.log(uni.postMessage,'uni.postMessage')
console.log('uni 对象:', typeof uni !== 'undefined' ? uni : '未定义');
console.log('uni.postMessage:', typeof uni !== 'undefined' && typeof uni.postMessage === 'function' ? '存在' : '不存在');
// 确保 uni 对象已加载
if (typeof uni === 'undefined' || typeof uni.postMessage !== 'function') {
console.error('uni.postMessage 不可用,等待 UniAppJSBridgeReady...');
// 等待 uni 对象加载
setTimeout(() => {
if (typeof uni !== 'undefined' && typeof uni.postMessage === 'function') {
sendModelPreviewMessage(projectInfo);
} else {
console.error('uni.postMessage 仍然不可用');
}
}, 100);
} else {
sendModelPreviewMessage(projectInfo);
}
}
});
}
}, 200);
}
// 发送模型预览消息的辅助函数
function sendModelPreviewMessage(projectInfo) {
console.log('=== 开始发送模型预览消息 ===');
console.log('projectInfo:', projectInfo);
console.log('uni 对象类型:', typeof uni);
console.log('uni.postMessage 类型:', typeof (uni && uni.postMessage));
// 根据 uni-app 文档,使用 data 对象格式(不是数组)
const messageData = {
data: {
action: 'modelPreview',
projectInfo: projectInfo
}
};
console.log('准备发送的消息数据:', messageData);
try {
if (typeof uni === 'undefined') {
console.error('uni 对象未定义!');
return;
}
if (typeof uni.postMessage !== 'function') {
console.error('uni.postMessage 不是函数!');
console.log('uni 对象内容:', Object.keys(uni || {}));
return;
}
uni.postMessage(messageData);
console.log('✓ 模型预览消息已通过 uni.postMessage 发送');
} catch (error) {
console.error('✗ 发送消息失败:', error);
console.error('错误详情:', error.message);
console.error('错误堆栈:', error.stack);
}
}
// 清空所有覆盖物
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) {
console.log('showModelPreview 被调用,模型数量:', models?.length);
console.log('点击的项目信息:', clickedProject);
// 如果传入了点击的项目信息,保存它
if (clickedProject) {
currentClickedProject = clickedProject;
}
console.log('当前 map 对象:', map);
console.log('当前 allOverlays 数量:', allOverlays.length);
if (!map) {
console.error('地图未初始化,等待地图初始化...');
// 如果地图未初始化,等待一下再试
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;
}
// 清空所有覆盖物
console.log('开始清空覆盖物,当前数量:', allOverlays.length);
clearAllOverlays();
console.log('已清空所有覆盖物');
// 渲染树形结构
treeContainer.innerHTML = '';
if (modelList.length === 0) {
treeContainer.innerHTML = '<div style="color: #999; text-align: center; padding: 20px;">暂无模型数据</div>';
} else {
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);
});
console.log('已渲染', modelList.length, '个模型节点');
}
// 显示面板
panel.style.display = 'flex';
console.log('预览面板已显示');
};
// 带数据的预览函数(用于直接调用)
window.showModelPreviewWithData = function(models, clickedProject) {
console.log('showModelPreviewWithData 被调用');
window.showModelPreview(models, clickedProject);
};
// 监听 postMessage 消息
window.addEventListener('message', function(event) {
console.log('收到 postMessage:', event.data);
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);
}
}
// 绑定关闭按钮事件
document.getElementById('close-preview-btn').addEventListener('click', closeModelPreview);
// 监听来自父组件的消息(用于接收模型列表)
// 注意uni-app 的 web-view 消息传递机制
window.addEventListener('message', function(event) {
console.log('收到 window.message 事件:', event.data);
if (event.data && event.data.type === 'showModelPreview') {
const models = event.data.modelList;
const clickedProject = event.data.clickedProject;
window.showModelPreview(models, clickedProject);
}
});
// 也监听 uni 的消息(如果支持)
if (typeof uni !== 'undefined' && uni.on) {
uni.on('modelPreviewData', function(data) {
console.log('收到 uni 消息:', data);
if (data && data.modelList) {
window.showModelPreview(data.modelList, data.clickedProject);
}
});
}
})
</script>
</html>