Browse Source

时间选择器

hm 2 weeks ago
parent
commit
cf7fa5ebcc

+ 1 - 0
package-lock.json

@@ -16,6 +16,7 @@
         "@jiaminghi/data-view": "^2.10.0",
         "axios": "^1.7.9",
         "dagre": "^0.8.5",
+        "dayjs": "^1.11.13",
         "echarts": "^5.6.0",
         "echarts-gl": "^2.0.9",
         "echarts-wordcloud": "^2.1.0",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "@jiaminghi/data-view": "^2.10.0",
     "axios": "^1.7.9",
     "dagre": "^0.8.5",
+    "dayjs": "^1.11.13",
     "echarts": "^5.6.0",
     "echarts-gl": "^2.0.9",
     "echarts-wordcloud": "^2.1.0",

BIN
src/assets/img/椭圆.png


BIN
src/assets/img/椭圆_03.png


+ 53 - 2
src/components/AreaLayerSwitch/index.vue

@@ -32,6 +32,11 @@
         <div :class="[legendOpen ? 'region_tuceng_active' : 'region_tuceng']" v-if="commonStore.activeIndex === 1" @click="handleLegendOpen"></div>
 
         <div class="region_bg"></div>
+        <div class="hx_bg">
+          <div class="time_text left">{{ leftTime }}</div>
+          <div class="time_text right">{{ rightTime }}</div>
+
+        </div>
       </div>
 
       <!-- 全球 -->
@@ -42,6 +47,7 @@
   </div>
 </template>
 <script setup>
+import dayjs from 'dayjs'
 import { ref, watch,nextTick, computed, reactive, toRefs, onBeforeMount, onMounted } from 'vue'
 import { changeStreet, initDistrict } from '@/utils/map/baseMethod.js'
 import { useEventInteractionStore } from '@/store/eventInteraction.js'
@@ -56,6 +62,9 @@ const emit = defineEmits(['btnClick'])
 // const isGlobalOrNational = computed(() => ['全国', '全球'].includes(commonStore.areaLayerSwitchActive))
 const isGlobalOrNational = ref(false)
 
+const leftTime = computed(() =>formatTimeRange(commonStore.timeSelect?.[0])  || '')
+const rightTime = computed(() =>formatTimeRange(commonStore.timeSelect?.[1])  || '')
+
 watch(
   () => commonStore.areaLayerSwitchActive,
   (newVal, oldVal) => {
@@ -182,12 +191,14 @@ function btnDblClick(index, item) {
 
 const legendOpen = ref(false)
 
-function handleLegendOpen() {
+function handleLegendOpen () {
+  console.log(commonStore.activeTools['图例'],'图例')
   commonStore.activeTools['图例'] = !commonStore.activeTools['图例']
+  legendOpen.value = commonStore.activeTools['图例']
 }
 
+
 onMounted(() => {
- 
 })
 
 watch(
@@ -196,6 +207,11 @@ watch(
     commonStore.areaLayerSwitchActive = val
   }
 )
+
+function formatTimeRange(range) {
+   return `${dayjs(range).format('YYYY-MM-DD HH:mm:ss')}`
+
+}
 </script>
 <style lang="scss" scoped>
 .area_layer_switch_container {
@@ -247,6 +263,7 @@ watch(
   // justify-content: space-between;
   justify-content: center;
   position: relative;
+  z-index: 3;
 
   // background-color: pink;
   .region_shi {
@@ -257,6 +274,7 @@ watch(
     display: flex;
     justify-content: center;
     cursor: pointer;
+    z-index: 3;
 
     > div {
       font-family: Alibaba PuHuiTi 3;
@@ -328,6 +346,7 @@ watch(
     background: url(../../assets/img/图层.png) no-repeat;
     background-size: 100% 100%;
     cursor: pointer;
+    z-index: 3;
   }
 
   .region_bg {
@@ -340,6 +359,17 @@ watch(
     background-size: 100% 100%;
     z-index: 1; // 设置较低层级,避免覆盖按钮
   }
+  .hx_bg{
+   position: absolute;
+    top: -46px;
+    // left: -40px;
+    width: 1448px;
+    // height: 69px;
+    height: 140px;
+    background: url(../../assets/img/椭圆_03.png) no-repeat;
+    background-size: 100% 100%;
+    z-index: 0; // 设置较低层级,避免覆盖按钮
+  }
 }
 
 .global {
@@ -376,6 +406,7 @@ watch(
   height: 100%;
   background: url(../../assets/img/图层高亮.png) no-repeat;
   background-size: 100% 100%;
+  z-index: 3;
 }
 
 
@@ -385,6 +416,26 @@ watch(
   opacity: 0.6;
 }
 
+.time_text {
+  position: absolute;
+  top: 46px; // 根据背景图调整
+  font-size: 14px;
+  color: #ffffff;
+  font-family: 'Alibaba PuHuiTi 3';
+  white-space: nowrap;
+
+  &.left {
+    left: 160px;
+    transform: rotate(-8deg); // 贴合左侧弧度
+    transform-origin: left center;
+  }
+
+  &.right {
+    right: 160px;
+    transform: rotate(8deg); // 贴合右侧弧度
+    transform-origin: right center;
+  }
+}
 
 </style>
 

+ 1 - 0
src/store/common.js

@@ -16,6 +16,7 @@ export const useCommonStore = defineStore('common', {
       showAlert: '',  //提示框开关
       alertMessage: '', //提示框内容
       alertKey: 0, //更新此值即可重启倒计时
+      timeSelect:[], //图例时间选择器
     }
   },
   actions: {

+ 232 - 145
src/views/right/components/common/RightLegend.vue

@@ -1,60 +1,50 @@
 <template>
   <div class="right-legend">
     <template v-for="(item, index) in rightPanelStore.riskLevelList" :key="index">
-      <div class="legend-item" >
+      <div class="legend-item">
         <div class="icon-box icon-box-fill">
           <img :src="item.img" />
         </div>
         <div class="text-box">{{ item.name }}</div>
-        <div
-          class="switch-box"
-          :class="{ switchOpen: item.value }"
-          @click="handleRiskLevel(item)"
-        ></div>
+        <div class="switch-box" :class="{ switchOpen: item.value }" @click="handleRiskLevel(item)"></div>
       </div>
     </template>
 
-    <div class="line-box"></div>
-    <template v-for="(item, index) in rightPanelStore.deviceList" :key="index">
-      <div class="legend-item" @click="isDistrictLevel && handleDeviceLevel(item)" :class="{ disabled: !isDistrictLevel }">
-        <div class="icon-box">
-          <img :src="item.img" />
-        </div>
-        <div class="text-box">{{ item.name }}</div>
-        <div class="switch-box" :class="{ switchOpen: item.value }"></div>
-      </div>
-    </template>
+    <!-- 折叠 -->
+    <transition name="fade-slide">
+      <div v-show="!isTimePickerOpen">
+        <div class="line-box"></div>
+        <template v-for="(item, index) in rightPanelStore.deviceList" :key="index">
+          <div class="legend-item" @click="isDistrictLevel && handleDeviceLevel(item)" :class="{ disabled: !isDistrictLevel }">
+            <div class="icon-box">
+              <img :src="item.img" />
+            </div>
+            <div class="text-box">{{ item.name }}</div>
+            <div class="switch-box" :class="{ switchOpen: item.value }"></div>
+          </div>
+        </template>
 
-    <div class="line-box"></div>
+        <div class="line-box"></div>
 
-    <template v-for="(item, index) in rightPanelStore.institutionList" :key="index">
-      <div class="legend-item" @click="isDistrictLevel && handleInstitutionLevel(item)" :class="{ disabled: !isDistrictLevel }">
-        <div class="icon-box">
-          <img :src="item.img" />
-        </div>
-        <div class="text-box">{{ item.name }}</div>
-        <div class="switch-box" :class="{ switchOpen: item.value }"></div>
+        <template v-for="(item, index) in rightPanelStore.institutionList" :key="index">
+          <div class="legend-item" @click="isDistrictLevel && handleInstitutionLevel(item)" :class="{ disabled: !isDistrictLevel }">
+            <div class="icon-box">
+              <img :src="item.img" />
+            </div>
+            <div class="text-box">{{ item.name }}</div>
+            <div class="switch-box" :class="{ switchOpen: item.value }"></div>
+          </div>
+        </template>
       </div>
-    </template>
-
+    </transition>
     <div class="line-box"></div>
 
-      <div class="legend-item">
+    <div class="legend-item">
       <div class="icon-box">
         <img src="../../../../assets/img/Component 240(6).png" />
       </div>
-      <el-select
-        v-model="bingliSelect"
-        class="legend-select"
-        placeholder="请选择病种"
-        popper-class="custom-select"
-      >
-        <el-option
-          v-for="(item, index) in bingliArr"
-          :key="index"
-          :label="item.label"
-          :value="item.value"
-        ></el-option>
+      <el-select v-model="bingliSelect" class="legend-select" placeholder="请选择病种" popper-class="custom-select">
+        <el-option v-for="(item, index) in bingliArr" :key="index" :label="item.label" :value="item.value"></el-option>
       </el-select>
     </div>
 
@@ -62,147 +52,175 @@
       <div class="icon-box">
         <img src="../../../../assets/img/Component 240(7).png" />
       </div>
-      <el-select
-        class="legend-select"
-        v-model="riskType"
-        placeholder="请选择风险类型"
-        popper-class="custom-select"
-      >
-        <el-option
-          v-for="(item, index) in riskTypeOptions"
-          :key="index"
-          :label="item.label"
-          :value="item.value"
-        ></el-option>
+      <el-select class="legend-select" v-model="riskType" placeholder="请选择风险类型" popper-class="custom-select">
+        <el-option v-for="(item, index) in riskTypeOptions" :key="index" :label="item.label" :value="item.value"></el-option>
       </el-select>
     </div>
     <div class="legend-item">
       <div class="icon-box">
         <img src="../../../../assets/img/Component 240(8).png" />
       </div>
-      <el-select
-        v-model="timeSelect"
-        class="legend-select"
-        popper-class="custom-select"
-        placeholder="请选择时间"
-      >
-        <el-option
-          v-for="(item, index) in timeArr"
-          :key="index"
-          :label="item.label"
-          :value="item.value"
-        ></el-option
-      ></el-select>
+    <el-tooltip :disabled="!commonStore.timeSelect.length" placement="bottom" effect="customized" :content="formatTimeRange(commonStore.timeSelect)" popper-class="tooltip-multiline">
+        <div class="date-picker-container" @click="handleDatePickerClick">
+          <el-date-picker
+            ref="datePickerRef"
+            v-model="commonStore.timeSelect"
+            type="datetimerange"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            format="YYYY-MM-DD HH:mm:ss"
+            date-format="YYYY/MM/DD ddd"
+            time-format="A hh:mm:ss"
+            teleported="false"
+            popper-class="date_picker_custom-class"
+            @visible-change="onDatePickerVisibleChange"
+            @change="onDateChange"
+          />
+        </div>
+      </el-tooltip>
     </div>
     <template v-if="commonStore.activeIndex == 1">
       <div class="line-box"></div>
 
       <div class="legend-item">
         <div class="text-box">病例热点</div>
-        <div
-          class="switch-box"
-          :class="{ switchOpen: bingliSwitch }"
-          @click="handleBingli()"
-        ></div>
+        <div class="switch-box" :class="{ switchOpen: bingliSwitch }" @click="handleBingli()"></div>
       </div>
       <div class="legend-item">
         <div class="text-box">视频会商</div>
-        <div
-          class="switch-box"
-          :class="{ switchOpen: videoSwitch }"
-          @click="handleVideo()"
-        ></div>
+        <div class="switch-box" :class="{ switchOpen: videoSwitch }" @click="handleVideo()"></div>
       </div>
     </template>
-    <div class="legend-item">
+    <!-- <div class="legend-item">
       <div class="text-box">时间轴</div>
-      <div
-        class="switch-box"
-        :class="{ switchOpen: rightPanelStore.timerSwitchStatus }"
-        @click="handleTimerSwitch"
-      ></div>
-    </div>
+      <div class="switch-box" :class="{ switchOpen: rightPanelStore.timerSwitchStatus }" @click="handleTimerSwitch"></div>
+    </div> -->
   </div>
 </template>
 <script setup>
-import { ref,computed } from "vue";
-import { useCommonStore } from "@/store/common.js";
-import { useRightPanelStore } from "../../../../store/rightPanel";
-import {handleHeatMap} from "@/utils/map/baseMethod.js";
-import { riskTypeOptions } from "../../../../data/dict";
+import { ref, computed ,nextTick } from 'vue'
+import { useCommonStore } from '@/store/common.js'
+import { useRightPanelStore } from '../../../../store/rightPanel'
+import { handleHeatMap } from '@/utils/map/baseMethod.js'
+import { riskTypeOptions } from '../../../../data/dict'
+
+const commonStore = useCommonStore()
+const rightPanelStore = useRightPanelStore()
 
-const commonStore = useCommonStore();
-const rightPanelStore = useRightPanelStore();
+const districtList = ['全国', '全球', '上海市']
+const isDistrictLevel = computed(() => !districtList.includes(commonStore.areaLayerSwitchActive))
 
-const districtList = ["全国", "全球", "上海市"]
-const isDistrictLevel = computed(() =>
-  !districtList.includes(commonStore.areaLayerSwitchActive)
-);
+const bingliSwitch = ref(false)
+const videoSwitch = ref(false)
 
-const bingliSwitch = ref(false);
-const videoSwitch = ref(false);
+const isTimePickerOpen = ref(false) // 控制是否折叠
+// 获取日期选择器组件实例
+const datePickerRef = ref(null)
+
+const onDatePickerVisibleChange = (visible) => {
+  if (visible) {
+    // 调整弹窗的位置
+    adjustPopperPosition()
+  }
+}
+
+const adjustPopperPosition = () => {
+  // 如果时间选择器弹窗被打开,动态调整其位置
+  const popper = document.querySelector('.el-picker-panel') // 获取弹窗
+  if (popper) {
+    const rect = datePickerRef.value.$el.getBoundingClientRect()
+    popper.style.top = `${rect.top + window.scrollY + 30}px` // 使其跟随
+    popper.style.left = `${rect.left + window.scrollX}px`
+  }
+}
+
+// 当用户点击整个日期选择器容器时,先折叠下面面板
+const handleDatePickerClick = () => {
+  // 如果面板还未折叠,则先折叠面板
+  if (!isTimePickerOpen.value) {
+    isTimePickerOpen.value = true
+    // 等待折叠动画结束(300ms),然后自动打开日期选择弹层
+    setTimeout(() => {
+      // 通过引用调用 datePicker 内部方法打开弹层
+      if (datePickerRef.value && datePickerRef.value.handleOpen) {
+        nextTick(() => {
+         datePickerRef.value.handleOpen()
+          
+        })
+       }
+    }, 300)
+  }
+}
+
+// // 日期选择完毕后,可执行其他逻辑,比如提示用户选择的时间范围
+const onDateChange = (val) => {
+  if (val && val.length === 2) {
+    const [start, end] = val
+    isTimePickerOpen.value =false
+  }
+}
 
 const handleBingli = () => {
-  bingliSwitch.value = !bingliSwitch.value;
+  bingliSwitch.value = !bingliSwitch.value
   handleHeatMap(bingliSwitch.value)
-};
+}
 
 const handleTimerSwitch = () => {
-  rightPanelStore.timerSwitchStatus = !rightPanelStore.timerSwitchStatus;
-};
+  rightPanelStore.timerSwitchStatus = !rightPanelStore.timerSwitchStatus
+}
 
 const handleVideo = () => {
-  videoSwitch.value = !videoSwitch.value;
-};
+  videoSwitch.value = !videoSwitch.value
+}
 
-const bingliSelect = ref("");
+const bingliSelect = ref('')
 
 const bingliArr = ref([
   {
-    label: "流感",
-    value: "流感",
+    label: '流感',
+    value: '流感'
   },
   {
-    label: "肺炎",
-    value: "肺炎",
+    label: '肺炎',
+    value: '肺炎'
   },
   {
-    label: "高温中暑",
-    value: "高温中暑",
+    label: '高温中暑',
+    value: '高温中暑'
   },
   {
-    label: "疟疾",
-    value: "疟疾",
+    label: '疟疾',
+    value: '疟疾'
   },
   {
-    label: "登革热",
-    value: "登革热",
+    label: '登革热',
+    value: '登革热'
   },
   {
-    label: "猩红热",
-    value: "猩红热",
+    label: '猩红热',
+    value: '猩红热'
   },
   {
-    label: "聚集性呕吐腹泻",
-    value: "聚集性呕吐腹泻",
-  },
-]);
+    label: '聚集性呕吐腹泻',
+    value: '聚集性呕吐腹泻'
+  }
+])
 
-const timeSelect = ref("");
+const timeSelect = ref([])
 const timeArr = ref([
   {
-    label: "当年",
-    value: "当年",
+    label: '当年',
+    value: '当年'
   },
   {
-    label: "当月",
-    value: "当月",
+    label: '当月',
+    value: '当月'
   },
   {
-    label: "当日",
-    value: "当日",
-  },
-]);
+    label: '当日',
+    value: '当日'
+  }
+])
 
 //风险等级
 // const riskLevelList = ref([
@@ -288,17 +306,35 @@ const timeArr = ref([
 //   },
 // ]);
 
-const handleRiskLevel = (item) => {
-  item.value = !item.value;
-};
+const handleRiskLevel = item => {
+  item.value = !item.value
+}
+
+const handleDeviceLevel = item => {
+  item.value = !item.value
+}
 
-const handleDeviceLevel = (item) => {
-  item.value = !item.value;
-};
+const handleInstitutionLevel = item => {
+  item.value = !item.value
+}
 
-const handleInstitutionLevel = (item) => {
-  item.value = !item.value;
-};
+const formatTimeRange = range => {
+  if (!range || range.length !== 2) return ''
+  const [start, end] = range
+  return `起:${formatDate(start)}\n止:${formatDate(end)}`
+}
+
+const formatDate = val => {
+  if (!val) return ''
+  const date = new Date(val)
+  const y = date.getFullYear()
+  const m = String(date.getMonth() + 1).padStart(2, '0')
+  const d = String(date.getDate()).padStart(2, '0')
+  const h = String(date.getHours()).padStart(2, '0')
+  const min = String(date.getMinutes()).padStart(2, '0')
+  const s = String(date.getSeconds()).padStart(2, '0')
+  return `${y}-${m}-${d} ${h}:${min}:${s}`
+}
 </script>
 
 <style lang="scss">
@@ -306,19 +342,26 @@ const handleInstitutionLevel = (item) => {
   padding: 15px 20px;
   width: 217px;
   // height: 750px;
-  background-image: url("../../../../assets/img/撒点弹窗.png");
+  background-image: url('../../../../assets/img/撒点弹窗.png');
   background-size: 100% 100%;
   z-index: 99;
+  overflow: hidden;
+  box-sizing: border-box;
+  
   .legend-item {
     display: flex;
     align-items: center;
     margin: 11px 0;
+    overflow: hidden;
     .icon-box {
       width: 28px;
       height: 28px;
       img {
         width: 100%;
         height: 100%;
+        width: 28px;
+        height: 28px;
+        
       }
     }
     .icon-box-fill {
@@ -343,12 +386,12 @@ const handleInstitutionLevel = (item) => {
     .switch-box {
       width: 47px;
       height: 26px;
-      background-image: url("../../../../assets/img/Component 15(2).png");
+      background-image: url('../../../../assets/img/Component 15(2).png');
       background-size: 100% 100%;
       cursor: pointer;
     }
     .switchOpen {
-      background-image: url("../../../../assets/img/Component 14.png");
+      background-image: url('../../../../assets/img/Component 14.png');
     }
     .legend-select {
       width: 146px;
@@ -358,12 +401,7 @@ const handleInstitutionLevel = (item) => {
       border-radius: 4px 4px 4px 4px;
       border: 1px solid #45d2ff;
       border-bottom: 1px solid #45d2ff;
-      border-image: linear-gradient(
-          174deg,
-          rgba(154, 249, 255, 1),
-          rgba(111, 246, 255, 0.3)
-        )
-        1 1;
+      border-image: linear-gradient(174deg, rgba(154, 249, 255, 1), rgba(111, 246, 255, 0.3)) 1 1;
       .el-select__wrapper {
         background-color: transparent !important;
         box-shadow: none;
@@ -377,7 +415,7 @@ const handleInstitutionLevel = (item) => {
     }
   }
   .line-box {
-    background-image: url("../../../../assets/img/Line 550.png");
+    background-image: url('../../../../assets/img/Line 550.png');
     height: 2px;
     background-size: 100% 100%;
   }
@@ -396,5 +434,54 @@ const handleInstitutionLevel = (item) => {
   }
 }
 
+.el-date-table td.in-range .el-date-table-cell {
+  background-color: #0c6d88;
+}
+
+.el-date-table td .el-date-table-cell:hover {
+  background-color: #052c4f !important;
+}
+.el-picker-panel__footer {
+  .el-picker-panel__link-btn {
+    background: #052c4f;
+    color: #ffffff;
+    border: 1px solid transparent;
+  }
+  .el-picker-panel__link-btn:hover {
+    background: #2769a0 !important;
+    color: #ffffff;
+    border: 1px solid transparent;
+  }
+}
+
+.fade-slide-enter-active,
+.fade-slide-leave-active {
+  transition: all 0.3s ease;
+}
+.fade-slide-enter-from,
+.fade-slide-leave-to {
+  opacity: 0;
+  transform: translateY(-10px);
+}
 
+.date_picker_custom-class {
+  position: absolute !important;
+  top: 550px !important;
+  z-index: 999; /* 保证弹窗在其他元素上方 */
+  // max-height: 300px;
+  // overflow-y: auto;
+}
+.date-picker-container{
+  margin-left: 15px;
+}
+.el-input__inner{
+  color: #ffffff;
+}
+.el-date-editor .el-range-input{
+  color: #ffffff;
+}
+.el-date-editor .el-range__icon{
+  display: none;
+}
 </style>
+<style scoped></style>