Browse Source

Merge remote-tracking branch 'origin/dev' into dev

yangyuxuan 3 months ago
parent
commit
17cf504e50

+ 0 - 1
src/components/Chart/index.vue

@@ -47,7 +47,6 @@ watch(
 
 // 加载事件
 const load = () => {
-  const that = this;
   if (!myChart.value || Object.keys(myChart.value).length === 0) {
     myChart.value = echarts.init(canvas.value);
   }

+ 0 - 10
src/types/components.d.ts

@@ -27,7 +27,6 @@ declare module 'vue' {
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
@@ -46,32 +45,25 @@ declare module 'vue' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
-    ElSegmented: typeof import('element-plus/es')['ElSegmented']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
     ElSlider: typeof import('element-plus/es')['ElSlider']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
-    ElTable: typeof import('element-plus/es')['ElTable']
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
-    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     ExcelEditor: typeof import('./../components/ExcelEditor/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
@@ -84,8 +76,6 @@ declare module 'vue' {
     HikvisionPlayer: typeof import('./../components/HKVideo/hikvision-player.vue')['default']
     HKVideo: typeof import('./../components/HKVideo/index.vue')['default']
     IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
-    IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
-    IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']

+ 21 - 76
src/views/emergencyCommandMap/RightSection/JointDuty.vue

@@ -34,7 +34,7 @@
         <img :src="qrCodeUrl" alt="" style="width: 300px; height: 300px" />
       </div>
       <div class="flex" style="margin-top: 20px">
-        <div class="common-btn-primary" v-print="printObj">打印</div>
+        <div v-print="printObj" class="common-btn-primary">打印</div>
         <div class="common-btn-primary" @click="handleDownLoad">下载</div>
       </div>
     </div>
@@ -44,20 +44,6 @@
 <script lang="ts" setup>
 import { getCheckinList } from '@/api/emergencyCommandMap/JointDuty';
 const route = useRoute();
-const tableHeader = reactive([
-  {
-    name: '值守单位',
-    key: 'dept_name'
-  },
-  {
-    name: '值守人员',
-    key: 'nick_name'
-  },
-  {
-    name: '联系方式',
-    key: 'phone'
-  }
-]);
 const listData = ref([]);
 let eventId = ref('');
 let qrCodeUrl = ref('');
@@ -70,17 +56,6 @@ const showQrCode = ref(false);
 const handleShowQrCode = () => {
   showQrCode.value = true;
 };
-const base64ToBlob = (code) => {
-  let parts = code.split(';base64,');
-  let contentType = parts[0].split(':')[1];
-  let raw = window.atob(parts[1]);
-  let rawLength = raw.length;
-  let uInt8Array = new Uint8Array(rawLength);
-  for (let i = 0; i < rawLength; ++i) {
-    uInt8Array[i] = raw.charCodeAt(i);
-  }
-  return new Blob([uInt8Array], { type: contentType });
-};
 // 下载
 const handleDownLoad = () => {
   let image = new Image();
@@ -102,66 +77,36 @@ const handleDownLoad = () => {
   };
 };
 
-
 // 设置定时器
 const fetchInterval = process.env.NODE_ENV === 'development' ? 60000 : 1500; // 每60秒刷新一次(刷新太频繁影响调试)
 
-const nextFetchData = ()=> {
-  setTimeout(()=>{
-    getCheckinList(eventId.value).then((res) => {
+let timer;
+let isMounted = ref(false);
+const nextFetchData = () => {
+  getCheckinList(eventId.value)
+    .then((res) => {
       listData.value = res.data;
-      nextFetchData();
+    })
+    .finally(() => {
+      if (isMounted.value) {
+        timer = setTimeout(nextFetchData, fetchInterval);
+      }
     });
-  }, fetchInterval)
 };
 
 onMounted(() => {
   eventId.value = route.query.event_id as string;
-  /*
-  listData.value = [
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    },
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    },
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    },
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    },
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    },
-    {
-      dept_name: '市应急局',
-      nick_name: '张平安',
-      phone: '13856642378',
-      duties: '职务'
-    }
-  ];
-  getCheckinList(eventId.value).then((res) => {
-     listData.value = res.data;
-  });
-  */
-  qrCodeUrl.value = import.meta.env.VITE_APP_BASE_API2 + 'api/qrcode/event/checkin?event_id=' + eventId.value;
+  isMounted.value = true;
   nextFetchData();
+  qrCodeUrl.value = import.meta.env.VITE_APP_BASE_API2 + 'api/qrcode/event/checkin?event_id=' + eventId.value;
+});
+
+onUnmounted(() => {
+  isMounted.value = false;
+  if (!!timer) {
+    clearTimeout(timer);
+    timer = null;
+  }
 });
 </script>
 

+ 26 - 44
src/views/emergencyCommandMap/RightSection/RenWuGenZong.vue

@@ -81,18 +81,22 @@ const showMoreEventManageList = () => {
   eventManageState.showListDialog = true;
 };
 
+let timer;
+let isMounted = ref(false);
 // 请求数据
-const fetchData = async () => {
-  try {
-    console.log('请求任务数据:', props.eventId);
-    const res = await selectTask({ event_code: props.eventId });
-    res.data.forEach((item) => {
-      item.update_time = parseTime(item.update_time);
+const fetchData = (unNeedTimeout?: boolean) => {
+  selectTask({ event_code: props.eventId })
+    .then((res) => {
+      res.data.forEach((item) => {
+        item.update_time = parseTime(item.update_time);
+      });
+      dataList.value = res.data;
+    })
+    .finally(() => {
+      if (isMounted.value && !unNeedTimeout) {
+        timer = setTimeout(fetchData, fetchInterval);
+      }
     });
-    dataList.value = res.data;
-  } catch (error) {
-    console.error('请求任务数据失败:', error);
-  }
 };
 
 const openUpdateDialog = (task) => {
@@ -102,7 +106,7 @@ const openUpdateDialog = (task) => {
 
 const handleUpdateSuccess = (updatedData) => {
   console.log('任务进度更新成功:', updatedData);
-  fetchData(); // 重新加载任务列表
+  fetchData(true); // 重新加载任务列表
 };
 const handleLeaderInstruction = (item) => {
   // 处理领导批示逻辑
@@ -113,53 +117,31 @@ const toggleExpand = (item) => {
   item.isExpanded = !item.isExpanded;
 };
 
-const toggleScroll = () => {
-  showScroll.value = !showScroll.value;
-};
-
 // 设置定时器
 const fetchInterval = process.env.NODE_ENV === 'development' ? 60000 : 3000; // 每60秒刷新一次(刷新太频繁影响调试)
-let intervalId: any | null = null;
-
-const startFetchingData = () => {
-  if (!intervalId) {
-    intervalId = setInterval(() => {
-      fetchData();
-    }, fetchInterval);
-  }
-};
-
-const stopFetchingData = () => {
-  if (intervalId) {
-    clearInterval(intervalId);
-    intervalId = null;
-  }
-};
-
 // 在组件挂载时开始定时获取数据
 onMounted(() => {
-  if (props.eventId) {
-    fetchData();
-    startFetchingData();
-  }
+  isMounted.value = true;
 });
 
 // 在组件卸载时清除定时器
 onUnmounted(() => {
-  stopFetchingData();
+  isMounted.value = false;
+  if (!!timer) {
+    clearTimeout(timer);
+    timer = null;
+  }
 });
-
 watch(
   () => props.eventId,
-  (newValue) => {
-    if (newValue) {
+  () => {
+    if (props.eventId) {
       fetchData();
-      startFetchingData();
-    } else {
-      stopFetchingData();
     }
   },
-  { immediate: true }
+  {
+    immediate: true
+  }
 );
 </script>
 

+ 1 - 2
src/views/emergencyCommandMap/RightSection/RightTop.vue

@@ -48,9 +48,8 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, reactive } from 'vue';
 import { taskList } from '@/api/duty/eventing';
-import RenWuGenZong from '@/views/emergencyCommandMap/RightSection/RenWuGenZong.vue'; // 确保 eventing.ts 的路径正确
+import RenWuGenZong from './RenWuGenZong.vue';
 
 const props = defineProps<{
   eventId?: string; // 使用可选属性

+ 128 - 16
src/views/globalMap/RightMenu/FixedPointAnalysis.vue

@@ -1,20 +1,34 @@
 <template>
   <div class="menu-content">
-    <div class="gradient-text common-dialog-title2">定点分析</div>
+    <div class="gradient-text title">定点分析</div>
     <div class="scroll-box">
-      <div v-if="!tempState.address" class="search-box">
-        <div class="text-box">
+      <div class="search-box">
+        <di class="text-box">
           <i class="icon-position2" />
-          <div class="text1" :title="selectData.event_title">{{ selectData.event_title }}</div>
-        </div>
-        <div class="common-btn-primary3" @click="toSelect">定点选取</div>
+          <div class="text1" :title="tempState.address ? tempState.address : selectData.event_title">
+            {{ tempState.address ? tempState.address : selectData.event_title }}
+          </div>
+        </di>
+        <div v-if="tempState.address" class="common-btn-primary4" @click="confirmSelect">确定定位</div>
       </div>
-      <div v-else class="search-box">
-        <div class="text-box">
-          <i class="icon-position2" />
-          <div class="text1" :title="tempState.address">{{ tempState.address }}</div>
+      <div ref="inputRef" class="search-item" style="position: relative">
+        <el-input v-model="inputText" class="custom-input2" placeholder="请输入地址或经纬度后回车搜索" @keyup.enter="handleSearch" />
+        <div v-if="searchPop" class="scroll_box">
+          <div style="height: 60px; line-height: 60px">
+            <span style="font-weight: bold">搜索结果列表</span>
+            <i class="el-icon-close" style="float: right; font-size: 20px; cursor: pointer" @click="closeSearchList()" />
+          </div>
+          <el-scrollbar class="scroll" style="height: 350px">
+            <div v-for="(item, index) in searchList" v-show="searchList.length" :key="index" class="item" @click="handleSearchSelect(item)">
+              <div>
+                <div class="text">{{ item.name }}</div>
+                <div>{{ item.address }}</div>
+              </div>
+            </div>
+            <div v-show="!searchList.length" class="empty" style="text-align: center">没有搜索到内容</div>
+          </el-scrollbar>
         </div>
-        <div class="common-btn-primary4" @click="confirmSelect">确定定位</div>
+        <div class="common-btn-primary4" @click="toSelect">地图选取</div>
       </div>
       <div class="search-item">
         <div class="text1">选择救灾资源:</div>
@@ -45,12 +59,12 @@
         </div>
         <div class="list-content">
-          <div v-for="(item, index) in dataList" :key="index" class="list-item">
+          <div v-for="(item, index) in dataList" :key="index" class="list-item" @click="handleRoutes(item)">
             <div class="text-box2">
               <div class="text2">{{ item.name }}</div>
               <div class="text3">{{ item.address }}</div>
             </div>
-            <div class="operate" @click="handleRoutes(item)">
+            <div class="operate">
               <i class="icon2" />
               路线
             </div>
@@ -73,7 +87,7 @@
           >
             <div class="text-box1">
               <div :class="'tag tag' + index">{{ getTag(index) }}</div>
-<!--              <div class="text1">{{ item.strategy }}</div>-->
+              <!--              <div class="text1">{{ item.strategy }}</div>-->
             </div>
             <div class="route-info">
               <div class="text-box2">
@@ -111,6 +125,7 @@ import useMapStore from '@/store/modules/map';
 import markImg from '@/assets/images/map/mark.png';
 import startImg from '@/assets/images/map/start.png';
 import endImg from '@/assets/images/map/end.png';
+import { onClickOutside } from '@vueuse/core';
 
 interface Props {
   location?: string | number[];
@@ -122,6 +137,7 @@ const getMap = inject('getMap');
 const getMapUtils = inject('getMapUtils');
 const mapStore = useMapStore();
 const amapKey = 'e45d4caa2bef3c84714a2ed9b1e27d98';
+let inputRef = ref();
 let showAddress = ref(true);
 let routeData = ref([]);
 let routeLine, startMarker, endMarker;
@@ -145,8 +161,11 @@ let queryParams = reactive({
   keyword: '',
   dataType: '2'
 });
+let inputText = ref('');
 let total = ref(0);
 let dataList = ref([]);
+let searchList = ref([]);
+let searchPop = ref(false);
 watch(
   () => mapStore.mapLoaded,
   (loaded) => {
@@ -159,6 +178,10 @@ watch(
     immediate: true
   }
 );
+onClickOutside(inputRef, (event) => {
+  searchPop.value = false;
+  searchList.value = [];
+});
 const toSelect = () => {
   map.on('click', handleClickMap);
   mapStore.setIsMapSelect(true);
@@ -463,6 +486,61 @@ const clearLine = () => {
     }
   }
 };
+// 搜索地址或者经纬度
+const handleSearch = () => {
+  const value = inputText.value.trim();
+  // 1. 检测是否为经纬度格式
+  const isCoordinate = /^\s*[-+]?\d{1,3}(\.\d+)?\s*[,,]\s*[-+]?\d{1,3}(\.\d+)?\s*$/.test(value);
+
+  if (isCoordinate) {
+    // 2. 分割并验证经纬度范围
+    const parts = value.split(/[,,]/);
+    if (parts.length !== 2) {
+      handleAddress(value);
+      return;
+    }
+
+    const lng = parseFloat(parts[0]);
+    const lat = parseFloat(parts[1]);
+
+    if (lng < -180 || lng > 180 || lat < -90 || lat > 90) {
+      proxy.$modal.msgError('请输入合法的坐标');
+      return;
+    }
+
+    handleCoordinate([lng, lat]); // 处理经纬度逻辑
+  } else {
+    handleAddress(value); // 处理地址逻辑
+  }
+};
+const handleCoordinate = (lnglat) => {
+  const newMap = mapStore.isAMap ? map : map.getView();
+  newMap.setCenter(lnglat);
+  getAddress(lnglat);
+};
+
+const handleAddress = async (value) => {
+  const url = `https://restapi.amap.com/v5/place/text?key=${amapKey}&keywords=${value}`;
+  const response = await fetch(url);
+  const result = await response.json();
+  searchList.value = result.pois;
+  searchPop.value = true;
+};
+
+const handleSearchSelect = (item) => {
+  const newMap = mapStore.isAMap ? map : map.getView();
+  const location = item.location.split(',');
+  newMap.setCenter(location);
+  const gcj02Coord = gcoord.transform(location, gcoord.WGS84, gcoord.GCJ02);
+  createMarks({ longitude: gcj02Coord[0], latitude: gcj02Coord[1] }, true);
+  tempState.address = item.address;
+};
+
+const closeSearchList = () => {
+  searchPop.value = false;
+  searchList.value = [];
+};
+
 onMounted(() => {
   if (mapStore.isAMap) {
     AMap = mapUtils.getAMap();
@@ -489,7 +567,7 @@ onMounted(() => {
   }
   .search-box {
     width: 100%;
-    height: 150px;
+    height: 75px;
     background: url('@/assets/images/electronicDisasterMapManage/box6.png') no-repeat;
     background-size: 100% 100%;
     padding: 0 10px 0 30px;
@@ -576,7 +654,7 @@ onMounted(() => {
       }
     }
     .list-content {
-      height: 210px;
+      height: 350px;
       overflow-y: auto;
       .list-item {
         display: flex;
@@ -736,4 +814,38 @@ onMounted(() => {
     }
   }
 }
+.scroll_box {
+  width: calc(100% - 105px);
+  background: #0a2c5c;
+  position: absolute;
+  left: 0;
+  top: 45px;
+  z-index: 9;
+  padding: 10px;
+  border-radius: 3px;
+  .close {
+    position: absolute;
+    right: 10px;
+    top: 10px;
+    cursor: pointer;
+    font-size: 14px;
+  }
+}
+.scroll {
+  width: 100%;
+  .item {
+    display: flex;
+    font-size: 14px;
+    cursor: pointer;
+    padding: 8px;
+    border-bottom: 1px solid #4574d5;
+    &:hover {
+      background-color: #102e76;
+    }
+    .text {
+      font-size: 16px;
+      margin-bottom: 6px;
+    }
+  }
+}
 </style>

+ 31 - 8
src/views/globalMap/RightMenu/LayerAnalysis.vue

@@ -21,10 +21,10 @@
       <div class="box2">
         <div class="box2-title">类型统计</div>
         <div class="box2-right">
-          <Chart :option="chartOption2" style="width: 250px; height: 100%" />
+          <Chart ref="chartRef" :option="chartOption2" style="width: 250px; height: 100%" />
           <div class="legend-box">
-            <div v-for="(item, index) in legendData" :key="index" class="legend-item">
-              <span class="dot" :style="{ backgroundColor: getColor(index) }"></span>{{ item.name }}:{{ item.value }}
+            <div v-for="(item, index) in legendData" :key="index" class="legend-item" @click="handleClickLegend(item)">
+              <span class="dot" :style="{ backgroundColor: getColor(item.checked, index) }"></span>{{ item.name }}:{{ item.value }}
             </div>
           </div>
         </div>
@@ -69,14 +69,17 @@ import BigNumber from 'bignumber.js';
 import useMapStore from '@/store/modules/map';
 
 const mapStore = useMapStore();
-let colorList = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
+let chartRef = ref();
+let colorList = ['#00dc98', '#ff3349', '#10b3ff', '#ef9330', '#f6e900', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
 let dataList = ref([]);
 let legendData = ref([]);
 let chartOption1 = reactive(option4);
 let chartOption2 = reactive(option5);
 
-const getColor = (index) => {
-  if (colorList[index]) {
+const getColor = (checked, index) => {
+  if (!checked) {
+    return '#edf2fa';
+  } else if (colorList[index]) {
     return colorList[index];
   } else {
     return '#edf2fa';
@@ -97,6 +100,15 @@ const getOption = (data, key = 'dataType') => {
   return path.toString();
 };
 
+const handleClickLegend = (item) => {
+  item.checked = !item.checked;
+  const newSelected = {};
+  legendData.value.forEach((item) => {
+    newSelected[item.name] = !!item.checked;
+  });
+  chartOption2.legend.selected = newSelected;
+};
+
 watch(
   () => mapStore.pointType,
   () => {
@@ -211,11 +223,21 @@ watch(
         '44': getCountPointInfoTypeErtongfulijigou,
         '45': getCountPointInfoTypeYangLaoJiGou
       };
-      let method = methodList[dataList.value[0].dataType];
+      let dataType = null;
+      for (let i = 0; i < dataList.value.length; i++) {
+        if (dataList.value[i].checked) {
+          dataType = dataList.value[i].dataType;
+          break;
+        }
+      }
+      let method = methodList[dataType];
       if (!method) return;
       method().then((res) => {
         legendData.value = res.rows;
-        chartOption2.legend = { show: false };
+        chartOption2.legend.data = legendData.value;
+        res.rows.forEach((row) => {
+          row.checked = true;
+        });
         chartOption2.series[0].data = res.rows;
       });
     } else {
@@ -318,6 +340,7 @@ const handleClick = (item) => {
         align-items: center;
         margin-bottom: 10px;
         margin-right: 10px;
+        cursor: pointer;
       }
       .dot {
         display: inline-block;

+ 6 - 3
src/views/globalMap/RightMenu/echartOptions.ts

@@ -352,7 +352,7 @@ export const option4 = {
 
 // 类型统计
 export const option5 = {
-  color: ['#00dc98', '#ff3349', '#10b3ff', '#ef9330', '#f6e900'],
+  color: ['#00dc98', '#ff3349', '#10b3ff', '#ef9330', '#f6e900', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
   graphic: {
     elements: [
       {
@@ -382,13 +382,16 @@ export const option5 = {
   tooltip: {
     show: false
   },
-  legend: [],
+  legend: {
+    show: false,
+    selected: {},
+    data: []
+  },
   toolbox: {
     show: false
   },
   series: [
     {
-      name: '',
       type: 'pie',
       clockWise: false,
       center: [120, 110],