wangjuncheng
2025-04-28 f54c45de2ec6dfadc825c064b75e6707513bd094
src/components/menu/TimeLine.vue
@@ -2,30 +2,20 @@
  <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 @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>
@@ -34,29 +24,18 @@
    <div class="timeline">
      <div class="dates">
        <div
          v-for="(date, index) in visibleDates"
          :key="index"
          class="date-label"
        >
          {{ formatDate(date) }}
        <div class="current-date">
          当前播放时间:{{ currentPlayingTime }}
        </div>
        <div v-for="(date, index) in visibleDates" :key="index" class="date-label">
          <!-- {{ formatDate(date) }} -->
        </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="time-markers">
          <div
            v-for="(time, index) in timeMarkers"
            :key="index"
            class="time-marker"
          >
          <div v-for="(time, index) in timeMarkers" :key="index" class="time-marker">
            {{ time }}
          </div>
        </div>
@@ -70,7 +49,6 @@
import {
  ref,
  computed,
  onUnmounted,
  onMounted,
  watch,
  defineProps,
@@ -79,11 +57,13 @@
} from "vue";
import dayjs from "dayjs";
import { createWaterPrimitive, destoryWaterPrimitive } from "@/utils/water";
import { getRainfall } from "@/api/index";
import { EventBus } from "@/eventBus"; // 引入事件总线
import { ElMessage } from 'element-plus'
import { fetchWaterSimulationData } from "@/api/trApi.js";
import { EventBus } from "@/eventBus";
import { ElMessage } from 'element-plus';
const emit = defineEmits(["timeUpdate", "isPlaying", "playbackFinished"]);
// 定义props
const props = defineProps({
  waterSimulateParams: {
    type: Object,
@@ -92,229 +72,145 @@
    }),
  },
});
// 响应式状态
const currentPlayingTime = ref(""); // 当前播放时间
const isPlaying = ref(false);
const playbackFinished = ref(true);
const currentTime = ref(0);
const duration = ref(86400); // 一天的秒数
const playbackRate = ref(1);
const duration = ref(60); // 一天的秒数
const playbackRate = ref(8);
const playbackRates = ref([1, 2, 4, 8]);
const showSpeedMenu = ref(false);
const timeMarkers = ref([
  "00:00",
  "03:19",
  "06:39",
  "09:59",
  "13:19",
  "16:39",
  "19:59",
  "23:19",
]);
const waterTimestamps = ref([]); // 存储时间轴数据
const timeMarkers = ref([]);
const timelineTrack = ref(null);
const startDate = computed(() => {
  return dayjs(props.waterSimulateParams.date[0]);
});
const endDate = computed(() => {
  return dayjs(props.waterSimulateParams.date[1]);
});
let playInterval = null;
// 计算属性
const progressPercentage = computed(() => {
  return (currentTime.value / duration.value) * 100;
});
const startDate = computed(() => dayjs(props.waterSimulateParams.date[0]));
const endDate = computed(() => dayjs(props.waterSimulateParams.date[1]));
const progressPercentage = computed(() => (currentTime.value / duration.value) * 100);
const visibleDates = computed(() =>
  Array.from(
    new Set(waterTimestamps.value.map((ts) => dayjs(ts).format("YYYY-MM-DD")))
  ).map((date) => dayjs(date).toDate())
);
const currentTimeFormatted = computed(() => formatTime(currentTime.value));
const visibleDates = computed(() => {
  // 生成时间轴上显示的日期
  const dates = [];
  const currentDateValue = dayjs(startDate.value);
  const endDateValue = dayjs(endDate.value);
  // let tempDate = currentDateValue
  // while (tempDate.isSame(endDateValue) || tempDate.isBefore(endDateValue)) {
  //   dates.push(tempDate.toDate())
  //   tempDate = tempDate.add(1, 'day')
  // }
  return [currentDateValue, endDateValue];
});
const currentTimeFormatted = computed(() => {
  return formatTime(currentTime.value);
});
// 播放控制
const togglePlay = () => {
  // 如果当前是停止状态且已经播放完毕,点击时重置时间
  if (!isPlaying.value && currentTime.value >= duration.value) {
    currentTime.value = 0;
    emit("timeUpdate", progressPercentage.value);
    // earthCtrl.environment.disableEffect("rain");
  }
  if (!isPlaying.value && currentTime.value >= duration.value) currentTime.value = 0;
  isPlaying.value = !isPlaying.value;
  emit("isPlaying", isPlaying.value);
  if (isPlaying.value) {
    startPlayback();
    // 如果是从头开始播放
    if (currentTime.value === 0) {
      emit("playbackFinished", false);
    }
  } else {
    stopPlayback();
  }
    if (currentTime.value === 0) emit("playbackFinished", false);
  } else stopPlayback();
};
const intervalMap = {
  1: 1000, // 1倍速
  2: 500,  // 2倍速
  4: 250,  // 4倍速
  8: 125,  // 8倍速
};
const startPlayback = () => {
  // earthCtrl.environment.showEffect("rain");
  // 根据当前倍速获取对应的 interval
  const interval = intervalMap[playbackRate.value] || 1000; // 默认为1000
  // 调用 createWaterPrimitive 并传递 interval
  createWaterPrimitive({ interval });
  clearInterval(playInterval);
  playInterval = setInterval(() => {
    currentTime.value += 600 * playbackRate.value;
    // emit("playbackFinished", true);
    // 计算每次增加的时间量
    const timeIncrement = playbackRate.value; // 倍速直接作为增量
    currentTime.value += timeIncrement;
    // 如果超过总时长,则停止播放
    if (currentTime.value >= duration.value) {
      emit("playbackFinished", false);
      currentTime.value = duration.value; // 停在最后一帧
      stopPlayback(); // 停止播放
      isPlaying.value = false; // 更新播放状态
      emit("isPlaying", isPlaying.value); // 通知播放状态变化
      emit("playbackFinished", true); // 通知播放完成
      emit("timeUpdate", progressPercentage.value); // 更新进度条位置
      stopPlayback();
      isPlaying.value = false;
      emit("isPlaying", false);
      emit("playbackFinished", true);
    }
    // 触发时间更新事件
    emit("timeUpdate", progressPercentage.value);
  }, 1000);
  }, 1000); // 每秒更新一次
};
const stopPlayback = () => {
  // earthCtrl.environment.disableEffect("rain");
  clearInterval(playInterval);
};
const skipForward = () => {
  // 向前跳转10分钟
  currentTime.value = Math.min(currentTime.value + 600, duration.value);
  emit("timeUpdate", progressPercentage.value);
};
const skipBackward = () => {
  // 向后跳转10分钟
  currentTime.value = Math.max(currentTime.value - 600, 0);
  emit("timeUpdate", progressPercentage.value);
};
const toggleSpeedMenu = () => {
  showSpeedMenu.value = !showSpeedMenu.value;
};
const stopPlayback = () => clearInterval(playInterval);
const skipForward = () => (currentTime.value = Math.min(currentTime.value + 1, duration.value)); // 向前跳转1秒
const skipBackward = () => (currentTime.value = Math.max(currentTime.value - 1, 0)); // 向后跳转1秒
const toggleSpeedMenu = () => (showSpeedMenu.value = !showSpeedMenu.value);
const setPlaybackRate = (rate) => {
  playbackRate.value = rate;
  showSpeedMenu.value = false;
  if (isPlaying.value) {
    stopPlayback();
    startPlayback();
  }
  if (isPlaying.value) stopPlayback(), startPlayback(); // 如果正在播放,则重新启动以应用新的速率
};
const formatTime = (seconds) => {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);
  return `${hours.toString().padStart(2, "0")}:${minutes
    .toString()
    .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
};
const formatDate = (date) => {
  return dayjs(date).format("YYYY-MM-DD");
};
const seekToPosition = (event) => {
  const rect = timelineTrack.value.getBoundingClientRect();
  const clickPosition = event.clientX - rect.left;
  const percentage = clickPosition / rect.width;
  currentTime.value = percentage * duration.value;
  const percentage = (event.clientX - rect.left) / rect.width;
  currentTime.value = Math.round(percentage * duration.value);
  emit("timeUpdate", progressPercentage.value);
  if (waterTimestamps.value.length > 0) {
    const clickedTimestamp = dayjs(waterTimestamps.value[0]).add(currentTime.value, "second");
    console.log("Clicked timestamp:", clickedTimestamp.valueOf(), clickedTimestamp.format("YYYY-MM-DD HH:mm:ss"));
  }
};
const rainFallData = ref([]);
function getRainfallData() {
  getRainfall().then((res) => {
    // rainFallData.value = res.data.map(item => {
    //   return dayjs(item.time).format("HH:mm")
    // })
    // const rainfallData = res.data.map(item => {
    //   return item.rainfall
    // })
    rainFallData.value = res.data;
  });
}
let mockTimer = null;
let currentRainfall = ref(0.0001);
function randomMockWater() {
  let delay = (3 / playbackRate.value) * 1000;
  if (delay < 1000) {
    delay = 1000;
watch(() => currentTime.value, () => {
  if (waterTimestamps.value.length > 0) {
    currentPlayingTime.value = dayjs(waterTimestamps.value[0])
      .add(currentTime.value, "second")
      .format("YYYY-MM-DD mm:ss");
  }
  if (mockTimer) {
    clearImmediate(mockTimer);
    mockTimer = null;
  }
  mockTimer = setTimeout(() => {
    const rainfall = rainFallData.value.find(
      (item) =>
        dayjs(item.time).format("HH:mm:ss") == currentTimeFormatted.value
    );
    if (rainfall && rainfall.total) {
      // console.log(rainfall.total);
      createWaterPrimitive(rainfall.total / 50000);
    }
  }, delay);
}
watch(
  () => currentTime.value,
  () => {
    randomMockWater();
  }
);
// 定义组件事件
const emit = defineEmits(["timeUpdate", "isPlaying", "playbackFinished"]);
// 初始化时触发一次时间更新,确保父组件能获取初始时间
onMounted(() => {
  getRainfallData();
  emit("timeUpdate", progressPercentage.value);
});
// 时间标记生成
function generateTimeMarkers(timestamps) {
  if (!timestamps || timestamps.length === 0) return [];
  const sorted = [...timestamps].sort((a, b) => dayjs(a).diff(dayjs(b)));
  const interval = Math.floor(dayjs(sorted.at(-1)).diff(dayjs(sorted[0]), "second") / 7);
  return Array.from({ length: 8 }, (_, i) => dayjs(sorted[0]).add(i * interval, "second").format("mm:ss"));
}
watch(() => waterTimestamps.value, (newTimestamps) => {
  if (newTimestamps.length > 0) timeMarkers.value = generateTimeMarkers(newTimestamps);
}, { immediate: true });
onMounted(async () => {
  try {
    const { waterTimestamps: timestamps } = await fetchWaterSimulationData();
    if (timestamps) {
      waterTimestamps.value = timestamps;
      updateTimelineRange();
      timeMarkers.value = generateTimeMarkers(timestamps);
      currentPlayingTime.value = dayjs(timestamps[0]).format("YYYY-MM-DD HH:mm:ss");
    }
  } catch (error) {
    console.error("Error loading water simulation data:", error);
  }
});
function updateTimelineRange() {
  if (waterTimestamps.value.length > 0) {
    const [first, last] = [waterTimestamps.value[0], waterTimestamps.value.at(-1)];
    props.waterSimulateParams.date = [dayjs(first).toISOString(), dayjs(last).toISOString()];
    duration.value = dayjs(last).diff(dayjs(first), "second");
    console.log("Updated timeline range:", { ...props.waterSimulateParams, duration: duration.value });
  }
}
onBeforeUnmount(() => {
  stopPlayback();
  let delay = (3 / playbackRate.value) * 1000;
  setTimeout(() => {
    destoryWaterPrimitive();
  }, delay);
  destoryWaterPrimitive();
});
const { startSimulate, endSimulate } = inject("simulateActions");
// 返回按钮点击事件
const { endSimulate } = inject("simulateActions");
function handleBack() {
  ElMessage({
    message: '模拟进程正在关闭中...',
    type: 'success',
  })
  ElMessage({ message: '模拟进程正在关闭中...', type: 'success' }); // 显示消息通知用户模拟进程正在关闭
  endSimulate();
  destoryWaterPrimitive();
  EventBus.emit("hide-schemeInfo");
}
</script>
<style scoped>
.timeline-container {
  display: flex;
@@ -395,7 +291,7 @@
}
.speed-menu div {
  padding: 5px 10px;
  /* padding: 5px 5px; */
  text-align: center;
}
@@ -406,7 +302,7 @@
}
.timeline {
  margin-top: 20px;
  margin-top: 10px;
  position: relative;
  flex: 1;
}
@@ -460,8 +356,16 @@
}
.time-marker {
  margin-top: 5px;
  font-size: 12px;
  color: #fff;
  transform: translateX(-50%);
  transform: translateX(-20%);
}
.current-date {
  margin-bottom: 5px;
  font-size: 15px;
  color: #fff;
  transform: translateX(-3%);
}
</style>