fa541dda36e58de1d491b3ff4073c51b16606515..1119a7837323e052d3e6256cddd3283d919bd959
2025-06-26 guonan
提交
1119a7 对比 | 目录
2025-06-26 guonan
1
48fc6f 对比 | 目录
2025-06-26 guonan
实时模拟
4da567 对比 | 目录
已修改7个文件
480 ■■■■ 文件已修改
src/api/hpApi.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/requestTR.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/menu/TimeLine.vue 302 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeCard.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/simulation.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Home.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/left/KGSimOption/RealTimeSimulation.vue 121 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hpApi.js
@@ -235,11 +235,17 @@
}
// 查询孙胡沟避险场所
export async function getSafePoint() {
export async function getSafePoint(data) {
  const response = await axios.get("/hp/safeHavenLocation/getDataSelect", {
    params: {
      divisionId: "110116110218"
      divisionId: data
    }
  });
  return response.data;
}
// 查询北京市
export async function getAllCode() {
  const response = await axios.get("/hp/district/getAll");
  return response.data;
}
src/api/requestTR.js
@@ -14,7 +14,7 @@
// 创建 Axios 实例
const instance = axios.create({
  baseURL: API_URL,
  timeout: 1000 * 60, // 请求超时时间(单位:毫秒)
  // timeout: 1000 * 60, // 请求超时时间(单位:毫秒)
  headers: {
    "Content-Type": "application/x-www-form-urlencoded", // 默认请求头
  },
src/components/menu/TimeLine.vue
@@ -2,20 +2,30 @@
  <div class="timeline-container">
    <div class="controls">
      <div class="control-btn" @click="skipBackward">
        <img src="@/assets/img/timeline/left.png" class="fas fa-step-backward" />
        <img
          src="@/assets/img/timeline/left.png"
          class="fas fa-step-backward"
        />
      </div>
      <div class="control-btn play-btn" @click="togglePlay">
        <img v-show="isPlaying" src="@/assets/img/timeline/stop.png" />
        <img v-show="!isPlaying" src="@/assets/img/timeline/start.png" />
      </div>
      <div class="control-btn" @click="skipForward">
        <img src="@/assets/img/timeline/right.png" class="fas fa-step-forward" />
        <img
          src="@/assets/img/timeline/right.png"
          class="fas fa-step-forward"
        />
      </div>
      <div class="speed-control" v-show="speedShow">
        <div @click="toggleSpeedMenu">{{ playbackRate }}X</div>
        <div class="speed-menu" v-show="showSpeedMenu">
          <div v-for="rate in playbackRates" :key="rate" @click.capture="setPlaybackRate(rate)"
            :class="{ active: playbackRate === rate }">
          <div
            v-for="rate in playbackRates"
            :key="rate"
            @click.capture="setPlaybackRate(rate)"
            :class="{ active: playbackRate === rate }"
          >
            {{ rate }}X
          </div>
        </div>
@@ -25,19 +35,33 @@
    <div class="timeline">
      <div class="dates">
        <div class="current-date">当前播放时间:{{ currentPlayingTime }}</div>
        <div v-for="(date, index) in visibleDates" :key="index" class="date-label">
        <div
          v-for="(date, index) in visibleDates"
          :key="index"
          class="date-label"
        >
          <!-- {{ formatDate(date) }} -->
        </div>
        <div>
          专题渲染:
          <el-switch v-model="isColorRenderEnabled" @change="handleColorRenderChange" style="margin-top: -3px"
            :disabled="!isPlaying || !isWaterPrimitiveCreated" />
          <el-switch
            v-model="isColorRenderEnabled"
            @change="handleColorRenderChange"
            style="margin-top: -3px"
            :disabled="!isPlaying || !isWaterPrimitiveCreated"
          />
          <!-- active-text="开" inactive-text="关" -->
        </div>
      </div>
      <div class="timeline-track" ref="timelineTrack" @click="seekToPosition">
        <div class="timeline-progress" :style="{ width: progressPercentage + '%' }"></div>
        <div class="timeline-cursor" :style="{ left: progressPercentage + '%' }"></div>
        <div
          class="timeline-progress"
          :style="{ width: progressPercentage + '%' }"
        ></div>
        <div
          class="timeline-cursor"
          :style="{ left: progressPercentage + '%' }"
        ></div>
        <div class="scale-markers">
          <div class="scale-marker" style="left: 0%"></div>
          <div class="scale-marker" style="left: 25%"></div>
@@ -46,8 +70,12 @@
          <div class="scale-marker" style="left: 100%"></div>
        </div>
        <div class="time-markers">
          <div v-for="(time, index) in timeMarkers" :key="index" class="time-marker"
            :style="{ left: `${index * 25}%`, transform: 'translateX(-50%)' }">
          <div
            v-for="(time, index) in timeMarkers"
            :key="index"
            class="time-marker"
            :style="{ left: `${index * 25}%`, transform: 'translateX(-50%)' }"
          >
            <div class="date-part">{{ time.split(" ")[0] }}</div>
            <div class="time-part">{{ time.split(" ")[1] }}</div>
          </div>
@@ -56,27 +84,38 @@
    </div>
    <div>
      <div style="display: flex">
        <ratelevel ref="ratelevelRef" :playing-time="sendCurrentPlayingTime"
          @finish-calculation="handleFinishCalculation" style="
        <ratelevel
          ref="ratelevelRef"
          :playing-time="sendCurrentPlayingTime"
          @finish-calculation="handleFinishCalculation"
          style="
            margin-top: 12px;
            margin-left: 28px;
            margin-right: 10px;
            justify-content: flex-end;
          " />
        <crossanalysis ref="crossRef" style="
          "
        />
        <crossanalysis
          ref="crossRef"
          style="
            margin-top: 12px;
            margin-left: 16px;
            margin-right: 20px;
            justify-content: flex-end;
          " />
          "
        />
      </div>
      <el-button @click="handleBack" style="
      <el-button
        @click="handleBack"
        style="
          margin-top: 3px;
          margin-left: 28px;
          margin-right: 10px;
          width: 75%;
          height: 30%;
        ">结束模拟</el-button>
        "
        >结束模拟</el-button
      >
    </div>
  </div>
</template>
@@ -91,6 +130,7 @@
  onBeforeUnmount,
  inject,
  reactive,
  watchEffect,
} from "vue";
import ratelevel from "@/components/menu/flowRate_waterLevel.vue";
import crossanalysis from "@/components/menu/CrossSectionalAnalysis.vue";
@@ -174,6 +214,9 @@
    new Set(waterTimestamps.value.map((ts) => dayjs(ts).format("YYYY-MM-DD")))
  ).map((date) => dayjs(date).toDate())
);
// 我需要加一个判断
const finishPlay = ref(false);
// 播放控制
const togglePlay = () => {
  // 这里应该再设定几个限制,如果缺少什么数据,无法进行仿真
@@ -241,38 +284,96 @@
  8: 125, // 8倍速
};
// 播放逻辑
// const startPlayback = () => {
//   clearInterval(playInterval);
//   playInterval = setInterval(() => {
//     // 找到当前时间对应的索引
//     const currentIndex = findClosestTimestampIndex(currentTime.value);
//     const nextIndex = currentIndex + 1;
//     // 如果已经是最后一个时间点,停止播放
//     if (nextIndex >= waterTimestamps.value.length) {
//       currentTime.value = duration.value;
//       stopPlayback();
//       isPlaying.value = false;
//       emit("isPlaying", false);
//       emit("playbackFinished", true);
//       return;
//     }
//     // 更新时间为下一个时间点的时间差(秒)
//     const nextTimestamp = waterTimestamps.value[nextIndex];
//     const baseTimestamp = waterTimestamps.value[0];
//     currentTime.value = (nextTimestamp - baseTimestamp) / 1000;
//     // 触发更新
//     if (selectedScheme.value.type !== 2) {
//       updateWaterColorByTime();
//       updateWeatherByProgress();
//     }
//     const progress = currentTime.value / duration.value;
//     emit("timeUpdate", progress * 100);
//   }, 1000 / playbackRate.value); // 根据播放速率调整间隔
// };
const startPlayback = () => {
  clearInterval(playInterval);
  playInterval = setInterval(() => {
    // 找到当前时间对应的索引
    const currentIndex = findClosestTimestampIndex(currentTime.value);
    const nextIndex = currentIndex + 1;
  if (selectedScheme.value.type === 2) {
    // 类型为 2:每 5 秒跳动一次
    playInterval = setInterval(() => {
      const fiveSeconds = 5;
      const totalDuration = duration.value; // 总时长(秒)
    // 如果已经是最后一个时间点,停止播放
    if (nextIndex >= waterTimestamps.value.length) {
      currentTime.value = duration.value;
      stopPlayback();
      isPlaying.value = false;
      emit("isPlaying", false);
      emit("playbackFinished", true);
      return;
    }
      currentTime.value += fiveSeconds;
    // 更新时间为下一个时间点的时间差(秒)
    const nextTimestamp = waterTimestamps.value[nextIndex];
    const baseTimestamp = waterTimestamps.value[0];
    currentTime.value = (nextTimestamp - baseTimestamp) / 1000;
      if (currentTime.value >= totalDuration) {
        currentTime.value = totalDuration;
        stopPlayback();
        isPlaying.value = false;
        finishPlay.value = true;
        emit("isPlaying", false);
        emit("playbackFinished", true);
        return;
      }
    // 触发更新
    if (selectedScheme.value.type !== 2) {
      // 触发进度更新
      const progress = currentTime.value / totalDuration;
      emit("timeUpdate", progress * 100);
      // 如果需要触发某些更新函数,也可以保留
      updateWaterColorByTime();
      updateWeatherByProgress();
    }
    }, 5000); // 每 5 秒执行一次
  } else {
    // 这里面还是你的播放代码,上面的if中是五秒钟跳动一次的实时模拟
    playInterval = setInterval(() => {
      const currentIndex = findClosestTimestampIndex(currentTime.value);
      const nextIndex = currentIndex + 1;
    const progress = currentTime.value / duration.value;
    emit("timeUpdate", progress * 100);
  }, 1000 / playbackRate.value); // 根据播放速率调整间隔
      if (nextIndex >= waterTimestamps.value.length) {
        currentTime.value = duration.value;
        stopPlayback();
        isPlaying.value = false;
        emit("isPlaying", false);
        emit("playbackFinished", true);
        return;
      }
      const nextTimestamp = waterTimestamps.value[nextIndex];
      const baseTimestamp = waterTimestamps.value[0];
      currentTime.value = (nextTimestamp - baseTimestamp) / 1000;
      if (selectedScheme.value.type !== 2) {
        updateWaterColorByTime();
        updateWeatherByProgress();
      }
      const progress = currentTime.value / duration.value;
      emit("timeUpdate", progress * 100);
    }, 1000 / playbackRate.value);
  }
};
// 降雨变化部分
// 降雨数据相关变量
@@ -428,10 +529,10 @@
}
// 全局状态记录
const colorState = {
  maxStage: 0,       // 记录历史最高阶段
  maxAlpha: -0.3,    // 记录历史最小透明度(负值)
  maxStage: 0, // 记录历史最高阶段
  maxAlpha: -0.3, // 记录历史最小透明度(负值)
  maxLuminance: 240.4, // 记录历史最低亮度(对应stage 0初始值)
  currentColor: "#F5F0E6" // 当前颜色
  currentColor: "#F5F0E6", // 当前颜色
};
function updateWaterColorByTime() {
@@ -448,16 +549,16 @@
    { hex: "#D4B483", luminance: 184.0 }, // stage 3
    { hex: "#B78B6A", luminance: 148.4 }, // stage 4
    { hex: "#8B5A3A", luminance: 101.0 }, // stage 5
    { hex: "#4A3123", luminance: 54.9 }   // stage 6
    { hex: "#4A3123", luminance: 54.9 }, // stage 6
  ];
  const alphaStops = [1
    -0.3,   // stage 0
    -0.4,   // stage 1
    -0.5,   // stage 2
    -0.6,   // stage 3
    -0.7,   // stage 4
    -0.75,  // stage 5
    -0.8    // stage 6
  const alphaStops = [
    1 - 0.3, // stage 0
    -0.4, // stage 1
    -0.5, // stage 2
    -0.6, // stage 3
    -0.7, // stage 4
    -0.75, // stage 5
    -0.8, // stage 6
  ];
  // 3. 更新阶段状态
  updateStageState(intensity, IR);
@@ -472,10 +573,15 @@
  function calculateRainData() {
    const initialTimestamp = new Date(rainTotalInfo.value[0].time).getTime();
    const currentTimestamp = new Date(
      rainTotalInfo.value[Math.min(
        Math.floor(currentTime.value / duration.value * (rainTotalInfo.value.length - 1)),
        rainTotalInfo.value.length - 2
      )].time
      rainTotalInfo.value[
        Math.min(
          Math.floor(
            (currentTime.value / duration.value) *
              (rainTotalInfo.value.length - 1)
          ),
          rainTotalInfo.value.length - 2
        )
      ].time
    ).getTime();
    // 降雨强度计算(带插值)
@@ -486,7 +592,8 @@
      index = rainTotalInfo.value.length - 2; // 防止 index+1 越界
    }
    const lerpAlpha = floatIndex - index;
    const intensity = rainTotalInfo.value[index].intensity * (1 - lerpAlpha) +
    const intensity =
      rainTotalInfo.value[index].intensity * (1 - lerpAlpha) +
      rainTotalInfo.value[index + 1].intensity * lerpAlpha;
    // 临界降雨强度计算
@@ -524,7 +631,13 @@
    const stageThresholds = [0, 0.2, 0.4, 0.6, 0.8, 1.0];
    const lowerThreshold = stageThresholds[colorState.maxStage - 1] * IR;
    const upperThreshold = stageThresholds[colorState.maxStage] * IR;
    const ratio = Math.min(1, Math.max(0, (intensity - lowerThreshold) / (upperThreshold - lowerThreshold)));
    const ratio = Math.min(
      1,
      Math.max(
        0,
        (intensity - lowerThreshold) / (upperThreshold - lowerThreshold)
      )
    );
    // 颜色插值
    const startColor = colorStops[colorState.maxStage];
@@ -538,22 +651,26 @@
      colorState.maxLuminance = newLuminance;
      colorState.maxAlpha = Math.min(
        colorState.maxAlpha,
        lerp(alphaStops[colorState.maxStage], alphaStops[colorState.maxStage + 1], ratio)
        lerp(
          alphaStops[colorState.maxStage],
          alphaStops[colorState.maxStage + 1],
          ratio
        )
      );
    }
    console.log(`阶段: ${colorState.maxStage} | 亮度: ${colorState.maxLuminance.toFixed(1)} | 颜色: ${colorState.currentColor}`);
    console.log(
      `阶段: ${colorState.maxStage} | 亮度: ${colorState.maxLuminance.toFixed(
        1
      )} | 颜色: ${colorState.currentColor}`
    );
  }
  // 颜色插值工具函数
  function lerpColor(c1, c2, t) {
    const [r1, g1, b1] = hexToRgb(c1);
    const [r2, g2, b2] = hexToRgb(c2);
    return rgbToHex(
      r1 + (r2 - r1) * t,
      g1 + (g2 - g1) * t,
      b1 + (b2 - b1) * t
    );
    return rgbToHex(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
  }
  function calculateLuminance(hex) {
@@ -567,7 +684,9 @@
  }
  function rgbToHex(r, g, b) {
    return `#${[r, g, b].map(x => Math.round(x).toString(16).padStart(2, '0')).join('')}`;
    return `#${[r, g, b]
      .map((x) => Math.round(x).toString(16).padStart(2, "0"))
      .join("")}`;
  }
  function lerp(a, b, t) {
@@ -774,16 +893,17 @@
  { immediate: true }
);
onMounted(async () => {
const jsonFetch = ref(null);
// 提取为独立函数
async function initializeSimulationData(force = false) {
  try {
    // 当前方案的所有信息
    const schemeInfo = selectedScheme.value;
    const jsonFetch = ref(null);
    serviceInfo = schemeInfo.serviceName;
    if (selectedScheme.value.type == 2) {
    if (schemeInfo.type == 2) {
      speedShow.value = false;
      jsonFetch.value = layerDate.value;
      // serviceInfo = layerDate.value;
    } else {
      getRainfallData();
      speedShow.value = true;
@@ -792,19 +912,20 @@
    // console.log('获取到的 serviceName:', serviceInfo);
    // 根据layer.json去获取时间轴信息
    // 根据 layer.json 获取时间轴信息
    const {
      waterTimestamps: timestamps,
      watersMaxHeight,
      watersMinHeight,
    } = await fetchWaterSimulationData(serviceInfo, jsonFetch.value);
    console.log(
      "当前方案下的最大水位深度和最小水位深度",
      watersMaxHeight,
      watersMinHeight
    );
    // 现在是按照总共有多少个点来渲染时间轴
    // 更新时间轴相关数据
    if (timestamps) {
      frameNum.value = timestamps.length;
      waterTimestamps.value = timestamps;
@@ -815,6 +936,7 @@
        "YYYY-MM-DD HH:mm:ss"
      );
    }
    minFlowRate = watersMinHeight;
    maxFlowRate = watersMaxHeight;
  } catch (error) {
@@ -824,7 +946,36 @@
      type: "warning",
    });
  }
}
// 挂载时调用
onMounted(async () => {
  // 因为这个函数实时模拟监听也需要使用,所以封装了一个函数
  await initializeSimulationData();
});
const shouldAutoPlay = ref(false);
// 监听 layerDate 变化后标记准备播放
watch(
  () => layerDate.value,
  async (newVal) => {
    if (selectedScheme.value.type === 2 && newVal) {
      shouldAutoPlay.value = true;
    }
  },
  { deep: true }
);
// 等待 finishPlay 成功后再播放
watchEffect(() => {
  if (shouldAutoPlay.value && finishPlay.value && !isPlaying.value) {
    initializeSimulationData();
    togglePlay();
    shouldAutoPlay.value = false;
  }
});
// 根据返回数据的个数去渲染时间轴
function updateTimelineRange() {
  if (waterTimestamps.value.length > 0) {
@@ -844,6 +995,9 @@
const { endSimulate } = inject("simulateActions");
function handleBack() {
  endSimulate();
  // 停止实时模拟定时器
  EventBus.emit("close-time");
  isWaterPrimitiveCreated.value = false;
  if (ratelevelRef.value) {
    ratelevelRef.value.endCalculation();
src/components/monifangzhen/schemeCard.vue
@@ -23,7 +23,12 @@
          <el-button size="small" @click="setSchemClick(item)"
            >方案详情</el-button
          >
          <el-button size="small" @click="startPlay(item)">进入模拟</el-button>
          <el-button
            size="small"
            v-show="item.type !== 2"
            @click="startPlay(item)"
            >进入模拟</el-button
          >
          <!--  :disabled="item.status !== 2" -->
        </div>
      </div>
@@ -97,6 +102,7 @@
const realTimeSimInterval = ref(null);
async function startPlay(item) {
  console.log(item, "item");
  if (item.status === 2) {
    ElMessage.warning("当前方案正在分析中,无法进入模拟!");
    return;
@@ -172,7 +178,6 @@
  try {
    const ress = await getSimStart(item.id);
    console.log(ress, "resssssssss");
    const res = await getSimDataById(item.id);
    item.serviceName = res.data[0]?.serviceName || null;
@@ -181,7 +186,6 @@
    if (ress.code === 200) {
      simStore.layerDate = ress.data;
      console.log(simStore.layerDate,'aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb')
      initeWaterPrimitiveView();
      emit("start");
    }
src/store/simulation.js
@@ -2,6 +2,9 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useSimStore = defineStore('simulation', () => {
    // 北京市所有村的code
    const townCodeAll = ref([])
    // 实时模拟最新的layer
    const layerDate = ref("")
    // 帧数
    const frameNum = ref(0)
src/views/Home.vue
@@ -58,7 +58,7 @@
// import ResultAssess from "@/components/monifangzhen/ResultAssess.vue";
// import DangerAssess from "@/components/monifangzhen/DangerAssess.vue";
import { showDeviceDetail } from "@/store";
import { setupTokenRefresh, getDangerPoint } from "@/api/hpApi.js";
import { setupTokenRefresh, getDangerPoint, getAllCode } from "@/api/hpApi.js";
import { convertToWKT } from "@/utils/wktUtils";
import { getDeviceInfoSHG, getSafePoint } from "@/api/hpApi";
@@ -100,7 +100,37 @@
// 计算属性
const showDetail = computed(() => showDeviceDetail.value);
function extractAllChildrenInfoUnique(dataArray) {
  const map = new Map();
  function traverse(nodes) {
    if (!Array.isArray(nodes)) return;
    for (const node of nodes) {
      const key = node.code;
      // 检查是否是最后一层(没有子节点或子节点为空数组)
      if (!node.children || node.children.length === 0) {
        if (key && !map.has(key)) {
          map.set(key, { name: node.nameChn, code: key });
        }
      } else {
        // 如果有子节点,继续递归遍历
        traverse(node.children);
      }
    }
  }
  traverse(dataArray);
  return Array.from(map.values());
}
onMounted(async () => {
  getAllCode().then((res) => {
    // 北京市所有村以及街道code
    simStore.townCodeAll = extractAllChildrenInfoUnique(res.data[0].children);
    console.log(simStore.townCodeAll,'aaaaaaaaa')
  });
  setupTokenRefresh(); // 获取宏图token
  // getSimData(); //测试tr后端
  // 获取隐患点列表(因为中科软后端接口获取加载时间较长)
src/views/left/KGSimOption/RealTimeSimulation.vue
@@ -73,15 +73,20 @@
  inject,
  reactive,
  onMounted,
  onUnmounted,
} from "vue";
import { ElMessage } from "element-plus";
import { initeWaterPrimitiveView } from "@/utils/water";
import { SimAPIStore } from "@/store/simAPI";
import { useSimStore } from "@/store/simulation.js";
import { EventBus } from "@/eventBus"; // 引入事件总线
import { getDeviceInfoSHG, getYLJData } from "@/api/hpApi";
import { getSimStart, getSimDataById } from "@/api/trApi";
// 获取 Store 实例
const simStore = SimAPIStore();
const simAPIStore = SimAPIStore();
const simStore = useSimStore();
// 表单数据
const formData = reactive({
@@ -200,12 +205,10 @@
// 保存方案
const saveSim = async () => {
  try {
    getYLJData("1101160300070101").then((res) => {
      console.log(res, "resres");
    });
    // getYLJData("1101160300070101")
    updateSelectedGauges();
    formData.geom = props.selectedArea;
    await simStore.addSimCheme(formData);
    await simAPIStore.addSimCheme(formData);
    resetForm();
    EventBus.emit("close-selectArea");
  } catch (err) {}
@@ -214,24 +217,101 @@
// 注入模拟操作方法
const { startSimulate, endSimulate } = inject("simulateActions");
// 实时模拟定时器
let pollingInterval = null;
async function startPlay() {
  const selectedItems = filteredTableData.value.filter((item) => item.selected);
  if (selectedItems.length > 0) {
    console.log(
      "选中的项:",
      selectedItems.map((item) => item.name)
    );
  } else {
    console.log("未选中任何项");
  }
  console.log("当前选中的区域:", props.selectedArea);
  // 开始模拟前需要先保存方案
  updateSelectedGauges();
  formData.geom = props.selectedArea;
  await simStore.addSimCheme(formData);
  // 保存方案
  const resApi = await simAPIStore.addSimCheme(formData);
  const schemeId = resApi.data?.data?.id;
  if (!schemeId) {
    ElMessage.error("方案保存失败,未获取到有效 ID");
    return;
  }
  EventBus.emit("close-selectArea");
  initeWaterPrimitiveView();
  startSimulate();
  // 显示加载中提示
  const loadingMessage = ElMessage({
    type: "info",
    message: "正在启动模拟...",
    duration: 0,
    offset: 80,
  });
  try {
    // 调用求解器并初始化模拟
    const resStart = await getSimStart(schemeId);
    // 请求完成后关闭加载提示
    loadingMessage.close();
    if (resStart.code === 200) {
      const res = await getSimDataById(schemeId);
      simStore.setSelectedScheme(res.data[0]);
      simStore.layerDate = resStart.data;
      initeWaterPrimitiveView();
      try {
        startSimulate(); // 这里可能会报错
      } catch (error) {
        console.error("调用 startSimulate 出错:", error);
      }
      // 开始轮询任务:每 5 分钟调用一次 getSimStart 并更新方案数据
      startPolling(schemeId);
    } else {
      ElMessage.error(resStart.message || "调用求解器失败");
    }
  } catch (error) {
    loadingMessage.close();
    ElMessage.error("请求失败:" + (error.message || "未知错误"));
    console.error("调用 getSimStart 出错:", error);
  }
}
// 启动轮询函数
function startPolling(schemeId) {
  stopPolling(); // 避免重复启动
  pollingInterval = setInterval(async () => {
    try {
      const resStart = await getSimStart(schemeId);
      if (resStart.code === 200) {
        const res = await getSimDataById(schemeId);
        simStore.setSelectedScheme(res.data[0]); // 更新方案数据
        simStore.layerDate = resStart.data; // 更新 layer 数据
        console.log("轮询获取最新数据成功");
      } else {
        console.warn("轮询请求失败:", resStart.message);
      }
    } catch (error) {
      console.error("轮询请求异常:", error);
    }
  }, 5 * 60 * 1000); // 每 5 分钟执行一次
}
// 停止轮询函数
function stopPolling() {
  if (pollingInterval) {
    clearInterval(pollingInterval);
    pollingInterval = null;
    console.log("轮询已停止");
  }
}
EventBus.on("close-time", () => {
  stopPolling();
});
const toggleDetails = () => {
  isCollapsed.value = !isCollapsed.value;
@@ -240,6 +320,11 @@
const futurePredictions = () => {
  console.log("未来预测按钮被点击");
};
onUnmounted(() => {
  EventBus.off("close-time");
  stopPolling();
});
</script>
<style scoped>