地图集成分析.md
8.46 KB
地图集成分析
最后更新: 2026-02-09 相关文件:
-
src/api/map.js- 地图 API -
src/components/Floor/- 楼层平面图组件 -
src/components/InfoWindow*.vue- 信息窗口组件 -
src/common/map_data.js- 地图数据处理
技术栈
地图服务提供商
高德地图 (AMap)
import AMapLoader from '@amap/amap-jsapi-loader';
版本: 1.0.1 文档: https://lbs.amap.com/api/jsapi-v2/summary
核心功能
1. 地图数据获取
API 端点: /srv/?a=map
// src/api/map.js
export const mapAPI = (params) => fn(fetch.get(Api.MAP, params));
请求参数:
-
id: 地图/位置 ID - 其他业务参数
响应数据结构(推测):
{
code: 1,
data: {
coordinates: [...], // 坐标数据
markers: [...], // 标记点数据
polygons: [...], // 多边形数据
// 其他地图数据
},
msg: ''
}
2. 楼层平面图组件 (Floor)
组件路径: src/components/Floor/index.vue
功能:
- ✅ 多楼层切换(1-4 层)
- ✅ SVG 交互式平面图
- ✅ 标记点(Pin)显示与点击
- ✅ 区域动画(安全区、厕所、入口)
- ✅ VR 全景入口
- ✅ 搜索功能
楼层切换:
// 左右箭头切换楼层
switchFloor('left') // 上一层
switchFloor('right') // 下一层
标记点交互:
<a @click="clickPin(item, $event)"
class="pin"
:data-category="item.category"
:data-space="item.space">
<!-- 标记点图标 -->
</a>
区域动画:
// 触发区域动画
createAnimation(true, levelIndex - 1, 'safe') // 安全区动画
createAnimation(true, levelIndex - 1, 'toilet') // 厕所动画
createAnimation(true, levelIndex - 1, 'door') // 入口动画
工具栏功能:
- 🔍 搜索按钮
- ❌ 关闭按钮
- ⬆️⬇️ 楼层切换
- 📺 VR 全景入口
- ⭐ 区域动画开关
3. 信息窗口组件
多种样式:
-
InfoWindowLite.vue- 轻量版 -
InfoWindowWarn.vue- 警告版 -
InfoWindowYard.vue- 院落版
功能:
- 显示位置信息
- 显示音频播放按钮
- 显示图片
- 显示操作按钮
4. 坐标系统
数据来源: src/common/map_data.js
坐标类型:
- 网格坐标: 用于平面图定位
- GPS 坐标: 用于地图定位
- 像素坐标: 用于 SVG 渲染
坐标转换(推测):
// 网格坐标 → 像素坐标
function gridToPixel(gridX, gridY, level) {
// 转换逻辑
return { x: pixelX, y: pixelY };
}
// GPS 坐标 → 网格坐标
function gpsToGrid(lat, lng) {
// 转换逻辑
return { x: gridX, y: gridY };
}
使用示例
1. 加载地图数据
import { mapAPI } from '@/api/map.js';
// 获取地图数据
const { data } = await mapAPI({ id: locationId });
// 处理坐标数据
if (data && data.coordinates) {
// 渲染地图
}
2. 使用 Floor 组件
<template>
<Floor
:level-list="floorData"
:current-level="currentFloor"
@pin-click="handlePinClick"
@floor-change="handleFloorChange"
@vr-click="handleVRClick"
/>
</template>
<script setup>
import Floor from '@components/Floor/index.vue';
import { ref } from 'vue';
const floorData = ref([
{
svg: '<svg>...</svg>',
pin: [
{
category: 'entrance',
space: 'main',
icon: 'door',
style: { left: '100px', top: '200px' }
}
]
}
]);
const currentFloor = ref(1);
const handlePinClick = (pin, event) => {
console.log('点击标记点:', pin);
// 显示信息窗口
// 播放音频
};
const handleFloorChange = (floor) => {
currentFloor.value = floor;
};
const handleVRClick = () => {
// 打开 VR 全景
};
</script>
3. 显示信息窗口
<template>
<InfoWindowLite
v-model:show="showInfoWindow"
:title="locationTitle"
:description="locationDesc"
:audio-url="audioUrl"
:images="locationImages"
/>
</template>
<script setup>
import InfoWindowLite from '@components/InfoWindowLite.vue';
import { ref } from 'vue';
const showInfoWindow = ref(false);
const locationTitle = ref('标题');
const locationDesc = ref('描述');
const audioUrl = ref('/audio/guide.mp3');
const locationImages = ref(['/img/1.jpg', '/img/2.jpg']);
</script>
地图集成流程
初始化流程
1. 加载高德地图 JS API
↓
2. 创建地图实例
↓
3. 获取地图数据 (mapAPI)
↓
4. 渲染平面图 (Floor 组件)
↓
5. 添加标记点 (Pin)
↓
6. 绑定交互事件
交互流程
用户点击标记点
↓
触发 clickPin 事件
↓
显示信息窗口 (InfoWindow)
↓
播放音频 (audioBackground/audioList)
↓
可选:打开 VR 全景 (VRViewer)
地图数据结构
响应数据示例
{
"code": 1,
"data": {
"id": 1,
"name": "别院",
"floors": [
{
"level": 1,
"svg": "<svg>...</svg>",
"pins": [
{
"id": 1,
"category": "entrance",
"space": "main",
"icon": "door",
"style": {
"left": "100px",
"top": "200px"
},
"info": {
"title": "正门入口",
"description": "...",
"audioUrl": "/audio/entrance.mp3",
"images": ["/img/1.jpg"]
}
}
]
}
],
"coordinates": {
"center": { "lat": 31.230416, "lng": 121.473701 },
"bounds": {
"northeast": { "lat": 31.231516, "lng": 121.474801 },
"southwest": { "lat": 31.229316, "lng": 121.472601 }
}
}
},
"msg": ""
}
性能优化
1. 懒加载
// 楼层 SVG 懒加载
const loadFloorSVG = async (level) => {
const { data } = await mapAPI({ id: locationId, level });
return data.floors[level - 1].svg;
};
2. 缓存策略
// 缓存地图数据
const mapDataCache = new Map();
export const mapAPI = async (params) => {
const cacheKey = JSON.stringify(params);
if (mapDataCache.has(cacheKey)) {
return { code: 1, data: mapDataCache.get(cacheKey) };
}
const result = await fn(fetch.get(Api.MAP, params));
if (result.code === 1) {
mapDataCache.set(cacheKey, result.data);
}
return result;
};
3. SVG 优化
- 使用简洁的 SVG 路径
- 压缩 SVG 文件大小
- 使用
v-html动态渲染(注意 XSS 风险)
已知问题
1. SVG 渲染性能
问题: 复杂 SVG 可能导致渲染卡顿
解决方案:
- 简化 SVG 路径
- 使用
will-change属性 - 虚拟滚动(如果标记点很多)
2. 坐标系混乱
问题: 多种坐标系容易混淆
解决方案:
- 统一使用网格坐标
- 提供统一的坐标转换工具函数
- 文档化每种坐标系的用途
3. XSS 风险
问题: v-html 渲染 SVG 可能存在 XSS 风险
解决方案:
// 清理 SVG 字符串
import DOMPurify from 'dompurify';
const cleanSVG = DOMPurify.sanitize(svgString);
最佳实践
1. 组件使用
<!-- ✅ 推荐:使用 v-model 绑定显示状态 -->
<Floor v-model:show="showFloor" />
<!-- ❌ 不推荐:手动控制显示隐藏 -->
<Floor :show="showFloor" @close="showFloor = false" />
2. 事件处理
// ✅ 推荐:使用事件修饰符
<div @click.stop="handlePinClick">
// ❌ 不推荐:在事件处理函数中阻止冒泡
<div @click="handlePinClick">
3. 数据缓存
// ✅ 推荐:使用 Pinia 缓存地图数据
import { useMapStore } from '@/store/map';
const mapStore = useMapStore();
const mapData = await mapStore.fetchMapData(locationId);
// ❌ 不推荐:每次都重新获取数据
const { data } = await mapAPI({ id: locationId });
调试技巧
1. 查看地图数据
// 在控制台查看地图数据
console.log('地图数据:', JSON.stringify(mapData, null, 2));
2. 查看标记点
// 高亮所有标记点
document.querySelectorAll('.pin').forEach(pin => {
pin.style.border = '2px solid red';
});
3. 模拟点击
// 模拟点击标记点
document.querySelector('.pin').click();