742 lines
20 KiB
Vue
742 lines
20 KiB
Vue
<!-- TrackPlayback.vue(轨迹回放页面) -->
|
||
<template>
|
||
<div class="track-playback-content">
|
||
<!-- 左侧搜索+列表 -->
|
||
<div class="left-list">
|
||
|
||
<el-input
|
||
v-model="search"
|
||
class="search-input"
|
||
placeholder="请输入任务号/车辆名"
|
||
:suffix-icon="Search"
|
||
/>
|
||
<el-scrollbar class="task-list">
|
||
<div
|
||
v-for="item in filteredTasks"
|
||
:key="item.id"
|
||
class="task-item"
|
||
:class="{ active: item.id === activeId }"
|
||
@click="selectTask(item)"
|
||
>
|
||
<!-- 选中三角角标 -->
|
||
<div v-if="item.id === activeId" class="corner-triangle">
|
||
|
||
</div>
|
||
<!-- 第一行:蓝色圆点+编号+任务名 -->
|
||
<div class="task-row1">
|
||
<span class="dot"></span>
|
||
<span class="task-no">{{ item.no }}</span>
|
||
<span class="task-name">{{ item.name }}</span>
|
||
</div>
|
||
<!-- 第二行:时间段 -->
|
||
<div class="task-row2">{{ item.time }}</div>
|
||
<!-- 第三行:起点——>终点 -->
|
||
<div class="task-row3">
|
||
<span class="point start">起点</span>
|
||
<span class="label">{{ item.start }}</span>
|
||
<!-- <span class="arrow">——></span> -->
|
||
<img class="arrow" src="@/assets/images/arrow.png" alt="arrow">
|
||
<span class="point end">终点</span>
|
||
<span class="label">{{ item.end }}</span>
|
||
</div>
|
||
</div>
|
||
</el-scrollbar>
|
||
</div>
|
||
|
||
<!-- 右侧地图及轨迹详情 -->
|
||
<div class="right-map">
|
||
<div class="map-container">
|
||
<div class="map-img-placeholder"></div>
|
||
<!-- 轨迹详情卡片(左上角) -->
|
||
<div class="track-detail-panel">
|
||
<!-- 第一行 -->
|
||
<div class="panel-header">
|
||
<span class="dot"></span>
|
||
<span class="panel-title">轨迹详情</span>
|
||
<el-button size="small" type="primary" class="replay-btn" @click="handleReplay">回放</el-button>
|
||
</div>
|
||
<!-- 第二行 -->
|
||
<div class="panel-info">
|
||
<div class="info-row">
|
||
<span class="carno">{{ activeTask?.licensePlate || activeVehicleId || "未选择车辆" }}</span>
|
||
<span class="info-item">最大时速 <b>{{ trackDetails.maxSpeed }}km/h</b></span>
|
||
<span class="info-item">平均时速 <b>{{ trackDetails.averageSpeed }}km/h</b></span>
|
||
<span class="info-item">总里程 <b>{{ trackDetails.totalDistance }}km</b></span>
|
||
<span class="info-item">耗时 <b>{{ trackDetails.totalTime }}min</b></span>
|
||
<span class="info-item warn">冲突告警 <b>{{ trackDetails.warnings }}</b></span>
|
||
<span class="info-item prewarn">冲突预警 <b>{{ trackDetails.preWarnings }}</b></span>
|
||
</div>
|
||
</div>
|
||
<!-- 第三行:进度条 -->
|
||
<div class="panel-progress">
|
||
<div class="progress-bar-wrap"
|
||
@mousemove="handleProgressHover"
|
||
@mouseleave="showTooltip = false"
|
||
@click="handleProgressClick">
|
||
<div class="progress-bar-bg"></div>
|
||
<div class="progress-bar-fg" :style="{width: progress+'%'}"></div>
|
||
<!-- 进度条拖动点 -->
|
||
<div class="progress-thumb" :style="{left: progress+'%'}" @mousedown="startDrag"></div>
|
||
<!-- 预警/告警红旗 -->
|
||
<div
|
||
v-for="flag in flags"
|
||
:key="flag.label"
|
||
class="progress-flag"
|
||
:style="{left: flag.percent+'%'}"
|
||
:title="flag.label"
|
||
>
|
||
<svg width="14" height="18" viewBox="0 0 14 18">
|
||
<polygon points="2,2 12,5 2,8" fill="#E34D59"/>
|
||
<rect x="1" y="2" width="2" height="14" fill="#E34D59"/>
|
||
</svg>
|
||
</div>
|
||
<!-- 鼠标悬浮提示 -->
|
||
<div v-if="showTooltip" class="progress-tooltip" :style="{left: tooltipLeft+'px'}">
|
||
{{ tooltipTime }}
|
||
</div>
|
||
</div>
|
||
<div class="progress-bottom">
|
||
<span class="start-time">{{ trackDetails.startTime || "未开始" }}</span>
|
||
<div class="speed-select">
|
||
<el-dropdown @command="setSpeed">
|
||
<span class="el-dropdown-link">
|
||
{{ speed }}x <i class="el-icon-arrow-down"></i>
|
||
</span>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item command="1">1x</el-dropdown-item>
|
||
<el-dropdown-item command="2">2x</el-dropdown-item>
|
||
<el-dropdown-item command="4">4x</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
<span class="end-time">{{ trackDetails.endTime || "未结束" }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, defineProps, watchEffect } from "vue";
|
||
import { Search } from "@element-plus/icons-vue";
|
||
import {
|
||
listCarRunInfo,
|
||
exportCarRunInfo,
|
||
listCarRunInfoByVehicleId,
|
||
listCarRunInfoByLicensePlate,
|
||
getLatestLocationByVehicleId,
|
||
getLatestLocationByLicensePlate
|
||
} from "@/api/monitor/carRunInfo";
|
||
import { ElMessage } from "element-plus";
|
||
|
||
// 查询参数
|
||
const queryParams = ref({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
timeRange: [],
|
||
vehicleId: '',
|
||
licensePlate: ''
|
||
});
|
||
|
||
// 数据列表
|
||
const runInfoList = ref([]);
|
||
const loading = ref(false);
|
||
const total = ref(0);
|
||
|
||
// 车辆信息模拟数据
|
||
const carInfo = {
|
||
img: "https://cdn.jsdelivr.net/gh/duogongneng/testcdn/car-demo.jpg",
|
||
no: "QN001",
|
||
status: "在线",
|
||
statusClass: "online",
|
||
type: "货运车",
|
||
driver: "张三",
|
||
phone: "15689742356",
|
||
};
|
||
|
||
// 任务模拟数据(将由接口数据替换)
|
||
const tasks = ref([]);
|
||
|
||
const search = ref("");
|
||
const activeId = ref(0);
|
||
|
||
// 接收vehicle属性
|
||
const props = defineProps({
|
||
vehicle: {
|
||
type: Object,
|
||
default: () => ({})
|
||
}
|
||
});
|
||
|
||
// 轨迹详情数据
|
||
const trackDetails = ref({
|
||
maxSpeed: "0",
|
||
averageSpeed: "0",
|
||
totalDistance: "0",
|
||
totalTime: "0",
|
||
warnings: "0",
|
||
preWarnings: "0",
|
||
startTime: "",
|
||
endTime: ""
|
||
});
|
||
|
||
const activeVehicleId = ref("");
|
||
|
||
// 监听vehicle属性变化
|
||
watchEffect(() => {
|
||
if (props.vehicle?.carId) {
|
||
activeVehicleId.value = props.vehicle.carId;
|
||
// 更新查询参数
|
||
queryParams.value.vehicleId = props.vehicle.carId;
|
||
}
|
||
});
|
||
|
||
// 获取车辆运动信息列表数据
|
||
async function getRunInfoList() {
|
||
loading.value = true;
|
||
try {
|
||
const response = await listCarRunInfo(queryParams.value);
|
||
// console.log("-----------------------------------,", response);
|
||
|
||
if (response.code === 200) {
|
||
runInfoList.value = response.rows || [];
|
||
total.value = response.total;
|
||
|
||
// 转换数据格式为任务列表
|
||
// 根据车辆ID对数据进行分组处理
|
||
const groupByVehicle = {};
|
||
runInfoList.value.forEach(item => {
|
||
if (!groupByVehicle[item.vehicleId]) {
|
||
groupByVehicle[item.vehicleId] = [];
|
||
}
|
||
groupByVehicle[item.vehicleId].push(item);
|
||
});
|
||
|
||
// 将分组后的数据转换为任务列表格式
|
||
const tasksList = [];
|
||
Object.keys(groupByVehicle).forEach((vehicleId, index) => {
|
||
const vehicleData = groupByVehicle[vehicleId];
|
||
const firstPoint = vehicleData[0];
|
||
const lastPoint = vehicleData[vehicleData.length - 1];
|
||
|
||
// 计算平均速度
|
||
const avgSpeed = (vehicleData.reduce((sum, point) => sum + point.speed, 0) / vehicleData.length).toFixed(2);
|
||
|
||
tasksList.push({
|
||
id: parseInt(vehicleId),
|
||
no: vehicleId,
|
||
name: `车辆${firstPoint.licensePlate}轨迹`,
|
||
time: `${firstPoint.timestamp}--${lastPoint.timestamp}`,
|
||
start: `经度${firstPoint.longitude},纬度${firstPoint.latitude}`,
|
||
end: `经度${lastPoint.longitude},纬度${lastPoint.latitude}`,
|
||
status: "已完成",
|
||
speed: `${avgSpeed}km/h`,
|
||
licensePlate: firstPoint.licensePlate,
|
||
points: vehicleData
|
||
});
|
||
});
|
||
|
||
tasks.value = tasksList;
|
||
|
||
// 设置默认选中第一项
|
||
if (tasks.value.length > 0) {
|
||
activeId.value = tasks.value[0].id;
|
||
}
|
||
} else {
|
||
ElMessage.error(response.msg || '获取车辆运动信息列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取车辆运动信息列表异常', error);
|
||
ElMessage.error('获取车辆运动信息列表异常');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
// 根据车辆ID获取轨迹详情
|
||
async function getTrackDetailByVehicleId(vehicleId) {
|
||
if (!vehicleId) return;
|
||
|
||
try {
|
||
const response = await listCarRunInfoByVehicleId(vehicleId, {});
|
||
if (response.code === 200) {
|
||
const trackData = response.data || [];
|
||
// 处理轨迹数据...
|
||
console.log('车辆轨迹数据:', trackData);
|
||
// 这里可以处理轨迹点、告警点等
|
||
// 假设返回的数据格式与tasks中的points一致
|
||
processTrackPoints(trackData);
|
||
} else {
|
||
ElMessage.error(response.msg || '获取车辆轨迹详情失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取车辆轨迹详情异常', error);
|
||
}
|
||
}
|
||
|
||
// 根据车牌号获取车辆最新位置
|
||
async function getLatestLocation(licensePlate) {
|
||
if (!licensePlate) return;
|
||
|
||
try {
|
||
const response = await getLatestLocationByLicensePlate(licensePlate);
|
||
if (response.code === 200) {
|
||
const locationData = response.data;
|
||
// 处理位置数据...
|
||
console.log('车辆最新位置:', locationData);
|
||
}
|
||
} catch (error) {
|
||
console.error('获取车辆最新位置异常', error);
|
||
}
|
||
}
|
||
|
||
const filteredTasks = computed(() => {
|
||
if (!search.value) return tasks.value;
|
||
return tasks.value.filter(
|
||
(t) =>
|
||
t.name.includes(search.value) ||
|
||
t.no.includes(search.value) ||
|
||
t.id.toString().includes(search.value)
|
||
);
|
||
});
|
||
|
||
const activeTask = computed(() => tasks.value.find((t) => t.id === activeId.value));
|
||
|
||
function selectTask(item) {
|
||
activeId.value = item.id;
|
||
|
||
// 从任务中提取轨迹数据
|
||
if (item.points && item.points.length > 0) {
|
||
processTrackPoints(item.points);
|
||
}
|
||
}
|
||
|
||
// 处理轨迹点数据
|
||
function processTrackPoints(points) {
|
||
// 按时间排序
|
||
points.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
||
|
||
// 计算最大速度
|
||
const maxSpeed = Math.max(...points.map(p => p.speed)).toFixed(2);
|
||
|
||
// 计算平均速度
|
||
const avgSpeed = (points.reduce((sum, p) => sum + p.speed, 0) / points.length).toFixed(2);
|
||
|
||
// 计算总距离 (简单估算,实际应该使用更复杂的地理距离计算)
|
||
let totalDistance = 0;
|
||
for (let i = 1; i < points.length; i++) {
|
||
const prev = points[i-1];
|
||
const curr = points[i];
|
||
// 使用直线距离简单估算
|
||
const dist = Math.sqrt(
|
||
Math.pow(curr.longitude - prev.longitude, 2) +
|
||
Math.pow(curr.latitude - prev.latitude, 2)
|
||
) * 111000; // 粗略转换为米
|
||
totalDistance += dist;
|
||
}
|
||
const distanceKm = (totalDistance / 1000).toFixed(2);
|
||
|
||
// 计算总时间
|
||
const startTime = new Date(points[0].timestamp);
|
||
const endTime = new Date(points[points.length-1].timestamp);
|
||
const totalMinutes = Math.round((endTime - startTime) / (60 * 1000));
|
||
|
||
// 更新轨迹详情
|
||
trackDetails.value = {
|
||
maxSpeed: maxSpeed,
|
||
averageSpeed: avgSpeed,
|
||
totalDistance: distanceKm,
|
||
totalTime: totalMinutes > 0 ? totalMinutes.toString() : "1",
|
||
warnings: "0", // 实际应从数据中获取告警信息
|
||
preWarnings: "0", // 实际应从数据中获取预警信息
|
||
startTime: points[0].timestamp,
|
||
endTime: points[points.length-1].timestamp
|
||
};
|
||
|
||
// 处理告警标记点 (示例,实际应根据数据判断)
|
||
flags.value = [
|
||
{
|
||
percent: 20,
|
||
label: "告警 " + points[Math.floor(points.length * 0.2)].timestamp
|
||
},
|
||
{
|
||
percent: 60,
|
||
label: "预警 " + points[Math.floor(points.length * 0.6)].timestamp
|
||
}
|
||
];
|
||
}
|
||
|
||
// 回放按钮点击处理函数
|
||
function handleReplay() {
|
||
// 获取当前选中任务
|
||
const activeTask = tasks.value.find(t => t.id === activeId.value);
|
||
|
||
if (activeTask) {
|
||
if (activeTask.points && activeTask.points.length > 0) {
|
||
// 如果任务已包含轨迹点数据,直接处理
|
||
processTrackPoints(activeTask.points);
|
||
ElMessage.success('开始轨迹回放');
|
||
} else {
|
||
// 如果没有轨迹点数据,根据车辆ID获取
|
||
getTrackDetailByVehicleId(activeTask.id);
|
||
}
|
||
} else if (queryParams.value.vehicleId) {
|
||
// 使用查询参数中的车辆ID
|
||
getTrackDetailByVehicleId(queryParams.value.vehicleId);
|
||
} else if (props.vehicle?.carId) {
|
||
// 使用传入的车辆ID
|
||
getTrackDetailByVehicleId(props.vehicle.carId);
|
||
} else {
|
||
// 获取列表数据
|
||
getRunInfoList();
|
||
ElMessage.info('请先选择一个车辆任务');
|
||
}
|
||
}
|
||
|
||
// 导出车辆运动信息
|
||
async function handleExport() {
|
||
try {
|
||
await exportCarRunInfo(queryParams.value);
|
||
ElMessage.success('导出成功');
|
||
} catch (error) {
|
||
console.error('导出车辆运动信息异常', error);
|
||
ElMessage.error('导出车辆运动信息异常');
|
||
}
|
||
}
|
||
|
||
const progress = ref(30); // 进度百分比
|
||
const speed = ref(1);
|
||
const showTooltip = ref(false);
|
||
const tooltipTime = ref("");
|
||
const tooltipLeft = ref(0);
|
||
|
||
// 预警/告警红旗模拟数据(将由接口数据替换)
|
||
const flags = ref([]);
|
||
|
||
// 拖动进度条
|
||
let dragging = false;
|
||
function startDrag(e) {
|
||
dragging = true;
|
||
document.addEventListener("mousemove", onDrag);
|
||
document.addEventListener("mouseup", stopDrag);
|
||
}
|
||
function onDrag(e) {
|
||
if (!dragging) return;
|
||
const bar = document.querySelector(".progress-bar-wrap");
|
||
const rect = bar.getBoundingClientRect();
|
||
let percent = ((e.clientX - rect.left) / rect.width) * 100;
|
||
percent = Math.max(0, Math.min(100, percent));
|
||
progress.value = percent;
|
||
}
|
||
function stopDrag() {
|
||
dragging = false;
|
||
document.removeEventListener("mousemove", onDrag);
|
||
document.removeEventListener("mouseup", stopDrag);
|
||
}
|
||
|
||
// 鼠标悬浮显示时分秒
|
||
function handleProgressHover(e) {
|
||
const bar = e.currentTarget;
|
||
const rect = bar.getBoundingClientRect();
|
||
let percent = ((e.clientX - rect.left) / rect.width) * 100;
|
||
percent = Math.max(0, Math.min(100, percent));
|
||
tooltipLeft.value = e.clientX - rect.left;
|
||
// 假设总时长20分钟
|
||
const totalSec = 20 * 60;
|
||
const sec = Math.round((percent / 100) * totalSec);
|
||
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
||
const ss = String(sec % 60).padStart(2, "0");
|
||
tooltipTime.value = `${mm}:${ss}`;
|
||
showTooltip.value = true;
|
||
}
|
||
function handleProgressClick(e) {
|
||
const bar = e.currentTarget;
|
||
const rect = bar.getBoundingClientRect();
|
||
let percent = ((e.clientX - rect.left) / rect.width) * 100;
|
||
percent = Math.max(0, Math.min(100, percent));
|
||
progress.value = percent;
|
||
}
|
||
function setSpeed(val) {
|
||
speed.value = val;
|
||
}
|
||
|
||
// 页面加载时获取数据
|
||
onMounted(() => {
|
||
getRunInfoList();
|
||
// getLatestLocation("鲁B579");
|
||
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.track-playback-content {
|
||
display: flex;
|
||
gap: 20px;
|
||
flex: 1;
|
||
}
|
||
.left-list {
|
||
width: 25%;
|
||
|
||
border-radius: 8px;
|
||
padding: 16px 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.search-input {
|
||
margin-bottom: 10px;
|
||
}
|
||
.task-list {
|
||
max-height: 400px;
|
||
}
|
||
.task-item {
|
||
position: relative;
|
||
padding: 16px 16px 14px 16px;
|
||
border-radius: 8px;
|
||
margin-bottom: 14px;
|
||
cursor: pointer;
|
||
background: #343744;
|
||
color: #fff;
|
||
border: 2px solid transparent;
|
||
transition: border 0.2s, box-shadow 0.2s;
|
||
box-sizing: border-box;
|
||
}
|
||
.task-item.active {
|
||
border: 2px solid #347ae2;
|
||
box-shadow: 0 2px 8px rgba(52,122,226,0.08);
|
||
}
|
||
.corner-triangle {
|
||
background: url('@/assets/images/choice.png') no-repeat 100% 100%;
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
width: 38px;
|
||
height: 33px;
|
||
z-index: 2;
|
||
}
|
||
.task-row1 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
margin-bottom: 4px;
|
||
.dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
background: #347ae2;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
margin-right: 2px;
|
||
}
|
||
.task-no {
|
||
color: #347ae2;
|
||
font-weight: bold;
|
||
}
|
||
.task-name {
|
||
color: #fff;
|
||
}
|
||
}
|
||
.task-row2 {
|
||
font-size: 13px;
|
||
color: #b0b8c9;
|
||
margin-bottom: 6px;
|
||
}
|
||
.task-row3 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
.label{
|
||
font-size: 13px;
|
||
color:#fff;
|
||
}
|
||
.point {
|
||
padding: 0px 5px;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
background: transparent;
|
||
border: 1.5px solid;
|
||
&.start {
|
||
border-color: #347ae2;
|
||
background: rgba(52, 122, 226, 0.20);
|
||
color: #347ae2;
|
||
}
|
||
&.end {
|
||
border-color: #27ae60;
|
||
background: rgba(39, 174, 96, 0.20);
|
||
color: #27ae60;
|
||
}
|
||
}
|
||
.arrow {
|
||
color: #b0b8c9;
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
.right-map {
|
||
flex: 1;
|
||
border: 1px solid #343744;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
.map-container {
|
||
flex: 1;
|
||
|
||
border-radius: 8px;
|
||
min-height: 400px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.map-img-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
|
||
border-radius: 12px;
|
||
}
|
||
.track-detail-panel {
|
||
position: absolute;
|
||
top: 24px;
|
||
left: 24px;
|
||
min-width: 520px;
|
||
background: rgba(41,44,56,0.8);
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
|
||
padding: 18px 28px 18px 20px;
|
||
z-index: 10;
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
.dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
background: #347ae2;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
}
|
||
.panel-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #4ea1ff;
|
||
margin-right: 12px;
|
||
}
|
||
.replay-btn {
|
||
margin-left: auto;
|
||
}
|
||
}
|
||
.panel-info {
|
||
margin-bottom: 10px;
|
||
.info-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 18px;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
.carno {
|
||
color: #4ea1ff;
|
||
font-weight: bold;
|
||
font-size: 15px;
|
||
}
|
||
.info-item {
|
||
color: #b0b8c9;
|
||
b { color: #fff; font-weight: 500; }
|
||
&.warn b { color: #e34d59; }
|
||
&.prewarn b { color: #f7b500; }
|
||
}
|
||
}
|
||
}
|
||
.panel-progress {
|
||
margin-top: 8px;
|
||
.progress-bar-wrap {
|
||
position: relative;
|
||
height: 8px;
|
||
background: transparent;
|
||
margin-bottom: 8px;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.progress-bar-bg {
|
||
position: absolute;
|
||
left: 0; top: 0; right: 0; bottom: 0;
|
||
background: #343744;
|
||
border-radius: 4px;
|
||
}
|
||
.progress-bar-fg {
|
||
position: absolute;
|
||
left: 0; top: 0; bottom: 0;
|
||
background: linear-gradient(90deg, #347ae2, #4ea1ff);
|
||
border-radius: 4px;
|
||
height: 8px;
|
||
z-index: 1;
|
||
}
|
||
.progress-thumb {
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 16px;
|
||
height: 16px;
|
||
background: #fff;
|
||
border: 3px solid #347ae2;
|
||
border-radius: 50%;
|
||
z-index: 2;
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 8px rgba(52,122,226,0.12);
|
||
}
|
||
.progress-flag {
|
||
position: absolute;
|
||
top: -14px;
|
||
z-index: 3;
|
||
width: 14px;
|
||
height: 18px;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
pointer-events: auto;
|
||
}
|
||
.progress-tooltip {
|
||
position: absolute;
|
||
top: -32px;
|
||
background: #23263a;
|
||
color: #fff;
|
||
font-size: 12px;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
white-space: nowrap;
|
||
box-shadow: 0 2px 8px rgba(52,122,226,0.10);
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
}
|
||
.progress-bottom {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
font-size: 13px;
|
||
color: #b0b8c9;
|
||
.speed-select {
|
||
margin: 0 16px;
|
||
.el-dropdown-link {
|
||
color: #4ea1ff;
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
.start-time, .end-time {
|
||
font-size: 13px;
|
||
color: #b0b8c9;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|