소스 검색

时间轴组件(缺拖拽功能)

Hwf 11 달 전
부모
커밋
e2150a115c
4개의 변경된 파일374개의 추가작업 그리고 3개의 파일을 삭제
  1. 72 0
      src/components/Dialog/index2.vue
  2. 285 0
      src/components/TimeAxis/index.vue
  3. 12 2
      src/views/globalMap/LeftMenu.vue
  4. 5 1
      src/views/globalMap/index.vue

+ 72 - 0
src/components/Dialog/index2.vue

@@ -0,0 +1,72 @@
+<template>
+  <div v-if="modelValue" class="dialog-wrap">
+<!--    <div class="overlay" @click="closeDialog"></div>-->
+    <div class="dialog">
+      <div class="dialog-header">
+        <div class="dialog-title">{{ title }}</div>
+        <div class="icon-close" @click="closeDialog">
+          <el-icon size="20px"><Close /></el-icon>
+        </div>
+      </div>
+      <div class="dialog-content">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+interface Props {
+  modelValue: boolean;
+  title?: string;
+}
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: false
+});
+const emit = defineEmits(['update:modelValue', 'close']);
+
+// 关闭弹窗
+const closeDialog = () => {
+  emit('update:modelValue', false);
+  emit('close');
+};
+</script>
+
+<style lang="scss" scoped>
+.dialog-wrap {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 2000;
+  width: 780px;
+  height: 590px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  .dialog {
+    width: 780px;
+    height: 590px;
+    margin: 0 auto;
+    background-color: #fff;
+    border-radius: 10px;
+  }
+}
+.dialog {
+  padding: 0 15px;
+  .dialog-header {
+    width: 100%;
+    height: 35px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .icon-close {
+      cursor: pointer;
+    }
+  }
+  .dialog-content {
+    padding: 10px 0;
+  }
+}
+</style>

+ 285 - 0
src/components/TimeAxis/index.vue

@@ -0,0 +1,285 @@
+<template>
+  <div>
+    <div class="time-axis-container">
+      <div class="expand-btn" @click="changeExpand">{{ timeAxisState.expand ? '收起' : '展开' }}</div>
+      <div v-show="timeAxisState.expand" class="flex">
+        <div class="time-picker-box">
+          <el-date-picker
+            v-model="timeAxisState.startTime"
+            type="datetime"
+            :editable="false"
+            :format="timeAxisState.format"
+            :value-format="timeAxisState.format"
+            :clearable="false"
+          />
+          <el-date-picker
+            v-model="timeAxisState.endTime"
+            type="datetime"
+            :editable="false"
+            :format="timeAxisState.format"
+            :value-format="timeAxisState.format"
+            :clearable="false"
+          />
+        </div>
+        <div class="time-list-box">
+          <el-timeline>
+            <el-timeline-item
+              v-for="(activity, index) in timeAxisState.data"
+              :key="index"
+              :class="timeAxisState.activeIndex === index ? 'active' : ''"
+              :timestamp="activity.time"
+            />
+          </el-timeline>
+        </div>
+        <div class="time-tool">
+          <el-icon class="tool-icon" color="#8dc8f8" @click="toPrevTime"><DArrowLeft /></el-icon>
+          <el-icon class="tool-icon" color="#8dc8f8" @click="toPlayTime"><VideoPlay /></el-icon>
+          <el-icon class="tool-icon" color="#8dc8f8" @click="toNextTime(true)"><DArrowRight /></el-icon>
+          <div class="speed-btn" @click="changeSpeed">{{ timeAxisState.speed === 1 ? 'x1' : 'x2' }}</div>
+          <el-icon class="tool-icon" color="#8dc8f8" @click="dragMove"><Rank /></el-icon>
+        </div>
+      </div>
+    </div>
+    <Dialog v-model="timeAxisState.showDialog" title="气象图" @close="dialogClose">
+      <img v-show="timeAxisState.activeIndex > -1" :src="timeAxisState.data[timeAxisState.activeIndex]?.img" style="width: 100%" />
+    </Dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="TimeAxis">
+import { parseTime } from '@/utils/ruoyi';
+import Dialog from '@/components/Dialog/index2.vue';
+
+const timeAxisState = reactive({
+  expand: true,
+  format: 'YYYY-MM-DD HH:mm',
+  startTime: '',
+  endTime: '',
+  activeIndex: -1,
+  playing: false,
+  show: false,
+  showDialog: false,
+  speed: 1,
+  data: [
+    {
+      time: '01:45',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804174500000.JPG?v=1722793756557'
+    },
+    {
+      time: '02:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804181500000.JPG?v=1722795586679'
+    },
+    {
+      time: '03:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804191500000.JPG?v=1722799177342'
+    },
+    {
+      time: '04:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804201500000.JPG?v=1722802769950'
+    },
+    {
+      time: '05:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804211500000.JPG?v=1722806363130'
+    },
+    {
+      time: '06:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804221500000.JPG?v=1722809978069'
+    },
+    {
+      time: '07:15',
+      img: 'http://image.nmc.cn/product/2024/08/04/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240804231500000.JPG?v=1722813558520'
+    },
+    {
+      time: '08:15',
+      img: 'http://image.nmc.cn/product/2024/08/05/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240805001500000.JPG?v=1722817164321'
+    },
+    {
+      time: '09:15',
+      img: 'http://image.nmc.cn/product/2024/08/05/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240805011500000.JPG?v=1722820750729'
+    },
+    {
+      time: '10:15',
+      img: 'http://image.nmc.cn/product/2024/08/05/WXCL/medium/SEVP_NSMC_WXCL_ASC_E99_ACHN_LNO_PY_20240805091500000.JPG?v=1722849566319'
+    }
+  ]
+});
+let timer;
+
+// 展开收起
+const changeExpand = () => {
+  timeAxisState.expand = !timeAxisState.expand;
+};
+
+// 设置初始时间
+const getInitTime = () => {
+  // 获取当前时间
+  const now = new Date();
+  timeAxisState.endTime = parseTime(now.getTime(), '{y}-{m}-{d} {h}:{i}');
+  // 计算12小时前的时间
+  now.setHours(now.getHours() - 12);
+  timeAxisState.startTime = parseTime(now.getTime(), '{y}-{m}-{d} {h}:{i}');
+};
+
+// 返回上一个时间点
+const toPrevTime = () => {
+  timeAxisState.showDialog = true;
+  timeAxisState.playing = false;
+  if (timeAxisState.activeIndex === 0) {
+    timeAxisState.playing = false;
+  } else {
+    timeAxisState.activeIndex -= 1;
+  }
+};
+
+// 播放
+const toPlayTime = () => {
+  timeAxisState.playing = true;
+  if (timeAxisState.activeIndex === timeAxisState.data.length - 1) {
+    timeAxisState.activeIndex = -1;
+  }
+  loopPlay();
+};
+
+// 循环播放
+const loopPlay = () => {
+  if (timeAxisState.playing === false) return;
+  toNextTime();
+  //  && timeAxisState.activeIndex < timeAxisState.data.length - 1
+  if (timeAxisState.playing) {
+    setTimeout(loopPlay, 1000 / timeAxisState.speed);
+  }
+};
+
+// 前往下一个时间点
+const toNextTime = (flag?: boolean) => {
+  timeAxisState.showDialog = true;
+  if (flag) {
+    timeAxisState.playing = false;
+  } else if (!timeAxisState.playing) {
+    return;
+  }
+  if (timeAxisState.activeIndex === timeAxisState.data?.length - 1) {
+    timeAxisState.playing = false;
+  } else {
+    timeAxisState.activeIndex += 1;
+  }
+};
+
+// 更换倍速
+const changeSpeed = () => {
+  timeAxisState.speed = timeAxisState.speed === 1 ? 2 : 1;
+};
+
+// 拖拽移动位置
+const dragMove = () => {
+
+};
+
+// 弹窗关闭后
+const dialogClose = () => {
+  timeAxisState.playing = false;
+};
+
+onMounted(() => {
+  getInitTime();
+});
+</script>
+
+<style lang="scss" scoped>
+.time-axis-container {
+  background-color: #023388;
+  color: #fff;
+  font-size: 16px;
+  height: 70px;
+  display: flex;
+  .expand-btn {
+    width: 35px;
+    height: 100%;
+    background-color: #06428f;
+    border-right: 1px solid #1256b0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 0 10px;
+    cursor: pointer;
+  }
+  .time-picker-box {
+    width: 152px;
+    display: flex;
+    flex-direction: column;
+    padding: 10px;
+    border-right: 1px solid #1256b0;
+    :deep(.el-date-editor.el-input) {
+      width: 132px;
+      height: 28px;
+    }
+    :deep(.el-input__wrapper) {
+      background-color: transparent;
+      border: unset;
+      box-shadow: unset;
+    }
+    :deep(.el-input__inner) {
+      color: #b5dbfc;
+      cursor: pointer;
+    }
+    :deep(.el-input__prefix) {
+      display: none;
+    }
+  }
+  .time-list-box {
+    display: flex;
+    align-items: flex-end;
+    overflow-x: auto;
+    background-color: #122b70;
+    border-right: 1px solid #1256b0;
+    .active {
+      :deep(.el-timeline-item__node) {
+        background-color: #ff0000;
+      }
+    }
+    :deep(.el-timeline) {
+      display: flex;
+      align-items: center;
+      width: 680px;
+      //height: 100%;
+      //overflow-x: auto;
+    }
+    :deep(.el-timeline-item__tail) {
+      border: 1px solid #3168bf;
+      height: 6px;
+      background-color: #1f54b6;
+      width: 100%;
+      position: absolute;
+      top: 3px;
+    }
+    :deep(.el-timeline-item__wrapper) {
+      top: 10px;
+      left: -42px;
+    }
+    :deep(.el-timeline-item__timestamp) {
+      font-size: 14px;
+      color: #b8d3f8;
+    }
+    :deep(.el-timeline-item__node) {
+      background-color: #e6fdfe;
+      box-shadow: 0 0 3px 3px #79a9cd;
+    }
+  }
+  .time-tool {
+    display: flex;
+    align-items: center;
+    padding: 0 5px;
+    .tool-icon {
+      margin-right: 5px;
+      cursor: pointer;
+      font-size: 20px;
+    }
+    .speed-btn {
+      margin-right: 5px;
+      color: #8dc8f8;
+      cursor: pointer;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 12 - 2
src/views/globalMap/LeftMenu.vue

@@ -29,7 +29,7 @@
               </div>
               <Transition>
                 <div v-show="item.show" class="box-content">
-                  <div v-for="(item2, index2) in item.childrens" :key="index2" class="box-item">{{ item2.label }}</div>
+                  <div v-for="(item2, index2) in item.childrens" :key="index2" class="box-item" @click="handleClick(item.type)">{{ item2.label }}</div>
                 </div>
               </Transition>
             </div>
@@ -82,7 +82,7 @@ const initData = () => {
         {
           label: '防风防汛',
           childrens: [
-            { label: '卫星云图', url: '' },
+            { label: '卫星云图', type: 1, url: '' },
             { label: '雷达图', url: '' },
             { label: '气温实况图', url: '' }
           ]
@@ -115,6 +115,13 @@ const initData = () => {
   menuState.menuData = menuData;
 };
 
+// 处理菜单点击事件
+const handleClick = (type) => {
+  if(type === 1) {
+
+  }
+};
+
 onMounted(() => {
   initData();
 });
@@ -129,6 +136,7 @@ onMounted(() => {
   padding: 0 10px;
   display: flex;
   align-items: center;
+  font-size: 16px;
   .input {
     border: none;
     outline: none;
@@ -187,6 +195,7 @@ onMounted(() => {
         justify-content: center;
         position: relative;
         border-bottom: 1px solid #83cbf3;
+        font-size: 16px;
         .icon {
           position: absolute;
           right: 30px;
@@ -204,6 +213,7 @@ onMounted(() => {
         .box-item {
           padding: 20px 15px;
           cursor: pointer;
+          font-size: 16px;
           &:hover {
             color: #b5dff0;
           }

+ 5 - 1
src/views/globalMap/index.vue

@@ -2,9 +2,12 @@
   <div class="global-map">
     <MapLogical v-if="activeMap === 'logical'" :mapData="logicalData" />
     <Map v-else :activeMap="activeMap"></Map>
-    <!--    左侧菜单-->
+    <!--左侧菜单-->
     <LeftMenu style="position: absolute; top: 20px; left: 20px" />
+    <!--更换地图类型-->
     <SwitchMapTool :activeMap="activeMap" @swtichMap="swtichMap" class="tool-box" />
+    <!--时间轴-->
+    <TimeAxis style="position: absolute; bottom: 20px; left: 20px" />
   </div>
 </template>
 
@@ -14,6 +17,7 @@ import MapLogical from '@/components/Map/MapLogical.vue';
 import { logicalData } from './data/mapData';
 import SwitchMapTool from '@/views/globalMap/SwitchMapTool.vue';
 import LeftMenu from './LeftMenu.vue';
+import TimeAxis from '@/components/TimeAxis/index.vue';
 
 const mapData = reactive(logicalData);
 let activeMap = ref('satellite');