纯纯的干货废话不多说、手摸手教。
第一步
安装插件,有两种方式。
第一种是npm安装
$ npm install vue-baidu-map --save
第二种是直接引入cdn
<script src="https://unpkg.com/vue-baidu-map"></script>
第二步
注册组件:有局部注册、和全局注册
全局注册
import Vue from 'vue'
import BaiduMap from 'vue-baidu-map'
Vue.use(BaiduMap, {
// ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key */
ak: 'YOUR_APP_KEY'
})
<template>
<baidu-map class="bm-view">
</baidu-map>
</template>
<style>
.bm-view {
width: 100%;
height: 300px;
}
</style>
局部注册(俗称按需引入)
<template>
<baidu-map class="bm-view" ak="YOUR_APP_KEY">
</baidu-map>
</template>
<script>
import BaiduMap from 'vue-baidu-map/components/map/Map.vue'
export default {
components: {
BaiduMap
}
}
</script>
<style>
.bm-view {
width: 100%;
height: 300px;
}
</style>
按需引入组件的好处,可以选择局部注册百度地图组件,这将减少工程打包后的容量尺寸。
常见的问题可以查看官网Vue Baidu Map
第三步
实现这个功能的难点在与如何让轨迹在运动过程中可以设置图标、切图标跟随轨迹运动、在绘制的过程中视口也要不断的变化、让运动的视觉处于地图视口的中间、以便于在运动的时候跑出地图以外的地方去、视口没有跟随就会看不见。其次就是控制轨迹回放的速度、以及轨迹回放的进度、其实官网也有相关的属性、主要的是如何融入到你的业务中去、比如滑动调节速度、暂停播放轨迹等。
具体的实现看一下代码
<template>
<view>
<view class="guided-tour-container">
<baidu-map :style="{ height: windowHeight + 'px', width: windowWidth + 'px' }"
ak="你的百度地图的key" :scroll-wheel-zoom='true' @ready="initMap" :center="center"
:zoom="18">
<bm-geolocation anchor="BMAP_ANCHOR_TOP_RIGHT" :showAddressBar="false" :autoLocation="true"
@locationError="locationError" :offset="{ width: 15, height: 320 }"></bm-geolocation>
<bm-polyline :path="polylinePath" stroke-color="blue" :stroke-opacity="0.8" :stroke-weight="4"
:icons='icon' :editing="false" @lineupdate="updatePolylinePath"></bm-polyline>
<bm-marker v-for="(item, index) in listData" :key="index" :position="{ lng: item.lng, lat: item.lat }"
:icon="{
url: item.icon,
size: { width: 40, height: 48 }
}" :offset="{ width: -5, height: -17 }">
</bm-marker>
<!-- <bm-label v-for="(items, indexs) in listDatas" :key="'label' + indexs" :content="items.mcType"
:position="{ lng: items.lng, lat: items.lat }" :labelStyle="labelStyle"
:offset="{ width:-50, height:-10 }" title="Hover me" /> -->
<bml-lushu :path="path" :icon="carIcon" :play="play" :speed="speed" :rotation="true" :autoView="true"
ref="bmlLushu" @stop="stop" @ready="lushuReady"></bml-lushu>
</baidu-map>
<view class="btn">
<view class="speed" style="width: 85%;">
<image src="../../static/tezhong/sotp.png" style="margin-right: 30rpx;" class="icons" v-if="isPlay"
@click="starts"></image>
<image src="../../static/tezhong/play.png" style="margin-right: 30rpx;" class="icons" v-else
@click="starts"></image>
<!-- <van-slider v-model="progressWidth"/> -->
<u-line-progress active-color="#6693FD" :percent="progressWidth"></u-line-progress>
</view>
<view style="color: #fff;padding: 0 20rpx 0 20rpx;">
<!-- 2024-02-14 17:22:00 -->
</view>
<view class="speed" style="width: 100%;">
<view style="color: #fff;min-width: 60rpx;margin-right: 32rpx;">速度</view>
<van-slider v-model="num" active-color="#6693FD" max="2000" min="50" bar-height="15"
@change="changeSpeed">
<template #button>
<div class="custom-button">{{ num }}</div>
</template>
</van-slider>
<view style="color: #fff;padding-left: 20rpx;min-width: 112rpx;margin-left: 10rpx;">(米/秒)</view>
</view>
</view>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import BaiduMap from 'vue-baidu-map/components/map/Map.vue'
import {
BmGeolocation,
BmlMarkerClusterer,
} from "vue-baidu-map/components";
import {
BmlLushu
} from "vue-baidu-map";
import BmLabel from 'vue-baidu-map/components/overlays/Label'
import BmMarker from "vue-baidu-map/components/overlays/Marker";
import BmPolygon from "vue-baidu-map/components/overlays/Polygon.vue"
import BmPolyline from "vue-baidu-map/components/overlays/Polyline.vue"
let map;
let geolocation;
let locationMarker;
let mk;
export default {
components: {
BaiduMap,
BmGeolocation,
BmPolyline,
BmPolygon,
BmMarker,
BmlLushu
},
data() {
return {
isPlay: false,
num: 50,
icon: '../../static/tezhong/home2.png',
labelStyle: {
color: 'black',
fontSize: '14px',
border: 'none',
background: 'none',
fontWeight: '600'
},
isShow: false,
textCustom: {
color: '#fff',
opacity: 0
},
keyword: '',
markers: [],
windowWidth: 0,
windowHeight: 0,
minZoom: 4,
maxZoom: 19,
isBmapInit: false,
zoom: 17,
map: null,
current: 0,
value1: 0,
value2: 'a',
option1: [],
option2: [],
listData: [],
pageNum: 1,
pageSize: 10,
total: null,
listHeight: 520,
lat: null,
lng: null,
uniLat: null,
uniLng: null,
idMap: {},
// 经纬度:114.94050 , 25.83518
center: {
lng: 114.94050,
lat: 25.83518
},
isActive: 0,
polylinePath: [],
deviceId: '',
startTime: '',
endTime: '',
carIcon: {
url: "http://api.map.baidu.com/library/LuShu/1.2/examples/car.png",
size: {
width: 52,
height: 26
},
opts: {
anchor: {
width: 27,
height: 13
}
}
},
play: false,
path: [],
speed: 50, // 路数播放速度 米/s
progressWidth:0
}
},
watch: {
keyword: function(val) {
this.$u.debounce(() => {
this.$refs.paging.reload(true)
}, 500)
},
},
// onReachBottom() {
// console.log('触底');
// this.$refs.paging.doLoadMore(true)
// this.$refs.paging.scrollToBottom()
// },
methods: {
starts() {
if (this.isPlay) {
if (this.progressWidth == 100) {
this.progressWidth = 0
}
this.start()
this.isPlay = false
} else {
this.stop()
this.isPlay = true
}
},
changeSpeed(e) {
console.log('拖动的速度', e);
this.speed = e
},
updatePolylinePath(e) {
this.polylinePath = e.target.getPath()
},
addPolylinePoint() {
this.polylinePath.push({
lng: 116.404,
lat: 39.915
})
},
initMap({
BMap,
map
}) {
this.center.lng = 114.94050;
this.center.lat = 25.83518;
// this.address = this.$route.query.address || '';
this.BMap = BMap;
this.map = map;
this.zoom = 15;
this.getList();
},
locationError({
StatusCode
}) {
uni.showToast({
title: '检验位置定位失败',
icon: 'none'
})
},
getMarker({
type,
target,
point
}, item, i) {
console.log('item', item, i, point);
uni.navigateTo({
url: '/pages/specialEquipment/info'
})
},
getClick() {
console.log('我触发了地图');
plus.key.hideSoftKeybord();
document.activeElement.blur();
console.log('执行了');
},
change(index) {
this.current = index;
},
async getList() {
var params = {
endTime: this.endTime,
deviceId: this.deviceId,
startTime: this.startTime
}
let res = await this.$teZhongApi.trajectory(params)
if (res.code == 200 || res.code == 0) {
console.log('轨迹类别', res.data)
this.polylinePath = res.data
this.trajectoryList = res.data
if (this.polylinePath.length == 0) {
uni.showToast({
title: '暂无轨迹',
icon: 'none'
})
} else {
// 清空之前的轨迹和标记
this.map.clearOverlays();
// 将接口返回的经纬度数据转换成BMapGL.Point对象
const newPoints = this.polylinePath.map(item => new BMapGL.Point(item.lng, item.lat));
this.path = newPoints
this.play = true // 路书播放
var pl = new BMapGL.Polyline(newPoints);
// var pls = new BMapGL.Polyline(newPoints, { strokeColor: 'red' });
// 更新标记点
const startMarker = new BMapGL.Marker(newPoints[0], {
icon: new BMapGL.Icon('../../static/tezhong/start.png', new BMapGL.Size(32, 32))
});
const endMarker = new BMapGL.Marker(newPoints[newPoints.length - 1], {
icon: new BMapGL.Icon('../../static/tezhong/end.png', new BMapGL.Size(32, 32))
});
this.map.addOverlay(startMarker);
this.map.addOverlay(endMarker);
}
}
},
lushuReady(res) {
this.timer_lushu = setTimeout(() => {
// 全局搜BMapLib.LuShu可查找到Lushu.vue和 bmaplib.lulhu/index.js源码位置
// 通过源码可得知路书bml-lushu使用 this.originInstance 存储路书实例,故使用refs调用
let lushu =
this.$refs.bmlLushu && this.$refs.bmlLushu.originInstance ?
this.$refs.bmlLushu.originInstance :
"";
this.lushu = lushu; // 存储路书实例
let proto = Object.getPrototypeOf(lushu); // 原型
let _this = this;
// 处理信息窗口
function handleInfoWindow(index, length) {
var opts = {
width: 260, // 信息窗口宽度
height: 80, // 信息窗口高度
title: "", // 信息窗口标题
}
console.log(_this.trajectoryList);
let trajectoryItem = _this.trajectoryList[index]
let trajectoryItemPoint = new BMap.Point(_this.path[index].lng, _this.path[index].lat)
var infoWindow = new BMap.InfoWindow(
`设备名称:${trajectoryItem.deviceName}<br>设备类型:${trajectoryItem.equipType}<br>定位时间:${trajectoryItem.gpsTime}<br>`,
opts); // 创建信息窗口对象
_this.map.openInfoWindow(infoWindow, trajectoryItemPoint)
}
// 处理进度条宽度
function handlePercent(index, length) {
let percent = Math.floor((index / length) * 100000) / 1000;
_this.progressWidth = percent;
if (_this.progressWidth == 100) {
_this.isPlay = true
}
}
// 点位时间
function handleStationPoint(index) {
// 如果路段的下标大于小车当前的index则表示小车在该路段
// let pointIndex = _this.pathLength.findIndex(v => v > index);
// let lastFlag = pointIndex == -1;
// pointIndex = lastFlag ? _this.drivingList.length - 1 : pointIndex;
// let pointArr = _this.drivingList[pointIndex] || [];
// let pointObj = lastFlag ? pointArr[1] : pointArr[0] || {};
// pointObj && Object.keys(pointObj).length
// ? _this.throughDate = pointObj.sdate
// : "";
}
// 改原型实现进度条和经过点位时返回信息 (改源码无效,因为已经被打包压缩到vue-baidu-map了)
proto._moveNext = function(index) {
var me = this; // 此处的this指lushu实例
if (index < this._path.length - 1) {
handlePercent.call(_this, index, this._path.length); // 进度条
handleInfoWindow.call(_this, index, this._path.length); // 信息窗口
handleStationPoint.call(_this, index); // 点位
me._move(me._path[index], me._path[index + 1], me._tween.linear);
} else {
handlePercent.call(_this, 1, 1); // 进度条设置为100%
me.stop();
}
};
}, 100);
},
stop() {
this.play = false;
},
start() {
this.play = true
},
// 播放/暂停
playClick() {
if (!this.map) {
return;
}
if (!this.path.length) {
alert("暂无轨迹")
return;
}
this.play = !this.play;
},
},
mounted() {
},
onLoad(option) {
this.deviceId = option.deviceId
this.startTime = option.startTime
this.endTime = option.endTime
if (option.deviceId) {
// this.getList()
}
},
onShow() {
uni.getSystemInfo({
success: (res => {
this.windowWidth = res.windowWidth
this.windowHeight = res.windowHeight
})
})
}
}
</script>
<style scoped lang="scss">
page {
background-color: #F9F9F9;
}
.btn {
/* width: 343px; */
width: 92%;
height: 111px;
border-radius: 8px;
background: #000;
/* opacity: 0.7; */
background-color: rgba(7, 0, 0, 0.7);
/* 设置背景颜色为红色,透明度为 0.7 */
z-index: 9999999999 !important;
position: absolute;
bottom: 20rpx;
left: 30rpx;
padding: 20rpx 0 0 20rpx;
}
.custom-button {
width: 16px;
color: #fff;
font-size: 10px;
line-height: 18px;
text-align: center;
background-color: #6693FD;
border-radius: 100px;
padding-right: 24rpx;
}
.icons {
width: 70rpx;
height: 60rpx;
// padding: 20rpx 20rpx 0 20rpx;
}
.speed {
display: flex;
// width: 90%;
align-items: center;
padding: 20rpx;
}
.title-tops {
height: 108rpx;
text-align: center;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
}
.BMapLabel {
max-width: none;
}
// .BMapLabel {
// z-index: -1!important;
// }
.middles {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.tab_color {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
color: #5982ff;
}
.tab_no {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
color: #333
}
.bottom-tab {
display: flex;
justify-content: space-around;
align-items: center;
padding-top: 20rpx;
}
.overflow-list {
// overflow-y: auto;
// overflow-x: hidden;
height: 500rpx;
}
.centers {
display: flex;
justify-content: center;
align-items: center;
}
.list-text {
display: flex;
padding: 34rpx;
background-color: #fff;
// margin: 20rpx;
justify-content: space-between;
.imgs {
width: 100rpx;
height: 40rpx;
padding: 10rpx;
}
.title {
font-family: "PingFang SC Bold";
font-weight: bold;
font-size: 28rpx;
color: #273147;
margin-bottom: 10rpx;
}
.desc {
font-family: "PingFang SC Medium";
font-weight: 500;
font-size: 26rpx;
color: #273147;
//超出一行省略号
white-space: nowrap; //禁止换行
overflow: hidden;
text-overflow: ellipsis; //
}
}
.tab {
position: absolute;
top: 0;
width: 100%
}
.guided-tour-container {
width: 100%;
height: 100%;
position: relative;
::v-deep .anchorBL {
display: none;
}
.location-icon {
width: 35px;
height: 35px;
position: absolute;
right: 20rpx;
bottom: 20rpx;
z-index: 99999;
}
}
::v-deep .u-input--border {
border: none;
}
.seach_text {
position: fixed;
width: 100%;
top: 0;
z-index: 9999;
.seach-input {
box-shadow: 0 8rpx 16rpx rgba(39, 49, 71, 0.08);
display: flex;
background-color: #fff;
border-radius: 8rpx;
}
}
</style>
需要注意的是:轨迹移动附带的窗口需要修改vue-baidu里面的源码的方法proto._moveNext、具体的处理也是在 handleInfoWindow回调里面进行处理的。
具体的效果