已修改9个文件
630 ■■■■■ 文件已修改
public/CimSDK/Workers/Model/ModelLibrary.html 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/trApi.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/menu/TimeLine.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/echartInfo.vue 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeCard.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeInfo.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tools/DebuffDetail.vue 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/water.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/left/CitySim.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/CimSDK/Workers/Model/ModelLibrary.html
@@ -113,12 +113,12 @@
<body>
    <div class="LibraryHead">
        <button type="button" title="打开" id='open' class="open layui-btn layui-btn-sm">
        <!-- <button type="button" title="打开" id='open' class="open layui-btn layui-btn-sm">
            <i class="layui-icon">&#xe67d;</i>
        </button>
        <button type="button" title="保存" class="save layui-btn layui-btn-sm layui-btn-normal">
            <i class="layui-icon">&#xe621;</i>
        </button>
        </button> -->
        <button type="button" title="是否开启编辑" class="edit layui-btn layui-btn-sm layui-btn-warm">
            <i class="layui-icon">&#xe673;</i>
        </button>
@@ -148,7 +148,7 @@
        var Viewer = parent.earthCtrl.viewer;
        var sgworld = parent.earthCtrl;
        var Cesium = parent.Cesium;
        var ModelLibraryURL = 'https://cim.smartearth.cn/sdkdemo/ModelLibrary/'
        var ModelLibraryURL = 'http://192.168.56.107:8088/ModelLibrary/'
        var imgRootURL = ModelLibraryURL + 'image/';
        //获取数据
@@ -163,7 +163,7 @@
        //开启编辑并启用属性弹窗
        let isEdit = true;
        sgworld.factory.SimpleGraphic.edit(true, { editProp: true });
        sgworld.factory.SimpleGraphic.edit(true, { editProp: true ,showRightButton:true});
        layui.use(['element', 'form', 'slider', 'upload'], function () {
            window.element = layui.element; //Tab的切换功能,切换事件监听等,需要依赖element模块
src/api/trApi.js
@@ -140,7 +140,7 @@
    let fileName = timestamp ? timestamp : 'layer.json';
    const url = `/simu/${serviceInfo}/${fileName}`;
    const response = await fetch(url); // 发起请求
    const response = await fetch(url); // 发起请求
    // console.log(url,'aaaaaaaaa')
src/components/menu/TimeLine.vue
@@ -302,7 +302,17 @@
      // 如果需要触发某些更新函数,也可以保留
      updateWaterColorByTime();
      updateWeatherByProgress();
      // updateWeatherByProgress();
      // 修改为固定阶段,缓慢下雨的状态
      const rainParams = {
        rainSize: 0.5,
        rainSpeed: 20,
        rainDensity: 15,
        rainColor: "#ADD8E6",
      };
      console.log("实时模拟开始下雨");
      // 调用工具方法更新雨效
      mapUtils.toggleRain(rainParams, true);
    }, 5000); // 每 5 秒执行一次
  } else {
    // 这里面还是你的播放代码,上面的if中是五秒钟跳动一次的实时模拟
@@ -344,9 +354,10 @@
    console.warn("selectedScheme 或 data 不存在");
    return;
  }
  // 注意:有时 data 可能是一个字符串(例如 JSON 字符串)
  let data = selectedScheme.value.data;
  // 如果是字符串,则尝试解析成对象
  // 如果是字符串,则尝试解析为对象
  if (typeof data === "string") {
    try {
      data = JSON.parse(data);
@@ -356,28 +367,69 @@
      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 单位,无法进行转换");
  // 判断 rainfalls 是否为对象,如果是则转成数组
  let rainfalls = data.rainfalls;
  if (typeof rainfalls === "object" && !Array.isArray(rainfalls)) {
    rainfalls = Object.values(rainfalls);
    console.warn("⚠️ rainfalls 是对象,已转换为数组");
  }
  const rainfallList = data.rainfalls;
  console.log("最终的 rainfallList:", rainfallList);
  rainTotalInfo.value = rainfallList;
  calculateTimeStep(rainTotalInfo.value);
  // 使用示例
  // 按小时聚合降雨数据
  const hourlyRainfallMap = {};
  rainfalls.forEach((record) => {
    const originalTime = new Date(record.time);
    if (isNaN(originalTime.getTime())) {
      console.warn("无效的时间格式:", record.time);
      return;
    }
    // 构造“小时”级别的时间键,比如:2024-08-25 20:00:00
    const hourKey = new Date(
      originalTime.getFullYear(),
      originalTime.getMonth(),
      originalTime.getDate(),
      originalTime.getHours()
    );
    const hourStr = hourKey.toISOString().slice(0, 16).replace("T", " ");
    if (!hourlyRainfallMap[hourStr]) {
      hourlyRainfallMap[hourStr] = {
        intensity: 0,
        time: hourStr,
        total: record.total, // 默认用第一个记录的 total
      };
    }
    hourlyRainfallMap[hourStr].intensity += record.intensity;
    // 取最大的 total(因为是累积值)
    if (record.total > hourlyRainfallMap[hourStr].total) {
      hourlyRainfallMap[hourStr].total = record.total;
    }
  });
  // 转换 map 成数组并排序
  const hourlyRainfallList = Object.values(hourlyRainfallMap).sort((a, b) =>
    a.time.localeCompare(b.time)
  );
  console.log("✅ 按小时聚合后的降雨数据:", hourlyRainfallList);
  // 设置全局变量
  rainTotalInfo.value = hourlyRainfallList;
  // 计算时间步长
  timeStepInfo = calculateTimeStep(rainTotalInfo.value);
  // 提取 intensity 值
  rainFallValues.value = rainfallList.map((r) => r.intensity);
  rainFallValues.value = hourlyRainfallList.map((r) => r.intensity);
  minRainValue.value = Math.min(...rainFallValues.value);
  maxRainValue.value = Math.max(...rainFallValues.value);
  console.log(
    "当前方案下最小雨量和最大雨量:",
    minRainValue.value,
@@ -616,20 +668,20 @@
  colorState.lastTime = currentTime.value;
  // ====== 新增:在 updateWaterColor 前打印当前信息 ======
  // // 获取当前累计降雨量
  // let currentTotal = null;
  // const baseTimestamp = new Date(rainTotalInfo.value[0].time).getTime();
  // const currentTimeMs = baseTimestamp + currentTime.value * 1000;
  let currentTotal = null;
  const baseTimestamp = new Date(rainTotalInfo.value[0].time).getTime();
  const currentTimeMs = baseTimestamp + currentTime.value * 1000;
  // // 找到最接近的降雨数据点
  // for (let i = rainTotalInfo.value.length - 1; i >= 0; i--) {
  //   const dataTimeMs = new Date(rainTotalInfo.value[i].time).getTime();
  //   if (dataTimeMs <= currentTimeMs) {
  //     currentTotal = rainTotalInfo.value[i].total;
  //     break;
  //   }
  // }
  // 找到最接近的降雨数据点
  for (let i = rainTotalInfo.value.length - 1; i >= 0; i--) {
    const dataTimeMs = new Date(rainTotalInfo.value[i].time).getTime();
    if (dataTimeMs <= currentTimeMs) {
      currentTotal = rainTotalInfo.value[i].total;
      break;
    }
  }
  // // 打印信息
  // 打印信息
  // console.log("========================================");
  // console.log(`【时间戳】: ${new Date(currentTimeMs).toLocaleString()}`);
  // console.log(`【累计降雨量 R】: ${currentTotal !== null ? currentTotal.toFixed(2) : '未知'} mm`);
@@ -879,7 +931,6 @@
    }
    // console.log('获取到的 serviceName:', serviceInfo);
    // 根据 layer.json 获取时间轴信息
    const {
      waterTimestamps: timestamps,
@@ -904,7 +955,6 @@
        "YYYY-MM-DD HH:mm:ss"
      );
    }
    minFlowRate = watersMinHeight;
    maxFlowRate = watersMaxHeight;
  } catch (error) {
@@ -921,9 +971,7 @@
  // 因为这个函数实时模拟监听也需要使用,所以封装了一个函数
  await initializeSimulationData();
});
const shouldAutoPlay = ref(false);
// 监听 layerDate 变化后标记准备播放
watch(
  () => layerDate.value,
@@ -934,7 +982,6 @@
  },
  { deep: true }
);
// 等待 finishPlay 成功后再播放
watchEffect(() => {
  if (shouldAutoPlay.value && finishPlay.value && !isPlaying.value) {
@@ -943,7 +990,6 @@
    shouldAutoPlay.value = false;
  }
});
// 根据返回数据的个数去渲染时间轴
function updateTimelineRange() {
  if (waterTimestamps.value.length > 0) {
@@ -954,7 +1000,6 @@
    duration.value = (last - first) / 1000; // 毫秒转秒
  }
}
onBeforeUnmount(() => {
  stopPlayback();
  destoryWaterPrimitive();
src/components/monifangzhen/echartInfo.vue
@@ -450,16 +450,33 @@
    }
  };
  // 控制方法:精确控制动画时间,最后一帧在第 90 秒
  let fixedFrameNum = null;
  let startTime = null; // 将startTime移到外层
  let elapsedBeforePause = 0; // 记录暂停前已经过去的时间
  const startUpdating = () => {
    if (updateInterval || dataIndex.value >= rainfallData.value.length) return;
    if (updateInterval || dataIndex.value >= rainfallData.value.length) {
      // console.log("Animation already running or completed");
      return;
    }
    const totalDuration = simStore.frameNum * 1000; // 90秒
    // 如果是首次启动或重新开始
    if (fixedFrameNum === null) {
      fixedFrameNum = simStore.frameNum;
      elapsedBeforePause = 0;
      startTime = Date.now();
    } else {
      // 如果是暂停后继续,调整startTime以反映已经过去的时间
      startTime = Date.now() - elapsedBeforePause;
    }
    const totalDuration = fixedFrameNum * 1000;
    const totalPoints = rainfallData.value.length;
    const startTime = Date.now();
    const animate = (index = 0) => {
    const animate = (index) => {
      if (index >= totalPoints) {
        console.log("Animation completed");
        stopUpdating();
        return;
      }
@@ -469,7 +486,7 @@
      const delay = Math.max(0, startTime + expectedTime - now);
      updateInterval = setTimeout(() => {
        dataIndex.value = index + 1; // 因为是从 0 开始 push 的
        dataIndex.value = index;
        updateData();
        animate(index + 1);
      }, delay);
@@ -478,13 +495,26 @@
    animate(dataIndex.value);
  };
  // 暂停函数需要记录已经过去的时间
  const stopUpdating = () => {
    clearTimeout(updateInterval);
    updateInterval = null;
    if (updateInterval) {
      clearTimeout(updateInterval);
      updateInterval = null;
      // 记录暂停时已经过去的时间
      elapsedBeforePause = Date.now() - startTime;
    }
  };
  // const stopUpdating = () => {
  //   clearTimeout(updateInterval);
  //   updateInterval = null;
  // };
  const resetLoading = () => {
    stopUpdating();
    fixedFrameNum = null;
    startTime = null;
    elapsedBeforePause = 0;
    dataIndex.value = 0;
    data1.value = [0];
    data2.value = [0];
src/components/monifangzhen/schemeCard.vue
@@ -220,9 +220,11 @@
      (item) =>
        item.result == "创建仿真" ||
        item.result == "完成" ||
        item.result == "-1"
        item.result == "-1" ||
        item.result == null
    );
    simAPIStore.shouldPoll = !shouldStop; // 修改 Pinia 状态
    console.log(shouldStop, "aaaaaaaaaaaaaaaa");
    // 3. 如果需要停止
    if (shouldStop) {
      if (intervalId) {
@@ -241,7 +243,6 @@
watch(
  () => simAPIStore.shouldPoll,
  (isStarted) => {
    console.log(isStarted, "定时器");
    if (isStarted) {
      getScheme(); // 首次立即获取一次
      intervalId = setInterval(getScheme, 60 * 1000); // 每隔一分钟执行
src/components/monifangzhen/schemeInfo.vue
@@ -108,61 +108,24 @@
// 处理 data 字段解析
function parseDataField(dataStr) {
  if (typeof dataStr !== "string") return [];
  // console.log(dataStr, '方案详情内的降雨数据');
  try {
    const parsed = JSON.parse(dataStr);
    const fields = {
      total: "降雨总量(mm):",
      duration: "降雨时长(小时):",
      intensity: "降雨强度(mm/小时):", // 统一为 mm/h
      intensity: "降雨强度(mm/小时):",
      prediction: "降雨场次:",
      model: "降雨模式:",
      history: "历史降雨:",
    };
    let { total, duration, intensity, intensityUnit } = parsed;
    // 根据 intensityUnit 确定转换系数
    let factor = 1;
    switch (intensityUnit) {
      case "mm/min":
        factor = 60;
        break;
      case "mm/5min":
        factor = 12;
        break;
      case "mm/h":
        factor = 1;
        break;
      default:
        factor = 1;
    }
    // 转换单位:将 intensity 和 total 统一为按小时计算的值
    intensity = intensity != null ? (intensity * factor).toFixed(2) : "无";
    total = total != null ? (total * factor).toFixed(2) : "无";
    // 控制台输出你需要的关键字段
    // console.log('转换后的降雨强度(mm/h):', intensity);
    // console.log('转换后的降雨总量(mm):', total);
    // 处理 duration,如果非数字则设为默认值
    duration = duration != null ? parseInt(duration) : "无";
    const result = Object.entries(parsed)
      .filter(([k]) => fields[k])
      .map(([k, v]) => {
        let displayValue = v || "无";
        if (k === "total") displayValue = total;
        if (k === "duration") displayValue = duration;
        if (k === "intensity") displayValue = intensity;
        return {
          name: fields[k],
          value: displayValue,
        };
      });
      .map(([k, v]) => ({
        name: fields[k],
        value: v || "无",
      }));
    // 处理雨量计数据
    if (parsed.type == 2 && parsed.gauges && Array.isArray(parsed.gauges)) {
@@ -177,7 +140,6 @@
    return result;
  } catch (e) {
    console.error("解析 dataStr 出错:", e);
    return [{ name: "数据:", value: dataStr || "无" }];
  }
}
src/components/tools/DebuffDetail.vue
@@ -12,108 +12,149 @@
  </div>
</template>
<script>
// 状态管理器
<script setup>
import { ref, onMounted } from "vue";
import { useSimStore } from "@/store/simulation";
import { storeToRefs } from "pinia";
const simStore = useSimStore();
const { selectedScheme } = storeToRefs(simStore);
export default {
  name: "detail",
  components: {},
  props: {
    areaName: {
      type: String,
      default: "尹家西沟",
    },
  },
  data() {
    return {
      show: false,
      detailList: [
        {
          name: "最大雨强:",
          value: Number(Math.random() * 100).toFixed(2) + " mm/h",
        },
        {
          name: "平均雨强:",
          value: Number(Math.random() * 10).toFixed(2) + " mm/h",
        },
        {
          name: "最大水深:",
          value: "1.86 m",
        },
        {
          name: "最大流速:",
          value: "7 m/s",
        },
        {
          name: "威胁房屋:",
          value: "406 间",
        },
        {
          name: "威胁人口:",
          value: "145 户",
        },
        {
          name: "威胁财产:",
          value: "4872 万元",
        },
      ],
    };
  },
  mounted() {
    this.getRainfallData(); // 组件挂载后执行获取雨量数据
  },
  methods: {
    getRainfallData() {
      if (!selectedScheme.value || !selectedScheme.value.data) {
        console.warn("selectedScheme 或 data 不存在");
        return;
      }
      let data = selectedScheme.value.data;
      // 如果是字符串,则尝试解析成对象
      if (typeof data === "string") {
        try {
          data = JSON.parse(data);
        } catch (e) {
          console.error("data 不是有效的 JSON 字符串");
          return;
        }
      }
// 隐患点
const filteredData = simStore.DangerPoint.filter((item) =>
  item.position?.includes("孙胡沟")
);
      if (selectedScheme.value.type !== 2) {
        const rainfallList = data.rainfalls;
        // 提取 intensity 值
        const rainValues = rainfallList.map((r) => r.intensity);
        const minRain = Math.min(...rainValues);
        const maxRain = Math.max(...rainValues);
        const avgRain =
          rainValues.reduce((sum, val) => sum + val, 0) / rainValues.length;
        // 更新 detailList 中的“最大雨强”和“平均雨强”
        this.detailList[0].value = maxRain.toFixed(2) + " mm/h"; // 最大雨强
        this.detailList[1].value = avgRain.toFixed(2) + " mm/h"; // 平均雨强
        console.log(
          "当前方案下最小雨量、最大雨量、平均雨量:",
          minRain.toFixed(2),
          maxRain.toFixed(2),
          avgRain.toFixed(2)
        );
      }
    },
    closeMsg() {
      this.$emit("close");
    },
    showMsg() {
      this.$emit("open");
    },
// 响应式数据
const detailList = ref([
  {
    name: "最大雨强:",
    value: (Math.random() * 100).toFixed(2) + " mm/h",
  },
  {
    name: "平均雨强:",
    value: (Math.random() * 10).toFixed(2) + " mm/h",
  },
  {
    name: "最大水深:",
    value: "1.86 m",
  },
  {
    name: "最大流速:",
    value: "7 m/s",
  },
  {
    name: "威胁房数:",
    value: "406 间",
  },
  {
    name: "威胁户数:",
    value: "145 户",
  },
  {
    name: "威胁人口:",
    value: "145 人",
  },
  {
    name: "威胁财产:",
    value: "4872 万元",
  },
]);
const updateThreatData = () => {
  // 筛选 position 包含 "孙胡沟" 的数据
  const filteredData = simStore.DangerPoint.filter((item) =>
    item.position?.includes("孙胡沟")
  );
  if (filteredData.length === 0) {
    console.warn("未找到 position 包含 '孙胡沟' 的数据");
    return;
  }
  // 初始化累加值
  let totalHousehold = 0; // 威胁户数
  let totalPerson = 0; // 威胁人数
  let totalRoom = 0; // 威胁房数
  // 遍历并累加
  filteredData.forEach((item) => {
    totalHousehold += Number(item.threatHouseNum) || 0;
    totalPerson += Number(item.threatPersonNum) || 0;
    totalRoom += Number(item.threatRoomNum) || 0;
  });
  // 更新 detailList
  detailList.value[5].value = `${totalHousehold} 户`;
  detailList.value[6].value = `${totalPerson} 人`;
  detailList.value[4].value = `${totalRoom} 间`;
  console.log("威胁户数:", totalHousehold);
  console.log("威胁人数:", totalPerson);
  console.log("威胁房数:", totalRoom);
};
// 方法定义
const getRainfallData = () => {
  if (!selectedScheme.value || !selectedScheme.value.data) {
    console.warn("selectedScheme 或 data 不存在");
    return;
  }
  let data = selectedScheme.value.data;
  // 如果是字符串,则尝试解析成对象
  if (typeof data === "string") {
    try {
      data = JSON.parse(data);
    } catch (e) {
      console.error("data 不是有效的 JSON 字符串");
      return;
    }
  }
  if (selectedScheme.value.type !== 2) {
    const rainfallList = data.rainfalls;
    // 提取 intensity 值
    const rainValues = rainfallList.map((r) => r.intensity);
    const minRain = Math.min(...rainValues);
    const maxRain = Math.max(...rainValues);
    const avgRain =
      rainValues.reduce((sum, val) => sum + val, 0) / rainValues.length;
    // 更新 detailList 中的“最大雨强”和“平均雨强”
    detailList.value[0].value = maxRain.toFixed(2) + " mm/h"; // 最大雨强
    detailList.value[1].value = avgRain.toFixed(2) + " mm/h"; // 平均雨强
    console.log(
      "当前方案下最小雨量、最大雨量、平均雨量:",
      minRain.toFixed(2),
      maxRain.toFixed(2),
      avgRain.toFixed(2)
    );
  }
};
const closeMsg = () => {
  // 使用 defineEmits 定义 emit
  emit("close");
};
const showMsg = () => {
  emit("open");
};
// 定义 emit
const emit = defineEmits(["close", "open"]);
// 生命周期钩子
onMounted(() => {
  console.log(filteredData);
  updateThreatData();
  getRainfallData();
});
</script>
<style lang="less" scoped>
.detail {
  background: url("@/assets/img/tools/messagebg.png");
@@ -133,7 +174,6 @@
  left: 20px;
  font-weight: 700;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  line-height: 40px;
  width: 270px;
@@ -148,11 +188,8 @@
  height: 20px;
  text-align: center;
  line-height: 20px;
  text-align: center;
  font-weight: 700;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  cursor: pointer;
}
src/utils/water.js
@@ -47,7 +47,7 @@
  ];
  const levelCount = colorStops.length;
  const minAllowed = 0.005; // 最小允许值
  const minAllowed = 0.05; // 最小允许值
  const threshold = 1; // 小值与大值分界点
  let effectiveMin = Math.max(minFlowRate, minAllowed); // 最小不能小于 0.01
src/views/left/CitySim.vue
@@ -447,15 +447,16 @@
/**
 * 数据处理主函数
 * @param {Array} data - 解析后的原始数据数组,每个元素是一个对象
 */
const processData = (data) => {
  // 检查空数据
  // 检查是否为空数据
  if (data.length === 0) {
    ElMessage.warning("文件内容为空!");
    return;
  }
  // 匹配字段名
  // 匹配列名(例如“时间”、“小时雨强”)
  const columns = matchColumns(data[0]);
  // 校验必要字段是否存在
@@ -475,22 +476,18 @@
    return;
  }
  // 时间列校验是否升序
  // 校验时间列是否升序排列
  if (!isTimeColumnSorted(data, columns.time)) {
    ElMessage.error("时间列必须按升序排列!");
    forms.fileList = [];
    return;
  }
  // 提取单位
  forms.intensityUnit = extractUnitFromHeader(columns.intensity);
  // 提取单位(如 mm/h),若没有则设为空字符串
  forms.intensityUnit = extractUnitFromHeader(columns.intensity) || "";
  if (!forms.intensityUnit) {
    forms.intensityUnit = "";
  }
  // 转换 key 名并转换数值类型
  forms.rainFallList = data.map((row) => ({
  // 将原始数据转换为统一结构的对象数组
  const rawRainFallList = data.map((row) => ({
    time: row[columns.time],
    intensity: parseFloat(row[columns.intensity]),
    total: columns.totalRainfall
@@ -498,21 +495,45 @@
      : undefined,
  }));
  console.log(forms.rainFallList, "解析后的降雨数据");
  console.log(rawRainFallList, "原始降雨数据");
  // 计算统计信息
  const firstTime = parseDateTime(data[0][columns.time]);
  const lastTime = parseDateTime(data[data.length - 1][columns.time]);
  // 判断是否为整小时数据(即相邻时间间隔是否为整小时)
  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,
    }));
  }
  // 更新 forms.rainFallList,可用于图表显示等用途
  forms.rainFallList = rawRainFallList;
  // 计算起始时间和结束时间(毫秒数)
  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); // 小时
  forms.duration = (durationSeconds / 3600).toFixed(2); // 单位:小时
  // 找出最大小时雨强
  const maxIntensity = Math.max(
    ...data
      .map((row) => parseFloat(row[columns.intensity]))
      .filter((v) => !isNaN(v))
    ...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);
@@ -521,56 +542,80 @@
  }
};
// // 处理数据
// const processData = (data) => {
//   // 1. 检查数据是否为空
//   if (data.length === 0) {
//     ElMessage.warning("文件内容为空!");
//     return;
//   }
/**
 * 检查数据是否为整小时记录
 * @param {Array} rainList - 原始降雨数据列表,每个元素包含 time 和 intensity
 * @returns {boolean} - 是否为整小时数据
 */
function checkIfHourlyData(rainList) {
  if (rainList.length < 2) return true; // 只有一个点,默认视为整小时数据
//   // 2. 获取表头(第一列是时间列)
//   const tableColumns = Object.keys(data[0]);
//   const timeColumn = tableColumns[0]; // 假设第一列是时间
  for (let i = 1; i < rainList.length; i++) {
    // 解析两个相邻时间点
    const time1 = parseDateTime(rainList[i - 1].time);
    const time2 = parseDateTime(rainList[i].time);
//   // 3. 校验时间列是否按升序排列
//   if (!isTimeColumnSorted(data, timeColumn)) {
//     ElMessage.error("时间列必须按升序排列!");
//     forms.fileList = [];
//     return; // 终止处理
//   }
    // 计算时间差(分钟)
    const diffMinutes = Math.abs(time2 - time1) / (1000 * 60);
//   const intensityColumn = tableColumns[1]; // 雨强列(如 "小时雨强(mm/h)")
//   // console.log(intensityColumn, "intensityColumnintensityColumnintensityColumn");
//   // 3. 提取第二列的单位(如 "(mm/h)" → "mm/h")
//   const intensityUnit = extractUnitFromHeader(intensityColumn);
//   forms.intensityUnit = intensityUnit; // 存储单位(可选)
//   console.log(forms.intensityUnit,'aaaaaaaaaaaaaaaaaaaaa')
    // 如果时间差不是整小时(不能被60整除),则不是整小时数据
    if (diffMinutes % 60 !== 0) {
      return false;
    }
  }
//   // 4. 如果校验通过,继续处理数据
//   forms.rainFallList = transformKeys(data);
//   console.log(forms.rainFallList, "data");
  return true;
}
//   // 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;
//     })
//   ).toFixed(2);
//   forms.intensity = maxValue;
/**
 * 将任意时间粒度的雨强数据,按小时聚合为“小时雨强”
 * @param {Array} rainList - 原始数据列表,每个元素包含 time 和 intensity
 * @returns {Array} - 按小时分组的聚合结果
 */
function aggregateToHourlyRainfall(rainList) {
  const grouped = {}; // 用于临时存储每个小时的数据
//   const lastValue = data[data.length - 1][tableColumns[2]];
//   forms.rainfall = lastValue;
  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数字日期