可能对应梳理mapbox或者天地图的一些大佬而言,我写的毫无意义,但是作为菜鸟的我,还是觉得应该记录一下,毕竟太难了 /笑哭
主要需求就是根据后端接口返回的经纬度进行视频一样的播放效果,功能点截图在下方
这是一个播放的控件,都是自己写的,具体代码:
<div id="map-container">
<div id="trackMapDiv" class="full-width full-height">
<div class="track-control-box width-35" :class="{'track-control-box-hidden': !show}">
<div class="tcb-title">
<b>轨迹控制</b>
<i class="fks-icon-caret-bottom tcb-icon" v-if="show" @click="show = !show"></i>
<i class="fks-icon-caret-top tcb-icon" v-else @click="show = !show"></i>
</div>
<div v-show="show" class="width-35">
<div class="tcb-line">
<span>播放控制:</span>
<fks-button type="primary" plain @click="pauseTrack" v-if="track.pause">播放</fks-button>
<fks-button type="primary" plain @click="pauseTrack" v-else>暂停</fks-button>
<fks-button type="primary" plain @click="restart">重播</fks-button>
<fks-button type="primary" plain @click="stopTrack">停止</fks-button>
</div>
<div class="tcb-line">
<span>播放速度:</span>
<fks-input-number controls-position="right" :min="1" :max="10" class="tcb-inputNum" size="small" v-model="track.speed" @change="changeSpeed"></fks-input-number>
<span class="tcb-left">显示轨迹:</span>
<fks-switch v-model="track.show" @change="changeVisible">
</fks-switch>
</div>
<div class="tcb-line">
<span>当前时间:</span>
<span>{{ track.end?animationDuration:secondToDate(new Date(routeTime * track.animationPhase).getTime())}}</span>
</div>
<div class="tcb-line">
<fks-slider :min="0" :max="routeTime" v-model="track.step" class="tcb-slider" :format-tooltip="formatTooltip" @input="sliderChange"></fks-slider>
</div>
<div class="tcb-line">
<span>轨迹时长:</span>
<span>{{ animationDuration }}</span>
<span class="tcb-mleft">剩余时长:</span>
<span>{{ track.end?secondToDate(0):secondToDate(new Date(routeTime * (1-track.animationPhase)).getTime())}}</span>
</div>
</div>
</div>
</div>
// 这个是轨迹需要用到的字段,同时在计算当前时间、轨迹总时长、剩余时长会有秒数的差别,原因可能是因为计算时的值太小,所以做了个判断
track: {
timer: 0, // 动画id
show: true, //是否显示轨迹
animationPhase: 0, // 动画进度
pause: true, // 是否暂停
end: false, //是否结束播放
start: 0, // 播放开始时间
prevTime: 0, // 上一个播放时间
pauseTime: 0, // 暂停的时间
playTime: 0, // 播放的时间
speed: 4, //播放速度
step: 0
},
接下来就是播放、暂停、重播、停止、播放速度、是否显示轨迹、拖动播放时间节点7个操作的方法
// 播放、暂停
pauseTrack(){
this.track.pause = !this.track.pause
}
// 重播 直接调用播放方法(点击暂停后调用会出问题,加pause判断状态并设置)
restart() {
this.stopTrack();
if(this.track.pause) {
this.pauseTrack();
}
this.start();
},
// 停止动画,设为初始值
stopTrack(){
this.track.end = true
window.cancelAnimationFrame(this.track.timer)
MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
'step',
['line-progress'],
'#027AFF',
1,
'rgba(255, 0, 0, 0)'
]);
},
// 设置播放速度
changeSpeed() {
if (this.track.pause) this.start();
},
// 是否显示轨迹
changeVisible() {
MapObj.map.setLayoutProperty('track-line-layer', 'visibility', this.track.show ? 'visible' : 'none');
},
//拖动播放器,设置播放时间
sliderChange(e) {
// 不加判断时初始化报错
if(!MapObj.map || !MapObj.map.getLayer('track-line-layer')) return;
let step = e / 100;
MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
'step',
['line-progress'],
'#027AFF',
step,
'rgba(255, 0, 0, 0)'
]);
},
以上的都是播放的大概逻辑,具体的整改文件,我会在后面粘贴出来,以免看不明白
接下来就是根据经纬度设置标识,不同的对象不同的标识
1、首先要获取需要渲染的对象,必须要有经纬度的,同时设置一个可以判断标识的字段,用于不同对象展示不一样的标识判断条件(我用的meta)
// activities是数据源,如果是有大量数据需要渲染,可直接让后端存储成这种格式的数据返回,这样前
// 端就不用做处理了
let points = this.activities.map((t, index)=>{
return {
type: 'Feature',
properties: {
meta: !t.scanType? 'red':t.scanType === 1?'orange':'green', //用于判断显示那种图标
name: t.capitalModel.capitalName,
...t
},
geometry: {
coordinates: [t.capitalModel.smx, t.capitalModel.smy], //该对象的经纬度
type: 'Point' //数据类型
}
}
})
接下来就是初始化图标(这里可以不需要循环,但是写的时候不知道那里有问题,不循环不显示,所以就先这样了,有空的可以自己研究一下)
const imgs = [{
id: 'start',
url: require('@/assets/img/GIS/start.png')
},{
id: 'end',
url: require('@/assets/img/GIS/end.png')
},{
id: 'green',
url: require('@/assets/img/GIS/green.png')
},{
id: 'orange',
url: require('@/assets/img/GIS/orange.png')
},{
id: 'red',
url: require('@/assets/img/GIS/red.png')
},{
id: 'photograph',
url: require('@/assets/img/GIS/photograph.png')
}]
// 创建图标layer, track-source是addSource方法中定义的名字
imgs.forEach(img=>{
if(!MapObj.map.hasImage(img.id)){
MapObj.map.loadImage(img.url, (error, image) => {
if (error) throw error;
MapObj.map.addImage(img.id, image);
if(!MapObj.map.getLayer(img.id + 'layer')){
MapObj.map.addLayer({
type: 'symbol',
source: 'track-source',
id: img.id + 'layer',
paint: { // 图标下字体的样式
'icon-opacity': 1,
'text-color': '#404040',
'text-halo-color': '#ffffff',
'text-halo-width': 1,
'text-opacity': 1,
},
layout: { // 图标和字体样式
'text-anchor': 'center',
'icon-allow-overlap': true,
'icon-image': img.id,
'icon-size': 0.5,
// 'icon-color': '#027AFF',
'text-field': ['get', 'name'],
'text-offset': [0, 0], // 文本偏移
'text-size': 12,
'icon-offset': [0, img.id === 'start' || img.id === 'end'?0:-50]
},
filter: ['==', 'meta', img.id] // 这里就是用来判断显示那个图标的
})
}
});
}
})
第三点就是弹出框了,弹出框主要用到 就是map中的click方法,点击的时候会获取一些经纬度信息,通过MapObj.map.queryRenderedFeatures(bbox, {})拿到对应盒子的数据
使用MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);打开弹出框
MapObj.map.on('click', (mapEvent)=>{
const META_TYPES = [ //这个可以不需要
'green',
'orange',
'red'
]
let buffer = 2 // 点击误差
let bbox = [ // 点击的originalEvent拿坐标所在的点位信息,point有问题
[mapEvent.originalEvent.layerX - buffer, mapEvent.originalEvent.layerY - buffer],
[mapEvent.originalEvent.layerX + buffer, mapEvent.originalEvent.layerY + buffer]
];
const features = MapObj.map.queryRenderedFeatures(bbox, {}).filter(feature => {
return META_TYPES.indexOf(feature.properties.meta) !== -1;
});
let feature
if(features.length){
feature = features[0]
// 以免出错判断必须是有的点
let hasFeature = this.features.find(t=> t.properties.objectId === feature.properties.objectId);
if(hasFeature){
let { contentName, resultContent, capitalName, kindName } = { ...hasFeature.properties.contentRecordInfos[0], ...hasFeature.properties.capitalModel };
this.currentProblem = {capitalName, bugIds: hasFeature.properties.bugIds, bugCount: hasFeature.properties.bugCount};
let content = `
<div class="mapboxgl-popup-mycontent">
<span class="font-weight-bold">${capitalName}</span>
<p><span>对象类型</span> <span class="text-primary">${kindName}</span></p>
<p class="max-height-5 ofy-scroll">
<fks-row>
<fks-col :span="6">
检查内容
</fks-col>
<fks-col :span="18" class="width-10 text-primary">
${contentName}
</fks-col>
</fks-row>
</p>
<p><span>结果描述</span> <span class="text-primary">${resultContent}</span></p>
<p><span>问题数量</span> <span class="text-primary cursor-pointer" onclick="getDefectDetails()">${hasFeature.properties.bugCount || '-'}</span></p>
</div>
`
MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);
}else{
MapObj.closePopup()
}
}
})
到此,以上说的三个功能就实现了,而外还有的一个点就是在map自带的弹出框里面点击接着弹出自定义的弹出框,这个功能也是有的。
下面是整个模块的代码,提供大家参考,有写不好的地方也请大家多多指教
<template>
<section class="bg-white">
<fks-row class="full-width full-height" v-loading="loading">
<fks-col :span="6" class="full-height">
<fks-timeline class="p-4 pl-20">
<fks-timeline-item
v-for="(activity, index) in activities"
:key="index"
placement="top"
icon="fks-icon-circle-check"
type="primary"
:timestamp="activity.scanTime || '2022-01-15 18:00:00'">
<p class="font-weight-6">{{activity.capitalModel && activity.capitalModel.capitalName}}</p>
<p>
<span class="mr-1" :class="activity.scanType === 2?'text-success-green': activity.scanType?'text-third':'text-dangerous'">
<img :src="require(activity.scanType === 2?'@/assets/img/GIS/photograph.png':activity.scanType?'@/assets/img/GIS/scan.png':'@/assets/img/GIS/manual.png')" alt="图片" width="20px" class="align-middle"/>
{{activity.scanType === 2?'已拍照定位':activity.scanType?'已扫码定位':'手动打卡'}}
</span>
<span v-if="activity.scanType === 2" class="text-primary">查看照片</span>
</p>
</fks-timeline-item>
</fks-timeline>
</fks-col>
<fks-col :span="18" class="full-height">
<div id="map-container">
<div id="trackMapDiv" class="full-width full-height">
<div class="track-control-box width-35" :class="{'track-control-box-hidden': !show}">
<div class="tcb-title">
<b>轨迹控制</b>
<i class="fks-icon-caret-bottom tcb-icon" v-if="show" @click="show = !show"></i>
<i class="fks-icon-caret-top tcb-icon" v-else @click="show = !show"></i>
</div>
<div v-show="show" class="width-35">
<div class="tcb-line">
<span>播放控制:</span>
<fks-button type="primary" plain @click="pauseTrack" v-if="track.pause">播放</fks-button>
<fks-button type="primary" plain @click="pauseTrack" v-else>暂停</fks-button>
<fks-button type="primary" plain @click="restart">重播</fks-button>
<fks-button type="primary" plain @click="stopTrack">停止</fks-button>
</div>
<div class="tcb-line">
<span>播放速度:</span>
<fks-input-number controls-position="right" :min="1" :max="10" class="tcb-inputNum" size="small" v-model="track.speed" @change="changeSpeed"></fks-input-number>
<span class="tcb-left">显示轨迹:</span>
<fks-switch v-model="track.show" @change="changeVisible">
</fks-switch>
</div>
<div class="tcb-line">
<span>当前时间:</span>
<span>{{ track.end?animationDuration:secondToDate(new Date(routeTime * track.animationPhase).getTime())}}</span>
</div>
<div class="tcb-line">
<fks-slider :min="0" :max="routeTime" v-model="track.step" class="tcb-slider" :format-tooltip="formatTooltip" @input="sliderChange"></fks-slider>
</div>
<div class="tcb-line">
<span>轨迹时长:</span>
<span>{{ animationDuration }}</span>
<span class="tcb-mleft">剩余时长:</span>
<span>{{ track.end?secondToDate(0):secondToDate(new Date(routeTime * (1-track.animationPhase)).getTime())}}</span>
</div>
</div>
</div>
</div>
<!-- 图标说明 -->
<div class="identification width-35 position-absolute bg-white text-center">
<fks-row>
<fks-col :span="8"><img :src="imgs[2].url" width="25px" class="align-middle mr-1"/>拍照定位</fks-col>
<fks-col :span="8"><img :src="imgs[3].url" width="25px" class="align-middle mr-1"/>NFC定位</fks-col>
<fks-col :span="8"><img :src="imgs[4].url" width="25px" class="align-middle mr-1"/>手动打卡</fks-col>
</fks-row>
</div>
</div>
</fks-col>
</fks-row>
<!-- <div v-else class="full-height d-flex justify-content-center align-items-center text-gray font-weight-normal'" v-loading="loading">
<p class="text-14">暂无数据</p>
</div> -->
<fks-dialog :visible.sync="showDialog" width="865px" :before-close="closeProblem">
<div slot="title" class="d-flex full-width justify-content-between align-items-center">
<p class="text-16 font-weight-bold">
<i class="iconfont text-primary icon-jibenxinxi text-14"></i>
{{ currentProblem.capitalName + '-' + `${currentProblem.checkResult === '0' ? '正常' : '异常'}` }}(
<span class="text-danger">{{ currentProblem.bugCount }}</span>)
</p>
<span class="text-primary cursor-pointer text-14 mr-6" @click="changeShowType">
<i class="iconfont icon-qiehuan1 text-12"></i> 切换{{ showType === 'line' ? '列表' : '时间线' }}查看
</span>
</div>
<div style="max-height: calc(75vh - 123px);min-height: 100px" v-loading="defectLoading">
<div v-for="(item, index) in problemList" :key="index">
<defectDetail :defect-info="item.defectDetails || []"
:basic-info="item"
:showType="showType"
:class="index === 0 ? '' : 'mt-4'"/>
</div>
</div>
</fks-dialog>
</section>
</template>
<script>
/* eslint-disable */
import { mapActions } from 'vuex';
import mapboxgl from 'mapbox-gl';
import defectDetail from '@/modules/Patrol/components/DefectDetail';
import MapboxLanguage from '@mapbox/mapbox-gl-language'
// 地图
const imgs = [{
id: 'start',
url: require('@/assets/img/GIS/start.png')
},{
id: 'end',
url: require('@/assets/img/GIS/end.png')
},{
id: 'green',
url: require('@/assets/img/GIS/green.png')
},{
id: 'orange',
url: require('@/assets/img/GIS/orange.png')
},{
id: 'red',
url: require('@/assets/img/GIS/red.png')
},{
id: 'photograph',
url: require('@/assets/img/GIS/photograph.png')
}]
const MapObj = {
map: null,
popup: null,
/**
* 显示一个气泡
* @param lngLat 气泡位置
* @param content 气泡内容
*/
showPopup(lngLat, content){
if(!MapObj.popup){
MapObj.popup = new mapboxgl.Popup().setLngLat(lngLat).setHTML(content).addTo(MapObj.map)
}else{
if(MapObj.popup.isOpen()){
MapObj.popup.setLngLat(lngLat).setHTML(content)
}else{
MapObj.popup = new mapboxgl.Popup().setLngLat(lngLat).setHTML(content).addTo(MapObj.map)
}
}
},
closePopup(){
if(MapObj.popup && MapObj.popup.isOpen()){
MapObj.popup.remove()
MapObj.popup = null
}
},
}
export default {
components: { defectDetail },
data() {
return {
imgs,
loading: false,
// 起点时间(毫秒)
startTime: null,
// 路线总时间(秒)
routeTime: 0,
curTimeStr: '',
otherTimeStr: '',
status: true,
// 是否显示轨迹控件
show: false,
routeVisible: true,
curTime: 0,
practicalLine: [],
positions: [],
taskId: this.$route.params.id,
track: {
timer: 0,
date: 0,
show: true,
animationPhase: 0,
pause: true,
end: false,
start: 0,
prevTime: 0,
pauseTime: 0,
playTime: 0,
speed: 4,
step: 0
},
// 拍照1、NFC2、手动打卡3
activities: [],
features: [],
resDataInspectionRoute: [],
problemList: [],
showType: 'line',
showDialog: false,
currentProblem: {},
problemList: [],
defectLoading: false
};
},
computed: {
// 轨迹时长
animationDuration() {
let result = this.secondToDate(this.routeTime);
return result;
}
},
mounted() {
this.getTaskObject();
this.drawRouteByTaskId();
},
methods: {
...mapActions('patrol/defect', [
'getProblemInfo'
]),
...mapActions('patrol/record', [
'findCoordinateInfoBytaskId',
'objectCoordinateInfo'
]),
// 初始化地图
initMap() {
mapboxgl.accessToken = 'mapbox的注册的token';
MapObj.map = new mapboxgl.Map({
container: 'map-container',
style: 'mapbox://styles/mapbox/streets-v11',
center: this.practicalLine[0] || [106.11966911319149, 29.63211347103112],
zoom: 8.152743104851709
});
// 地图语言设置为中文
var language = new MapboxLanguage({ defaultLanguage: "zh-Hans" });
MapObj.map.addControl(language);
// 设置导航
MapObj.map.addControl(new mapboxgl.NavigationControl({showCompass: true,showZoom: true}),'top-left');
// 是否全局显示
MapObj.map.addControl(new mapboxgl.FullscreenControl(), "top-right");
MapObj.map.on('load', () => {
if(this.practicalLine.length === 0) return
this.addAllLayer();
});
},
// 根据taskId获取巡检路线,初始化调用
drawRouteByTaskId() {
this.loading = true;
this.findCoordinateInfoBytaskId(this.taskId)
.then((res) => {
this.resDataInspectionRoute = res.result || [];
this.practicalLine = this.resDataInspectionRoute.map((el) =>{
return [el.longitude, el.latitude];
});
if(this.practicalLine.length === 0) {
this.$message.warning('当前路线暂时没有轨迹');
this.loading = false;
} else {
this.loading = false;
this.calculatePos(this.resDataInspectionRoute);
}
this.initMap();
}).catch(() => {
this.loading = false;
});
},
// 根据taskId获取巡检对象,获取对象和默认对象位置、打卡标识
getTaskObject() {
this.activities = [];
this.objectCoordinateInfo(this.taskId).then((res) => {
if(res.code === 8000000){
this.activities = res.result || [];
this.positions = this.activities.map((el) =>{
return [el.capitalModel.smx, el.capitalModel.smy]
});
return
}
}).catch(() => {});
},
// 加载图层
addAllLayer() {
// 去除mapbox的logo
document.getElementsByClassName('mapboxgl-ctrl-logo')[0].style.display = 'none';
let points = this.activities.map((t, index)=>{
return {
type: 'Feature',
properties: {
meta: !t.scanType? 'red':t.scanType === 1?'orange':'green', //用于判断显示那种图标
name: t.capitalModel.capitalName,
...t
},
geometry: {
coordinates: [t.capitalModel.smx, t.capitalModel.smy], //该对象的经纬度
type: 'Point'
}
}
})
// 拍照的点位信息
let pointArr = this.activities.filter(el =>{
return el.scanType === 2
}).map(els =>{
return {
type: 'Feature',
properties: {
id: 1006,
meta: 'photograph',
},
geometry: {
coordinates: [els.codeX, els.codeY],
type: 'Point'
}
}
});
// 起点和终点
let EndAndBeginning = [
{
type: 'Feature',
properties: {
meta: 'start',
},
geometry: {
type: 'Point',
coordinates: this.practicalLine[0]
}
},
{
type: 'Feature',
properties: {
meta: 'end',
},
geometry: {
type: 'Point',
coordinates: this.practicalLine[this.practicalLine.length - 1]
},
},
];
let line = {
type: 'Feature',
properties: {
name: 'name',
meta: 'line'
},
geometry: {
coordinates: this.practicalLine,
type: 'LineString'
}
}
// 组装feature
let features = [line].concat(points, pointArr, EndAndBeginning)
this.features = features
// 创建默认路线source
if(!MapObj.map.getSource('track-source')){
MapObj.map.addSource('track-source',{
type: 'geojson',
lineMetrics: true,
data: {
'type': 'FeatureCollection',
'features': features,
}
})
}
// 创建默认layer
if(!MapObj.map.getLayer('track-line-layer')){
MapObj.map.addLayer({
type: 'line',
source: 'track-source',
id: 'track-line-layer',
paint: {
'line-color': '#027AFF',
'line-width': 5,
'line-gradient': [
'step',
['line-progress'],
'#027AFF',
1,
'rgba(255, 0, 0, 0)'
]
},
layout: {
'line-cap': 'round',
'line-join': 'round'
},
filter: ['==', '$type', 'LineString']
})
}
// 默认的插入巡检起始点连接
this.positions.unshift(this.practicalLine[0]);
this.positions.push(this.practicalLine[this.practicalLine.length - 1]);
MapObj.map.addLayer({
"id": "route",
"type": "line",
"source": {
"type": "geojson",
"data": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": this.positions
}
}
},
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "#ccc",
"line-width": 5
}
});
// 创建图标layer
imgs.forEach(img=>{
if(!MapObj.map.hasImage(img.id)){
MapObj.map.loadImage(img.url, (error, image) => {
if (error) throw error;
MapObj.map.addImage(img.id, image);
if(!MapObj.map.getLayer(img.id + 'layer')){
MapObj.map.addLayer({
type: 'symbol',
source: 'track-source',
id: img.id + 'layer',
paint: {
'icon-opacity': 1,
'text-color': '#404040',
'text-halo-color': '#ffffff',
'text-halo-width': 1,
'text-opacity': 1,
},
layout: {
'text-anchor': 'center',
'icon-allow-overlap': true,
'icon-image': img.id,
'icon-size': 0.5,
// 'icon-color': '#027AFF',
'text-field': ['get', 'name'],
'text-offset': [0, 0], // 文本偏移
'text-size': 12,
'icon-offset': [0, img.id === 'start' || img.id === 'end'?0:-50]
},
filter: ['==', 'meta', img.id]
})
}
});
}
})
// 开始时间固有的 markers
new mapboxgl.Popup({anchor: 'top',offset: [0, 20], className: 'info', closeButton:false, closeOnClick:false })
.setLngLat(this.positions[0])
.setHTML(`<div class="height-2"><i class="fks-icon-time text-primary text-14"></i> 开始时间:${this.resDataInspectionRoute[0].createTime}</div>`)
.addTo(MapObj.map);
// 结束时间
new mapboxgl.Popup({anchor: 'top',offset: [0, 20], className: 'info', closeButton:false, closeOnClick:false })
.setLngLat(this.positions[this.positions.length - 1])
.setHTML(`<div class="height-2"><i class="fks-icon-warning-outline text-primary text-14"></i> 结束时间:${this.resDataInspectionRoute[this.resDataInspectionRoute.length - 1].createTime}</div>`)
.addTo(MapObj.map);
MapObj.map.on('click', (mapEvent)=>{
const META_TYPES = [
'green',
'orange',
'red'
]
let buffer = 2 // 点击误差
let bbox = [ // 点击的originalEvent拿坐标所在的点位信息,point有问题
[mapEvent.originalEvent.layerX - buffer, mapEvent.originalEvent.layerY - buffer],
[mapEvent.originalEvent.layerX + buffer, mapEvent.originalEvent.layerY + buffer]
];
const features = MapObj.map.queryRenderedFeatures(bbox, {}).filter(feature => {
return META_TYPES.indexOf(feature.properties.meta) !== -1;
});
let feature
if(features.length){
feature = features[0]
// 以免出错判断必须是有的点
let hasFeature = this.features.find(t=> t.properties.objectId === feature.properties.objectId);
if(hasFeature){
let { contentName, resultContent, capitalName, kindName } = { ...hasFeature.properties.contentRecordInfos[0], ...hasFeature.properties.capitalModel };
this.currentProblem = {capitalName, bugIds: hasFeature.properties.bugIds, bugCount: hasFeature.properties.bugCount};
let content = `
<div class="mapboxgl-popup-mycontent">
<span class="font-weight-bold">${capitalName}</span>
<p><span>对象类型</span> <span class="text-primary">${kindName}</span></p>
<p class="max-height-5 ofy-scroll">
<fks-row>
<fks-col :span="6">
检查内容
</fks-col>
<fks-col :span="18" class="width-10 text-primary">
${contentName}
</fks-col>
</fks-row>
</p>
<p><span>结果描述</span> <span class="text-primary">${resultContent}</span></p>
<p><span>问题数量</span> <span class="text-primary cursor-pointer" onclick="getDefectDetails()">${hasFeature.properties.bugCount || '-'}</span></p>
</div>
`
MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);
}else{
MapObj.closePopup()
}
}
})
// 显示缺陷详情
let that = this;
window.getDefectDetails = () => {
if(!that.currentProblem.bugCount) return
that.showDialog = true;
that.defectLoading = true;
that.getProblemInfo({bugId: that.currentProblem.bugIds}).then(res => {
that.problemList = res.result;
that.problemList.forEach((el) => {
el.defectDetails.forEach((els) => {
els.bugName = el.bugName;
els.bugCode = el.bugCode;
els.bugDescription = el.bugDescription;
els.bringDefect = el.bringDefect;
els.bugTypeName = el.bugTypeName;
});
});
}).finally(() => {
that.defectLoading = false;
});
}
},
closeProblem() {
this.showDialog = false;
this.problemList = [];
this.currentProblem = {};
},
changeShowType() {
this.showType = this.showType === 'line' ? 'table' : 'line';
},
// 计算每个时间点对应的坐标 routeTime:轨迹时长
calculatePos(positions) {
this.startTime = new Date(positions[0].createTime).getTime();
this.routeTime = new Date(positions[positions.length - 1].createTime).getTime() / 1000 - new Date(positions[0].createTime).getTime() / 1000;
},
// 计算时间
secondToDate(result) {
var h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
var m = Math.floor((result / 60) % 60) < 10 ? '0' + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60);
var s = Math.floor(result % 60) < 10 ? '0' + Math.floor(result % 60) : Math.floor(result % 60);
return h + ':' + m + ':' + s;
},
// 停止动画,设为初始值
stopTrack(){
this.track.end = true
window.cancelAnimationFrame(this.track.timer)
MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
'step',
['line-progress'],
'#027AFF',
1,
'rgba(255, 0, 0, 0)'
]);
},
// 重播 直接调用播放方法(点击暂停后调用会出问题,加pause判断状态并设置)
restart() {
this.stopTrack();
if(this.track.pause) {
this.pauseTrack();
}
this.start();
},
// 播放
start() {
let _this = this
if(!this.track.end) return
this.track.start = 0
this.track.animationPhase = 0
this.track.pauseTime = 0;
this.track.prevTime = 0;
function frame(time) {
if (_this.track.start === 0)
_this.track.start = time;
if(_this.track.pause){
_this.track.pauseTime += (time - _this.track.prevTime)
}else{
_this.track.animationPhase = _this.track.speed * (time - _this.track.start - _this.track.pauseTime) / _this.routeTime;
}
_this.track.step = _this.track.animationPhase * 100
_this.track.prevTime = time
// 播放时长=总时长*当前播放进度
// 剩余时长=总时长-播放时长
// 剩余距离同理
_this.track.playTime = _this.track.animationPhase * _this.routeTime
if (_this.track.animationPhase > 1) {
_this.stopTrack()
return
}
MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
'step',
['line-progress'],
'#027AFF',
_this.track.animationPhase,
'rgba(255, 0, 0, 0)'
]);
_this.track.timer = window.requestAnimationFrame(frame);
}
this.track.timer = window.requestAnimationFrame(frame);
this.track.end = false
},
changeSpeed() {
if (this.track.pause) this.start();
},
// 是否显示轨迹
changeVisible() {
MapObj.map.setLayoutProperty('track-line-layer', 'visibility', this.track.show ? 'visible' : 'none');
},
sliderChange(e) {
// 不加判断时初始化报错
if(!MapObj.map || !MapObj.map.getLayer('track-line-layer')) return;
let step = e / 100;
MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
'step',
['line-progress'],
'#027AFF',
step,
'rgba(255, 0, 0, 0)'
]);
},
formatTooltip(val) {
let result = this.getDateString(this.startTime + val * 1000);
return result;
},
getDateString(stamp) {
let d = new Date(parseInt(stamp));
let month =
d.getMonth() + 1 < 10 ? 0 + '' + (d.getMonth() + 1) : d.getMonth() + 1;
let day = d.getDate() < 10 ? 0 + '' + d.getDate() : d.getDate();
let hour = d.getHours() < 10 ? 0 + '' + d.getHours() : d.getHours();
let minute =
d.getMinutes() < 10 ? 0 + '' + d.getMinutes() : d.getMinutes();
let second =
d.getSeconds() < 10 ? 0 + '' + d.getSeconds() : d.getSeconds();
let dateString =
d.getFullYear() + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
return dateString;
},
// 播放、暂停
pauseTrack(){
this.track.pause = !this.track.pause
}
}
};
</script>
<style lang="scss" scoped>
/deep/.fks-timeline {
margin-left: 80px;
.fks-timeline-item {
display: flex;
align-items: center;
}
.fks-timeline-item__tail{
left: 10px !important;
height: 70% !important;
top: 60px !important;
}
.fks-timeline-item__node--normal {
width: 25px !important;
height: 25px !important;
}
.fks-timeline-item__icon {
font-size: 26px !important;
}
.fks-timeline-item__timestamp.is-top {
position: absolute;
left: -94px !important;
width: 90px !important;
text-align: center;
margin-top: 30px;
}
.fks-timeline-item__content{
width: 250px;
background-color: #E7F4FF;
padding: 1px 10px;
border-radius: 5px;
margin-left: 10px;
}
}
#map-container {
height: 100%;
width: 100%;
position: relative;
overflow: hidden;
-webkit-touch-callout: none; /*系统默认菜单被禁用*/
-webkit-user-select: none; /*webkit浏览器*/
-khtml-user-select: none; /*早期浏览器*/
-moz-user-select: none; /*火狐*/
-ms-user-select: none; /*IE10*/
user-select: none;
.track-control-box {
width: 380px;
height: 270px;
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1;
background: white;
border-radius: 5px;
.tcb-title {
height: 40px;
line-height: 40px;
font-size: 15px;
margin-left: 15px;
.tcb-icon {
float: right;
font-size: 19px;
margin-top: 8px;
margin-right: 15px;
color: #2ba3fd;
cursor: pointer;
}
}
.tcb-line {
margin-left: 25px;
margin-bottom: 10px;
line-height: 36px;
height: 36px;
&/deep/ .fks-button--primary.is-plain:active{
color: #2BA3FD;
background: #eaf6ff;
border-color: #aadafe;
}
&/deep/ .fks-button--primary.is-plain:focus{
color: #2BA3FD;
background: #eaf6ff;
border-color: #aadafe;
}
&/deep/ .fks-switch__label--left {
display: none;
}
&/deep/ .fks-switch__label--left.is-active {
position: absolute;
color: white !important;
z-index: 6;
left: 23px;
display: block;
}
&/deep/ .fks-switch__label--right {
display: none;
}
&/deep/ .fks-switch__label--right.is-active {
position: absolute;
color: white !important;
left: -2px;
display: block;
}
.tcb-left {
margin-left: 10px;
}
.tcb-mleft {
margin-left: 28px;
}
.tcb-slider {
width: 310px;
}
}
.tcb-inputNum {
width: 70px;
&/deep/ input {
padding-left: 10px !important;
padding-right: 40px !important;
}
}
}
.track-control-box-hidden {
height: 40px;
width: 130px;
}
.identification {
left: 10px;
bottom: 20px;
z-index: 9;
border-radius: 5px;
height: 40px;
line-height: 40px;
// text-align: center;
}
.mapboxgl-popup-mycontent{
background-color: #FFFFFF;
z-index: 99999;
height: 100px;
width: 400px;
padding: 20px;
}
}
</style>