<template>
<div ref="targetRef" class="app-no-padding-container">
<!-- 地图容器:高度由ResizeObserver动态计算 -->
<div id="allmap" :style="{ height: `${elementHeight}px`, width: '100%' }"></div>
</div>
</template>
<script setup>
import { onMounted, ref, onUnmounted, watch } from "vue";
import { useResizeObserver } from "@/utils/use-resize-observer";
// ------------------------------
// 对外暴露配置(父组件通过Props传递)
// ------------------------------
const props = defineProps({
// 初始中心点坐标(父组件可自定义)
center: {
type: Array,
default: () => [104.0426, 30.5561], // 默认成都坐标 [lng, lat]
},
// 初始缩放级别(父组件可自定义)
zoom: {
type: Number,
default: 11,
},
// 是否开启滚轮缩放(父组件可控制)
enableScrollWheelZoom: {
type: Boolean,
default: true,
},
// 是否开启拖拽(父组件可控制)
enableDragging: {
type: Boolean,
default: true,
},
// 地图倾斜角度
tilt: {
type: Number,
default: 35,
},
// 是否使用轨迹回放
isTrack: {
type: Boolean,
default: false,
},
// 轨迹经纬度数组
trackData: {
type: Array,
default: () => [],
},
});
// ------------------------------
// 对外传递事件(父组件可监听)
// ------------------------------
const emit = defineEmits([
"map-loaded", // 地图加载完成(传递地图实例)
"map-click", // 地图点击事件(传递点击坐标)
"map-resize", // 地图容器 resize 事件(传递新宽高)
"track-playing", //轨迹回放事件
"track-finished", //轨迹完成事件
]);
// ------------------------------
// 内部状态与工具
// ------------------------------
// ResizeObserver:动态计算容器高度
const { targetRef, elementHeight } = useResizeObserver();
// 地图实例(内部维护,对外暴露)
const mapInstance = ref(null);
// 百度地图命名空间(3.0+版本为BMapGL,避免混淆)
const BMapGL = ref(null);
// 轨迹命名空间
const track = ref(null);
// ------------------------------
// 地图初始化核心逻辑
// ------------------------------
// 加载百度地图API脚本// 加载地图依赖插件 + 地图主脚本
const loadMapScript = () => {
// 验证是否已加载(注意 three.js 暴露的是大写 THREE)
if (window.BMapGL && window.BMapGL.TrackAnimation && window.THREE) {
BMapGL.value = window.BMapGL;
initMap();
return;
}
// 插件列表(顺序必须严格:three.js → GLTFLoader → 轨迹插件)
const plugins = [
"https://unpkg.com/three@0.126.0/build/three.min.js", // 1. 核心库(暴露 THREE)
"https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js", // 2. 依赖 THREE
"https://unpkg.com/@bmapgl-plugin/track",
"//mapopen-pub-jsapi.bj.bcebos.com/jsapiGlgeo/track.js",
];
// 链式加载函数(确保按顺序加载)
const loadPlugin = (index) => {
if (index >= plugins.length) {
// 所有插件加载完成,加载地图主脚本
loadBaiduMapMainScript();
return;
}
const pluginUrl = plugins[index];
const script = document.createElement("script");
script.type = "text/javascript";
script.src = pluginUrl;
// 加载成功回调
script.onload = () => {
console.log(`插件加载成功:${pluginUrl}`);
// 验证 three.js 是否正确暴露 THREE(仅在加载第一个插件时检查)
if (index === 0 && !window.THREE) {
console.error("three.js 加载失败,未找到 THREE 全局对象");
return; // 终止后续加载
}
// 加载下一个插件
loadPlugin(index + 1);
};
// 加载失败回调
script.onerror = () => {
console.error(`插件加载失败:${pluginUrl}`);
// 若 three.js 加载失败,直接终止(后续插件依赖它)
if (index === 0) {
emit("map-loaded", { success: false, error: "three.js 加载失败,无法继续" });
}
};
document.body.appendChild(script);
};
// 从第一个插件开始加载(链式执行)
loadPlugin(0);
// 加载百度地图主脚本(逻辑不变)
const loadBaiduMapMainScript = () => {
const mapScript = document.createElement("script");
mapScript.type = "text/javascript";
mapScript.className = "loadmap";
mapScript.src = `https://api.map.baidu.com/getscript?v=1.0&type=webgl&ak=98RJrQoJ9zhIsMGF5ykJjDMlotBMSy5E`;
mapScript.onload = () => {
BMapGL.value = window.BMapGL;
initMap();
// 清理旧脚本
const oldMapScripts = document.getElementsByClassName("loadmap");
Array.from(oldMapScripts).forEach((el) => {
if (el !== mapScript) document.body.removeChild(el);
});
};
mapScript.onerror = () => {
console.error("百度地图API加载失败");
emit("map-loaded", { success: false, error: "API加载失败" });
};
document.body.appendChild(mapScript);
};
};
// 初始化地图(API加载完成后执行)
const initMap = () => {
if (!BMapGL.value) return;
console.log(window);
try {
// 1. 创建地图实例(绑定容器)
const map = new BMapGL.value.Map("allmap", {
style: "grayed-out",
displayOptions: {
indoor: false,
poiText: false,
poiIcon: false,
building: false,
},
});
mapInstance.value = map;
// 2. 应用父组件传递的配置(中心点、缩放级别、城市)
const centerPoint = new BMapGL.value.Point(props.center[0], props.center[1]);
map.centerAndZoom(centerPoint, props.zoom);
// 3. 应用交互配置(滚轮缩放、拖拽)
// 滚轮缩放:props为true时开启
if (props.enableScrollWheelZoom) {
map.enableScrollWheelZoom(true);
}
// 拖拽:props为true时开启
if (props.enableDragging) {
map.enableDragging(true);
}
//设置地图倾斜角度
if (props.tilt) {
map.setTilt(props.tilt);
}
// 生成轨迹实例
if (props.isTrack) {
track.value = new window.Track.View(map, {
lineLayerOptions: {
style: {
strokeWeight: 18,
strokeLineJoin: "round",
strokeLineCap: "round",
},
},
});
}
// 4. 绑定地图事件(传递给父组件)
bindMapEvents(map);
// 5. 通知父组件:地图加载完成(传递地图实例和命名空间)
emit("map-loaded", {
success: true,
map: mapInstance.value, // 原生地图实例(父组件可直接调用API)
BMapGL: BMapGL.value, // 百度地图命名空间(父组件可创建Marker/Polyline等)
});
} catch (error) {
console.error("地图初始化失败:", error);
emit("map-loaded", { success: false, error: error.message });
}
};
//初始化轨迹
const trackLine = ref(null);
function initTrack() {
// 1. 容错:确保轨迹视图、父组件轨迹数据都存在
if (!track.value || !props.trackData || props.trackData.length === 0) {
console.warn("轨迹视图未初始化或轨迹数据为空,无法创建轨迹");
return;
}
const tempTrackData = ref([]);
const colorOffset = ref([]);
for (const item of props.trackData) {
const point = new window.BMapGL.Point(item.longittude, item.latitude);
const trackPoint = new window.Track.TrackPoint(point);
tempTrackData.value.push(trackPoint);
const choose = [0.9, 0.5, 0.1];
const color = choose[Math.floor(Math.random() * choose.length)];
colorOffset.value.push(color);
}
// 创建本地轨迹实例
trackLine.value = new window.Track.LocalTrack({
trackPath: tempTrackData.value,
duration: 60,
style: {
sequence: true,
marginLength: 32,
arrowColor: "#fff",
strokeTextureUrl: "//mapopen-pub-jsapi.bj.bcebos.com/jsapiGlgeo/img/down.png",
strokeTextureWidth: 64,
strokeTextureHeight: 32,
traceColor: [27, 142, 236],
},
linearTexture: [
[0, "#f45e0c"],
[0.5, "#f6cd0e"],
[1, "#2ad61d"],
],
gradientColor: colorOffset,
});
// 监听轨迹线状态变化并自动动态视口适配
trackLine.value.on(window.Track.LineCodes.STATUS, (status) => {
switch (status) {
case window.Track.StatusCodes.PLAY: //播放中
case window.Track.StatusCodes.RESUME: //从暂停恢复播放
break;
case window.Track.StatusCodes.INIT: //轨迹初始化完成
case window.Track.StatusCodes.PAUSE: //轨迹暂停
case window.Track.StatusCodes.STOP: //轨迹停止
case window.Track.StatusCodes.FINISH: {
//轨迹播放完成
const box = trackLine.value.getBBox();
if (box) {
const bounds = [
new window.BMapGL.Point(box[0], box[1]),
new window.BMapGL.Point(box[2], box[3]),
];
map.setViewport(bounds);
}
break;
}
default:
break;
}
});
track.value.addTrackLine(trackLine.value);
track.value.focusTrack(trackLine.value);
//创建移动图标
const movePoint = new window.Track.GroundPoint({
point: tempTrackData.value[0].getPoint(),
style: {
url: "//mapopen-pub-jsapi.bj.bcebos.com/jsapiGlgeo/img/car.png",
level: 18,
scale: 1,
size: new BMapGL.value.Size(16, 32),
anchor: new BMapGL.value.Size(0.5, 0.5),
},
});
//移动图标绑定监听事件
movePoint.addEventListener(window.Track.MapCodes.CLICK, (e) => {
console.log("Track.GroundPoint.click", e);
});
movePoint.addEventListener(window.Track.MapCodes.MOUSE_OVER, (e) => {
console.log("Track.GroundPoint.MOUSE_OVER", e);
});
movePoint.addEventListener(window.Track.MapCodes.MOUSE_OUT, (e) => {
console.log("Track.GroundPoint.MOUSE_OUT", e);
});
trackLine.value.setMovePoint(movePoint);
}
// 清除现有轨迹(核心新增方法)
const clearTrack = () => {
if (!track.value || !trackLine.value) return;
try {
// 1. 停止轨迹动画
if (trackLine.value.stopAnimation) {
trackLine.value.stopAnimation();
}
// 2. 移除轨迹线
track.value.removeTrackLine(trackLine.value);
// 3. 销毁轨迹线实例(释放资源)
if (trackLine.value.destroy) {
trackLine.value.destroy();
}
// 4. 重置轨迹线引用
trackLine.value = null;
} catch (error) {
console.error("清除轨迹失败:", error);
}
};
// 深度监听trackData变化(核心新增逻辑)
watch(
() => props.trackData,
(newData) => {
// 地图和轨迹视图初始化完成后才处理
if (mapInstance.value && track.value) {
// 先清除原有轨迹,再初始化新轨迹
clearTrack();
// 新数据不为空时才初始化
if (newData && newData.length > 0) {
initTrack();
}
}
},
{ deep: true, immediate: false } // deep: 深度监听数组和对象变化
);
// 绑定地图事件(传递给父组件)
const bindMapEvents = (map) => {
// 地图点击事件:传递点击的经纬度
map.addEventListener("click", (e) => {
emit("map-click", {
lng: e.latlng.lng,
lat: e.latlng.lat,
point: e.latlng, // 原生Point对象
});
});
// 地图容器resize事件:传递新高度(配合ResizeObserver)
// watch(elementHeight, (newHeight) => {
// emit("map-resize", { height: newHeight, width: "100%" });
// });
};
// ------------------------------
// 对外暴露组件内部方法(父组件可调用)
// ------------------------------
defineExpose({
// 手动设置地图中心点(父组件可调用)
setCenter: (lng, lat) => {
if (!mapInstance.value || !BMapGL.value) return;
const point = new BMapGL.value.Point(lng, lat);
mapInstance.value.centerAndZoom(point, props.zoom);
},
// 手动添加标记(父组件可调用)
addMarker: (lng, lat, options = {}) => {
if (!mapInstance.value || !BMapGL.value) return null;
const point = new BMapGL.value.Point(lng, lat);
const marker = new BMapGL.value.Marker(point, options);
mapInstance.value.addOverlay(marker);
return marker; // 返回标记实例,父组件可后续操作(如绑定点击事件)
},
// 获取当前地图实例(父组件可直接操作)
getMapInstance: () => mapInstance.value,
//轨迹开始回放
startAnimation: () => {
trackLine.value.startAnimation();
},
//轨迹停止回放
stopAnimation: () => {
trackLine.value.stopAnimation();
},
//轨迹继续回放
pauseAnimation: () => {
trackLine.value.pauseAnimation();
},
//轨迹重置
resumeAnimation: () => {
trackLine.value.resumeAnimation();
},
});
// ------------------------------
// 组件生命周期
// ------------------------------
// 组件挂载:加载地图
onMounted(() => {
loadMapScript();
});
// 组件卸载:清理地图资源(避免内存泄漏)
onUnmounted(() => {
if (mapInstance.value) {
// 清除所有覆盖物(Marker、轨迹等)
mapInstance.value.clearOverlays();
// 销毁地图实例
mapInstance.value.destroy();
mapInstance.value = null;
}
// 清理轨迹资源
clearTrack();
if (track.value && track.value.destroy) {
track.value.destroy();
track.value = null;
}
// 移除地图API脚本
const oldScripts = document.getElementsByClassName("loadmap");
Array.from(oldScripts).forEach((el) => document.body.removeChild(el));
});
</script>
<style scoped>
#allmap {
transition: height 0.3s ease;
}
</style>
为什么没有显示轨迹线