From f37845dd0a787dd42bf6c72e923433f30fcd8cc3 Mon Sep 17 00:00:00 2001 From: guonan <guonan201020@163.com> Date: 星期四, 03 七月 2025 15:40:09 +0800 Subject: [PATCH] 实时模拟 --- src/views/left/CitySim.vue | 364 ++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 310 insertions(+), 54 deletions(-) diff --git a/src/views/left/CitySim.vue b/src/views/left/CitySim.vue index cdc0e91..f74e646 100644 --- a/src/views/left/CitySim.vue +++ b/src/views/left/CitySim.vue @@ -10,7 +10,11 @@ <div class="left-top" v-if="simStore.selectTab == '閲嶇偣鍖哄煙浠跨湡'"> 閲嶇偣鍖哄煙浠跨湡锛�10m绮惧害锛� </div> - <div class="forms"> + <div class="left-top" v-if="simStore.selectTab == '閲嶇偣娌熶豢鐪�'"> + 鍘嗗彶妯℃嫙 + </div> + + <div class="forms" :class="{ 'no-background': !showBackground }"> <el-form :rules="rules" :model="forms" @@ -27,6 +31,7 @@ </el-form-item> <el-form-item label="涓婁紶鍙傛暟"> <el-upload + :on-remove="handleRemove" v-model:file-list="forms.fileList" class="upload-demo" :auto-upload="false" @@ -44,8 +49,9 @@ <el-form-item label="闆ㄥ己鍗曚綅" v-if="forms.fileList.length !== 0"> <el-select v-model="forms.intensityUnit" - placeholder="Select" + placeholder="璇烽�夋嫨闆ㄥ己鍗曚綅" style="max-width: 600px" + :disabled="!!forms.intensityUnit" > <el-option v-for="item in intensityOptions" @@ -106,7 +112,7 @@ v-if="forms.fileList.length !== 0" v-model="forms.hours" type="datetime" - placeholder="Select date and time" + placeholder="璇烽�夋嫨寮�濮嬫椂闂�" /> <el-date-picker v-if="forms.fileList.length == 0" @@ -145,7 +151,7 @@ </el-form> <div style="display: flex; justify-content: flex-end"> <el-button type="primary" @click="addSimCheme">淇濆瓨鏂规</el-button> - <el-button type="success" @click="startPlay">寮�濮嬫ā鎷�</el-button> + <el-button type="success" @click="startPlay">淇濆瓨骞跺紑濮嬫ā鎷�</el-button> </div> </div> </div> @@ -158,7 +164,8 @@ import { ElMessage, ElMessageBox } from "element-plus"; import { initeWaterPrimitiveView } from "@/utils/water"; import { SimAPIStore } from "@/store/simAPI"; -import { getRegionData } from "@/api/trApi"; +import { getRegionData, getSimStart, getSimDataById } from "@/api/trApi"; + import { storeToRefs } from "pinia"; import dayjs from "dayjs"; import { EventBus } from "@/eventBus"; // 寮曞叆浜嬩欢鎬荤嚎 @@ -168,9 +175,18 @@ const options = reactive([]); +// 鍘嗗彶妯℃嫙閫変腑鍖哄煙 +const props = defineProps({ + selectedArea: { + type: Object, + required: true, + }, +}); + const intensityOptions = ref([ { value: "mm/h", label: "mm/h" }, { value: "mm/5min", label: "mm/5min" }, + { value: "mm/min", label: "mm/min" }, ]); // 瀹氫箟涓�涓柟娉曪紝鐢ㄤ簬鏍规嵁 type 鑾峰彇鍖哄煙鏁版嵁 @@ -192,6 +208,8 @@ fetchRegionData(1); }); +const showBackground = ref(true); // 榛樿鏄剧ず鑳屾櫙鍥� + // 鐩戝惉 selectTab 鐨勫彉鍖� watch(selectTab, (newVal) => { let type; @@ -202,10 +220,21 @@ case "閲嶇偣鍖哄煙浠跨湡": type = 2; break; + case "閲嶇偣娌熶豢鐪�": + type = 3; + break; default: type = 1; // 榛樿鍊� } + // 鏍规嵁 type 璁剧疆鏄惁鏄剧ず鑳屾櫙鍥撅紙鍥犱负鍘嗗彶妯℃嫙涓〃鍗曞甫浜嗚儗鏅浘锛� + if (type == 3) { + showBackground.value = false; + } else { + showBackground.value = true; + } fetchRegionData(type); + // Tab鍒囨崲鐨勬椂鍊欐竻绌鸿〃鍗� + resetForm(); }); // 娉ㄥ叆鐖剁粍浠舵彁渚涚殑鏂规硶 @@ -244,6 +273,9 @@ const addSimCheme = async () => { try { + if (selectTab.value == "閲嶇偣娌熶豢鐪�") { + forms.geom = props.selectedArea; + } await simStore.addSimCheme(forms); resetForm(); // 鍙湁鍦ㄤ繚瀛樻垚鍔熷悗鎵嶉噸缃〃鍗� EventBus.emit("close-selectArea"); @@ -298,7 +330,6 @@ // 瑙f瀽Excel鏂囦欢 const parseExcel = (data) => { const workbook = XLSX.read(data, { type: "array" }); - console.log(workbook, "wokr"); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet, { @@ -307,14 +338,6 @@ }); processData(jsonData); -}; - -const transformKeys = (data) => { - return data.map((item) => ({ - time: item["鏃堕棿"], // "鏃堕棿" 鈫� "time" - intensity: parseFloat(item["灏忔椂闆ㄥ己mm/h"]), // 杞负娴偣鏁� - total: parseFloat(item["绱闆ㄩ噺"]), // 杞负娴偣鏁� - })); }; /** @@ -349,62 +372,249 @@ if (!header) return ""; // 鐩存帴鍖归厤 "mm/h"銆�"m/s" 绛夊父瑙佸崟浣� - const unitRegex = /(mm\/h|m\/s|mm|鈩億%|hPa|km\/h)/; // 鏍规嵁闇�瑕佹墿灞� + const unitRegex = /(mm\/h|mm\/1min|mm\/5min|mm\/15min)/; // 鏍规嵁闇�瑕佹墿灞� const match = header.match(unitRegex); return match ? match[0] : ""; }; -// 澶勭悊鏁版嵁 +const transformKeys = (data) => { + return data.map((item) => ({ + time: item["鏃堕棿"], // "鏃堕棿" 鈫� "time" + intensity: parseFloat(item["灏忔椂闆ㄥ己"]), // 杞负娴偣鏁� + total: parseFloat(item["绱闆ㄩ噺"]), // 杞负娴偣鏁� + })); +}; + +// 鍙厤缃殑瀛楁鍚嶅尮閰嶈鍒� +const COLUMN_MATCH_RULES = { + time: ["鏃堕棿", "time", "datetime", "date"], + intensity: [ + "闆ㄥ己", + "灏忔椂闆ㄥ己", + "rain_intensity", + "rain_rate", + "hour_rain", + "闄嶉洦寮哄害", + ], + totalRainfall: [ + "绱闆ㄩ噺", + "鎬婚洦閲�", + "total_rain", + "cumulative_rainfall", + "闄嶉洦鎬婚噺", + ], +}; + +/** + * 鑷姩鍖归厤瀛楁鍚� + * @param {Object} headers - 琛ㄥご瀵硅薄锛堢涓�琛岋級 + * @returns {{time: string, intensity: string, totalRainfall: string}} + */ +function matchColumns(headers) { + const matched = { + time: null, + intensity: null, + totalRainfall: null, + }; + + for (const header of Object.keys(headers)) { + const cleanHeader = header.trim(); + + if ( + !matched.time && + COLUMN_MATCH_RULES.time.some((k) => cleanHeader.includes(k)) + ) { + matched.time = header; + } + + if ( + !matched.intensity && + COLUMN_MATCH_RULES.intensity.some((k) => cleanHeader.includes(k)) + ) { + matched.intensity = header; + } + + if ( + !matched.totalRainfall && + COLUMN_MATCH_RULES.totalRainfall.some((k) => cleanHeader.includes(k)) + ) { + matched.totalRainfall = header; + } + } + + return matched; +} + +/** + * 鏁版嵁澶勭悊涓诲嚱鏁� + * @param {Array} data - 瑙f瀽鍚庣殑鍘熷鏁版嵁鏁扮粍锛屾瘡涓厓绱犳槸涓�涓璞� + */ const processData = (data) => { - // 1. 妫�鏌ユ暟鎹槸鍚︿负绌� + // 妫�鏌ユ槸鍚︿负绌烘暟鎹� if (data.length === 0) { ElMessage.warning("鏂囦欢鍐呭涓虹┖锛�"); return; } - // 2. 鑾峰彇琛ㄥご锛堢涓�鍒楁槸鏃堕棿鍒楋級 - const tableColumns = Object.keys(data[0]); - const timeColumn = tableColumns[0]; // 鍋囪绗竴鍒楁槸鏃堕棿 + // 鍖归厤鍒楀悕锛堜緥濡傗�滄椂闂粹�濄�佲�滃皬鏃堕洦寮衡�濓級 + const columns = matchColumns(data[0]); - // 3. 鏍¢獙鏃堕棿鍒楁槸鍚︽寜鍗囧簭鎺掑垪 - if (!isTimeColumnSorted(data, timeColumn)) { - ElMessage.error("鏃堕棿鍒楀繀椤绘寜鍗囧簭鎺掑垪锛�"); + // 鏍¢獙蹇呰瀛楁鏄惁瀛樺湪 + if (!columns.time) { + ElMessage.error( + "鏈壘鍒版湁鏁堢殑鏃堕棿鍒楋紝璇锋鏌ュ垪鍚嶆槸鍚︿负鈥滄椂闂粹�濇垨鍏朵粬鏀寔鐨勬牸寮�" + ); forms.fileList = []; - return; // 缁堟澶勭悊 + return; } - const intensityColumn = tableColumns[1]; // 闆ㄥ己鍒楋紙濡� "灏忔椂闆ㄥ己(mm/h)"锛� - console.log(intensityColumn, "intensityColumnintensityColumnintensityColumn"); - // 3. 鎻愬彇绗簩鍒楃殑鍗曚綅锛堝 "(mm/h)" 鈫� "mm/h"锛� - const intensityUnit = extractUnitFromHeader(intensityColumn); - console.log( - intensityUnit, - "intensityUnitintensityUnitintensityUnitintensityUnit" + if (!columns.intensity) { + ElMessage.error( + "鏈壘鍒版湁鏁堢殑闆ㄥ己鍒楋紝璇锋鏌ュ垪鍚嶆槸鍚︿负鈥滃皬鏃堕洦寮衡�濇垨鍏朵粬鏀寔鐨勬牸寮�" + ); + forms.fileList = []; + return; + } + + // 鏍¢獙鏃堕棿鍒楁槸鍚﹀崌搴忔帓鍒� + if (!isTimeColumnSorted(data, columns.time)) { + ElMessage.error("鏃堕棿鍒楀繀椤绘寜鍗囧簭鎺掑垪锛�"); + forms.fileList = []; + return; + } + + // 鎻愬彇鍗曚綅锛堝 mm/h锛夛紝鑻ユ病鏈夊垯璁句负绌哄瓧绗︿覆 + forms.intensityUnit = extractUnitFromHeader(columns.intensity) || ""; + + // 灏嗗師濮嬫暟鎹浆鎹负缁熶竴缁撴瀯鐨勫璞℃暟缁� + const rawRainFallList = data.map((row) => ({ + time: row[columns.time], + intensity: parseFloat(row[columns.intensity]), + total: columns.totalRainfall + ? parseFloat(row[columns.totalRainfall]) + : undefined, + })); + + // 鏇存柊 forms.rainFallList锛屽彲鐢ㄤ簬鍥捐〃鏄剧ず绛夌敤閫� + forms.rainFallList = rawRainFallList; + + + // 鍒ゆ柇鏄惁涓烘暣灏忔椂鏁版嵁锛堝嵆鐩搁偦鏃堕棿闂撮殧鏄惁涓烘暣灏忔椂锛� + const isHourlyData = checkIfHourlyData(rawRainFallList); + + let hourlyRainfallList = []; + + if (!isHourlyData) { + // 濡傛灉涓嶆槸鏁村皬鏃舵暟鎹紝鎸夊皬鏃惰繘琛岃仛鍚堝鐞� + hourlyRainfallList = aggregateToHourlyRainfall(rawRainFallList); + console.log(hourlyRainfallList, "淇鍚庣殑灏忔椂闆ㄥ己"); + } else { + // 濡傛灉鏄暣灏忔椂鏁版嵁锛岀洿鎺ヤ娇鐢ㄥ師濮嬮洦寮哄�� + hourlyRainfallList = rawRainFallList.map((item) => ({ + time: item.time, + intensity: item.intensity, + })); + } + + // 璁$畻璧峰鏃堕棿鍜岀粨鏉熸椂闂达紙姣鏁帮級 + const firstTime = parseDateTime(hourlyRainfallList[0]?.time); + const lastTime = parseDateTime( + hourlyRainfallList[hourlyRainfallList.length - 1]?.time ); - forms.intensityUnit = intensityUnit; // 瀛樺偍鍗曚綅锛堝彲閫夛級 - // 4. 濡傛灉鏍¢獙閫氳繃锛岀户缁鐞嗘暟鎹� - forms.rainFallList = transformKeys(data); - console.log(forms.rainFallList, "data"); + // 璁$畻鎸佺画鏃堕棿锛堝崟浣嶏細灏忔椂锛� + const durationSeconds = Math.floor((lastTime - firstTime) / 1000); + forms.duration = (durationSeconds / 3600).toFixed(2); // 鍗曚綅锛氬皬鏃� - // 5. 璁$畻闄嶉洦鏃堕暱銆佹渶澶ч洦寮恒�佺疮璁¢洦閲忥紙鍘熼�昏緫锛� - const firstTime = parseDateTime(data[0][timeColumn]); - const lastTime = parseDateTime(data[data.length - 1][timeColumn]); - const timeDuration = Math.floor((lastTime - firstTime) / 1000); - forms.duration = (timeDuration / 3600).toFixed(2); - - const maxValue = Math.max( - ...data.map((row) => { - const value = parseFloat(row[tableColumns[1]]); - return isNaN(value) ? -Infinity : value; - }) + // 鎵惧嚭鏈�澶у皬鏃堕洦寮� + const maxIntensity = Math.max( + ...hourlyRainfallList.map((item) => item.intensity).filter((v) => !isNaN(v)) ).toFixed(2); - forms.intensity = maxValue; + forms.intensity = maxIntensity; - const lastValue = data[data.length - 1][tableColumns[2]]; - forms.rainfall = lastValue; + // 鑻ユ湁鎬婚檷闆ㄩ噺鍒楋紝鍙栧嚭鏈�鍚庝竴涓�间綔涓烘�婚檷闆ㄩ噺 + if (columns.totalRainfall) { + const lastTotal = parseFloat(data[data.length - 1][columns.totalRainfall]); + forms.rainfall = isNaN(lastTotal) ? 0 : lastTotal.toFixed(2); + } else { + forms.rainfall = 0; + } }; +/** + * 妫�鏌ユ暟鎹槸鍚︿负鏁村皬鏃惰褰� + * @param {Array} rainList - 鍘熷闄嶉洦鏁版嵁鍒楄〃锛屾瘡涓厓绱犲寘鍚� time 鍜� intensity + * @returns {boolean} - 鏄惁涓烘暣灏忔椂鏁版嵁 + */ +function checkIfHourlyData(rainList) { + if (rainList.length < 2) return true; // 鍙湁涓�涓偣锛岄粯璁よ涓烘暣灏忔椂鏁版嵁 + + for (let i = 1; i < rainList.length; i++) { + // 瑙f瀽涓や釜鐩搁偦鏃堕棿鐐� + const time1 = parseDateTime(rainList[i - 1].time); + const time2 = parseDateTime(rainList[i].time); + + // 璁$畻鏃堕棿宸紙鍒嗛挓锛� + const diffMinutes = Math.abs(time2 - time1) / (1000 * 60); + + // 濡傛灉鏃堕棿宸笉鏄暣灏忔椂锛堜笉鑳借60鏁撮櫎锛夛紝鍒欎笉鏄暣灏忔椂鏁版嵁 + if (diffMinutes % 60 !== 0) { + return false; + } + } + + return true; +} + +/** + * 灏嗕换鎰忔椂闂寸矑搴︾殑闆ㄥ己鏁版嵁锛屾寜灏忔椂鑱氬悎涓衡�滃皬鏃堕洦寮衡�� + * @param {Array} rainList - 鍘熷鏁版嵁鍒楄〃锛屾瘡涓厓绱犲寘鍚� time 鍜� intensity + * @returns {Array} - 鎸夊皬鏃跺垎缁勭殑鑱氬悎缁撴灉 + */ +function aggregateToHourlyRainfall(rainList) { + const grouped = {}; // 鐢ㄤ簬涓存椂瀛樺偍姣忎釜灏忔椂鐨勬暟鎹� + + for (const item of rainList) { + // 瑙f瀽鏃堕棿瀛楃涓蹭负鏃堕棿鎴� + const timestamp = parseDateTime(item.time); + + // 濡傛灉瑙f瀽澶辫触锛岃烦杩囧綋鍓嶉」 + if (isNaN(timestamp)) { + console.warn("鏃犳晥鐨勬椂闂存牸寮忥紝宸茶烦杩�", item.time); + continue; + } + + // 灏嗘椂闂存埑杞负 Date 瀵硅薄浠ヤ究鎿嶄綔鏃ユ湡 + const dt = new Date(timestamp); + + // 鏋勯�犲勾鏈堟棩+灏忔椂閿紙濡傦細"2024-08-25 14"锛� + const year = String(dt.getFullYear()).padStart(4, "0"); + const month = String(dt.getMonth() + 1).padStart(2, "0"); // 娉ㄦ剰鏈堜唤浠�0寮�濮� + const date = String(dt.getDate()).padStart(2, "0"); + const hour = String(dt.getHours()).padStart(2, "0"); + + const hourKey = `${year}-${month}-${date} ${hour}`; + + // 鍒濆鍖栬灏忔椂鐨勮仛鍚堝璞� + if (!grouped[hourKey]) { + grouped[hourKey] = { + time: `${hourKey}:00:00`, // 鏍囧噯鍖栦负鏁寸偣鏃堕棿 + intensity: 0, + }; + } + + // 绱姞璇ュ皬鏃跺唴鎵�鏈夐洦寮哄�� + grouped[hourKey].intensity += item.intensity; + } + + // 灏嗚仛鍚堢粨鏋滆浆涓烘暟缁勫苟淇濈暀涓や綅灏忔暟 + const result = Object.values(grouped).map((item) => ({ + time: item.time, + intensity: Number(item.intensity.toFixed(2)), + })); + + return result; +} /** * 瑙f瀽鏃ユ湡鏃堕棿瀛楃涓叉垨Excel鏁板瓧鏃ユ湡锛岃繑鍥炴椂闂存埑锛堟绉掓暟锛� * @param {string|number} dateString - 鏃ユ湡瀛楃涓叉垨Excel鏁板瓧鏃ユ湡 @@ -460,6 +670,16 @@ ElMessage.warning("姣忔鍙兘涓婁紶涓�涓枃浠�"); }; +const handleRemove = () => { + forms.rainfall = null; + forms.duration = null; + forms.intensity = null; + forms.fileList = []; + forms.rainFallList = []; + forms.hours = null; + forms.intensityUnit = ""; +}; + const beforeUpload = (file) => { const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); const isCSV = file.name.endsWith(".csv"); @@ -472,11 +692,38 @@ // 寮�濮嬫ā鎷� async function startPlay() { - // 寮�濮嬫ā鎷熷墠闇�瑕佸厛淇濆瓨鏂规 - await simStore.addSimCheme(forms); - EventBus.emit("close-selectArea"); - initeWaterPrimitiveView(); - startSimulate(); + try { + // 淇濆瓨鏂规 + if (selectTab.value == "閲嶇偣娌熶豢鐪�") { + forms.geom = props.selectedArea; + } + const res = await simStore.addSimCheme(forms); + const schemeId = res.data?.data?.id; + + if (!schemeId) { + ElMessage.error("鏂规淇濆瓨澶辫触锛屾湭鑾峰彇鍒版湁鏁� ID"); + return; + } + + // 璋冪敤姹傝В鍣� + const simStartRes = await getSimStart(schemeId); + + // 鍏抽棴閫夋嫨鍖哄煙绐楀彛銆佸垵濮嬪寲瑙嗗浘骞跺紑濮嬫ā鎷� + EventBus.emit("close-selectArea"); + simStore.shouldPoll = true; + + // 鏆傛椂涓嶅湪姝ゅ寮�濮嬫ā鎷燂紝妯℃嫙閮藉湪鏂规鍒楄〃涓繘琛屾ā鎷� + // initeWaterPrimitiveView(); + // startSimulate(); + + ElMessage.warning({ + message: "璇疯繑鍥炴柟妗堝垪琛ㄧ瓑寰呮ā鎷熺粨鏋滐紒", + duration: 10000, // 鎻愮ず妗嗘樉绀烘椂闀匡紝鍗曚綅涓烘绉掞紝榛樿鏄�3000姣 + }); + } catch (error) { + console.error("鍚姩妯℃嫙杩囩▼涓彂鐢熼敊璇細", error); + // ElMessage.error("鍚姩妯℃嫙澶辫触锛岃绋嶅悗鍐嶈瘯"); + } } </script> @@ -488,6 +735,12 @@ height: 100%; padding: 10px 10px 0px 0px; box-sizing: border-box; + transition: background 0.3s ease; // 鍙�夎繃娓℃晥鏋� +} + +.forms.no-background { + margin-top: 0px; + background-image: none; } /deep/ .el-input-group__append, .el-input-group__prepend { @@ -497,4 +750,7 @@ /deep/ .el-form-item__label { color: #61f7d4 !important; } +/deep/ .el-upload-list__item-file-name { + white-space: normal; +} </style> -- Gitblit v1.9.3