¶Ô±ÈÐÂÎļþ |
| | |
| | | <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" 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 }" |
| | | > |
| | | {{ 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> |
| | | <div style="display: flex"> |
| | | <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=" |
| | | margin-top: 12px; |
| | | margin-left: 16px; |
| | | margin-right: 20px; |
| | | justify-content: flex-end; |
| | | " |
| | | /> |
| | | </div> |
| | | <el-button |
| | | @click="handleBack" |
| | | style=" |
| | | margin-top: 3px; |
| | | margin-left: 28px; |
| | | margin-right: 10px; |
| | | width: 75%; |
| | | height: 30%; |
| | | " |
| | | >ç»ææ¨¡æ</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 crossanalysis from "@/components/menu/CrossSectionalAnalysis.vue"; |
| | | |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | createWaterPrimitive, |
| | | destoryWaterPrimitive, |
| | | pauseWaterSimulation, |
| | | resumeWaterSimulation, |
| | | setTimeForWaterSimulation, |
| | | toggleWaterColorRender, |
| | | updateWaterColor, |
| | | } 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, frameNum } = 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 crossRef = 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 speedShow = ref(false); |
| | | |
| | | const waterTimestamps = ref([]); // å卿¶é´è½´æ°æ® |
| | | const timeMarkers = ref([]); |
| | | const timelineTrack = ref(null); |
| | | const isColorRenderEnabled = ref(false); // åè®¾è¿æ¯ä½ çé¢è²æ¸²æå¼å
³ç¶æ |
| | | const isWaterPrimitiveCreated = ref(false); |
| | | let playInterval = null; |
| | | <<<<<<< HEAD |
| | | let rainTotalInfo = []; |
| | | ======= |
| | | let timeStepInfo = null; |
| | | let rainTotalInfo = ([]); |
| | | >>>>>>> 68e0faf021b54ab38bba0b07c76c3d5f43dfc311 |
| | | const isRainEnabled = ref(false); |
| | | const rainParams = reactive({ |
| | | rainSize: 0.5, |
| | | rainSpeed: 50, |
| | | rainColor: "#99B3CC", |
| | | rainDensity: 30, // é¨çå¯åº¦ |
| | | }); |
| | | let minFlowRate = ref(); |
| | | let maxFlowRate = ref(); |
| | | // 计ç®å±æ§ |
| | | 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, |
| | | minFlowRate, |
| | | maxFlowRate, |
| | | }); |
| | | 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; |
| | | |
| | | // è§¦åæ´æ° |
| | | if (selectedScheme.value.type !== 2) { |
| | | updateWaterColorByTime(); |
| | | 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); |
| | | <<<<<<< HEAD |
| | | rainTotalInfo.value = rainfallList; |
| | | ======= |
| | | rainTotalInfo.value = rainfallList |
| | | calculateTimeStep(rainTotalInfo.value) |
| | | // 使ç¨ç¤ºä¾ |
| | | timeStepInfo = calculateTimeStep(rainTotalInfo.value); |
| | | >>>>>>> 68e0faf021b54ab38bba0b07c76c3d5f43dfc311 |
| | | |
| | | // æå 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.6, |
| | | speed: 24, |
| | | density: 18, |
| | | color: "#ADD8E6", |
| | | }, |
| | | { |
| | | name: "大é¨", |
| | | min: 25, |
| | | max: 49.9, |
| | | size: 0.7, |
| | | speed: 28, |
| | | density: 21, |
| | | color: "#ADD8E6", |
| | | }, |
| | | { |
| | | name: "æ´é¨", |
| | | min: 50, |
| | | max: 99.9, |
| | | size: 0.8, |
| | | speed: 32, |
| | | density: 24, |
| | | color: "#ADD8E6", |
| | | }, |
| | | { |
| | | name: "大æ´é¨", |
| | | min: 100, |
| | | size: 0.9, |
| | | speed: 36, |
| | | density: 27, |
| | | 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.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; |
| | | 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]; |
| | | // å¯ç¨æå¼ï¼alpha å¹³æ»è¿æ¸¡ï¼ |
| | | const alpha = floatIndex - index; |
| | | const currentTotal = currentData.total; |
| | | const nextTotal = nextData.total; |
| | | const total = currentTotal + (nextTotal - currentTotal) * alpha; |
| | | console.log(`计ç®å¾å°çæ¶é´æ¥é¿ä¸º: ${timeStepInfo} å°æ¶`); |
| | | // æ ¹æ® total 设置é¢è² |
| | | let color = "#D4F2E7"; // é»è®¤èè² |
| | | |
| | | if (total >= 150) { |
| | | <<<<<<< HEAD |
| | | 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"; // æµ
è - å¾®é |
| | | ======= |
| | | 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'; |
| | | >>>>>>> 68e0faf021b54ab38bba0b07c76c3d5f43dfc311 |
| | | } |
| | | // console.log(`å½å total: ${total.toFixed(2)}, é¢è²: ${color}`); |
| | | // updateWaterColor(color) |
| | | } |
| | | |
| | | 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; |
| | | const rainValue = currentRain + (nextRain - currentRain); |
| | | // æå°å½åå¤ççé¨éæ°æ® |
| | | 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; |
| | | if (selectedScheme.value.type == 2) { |
| | | speedShow.value = false; |
| | | } else { |
| | | getRainfallData(); |
| | | speedShow.value = true; |
| | | } |
| | | |
| | | // console.log('è·åå°ç serviceName:', serviceInfo); |
| | | |
| | | // æ ¹æ®layer.jsonå»è·åæ¶é´è½´ä¿¡æ¯ |
| | | const { |
| | | waterTimestamps: timestamps, |
| | | watersMaxHeight, |
| | | watersMinHeight, |
| | | } = await fetchWaterSimulationData(serviceInfo); |
| | | console.log( |
| | | "å½åæ¹æ¡ä¸çæå¤§æ°´ä½æ·±åº¦åæå°æ°´ä½æ·±åº¦", |
| | | watersMaxHeight, |
| | | watersMinHeight |
| | | ); |
| | | |
| | | // ç°å¨æ¯æç
§æ»å
±æå¤å°ä¸ªç¹æ¥æ¸²ææ¶é´è½´ |
| | | if (timestamps) { |
| | | frameNum.value = timestamps.length; |
| | | waterTimestamps.value = timestamps; |
| | | updateTimelineRange(); |
| | | timeMarkers.value = generateTimeMarkers(timestamps); |
| | | sendCurrentPlayingTime.value = timestamps[0]; |
| | | currentPlayingTime.value = dayjs(timestamps[0]).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | } |
| | | minFlowRate = watersMinHeight; |
| | | maxFlowRate = watersMaxHeight; |
| | | } 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(); |
| | | ratelevelRef.value.stopPicking(); |
| | | } |
| | | if (crossRef.value) { |
| | | crossRef.value.clearPoints(); |
| | | console.log("æ§è¡å é¤ç¹åè½"); |
| | | } |
| | | emit("isColorRender", false); |
| | | setTimeout(() => { |
| | | mapUtils.delRain(); |
| | | }, 3000); |
| | | destoryWaterPrimitive(); |
| | | EventBus.emit("hide-schemeInfo"); |
| | | EventBus.emit("clear-water-depth"); |
| | | EventBus.emit("clear-water-velocity"); |
| | | 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: 44%; |
| | | 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: 0.9; |
| | | } |
| | | |
| | | .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> |