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 | 817 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 files changed, 637 insertions(+), 180 deletions(-) diff --git a/src/views/left/CitySim.vue b/src/views/left/CitySim.vue index e4a180f..f74e646 100644 --- a/src/views/left/CitySim.vue +++ b/src/views/left/CitySim.vue @@ -1,23 +1,43 @@ <template> <div style="width: 100%; height: 100%"> - <div class="left-top" v-if="disForm == '琛屾斂鍖哄垝浠跨湡'"> + <div + class="left-top" + v-if="simStore.selectTab == '琛屾斂鍖哄垝浠跨湡'" + style="margin-top: 0px" + > 琛屾斂鍖哄垝浠跨湡锛�30m绮惧害锛� </div> - <div class="left-top" v-if="disForm == '閲嶇偣鍖哄煙浠跨湡'"> + <div class="left-top" v-if="simStore.selectTab == '閲嶇偣鍖哄煙浠跨湡'"> 閲嶇偣鍖哄煙浠跨湡锛�10m绮惧害锛� </div> - <div class="forms"> - <el-form :model="forms" label-width="auto" style="max-width: 600px"> + <div class="left-top" v-if="simStore.selectTab == '閲嶇偣娌熶豢鐪�'"> + 鍘嗗彶妯℃嫙 + </div> + + <div class="forms" :class="{ 'no-background': !showBackground }"> + <el-form + :rules="rules" + :model="forms" + label-width="auto" + style="max-width: 600px" + > + <el-form-item label="鏂规鍚嶇О:"> + <el-input + v-model="forms.name" + style="max-width: 600px" + placeholder="璇疯緭鍏ユ柟妗堝悕绉�" + > + </el-input> + </el-form-item> <el-form-item label="涓婁紶鍙傛暟"> <el-upload + :on-remove="handleRemove" v-model:file-list="forms.fileList" class="upload-demo" - action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" - multiple - :on-preview="handlePreview" - :on-remove="handleRemove" - :before-remove="beforeRemove" - :limit="3" + :auto-upload="false" + :multiple="false" + :on-change="handleFileChange" + :limit="1" :on-exceed="handleExceed" :before-upload="beforeUpload" accept=".xlsx,.xls,.csv" @@ -26,32 +46,54 @@ <template #append>mm/h</template> </el-upload> </el-form-item> - <el-form-item label="琛屾斂鍖哄煙:" v-if="disForm == '琛屾斂鍖哄垝浠跨湡'"> + <el-form-item label="闆ㄥ己鍗曚綅" v-if="forms.fileList.length !== 0"> <el-select - v-model="forms.eare" - placeholder="Select" + v-model="forms.intensityUnit" + placeholder="璇烽�夋嫨闆ㄥ己鍗曚綅" style="max-width: 600px" + :disabled="!!forms.intensityUnit" > <el-option - v-for="item in cityOptions" + v-for="item in intensityOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> - - <el-form-item label="閲嶇偣鍖哄煙" v-if="disForm == '閲嶇偣鍖哄煙浠跨湡'"> + <el-form-item + label="琛屾斂鍖哄煙:" + v-if="simStore.selectTab == '琛屾斂鍖哄垝浠跨湡'" + > <el-select - v-model="forms.eares" - placeholder="Select" + @change="changeGeom" + v-model="forms.geom" + placeholder="璇烽�夋嫨妯℃嫙鍖哄煙" style="max-width: 600px" > <el-option - v-for="item in earesOptions" + v-for="item in options" :key="item.value" :label="item.label" - :value="item.value" + :value="item" + /> + </el-select> + </el-form-item> + <el-form-item + label="閲嶇偣鍖哄煙:" + v-if="simStore.selectTab == '閲嶇偣鍖哄煙浠跨湡'" + > + <el-select + @change="changeGeom" + v-model="forms.geom" + placeholder="璇烽�夋嫨妯℃嫙鍖哄煙" + style="max-width: 600px" + > + <el-option + v-for="item in options" + :key="item.value" + :label="item.label" + :value="item" /> </el-select> </el-form-item> @@ -60,222 +102,628 @@ <el-input v-model="forms.rainfall" style="max-width: 600px" - placeholder="Please input" + placeholder="璇疯緭鍏ラ檷闆ㄩ噺" > <template #append>mm</template> </el-input> </el-form-item> + <el-form-item label="閫夋嫨鏃堕棿:"> + <el-date-picker + v-if="forms.fileList.length !== 0" + v-model="forms.hours" + type="datetime" + placeholder="璇烽�夋嫨寮�濮嬫椂闂�" + /> + <el-date-picker + v-if="forms.fileList.length == 0" + v-model="forms.hours" + type="datetimerange" + start-placeholder="寮�濮嬫椂闂�" + end-placeholder="缁撴潫鏃堕棿" + format="YYYY-MM-DD HH:mm:ss" + date-format="YYYY/MM/DD ddd" + time-format="A hh:mm:ss" + @change="change" + /> + </el-form-item> <el-form-item label="闄嶉洦鏃堕暱:"> <el-input + disabled v-model="forms.duration" style="max-width: 600px" - placeholder="Please input" + placeholder="璇疯緭鍏ラ檷闆ㄦ椂闀�" > <template #append>h</template> - </el-input></el-form-item - > + </el-input> + </el-form-item> + <el-form-item label="闄嶉洦寮哄害:"> <el-input v-model="forms.intensity" style="max-width: 600px" - placeholder="Please input" + placeholder="璇疯緭鍏ラ檷闆ㄥ己搴�" > <template #append>mm/h</template> </el-input> </el-form-item> - <el-form-item label="浠跨湡鍙傛暟:"> </el-form-item> + + <!-- <el-form-item label="浠跨湡鍙傛暟:"></el-form-item> --> </el-form> <div style="display: flex; justify-content: flex-end"> - <el-button type="primary" @click="openSaveDialog">淇濆瓨鏂规</el-button> - <el-button type="success" @click="startPlay">寮�濮嬫ā鎷�</el-button> + <el-button type="primary" @click="addSimCheme">淇濆瓨鏂规</el-button> + <el-button type="success" @click="startPlay">淇濆瓨骞跺紑濮嬫ā鎷�</el-button> </div> </div> - - <!-- 淇濆瓨鏂规瀵硅瘽妗� --> - <el-dialog - v-model="saveDialogVisible" - :title="dialogTitle" - width="50%" - :before-close="handleClose" - custom-class="custom-dialog" - > - <div class="dialog-content"> - <p><strong>妯℃嫙绫诲瀷锛�</strong>{{ dialogTitle }}</p> - <p v-if="disForm === '琛屾斂鍖哄垝浠跨湡'"><strong>琛屾斂鍖哄煙锛�</strong>{{ forms.eare }}</p> - <p v-if="disForm === '閲嶇偣鍖哄煙浠跨湡'"><strong>閲嶇偣鍖哄煙锛�</strong>{{ forms.eares }}</p> - <p><strong>闄嶉洦閲忥細</strong>{{ forms.rainfall }} mm</p> - <p><strong>闄嶉洦鏃堕暱锛�</strong>{{ forms.duration }} h</p> - <p><strong>闄嶉洦寮哄害锛�</strong>{{ forms.intensity }} mm/h</p> - <!-- <p><strong>涓婁紶鏂囦欢锛�</strong>{{ uploadedFilesText }}</p> --> - </div> - <template #footer> - <span class="dialog-footer"> - <el-button @click="saveDialogVisible = false">鍙栨秷</el-button> - <el-button type="primary" @click="confirmSave">纭畾淇濆瓨</el-button> - </span> - </template> - </el-dialog> </div> </template> <script setup> -import { reactive, ref, computed ,watch} from "vue"; +import { reactive, ref, watch, inject, computed, onMounted } from "vue"; +import * as XLSX from "xlsx"; +import Papa from "papaparse"; import { ElMessage, ElMessageBox } from "element-plus"; +import { initeWaterPrimitiveView } from "@/utils/water"; +import { SimAPIStore } from "@/store/simAPI"; +import { getRegionData, getSimStart, getSimDataById } from "@/api/trApi"; -// 瀹氫箟 Props +import { storeToRefs } from "pinia"; +import dayjs from "dayjs"; +import { EventBus } from "@/eventBus"; // 寮曞叆浜嬩欢鎬荤嚎 + +const simStore = SimAPIStore(); +const { selectTab } = storeToRefs(simStore); + +const options = reactive([]); + +// 鍘嗗彶妯℃嫙閫変腑鍖哄煙 const props = defineProps({ - clickValue: String, + selectedArea: { + type: Object, + required: true, + }, }); -// 鏁版嵁缁戝畾 -const disForm = ref(""); // 褰撳墠鏄剧ず鐨勮〃鍗曠被鍨� -const saveDialogVisible = ref(false); // 鎺у埗淇濆瓨鏂规瀵硅瘽妗嗙殑鏄剧ず鐘舵�� +const intensityOptions = ref([ + { value: "mm/h", label: "mm/h" }, + { value: "mm/5min", label: "mm/5min" }, + { value: "mm/min", label: "mm/min" }, +]); + +// 瀹氫箟涓�涓柟娉曪紝鐢ㄤ簬鏍规嵁 type 鑾峰彇鍖哄煙鏁版嵁 +const fetchRegionData = (type) => { + getRegionData({ type: type }).then((res) => { + // 浣跨敤鍝嶅簲寮忔暟缁勭殑鏂规硶鏇存柊鍐呭 + options.splice( + 0, + options.length, + ...res.data.map((item) => ({ + value: item.geom, + label: item.name, + })) + ); + }); +}; + +onMounted(() => { + fetchRegionData(1); +}); + +const showBackground = ref(true); // 榛樿鏄剧ず鑳屾櫙鍥� + +// 鐩戝惉 selectTab 鐨勫彉鍖� +watch(selectTab, (newVal) => { + let type; + switch (newVal) { + case "琛屾斂鍖哄垝浠跨湡": + type = 1; + break; + 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(); +}); + +// 娉ㄥ叆鐖剁粍浠舵彁渚涚殑鏂规硶 +const { startSimulate, endSimulate } = inject("simulateActions"); +// 琛ㄥ崟鏁版嵁 const forms = reactive({ - eare: "鍖椾含甯�", // 琛屾斂鍖哄煙 - eares: "瀛欒儭娌�", // 閲嶇偣鍖哄煙 - rainfall: "50", // 闄嶉洦閲� - duration: "5", // 闄嶉洦鏃堕暱 - intensity: "70", // 闄嶉洦寮哄害 - fileList: [], // 涓婁紶鐨勬枃浠跺垪琛� + name: "", + geom: "", + rainfall: null, + duration: null, + intensity: null, + fileList: [], + type: 3, + rainFallList: [], + hours: null, + intensityUnit: "", }); -// 琛屾斂鍖哄煙閫夐」 -const cityOptions = [ - { value: "鍖椾含甯�", label: "鍖椾含甯�" }, - { value: "涓滃煄鍖�", label: "涓滃煄鍖�" }, - { value: "瑗垮煄鍖�", label: "瑗垮煄鍖�" }, - { value: "鏈濋槼鍖�", label: "鏈濋槼鍖�" }, - { value: "娴锋穩鍖�", label: "娴锋穩鍖�" }, - { value: "涓板彴鍖�", label: "涓板彴鍖�" }, - { value: "鐭虫櫙灞卞尯", label: "鐭虫櫙灞卞尯" }, - { value: "闂ㄥご娌熷尯", label: "闂ㄥご娌熷尯" }, - { value: "鎴垮北鍖�", label: "鎴垮北鍖�" }, - { value: "閫氬窞鍖�", label: "閫氬窞鍖�" }, - { value: "椤轰箟鍖�", label: "椤轰箟鍖�" }, - { value: "鏄屽钩鍖�", label: "鏄屽钩鍖�" }, - { value: "澶у叴鍖�", label: "澶у叴鍖�" }, - { value: "鎬�鏌斿尯", label: "鎬�鏌斿尯" }, - { value: "骞宠胺鍖�", label: "骞宠胺鍖�" }, - { value: "瀵嗕簯鍖�", label: "瀵嗕簯鍖�" }, - { value: "寤跺簡鍖�", label: "寤跺簡鍖�" }, -]; +const flyHeight = ref(100000); -// 閲嶇偣鍖哄煙閫夐」 -const earesOptions = [ - { value: "瀛欒儭娌�", label: "瀛欒儭娌�" }, - { value: "楸兼按娲炲悗娌�", label: "楸兼按娲炲悗娌�" }, - { value: "浜庡瑗挎矡", label: "浜庡瑗挎矡" }, - { value: "鍖楁渤娌�", label: "鍖楁渤娌�" }, - { value: "榫欐硥宄潙", label: "榫欐硥宄潙" }, -]; +// 灏嗛�変腑鍖哄煙浼犻�掔粰gisView鏂囦欢锛屽仛鏍囩孩flyTo鏄剧ず +const changeGeom = (val) => { + if (selectTab.value == "琛屾斂鍖哄垝浠跨湡") { + flyHeight.value = 100000; + } else { + flyHeight.value = 5000; + } + EventBus.emit("select-geom", { geom: val.value, flyHeight: flyHeight.value }); +}; -// 璁$畻灞炴�э細鑾峰彇瀵硅瘽妗嗘爣棰� -const dialogTitle = computed(() => { - return disForm.value === "琛屾斂鍖哄垝浠跨湡" ? "琛屾斂鍖哄垝浠跨湡" : "閲嶇偣鍖哄煙浠跨湡"; -}); +const { calculateHoursDifference } = inject("calculateHours"); + +const change = (val) => { + forms.duration = calculateHoursDifference(val); +}; + +const addSimCheme = async () => { + try { + if (selectTab.value == "閲嶇偣娌熶豢鐪�") { + forms.geom = props.selectedArea; + } + await simStore.addSimCheme(forms); + resetForm(); // 鍙湁鍦ㄤ繚瀛樻垚鍔熷悗鎵嶉噸缃〃鍗� + EventBus.emit("close-selectArea"); + } catch (error) {} +}; + +// 閲嶇疆琛ㄥ崟 +const resetForm = () => { + forms.name = ""; + forms.geom = ""; + forms.rainfall = null; + forms.duration = null; + forms.intensity = null; + forms.fileList = []; + forms.rainFallList = []; + forms.hours = null; + forms.intensityUnit = ""; +}; // 璁$畻灞炴�э細鑾峰彇涓婁紶鏂囦欢鐨勫悕绉板垪琛� const uploadedFilesText = computed(() => { - return forms.fileList.map(file => file.name).join(", ") || "鏃�"; + return forms.fileList.map((file) => file.name).join(", ") || "鏃�"; }); -// 鐩戝惉 Props 鍙樺寲锛屾洿鏂板綋鍓嶈〃鍗曠被鍨� -watch( - () => props.clickValue, - (newValue) => { - disForm.value = newValue || "琛屾斂鍖哄垝浠跨湡"; - }, - { immediate: true, deep: true } -); +// 鏂囦欢鍙樺寲鏃惰Е鍙戣В鏋� +const handleFileChange = (file) => { + const reader = new FileReader(); + reader.onload = (e) => { + const data = e.target.result; + if (file.name.endsWith(".csv")) { + parseCSV(data); + } else { + parseExcel(data); + } + }; + reader.readAsArrayBuffer(file.raw); +}; -// 鎵撳紑淇濆瓨鏂规瀵硅瘽妗� -const openSaveDialog = () => { - if ( - !forms.rainfall || - !forms.duration || - !forms.intensity || - (disForm.value === "琛屾斂鍖哄垝浠跨湡" && !forms.eare) || - (disForm.value === "閲嶇偣鍖哄煙浠跨湡" && !forms.eares) - ) { - ElMessage.warning("璇峰厛濉啓鎵�鏈夊繀濉」"); +// 瑙f瀽CSV鏂囦欢 +const parseCSV = (data) => { + Papa.parse(new TextDecoder("utf-8").decode(data), { + complete: (results) => { + if (results.data.length > 0) { + processData(results.data); + } + }, + header: true, + skipEmptyLines: true, + }); +}; + +// 瑙f瀽Excel鏂囦欢 +const parseExcel = (data) => { + const workbook = XLSX.read(data, { type: "array" }); + const firstSheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[firstSheetName]; + const jsonData = XLSX.utils.sheet_to_json(worksheet, { + raw: false, // 浣跨敤鏍煎紡鍖栧瓧绗︿覆鑰屼笉鏄師濮嬪�� + dateNF: "yyyy-mm-dd hh:mm:ss", // 鎸囧畾鏃ユ湡鏍煎紡 + }); + + processData(jsonData); +}; + +/** + * 妫�鏌ユ椂闂村垪鏄惁鎸夊崌搴忔帓鍒� + * @param {Array} data - 琛ㄦ牸鏁版嵁 + * @param {string} timeColumn - 鏃堕棿鍒楃殑瀛楁鍚� + * @returns {boolean} - 鏄惁鎸夊崌搴忔帓鍒� + */ +const isTimeColumnSorted = (data, timeColumn) => { + for (let i = 1; i < data.length; i++) { + const prevTime = parseDateTime(data[i - 1][timeColumn]); + const currentTime = parseDateTime(data[i][timeColumn]); + + // 濡傛灉鍓嶄竴涓椂闂� > 褰撳墠鏃堕棿锛岃鏄庝笉鏄崌搴� + if (prevTime > currentTime) { + console.error(`鏃堕棿涔卞簭锛氱 ${i} 琛宍, { + prevTime: new Date(prevTime), + currentTime: new Date(currentTime), + }); + return false; + } + } + return true; // 鎵�鏈夋椂闂撮兘鎸夊崌搴忔帓鍒� +}; + +/** + * 浠庤〃澶存彁鍙栧崟浣嶏紙濡� "灏忔椂闆ㄥ己mm/h" 鈫� "mm/h"锛� + * @param {string} header - 琛ㄥご瀛楃涓� + * @returns {string} - 鎻愬彇鐨勫崟浣嶏紙濡� "mm/h"锛夛紝榛樿杩斿洖绌哄瓧绗︿覆 + */ +const extractUnitFromHeader = (header) => { + if (!header) return ""; + + // 鐩存帴鍖归厤 "mm/h"銆�"m/s" 绛夊父瑙佸崟浣� + 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) => { + // 妫�鏌ユ槸鍚︿负绌烘暟鎹� + if (data.length === 0) { + ElMessage.warning("鏂囦欢鍐呭涓虹┖锛�"); return; } - saveDialogVisible.value = true; + + // 鍖归厤鍒楀悕锛堜緥濡傗�滄椂闂粹�濄�佲�滃皬鏃堕洦寮衡�濓級 + const columns = matchColumns(data[0]); + + // 鏍¢獙蹇呰瀛楁鏄惁瀛樺湪 + if (!columns.time) { + ElMessage.error( + "鏈壘鍒版湁鏁堢殑鏃堕棿鍒楋紝璇锋鏌ュ垪鍚嶆槸鍚︿负鈥滄椂闂粹�濇垨鍏朵粬鏀寔鐨勬牸寮�" + ); + forms.fileList = []; + return; + } + + 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 + ); + + // 璁$畻鎸佺画鏃堕棿锛堝崟浣嶏細灏忔椂锛� + const durationSeconds = Math.floor((lastTime - firstTime) / 1000); + forms.duration = (durationSeconds / 3600).toFixed(2); // 鍗曚綅锛氬皬鏃� + + // 鎵惧嚭鏈�澶у皬鏃堕洦寮� + const maxIntensity = Math.max( + ...hourlyRainfallList.map((item) => item.intensity).filter((v) => !isNaN(v)) + ).toFixed(2); + forms.intensity = maxIntensity; + + // 鑻ユ湁鎬婚檷闆ㄩ噺鍒楋紝鍙栧嚭鏈�鍚庝竴涓�间綔涓烘�婚檷闆ㄩ噺 + if (columns.totalRainfall) { + const lastTotal = parseFloat(data[data.length - 1][columns.totalRainfall]); + forms.rainfall = isNaN(lastTotal) ? 0 : lastTotal.toFixed(2); + } else { + forms.rainfall = 0; + } }; -// 鍏抽棴淇濆瓨鏂规瀵硅瘽妗� -const handleClose = () => { - saveDialogVisible.value = false; +/** + * 妫�鏌ユ暟鎹槸鍚︿负鏁村皬鏃惰褰� + * @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鏁板瓧鏃ユ湡 + * @returns {number} 鏃堕棿鎴筹紙姣鏁帮級锛岃В鏋愬け璐ヨ繑鍥� NaN + */ +const parseDateTime = (dateString) => { + // 1. 澶勭悊 Excel 鏁板瓧鏃ユ湡锛堝 45136.91666666666锛夛紝浣嗘槸姝ゅ鎴戝湪Excel瑙f瀽鏃堕棿鐨勬椂鍊欏凡缁忚浆鎹簡锛屾墍浠ヨ繖涓殏鏃舵棤鐢� + if (typeof dateString === "number") { + // 浣跨敤 XLSX 宸ュ叿瑙f瀽 Excel 鏃ユ湡缂栫爜 + const parsedDate = XLSX.SSF.parse_date_code(dateString); + if (parsedDate) { + // 杞崲涓� JavaScript Date 瀵硅薄骞惰繑鍥炴椂闂存埑 + return new Date( + parsedDate.y, // 骞� + parsedDate.m - 1, // 鏈堬紙Excel 涓� 1-12锛孞S 涓� 0-11锛� + parsedDate.d, // 鏃� + parsedDate.H || 0, // 鏃讹紙鍙兘涓嶅瓨鍦紝榛樿涓� 0锛� + parsedDate.M || 0, // 鍒嗭紙鍙兘涓嶅瓨鍦紝榛樿涓� 0锛� + parsedDate.S || 0 // 绉掞紙鍙兘涓嶅瓨鍦紝榛樿涓� 0锛� + ).getTime(); // 杩斿洖鏃堕棿鎴� + } + } + + // 2. 灏濊瘯鐩存帴瑙f瀽鏃ユ湡瀛楃涓诧紙濡� "2023-07-30T16:00:00"锛夛紝鐜板湪浣跨敤鐨勬槸杩欎釜 + const parsedDate = new Date(dateString); + if (!isNaN(parsedDate.getTime())) { + return parsedDate.getTime(); // 杩斿洖鏈夋晥鏃堕棿鎴� + } + + // 3. 澶勭悊鑷畾涔夋牸寮忕殑鏃ユ湡瀛楃涓诧紙濡� "2023/07/30 16:00:00"锛� + const parts = dateString.split(/[/\s:]/); // 鎸� `/`銆佺┖鏍笺�乣:` 鍒嗗壊 + if (parts.length >= 6) { + const year = parseInt(parts[0], 10); // 骞� + const month = parseInt(parts[1], 10) - 1; // 鏈堬紙杞崲涓� JS 鐨� 0-11锛� + const day = parseInt(parts[2], 10); // 鏃� + const hour = parseInt(parts[3], 10) || 0; // 鏃讹紙榛樿 0锛� + const minute = parseInt(parts[4], 10) || 0; // 鍒嗭紙榛樿 0锛� + const second = parseInt(parts[5], 10) || 0; // 绉掞紙榛樿 0锛� + + // 鏋勯�� Date 瀵硅薄骞惰繑鍥炴椂闂存埑 + const date = new Date(year, month, day, hour, minute, second); + if (!isNaN(date.getTime())) { + return date.getTime(); + } + } + + // 4. 瑙f瀽澶辫触鏃惰鍛婂苟杩斿洖 NaN + console.warn(`鏃犳硶瑙f瀽鏃ユ湡: ${dateString}`); + return NaN; }; -// 纭淇濆瓨 -const confirmSave = () => { - console.log('淇濆瓨鏂规鎴愬姛', { - 妯℃嫙绫诲瀷: dialogTitle.value, - 琛屾斂鍖哄煙: disForm.value === "琛屾斂鍖哄垝浠跨湡" ? forms.eare : null, - 閲嶇偣鍖哄煙: disForm.value === "閲嶇偣鍖哄煙浠跨湡" ? forms.eares : null, - 闄嶉洦閲�: `${forms.rainfall} mm`, - 闄嶉洦鏃堕暱: `${forms.duration} h`, - 闄嶉洦寮哄害: `${forms.intensity} mm/h`, - 涓婁紶鏂囦欢: forms.fileList.map(file => file.name), - }); - ElMessage.success('鏂规宸蹭繚瀛�'); - saveDialogVisible.value = false; +const handleExceed = () => { + 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"); + if (!isExcel && !isCSV) { + ElMessage.error("鍙兘涓婁紶Excel鎴朇SV鏂囦欢"); + return false; + } + return true; }; // 寮�濮嬫ā鎷� -function startPlay() { - console.log("寮�濮嬫ā鎷熸寜閽鐐瑰嚮"); -} +async function startPlay() { + try { + // 淇濆瓨鏂规 + if (selectTab.value == "閲嶇偣娌熶豢鐪�") { + forms.geom = props.selectedArea; + } + const res = await simStore.addSimCheme(forms); + const schemeId = res.data?.data?.id; -// 鏂囦欢涓婁紶鐩稿叧鏂规硶 -function handleRemove(file, uploadFiles) { - console.log(file, uploadFiles); -} + if (!schemeId) { + ElMessage.error("鏂规淇濆瓨澶辫触锛屾湭鑾峰彇鍒版湁鏁� ID"); + return; + } -function handlePreview(uploadFile) { - console.log(uploadFile); -} + // 璋冪敤姹傝В鍣� + const simStartRes = await getSimStart(schemeId); -function handleExceed(files, uploadFiles) { - ElMessage.warning( - `The limit is 3, you selected ${files.length} files this time, add up to ${ - files.length + uploadFiles.length - } totally` - ); -} + // 鍏抽棴閫夋嫨鍖哄煙绐楀彛銆佸垵濮嬪寲瑙嗗浘骞跺紑濮嬫ā鎷� + EventBus.emit("close-selectArea"); + simStore.shouldPoll = true; -const beforeUpload = (file) => { - const allowedTypes = [ - "application/vnd.ms-excel", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "text/csv", - "application/csv", - "text/x-csv", - "application/x-csv", - "text/comma-separated-values", - "text/x-comma-separated-values", - ]; + // 鏆傛椂涓嶅湪姝ゅ寮�濮嬫ā鎷燂紝妯℃嫙閮藉湪鏂规鍒楄〃涓繘琛屾ā鎷� + // initeWaterPrimitiveView(); + // startSimulate(); - const isAllowed = allowedTypes.includes(file.type); - const extension = file.name.split(".").pop().toLowerCase(); - const isExtensionValid = ["xls", "xlsx", "csv"].includes(extension); - - if (!isAllowed || !isExtensionValid) { - ElMessage.error("鍙兘涓婁紶 Excel (.xls, .xlsx) 鎴� CSV 鏂囦欢"); - return false; // 闃绘涓婁紶 + ElMessage.warning({ + message: "璇疯繑鍥炴柟妗堝垪琛ㄧ瓑寰呮ā鎷熺粨鏋滐紒", + duration: 10000, // 鎻愮ず妗嗘樉绀烘椂闀匡紝鍗曚綅涓烘绉掞紝榛樿鏄�3000姣 + }); + } catch (error) { + console.error("鍚姩妯℃嫙杩囩▼涓彂鐢熼敊璇細", error); + // ElMessage.error("鍚姩妯℃嫙澶辫触锛岃绋嶅悗鍐嶈瘯"); } - - return true; // 鍏佽涓婁紶 -}; - -function beforeRemove(uploadFile, uploadFiles) { - return ElMessageBox.confirm( - `Cancel the transfer of ${uploadFile.name} ?` - ).then( - () => true, - () => false - ); } </script> @@ -287,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 { @@ -296,4 +750,7 @@ /deep/ .el-form-item__label { color: #61f7d4 !important; } -</style> \ No newline at end of file +/deep/ .el-upload-list__item-file-name { + white-space: normal; +} +</style> -- Gitblit v1.9.3