guonan
2025-06-24 c0b4517e7362144cc1683ee0bf0b7e00b67d539a
提交初版实时模拟
已修改3个文件
527 ■■■■■ 文件已修改
src/components/menu/TimeLine.vue 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/monifangzhen/schemeCard.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tools/DebuffDetail.vue 303 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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">
      <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>
@@ -143,13 +182,15 @@
const playbackRate = ref(1);
const playbackRates = ref([1, 2, 4, 8]);
const showSpeedMenu = ref(false);
const speedShow = ref(false);
const waterTimestamps = ref([]); // 存储时间轴数据
const timeMarkers = ref([]);
const timelineTrack = ref(null);
const isColorRenderEnabled = ref(false); // 假设这是你的颜色渲染开关状态
const isWaterPrimitiveCreated = ref(false);
let playInterval = null;
let rainTotalInfo = ([]);
let rainTotalInfo = [];
const isRainEnabled = ref(false);
const rainParams = reactive({
  rainSize: 0.5,
@@ -259,8 +300,11 @@
    currentTime.value = (nextTimestamp - baseTimestamp) / 1000;
    // 触发更新
    updateWaterColorByTime()
    updateWeatherByProgress();
    if (selectedScheme.value.type !== 2) {
      updateWaterColorByTime();
      updateWeatherByProgress();
    }
    const progress = currentTime.value / duration.value;
    emit("timeUpdate", progress * 100);
  }, 1000 / playbackRate.value); // 根据播放速率调整间隔
@@ -303,7 +347,7 @@
  const rainfallList = data.rainfalls;
  console.log("最终的 rainfallList:", rainfallList);
  rainTotalInfo.value = rainfallList
  rainTotalInfo.value = rainfallList;
  // 提取 intensity 值
  rainFallValues.value = rainfallList.map((r) => r.intensity);
@@ -373,7 +417,7 @@
    }
  }
  // 默认无雨状态
  return { name: "无雨", size: 0.3, speed: 10, density: 10, color: "#F0F8FF" };
}
// 根据播放进度更新天气效果(已优化)
@@ -393,20 +437,20 @@
  const nextTotal = nextData.total;
  const total = currentTotal + (nextTotal - currentTotal) * alpha;
  // 根据 total 设置颜色
  let color = '#D4F2E7'; // 默认蓝色
  let color = "#D4F2E7"; // 默认蓝色
  if (total >= 150) {
    color = '#663300'; // 黄 - 大雨
    color = "#663300"; // 黄 - 大雨
  } else if (total >= 125) {
    color = '#B26633'; // 黄绿 - 中雨
    color = "#B26633"; // 黄绿 - 中雨
  } else if (total >= 100) {
    color = '#CC9966'; // 绿 - 中雨
    color = "#CC9966"; // 绿 - 中雨
  } else if (total >= 75) {
    color = '#CCE5FF'; // 青绿 - 小雨
    color = "#CCE5FF"; // 青绿 - 小雨
  } else if (total >= 50) {
    color = '#99CCFF'; // 天蓝 - 小雨
    color = "#99CCFF"; // 天蓝 - 小雨
  } else if (total >= 25) {
    color = '#66B3FF'; // 浅蓝 - 微量
    color = "#66B3FF"; // 浅蓝 - 微量
  }
  // console.log(`当前 total: ${total.toFixed(2)}, 颜色: ${color}`);
  // updateWaterColor(color)
@@ -426,7 +470,11 @@
  // const rainValue = currentRain + (nextRain - currentRain) * alpha;
  const rainValue = currentRain + (nextRain - currentRain);
  // 打印当前处理的雨量数据
  console.log(`正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed(2)}, 索引=${index}`);
  console.log(
    `正在处理的雨量数据点: 当前=${currentRain}, 下一个=${nextRain}, 插值后=${rainValue.toFixed(
      2
    )}, 索引=${index}`
  );
  // 如果当前索引未变化且插值差异不大,跳过重复更新
  if (index === lastUsedIndex && Math.abs(rainValue - lastRainValue) < 0.1) {
    // console.log('由于数据无显著变化,跳过本次更新');
@@ -612,9 +660,15 @@
    // 当前方案的所有信息
    const schemeInfo = selectedScheme.value;
    serviceInfo = schemeInfo.serviceName;
    if (selectedScheme.value.type == 2) {
      speedShow.value = false;
    } else {
      getRainfallData();
      speedShow.value = true;
    }
    // console.log('获取到的 serviceName:', serviceInfo);
    getRainfallData();
    // 根据layer.json去获取时间轴信息
    const {
      waterTimestamps: timestamps,
src/components/monifangzhen/schemeCard.vue
@@ -16,10 +16,6 @@
          <span style="color: aquamarine">
            {{ item.result === "-1" ? "出错" : item.result || "创建仿真" }}
          </span>
          <!-- <span style="color: aquamarine">{{ item.result || "创建仿真" }}</span> -->
          <!-- <span style="color: aquamarine">{{
            statusText[item.status] || "未知"
          }}</span> -->
        </p>
      </div>
      <div class="cardMenu">
@@ -37,10 +33,7 @@
      :selectedScheme="currentScheme"
      @back="handleBack"
    />
    <flowRateTab
    v-if="schemeInfoShow">
      123
    </flowRateTab>
    <flowRateTab v-if="schemeInfoShow"> 123 </flowRateTab>
  </div>
  <Message
    @close="close"
@@ -81,13 +74,6 @@
  selectedId.value = id;
}
const statusText = {
  0: "创建仿真",
  1: "预处理",
  2: "分析中",
  10: "完成",
  20: "出错",
};
function formatTime(time) {
  return dayjs(time).format("YYYY-MM-DD HH:mm:ss");
@@ -108,62 +94,80 @@
  messageShow.value = false;
}
function startPlay(item) {
  // 分析中
  if (item.status == 2) {
async function startPlay(item) {
  if (item.status === 2) {
    ElMessage.warning("当前方案正在分析中,无法进入模拟!");
    return;
  }
  // 出错
  if (item.status == 20) {
  if (item.status === 20) {
    ElMessage.error("当前方案分析出错,请重新新建方案!");
    return;
  }
  // 调用求解器并拿到最新生成的serviceName
  // 新创建的方案没有状态以及serviceName则执行调用求解器
  if (!item.status && !item.serviceName) {
    getSimStart(item.id).then((res) => {
      getSimDataById(item.id).then((res) => {
        item.serviceName = res.data[0].serviceName;
        simStore.setSelectedScheme(item);
        console.log(item, "无服务名称");
        ElMessage.warning("当前方案正在分析中,请稍后再模拟");
        getScheme();
      });
    });
  } else {
  // 如果是已完成的方案(status == 10)
  if (item.status === 10) {
    const flyHeight = item.areaType === 1 ? 100000 : 50000;
    simStore.setSelectedScheme(item);
    console.log("有服务名称");
  }
  const flyHeight = ref(100000);
  const shouldShowFill = false;
  // 求解器求解完成之后才可以显示时间轴
  if (item.status == 10) {
    // 只有行政区划执行
    if (item.areaType == 1) {
      flyHeight.value = 100000;
    if (item.areaType === 1) {
      EventBus.emit("select-geom", {
        geom: item.geom,
        flyHeight: flyHeight.value,
        shouldShowFill: shouldShowFill,
        flyHeight,
        shouldShowFill: false,
      });
    } else {
      // 孙胡沟区域跳转视角
      initeWaterPrimitiveView();
    }
    currentScheme.value = item;
    schemeInfoShow.value = true;
    emit("closeBtn", false);
    emit("start");
    return;
  }
  // 调用求解器(不在实时模拟的情况下)
  if (!item.status && !item.serviceName && item.type !== 2) {
    try {
      await getSimStart(item.id);
      const res = await getSimDataById(item.id);
      item.serviceName = res.data[0]?.serviceName || null;
      simStore.setSelectedScheme(item);
      ElMessage.warning("当前方案正在分析中,请稍后再模拟");
      getScheme();
    } catch (e) {
      console.error("获取模拟数据失败:", e);
    }
    return;
  }
  // 实时模拟
  if (item.type === 2) {
    try {
      // 实时模拟调用求解器会直接在接口中返回结果
      const ress = await getSimStart(item.id);
      const res = await getSimDataById(item.id);
      item.serviceName = res.data[0]?.serviceName || null;
      simStore.setSelectedScheme(item);
      getScheme();
      if (ress.code === 200) {
        initeWaterPrimitiveView();
        emit("start");
      }
    } catch (e) {
      console.error("实时模拟获取模拟数据失败:", e);
    }
    return;
  }
  // 默认情况:有服务名称
  simStore.setSelectedScheme(item);
}
function endPlay() {
  emit("end");
}
function handleBack(value) {
  if (value === false) {
src/components/tools/DebuffDetail.vue
@@ -1,15 +1,15 @@
<template>
    <div class="detail">
        <div class="detail-top">统计分析结果</div>
        <!-- <div class="detail-btn" @click="showMsg">查看详情</div> -->
        <div class="detail-close" @click="closeMsg"></div>
        <div class="detail-context">
            <div v-for="(item, key) in detailList" :key="key" class="detail-item">
                <div class="detail-name">{{ item.name }}</div>
                <div class="detail-value">{{ item.value }}</div>
            </div>
        </div>
    </div>
  <div class="detail">
    <div class="detail-top">统计分析结果</div>
    <!-- <div class="detail-btn" @click="showMsg">查看详情</div> -->
    <div class="detail-close" @click="closeMsg"></div>
    <div class="detail-context">
      <div v-for="(item, key) in detailList" :key="key" class="detail-item">
        <div class="detail-name">{{ item.name }}</div>
        <div class="detail-value">{{ item.value }}</div>
      </div>
    </div>
  </div>
</template>
<script>
@@ -20,171 +20,176 @@
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 rainfallList = data.rainfalls;
  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;
        }
      }
            // 提取 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;
      if (selectedScheme.value.type !== 2) {
        const rainfallList = data.rainfalls;
            // 更新 detailList 中的“最大雨强”和“平均雨强”
            this.detailList[0].value = maxRain.toFixed(2) + " mm/h";       // 最大雨强
            this.detailList[1].value = avgRain.toFixed(2) + " mm/h";       // 平均雨强
        // 提取 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;
            console.log('当前方案下最小雨量、最大雨量、平均雨量:',
                minRain.toFixed(2),
                maxRain.toFixed(2),
                avgRain.toFixed(2)
            );
        },
        closeMsg() {
            this.$emit("close")
        },
        showMsg() {
            this.$emit("open")
        },
    },
}
        // 更新 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");
    },
  },
};
</script>
<style lang="less" scoped>
.detail {
    background: url("@/assets/img/tools/messagebg.png");
    background-size: 100% 100%;
    width: 391px;
    height: 420px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 99;
  background: url("@/assets/img/tools/messagebg.png");
  background-size: 100% 100%;
  width: 391px;
  height: 420px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 99;
}
.detail-top {
    position: absolute;
    top: 5px;
    left: 20px;
    font-weight: 700;
    font-size: 18px;
    font-weight: 700;
    color: #fff;
    line-height: 40px;
    width: 270px;
    cursor: pointer;
  position: absolute;
  top: 5px;
  left: 20px;
  font-weight: 700;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  line-height: 40px;
  width: 270px;
  cursor: pointer;
}
.detail-close {
    position: absolute;
    right: 3px;
    top: 10px;
    width: 20px;
    height: 20px;
    text-align: center;
    line-height: 20px;
    text-align: center;
  position: absolute;
  right: 3px;
  top: 10px;
  width: 20px;
  height: 20px;
  text-align: center;
  line-height: 20px;
  text-align: center;
    font-weight: 700;
    font-size: 18px;
    font-weight: 700;
    color: #fff;
    cursor: pointer;
  font-weight: 700;
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  cursor: pointer;
}
.detail-context {
    position: absolute;
    top: 40px;
    left: 20px;
    width: 350px;
  position: absolute;
  top: 40px;
  left: 20px;
  width: 350px;
}
.detail-item {
    height: 23px;
    margin-top: 15px;
    margin-left: 10px;
  height: 23px;
  margin-top: 15px;
  margin-left: 10px;
}
.detail-name {
    float: left;
    font-weight: 700;
    color: #94e0c4;
  float: left;
  font-weight: 700;
  color: #94e0c4;
}
.detail-value {
    float: left;
    color: #e1eee9;
  float: left;
  color: #e1eee9;
}
.detail-btn {
    background: url("@/assets/img/tools/messagebtn.png") no-repeat;
    position: absolute;
    bottom: 60px;
    right: 60px;
    width: 105px;
    height: 26px;
    text-align: center;
    color: #fff;
    cursor: pointer;
  background: url("@/assets/img/tools/messagebtn.png") no-repeat;
  position: absolute;
  bottom: 60px;
  right: 60px;
  width: 105px;
  height: 26px;
  text-align: center;
  color: #fff;
  cursor: pointer;
}
</style>