guonan
2025-04-22 8d7fca46fd7f1bf85dbe9fa6fcfd287f45ad1b71
src/views/left/CitySim.vue
@@ -1,14 +1,45 @@
<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-form-item label="方案名称:">
          <el-input
            v-model="forms.name"
            style="max-width: 600px"
            placeholder="Please input"
          >
          </el-input>
        </el-form-item>
        <el-form-item label="上传参数">
          <el-upload
            v-model:file-list="forms.fileList"
            class="upload-demo"
            :auto-upload="false"
            :multiple="false"
            :on-change="handleFileChange"
            :limit="1"
            :on-exceed="handleExceed"
            :before-upload="beforeUpload"
            accept=".xlsx,.xls,.csv"
          >
            <el-button type="primary">点击上传降雨数据</el-button>
            <template #append>mm/h</template>
          </el-upload>
        </el-form-item>
        <el-form-item
          label="行政区域:"
          v-if="simStore.selectTab == '行政区划仿真'"
        >
          <el-select
            v-model="forms.eare"
            placeholder="Select"
@@ -22,25 +53,10 @@
            />
          </el-select>
        </el-form-item>
        <el-form-item label="上传参数">
          <el-upload
            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"
            :on-exceed="handleExceed"
            :before-upload="beforeUpload"
            accept=".xlsx,.xls,.csv"
          >
            <el-button type="primary">点击上传降雨数据</el-button>
            <template #append>mm/h</template>
          </el-upload>
        </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"
@@ -64,6 +80,7 @@
            <template #append>mm</template>
          </el-input>
        </el-form-item>
        <el-form-item label="降雨时长:">
          <el-input
            v-model="forms.duration"
@@ -71,8 +88,9 @@
            placeholder="Please input"
          >
            <template #append>h</template>
          </el-input></el-form-item
        >
          </el-input>
        </el-form-item>
        <el-form-item label="降雨强度:">
          <el-input
            v-model="forms.intensity"
@@ -82,197 +100,215 @@
            <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 } from "@/api/trApi";
const emit = defineEmits(["start", "end"]);
function endPlay() {
  emit("end");
}
const cityOptions = reactive([]);
function startPlay() {
  initeWaterPrimitiveView();
  emit("start");
}
onMounted(() => {
  getRegionData({ type: 1 }).then((res) => {
    console.log(res, "res");
    // 使用响应式数组的方法更新内容
    cityOptions.splice(
      0,
      cityOptions.length,
      ...res.data.map((item) => ({
        value: item.geom,
        label: item.name,
      }))
    );
  });
});
const value = ref("");
const simStore = SimAPIStore();
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 earesOptions = [
  {
    value: "孙胡沟",
    label: "孙胡沟",
  },
  {
    value: "鱼水洞后沟",
    label: "鱼水洞后沟",
  },
  {
    value: "于家西沟",
    label: "于家西沟",
  },
  {
    value: "北河沟",
    label: "北河沟",
  },
  {
    value: "龙泉峪村",
    label: "龙泉峪村",
  },
];
// 注入父组件提供的方法
const { startSimulate, endSimulate } = inject("simulateActions");
// 表单数据
const forms = reactive({
  name: "",
  eare: "北京市",
  eares: "孙胡沟",
  rainfall: "50",
  duration: "5",
  intensity: "70",
  rainfall: "",
  duration: "",
  intensity: "",
  fileList: [],
});
function handleRemove(file, uploadFiles) {
  console.log(file, uploadFiles);
}
const earesOptions = [
  { value: "孙胡沟", label: "孙胡沟" },
  { value: "鱼水洞后沟", label: "鱼水洞后沟" },
  { value: "于家西沟", label: "于家西沟" },
  { value: "北河沟", label: "北河沟" },
  { value: "龙泉峪村", label: "龙泉峪村" },
];
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; // 允许上传
};
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,
// 计算属性:获取对话框标题
const dialogTitle = computed(() => {
  return simStore.selectTab === "行政区划仿真"
    ? "行政区划仿真"
    : "重点区域仿真";
});
// 监听 Props 变化
watch(
  () => props.clickValue,
  (newValue) => {
    disForm.value = newValue || "行政区划仿真";
  },
  { immediate: true, deep: true }
);
// 计算属性:获取上传文件的名称列表
const uploadedFilesText = computed(() => {
  return forms.fileList.map((file) => file.name).join(", ") || "无";
});
const addSimCheme = async () => {
  await simStore.addSimCheme(forms);
};
// 重置表单
const resetForm = () => {
  forms.eare = "北京市";
  forms.eares = "孙胡沟";
  forms.rainfall = "";
  forms.duration = "";
  forms.intensity = "";
  forms.fileList = [];
};
// 文件变化时触发解析
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);
  processData(jsonData);
};
// 处理数据
const processData = (data) => {
  if (data.length === 0) return;
  const tableColumns = Object.keys(data[0]);
  // 第一列:时间
  const firstTime = parseDateTime(data[0][tableColumns[0]]);
  const lastTime = parseDateTime(data[data.length - 1][tableColumns[0]]);
  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;
    })
  ).toFixed(2);
  forms.intensity = maxValue; // 更新降雨强度
  // 第三列:累计降雨量
  const lastValue = data[data.length - 1][tableColumns[2]];
  forms.rainfall = lastValue; // 更新降雨量
};
// 解析日期时间
const parseDateTime = (dateString) => {
  if (typeof dateString === "number") {
    const parsedDate = XLSX.SSF.parse_date_code(dateString);
    if (parsedDate) {
      return new Date(
        parsedDate.y,
        parsedDate.m - 1,
        parsedDate.d,
        parsedDate.H || 0,
        parsedDate.M || 0,
        parsedDate.S || 0
      ).getTime();
    }
  }
  const parsedDate = new Date(dateString);
  if (!isNaN(parsedDate.getTime())) {
    return parsedDate.getTime();
  }
  const parts = dateString.split(/[/\s:]/);
  if (parts.length >= 6) {
    const year = parseInt(parts[0], 10);
    const month = parseInt(parts[1], 10) - 1;
    const day = parseInt(parts[2], 10);
    const hour = parseInt(parts[3], 10) || 0;
    const minute = parseInt(parts[4], 10) || 0;
    const second = parseInt(parts[5], 10) || 0;
    const date = new Date(year, month, day, hour, minute, second);
    if (!isNaN(date.getTime())) {
      return date.getTime();
    }
  }
  console.warn(`无法解析日期: ${dateString}`);
  return NaN;
};
const handleExceed = () => {
  ElMessage.warning("每次只能上传一个文件");
};
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;
};
// 开始模拟
function startPlay() {
  initeWaterPrimitiveView();
  startSimulate();
}
</script>
<style lang="less" scoped>
.forms {
  background: url("@/assets/img/screen/leftbg.png");