chuangte_bike_newxcx/page_shanghu/guanli/bike_track.vue

960 lines
25 KiB
Vue
Raw Normal View History

2025-04-01 21:35:30 +08:00
<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"
2025-04-16 09:14:52 +08:00
:markers="markers" :scale="18" :polygons="polygon">
2025-04-01 21:35:30 +08:00
<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>
<!-- 快捷时间选择器 -->
2025-04-30 18:03:27 +08:00
<view class="time-selector" v-if="qufen != 123">
2025-04-01 21:35:30 +08:00
<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">
2025-04-30 18:03:27 +08:00
<text class="device-number" style="margin-right: 200rpx;">车牌{{ vehicleNum == null ? '' : vehicleNum }}</text><text class="device-number">SN{{ deviceNumber }}</text>
2025-04-01 21:35:30 +08:00
</view>
<view class="device-item">
2025-04-30 18:03:27 +08:00
<text class="value">状态{{zhuangtai}}</text>
<text class="value">速度{{ currentSpeed }}km/h</text>
2025-04-01 21:35:30 +08:00
</view>
<view class="device-item">
2025-04-30 18:03:27 +08:00
<text class="value">电压{{ currentBattery }}V</text>
2025-04-01 21:35:30 +08:00
</view>
<view class="device-item">
2025-04-30 18:03:27 +08:00
<text class="value">卫星{{ satellites }}</text>
<text class="value">信号{{ signal }}</text>
2025-04-01 21:35:30 +08:00
</view>
<view class="device-item">
2025-04-30 18:03:27 +08:00
<text class="value">电门{{ quality }}</text>
<text class="value">锁状态{{ lockStatus }}</text>
2025-04-01 21:35:30 +08:00
</view>
<view class="device-item">
2025-04-30 18:03:27 +08:00
<text class="value">定位{{ location }}</text>
2025-04-01 21:35:30 +08:00
</view>
</view>
<!-- 轨迹回放控制面板 -->
<view class="playback-panel">
<view class="playback-controls">
2025-04-30 18:03:27 +08:00
<!-- 进度条行 -->
<view class="progress-row">
<slider class="progress-slider" :value="currentProgress" :min="0" :max="trackPoints.length - 1"
:step="1" @change="onSliderChange" @changing="onSliderChanging" />
</view>
2025-04-01 21:35:30 +08:00
<!-- 控制按钮行 -->
<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' }
],
2025-04-16 09:14:52 +08:00
type: '',
2025-04-27 17:37:23 +08:00
qufen:'',
2025-04-30 18:03:27 +08:00
orid:'',
id:'',
lockStatus:'',
satellites:'',
signal:'',
quality:'',
includePoints: [], // 添加includePoints数组
vehicleNum:'',
zhuangtai:''
2025-04-01 21:35:30 +08:00
}
},
onLoad(e) {
2025-04-30 18:03:27 +08:00
console.log(e);
2025-04-01 21:35:30 +08:00
this.sn = e.sn
this.type = e.type
2025-04-30 18:03:27 +08:00
if(e.id){
this.id = e.id
}
2025-04-16 09:14:52 +08:00
if(e.qufen){
2025-04-27 17:37:23 +08:00
this.orid = e.orid
2025-04-16 09:14:52 +08:00
this.qufen = e.qufen
2025-04-01 21:35:30 +08:00
this.startTime = e.startTime
2025-04-30 18:03:27 +08:00
this.endTime = e.endTime == null ? this.formatCurrentTime() : e.endTime
console.log(this.startTime, this.endTime,'0000')
2025-04-16 09:14:52 +08:00
}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
2025-04-30 18:03:27 +08:00
this.endTime = e.endTime == null ? this.formatCurrentTime() : e.endTime
console.log(this.startTime, this.endTime,'1111')
2025-04-16 09:14:52 +08:00
}
2025-04-01 21:35:30 +08:00
}
2025-04-16 09:14:52 +08:00
2025-04-01 21:35:30 +08:00
this.getDeviceInfo()
this.updateTrackData()
},
methods: {
getDeviceInfo() {
2025-04-30 18:03:27 +08:00
this.$u.get(`/bst/device?id=${this.id}`).then((res) => {
2025-04-01 21:35:30 +08:00
if (res.code === 200) {
2025-04-30 18:03:27 +08:00
this.vehicleNum = res.data.vehicleNum
2025-04-01 21:35:30 +08:00
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()
}
})
},
2025-04-30 18:03:27 +08:00
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())}`;
},
2025-04-01 21:35:30 +08:00
updateTrackData() {
console.log(this.startTime, this.endTime)
const formattedStartTime = this.startTime
const formattedEndTime = this.endTime
2025-04-27 17:37:23 +08:00
if(this.orid){
2025-04-30 18:03:27 +08:00
this.$u.get('/bst/locationLog/listAll?orderId=' + this.orid + '&startTime=' + formattedStartTime + '&endTime=' + formattedEndTime).then((res) => {
2025-04-27 17:37:23 +08:00
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
}
2025-04-30 18:03:27 +08:00
// 按时间排序轨迹点
const sortedData = res.data.sort((a, b) => {
return new Date(a.at) - new Date(b.at)
})
this.trackPoints = sortedData.map(point => ({
2025-04-27 17:37:23 +08:00
latitude: parseFloat(point.latitude),
longitude: parseFloat(point.longitude),
time: point.at,
status: point.status,
onlineStatus: point.onlineStatus,
2025-04-30 18:03:27 +08:00
remainingPower: point.voltage || 0,
lockStatus:point.lockStatus,
satellites:point.satellites,
signal:point.signal,
quality:point.quality
2025-04-27 17:37:23 +08:00
}))
2025-04-30 18:03:27 +08:00
console.log(this.trackPoints,'000');
2025-04-27 17:37:23 +08:00
if (this.trackPoints.length > 0) {
this.currentProgress = 0;
this.updateMapPoint(0);
}
2025-04-01 21:35:30 +08:00
}
2025-04-27 17:37:23 +08:00
}).catch(error => {
uni.showToast({
title: '获取轨迹数据失败',
icon: 'none',
duration: 2000
})
console.error("Error fetching track data:", error);
})
}else{
2025-04-30 18:03:27 +08:00
this.$u.get('/bst/locationLog/listAll?deviceId=' + this.id + '&startTime=' + formattedStartTime + '&endTime=' + formattedEndTime).then((res) => {
2025-04-27 17:37:23 +08:00
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
}
2025-04-30 18:03:27 +08:00
// 按时间排序轨迹点
const sortedData = res.data.sort((a, b) => {
return new Date(a.at) - new Date(b.at)
})
this.trackPoints = sortedData.map(point => ({
2025-04-27 17:37:23 +08:00
latitude: parseFloat(point.latitude),
longitude: parseFloat(point.longitude),
time: point.at,
status: point.status,
onlineStatus: point.onlineStatus,
2025-04-30 18:03:27 +08:00
remainingPower: point.voltage || 0,
lockStatus:point.lockStatus,
satellites:point.satellites,
signal:point.signal,
quality:point.quality
2025-04-27 17:37:23 +08:00
}))
if (this.trackPoints.length > 0) {
this.currentProgress = 0;
this.updateMapPoint(0);
}
2025-04-01 21:35:30 +08:00
}
2025-04-27 17:37:23 +08:00
}).catch(error => {
uni.showToast({
title: '获取轨迹数据失败',
icon: 'none',
duration: 2000
})
console.error("Error fetching track data:", error);
2025-04-01 21:35:30 +08:00
})
2025-04-27 17:37:23 +08:00
}
2025-04-01 21:35:30 +08:00
},
// 转换多个边界数据为多边形
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,
2025-04-11 18:23:16 +08:00
strokeWidth: 1,
2025-04-01 21:35:30 +08:00
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,
2025-04-30 18:03:27 +08:00
fillColor: "#55888840",
strokeColor: "#22FF0040",
2025-04-11 18:23:16 +08:00
strokeWidth: 1,
2025-04-01 21:35:30 +08:00
zIndex: 1
}
},
updateMapPoint(index) {
if (!this.trackPoints[index]) return
const currentPoint = this.trackPoints[index]
const prevPoint = index > 0 ? this.trackPoints[index - 1] : null
2025-04-30 18:03:27 +08:00
2025-04-01 21:35:30 +08:00
// 计算速度
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'
}
2025-04-30 18:03:27 +08:00
2025-04-01 21:35:30 +08:00
// 更新信息显示
this.currentPointTime = currentPoint.time
this.location = `${currentPoint.longitude}, ${currentPoint.latitude}`
2025-04-30 18:03:27 +08:00
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
}))
2025-04-01 21:35:30 +08:00
// 更新轨迹线
2025-04-30 18:03:27 +08:00
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
2025-04-01 21:35:30 +08:00
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'
2025-04-30 18:03:27 +08:00
this.zhuangtai = '仓库中'
2025-04-01 21:35:30 +08:00
break;
case '1':
iconPath = currentPoint.onlineStatus == '0' ?
'https://lxnapi.ccttiot.com/bike/img/static/uzhMeExOQJbMcZtrfGUV' :
'https://lxnapi.ccttiot.com/bike/img/static/uheL17wVZn24BwCwEztT';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '待租'
2025-04-01 21:35:30 +08:00
break;
case '2':
iconPath = currentPoint.onlineStatus == '0' ?
'https://lxnapi.ccttiot.com/bike/img/static/uR3DQEssiK62ovhh88y8' :
'https://lxnapi.ccttiot.com/bike/img/static/u460R1NKWHEpHbt0U4H7';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '预约中'
2025-04-01 21:35:30 +08:00
break;
case '3':
iconPath = currentPoint.onlineStatus == '0' ?
'https://lxnapi.ccttiot.com/bike/img/static/uG13E7BpUFF44wVYC9no' :
'https://lxnapi.ccttiot.com/bike/img/static/uHQIdWCTmtUztl49wBKU';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '骑行中'
2025-04-01 21:35:30 +08:00
break;
case '4':
iconPath = currentPoint.onlineStatus == '0' ?
'https://lxnapi.ccttiot.com/bike/img/static/uRod2zf3t9dAOYafWoWt' :
'https://lxnapi.ccttiot.com/bike/img/static/uZpXq3TBtM5gVgJJeImY';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '临时锁车'
2025-04-01 21:35:30 +08:00
break;
case '6':
iconPath = currentPoint.onlineStatus == '0' ?
'https://lxnapi.ccttiot.com/bike/img/static/uhZudZM3nEKj0tYKlho2' :
'https://lxnapi.ccttiot.com/bike/img/static/ujur6TezvPf4buFAqPHo';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '调度中'
2025-04-01 21:35:30 +08:00
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';
2025-04-30 18:03:27 +08:00
this.zhuangtai = '禁用'
2025-04-01 21:35:30 +08:00
break;
default:
iconPath = 'https://lxnapi.ccttiot.com/bike/img/static/uzhMeExOQJbMcZtrfGUV';
}
2025-04-30 18:03:27 +08:00
2025-04-01 21:35:30 +08:00
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
2025-04-30 18:03:27 +08:00
const φ1 = this.toRadians(lat1)
2025-04-01 21:35:30 +08:00
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;
2025-04-30 18:03:27 +08:00
// position: absolute;
// top: 0;
// right: 0;
2025-04-01 21:35:30 +08:00
.progress-row {
2025-04-30 18:03:27 +08:00
padding: 0 80rpx;
2025-04-01 21:35:30 +08:00
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>