guonan
2025-07-03 f37845dd0a787dd42bf6c72e923433f30fcd8cc3
src/views/left/CitySim.vue
@@ -1,37 +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">
        <el-form-item label="行政区域:" v-if="disForm == '行政区划仿真'">
          <el-select
            v-model="forms.eare"
            placeholder="Select"
    <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-option
              v-for="item in cityOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
          </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"
@@ -40,17 +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.eares"
            placeholder="Select"
            v-model="forms.intensityUnit"
            placeholder="请选择雨强单位"
            style="max-width: 600px"
            :disabled="!!forms.intensityUnit"
          >
            <el-option
              v-for="item in earesOptions"
              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="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>
        <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>
@@ -59,220 +102,631 @@
          <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>保存方案</el-button>
        <el-button @click="startPlay">开始模拟</el-button>
        <el-button type="primary" @click="addSimCheme">保存方案</el-button>
        <el-button type="success" @click="startPlay">保存并开始模拟</el-button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { reactive, ref, 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";
const emit = defineEmits(["start", "end"]);
function endPlay() {
  emit("end");
}
import { storeToRefs } from "pinia";
import dayjs from "dayjs";
import { EventBus } from "@/eventBus"; // 引入事件总线
function startPlay() {
  initeWaterPrimitiveView();
  emit("start");
}
const simStore = SimAPIStore();
const { selectTab } = storeToRefs(simStore);
const value = ref("");
const options = reactive([]);
const cityOptions = [
  {
    value: "北京市",
    label: "北京市",
// 历史模拟选中区域
const props = defineProps({
  selectedArea: {
    type: Object,
    required: true,
  },
  {
    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 earesOptions = [
  {
    value: "孙胡沟",
    label: "孙胡沟",
  },
  {
    value: "鱼水洞后沟",
    label: "鱼水洞后沟",
  },
  {
    value: "于家西沟",
    label: "于家西沟",
  },
  {
    value: "北河沟",
    label: "北河沟",
  },
  {
    value: "龙泉峪村",
    label: "龙泉峪村",
  },
];
const forms = reactive({
  eare: "北京市",
  eares: "孙胡沟",
  rainfall: "50",
  duration: "5",
  intensity: "70",
  fileList: [],
});
function handleRemove(file, uploadFiles) {
  console.log(file, uploadFiles);
}
const intensityOptions = ref([
  { value: "mm/h", label: "mm/h" },
  { value: "mm/5min", label: "mm/5min" },
  { value: "mm/min", label: "mm/min" },
]);
function handlePreview(uploadFile) {
  console.log(uploadFile);
}
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`
  );
}
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",
  ];
  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; // 阻止上传
  }
  return true; // 允许上传
// 定义一个方法,用于根据 type 获取区域数据
const fetchRegionData = (type) => {
  getRegionData({ type: type }).then((res) => {
    // 使用响应式数组的方法更新内容
    options.splice(
      0,
      options.length,
      ...res.data.map((item) => ({
        value: item.geom,
        label: item.name,
      }))
    );
  });
};
function beforeRemove(uploadFile, uploadFiles) {
  return ElMessageBox.confirm(
    `Cancel the transfer of ${uploadFile.name} ?`
  ).then(
    () => true,
    () => false
  );
}
const disForm = ref("");
// 定义 Props
const props = defineProps({
  clickValue: String,
onMounted(() => {
  fetchRegionData(1);
});
// 监听 Props 变化
watch(
  () => props.clickValue,
  (newValue) => {
    disForm.value = newValue || "行政区划仿真";
  },
  { immediate: true, deep: true }
);
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({
  name: "",
  geom: "",
  rainfall: null,
  duration: null,
  intensity: null,
  fileList: [],
  type: 3,
  rainFallList: [],
  hours: null,
  intensityUnit: "",
});
const flyHeight = ref(100000);
// 将选中区域传递给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 { 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(", ") || "无";
});
// 文件变化时触发解析
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);
};
// 解析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,
  });
};
// 解析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 - 解析后的原始数据数组,每个元素是一个对象
 */
const processData = (data) => {
  // 检查是否为空数据
  if (data.length === 0) {
    ElMessage.warning("文件内容为空!");
    return;
  }
  // 匹配列名(例如“时间”、“小时雨强”)
  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;
  }
};
/**
 * 检查数据是否为整小时记录
 * @param {Array} rainList - 原始降雨数据列表,每个元素包含 time 和 intensity
 * @returns {boolean} - 是否为整小时数据
 */
function checkIfHourlyData(rainList) {
  if (rainList.length < 2) return true; // 只有一个点,默认视为整小时数据
  for (let i = 1; i < rainList.length; i++) {
    // 解析两个相邻时间点
    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) {
    // 解析时间字符串为时间戳
    const timestamp = parseDateTime(item.time);
    // 如果解析失败,跳过当前项
    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;
}
/**
 * 解析日期时间字符串或Excel数字日期,返回时间戳(毫秒数)
 * @param {string|number} dateString - 日期字符串或Excel数字日期
 * @returns {number} 时间戳(毫秒数),解析失败返回 NaN
 */
const parseDateTime = (dateString) => {
  // 1. 处理 Excel 数字日期(如 45136.91666666666),但是此处我在Excel解析时间的时候已经转换了,所以这个暂时无用
  if (typeof dateString === "number") {
    // 使用 XLSX 工具解析 Excel 日期编码
    const parsedDate = XLSX.SSF.parse_date_code(dateString);
    if (parsedDate) {
      // 转换为 JavaScript Date 对象并返回时间戳
      return new Date(
        parsedDate.y, // 年
        parsedDate.m - 1, // 月(Excel 中 1-12,JS 中 0-11)
        parsedDate.d, // 日
        parsedDate.H || 0, // 时(可能不存在,默认为 0)
        parsedDate.M || 0, // 分(可能不存在,默认为 0)
        parsedDate.S || 0 // 秒(可能不存在,默认为 0)
      ).getTime(); // 返回时间戳
    }
  }
  // 2. 尝试直接解析日期字符串(如 "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. 解析失败时警告并返回 NaN
  console.warn(`无法解析日期: ${dateString}`);
  return NaN;
};
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或CSV文件");
    return false;
  }
  return true;
};
// 开始模拟
async function startPlay() {
  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>
<style lang="less" scoped>
.forms {
  background: url("@/assets/img/screen/leftbg.png");
@@ -281,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 {
@@ -290,4 +750,7 @@
/deep/ .el-form-item__label {
  color: #61f7d4 !important;
}
/deep/ .el-upload-list__item-file-name {
  white-space: normal;
}
</style>