960 lines
25 KiB
Vue
960 lines
25 KiB
Vue
<template>
|
||
<view class="page">
|
||
<u-navbar title="轨迹" :border-bottom="false" :background="bgc" title-color='#000' title-size='36' height='45'>
|
||
</u-navbar>
|
||
|
||
<!-- 地图容器 -->
|
||
<view class="map-container">
|
||
<map id="map" ref="map" :latitude="latitude" :longitude="longitude" class="map" :polyline="currentPolyline"
|
||
:markers="markers" :scale="18" :polygons="polygon">
|
||
<cover-view class="park" @click="toggleIconAndCallout">
|
||
<cover-image class="img" src="https://lxnapi.ccttiot.com/bike/img/static/uRiYQZQEb3l2LsltEsyW"
|
||
mode=""></cover-image>
|
||
</cover-view>
|
||
</map>
|
||
</view>
|
||
|
||
<!-- 轨迹信息面板 -->
|
||
<view class="track-panel">
|
||
<!-- 时间范围选择 -->
|
||
<view class="time-range" v-if="type == 1">
|
||
<view class="time-picker" @click="time1 = true">
|
||
<text>{{ startTime }}</text>
|
||
<u-icon name="arrow-down" size="14" color="#666"></u-icon>
|
||
</view>
|
||
<text class="separator">至</text>
|
||
<view class="time-picker" @click="time2 = true">
|
||
<text>{{ endTime }}</text>
|
||
<u-icon name="arrow-down" size="14" color="#666"></u-icon>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 快捷时间选择器 -->
|
||
<view class="time-selector" v-if="qufen != 123">
|
||
<view class="time-btn" v-for="(item, index) in timeOptions" :key="index"
|
||
:class="{ active: activeTime === item.value }" @tap="selectTime(item.value)">
|
||
{{ item.label }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 设备信息 -->
|
||
<view class="device-info">
|
||
<view class="device-item">
|
||
<text class="device-number" style="margin-right: 200rpx;">车牌:{{ vehicleNum == null ? '' : vehicleNum }}</text><text class="device-number">SN:{{ deviceNumber }}</text>
|
||
</view>
|
||
<view class="device-item">
|
||
<text class="value">状态:{{zhuangtai}}</text>
|
||
<text class="value">速度:{{ currentSpeed }}km/h</text>
|
||
</view>
|
||
<view class="device-item">
|
||
<text class="value">电压:{{ currentBattery }}V</text>
|
||
</view>
|
||
<view class="device-item">
|
||
<text class="value">卫星:{{ satellites }}</text>
|
||
<text class="value">信号:{{ signal }}</text>
|
||
</view>
|
||
<view class="device-item">
|
||
<text class="value">电门:{{ quality }}</text>
|
||
<text class="value">锁状态:{{ lockStatus }}</text>
|
||
</view>
|
||
<view class="device-item">
|
||
<text class="value">定位:{{ location }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 轨迹回放控制面板 -->
|
||
<view class="playback-panel">
|
||
<view class="playback-controls">
|
||
<!-- 进度条行 -->
|
||
<view class="progress-row">
|
||
<slider class="progress-slider" :value="currentProgress" :min="0" :max="trackPoints.length - 1"
|
||
:step="1" @change="onSliderChange" @changing="onSliderChanging" />
|
||
</view>
|
||
|
||
<!-- 控制按钮行 -->
|
||
<view class="control-row">
|
||
<view class="left-controls">
|
||
<view class="play-btn" @tap="togglePlay">
|
||
<text>{{ isPlaying ? '暂停' : '播放' }}</text>
|
||
</view>
|
||
|
||
<view class="speed-control">
|
||
<text class="speed-label">{{ playbackSpeed }}x</text>
|
||
<view class="speed-buttons">
|
||
<text @tap="changeSpeed('down')" class="speed-btn">-</text>
|
||
<text @tap="changeSpeed('up')" class="speed-btn">+</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="point-info">
|
||
<text>{{ currentPointTime }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 时间选择器组件 -->
|
||
<u-picker mode="time" v-model="time1" :params="params" :default-time="startTime" @confirm="confirm1"
|
||
:start-year="startYear" :end-year="endYear" title="选择开始时间"></u-picker>
|
||
<u-picker mode="time" v-model="time2" :params="params" :default-time="endTime" @confirm="confirm2"
|
||
:start-year="startYear" :end-year="endYear" title="选择结束时间"></u-picker>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
bgc: {
|
||
backgroundColor: "#fff",
|
||
},
|
||
latitude: '',
|
||
longitude: '',
|
||
currentPolyline: [], // 当前显示的轨迹线
|
||
polygon: [], // 用于存储区域多边形
|
||
markers: [],
|
||
activeTime: '3',
|
||
deviceNumber: '',
|
||
speed: '0.00',
|
||
voltage: '0.000',
|
||
location: '',
|
||
|
||
// 区域和停车场相关数据
|
||
parkingList: [],
|
||
showIconAndCallout: false,
|
||
areaId: '',
|
||
|
||
// 轨迹回放相关数据
|
||
trackPoints: [], // 存储轨迹点数据
|
||
currentProgress: 0, // 当前进度
|
||
isPlaying: false, // 是否正在播放
|
||
playbackTimer: null, // 回放定时器
|
||
currentPointTime: '', // 当前点的时间
|
||
playbackSpeed: 1, // 播放速度倍数
|
||
speedOptions: [0.5, 1, 2, 4, 8], // 可选的播放速度
|
||
currentSpeedIndex: 1, // 当前速度索引
|
||
currentSpeed: '0.00', // 当前估算速度
|
||
playbackInterval: 1000, // 播放间隔(毫秒)
|
||
currentBattery: '未知', // 当前电池电量
|
||
currentLockStatus: '关锁', // 当前锁状态
|
||
|
||
// 时间选择相关
|
||
time1: false,
|
||
time2: false,
|
||
params: {
|
||
year: true,
|
||
month: true,
|
||
day: true,
|
||
hour: true,
|
||
minute: true,
|
||
second: false
|
||
},
|
||
startYear: 2022,
|
||
endYear: new Date().getFullYear(),
|
||
startTime: '',
|
||
endTime: '',
|
||
sn: '',
|
||
timeOptions: [
|
||
{ label: '1小时', value: '1' },
|
||
{ label: '3小时', value: '3' },
|
||
{ label: '6小时', value: '6' },
|
||
{ label: '12小时', value: '12' }
|
||
],
|
||
type: '',
|
||
qufen:'',
|
||
orid:'',
|
||
id:'',
|
||
lockStatus:'',
|
||
satellites:'',
|
||
signal:'',
|
||
quality:'',
|
||
includePoints: [], // 添加includePoints数组
|
||
vehicleNum:'',
|
||
zhuangtai:''
|
||
}
|
||
},
|
||
|
||
onLoad(e) {
|
||
console.log(e);
|
||
this.sn = e.sn
|
||
this.type = e.type
|
||
if(e.id){
|
||
this.id = e.id
|
||
}
|
||
if(e.qufen){
|
||
this.orid = e.orid
|
||
this.qufen = e.qufen
|
||
this.startTime = e.startTime
|
||
this.endTime = e.endTime == null ? this.formatCurrentTime() : e.endTime
|
||
console.log(this.startTime, this.endTime,'0000')
|
||
}else{
|
||
if (e.type == 1) {
|
||
const now = new Date()
|
||
const threeHoursAgo = new Date(now - 3 * 60 * 60 * 1000)
|
||
this.startTime = this.formatDateTime(threeHoursAgo)
|
||
this.endTime = this.formatDateTime(now)
|
||
} else {
|
||
this.startTime = e.startTime
|
||
this.endTime = e.endTime == null ? this.formatCurrentTime() : e.endTime
|
||
console.log(this.startTime, this.endTime,'1111')
|
||
}
|
||
}
|
||
|
||
this.getDeviceInfo()
|
||
this.updateTrackData()
|
||
},
|
||
methods: {
|
||
getDeviceInfo() {
|
||
this.$u.get(`/bst/device?id=${this.id}`).then((res) => {
|
||
if (res.code === 200) {
|
||
this.vehicleNum = res.data.vehicleNum
|
||
this.deviceNumber = res.data.sn
|
||
this.voltage = res.data.voltage
|
||
this.areaId = res.data.areaId
|
||
this.latitude = res.data.latitude
|
||
this.longitude = res.data.longitude
|
||
this.getArea()
|
||
}
|
||
})
|
||
},
|
||
|
||
getParking() {
|
||
let data = {
|
||
areaId: this.areaId
|
||
}
|
||
this.$u.get('/bst/areaSub/listByAreaId', data).then((res) => {
|
||
if (res.code === 200) {
|
||
const type1Data = []
|
||
const type2Data = []
|
||
const type3Data = []
|
||
res.data.forEach(row => {
|
||
if (row.type == 1) type1Data.push(row)
|
||
else if (row.type == 2) type2Data.push(row)
|
||
else if (row.type == 3) type3Data.push(row)
|
||
})
|
||
const validBoundaries = type1Data.map(row => row.boundaryStr).filter(boundary =>
|
||
typeof boundary === 'string' && boundary.trim() !== '')
|
||
const polygons = this.convertBoundaryToPolygons(validBoundaries, 1)
|
||
const validBoundaries1 = type2Data.map(row => row.boundaryStr).filter(boundary =>
|
||
typeof boundary === 'string' && boundary.trim() !== '')
|
||
const polygons1 = this.convertBoundaryToPolygons(validBoundaries1, 2)
|
||
const validBoundaries2 = type3Data.map(row => row.boundaryStr).filter(boundary =>
|
||
typeof boundary === 'string' && boundary.trim() !== '')
|
||
const polygons2 = this.convertBoundaryToPolygons(validBoundaries2, 3)
|
||
this.polygon = [...this.polygon, ...polygons2, ...polygons1, ...polygons]
|
||
this.parkingList = res.data
|
||
}
|
||
})
|
||
},
|
||
// 获取区域数据
|
||
getArea() {
|
||
this.$u.get(`/bst/area/${this.areaId}`).then((res) => {
|
||
if (res.code === 200) {
|
||
const polygons = this.convertBoundaryToPolygon(res.data.boundaryStr)
|
||
if (polygons) {
|
||
this.polygon.push(polygons)
|
||
}
|
||
this.getParking()
|
||
}
|
||
})
|
||
},
|
||
formatCurrentTime() {
|
||
const now = new Date();
|
||
const padZero = num => num < 10 ? `0${num}` : num;
|
||
return `${now.getFullYear()}-${padZero(now.getMonth() + 1)}-${padZero(now.getDate())} ${padZero(now.getHours())}:${padZero(now.getMinutes())}:${padZero(now.getSeconds())}`;
|
||
},
|
||
updateTrackData() {
|
||
console.log(this.startTime, this.endTime)
|
||
const formattedStartTime = this.startTime
|
||
const formattedEndTime = this.endTime
|
||
if(this.orid){
|
||
this.$u.get('/bst/locationLog/listAll?orderId=' + this.orid + '&startTime=' + formattedStartTime + '&endTime=' + formattedEndTime).then((res) => {
|
||
if (res.code === 200) {
|
||
if (!res.data || res.data.length === 0) {
|
||
uni.showToast({
|
||
title: '该时间段内无轨迹数据',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
this.trackPoints = []
|
||
this.currentPolyline = []
|
||
this.markers = []
|
||
return
|
||
}
|
||
// 按时间排序轨迹点
|
||
const sortedData = res.data.sort((a, b) => {
|
||
return new Date(a.at) - new Date(b.at)
|
||
})
|
||
this.trackPoints = sortedData.map(point => ({
|
||
latitude: parseFloat(point.latitude),
|
||
longitude: parseFloat(point.longitude),
|
||
time: point.at,
|
||
status: point.status,
|
||
onlineStatus: point.onlineStatus,
|
||
remainingPower: point.voltage || 0,
|
||
lockStatus:point.lockStatus,
|
||
satellites:point.satellites,
|
||
signal:point.signal,
|
||
quality:point.quality
|
||
}))
|
||
console.log(this.trackPoints,'000');
|
||
if (this.trackPoints.length > 0) {
|
||
this.currentProgress = 0;
|
||
this.updateMapPoint(0);
|
||
}
|
||
}
|
||
}).catch(error => {
|
||
uni.showToast({
|
||
title: '获取轨迹数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
console.error("Error fetching track data:", error);
|
||
})
|
||
}else{
|
||
this.$u.get('/bst/locationLog/listAll?deviceId=' + this.id + '&startTime=' + formattedStartTime + '&endTime=' + formattedEndTime).then((res) => {
|
||
if (res.code === 200) {
|
||
if (!res.data || res.data.length === 0) {
|
||
uni.showToast({
|
||
title: '该时间段内无轨迹数据',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
this.trackPoints = []
|
||
this.currentPolyline = []
|
||
this.markers = []
|
||
return
|
||
}
|
||
// 按时间排序轨迹点
|
||
const sortedData = res.data.sort((a, b) => {
|
||
return new Date(a.at) - new Date(b.at)
|
||
})
|
||
|
||
this.trackPoints = sortedData.map(point => ({
|
||
latitude: parseFloat(point.latitude),
|
||
longitude: parseFloat(point.longitude),
|
||
time: point.at,
|
||
status: point.status,
|
||
onlineStatus: point.onlineStatus,
|
||
remainingPower: point.voltage || 0,
|
||
lockStatus:point.lockStatus,
|
||
satellites:point.satellites,
|
||
signal:point.signal,
|
||
quality:point.quality
|
||
}))
|
||
if (this.trackPoints.length > 0) {
|
||
this.currentProgress = 0;
|
||
this.updateMapPoint(0);
|
||
}
|
||
}
|
||
}).catch(error => {
|
||
uni.showToast({
|
||
title: '获取轨迹数据失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
})
|
||
console.error("Error fetching track data:", error);
|
||
})
|
||
}
|
||
},
|
||
// 转换多个边界数据为多边形
|
||
convertBoundaryToPolygons(boundaries, num) {
|
||
if (!boundaries || !boundaries.length) return []
|
||
const colors = {
|
||
1: { fill: "#3A7EDB40", stroke: "#3A7EDB" },
|
||
2: { fill: "#FFF5D640", stroke: "#FFC107" },
|
||
3: { fill: "#FFE0E040", stroke: "#FF473E" }
|
||
}
|
||
return boundaries.map(boundary => {
|
||
if (!boundary) return null
|
||
let coords
|
||
try {
|
||
coords = JSON.parse(boundary)
|
||
} catch (error) {
|
||
console.error("Error parsing boundary JSON:", error)
|
||
return null
|
||
}
|
||
if (!Array.isArray(coords)) {
|
||
console.error("Parsed boundary is not an array:", coords)
|
||
return null
|
||
}
|
||
const points = coords.map(coord => ({
|
||
latitude: coord[1],
|
||
longitude: coord[0]
|
||
}))
|
||
return {
|
||
points: points,
|
||
fillColor: colors[num].fill,
|
||
strokeColor: colors[num].stroke,
|
||
strokeWidth: 1,
|
||
zIndex: 1
|
||
}
|
||
}).filter(Boolean)
|
||
},
|
||
// 切换图标和标注显示
|
||
toggleIconAndCallout() {
|
||
this.showIconAndCallout = !this.showIconAndCallout
|
||
if (this.showIconAndCallout) {
|
||
const newMarkers = []
|
||
this.parkingList.forEach(item => {
|
||
newMarkers.push({
|
||
id: parseFloat(item.id),
|
||
latitude: parseFloat(item.latitude),
|
||
longitude: parseFloat(item.longitude),
|
||
width: 20,
|
||
height: 28.95,
|
||
iconPath: item.type == 1 ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/up2xXqAgwCX5iER600k3' : item.type == 2 ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/u53BAQcFIX3vxsCzEZ7t' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uDNY5Q4zOiZTCBTA2Jdq',
|
||
callout: {
|
||
content: item.name,
|
||
color: '#ffffff',
|
||
fontSize: 14,
|
||
borderRadius: 10,
|
||
bgColor: item.type == 1 ? '#3A7EDB' : item.type == 2 ? '#FFC107' : '#FF473E',
|
||
padding: 6,
|
||
display: 'ALWAYS'
|
||
}
|
||
})
|
||
})
|
||
this.markers = newMarkers
|
||
} else {
|
||
this.markers = []
|
||
}
|
||
},
|
||
// 转换边界数据为多边形
|
||
convertBoundaryToPolygon(boundary) {
|
||
if (!boundary) return null;
|
||
const points = JSON.parse(boundary).map(coord => ({
|
||
latitude: coord[1],
|
||
longitude: coord[0]
|
||
}))
|
||
return {
|
||
points: points,
|
||
fillColor: "#55888840",
|
||
strokeColor: "#22FF0040",
|
||
strokeWidth: 1,
|
||
zIndex: 1
|
||
}
|
||
},
|
||
updateMapPoint(index) {
|
||
if (!this.trackPoints[index]) return
|
||
const currentPoint = this.trackPoints[index]
|
||
const prevPoint = index > 0 ? this.trackPoints[index - 1] : null
|
||
|
||
// 计算速度
|
||
if (prevPoint) {
|
||
const distance = this.calculateDistance(
|
||
prevPoint.latitude,
|
||
prevPoint.longitude,
|
||
currentPoint.latitude,
|
||
currentPoint.longitude
|
||
)
|
||
const timeGap = (new Date(currentPoint.time) - new Date(prevPoint.time)) / 1000
|
||
this.currentSpeed = timeGap > 0 ? ((distance / timeGap) * 3.6).toFixed(1) : '0.00'
|
||
} else {
|
||
this.currentSpeed = '0.00'
|
||
}
|
||
|
||
// 更新信息显示
|
||
this.currentPointTime = currentPoint.time
|
||
this.location = `${currentPoint.longitude}, ${currentPoint.latitude}`
|
||
this.currentBattery = currentPoint.remainingPower == null ? '0.00' : currentPoint.remainingPower.toFixed(2)
|
||
this.lockStatus = currentPoint.lockStatus == '0' ? '关锁' : '开锁'
|
||
this.satellites = currentPoint.satellites
|
||
this.signal = currentPoint.signal
|
||
this.quality = currentPoint.quality == '0' ? '关' : '开'
|
||
|
||
// 更新地图中心点
|
||
this.latitude = currentPoint.latitude
|
||
this.longitude = currentPoint.longitude
|
||
|
||
// 更新includePoints
|
||
this.includePoints = this.trackPoints.slice(0, index + 1).map(point => ({
|
||
latitude: point.latitude,
|
||
longitude: point.longitude
|
||
}))
|
||
|
||
// 更新轨迹线
|
||
this.currentPolyline = [
|
||
{
|
||
points: this.trackPoints,
|
||
width: 4,
|
||
arrowLine: true,
|
||
color: 'rgb(165,208,240)',
|
||
zIndex: 2
|
||
},{
|
||
points: this.trackPoints.slice(0, index + 1),
|
||
width: 4,
|
||
arrowLine: true,
|
||
color: '#00AF99',
|
||
zIndex: 2
|
||
}
|
||
]
|
||
|
||
// 更新markers
|
||
let iconPath = ''
|
||
let calloutColor = '#2679D1'
|
||
let calloutBgColor = '#D4ECFF'
|
||
switch (currentPoint.status) {
|
||
case '0':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uQRng4QNKA38Amk8Wgt5' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uocjFo8Ar2BJVpzC2G2f';
|
||
calloutColor = '#ffffff'
|
||
calloutBgColor = '#000000'
|
||
this.zhuangtai = '仓库中'
|
||
break;
|
||
case '1':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uzhMeExOQJbMcZtrfGUV' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uheL17wVZn24BwCwEztT';
|
||
this.zhuangtai = '待租'
|
||
break;
|
||
case '2':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uR3DQEssiK62ovhh88y8' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/u460R1NKWHEpHbt0U4H7';
|
||
this.zhuangtai = '预约中'
|
||
break;
|
||
case '3':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uG13E7BpUFF44wVYC9no' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uHQIdWCTmtUztl49wBKU';
|
||
this.zhuangtai = '骑行中'
|
||
break;
|
||
case '4':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uRod2zf3t9dAOYafWoWt' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uZpXq3TBtM5gVgJJeImY';
|
||
this.zhuangtai = '临时锁车'
|
||
break;
|
||
case '6':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uhZudZM3nEKj0tYKlho2' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/ujur6TezvPf4buFAqPHo';
|
||
this.zhuangtai = '调度中'
|
||
break;
|
||
case '8':
|
||
iconPath = currentPoint.onlineStatus == '0' ?
|
||
'https://lxnapi.ccttiot.com/bike/img/static/ucBKG3ebYRAToVweJihu' :
|
||
'https://lxnapi.ccttiot.com/bike/img/static/uyK7Vg4Lu8xb3oNVuG2l';
|
||
calloutColor = '#ffffff';
|
||
calloutBgColor = '#000000';
|
||
this.zhuangtai = '禁用'
|
||
break;
|
||
default:
|
||
iconPath = 'https://lxnapi.ccttiot.com/bike/img/static/uzhMeExOQJbMcZtrfGUV';
|
||
}
|
||
|
||
this.markers = [{
|
||
id: 1,
|
||
latitude: currentPoint.latitude,
|
||
longitude: currentPoint.longitude,
|
||
width: 40,
|
||
height: 47,
|
||
iconPath: iconPath,
|
||
callout: {
|
||
content: ` ${this.currentSpeed}km/h`,
|
||
color: calloutColor,
|
||
fontSize: 10,
|
||
borderRadius: 10,
|
||
bgColor: calloutBgColor,
|
||
padding: 2,
|
||
display: 'ALWAYS'
|
||
}
|
||
}]
|
||
},
|
||
calculateDistance(lat1, lon1, lat2, lon2) {
|
||
const R = 6371000
|
||
const φ1 = this.toRadians(lat1)
|
||
const φ2 = this.toRadians(lat2)
|
||
const Δφ = this.toRadians(lat2 - lat1)
|
||
const Δλ = this.toRadians(lon2 - lon1)
|
||
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||
Math.cos(φ1) * Math.cos(φ2) *
|
||
Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||
return R * c
|
||
},
|
||
toRadians(degrees) {
|
||
return degrees * Math.PI / 180
|
||
},
|
||
changeSpeed(direction) {
|
||
if (direction === 'up' && this.currentSpeedIndex < this.speedOptions.length - 1) {
|
||
this.currentSpeedIndex++
|
||
} else if (direction === 'down' && this.currentSpeedIndex > 0) {
|
||
this.currentSpeedIndex--
|
||
}
|
||
this.playbackSpeed = this.speedOptions[this.currentSpeedIndex]
|
||
this.playbackInterval = 1000 / this.playbackSpeed
|
||
if (this.isPlaying) {
|
||
this.stopPlayback();
|
||
this.startPlayback();
|
||
}
|
||
},
|
||
onSliderChange(e) {
|
||
const index = parseInt(e.detail.value);
|
||
this.currentProgress = index;
|
||
this.updateMapPoint(index);
|
||
},
|
||
onSliderChanging(e) {
|
||
const index = parseInt(e.detail.value);
|
||
this.updateMapPoint(index);
|
||
},
|
||
togglePlay() {
|
||
this.isPlaying = !this.isPlaying;
|
||
if (this.isPlaying) {
|
||
this.startPlayback();
|
||
} else {
|
||
this.stopPlayback();
|
||
}
|
||
},
|
||
startPlayback() {
|
||
if (this.playbackTimer) return
|
||
this.playbackTimer = setInterval(() => {
|
||
if (this.currentProgress >= this.trackPoints.length - 1) {
|
||
this.stopPlayback()
|
||
return
|
||
}
|
||
this.currentProgress++
|
||
this.updateMapPoint(this.currentProgress)
|
||
}, this.playbackInterval)
|
||
},
|
||
stopPlayback() {
|
||
if (this.playbackTimer) {
|
||
clearInterval(this.playbackTimer)
|
||
this.playbackTimer = null
|
||
}
|
||
this.isPlaying = false
|
||
},
|
||
formatDateTime(date) {
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
const hour = date.getHours().toString().padStart(2, '0')
|
||
const minute = date.getMinutes().toString().padStart(2, '0')
|
||
const second = '00'
|
||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||
},
|
||
confirm1(e) {
|
||
this.activeTime=0
|
||
const selectedTime = new Date(e.year, e.month - 1, e.day, e.hour, e.minute)
|
||
this.startTime = this.formatDateTime(selectedTime)
|
||
this.updateTrackData()
|
||
},
|
||
|
||
confirm2(e) {
|
||
this.activeTime=0
|
||
const selectedTime = new Date(e.year, e.month - 1, e.day, e.hour, e.minute)
|
||
this.endTime = this.formatDateTime(selectedTime)
|
||
this.updateTrackData()
|
||
},
|
||
|
||
selectTime(time) {
|
||
this.activeTime = time
|
||
const hours = parseInt(time)
|
||
const now = new Date()
|
||
const startTime = new Date(now - hours * 60 * 60 * 1000)
|
||
this.startTime = this.formatDateTime(startTime)
|
||
this.endTime = this.formatDateTime(now)
|
||
this.updateTrackData()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
.page {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background-color: #f5f7fa;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
.map-container {
|
||
width: 100%;
|
||
height: 45vh;
|
||
position: relative;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
flex-shrink: 0;
|
||
|
||
.map {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.park {
|
||
position: absolute;
|
||
right: 24rpx;
|
||
bottom: 48rpx;
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
z-index: 999;
|
||
transition: transform 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 16rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.track-panel {
|
||
flex: 1;
|
||
background: #fff;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
margin-top: -30rpx;
|
||
position: relative;
|
||
z-index: 2;
|
||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||
padding: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.time-range {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 16rpx;
|
||
gap: 16rpx;
|
||
|
||
.time-picker {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #f8fafc;
|
||
padding: 16rpx;
|
||
border-radius: 12rpx;
|
||
border: 2rpx solid #e2e8f0;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
background: #f1f5f9;
|
||
}
|
||
|
||
text {
|
||
margin-right: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #334155;
|
||
}
|
||
}
|
||
|
||
.separator {
|
||
padding: 0 16rpx;
|
||
color: #64748b;
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
.time-selector {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
gap: 12rpx;
|
||
|
||
.time-btn {
|
||
flex: 1;
|
||
padding: 12rpx 20rpx;
|
||
border-radius: 32rpx;
|
||
font-size: 24rpx;
|
||
color: #64748b;
|
||
background: #f1f5f9;
|
||
transition: all 0.3s ease;
|
||
text-align: center;
|
||
|
||
&.active {
|
||
background: #3b82f6;
|
||
color: #fff;
|
||
box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
}
|
||
|
||
.device-info {
|
||
padding: 16rpx;
|
||
background: #f8fafc;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 16rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
|
||
|
||
.device-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.device-number {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.device-type {
|
||
font-size: 24rpx;
|
||
color: #64748b;
|
||
margin-left: 12rpx;
|
||
}
|
||
|
||
.label {
|
||
width: 100rpx;
|
||
font-size: 24rpx;
|
||
color: #64748b;
|
||
}
|
||
|
||
.value {
|
||
flex: 1;
|
||
font-size: 24rpx;
|
||
color: #334155;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
.playback-panel {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 16rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
|
||
.playback-controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
// position: absolute;
|
||
// top: 0;
|
||
// right: 0;
|
||
.progress-row {
|
||
padding: 0 80rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.control-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 8rpx;
|
||
|
||
.left-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.play-btn {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: #3b82f6;
|
||
border-radius: 50%;
|
||
box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
|
||
transition: all 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
text {
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.speed-control {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #f8fafc;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 32rpx;
|
||
border: 2rpx solid #e2e8f0;
|
||
|
||
.speed-label {
|
||
font-size: 24rpx;
|
||
color: #334155;
|
||
font-weight: 500;
|
||
margin-right: 12rpx;
|
||
min-width: 44rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.speed-buttons {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.speed-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
line-height: 60rpx;
|
||
text-align: center;
|
||
background: #fff;
|
||
border-radius: 50%;
|
||
font-size: 28rpx;
|
||
color: #334155;
|
||
font-weight: 500;
|
||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||
transition: all 0.2s ease;
|
||
|
||
&:active {
|
||
background: #f1f5f9;
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.point-info {
|
||
font-size: 24rpx;
|
||
color: #64748b;
|
||
background: #f8fafc;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 24rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 自定义滑块样式
|
||
::v-deep .uni-slider {
|
||
margin: 0;
|
||
}
|
||
|
||
::v-deep .uni-slider-handle {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
background-color: #3b82f6;
|
||
box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.3);
|
||
transition: transform 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
|
||
::v-deep .uni-slider-track {
|
||
height: 4rpx;
|
||
background-color: #3b82f6;
|
||
}
|
||
</style> |