| | |
| | | <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" /> |
| | | <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" /> |
| | | <img |
| | | src="@/assets/img/timeline/right.png" |
| | | class="fas fa-step-forward" |
| | | /> |
| | | </div> |
| | | <div class="speed-control"> |
| | | <div class="speed-control" v-show="speedShow"> |
| | | <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 }"> |
| | | <div |
| | | v-for="rate in playbackRates" |
| | | :key="rate" |
| | | @click.capture="setPlaybackRate(rate)" |
| | | :class="{ active: playbackRate === rate }" |
| | | > |
| | | {{ rate }}X |
| | | </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"> |
| | | <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" /> |
| | | <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="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: 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 |
| | | 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 style="display: flex"> |
| | | <ratelevel ref="ratelevelRef" :playing-time="sendCurrentPlayingTime" |
| | | @finish-calculation="handleFinishCalculation" style=" |
| | | <ratelevel |
| | | ref="ratelevelRef" |
| | | :playing-time="sendCurrentPlayingTime" |
| | | @finish-calculation="handleFinishCalculation" |
| | | style=" |
| | | margin-top: 12px; |
| | | margin-left: 28px; |
| | | margin-right: 10px; |
| | | justify-content: flex-end; |
| | | " /> |
| | | <crossanalysis ref="crossRef" style=" |
| | | " |
| | | /> |
| | | <crossanalysis |
| | | ref="crossRef" |
| | | style=" |
| | | margin-top: 12px; |
| | | margin-left: 16px; |
| | | margin-right: 20px; |
| | | justify-content: flex-end; |
| | | " /> |
| | | " |
| | | /> |
| | | </div> |
| | | <el-button @click="handleBack" style=" |
| | | <el-button |
| | | @click="handleBack" |
| | | style=" |
| | | margin-top: 3px; |
| | | margin-left: 28px; |
| | | margin-right: 10px; |
| | | width: 75%; |
| | | height: 30%; |
| | | ">结束模拟</el-button> |
| | | " |
| | | >结束模拟</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | import { useSimStore } from "@/store/simulation"; |
| | | import { storeToRefs } from "pinia"; |
| | | const simStore = useSimStore(); |
| | | const { selectedScheme, frameNum } = storeToRefs(simStore); |
| | | const { selectedScheme, frameNum, layerDate } = storeToRefs(simStore); |
| | | |
| | | const emit = defineEmits([ |
| | | "timeUpdate", |
| | |
| | | const playbackRate = ref(1); |
| | | const playbackRates = ref([1, 2, 4, 8]); |
| | | const showSpeedMenu = ref(false); |
| | | const speedShow = ref(false); |
| | | |
| | | const waterTimestamps = ref([]); // 存储时间轴数据 |
| | | const timeMarkers = ref([]); |
| | | const timelineTrack = ref(null); |
| | | const isColorRenderEnabled = ref(false); // 假设这是你的颜色渲染开关状态 |
| | | const isWaterPrimitiveCreated = ref(false); |
| | | let playInterval = null; |
| | | let rainTotalInfo = ([]); |
| | | let timeStepInfo = null; |
| | | let rainTotalInfo = []; |
| | | const isRainEnabled = ref(false); |
| | | const rainParams = reactive({ |
| | | rainSize: 0.5, |
| | |
| | | }); |
| | | let minFlowRate = ref(); |
| | | let maxFlowRate = ref(); |
| | | let maxStage = null; |
| | | // 计算属性 |
| | | const progressPercentage = computed( |
| | | () => (currentTime.value / duration.value) * 100 |
| | |
| | | currentTime.value = (nextTimestamp - baseTimestamp) / 1000; |
| | | |
| | | // 触发更新 |
| | | updateWaterColorByTime() |
| | | updateWeatherByProgress(); |
| | | if (selectedScheme.value.type !== 2) { |
| | | updateWaterColorByTime(); |
| | | updateWeatherByProgress(); |
| | | } |
| | | |
| | | const progress = currentTime.value / duration.value; |
| | | emit("timeUpdate", progress * 100); |
| | | }, 1000 / playbackRate.value); // 根据播放速率调整间隔 |
| | |
| | | |
| | | const rainfallList = data.rainfalls; |
| | | console.log("最终的 rainfallList:", rainfallList); |
| | | rainTotalInfo.value = rainfallList |
| | | rainTotalInfo.value = rainfallList; |
| | | calculateTimeStep(rainTotalInfo.value); |
| | | // 使用示例 |
| | | timeStepInfo = calculateTimeStep(rainTotalInfo.value); |
| | | |
| | | // 提取 intensity 值 |
| | | rainFallValues.value = rainfallList.map((r) => r.intensity); |
| | |
| | | } |
| | | } |
| | | // 默认无雨状态 |
| | | |
| | | |
| | | return { name: "无雨", size: 0.3, speed: 10, density: 10, color: "#F0F8FF" }; |
| | | } |
| | | // 根据播放进度更新天气效果(已优化) |
| | | let lastUsedIndex = -1; // 缓存上一次使用的索引,防止重复更新 |
| | | let lastRainValue = null; |
| | | |
| | | function calculateTimeStep(dataArray) { |
| | | if (!dataArray || dataArray.length < 2) { |
| | | console.warn("数据不足,无法计算时间步长"); |
| | | return null; |
| | | } |
| | | |
| | | // 解析时间字符串为 Date 对象 |
| | | function parseTime(timeStr) { |
| | | return new Date(timeStr.replace(" ", "T")); // 兼容 ISO 格式 |
| | | } |
| | | |
| | | const firstTime = parseTime(dataArray[0].time); |
| | | const secondTime = parseTime(dataArray[1].time); |
| | | |
| | | // 计算时间差(毫秒) |
| | | const diffMs = Math.abs(secondTime - firstTime); |
| | | |
| | | // 转换为小时数(保留小数) |
| | | let timeStepHours = diffMs / (1000 * 60 * 60); // 毫秒 -> 小时 |
| | | |
| | | // 可选:遍历所有相邻项检查是否一致 |
| | | for (let i = 1; i < dataArray.length - 1; i++) { |
| | | const current = parseTime(dataArray[i].time); |
| | | const next = parseTime(dataArray[i + 1].time); |
| | | const step = Math.abs(next - current) / (1000 * 60 * 60); // 毫秒 -> 小时 |
| | | if (Math.abs(step - timeStepHours) > 0.01) { |
| | | console.warn( |
| | | `在索引 ${i} 处发现了不同的时间步长: ${step.toFixed(2)} 小时` |
| | | ); |
| | | } |
| | | } |
| | | |
| | | return timeStepHours; |
| | | } |
| | | |
| | | function updateWaterColorByTime() { |
| | | if (!rainTotalInfo.value || rainTotalInfo.value.length === 0) return; |
| | | |
| | | // 辅助函数:将 "YYYY-MM-DD HH:mm:ss" 转换为 JavaScript Date 对象 |
| | | const timeToTimestamp = (timeStr) => new Date(timeStr).getTime(); |
| | | |
| | | // 获取初始时间戳(第一个数据点的时间) |
| | | const initialTimestamp = timeToTimestamp(rainTotalInfo.value[0].time); |
| | | |
| | | // 计算当前进度 |
| | | const progress = currentTime.value / duration.value; |
| | | const floatIndex = progress * (rainTotalInfo.value.length - 1); |
| | | const index = Math.floor(floatIndex); |
| | | const nextIndex = Math.min(index + 1, rainTotalInfo.value.length - 1); |
| | | const currentData = rainTotalInfo.value[index]; |
| | | const nextData = rainTotalInfo.value[nextIndex]; |
| | | const currentTimestamp = timeToTimestamp(currentData.time); |
| | | |
| | | // 已过去的时间(小时) |
| | | const elapsedTimeInHours = parseFloat( |
| | | (currentTimestamp - initialTimestamp) / (1000 * 60 * 60) |
| | | ); |
| | | |
| | | console.log(`持续了 ${elapsedTimeInHours} 小时`); |
| | | |
| | | // 启用插值(alpha 平滑过渡) |
| | | const alpha = floatIndex - index; |
| | | const currentTotal = currentData.total; |
| | | const nextTotal = nextData.total; |
| | | const total = currentTotal + (nextTotal - currentTotal) * alpha; |
| | | // 根据 total 设置颜色 |
| | | let color = '#D4F2E7'; // 默认蓝色 |
| | | const currentIntensity = currentData.intensity; |
| | | const nextIntensity = nextData.intensity; |
| | | const intensity = currentIntensity + (nextIntensity - currentIntensity); |
| | | // 计算 IR(t) |
| | | const D = elapsedTimeInHours + 0.0001; // 加一个极小量防止除零 |
| | | const IR = 56.9 * Math.pow(D, -0.746); // 单位 mm/h |
| | | |
| | | if (total >= 150) { |
| | | color = '#663300'; // 黄 - 大雨 |
| | | } else if (total >= 125) { |
| | | color = '#B26633'; // 黄绿 - 中雨 |
| | | } else if (total >= 100) { |
| | | color = '#CC9966'; // 绿 - 中雨 |
| | | } else if (total >= 75) { |
| | | color = '#CCE5FF'; // 青绿 - 小雨 |
| | | } else if (total >= 50) { |
| | | color = '#99CCFF'; // 天蓝 - 小雨 |
| | | } else if (total >= 25) { |
| | | color = '#66B3FF'; // 浅蓝 - 微量 |
| | | // 判断当前阶段 |
| | | let stage = 0; |
| | | if (intensity >= 1.0 * IR) { |
| | | stage = 6; |
| | | } else if (intensity >= 0.8 * IR) { |
| | | stage = 5; |
| | | } else if (intensity >= 0.6 * IR) { |
| | | stage = 4; |
| | | } else if (intensity >= 0.4 * IR) { |
| | | stage = 3; |
| | | } else if (intensity >= 0.2 * IR) { |
| | | stage = 2; |
| | | } else if (intensity > 0) { |
| | | stage = 1; |
| | | } |
| | | // console.log(`当前 total: ${total.toFixed(2)}, 颜色: ${color}`); |
| | | // updateWaterColor(color) |
| | | |
| | | // 更新全局最大阶段(不会回退) |
| | | if (!maxStage) maxStage = 0; |
| | | maxStage = Math.max(maxStage, stage); |
| | | |
| | | // 输出关键信息 |
| | | console.table({ |
| | | 当前时间: currentData.time, |
| | | "累计时长 D(t) (h)": D.toFixed(2), |
| | | "雨强阈值 IR(t) (mm/h)": IR.toFixed(2), |
| | | "当前降雨强度 I(t) (mm/h)": intensity.toFixed(2), |
| | | 当前阶段编号: stage, |
| | | 最大阶段编号: maxStage, |
| | | 是否触发泥石流: stage >= 5 ? "是" : "否", |
| | | }); |
| | | |
| | | // 根据最大阶段设置颜色 |
| | | let color = "#D4F2E7"; |
| | | switch (maxStage) { |
| | | case 0: |
| | | color = "#D4F2E7"; |
| | | break; |
| | | case 1: |
| | | color = "#66B3FF"; |
| | | break; |
| | | case 2: |
| | | color = "#99CCFF"; |
| | | break; |
| | | case 3: |
| | | color = "#CCE5FF"; |
| | | break; |
| | | case 4: |
| | | color = "#CC9966"; |
| | | break; |
| | | case 5: |
| | | color = "#B26633"; |
| | | break; |
| | | case 6: |
| | | color = "#663300"; |
| | | break; |
| | | } |
| | | |
| | | updateWaterColor(color); |
| | | } |
| | | |
| | | function updateWeatherByProgress() { |
| | |
| | | // const rainValue = currentRain + (nextRain - currentRain) * alpha; |
| | | const rainValue = currentRain + (nextRain - currentRain); |
| | | // 打印当前处理的雨量数据 |
| | | console.log(`正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed(2)}, 索引=${index}`); |
| | | // console.log( |
| | | // `正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed( |
| | | // 2 |
| | | // )}, 索引=${index}` |
| | | // ); |
| | | // 如果当前索引未变化且插值差异不大,跳过重复更新 |
| | | if (index === lastUsedIndex && Math.abs(rainValue - lastRainValue) < 0.1) { |
| | | // console.log('由于数据无显著变化,跳过本次更新'); |
| | |
| | | try { |
| | | // 当前方案的所有信息 |
| | | const schemeInfo = selectedScheme.value; |
| | | const jsonFetch = ref(null); |
| | | serviceInfo = schemeInfo.serviceName; |
| | | if (selectedScheme.value.type == 2) { |
| | | speedShow.value = false; |
| | | jsonFetch.value = layerDate.value; |
| | | // serviceInfo = layerDate.value; |
| | | } else { |
| | | getRainfallData(); |
| | | speedShow.value = true; |
| | | jsonFetch.value = null; |
| | | } |
| | | |
| | | // console.log('获取到的 serviceName:', serviceInfo); |
| | | getRainfallData(); |
| | | |
| | | // 根据layer.json去获取时间轴信息 |
| | | const { |
| | | waterTimestamps: timestamps, |
| | | watersMaxHeight, |
| | | watersMinHeight, |
| | | } = await fetchWaterSimulationData(serviceInfo); |
| | | } = await fetchWaterSimulationData(serviceInfo, jsonFetch.value); |
| | | console.log( |
| | | "当前方案下的最大水位深度和最小水位深度", |
| | | watersMaxHeight, |