<template>
|
<div class="timeline-container">
|
<div class="controls">
|
<div class="control-btn" @click="skipBackward">
|
<img src="@/assets/img/timeline/left.png" class="fas fa-step-backward" />
|
</div>
|
<div class="control-btn play-btn" @click="togglePlay">
|
<img v-show="isPlaying" src="@/assets/img/timeline/stop.png" />
|
<img v-show="!isPlaying" src="@/assets/img/timeline/start.png" />
|
</div>
|
<div class="control-btn" @click="skipForward">
|
<img src="@/assets/img/timeline/right.png" class="fas fa-step-forward" />
|
</div>
|
<div class="speed-control">
|
<div @click="toggleSpeedMenu">{{ playbackRate }}X</div>
|
<div class="speed-menu" v-show="showSpeedMenu">
|
<div v-for="rate in playbackRates" :key="rate" @click.capture="setPlaybackRate(rate)"
|
:class="{ active: playbackRate === rate }">
|
{{ rate }}X
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="timeline">
|
<div class="dates">
|
<div class="current-date">当前播放时间:{{ currentPlayingTime }}</div>
|
<div v-for="(date, index) in visibleDates" :key="index" class="date-label">
|
<!-- {{ formatDate(date) }} -->
|
</div>
|
<div>专题渲染:
|
<el-switch v-model="isColorRenderEnabled" @change="handleColorRenderChange" style="margin-top:-3px"
|
:disabled="!isPlaying || !isWaterPrimitiveCreated" />
|
<!-- active-text="开" inactive-text="关" -->
|
</div>
|
</div>
|
<div class="timeline-track" ref="timelineTrack" @click="seekToPosition">
|
<div class="timeline-progress" :style="{ width: progressPercentage + '%' }"></div>
|
<div class="timeline-cursor" :style="{ left: progressPercentage + '%' }"></div>
|
<div class="scale-markers">
|
<div class="scale-marker" style="left: 0%"></div>
|
<div class="scale-marker" style="left: 25%"></div>
|
<div class="scale-marker" style="left: 50%"></div>
|
<div class="scale-marker" style="left: 75%"></div>
|
<div class="scale-marker" style="left: 100%"></div>
|
</div>
|
<div class="time-markers">
|
<div v-for="(time, index) in timeMarkers" :key="index" class="time-marker"
|
:style="{ left: `${index * 25}%`, transform: 'translateX(-50%)' }">
|
<div class="date-part">{{ time.split(' ')[0] }}</div>
|
<div class="time-part">{{ time.split(' ')[1] }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div>
|
<ratelevel ref="ratelevelRef" :playing-time="sendCurrentPlayingTime" @finish-calculation="handleFinishCalculation"
|
style="margin-top: 12px; margin-left: 28px; margin-right: 10px;justify-content: flex-end;"></ratelevel>
|
<el-button @click="handleBack" style="margin-top: 3px; margin-left: 30px; margin-right: 10px">结束模拟</el-button>
|
</div>
|
|
</div>
|
</template>
|
|
<script setup>
|
import {
|
ref,
|
computed,
|
onMounted,
|
watch,
|
defineProps,
|
onBeforeUnmount,
|
inject,
|
reactive
|
} from "vue";
|
import ratelevel from "@/components/menu/flowRate_waterLevel.vue";
|
|
import dayjs from "dayjs";
|
import {
|
createWaterPrimitive,
|
destoryWaterPrimitive,
|
pauseWaterSimulation,
|
resumeWaterSimulation,
|
setTimeForWaterSimulation,
|
toggleWaterColorRender,
|
} from "@/utils/water";
|
import mapUtils from "@/utils/tools.js";
|
import { fetchWaterSimulationData } from "@/api/trApi.js";
|
import { EventBus } from "@/eventBus";
|
import { ElMessage } from "element-plus";
|
// 状态管理器
|
import { useSimStore } from "@/store/simulation";
|
import { storeToRefs } from "pinia";
|
const simStore = useSimStore();
|
const { selectedScheme } = storeToRefs(simStore);
|
|
const emit = defineEmits(["timeUpdate", "isPlaying", "playbackFinished", "isColorRender"]);
|
// 定义props
|
const props = defineProps({
|
waterSimulateParams: {
|
type: Object,
|
default: () => ({
|
date: ["2025-02-14T16:00:00.000Z", "2025-02-15T16:00:00.000Z"],
|
}),
|
},
|
});
|
// 响应式状态
|
let serviceInfo = ref(null); // 当前方案的服务地址
|
const ratelevelRef = ref(null); // 获取子组件实例的引用
|
const currentPlayingTime = ref(""); // 当前播放时间
|
const sendCurrentPlayingTime = ref(""); // 当前播放时间
|
const isPlaying = ref(false);
|
const playbackFinished = ref(true);
|
const currentTime = ref(0);
|
const duration = ref(60); // 一天的秒数
|
const playbackRate = ref(1);
|
const playbackRates = ref([1, 2, 4, 8]);
|
const showSpeedMenu = ref(false);
|
const waterTimestamps = ref([]); // 存储时间轴数据
|
const timeMarkers = ref([]);
|
const timelineTrack = ref(null);
|
const isColorRenderEnabled = ref(false); // 假设这是你的颜色渲染开关状态
|
const isWaterPrimitiveCreated = ref(false);
|
let playInterval = null;
|
const isRainEnabled = ref(false);
|
const rainParams = reactive({
|
rainSize: 0.5,
|
rainSpeed: 50,
|
rainColor: "#99B3CC",
|
rainDensity: 30 // 雨的密度
|
});
|
// 计算属性
|
const progressPercentage = computed(
|
() => (currentTime.value / duration.value) * 100
|
);
|
const visibleDates = computed(() =>
|
Array.from(
|
new Set(waterTimestamps.value.map((ts) => dayjs(ts).format("YYYY-MM-DD")))
|
).map((date) => dayjs(date).toDate())
|
);
|
// 播放控制
|
const togglePlay = () => {
|
// 这里应该再设定几个限制,如果缺少什么数据,无法进行仿真
|
if (!isPlaying.value && currentTime.value >= duration.value)
|
currentTime.value = 0;
|
|
isPlaying.value = !isPlaying.value;
|
emit("isPlaying", isPlaying.value);
|
|
if (isPlaying.value) {
|
startPlayback();
|
|
if (!isWaterPrimitiveCreated.value) {
|
// console.log(serviceInfo, '这里是当前方案的服务信息!');
|
// 这里通过water.js中去发送请求获取水面模拟
|
createWaterPrimitive({
|
baseUrl: `/simu/${serviceInfo}`,
|
// baseUrl: `/simu/c2h1dc`,
|
interval: intervalMap[playbackRate.value],
|
colorRender: isColorRenderEnabled.value
|
});
|
isWaterPrimitiveCreated.value = true;
|
} else {
|
resumeWaterSimulation();
|
toggleWaterColorRender(isColorRenderEnabled.value); // 更新颜色渲染
|
}
|
|
if (currentTime.value === 0) emit("playbackFinished", false);
|
|
if (isRainEnabled.value) {
|
mapUtils.toggleRain(rainParams, true);
|
}
|
} else {
|
stopPlayback();
|
pauseWaterSimulation();
|
|
isRainEnabled.value = true;
|
setTimeout(() => {
|
mapUtils.delRain();
|
}, 3000);
|
}
|
};
|
|
// 颜色渲染切换事件
|
const handleColorRenderChange = (enabled) => {
|
if (!isPlaying.value) {
|
ElMessage({
|
message: "请先启动水体模拟后再进行专题效果切换。",
|
type: "warning",
|
});
|
return; // 阻止后续逻辑执行
|
}
|
if (isWaterPrimitiveCreated.value) {
|
console.log('当前是否开启专题渲染:', enabled);
|
emit("isColorRender", enabled)
|
toggleWaterColorRender(enabled);
|
}
|
};
|
const intervalMap = {
|
1: 1000, // 1倍速
|
2: 500, // 2倍速
|
4: 250, // 4倍速
|
8: 125, // 8倍速
|
};
|
// 播放逻辑
|
const startPlayback = () => {
|
clearInterval(playInterval);
|
|
playInterval = setInterval(() => {
|
// 找到当前时间对应的索引
|
const currentIndex = findClosestTimestampIndex(currentTime.value);
|
const nextIndex = currentIndex + 1;
|
|
// 如果已经是最后一个时间点,停止播放
|
if (nextIndex >= waterTimestamps.value.length) {
|
currentTime.value = duration.value;
|
stopPlayback();
|
isPlaying.value = false;
|
emit("isPlaying", false);
|
emit("playbackFinished", true);
|
return;
|
}
|
|
// 更新时间为下一个时间点的时间差(秒)
|
const nextTimestamp = waterTimestamps.value[nextIndex];
|
const baseTimestamp = waterTimestamps.value[0];
|
currentTime.value = (nextTimestamp - baseTimestamp) / 1000;
|
|
// 触发更新
|
updateWeatherByProgress();
|
const progress = currentTime.value / duration.value;
|
emit("timeUpdate", progress * 100);
|
}, 1000 / playbackRate.value); // 根据播放速率调整间隔
|
};
|
// 降雨变化部分
|
// 降雨数据相关变量
|
let rainFallValues = ref([]); // 存储原始降雨量数据
|
let minRainValue = ref(Infinity);
|
let maxRainValue = ref(-Infinity);
|
// 获取降雨数据
|
function getRainfallData() {
|
if (!selectedScheme.value || !selectedScheme.value.data) {
|
console.warn("selectedScheme 或 data 不存在");
|
return;
|
}
|
// 注意:有时 data 可能是一个字符串(例如 JSON 字符串)
|
let data = selectedScheme.value.data;
|
// 如果是字符串,则尝试解析成对象
|
if (typeof data === 'string') {
|
try {
|
data = JSON.parse(data);
|
console.log('解析后的降雨数据:', data);
|
} catch (e) {
|
console.error("data 不是有效的 JSON 字符串");
|
return;
|
}
|
}
|
// 打印降雨强度的单位
|
console.log('降雨强度的单位是:', data.intensityUnit);
|
// 根据 intensityUnit 调整 rainfalls 中的 intensity 值
|
if (data.intensityUnit === 'mm/min') {
|
data.rainfalls.forEach(r => r.intensity *= 60);
|
console.log('将 mm/min 转换为 mm/h 后的 rainfalls:', data.rainfalls);
|
} else if (data.intensityUnit === 'mm/5min') {
|
data.rainfalls.forEach(r => r.intensity *= 12);
|
console.log('将 mm/5min 转换为 mm/h 后的 rainfalls:', data.rainfalls);
|
} else if (data.intensityUnit !== 'mm/h') {
|
console.warn('未知的 intensity 单位,无法进行转换');
|
}
|
|
const rainfallList = data.rainfalls;
|
console.log('最终的 rainfallList:', rainfallList);
|
|
// 提取 intensity 值
|
rainFallValues.value = rainfallList.map(r => r.intensity);
|
minRainValue.value = Math.min(...rainFallValues.value);
|
maxRainValue.value = Math.max(...rainFallValues.value);
|
console.log('当前方案下最小雨量和最大雨量:', minRainValue.value, maxRainValue.value);
|
}
|
// 定义降雨等级及其对应的视觉参数
|
const rainLevels = [
|
{
|
name: '小雨',
|
min: 0.1,
|
max: 9.9,
|
size: 0.5, // 雨滴大小:更小
|
speed: 20, // 下落速度:更慢
|
density: 15, // 雨滴密度:更稀疏
|
color: '#ADD8E6' // 浅蓝色,象征轻柔的小雨
|
},
|
{
|
name: '中雨',
|
min: 10,
|
max: 24.9,
|
size: 0.7,
|
speed: 40,
|
density: 35,
|
color: '#ADD8E6'
|
},
|
{
|
name: '大雨',
|
min: 25,
|
max: 49.9,
|
size: 1.0,
|
speed: 70,
|
density: 60,
|
color: '#ADD8E6'
|
},
|
{
|
name: '暴雨',
|
min: 50,
|
max: 99.9,
|
size: 1.3,
|
speed: 90,
|
density: 80,
|
color: '#ADD8E6'
|
},
|
{
|
name: '大暴雨',
|
min: 100,
|
size: 1.6,
|
speed: 110,
|
density: 100,
|
color: '#ADD8E6'
|
}
|
];
|
// 根据降雨量返回对应的雨形配置
|
function getRainLevel(rainValue) {
|
for (let level of rainLevels) {
|
if (level.min <= rainValue && (level.max === undefined || rainValue <= level.max)) {
|
return level;
|
}
|
}
|
// 默认无雨状态
|
return { name: '无雨', size: 0.5, speed: 30, density: 20, color: '#F0F8FF' };
|
}
|
// 根据播放进度更新天气效果(已优化)
|
let lastUsedIndex = -1; // 缓存上一次使用的索引,防止重复更新
|
let lastRainValue = null;
|
function updateWeatherByProgress() {
|
if (rainFallValues.value.length === 0) return;
|
console.log(`时间轴总时长: ${duration.value}, 当前时间: ${currentTime.value}`); // 打印时间轴信息
|
const progress = currentTime.value / duration.value;
|
const floatIndex = progress * (rainFallValues.value.length - 1);
|
const index = Math.floor(floatIndex); // 当前索引
|
const nextIndex = Math.min(index + 1, rainFallValues.value.length - 1); // 下一索引
|
const currentRain = rainFallValues.value[index];
|
const nextRain = rainFallValues.value[nextIndex];
|
// 启用插值(alpha 平滑过渡)
|
const alpha = floatIndex - index;
|
const rainValue = currentRain + (nextRain - currentRain) * alpha;
|
// 打印当前处理的雨量数据
|
// console.log(`正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed(2)}, 索引=${index}`);
|
// 如果当前索引未变化且插值差异不大,跳过重复更新
|
if (index === lastUsedIndex && Math.abs(rainValue - lastRainValue) < 0.1) {
|
console.log('由于数据无显著变化,跳过本次更新');
|
return;
|
}
|
|
lastUsedIndex = index;
|
lastRainValue = rainValue;
|
|
// 获取对应的雨形配置
|
const rainLevel = getRainLevel(rainValue);
|
|
if (rainLevel.name === '无雨') {
|
// 无雨状态:清除雨效
|
mapUtils.delRain();
|
console.log('执行了无雨状态,清除了雨效');
|
return;
|
}
|
|
// 非无雨状态:构建雨滴参数并更新雨效
|
const rainParams = {
|
rainSize: rainLevel.size,
|
rainSpeed: rainLevel.speed,
|
rainDensity: rainLevel.density,
|
rainColor: rainLevel.color
|
};
|
console.log('当前雨量数据:', rainValue, '当前雨形:', rainLevel);
|
// 调用工具方法更新雨效
|
mapUtils.toggleRain(rainParams, true);
|
}
|
const stopPlayback = () => {
|
clearInterval(playInterval);
|
};
|
|
const skipForward = () => {
|
if (waterTimestamps.value.length === 0) return;
|
const currentIndex = findClosestTimestampIndex(currentTime.value);
|
const nextIndex = currentIndex + 1;
|
if (nextIndex >= waterTimestamps.value.length) {
|
return;
|
}
|
const baseTimestamp = waterTimestamps.value[0];
|
currentTime.value = (waterTimestamps.value[nextIndex] - baseTimestamp) / 1000;
|
setTimeForWaterSimulation(nextIndex);
|
if (!isPlaying.value) pauseWaterSimulation();
|
};
|
|
const skipBackward = () => {
|
if (waterTimestamps.value.length === 0) return;
|
const currentIndex = findClosestTimestampIndex(currentTime.value);
|
const prevIndex = currentIndex - 1;
|
if (prevIndex < 0) {
|
return;
|
}
|
const baseTimestamp = waterTimestamps.value[0];
|
currentTime.value = (waterTimestamps.value[prevIndex] - baseTimestamp) / 1000;
|
setTimeForWaterSimulation(prevIndex);
|
if (!isPlaying.value) pauseWaterSimulation();
|
};
|
const toggleSpeedMenu = () => (showSpeedMenu.value = !showSpeedMenu.value);
|
|
// 设置播放速率
|
const setPlaybackRate = (rate) => {
|
isColorRenderEnabled.value = false
|
playbackRate.value = rate;
|
showSpeedMenu.value = false;
|
// 停止当前播放
|
stopPlayback();
|
setTimeout(() => {
|
mapUtils.delRain();
|
}, 3000);
|
// 重置时间轴到初始状态
|
currentTime.value = 0; // 时间归零
|
emit("timeUpdate", progressPercentage.value);
|
isPlaying.value = false;
|
emit("isPlaying", false);
|
|
// 销毁现有的水体模拟层
|
if (isWaterPrimitiveCreated.value) {
|
destoryWaterPrimitive();
|
isWaterPrimitiveCreated.value = false; // 重置标志变量
|
}
|
isPlaying.value = false;
|
emit("isPlaying", false);
|
|
pauseWaterSimulation(); // 调用暂停接口
|
EventBus.emit("clear-echart");
|
EventBus.emit("reset-table");
|
};
|
// 时间轴跳转
|
const seekToPosition = (event) => {
|
if (!isWaterPrimitiveCreated.value) {
|
ElMessage.warning("请先启动水体模拟后再进行时间轴跳转。");
|
return;
|
}
|
|
const rect = timelineTrack.value.getBoundingClientRect();
|
const percentage = (event.clientX - rect.left) / rect.width;
|
const targetTime = Math.round(percentage * duration.value);
|
|
// 直接找到最近的 timestamp 索引
|
const closestIndex = findClosestTimestampIndex(targetTime);
|
const baseTimestamp = waterTimestamps.value[0];
|
currentTime.value = (waterTimestamps.value[closestIndex] - baseTimestamp) / 1000;
|
|
// 更新水体模拟时间
|
setTimeForWaterSimulation(closestIndex);
|
if (!isPlaying.value) pauseWaterSimulation();
|
};
|
// 辅助函数:找到最接近的时间戳索引
|
function findClosestTimestampIndex(currentTimeValue) {
|
if (waterTimestamps.value.length === 0) return 0;
|
|
// 计算当前时间对应的毫秒时间戳
|
const baseTime = waterTimestamps.value[0];
|
const currentTimestamp = baseTime + currentTimeValue * 1000;
|
|
// 找到最接近的 timestamp 索引
|
let closestIndex = 0;
|
let minDiff = Infinity;
|
|
waterTimestamps.value.forEach((timestamp, index) => {
|
const diff = Math.abs(timestamp - currentTimestamp);
|
if (diff < minDiff) {
|
minDiff = diff;
|
closestIndex = index;
|
}
|
});
|
|
return closestIndex;
|
}
|
watch(
|
() => selectedScheme.value,
|
(newVal) => {
|
if (newVal) {
|
console.log('选中方案已改变:', newVal)
|
}
|
}
|
);
|
watch(
|
() => currentTime.value,
|
() => {
|
if (waterTimestamps.value.length > 0) {
|
sendCurrentPlayingTime.value = dayjs(waterTimestamps.value[0])
|
.add(currentTime.value, "second")
|
.valueOf(); // 使用 valueOf() 获取原始时间戳
|
|
// 更新 currentPlayingTime 格式化后的时间字符串
|
currentPlayingTime.value = dayjs(sendCurrentPlayingTime.value).format("YYYY-MM-DD HH:mm:ss");
|
EventBus.emit("time-update", currentPlayingTime.value);
|
}
|
}
|
);
|
|
// 时间标记生成
|
function generateTimeMarkers(timestamps) {
|
if (!timestamps || timestamps.length === 0) return [];
|
const sorted = [...timestamps].sort((a, b) => dayjs(a).diff(dayjs(b)));
|
const interval = Math.floor(
|
dayjs(sorted.at(-1)).diff(dayjs(sorted[0]), "second") / 4
|
);
|
return Array.from({ length: 5 }, (_, i) =>
|
dayjs(sorted[0])
|
.add(i * interval, "second")
|
.format("MM-DD HH:mm:ss")
|
);
|
}
|
|
watch(
|
() => waterTimestamps.value,
|
(newTimestamps) => {
|
if (newTimestamps.length > 0)
|
timeMarkers.value = generateTimeMarkers(newTimestamps);
|
},
|
{ immediate: true }
|
);
|
|
onMounted(async () => {
|
try {
|
// 当前方案的所有信息
|
const schemeInfo = selectedScheme.value;
|
serviceInfo = schemeInfo.serviceName;
|
// console.log('获取到的 serviceName:', serviceInfo);
|
getRainfallData()
|
// 根据layer.json去获取时间轴信息
|
const { waterTimestamps: timestamps } = await fetchWaterSimulationData(serviceInfo);
|
// 现在是按照总共有多少个点来渲染时间轴
|
if (timestamps) {
|
waterTimestamps.value = timestamps;
|
updateTimelineRange();
|
timeMarkers.value = generateTimeMarkers(timestamps);
|
sendCurrentPlayingTime.value = timestamps[0]
|
currentPlayingTime.value = dayjs(timestamps[0]).format(
|
"YYYY-MM-DD HH:mm:ss"
|
);
|
}
|
} catch (error) {
|
console.error("Error loading water simulation data:", error);
|
ElMessage({
|
message: "降雨数据出错,请重新新建模拟方案!",
|
type: "warning",
|
});
|
}
|
});
|
// 根据返回数据的个数去渲染时间轴
|
function updateTimelineRange() {
|
if (waterTimestamps.value.length > 0) {
|
const [first, last] = [
|
waterTimestamps.value[0],
|
waterTimestamps.value.at(-1),
|
];
|
duration.value = (last - first) / 1000; // 毫秒转秒
|
}
|
}
|
|
onBeforeUnmount(() => {
|
stopPlayback();
|
destoryWaterPrimitive();
|
});
|
|
const { endSimulate } = inject("simulateActions");
|
function handleBack() {
|
endSimulate();
|
isWaterPrimitiveCreated.value = false;
|
if (ratelevelRef.value) {
|
ratelevelRef.value.endCalculation();
|
}
|
emit("isColorRender", false);
|
setTimeout(() => {
|
mapUtils.delRain();
|
}, 3000);
|
destoryWaterPrimitive();
|
EventBus.emit("hide-schemeInfo");
|
ElMessage({ message: "模拟进程正在关闭中...", type: "success" });
|
}
|
</script>
|
<style scoped>
|
.timeline-container {
|
display: flex;
|
/* align-items: center; */
|
justify-content: space-between;
|
position: absolute;
|
bottom: 10%;
|
left: 50%;
|
transform: translateX(-50%);
|
z-index: 99;
|
width: 38%;
|
height: 10%;
|
/* background-color: #1a2634; */
|
background: url("@/assets/img/menubar/bar.png");
|
background-size: 100% 100%;
|
color: white;
|
/* border-radius: 8px; */
|
font-family: Arial, sans-serif;
|
padding: 0 25px;
|
}
|
|
.controls {
|
display: flex;
|
/* align-items: center; */
|
margin: 25px 25px 10px 0;
|
}
|
|
.control-btn {
|
background: none;
|
border: none;
|
color: white;
|
font-size: 16px;
|
cursor: pointer;
|
margin-right: 10px;
|
width: 30px;
|
height: 30px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 50%;
|
|
img {
|
width: 100%;
|
height: 100%;
|
}
|
}
|
|
.control-btn:hover {
|
background-color: rgba(255, 255, 255, 0.1);
|
}
|
|
.play-btn {
|
background-color: #4a90e2;
|
width: 36px;
|
height: 36px;
|
}
|
|
.speed-control {
|
position: relative;
|
cursor: pointer;
|
/* padding: 5px 10px; */
|
border-radius: 4px;
|
background-color: rgba(255, 255, 255, 0.1);
|
width: 36px;
|
height: 36px;
|
line-height: 36px;
|
text-align: center;
|
}
|
|
.speed-menu {
|
position: absolute;
|
top: 100%;
|
left: 0;
|
background-color: #2a3a4a;
|
border-radius: 4px;
|
z-index: 10;
|
width: 60px;
|
}
|
|
.speed-menu div {
|
/* padding: 5px 5px; */
|
text-align: center;
|
}
|
|
.speed-menu div:hover,
|
.speed-menu div.active {
|
/* background-color: #4a90e2; */
|
background-color: rgba(127, 255, 212, 0.5);
|
}
|
|
.timeline {
|
margin-top: 10px;
|
position: relative;
|
flex: 1;
|
}
|
|
.dates {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 5px;
|
}
|
|
.date-label {
|
font-size: 14px;
|
color: #fff;
|
}
|
|
.timeline-track {
|
height: 8px;
|
background-color: rgba(255, 255, 255, 0.1);
|
border-radius: 4px;
|
position: relative;
|
cursor: pointer;
|
}
|
|
.timeline-progress {
|
height: 100%;
|
background-color: #4a90e2;
|
border-radius: 4px;
|
position: absolute;
|
top: 0;
|
left: 0;
|
}
|
|
.timeline-cursor {
|
width: 12px;
|
height: 12px;
|
background-color: white;
|
border-radius: 50%;
|
position: absolute;
|
top: 50%;
|
transform: translate(-50%, -50%);
|
z-index: 2;
|
}
|
|
.time-markers {
|
position: absolute;
|
width: 100%;
|
top: 20px;
|
display: flex;
|
justify-content: space-between;
|
}
|
|
.time-marker {
|
position: absolute;
|
font-size: 12px;
|
color: #fff;
|
text-align: center;
|
width: 25%;
|
white-space: nowrap;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
|
/* .date-part {
|
margin-bottom: 2px;
|
} */
|
.time-part {
|
font-size: 11px;
|
opacity: 0.8;
|
}
|
|
.current-date {
|
margin-bottom: 5px;
|
font-size: 15px;
|
color: #fff;
|
transform: translateX(-8%);
|
}
|
|
.scale-markers {
|
position: absolute;
|
width: 100%;
|
height: 10px;
|
top: 10px;
|
}
|
|
.scale-marker {
|
position: absolute;
|
width: 3px;
|
height: 6px;
|
background-color: rgba(255, 255, 255, 1);
|
transform: translateX(-50%);
|
}
|
</style>
|