<template>
|
<div class="app-container">
|
<el-form
|
:model="queryParams"
|
ref="queryForm"
|
:inline="true"
|
v-show="showSearch"
|
label-width="68px"
|
>
|
<el-form-item label="车牌号" prop="vehicleNo">
|
<el-input
|
v-model="queryParams.vehicleNo"
|
placeholder="请输入车牌号"
|
clearable
|
size="small"
|
@keyup.enter.native="handleQuery"
|
/>
|
</el-form-item>
|
<el-form-item label="时间范围">
|
<el-date-picker
|
v-model="dateRange"
|
size="small"
|
style="width: 240px"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
type="datetimerange"
|
range-separator="-"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
:default-time="['00:00:00', '23:59:59']"
|
@change="handleDateRangeChange"
|
></el-date-picker>
|
</el-form-item>
|
<el-form-item>
|
<el-button
|
type="primary"
|
icon="el-icon-search"
|
size="mini"
|
@click="handleQuery"
|
>搜索</el-button
|
>
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
|
>重置</el-button
|
>
|
</el-form-item>
|
</el-form>
|
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<el-card class="box-card">
|
<div slot="header" class="clearfix">
|
<span>轨迹列表</span>
|
</div>
|
<el-table
|
v-loading="loading"
|
:data="gpsList"
|
height="600"
|
@row-click="handleRowClick"
|
>
|
<el-table-column label="时间" align="center" prop="collectTime" />
|
<el-table-column label="速度(km/h)" align="center" prop="speed" />
|
<el-table-column label="经度" align="center" prop="longitude" />
|
<el-table-column label="纬度" align="center" prop="latitude" />
|
<el-table-column label="方向(°)" align="center" prop="direction" />
|
</el-table>
|
</el-card>
|
</el-col>
|
<el-col :span="18">
|
<div id="mapContainer" style="height: 600px"></div>
|
<div class="track-controls">
|
<el-button-group>
|
<el-button
|
size="small"
|
@click="showPreviousSegment"
|
:disabled="segmentIndex === 0"
|
>
|
<i class="el-icon-arrow-left"></i> 上一段
|
</el-button>
|
<el-button
|
size="small"
|
@click="showNextSegment"
|
:disabled="(segmentIndex + 1) * segmentSize >= gpsList.length"
|
>
|
下一段 <i class="el-icon-arrow-right"></i>
|
</el-button>
|
</el-button-group>
|
<el-button-group style="margin-left: 10px">
|
<el-button
|
size="small"
|
type="primary"
|
@click="startPlayback"
|
:disabled="isPlaying"
|
>
|
<i class="el-icon-video-play"></i> 开始回放
|
</el-button>
|
<el-button
|
size="small"
|
type="danger"
|
@click="stopPlayback"
|
:disabled="!isPlaying"
|
>
|
<i class="el-icon-video-pause"></i> 停止回放
|
</el-button>
|
</el-button-group>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script>
|
import { anonymousList } from "@/api/system/gps";
|
|
export default {
|
name: "GpsMap",
|
data() {
|
return {
|
// 遮罩层
|
loading: true,
|
// 显示搜索条件
|
showSearch: true,
|
// 总条数
|
total: 0,
|
// GPS数据表格数据
|
gpsList: [],
|
// 弹出层标题
|
title: "",
|
// 是否显示弹出层
|
open: false,
|
// 日期范围
|
dateRange: [],
|
// 查询参数
|
queryParams: {
|
pageNum: 1,
|
pageSize: 1000,
|
deviceId: null,
|
appId: null,
|
sign: null,
|
timestamp: null,
|
vehicleNo: null,
|
beginTime: null,
|
endTime: null
|
},
|
// 表单参数
|
form: {
|
vehicleNo: null
|
},
|
// 表单校验
|
rules: {
|
vehicleNo: [
|
{ required: true, message: "车牌号不能为空", trigger: "blur" }
|
],
|
dateRange: [
|
{ required: true, message: "请选择时间范围", trigger: "change" }
|
]
|
},
|
// 地图对象
|
map: null,
|
// 轨迹线
|
polyline: null,
|
// 标记点数组
|
markers: [],
|
// 轨迹分段显示
|
segmentIndex: 0,
|
segmentSize: 50, // 每段显示的点数
|
// 轨迹回放
|
isPlaying: false,
|
playInterval: null,
|
smoothFactor: 0.3, // 平滑因子,值越大越平滑
|
minDistance: 50, // 最小插值距离(米)
|
|
currentPlayIndex: 0,
|
playSpeed: 1000, // 回放速度(毫秒)
|
currentMarker: null,
|
};
|
},
|
created() {
|
// 获取URL参数
|
const query = this.$route.query;
|
// if (query.vehicleNo) {
|
// this.queryParams.vehicleNo = query.vehicleNo;
|
// } else {
|
// this.$message.error('缺少车牌号参数');
|
// return;
|
// }
|
//获取订单号
|
this.queryParams.orderId = query.orderId;
|
if(this.queryParams.orderId==null)
|
{
|
this.$message.error('缺少订单号参数');
|
return;
|
}
|
|
// 检查时间参数
|
// if (query.beginTime && query.endTime) {
|
// // 格式化时间
|
// this.dateRange = [
|
// this.formatDateTime(query.beginTime),
|
// this.formatDateTime(query.endTime)
|
// ];
|
// this.queryParams.beginTime = this.dateRange[0];
|
// this.queryParams.endTime = this.dateRange[1];
|
// } else {
|
// this.$message.error('缺少时间范围参数');
|
// return;
|
// }
|
|
// 设置认证参数
|
if (query.appId) {
|
this.queryParams.appId = query.appId;
|
}
|
if (query.sign) {
|
this.queryParams.sign = query.sign;
|
}
|
if (query.timestamp) {
|
this.queryParams.timestamp = query.timestamp;
|
}
|
|
this.getList();
|
},
|
mounted() {
|
// 动态加载百度地图API
|
this.initMap().then(() => {
|
window.initMapFlag = true;
|
if (window.loadGpsList) {
|
this.drawTrack();
|
}
|
});
|
},
|
methods: {
|
/** 加载百度地图API */
|
loadBMapScript() {
|
return new Promise((resolve, reject) => {
|
if (window.BMap) {
|
resolve(window.BMap);
|
return;
|
}
|
|
window.initBMap = () => {
|
console.log("百度地图API加载成功");
|
resolve(window.BMap);
|
};
|
|
const script = document.createElement("script");
|
script.type = "text/javascript";
|
script.src =
|
"https://api.map.baidu.com/api?v=3.0&ak=n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn&callback=initBMap";
|
script.onerror = (error) => {
|
console.error("百度地图API加载失败", error);
|
reject(error);
|
};
|
document.head.appendChild(script);
|
});
|
},
|
/** 格式化时间 */
|
parseTime(time) {
|
const year = time.getFullYear();
|
const month = String(time.getMonth() + 1).padStart(2, '0');
|
const day = String(time.getDate()).padStart(2, '0');
|
const hours = String(time.getHours()).padStart(2, '0');
|
const minutes = String(time.getMinutes()).padStart(2, '0');
|
const seconds = String(time.getSeconds()).padStart(2, '0');
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
},
|
/** 处理时间格式 */
|
formatDateTime(dateTimeStr) {
|
if (!dateTimeStr) return '';
|
// 如果时间字符串不包含秒,添加秒
|
if (dateTimeStr.length === 16) { // yyyy-MM-dd HH:mm
|
return dateTimeStr + ':00';
|
}
|
return dateTimeStr;
|
},
|
/** 查询GPS列表 */
|
getList() {
|
this.loading = true;
|
// 构建查询参数
|
const params = {
|
...this.queryParams
|
};
|
|
// 如果没有选择时间范围,则使用URL中的时间
|
// if (!this.dateRange || this.dateRange.length === 0) {
|
// const query = this.$route.query;
|
// if (query.beginTime && query.endTime) {
|
// params.beginTime = query.beginTime;
|
// params.endTime = query.endTime;
|
// } else {
|
// this.$message.error('请选择时间范围');
|
// this.loading = false;
|
// return;
|
// }
|
// } else {
|
// params.beginTime = this.dateRange[0];
|
// params.endTime = this.dateRange[1];
|
// }
|
|
anonymousList(params).then(response => {
|
this.gpsList = response.rows;
|
this.total = response.total;
|
this.loading = false;
|
window.loadGpsList=true;
|
if(window.initMapFlag){
|
this.drawTrack();
|
}
|
}).catch(error => {
|
this.loading = false;
|
// 显示友好的错误提示
|
if (error.message && error.message.includes("未找到该车辆对应的GPS设备")) {
|
this.$message.warning("该车辆暂无GPS设备信息");
|
} else {
|
this.$message.error(error.message || "查询GPS轨迹失败");
|
}
|
});
|
},
|
/** 坐标转换方法 */
|
translatePoint(point) {
|
return new Promise((resolve) => {
|
// 使用百度地图API内置的坐标转换
|
const convertor = new BMap.Convertor();
|
const pointArr = [];
|
pointArr.push(point);
|
convertor.translate(pointArr, 1, 5, (data) => {
|
if (data.status === 0) {
|
resolve(data.points[0]);
|
} else {
|
// 如果转换失败,返回原始坐标
|
resolve(point);
|
}
|
});
|
});
|
},
|
/** 批量坐标转换 */
|
async translatePoints(points) {
|
const translatePoints = [];
|
for (const point of points) {
|
const translatedPoint = await this.translatePoint(point);
|
translatePoints.push(translatedPoint);
|
}
|
return translatePoints;
|
},
|
/** 搜索按钮操作 */
|
handleQuery() {
|
if (!this.queryParams.vehicleNo) {
|
this.$message.error('请输入车牌号');
|
return;
|
}
|
if (!this.dateRange || this.dateRange.length !== 2) {
|
this.$message.error('请选择时间范围');
|
return;
|
}
|
// 更新查询参数
|
this.queryParams.beginTime = this.dateRange[0];
|
this.queryParams.endTime = this.dateRange[1];
|
this.getList();
|
},
|
/** 重置按钮操作 */
|
resetQuery() {
|
// 重置为当天时间范围
|
const today = new Date();
|
const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
|
const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
|
this.dateRange = [this.parseTime(startTime), this.parseTime(endTime)];
|
this.resetForm("queryForm");
|
// 保留车牌号
|
this.queryParams.vehicleNo = this.$route.query.vehicleNo;
|
// 更新查询参数
|
this.queryParams.beginTime = this.dateRange[0];
|
this.queryParams.endTime = this.dateRange[1];
|
this.handleQuery();
|
},
|
/** 初始化地图 */
|
async initMap() {
|
try {
|
await this.loadBMapScript();
|
// 创建地图实例
|
this.map = new BMap.Map("mapContainer");
|
// 设置地图中心点和缩放级别
|
this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
|
// 启用滚轮放大缩小
|
this.map.enableScrollWheelZoom();
|
console.log("地图初始化成功");
|
} catch (error) {
|
console.error("地图初始化失败", error);
|
this.$message.error("地图加载失败,请刷新页面重试");
|
}
|
},
|
/** 计算两点之间的距离(米) */
|
getDistance(point1, point2) {
|
return this.map.getDistance(point1, point2);
|
},
|
/** 计算两点之间的角度 */
|
getAngle(point1, point2) {
|
const dx = point2.lng - point1.lng;
|
const dy = point2.lat - point1.lat;
|
return Math.atan2(dy, dx) * 180 / Math.PI;
|
},
|
|
|
|
/** 绘制轨迹 */
|
async drawTrack() {
|
// 清除之前的轨迹
|
if (this.polyline) {
|
this.map.removeOverlay(this.polyline);
|
}
|
this.markers.forEach((marker) => {
|
this.map.removeOverlay(marker);
|
});
|
this.markers = [];
|
|
if (this.gpsList.length === 0) {
|
return;
|
}
|
|
// 按时间排序
|
this.gpsList.sort((a, b) => {
|
return new Date(a.collectTime) - new Date(b.collectTime);
|
});
|
|
// 计算当前段落的起始和结束索引
|
const startIndex = this.segmentIndex * this.segmentSize;
|
const endIndex = Math.min(
|
startIndex + this.segmentSize,
|
this.gpsList.length
|
);
|
const currentSegment = this.gpsList.slice(startIndex, endIndex);
|
|
// 获取所有坐标数组
|
const originPoints = currentSegment.map(
|
(item) => new BMap.Point(item.longitude, item.latitude)
|
);
|
|
this.gpsList.sort((a, b) => {
|
return new Date(b.collectTime) - new Date(a.collectTime);
|
}).forEach(item => {
|
item.speed = item.speed/1000;
|
});
|
|
// 批量转换坐标
|
const translatePoints = await this.translatePoints(originPoints);
|
|
// 创建轨迹点数组
|
const points = translatePoints;
|
translatePoints.forEach((item, index) => {
|
const bdPoint = item;
|
|
// 判断起点和终点是否相同
|
const isStartPoint = index === 0;
|
const isEndPoint = index === translatePoints.length - 1;
|
const isStartEndSame = isStartPoint && isEndPoint &&
|
translatePoints[0].lng === translatePoints[translatePoints.length - 1].lng &&
|
translatePoints[0].lat === translatePoints[translatePoints.length - 1].lat;
|
|
// 只在起点和终点创建标记,且起点和终点不同时才显示起点标记
|
if ((isStartPoint && !isStartEndSame) || isEndPoint) {
|
let marker;
|
let direction=currentSegment[index].direction;
|
if (isStartPoint && !isStartEndSame) {
|
// 起点显示"起"字
|
const label = new BMap.Label("起", {
|
offset: new BMap.Size(0, 0),
|
position: bdPoint,
|
});
|
label.setStyle({
|
color: "white",
|
fontSize: "12px",
|
backgroundColor: "#3388ff",
|
border: "none",
|
padding: "2px 6px",
|
borderRadius: "3px",
|
});
|
|
marker = new BMap.Marker(bdPoint,{rotation:direction});
|
marker.setLabel(label);
|
} else {
|
// 终点显示车辆图标
|
const myIcon = new BMap.Icon(
|
"/car_blue.png",
|
new BMap.Size(20, 20),
|
{
|
imageSize: new BMap.Size(20, 20),
|
anchor: new BMap.Size(10, 10),
|
}
|
);
|
marker = new BMap.Marker(bdPoint, {
|
icon: myIcon,
|
rotation: direction,
|
});
|
}
|
//在车图标上显示车牌
|
const label = new BMap.Label(currentSegment[index].vehicleNo, {
|
offset: new BMap.Size(0, -25), // 向上偏移25像素
|
position: bdPoint,
|
});
|
label.setStyle({
|
color: "white",
|
fontSize: "12px",
|
backgroundColor: "#3388ff",
|
border: "none",
|
padding: "2px 6px",
|
borderRadius: "3px",
|
});
|
marker.setLabel(label);
|
|
// 获取地址信息
|
const geoc = new BMap.Geocoder();
|
geoc.getLocation(bdPoint, (rs) => {
|
const addComp = rs.addressComponents;
|
const address =
|
addComp.province +
|
addComp.city +
|
addComp.district +
|
addComp.street +
|
addComp.streetNumber;
|
|
// 添加点击事件监听器
|
marker.addEventListener("click", () => {
|
// 创建信息窗口
|
const infoWindow = new BMap.InfoWindow(
|
`车牌号:${currentSegment[index].vehicleNo}<br/>时间:${currentSegment[index].collectTime}<br/>速度:${
|
currentSegment[index].speed
|
}km/h<br/>方向:${currentSegment[index].direction}°<br/>地址:${address}`
|
);
|
this.map.openInfoWindow(infoWindow, bdPoint);
|
});
|
});
|
|
this.map.addOverlay(marker);
|
this.markers.push(marker);
|
}
|
|
// 如果是最后一个点,绘制轨迹线
|
if (index === currentSegment.length - 1) {
|
// 创建轨迹线
|
this.polyline = new BMap.Polyline(points, {
|
strokeColor: "#3388ff",
|
strokeWeight: 5,
|
strokeOpacity: 0.8,
|
strokeStyle: "solid",
|
});
|
this.map.addOverlay(this.polyline);
|
|
// 调整地图视野以显示完整轨迹
|
this.map.setViewport(points);
|
}
|
});
|
},
|
/** 点击表格行 */
|
handleRowClick(row) {
|
const point = new BMap.Point(row.longitude, row.latitude);
|
const convertor = new BMap.Convertor();
|
convertor.translate([point], 1, 5, (data) => {
|
if (data.status === 0) {
|
// 移除之前的标记点
|
if (this.currentMarker) {
|
this.map.removeOverlay(this.currentMarker);
|
}
|
|
// 创建新的标记点
|
const myIcon = new BMap.Icon(
|
"/car_blue.png",
|
new BMap.Size(20, 20),
|
{
|
imageSize: new BMap.Size(20, 20),
|
anchor: new BMap.Size(10, 10),
|
}
|
);
|
const marker = new BMap.Marker(data.points[0], {
|
icon: myIcon,
|
rotation: row.direction,
|
});
|
|
// 显示车牌
|
const label = new BMap.Label(row.vehicleNo, {
|
offset: new BMap.Size(0, -25), // 向上偏移25像素
|
position: data.points[0],
|
});
|
label.setStyle({
|
color: "white",
|
fontSize: "12px",
|
backgroundColor: "#3388ff",
|
border: "none",
|
padding: "2px 6px",
|
borderRadius: "3px",
|
whiteSpace: "nowrap", // 防止文字换行
|
});
|
marker.setLabel(label);
|
|
// 获取地址信息
|
const geoc = new BMap.Geocoder();
|
geoc.getLocation(data.points[0], (rs) => {
|
const addComp = rs.addressComponents;
|
const address =
|
addComp.province +
|
addComp.city +
|
addComp.district +
|
addComp.street +
|
addComp.streetNumber;
|
|
// 添加点击事件监听器
|
marker.addEventListener("click", () => {
|
// 创建信息窗口
|
const infoWindow = new BMap.InfoWindow(
|
`车牌号:${row.vehicleNo}<br/>时间:${row.collectTime}<br/>速度:${row.speed}km/h<br/>方向:${row.direction}°<br/>地址:${address}`
|
);
|
this.map.openInfoWindow(infoWindow, data.points[0]);
|
});
|
});
|
|
// 保存当前标记点引用
|
this.currentMarker = marker;
|
|
// 添加到地图并居中显示
|
this.map.addOverlay(marker);
|
this.map.setCenter(data.points[0]);
|
this.map.setZoom(15);
|
}
|
});
|
},
|
/** 显示上一段轨迹 */
|
showPreviousSegment() {
|
if (this.segmentIndex > 0) {
|
this.segmentIndex--;
|
this.drawTrack();
|
}
|
},
|
/** 显示下一段轨迹 */
|
showNextSegment() {
|
if ((this.segmentIndex + 1) * this.segmentSize < this.gpsList.length) {
|
this.segmentIndex++;
|
this.drawTrack();
|
}
|
},
|
/** 开始轨迹回放 */
|
startPlayback() {
|
if (this.isPlaying) return;
|
this.isPlaying = true;
|
this.currentPlayIndex = 0;
|
this.playInterval = setInterval(() => {
|
if (this.currentPlayIndex < this.gpsList.length) {
|
const item = this.gpsList[this.currentPlayIndex];
|
const point = new BMap.Point(item.longitude, item.latitude);
|
const convertor = new BMap.Convertor();
|
convertor.translate([point], 1, 5, (data) => {
|
if (data.status === 0) {
|
const bdPoint = data.points[0];
|
//显示车辆图标
|
const myIcon = new BMap.Icon(
|
"/car_blue.png",
|
new BMap.Size(20, 20),
|
{
|
imageSize: new BMap.Size(20, 20),
|
anchor: new BMap.Size(10, 10),
|
}
|
);
|
const marker = new BMap.Marker(bdPoint, {
|
icon: myIcon,
|
rotation: item.direction,
|
});
|
// 移除之前的标记点
|
if (this.currentMarker) {
|
this.map.removeOverlay(this.currentMarker);
|
}
|
this.currentMarker=marker;
|
this.map.addOverlay(marker);
|
this.map.setCenter(bdPoint);
|
this.map.setZoom(15);
|
}
|
});
|
this.currentPlayIndex++;
|
} else {
|
this.stopPlayback();
|
}
|
}, this.playSpeed);
|
},
|
/** 停止轨迹回放 */
|
stopPlayback() {
|
if (this.playInterval) {
|
clearInterval(this.playInterval);
|
this.playInterval = null;
|
}
|
this.isPlaying = false;
|
},
|
/** 处理时间范围变化 */
|
handleDateRangeChange(val) {
|
if (val && val.length === 2) {
|
// 格式化时间
|
this.dateRange = [
|
this.formatDateTime(val[0]),
|
this.formatDateTime(val[1])
|
];
|
|
// 检查结束时间是否包含具体时间
|
const endDate = new Date(this.dateRange[1]);
|
const hasSpecificTime = endDate.getHours() !== 0 || endDate.getMinutes() !== 0;
|
|
if (!hasSpecificTime) {
|
// 如果没有具体时间,设置为23:59:59
|
endDate.setHours(23, 59, 59);
|
this.dateRange[1] = this.parseTime(endDate);
|
}
|
|
// 更新查询参数
|
this.queryParams.beginTime = this.dateRange[0];
|
this.queryParams.endTime = this.dateRange[1];
|
}
|
},
|
},
|
beforeDestroy() {
|
this.stopPlayback();
|
},
|
};
|
</script>
|
|
<style scoped>
|
.box-card {
|
margin-bottom: 20px;
|
}
|
.track-controls {
|
position: absolute;
|
bottom: 20px;
|
left: 50%;
|
transform: translateX(-50%);
|
z-index: 1000;
|
background: rgba(255, 255, 255, 0.9);
|
padding: 10px;
|
border-radius: 4px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
</style>
|