Просмотр исходного кода

地图右侧菜单 接口对接

Hwf 10 месяцев назад
Родитель
Сommit
6a37621273

Разница между файлами не показана из-за своего большого размера
+ 5 - 0
public/video/h5player.min.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
public/video/jquery-1.12.4.min.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
public/video/jsencrypt.min.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
public/video/web-control_1.2.5.min.js


+ 9 - 1
src/api/globalMap/index.ts

@@ -1,5 +1,4 @@
 import request from '@/utils/request';
-import qs from 'qs'
 import { MapQuery } from './type';
 
 // 加载粤政图服务
@@ -52,3 +51,12 @@ export const getEmergencyExpertNameLike = (params) => {
     data: params
   });
 };
+
+// 获取周围视频点位接口
+export const getVideoInfo = (params) => {
+  return request({
+    url: '/api/gateway/v1/get_video_info',
+    method: 'post',
+    data: params
+  });
+};

+ 17 - 0
src/api/globalMap/reservoir.ts

@@ -0,0 +1,17 @@
+import request from '@/utils/request';
+// 水库监测-获取水库列表
+export const getWaterList = (data) => {
+  return request({
+    url: '/api/gateway/v1/get_water_list',
+    method: 'post',
+    data: data
+  });
+};
+
+export const getWaterList2 = (data) => {
+  return request({
+    url: '/api/gateway/v2/get_water_list',
+    method: 'post',
+    data: data
+  });
+};

+ 18 - 0
src/api/globalMap/reservoirMonitor.ts

@@ -0,0 +1,18 @@
+import request from '@/utils/request';
+// 水库监测-获取水库列表
+export const getReservoirList = (data) => {
+  return request({
+    url: '/api/gateway/v2/get_reservoir_list',
+    method: 'post',
+    data: data
+  });
+};
+
+// 水库监测-过去24小时水库水位变化图
+export const getReservoirCourseLevel = (params) => {
+  return request({
+    url: '/api/gateway/v1/get_reservoir_course_level',
+    method: 'get',
+    params: params
+  });
+};

+ 19 - 0
src/api/globalMap/riverMonitor.ts

@@ -0,0 +1,19 @@
+import request from '@/utils/request';
+// 获取河流列表
+export const getRiverList = (data) => {
+  return request({
+    url: '/api/gateway/v2/get_river_list',
+    method: 'post',
+    data: data
+  });
+};
+
+// 河道监测-过去24小时河流水位变化图
+export const getRiverCourseLevel = (params) => {
+  return request({
+    url: '/api/gateway/v1/get_river_course_level',
+    method: 'get',
+    params: params
+  });
+};
+

+ 10 - 8
src/components/Dialog/index2.vue

@@ -1,11 +1,11 @@
 <template>
-  <div v-if="modelValue" class="dialog-wrap">
+  <div v-if="modelValue" class="dialog-wrap" :style="{ width: width ? width : '780px', height: height ? height : '590px' }">
 <!--    <div class="overlay" @click="closeDialog"></div>-->
-    <div class="dialog">
+    <div class="dialog" :style="{ width: width ? width : '780px', height: height ? height : '590px' }">
       <div class="dialog-header">
         <div class="dialog-title">{{ title }}</div>
         <div class="icon-close" @click="closeDialog">
-          <el-icon size="20px"><Close /></el-icon>
+          <el-icon size="40px"><Close /></el-icon>
         </div>
       </div>
       <div class="dialog-content">
@@ -19,6 +19,8 @@
 interface Props {
   modelValue: boolean;
   title?: string;
+  width?: string;
+  height?: string;
 }
 const props = withDefaults(defineProps<Props>(), {
   modelValue: false
@@ -39,14 +41,11 @@ const closeDialog = () => {
   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;
@@ -54,13 +53,16 @@ const closeDialog = () => {
   }
 }
 .dialog {
-  padding: 0 15px;
+  padding: 0 20px;
   .dialog-header {
     width: 100%;
-    height: 35px;
+    height: 70px;
     display: flex;
     justify-content: space-between;
     align-items: center;
+    .dialog-title {
+      font-size: 36px;
+    }
     .icon-close {
       cursor: pointer;
     }

+ 331 - 0
src/components/HKVideo/hikvision-player.vue

@@ -0,0 +1,331 @@
+<!-- 海康威视组件
+https://blog.csdn.net/weixin_53322542/article/details/134334347
+-->
+<template>
+  <div :id="state.id" class="playWnd"></div>
+</template>
+
+<script setup lang="ts" name="hikvision-player">
+import { ref, reactive, onMounted, onDeactivated, onActivated, onUnmounted } from 'vue';
+const WebControl = window.WebControl; //index.html引入 直接从window里面拿到sdk
+const JSEncrypt = window.JSEncrypt; //index.html引入 直接从window里面拿到sdk
+const props = defineProps({
+  wsUrl: {
+    type: String //视频监控code
+  },
+  objData: {}
+});
+const objData = props.objData;
+// console.log(props.wsUrl, '====>wsurl')
+const state = reactive({
+  id: 'playWnd' + Math.random().toString(16).slice(2), //多个监控同时显示需要不同的id
+  idWidth: 0 as any,
+  idHeight: 0 as any,
+  initCount: 0,
+  pubKey: '',
+  oWebControl: null as any
+});
+// console.log(state)
+//创建播放实例
+const initPlugin = () => {
+  console.log('initPlugin');
+  state.oWebControl = new WebControl({
+    szPluginContainer: state.id, // 指定容器id
+    iServicePortStart: 15900, // 指定起止端口号,建议使用该值
+    iServicePortEnd: 15900,
+    // szClassId: '23BF3B0A-2C56-4D97-9C03-0CB103AA8F11', // 用于IE10使用ActiveX的clsid
+    cbConnectSuccess: function () {
+      // 创建WebControl实例成功
+      state.oWebControl
+        .JS_StartService('window', {
+          // WebControl实例创建成功后需要启动服务
+          dllPath: './VideoPluginConnect.dll' // 值"./VideoPluginConnect.dll"写死
+        })
+        .then(
+          () => {
+            console.log('启动成功');
+            // 启动插件服务成功
+            state.oWebControl.JS_SetWindowControlCallback({
+              // 设置消息回调
+              cbIntegrationCallBack: cbIntegrationCallBack
+            });
+
+            state.oWebControl.JS_CreateWnd(state.id, state.idWidth, state.idHeight).then(() => {
+              console.log('JS_CreateWnd success');
+              //JS_CreateWnd创建视频播放窗口,宽高可设定
+              init(); // 创建播放实例成功后初始化
+            });
+          },
+          function () {
+            console.log('启动失败');
+            // 启动插件服务失败
+          }
+        );
+    },
+    cbConnectError: function () {
+      // 创建WebControl实例失败
+      state.oWebControl = null;
+      console.log('插件未启动,正在尝试启动,请稍候...');
+      WebControl.JS_WakeUp('VideoWebPlugin://'); // 程序未启动时执行error函数,采用wakeup来启动程序
+      state.initCount++;
+      if (state.initCount < 3) {
+        setTimeout(function () {
+          initPlugin();
+        }, 3000);
+      } else {
+        console.log('插件启动失败,请检查插件是否安装!');
+      }
+    },
+    cbConnectClose: function (bNormalClose) {
+      // 异常断开:bNormalClose = false
+      // JS_Disconnect正常断开:bNormalClose = true
+      console.log('cbConnectClose', bNormalClose);
+      state.oWebControl = null;
+      if (!bNormalClose) {
+        console.log('插件未启动,正在尝试启动,请稍候...');
+        WebControl.JS_WakeUp('VideoWebPlugin://');
+        state.initCount++;
+        if (state.initCount < 3) {
+          setTimeout(function () {
+            initPlugin();
+          }, 3000);
+        } else {
+          console.log('插件启动失败,请检查插件是否安装!');
+        }
+      }
+    }
+  });
+};
+// 设置窗口控制回调
+const setCallbacks = () => {
+  state.oWebControl.JS_SetWindowControlCallback({
+    cbIntegrationCallBack: cbIntegrationCallBack
+  });
+};
+
+// 推送消息
+const cbIntegrationCallBack = (oData) => {
+  console.log(oData.responseMsg, '--oData');
+  if (oData.responseMsg.type === 7) {
+    doubleClick();
+  }
+};
+// 双击窗口放大
+const doubleClick = () => {
+  state.oWebControl
+    .JS_RequestInterface({
+      funcName: 'setFullScreen'
+    })
+    .then(function (oData) {
+      console.log(oData);
+      // showCBInfo(JSON.stringify(oData ? oData.responseMsg : ''))
+    });
+};
+//初始化
+const init = () => {
+  getPubKey(() => {
+    // 请自行修改以下变量值
+    let appkey = objData.appkey; //综合安防管理平台提供的appkey,必填
+    let secret = setEncrypt(objData.secret); //综合安防管理平台提供的secret,必填
+    let ip = objData.ip; //综合安防管理平台IP地址,必填
+    let playMode = objData.playMode; //初始播放模式:0-预览,1-回放
+    let port = objData.port; //综合安防管理平台端口,若启用HTTPS协议,默认443
+    let snapDir = 'D:\\SnapDir'; //抓图存储路径
+    let videoDir = 'D:\\VideoDir'; //紧急录像或录像剪辑存储路径
+    let layout = objData.layout; //playMode指定模式的布局
+    let enableHTTPS = 1; //是否启用HTTPS协议与综合安防管理平台交互,这里总是填1
+    let encryptedFields = 'secret'; //加密字段,默认加密领域为secret
+    let showToolbar = objData.showToolbar; //是否显示工具栏,0-不显示,非0-显示
+    let showSmart = 0; //是否显示移动框线框,0-不显示,非0-显示
+    let buttonIDs = '0,16,256,257,258,259,260,512,513,514,515,516,517,768,769'; //自定义工具条按钮
+    // 请自行修改以上变量值
+
+    state.oWebControl
+      .JS_RequestInterface({
+        funcName: 'init',
+        argument: JSON.stringify({
+          appkey: appkey, //API网关提供的appkey
+          secret: secret, //API网关提供的secret
+          ip: ip, //API网关IP地址
+          playMode: playMode, //播放模式(决定显示预览还是回放界面)
+          port: port, //端口
+          snapDir: snapDir, //抓图存储路径
+          videoDir: videoDir, //紧急录像或录像剪辑存储路径
+          layout: layout, //布局
+          enableHTTPS: enableHTTPS, //是否启用HTTPS协议
+          encryptedFields: encryptedFields, //加密字段
+          showToolbar: showToolbar, //是否显示工具栏
+          showSmart: showSmart, //是否显示智能信息
+          buttonIDs: buttonIDs //自定义工具条按钮
+        })
+      })
+      .then(function (oData: any) {
+        console.log(oData, 'oData');
+        state.oWebControl.JS_Resize(state.idWidth, state.idHeight); // 初始化后resize一次,规避firefox下首次显示窗口后插件窗口未与DIV窗口重合问题
+        props.wsUrl && previewClick();
+      });
+  });
+};
+
+//获取公钥
+const getPubKey = (callback: any) => {
+  state.oWebControl
+    .JS_RequestInterface({
+      funcName: 'getRSAPubKey',
+      argument: JSON.stringify({
+        keyLength: 1024
+      })
+    })
+    .then((oData: { responseMsg: { data: string } }) => {
+      console.log('getPubKey', oData);
+      if (oData.responseMsg.data) {
+        state.pubKey = oData.responseMsg.data;
+        callback();
+      }
+    });
+};
+
+//RSA加密
+const setEncrypt = (value: string) => {
+  var encrypt = new JSEncrypt();
+  encrypt.setPublicKey(state.pubKey);
+  return encrypt.encrypt(value);
+};
+
+// 监听resize事件,使插件窗口尺寸跟随DIV窗口变化
+window.addEventListener('resize', () => {
+  console.log('resize');
+  if (state.oWebControl != null) {
+    getElementWidth();
+    state.oWebControl.JS_Resize(state.idWidth, state.idHeight);
+    // setWndCover();
+  }
+});
+
+// 监听滚动条scroll事件,使插件窗口跟随浏览器滚动而移动
+window.addEventListener('scroll', () => {
+  if (state.oWebControl != null) {
+    getElementWidth();
+    state.oWebControl.JS_Resize(state.idWidth, state.idHeight);
+    // setWndCover();
+  }
+});
+
+//视频预览功能
+const previewClick = (data) => {
+  console.log('previewClick ===========>>>>');
+  var cameraIndexCode = data ? data : (props.wsUrl as any);
+  // var cameraIndexCode = props.wsUrl as any //获取输入的监控点编号值,必填
+  var streamMode = 0; //主子码流标识:0-主码流,1-子码流
+  var transMode = 1; //传输协议:0-UDP,1-TCP
+  var gpuMode = 0; //是否启用GPU硬解,0-不启用,1-启用
+  var wndId = -1; //播放窗口序号(在2x2以上布局下可指定播放窗口)
+
+  state.oWebControl.JS_RequestInterface({
+    funcName: 'startPreview',
+    argument: JSON.stringify({
+      cameraIndexCode: cameraIndexCode, //监控点编号
+      streamMode: streamMode, //主子码流标识
+      transMode: transMode, //传输协议
+      gpuMode: gpuMode, //是否开启GPU硬解
+      wndId: wndId //可指定播放窗口
+    })
+  });
+  console.log(state.oWebControl.JS_RequestInterface, ' ------face');
+  console.log('当前的编号', cameraIndexCode);
+  console.log('执行完成');
+};
+
+const playbackClick = (data, begin_time, end_time) => {
+  console.log('playbackClick ===========>>>> ');
+  var cameraIndexCode = data ? data : (props.wsUrl as any);
+  var startTimeStamp = begin_time; //回放开始时间戳
+  var endTimeStamp = end_time; //回放结束时间戳
+  var recordLocation = 0; //录像存储位置:0-中心存储,1-设备存储
+  var transMode = 1; //传输协议:0-UDP,1-TCP
+  var gpuMode = 0; //是否启用GPU硬解,0-不启用,1-启用
+  var wndId = -1; //播放窗口序号(在2x2以上布局下可指定播放窗口)
+
+  state.oWebControl.JS_RequestInterface({
+    funcName: 'startPlayback',
+    argument: JSON.stringify({
+      cameraIndexCode: cameraIndexCode, //监控点编号
+      startTimeStamp: Math.floor(startTimeStamp / 1000).toString(), //回放开始时间戳
+      endTimeStamp: Math.floor(endTimeStamp / 1000).toString(), //回放结束时间戳
+      recordLocation: recordLocation, //录像存储位置
+      transMode: transMode, //传输协议
+      gpuMode: gpuMode, //是否开启GPU硬解
+      wndId: wndId //可指定播放窗口
+    })
+  });
+  console.log(state.oWebControl.JS_RequestInterface, ' ------face');
+  console.log('当前的编号', cameraIndexCode);
+  console.log('执行完成');
+};
+
+// 停止全部预览
+const stopAllPreview = () => {
+  state.oWebControl.JS_RequestInterface({
+    funcName: 'stopAllPreview'
+  });
+  console.log('stopAllPreview');
+};
+// 获取元素宽高
+const getElementWidth = () => {
+  let scale = window.innerWidth / 3840;
+  state.idWidth = document.getElementById(state.id)?.offsetWidth;
+  state.idHeight = document.getElementById(state.id)?.offsetHeight;
+
+  state.idWidth = parseInt(state.idWidth * scale);
+  state.idHeight = parseInt(state.idHeight * scale);
+};
+
+onMounted(() => {
+  getElementWidth();
+  initPlugin();
+});
+onActivated(() => {
+  getElementWidth();
+  initPlugin();
+});
+onDeactivated(() => {
+  console.log('销毁了');
+  state.oWebControl.JS_HideWnd();
+  state.oWebControl.JS_Disconnect().then(
+    function () {
+      //断开与插件服务连接成功
+      console.log('断开与插件服务连接成功');
+    },
+    function () {
+      //断开与插件服务连接失败
+      console.log('断开与插件服务连接失败');
+    }
+  );
+});
+onUnmounted(() => {
+  console.log('销毁了');
+  state.oWebControl.JS_HideWnd();
+  state.oWebControl.JS_Disconnect().then(
+    function () {
+      //断开与插件服务连接成功
+      console.log('断开与插件服务连接成功');
+    },
+    function () {
+      //断开与插件服务连接失败
+      console.log('断开与插件服务连接失败');
+    }
+  );
+});
+// 暴露给父组件调用
+defineExpose({
+  previewClick,
+  playbackClick,
+  stopAllPreview
+});
+</script>
+<style>
+.playWnd {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 7 - 6
src/components/HKVideo/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="dot-box" :style="{ width: width ? width + 'px' : '100%', height: height ? height + 'px' : '190px' }">
-    <div class="video-box">
+    <div class="video-box" @click="play_now">
       <HikvisionPlayer
         v-if="isPlaying"
         ref="videoPlayer"
@@ -8,9 +8,8 @@
         @on-playing="onHkPlaying"
         @on-play-error="onHKPlayError"
       ></HikvisionPlayer>
-      <img v-if="posterVisible" class="video-poster" src="@/assets/images/video/video_poster.png" alt="" @click="play_now" />
-      <img v-if="posterVisible" class="video-play" src="@/assets/images/video/play.png" alt="" @click="play_now" />
-<!--      <img v-if="posterVisible" class="video-poster" :src="dot_data.poster" />-->
+      <img v-if="posterVisible" class="video-play" src="@/assets/images/video/play.png" alt="" />
+      <img v-if="posterVisible && dot_data.poster" class="video-poster" :src="dot_data.poster" />
       <div v-if="errBKVisible" class="err_bk">
         <div class="err_box">
           <div class="err_inner_box">
@@ -111,14 +110,14 @@ const stop_now = async () => {
   width: 100%;
   display: flex;
   justify-content: space-between;
-  background: url('@/assets/images/emergencyCommandMap/videoBox1.png') no-repeat 100% 100%;
+  background-color: #000;
 }
 
 .video-box {
   width: 100%;
   height: 100%;
   overflow: hidden;
-
+  cursor: pointer;
   position: relative;
   border-radius: 2px;
   //border: 1px solid #0056cf;
@@ -126,6 +125,7 @@ const stop_now = async () => {
   .video-poster {
     width: 100%;
     height: 100%;
+    cursor: pointer;
   }
   .video-play {
     width: 72px;
@@ -134,6 +134,7 @@ const stop_now = async () => {
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
+    cursor: pointer;
   }
 
   .err_bk {

+ 150 - 0
src/components/HKVideo/modal-video.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="modal-box-video">
+    <div class="modal-content-video">
+      <div class="video-title">
+        <div class="video-name">{{ dot_data.name }}</div>
+        <div class="video-name-right">
+          <div class="inspect-time"></div>
+          <div class="close_btn" @click="handleClose"
+        </div>
+      </div>
+    </div>
+    <div class="video-content">
+      <img class="video-poster" v-if="isPlaying == false" :src="dot_data.poster" />
+
+      <HikVideo v-if="isPlaying"
+                :wsUrl="dot_data.code"
+                :objData="HKobjData"
+                ref="hkplayer"
+      ></HikVideo>
+    </div>
+  </div>
+  </div>
+</template>
+
+<script setup name="modal-video">
+import { ref, onUnmounted, nextTick, defineProps, defineEmits } from 'vue';
+import HikVideo from '@/components/HKVideo/hikvision-player.vue';
+
+const props = defineProps({
+  dot_data: Object,
+  isPlaying: Boolean
+});
+
+const emits = defineEmits([
+  'close_modal'
+]);
+
+const handleClose = async () => {
+  if (props.isPlaying) {
+    hkplayer.value.stopAllPreview();
+    setTimeout(() => {
+      emits('close_modal');
+    }, 500);
+  } else {
+    emits('close_modal');
+  }
+};
+
+const HKobjData = ref({
+  // 海康初始化数据
+  appkey: '28132932', //综合安防管理平台提供的appkey,必填
+  ip: '19.155.243.66', //综合安防管理平台IP地址,必填
+  secret: 'vPUDL4ilt2mENr8XEfa7', //综合安防管理平台提供的secret,必填
+  port: 443, //综合安防管理平台端口,若启用HTTPS协议,默认443
+  playMode: 0, // 0 预览 1回放
+  layout: '1x1', //页面展示的模块数【16】
+  showToolbar: 1 //是否显示工具栏,0-不显示,非0-显示
+});
+
+const hkplayer = ref(null);
+
+nextTick(() => {
+  console.log('props.isPlaying', props.isPlaying);
+  if (props.isPlaying) {
+    hkplayer.value.previewClick('');
+  }
+});
+
+onUnmounted(() => {
+  // console.log("modal-video", "onUnmounted")
+  if (hkplayer.value) {
+    hkplayer.value.dispose();
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.modal-box-video {
+  display: flex;
+  align-items: flex-start;
+  position: relative;
+  background: #001B41;
+  padding: 0px;
+  width: 100%;
+  height: 100%;
+
+  .modal-content-video {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    overflow: hidden;
+
+    .video-title {
+      height: 58px;
+      width: 990px;
+      background-image: url("@/assets/img/video-title.png");
+
+      line-height: 58px;
+      padding: 0 20px 0 30px;
+      font-size: 22px;
+      color: #ffffffb3;
+      font-family: PingFangSC-Regular;
+      white-space: nowrap;
+
+      display: flex;
+      justify-content: space-between;
+
+      .video-name-right {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+
+        .inspect-time {
+          font-size: 20px;
+          margin-right: 10px;
+        }
+
+        .close_btn {
+          width: 92px;
+          height: 36px;
+          background-image: url("@/assets/img/close2.png");
+        }
+      }
+    }
+
+    .video-content {
+      height: 600px;
+      overflow: hidden;
+
+      .video-js {
+        width: 100%;
+        height: 100%;
+        object-fit: fill;
+      }
+
+      .vjs-poster {
+        cursor: default;
+      }
+
+      .video-poster {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}
+
+</style>

+ 163 - 0
src/components/HKVideo/video-container.vue

@@ -0,0 +1,163 @@
+<template>
+  <div v-if="videoData.length === 1" class="video-container1 ">
+    <div class="video-item">
+      <div class="name">{{ videoData[0].name }}</div>
+      <HKVideo :dot_data="videoData[0]" :height="900" />
+    </div>
+  </div>
+  <div v-else-if="videoData.length >= 2 && videoData.length <= 4" class="video-container2">
+    <div v-for="(item, index) in videoData" :key="index" class="video-item">
+      <div class="name">{{ item.name }}</div>
+      <HKVideo :dot_data="item" :height="444" />
+    </div>
+  </div>
+  <div v-else-if="videoData.length >= 5 && videoData.length <= 6" class="video-container3">
+    <div class="box1">
+      <div class="video-item2">
+        <div class="name">{{ videoData[0].name }}</div>
+        <HKVideo :dot_data="videoData[0]" :height="596" />
+      </div>
+    </div>
+    <div class="box2">
+      <div class="video-item3">
+        <div class="name">{{ videoData[1].name }}</div>
+        <HKVideo :dot_data="videoData[1]" :height="292" />
+      </div>
+      <div class="video-item3">
+        <div class="name">{{ videoData[2].name }}</div>
+        <HKVideo :dot_data="videoData[2]" :height="292" />
+      </div>
+    </div>
+    <div class="box3">
+      <div class="video-item">
+        <div class="name">{{ videoData[3].name }}</div>
+        <HKVideo :dot_data="videoData[3]" :height="292" />
+      </div>
+      <div class="video-item">
+        <div class="name">{{ videoData[4].name }}</div>
+        <HKVideo :dot_data="videoData[4]" :height="292" />
+      </div>
+      <div class="video-item">
+        <div class="name">{{ videoData[5].name }}</div>
+        <HKVideo :dot_data="videoData[5]" :height="292" />
+      </div>
+    </div>
+  </div>
+  <div v-else-if="videoData.length >= 7" class="video-container4">
+    <div v-for="(item, index) in videoData" :key="index" class="video-item">
+      <div class="name">{{ item.name }}</div>
+      <HKVideo :dot_data="item" :height="292" />
+    </div>
+  </div>
+  <div v-else class="tip">附近没有可用监控视频</div>
+</template>
+
+<script lang="ts" setup>
+interface Props {
+  videoData: any[];
+}
+const props = withDefaults(defineProps<Props>(), {
+});
+</script>
+
+<style lang="scss" scoped>
+.video-container1 {
+  width: 100%;
+  height: 100%;
+  .video-item {
+    width: 100%;
+    height: 100%;
+  }
+}
+.video-container2 {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  height: 100%;
+  .video-item {
+    width: calc(50% - 12px);
+    height: 432px;
+    margin-bottom: 12px;
+    margin-right: 12px;
+    &:nth-child(2n) {
+      margin-right: 0;
+    }
+  }
+}
+.video-container3 {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  height: 100%;
+  .box1 {
+    width: calc(66.66666% - 6px);
+    height: 596px;
+    margin-bottom: 12px;
+    margin-right: 12px;
+    .video-item2 {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .box2 {
+    width: calc(33.33333% - 6px);
+    height: 596px;
+    .video-item3 {
+      width: 100%;
+      height: 292px;
+      margin-bottom: 12px;
+      &:nth-child(2) {
+        margin-top: 12px;
+      }
+    }
+  }
+  .box3 {
+    width: 100%;
+    height: 292px;
+    display: flex;
+  }
+  .video-item {
+    width: calc(33.33333% - 8px);
+    height: 292px;
+    margin-bottom: 12px;
+    margin-right: 12px;
+    &:nth-child(3n) {
+      margin-right: 0;
+    }
+  }
+}
+.video-container4 {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  .video-item {
+    width: calc(33.33333% - 8px);
+    height: 292px;
+    margin-bottom: 12px;
+    margin-right: 12px;
+    &:nth-child(3n) {
+      margin-right: 0;
+    }
+  }
+}
+.tip {
+  width: 100%;
+  height: 100%;
+  font-size: 44px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.video-item, .video-item2, .video-item3 {
+  position: relative;
+  .name {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    color: #fff;
+    z-index: 11;
+  }
+}
+</style>

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

@@ -21,6 +21,7 @@ declare module 'vue' {
     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']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -66,6 +67,7 @@ declare module 'vue' {
     HeaderSearch: typeof import('./../components/HeaderSearch/index.vue')['default']
     HeaderSection: typeof import('./../components/HeaderSection/index.vue')['default']
     HikvisionH5player: typeof import('./../components/HKVideo/hikvision-h5player.vue')['default']
+    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']
     IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
@@ -76,6 +78,7 @@ declare module 'vue' {
     LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
     Map: typeof import('./../components/Map/index.vue')['default']
     MapLogical: typeof import('./../components/Map/MapLogical.vue')['default']
+    ModalVideo: typeof import('./../components/HKVideo/modal-video.vue')['default']
     MultiInstanceUser: typeof import('./../components/Process/multiInstanceUser.vue')['default']
     Pagination: typeof import('./../components/Pagination/index.vue')['default']
     ParentView: typeof import('./../components/ParentView/index.vue')['default']
@@ -94,6 +97,7 @@ declare module 'vue' {
     TopNav: typeof import('./../components/TopNav/index.vue')['default']
     TreeSelect: typeof import('./../components/TreeSelect/index.vue')['default']
     UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
+    VideoContainer: typeof import('./../components/HKVideo/video-container.vue')['default']
     YMap: typeof import('./../components/Map/YMap.vue')['default']
     YMapold: typeof import('./../components/Map/YMapold.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/index.vue')['default']

+ 11 - 6
src/views/globalMap/AnalyzeDataDialog.vue

@@ -1,19 +1,25 @@
 <template>
+  <div>空间分析</div>
   <div class="analyze-data-container">
     <div class="item">
-      <div class="item-label">行政镇</div>
+      <div class="item-label">行政镇(个)</div>
       <div class="item-value">{{ analysisSpatialData.townName }}个</div>
     </div>
     <div class="item">
-      <div class="item-label">人口</div>
-      <div class="item-value">{{ analysisSpatialData.populationNum }}个</div>
+      <div class="item-label">行政村(个)</div>
+      <div class="item-value">{{ analysisSpatialData.townName }}个</div>
     </div>
     <div class="item">
-      <div class="item-label">面积</div>
+      <div class="item-label">面积(km²)</div>
       <div class="item-value">{{ analysisSpatialData.area }}平方米</div>
     </div>
     <div class="item">
-      <div class="item-label">专家</div>
+      <div class="item-label">常住人口(万)</div>
+      <div class="item-value">{{ analysisSpatialData.populationNum }}个</div>
+    </div>
+
+    <div class="item">
+      <div class="item-label">GDP(万元)</div>
       <div class="item-value">{{ analysisSpatialData.expert }}个</div>
     </div>
 <!--    <div class="item">-->
@@ -73,7 +79,6 @@ const analysisSpatialData = computed(() => {
   position: absolute;
   top: 30px;
   right: 30px;
-  background-color: #041d55;
   color: #fff;
   font-size: 16px;
   padding: 15px;

+ 36 - 10
src/views/globalMap/LeftMenu.vue

@@ -53,12 +53,11 @@
 
 <script lang="ts" setup>
 import { getEmergencyExpertNameLike } from '@/api/globalMap';
+import { listMenu } from '@/api/system/menu';
 
-interface Props {
-  menuData: object;
-}
-const props = withDefaults(defineProps<Props>(), {});
-const emits = defineEmits(['switchMap', 'addMarkers', 'clickMenu', 'selectSearchMarker']);
+const emits = defineEmits(['switchMap', 'clickMenu', 'selectSearchMarker']);
+// 左侧菜单
+let menuData = ref([]);
 
 const searchState = reactive({
   searchText: '',
@@ -112,12 +111,39 @@ const changeBoxShow = (item) => {
 // 处理菜单点击事件
 const handleClick = (item) => {
   item.checked = !item.checked;
-  if (item.component === '2') {
-    emits('addMarkers', item);
-  } else {
-    emits('clickMenu', item);
-  }
+  emits('clickMenu', item);
+  // if (item.component === '2') {
+  //   emits('addMarkers', item);
+  // } else {
+  //
+  // }
 };
+
+const initData = () => {
+  listMenu().then((res: any) => {
+    const data = res.data ? res.data[0]?.children : [];
+    data.forEach((item) => {
+      item.show = true;
+      item.name = item.meta?.title;
+      item.children?.forEach((item2) => {
+        item2.show = true;
+        item2.name = item2.meta?.title;
+        item2.children?.forEach((item3) => {
+          item3.name = item3.meta?.title;
+          if (item3.component === '2' && !!item3.path) {
+            item3.checked = false;
+          }
+        });
+      });
+    });
+    menuData.value = data;
+    console.log(menuData.value);
+  });
+};
+
+onMounted(() => {
+  initData();
+});
 </script>
 
 <style lang="scss" scoped>

+ 101 - 0
src/views/globalMap/RightMenu/Reservoir.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="container">
+    <div class="title">江湖河库</div>
+    <div>
+      <el-input v-model="queryParams.name" :prefix-icon="Search" size="large" clearable />
+    </div>
+    <div>视频类型</div>
+    <el-table :data="listData" border style="width: 100%">
+      <el-table-column prop="data1" show-overflow-tooltip align="center">
+        <template #header>
+          <el-select v-model="queryParams.range" placeholder="所有区县" size="large">
+            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column prop="name" label="名称" show-overflow-tooltip align="center">
+        <template #default="scope">
+          <div style="color: #409eff; cursor: pointer" @click="handleShowDialog(scope.row)">
+            {{ scope.row.name }}
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total > queryParams.size"
+      v-model:page="queryParams.current"
+      v-model:limit="queryParams.size"
+      :total="total"
+      @pagination="initData"
+    />
+    <Dialog v-if="showDialog" v-model="showDialog" title="江湖河库" width="2500px" height="1200px">
+      <div style="width: 2500px; height: 1120px; display: flex; justify-content: center; align-items: center">
+        <HKVideo :dot_data="videoMonitorData" :width="2400" :height="1000" />
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Search } from '@element-plus/icons-vue';
+import Dialog from '@/components/Dialog/index2.vue';
+import { getWaterList2 } from '@/api/globalMap/reservoir';
+
+const queryParams = reactive({
+  current: 1,
+  size: 10,
+  name: '',
+  type: '检测站',
+  range: ''
+});
+const total = ref(0);
+
+const options = reactive([
+  {
+    value: '茂南区',
+    label: '茂南区'
+  },
+  {
+    value: '高州市',
+    label: '高州市'
+  },
+  {
+    value: '电白区',
+    label: '电白区'
+  },
+  {
+    value: '信宜市',
+    label: '信宜市'
+  },
+  {
+    value: '化州市',
+    label: '化州市'
+  }
+]);
+const listData = ref([{ data1: '高州', data2: '高州市根子水库' }]);
+
+let showDialog = ref(false);
+// 请求参数
+const queryParams2 = reactive({
+  current: 1,
+  size: 1
+});
+let videoMonitorData = ref({});
+const handleShowDialog = (row) => {
+  showDialog.value = false;
+  nextTick(() => {
+    videoMonitorData.value = row;
+    showDialog.value = true;
+  });
+};
+const initData = () => {
+  getWaterList2(queryParams).then((res) => {
+    listData.value = res.data.list;
+  });
+};
+initData();
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 331 - 0
src/views/globalMap/RightMenu/ReservoirMonitor.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="title">水库监测</div>
+  <div style="margin-top: 20px">更新时间:{{ ReservoirMonitorData.time }}</div>
+  <div class="flex" style="margin-top: 20px">
+    <div style="margin-right: 20px">
+      <div>漫坝</div>
+      {{ ReservoirMonitorData.data1 }}
+    </div>
+    <div style="margin-right: 20px">
+      <div>超汛限</div>
+      {{ ReservoirMonitorData.data2 }}
+    </div>
+    <div>
+      <div>超堰顶(在溢洪)</div>
+      {{ ReservoirMonitorData.data3 }}
+    </div>
+  </div>
+  <el-table :data="ReservoirMonitorData.listData" border style="width: 100%">
+    <el-table-column prop="name" label="名称" show-overflow-tooltip align="center">
+      <template #default="scope">
+        <div style="color: #409eff; cursor: pointer" @click="handleShowDialog(scope.row)">
+          {{ scope.row.name }}
+        </div>
+      </template>
+    </el-table-column>
+    <el-table-column prop="area" show-overflow-tooltip align="center">
+      <template #header>
+        <el-select v-model="queryParams.area" placeholder="所属区县2" size="large">
+          <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </template>
+    </el-table-column>
+    <el-table-column prop="floodLimit" label="汛限水位(m)" show-overflow-tooltip sortable align="center" />
+    <el-table-column prop="waterStor" label="当前水位(m)" show-overflow-tooltip sortable align="center" />
+    <el-table-column prop="waterDiff" label="差值" show-overflow-tooltip sortable align="center" />
+  </el-table>
+  <pagination
+    v-show="total > queryParams.size"
+    v-model:page="queryParams.current"
+    v-model:limit="queryParams.size"
+    :total="total"
+    @pagination="initData"
+  />
+  <Dialog v-model="showDialog" title="水库监测" width="2500px" height="1200px">
+    <div class="flex">
+      <div class="detail-container">
+        <div class="flex">
+          <div style="font-size: 44px;margin-right: 10px">{{ selectRow.name }}</div>
+          <div>{{ selectRow.address }}</div>
+        </div>
+        <div class="flex" style="justify-content: space-between">
+          <div class="flex">
+            <div>汛限水位</div>
+            <div>{{ selectRow.floodLimit }}米</div>
+          </div>
+          <div class="flex">
+            <div>当前水位</div>
+            <div>{{ selectRow.waterStor }}米</div>
+          </div>
+        </div>
+        <div>
+          <div style="font-size: 44px">水位过程图</div>
+          <Chart :option="chartOption1" style="width: 100%; height: 400px" />
+        </div>
+        <div class="table-wrap">
+          <div class="table-title">
+            <div
+              class="title-item"
+              v-for="(item, index) in tableTitle"
+              :key="index"
+            >
+              {{ item }}
+            </div>
+          </div>
+          <div class="content-wrap">
+            <div
+              class="content-item"
+              v-for="(item, index) in detailData"
+              :key="index"
+            >
+              <div class="time">{{ item.time }}</div>
+              <div class="value">{{ item.waterStor }}</div>
+              <div class="value1">{{ item.inFlow ? item.inFlow : '-' }}</div>
+              <div class="value2">{{ item.ouFlow ? item.ouFlow : '-' }}</div>
+              <div
+                class="waterunit"
+                :style="{
+                  color: item.ss == '涨' ? '#fc5a5a' : item.ss == '落' ? '#00e9ea' : '#cbddff'
+                }"
+              >
+                {{ item.ss }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="right-box">
+        <div class="flex" style="width: 100%; align-items: center">
+          <div style="font-size: 44px; flex-shrink: 0; margin-right: 10px">附近视频</div>
+          <el-select v-model="queryParams2.radius" size="large" style="width: 150px" @change="getVideoList">
+            <el-option v-for="item in radiusOption" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </div>
+        <div class="video-box">
+          <video-container :video-data="videoMonitorData" />
+        </div>
+      </div>
+    </div>
+  </Dialog>
+</template>
+
+<script lang="ts" setup name="ReservoirMonitor">
+import Dialog from '@/components/Dialog/index2.vue';
+import { option3 } from './echartOptions';
+import { getEmergencyVideoCata } from '@/api/routineCommandMap';
+import { getReservoirCourseLevel, getReservoirList } from '@/api/globalMap/reservoirMonitor';
+import { parseTime } from '@/utils/ruoyi';
+import { getVideoInfo } from '@/api/globalMap';
+
+const options = reactive([
+  {
+    value: '茂南区',
+    label: '茂南区'
+  },
+  {
+    value: '高州市',
+    label: '高州市'
+  },
+  {
+    value: '电白区',
+    label: '电白区'
+  },
+  {
+    value: '信宜市',
+    label: '信宜市'
+  },
+  {
+    value: '化州市',
+    label: '化州市'
+  }
+]);
+// 请求参数
+const queryParams = reactive({
+  current: 1,
+  size: 10,
+  area: ''
+});
+// 总数量
+let total = ref(0);
+// 数据
+const ReservoirMonitorData = reactive({
+  time: '',
+  data1: '',
+  data2: '',
+  data3: '',
+  listData: []
+});
+// 获取数据
+const initData = async () => {
+  getReservoirList(queryParams).then((res) => {
+    total.value = res.total;
+    ReservoirMonitorData.listData = res.rows;
+  });
+};
+
+initData();
+
+// 弹窗数据
+const selectRow = ref({
+  name: '',
+  address: '',
+  floodLimit: '',
+  waterStor: ''
+});
+const detailData = ref([]);
+const tableTitle = ref(['时间', '库上水位(m)', '入库流量(m²/s)', '出库流量(m²/s)', '水势']);
+let chartOption1 = ref(option3);
+let showDialog = ref(false);
+let videoMonitorData = ref([]);
+const radiusOption = reactive([
+  { label: '500米', value: '500' },
+  { label: '1000米', value: '1000' },
+  { label: '1500米', value: '1500' },
+  { label: '2000米', value: '2000' }
+]);
+// 请求参数
+const queryParams2 = reactive({
+  location: '',
+  radius: '500'
+});
+const handleShowDialog = (row) => {
+  showDialog.value = false;
+  videoMonitorData.value = [];
+  // queryParams2.location = '';
+  // queryParams2.radius = '500';
+  nextTick(() => {
+    selectRow.value = row;
+    getReservoirCourseLevel({ code: row.code }).then((res) => {
+      const data = res.data.list;
+      const time = [];
+      const resultData = [];
+      const inFlowData2 = [];
+      const outFlowData = [];
+
+      data.forEach((item) => {
+        time.push(parseTime(item.time, '{h}'));
+        resultData.push(item.waterStor);
+        inFlowData2.push(item.inFlow);
+        outFlowData.push(item.outFlow);
+        item.time = parseTime(item.time, '{m}-{d} {h}:{i}');
+      });
+      detailData.value = data;
+      chartOption1.value.xAxis.data = time;
+      chartOption1.value.series[0].data = resultData;
+      chartOption1.value.series[1].data = inFlowData2;
+      chartOption1.value.series[2].data = outFlowData;
+      chartOption1.value.series[0].markLine.data[0].yAxis = row.floodLimit;
+    });
+    queryParams2.location = `POINT(${row.longitude} ${row.latitude})`;
+    getVideoList();
+    showDialog.value = true;
+  });
+};
+const getVideoList = () => {
+  getVideoInfo(queryParams2).then((res) => {
+    res.data?.list.forEach((item) => {
+      item.video_code = item.indexcode;
+    });
+    videoMonitorData.value = res.data?.list;
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.detail-container {
+  font-size: 36px;
+  width: 1000px;
+  .dialog-content {
+    display: flex;
+  }
+  .info-box {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    height: 520px;
+    background-color: #d7d7d7;
+    padding: 10px;
+    .info-item {
+      display: flex;
+    }
+  }
+}
+.right-box {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  background-color: #d7d7d7;
+  padding: 10px;
+  height: 1009px;
+  .video-box {
+    margin-top: 30px;
+    height: 900px;
+    display: flex;
+    flex-wrap: wrap;
+  }
+}
+
+.table-wrap {
+  height: 400px;
+  // margin-top: 10px;
+  font-size: 28px;
+  background-color: #102043;
+  color: #fff;
+  .table-title {
+    height: 70px;
+    display: flex;
+    align-items: center;
+    border-bottom: 2px solid #465979;
+    .title-item {
+      width: 195px;
+      text-align: center;
+      color: #4c97f6;
+    }
+  }
+  .content-wrap {
+    height: 92%;
+    overflow-y: auto;
+    .content-item {
+      display: flex;
+      justify-content: space-around;
+      align-items: center;
+      height: 70px;
+      font-size: 28px;
+      margin-bottom: 8.5px;
+      background: #0b2b5a;
+      > div {
+        width: 200px;
+        text-align: center;
+      }
+      // .time {
+      //   width: 120px;
+      // }
+      // .value {
+      //   width: 120px;
+      // }
+      // .value1 {
+      //   width: 120px;
+      // }
+      // .value2 {
+      //   width: 120px;
+      // }
+      // .waterunit {
+      //   width: 120px;
+      // }
+    }
+    //.content-item:nth-child(2n) {
+    //  background: rgba(11, 43, 90, 0.2);
+    //}
+  }
+}
+
+.text-green {
+  color: #67c23a;
+}
+.text-warning {
+  color: #e6a23c;
+}
+.text-danger {
+  color: #f56c6c;
+}
+</style>

+ 262 - 0
src/views/globalMap/RightMenu/RiverMonitor.vue

@@ -0,0 +1,262 @@
+<template>
+  <div class="title">河道监测</div>
+  <div style="margin-top: 20px">更新时间:{{ riverMonitorData.time }}</div>
+  <div class="flex" style="margin-top: 20px">
+    <div style="margin-right: 20px">
+      <div>漫坝</div>
+      {{ riverMonitorData.data1 }}
+    </div>
+    <div style="margin-right: 20px">
+      <div>超汛限</div>
+      {{ riverMonitorData.data2 }}
+    </div>
+    <div>
+      <div>超堰顶(在溢洪)</div>
+      {{ riverMonitorData.data3 }}
+    </div>
+  </div>
+  <el-table :data="riverMonitorData.listData" border style="width: 100%">
+    <el-table-column prop="name" label="名称" show-overflow-tooltip align="center">
+      <template #default="scope">
+        <div style="color: #409eff; cursor: pointer" @click="handleShowDialog(scope.row)">
+          {{ scope.row.name }}
+        </div>
+      </template>
+    </el-table-column>
+    <el-table-column prop="area" label="所属区县" show-overflow-tooltip align="center">
+      <template #header>
+        <el-select v-model="queryParams.area" placeholder="所属区县" size="large">
+          <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </template>
+    </el-table-column>
+    <el-table-column prop="warningLevel" label="汛限水位(m)" show-overflow-tooltip sortable align="center" />
+    <el-table-column prop="waterLevel" label="当前水位(m)" show-overflow-tooltip sortable align="center" />
+    <el-table-column prop="waterDiff" label="差值" show-overflow-tooltip sortable align="center" />
+  </el-table>
+  <pagination
+    v-show="total > queryParams.size"
+    v-model:page="queryParams.current"
+    v-model:limit="queryParams.size"
+    :total="total"
+    @pagination="initData"
+  />
+  <Dialog v-model="showDialog" title="河道监测" width="2500px" height="1200px">
+    <div class="flex">
+      <div class="detail-container">
+        <div class="flex">
+          <div class="info-box">
+            <div class="info-item">
+              <div>测站名称:</div>
+              <div>{{ selectRow.name }}</div>
+            </div>
+            <div class="info-item">
+              <div>水系河流名称:</div>
+              <div>{{ selectRow.data1 }}</div>
+            </div>
+            <div class="info-item">
+              <div>流域名称:</div>
+              <div>{{ selectRow.data2 }}</div>
+            </div>
+            <div class="info-item">
+              <div>水系站址:</div>
+              <div>{{ selectRow.data3 }}</div>
+            </div>
+            <div class="info-item">
+              <div>超警戒水位:</div>
+              <div>{{ selectRow.waterDiff }}</div>
+            </div>
+            <div class="info-item">
+              <div>时间:</div>
+              <div>{{ selectRow.data5 }}</div>
+            </div>
+          </div>
+          <div class="">
+            <div style="font-size: 44px">降雨过程实况</div>
+            <Chart :option="chartOption1" style="width: 600px; height: 500px" />
+          </div>
+        </div>
+        <div>
+          <div>过去24小时河流水位变化图</div>
+          <Chart :option="chartOption2" style="width: 1200px; height: 500px" />
+        </div>
+      </div>
+      <div class="right-box">
+        <div class="flex" style="width: 100%; align-items: center">
+          <div style="font-size: 44px; flex-shrink: 0; margin-right: 10px">附近视频</div>
+          <el-select v-model="queryParams2.radius" size="large" style="width: 150px">
+            <el-option v-for="item in radiusOption" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </div>
+        <div class="video-box">
+          <video-container :video-data="videoMonitorData" />
+        </div>
+      </div>
+    </div>
+  </Dialog>
+</template>
+
+<script lang="ts" setup name="RiverMonitor">
+import Dialog from '@/components/Dialog/index2.vue';
+import { option1, option2 } from './echartOptions';
+import { getEmergencyVideoCata } from '@/api/routineCommandMap';
+import { getRiverCourseLevel, getRiverList } from '@/api/globalMap/riverMonitor';
+import { parseTime } from '@/utils/ruoyi';
+import { getVideoInfo } from '@/api/globalMap';
+
+const options = reactive([
+  {
+    value: '茂南区',
+    label: '茂南区'
+  },
+  {
+    value: '高州市',
+    label: '高州市'
+  },
+  {
+    value: '电白区',
+    label: '电白区'
+  },
+  {
+    value: '信宜市',
+    label: '信宜市'
+  },
+  {
+    value: '化州市',
+    label: '化州市'
+  }
+]);
+// 请求参数
+const queryParams = reactive({
+  current: 1,
+  size: 10,
+  area: ''
+});
+// 总数量
+let total = ref(0);
+// 数据
+const riverMonitorData = reactive({
+  time: '',
+  data1: '',
+  data2: '',
+  data3: '',
+  listData: []
+});
+// 获取数据
+const initData = async () => {
+  getRiverList(queryParams).then((res) => {
+    total.value = res.total;
+    riverMonitorData.listData = res.rows;
+  });
+};
+
+initData();
+
+// 弹窗数据
+const detailData = reactive({
+  name: '茂名',
+  data1: '粤西沿海',
+  data2: '粤西沿海诸河',
+  data3: '广东省茂名市茂南区站',
+  data4: '-1.32m',
+  data5: '2024-08-23 10:00:00'
+});
+let chartOption1 = ref(option1);
+let chartOption2 = ref(option2);
+let showDialog = ref(false);
+let videoMonitorData = ref([]);
+const radiusOption = reactive([
+  { label: '500米', value: '500' },
+  { label: '1000米', value: '1000' },
+  { label: '1500米', value: '1500' },
+  { label: '2000米', value: '2000' }
+]);
+// 请求参数
+const queryParams2 = reactive({
+  current: 1,
+  size: 9,
+  location: '',
+  radius: '500'
+});
+let selectRow = ref({
+  name: '',
+  waterDiff: ''
+});
+const handleShowDialog = (row) => {
+  showDialog.value = false;
+  selectRow.value = {
+    name: '',
+    waterDiff: ''
+  };
+  videoMonitorData.value = [];
+  nextTick(() => {
+    chartOption1.value.xAxis.data = ['12时', '15时', '18时', '21时', '0时', '3时', '6时', '9时'];
+    chartOption1.value.series[0].data = [123, 232, 0, 0, 0, 0, 0, 0];
+    getRiverCourseLevel({ code: row.code }).then((res) => {
+      const data = res.data.list;
+      const time = [];
+      const resultData = [];
+      selectRow.value = data;
+      data.forEach((item) => {
+        time.push(parseTime(item.time, '{h}'));
+        resultData.push(item.water_level);
+      });
+      chartOption2.value.xAxis.data = time;
+      chartOption2.value.series[0].data = resultData;
+      chartOption2.value.series[0].markLine.data[0].yAxis = row.warningLevel;
+    });
+    queryParams2.location = `POINT(${row.longitude} ${row.latitude})`;
+    getVideoInfo(queryParams2).then((res) => {
+      res.data?.list.forEach((item) => {
+        item.video_code = item.indexcode;
+      });
+      videoMonitorData.value = res.data?.list;
+    });
+    showDialog.value = true;
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.detail-container {
+  font-size: 36px;
+  .dialog-content {
+    display: flex;
+  }
+  .info-box {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    height: 520px;
+    background-color: #d7d7d7;
+    padding: 10px;
+    .info-item {
+      display: flex;
+    }
+  }
+}
+.right-box {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  background-color: #d7d7d7;
+  padding: 10px;
+  height: 1009px;
+  .video-box {
+    margin-top: 30px;
+    height: 900px;
+    display: flex;
+    flex-wrap: wrap;
+  }
+}
+
+.text-green {
+  color: #67c23a;
+}
+.text-warning {
+  color: #e6a23c;
+}
+.text-danger {
+  color: #f56c6c;
+}
+</style>

+ 77 - 0
src/views/globalMap/RightMenu/RoadNetworkVideo.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="container">
+    <div class="title">路网视频</div>
+    <div>
+      <el-input v-model="queryParams.name" :prefix-icon="Search" size="large" clearable />
+    </div>
+    <div>类型</div>
+    <el-checkbox-group v-model="queryParams.type" size="large">
+      <el-checkbox v-for="(item, index) in options" :key="index" :label="item.label" :value="item.value" />
+    </el-checkbox-group>
+    <div>视频类型</div>
+    <el-table :data="listData" border style="width: 100%">
+      <el-table-column prop="data1" show-overflow-tooltip align="center">
+        <template #header>
+          <el-select v-model="queryParams.range" placeholder="全部" size="large">
+            <el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </template>
+        <template #default="scope">
+          <div style="color: #409eff; cursor: pointer" @click="handleShowDialog(scope.row)">
+            {{ scope.row.data1 }}
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="data2" label="名称" show-overflow-tooltip align="center" />
+    </el-table>
+    <Dialog v-model="showDialog" title="路网视频" width="2500px" height="1200px">
+      <div style="width: 2500px; height: 1120px; display: flex; justify-content: center; align-items: center;">
+        <HKVideo :dot_data="videoMonitorData[0]" :width="2400" :height="1000" />
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Search } from '@element-plus/icons-vue';
+import { getEmergencyVideoCata } from '@/api/routineCommandMap';
+import Dialog from '@/components/Dialog/index2.vue';
+
+const queryParams = reactive({
+  page: 1,
+  pageSize: 10,
+  name: '',
+  type: '检测站',
+  range: ''
+});
+
+const options = reactive([
+  { label: '检测站', value: '检测站' },
+  { label: '收费站', value: '收费站' },
+  { label: '高速公路', value: '高速公路' },
+  { label: '国道', value: '国道' },
+  { label: '省市县际道路', value: '省市县际道路' },
+  { label: '高速服务区', value: '高速服务区' }
+]);
+
+const options2 = reactive([{ label: '包茂高速', value: '包茂高速' }]);
+const listData = ref([{ data1: '包茂高速', data2: '钱排互通 往南宁方向' }]);
+
+let showDialog = ref(false);
+// 请求参数
+const queryParams2 = reactive({
+  current: 1,
+  size: 1
+});
+let videoMonitorData = ref([]);
+const handleShowDialog = (row) => {
+  showDialog.value = true;
+  getEmergencyVideoCata(queryParams2).then((res) => {
+    videoMonitorData.value = res.rows;
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 96 - 0
src/views/globalMap/RightMenu/SpatialAnalysis.vue

@@ -0,0 +1,96 @@
+<template>
+  <div>空间分析</div>
+  <div class="analyze-data-container">
+    <div class="item">
+      <div class="item-label">行政镇(个)</div>
+      <div class="item-value">{{ analysisSpatialData.townName }}</div>
+    </div>
+    <div class="item">
+      <div class="item-label">行政村(个)</div>
+      <div class="item-value">{{ analysisSpatialData.townName }}</div>
+    </div>
+    <div class="item">
+      <div class="item-label">面积(km²)</div>
+      <div class="item-value">{{ analysisSpatialData.area }}</div>
+    </div>
+    <div class="item">
+      <div class="item-label">常住人口(万)</div>
+      <div class="item-value">{{ analysisSpatialData.populationNum }}</div>
+    </div>
+    <div class="item">
+      <div class="item-label">GDP(万元)</div>
+      <div class="item-value">{{ analysisSpatialData.expert }}</div>
+    </div>
+    <div class="flex" style="margin: 20px 0; width: 100%">
+      <el-input v-model="keyword" size="large" style="flex: 1;" />
+      <el-button type="primary" size="large">搜索</el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup name="AnalyzeDataDialog">
+import BigNumber from 'bignumber.js';
+
+interface AnalysisSpatialData {
+  townName: string;
+  area: number;
+  populationNum: number;
+  gdp: number;
+  easyFloodPoint: number;
+  medicalInstitutionNum: number;
+  expert: number;
+}
+interface Props {
+  selectedScope: any;
+}
+const props = withDefaults(defineProps<Props>(), {});
+const keyword = ref('');
+const analysisSpatialData = computed(() => {
+  const data: AnalysisSpatialData = {
+    townName: '',
+    area: 0,
+    populationNum: '',
+    gdp: '',
+    easyFloodPoint: '',
+    medicalInstitutionNum: '',
+    expert: 0
+  };
+  for (let key in props.selectedScope) {
+    data.area = new BigNumber(data.area).plus(props.selectedScope[key].area);
+    data.expert = new BigNumber(data.expert).plus(props.selectedScope[key].expert);
+    // selectedScope[key] = '';
+  }
+  return data;
+});
+</script>
+
+<style lang="scss" scoped>
+.analyze-data-container {
+  width: 100%;
+  font-size: 16px;
+  padding: 15px;
+  display: flex;
+  flex-wrap: wrap;
+  .item {
+    width: calc(33.333333%);
+    height: 100px;
+    background-color: #fff;
+    border-left: 1px solid #000;
+    border-top: 1px solid #000;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    font-size: 36px;
+    &:nth-child(3), &:nth-child(4), &:nth-child(5) {
+      border-bottom: 1px solid #000;
+    }
+    &:nth-child(5) {
+      border-right: 1px solid #000;
+    }
+    .item-label {
+      margin-bottom: 18px;
+    }
+  }
+}
+</style>

+ 111 - 0
src/views/globalMap/RightMenu/echartOptions.ts

@@ -0,0 +1,111 @@
+// 降雨过程实况
+export const option1 = {
+  xAxis: {
+    type: 'category',
+    data: []
+  },
+  yAxis: {
+    type: 'value'
+  },
+  visualMap: [
+    {
+      show: false,
+      pieces: [
+        {
+          gt: 0,
+          lte: 8.5,
+          color: 'blue'
+        },
+        {
+          gt: 8.5,
+          color: 'red'
+        }
+      ],
+      seriesIndex: 1
+    }
+  ],
+  series: [
+    {
+      data: [],
+      type: 'line',
+      smooth: true
+    }
+  ]
+};
+
+// 过去24小时河流水位变化图
+export const option2 = {
+  xAxis: {
+    type: 'category',
+    data: []
+  },
+  yAxis: {
+    type: 'value'
+  },
+  tooltip: {
+    show: true
+  },
+  series: [
+    {
+      data: [],
+      type: 'line',
+      markLine: {
+        // 警戒线样式
+        silent: true, // 不响应和触发鼠标事件
+        data: [
+          {
+            yAxis: '', // 自定义警戒线的y轴值
+            name: '警戒水位',
+            lineStyle: {
+              color: 'red', // 警戒线颜色为红色
+              type: 'dashed' // 警戒线线型为虚线
+            }
+          }
+        ]
+      }
+    }
+  ]
+};
+
+const yName = `水位 : m`;
+
+export const option3 = {
+  xAxis: {
+    type: 'category',
+    data: []
+  },
+  yAxis: {
+    type: 'value'
+  },
+  tooltip: {
+    show: true
+  },
+  series: [
+    {
+      data: [],
+      type: 'line',
+      markLine: {
+        // 警戒线样式
+        silent: true, // 不响应和触发鼠标事件
+        data: [
+          {
+            yAxis: '', // 自定义警戒线的y轴值
+            name: '警戒水位',
+            lineStyle: {
+              color: 'red', // 警戒线颜色为红色
+              type: 'dashed' // 警戒线线型为虚线
+            }
+          }
+        ]
+      }
+    },
+    {
+      data: [],
+      type: 'line'
+    },
+    {
+      data: [],
+      type: 'line'
+    }
+  ]
+};

+ 136 - 0
src/views/globalMap/RightMenu/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="right-menu">
+    <div v-show="!menuState.showMenu" class="expand-btn" @click="clickExpandBtn">{{ activeName }}</div>
+    <div v-show="menuState.showMenu" class="menu-container">
+      <div class="menu-list">
+        <div
+          v-for="(item, index) in menuState.menuData"
+          :key="index"
+          :class="menuState.activeIndex === index ? 'menu-item menu-active' : 'menu-item'"
+          @click="clickMenu(index)"
+        >
+          {{ item.name }}
+        </div>
+        <div class="contract-btn" @click="clickContractMenu">{{ '>' }}</div>
+      </div>
+      <div class="menu-content">
+        <!--图层分析-->
+        <template v-if="menuState.activeIndex === 0" />
+        <!--空间分析-->
+        <SpatialAnalysis v-if="menuState.activeIndex === 1" />
+        <!--江湖河库-->
+        <Reservoir v-if="menuState.activeIndex === 2" />
+        <!--路网视频-->
+        <RoadNetworkVideo v-if="menuState.activeIndex === 3" />
+        <!--水库监测-->
+        <ReservoirMonitor v-if="menuState.activeIndex === 4" />
+        <!--河道监测-->
+        <RiverMonitor v-if="menuState.activeIndex === 5" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup name="rightMenu">
+import RiverMonitor from './RiverMonitor.vue';
+import ReservoirMonitor from './ReservoirMonitor.vue';
+import RoadNetworkVideo from './RoadNetworkVideo.vue';
+import Reservoir from './Reservoir.vue';
+import SpatialAnalysis from '@/views/globalMap/RightMenu/SpatialAnalysis.vue';
+
+const menuState = reactive({
+  showMenu: false,
+  activeIndex: 0,
+  menuData: [{ name: '图层分析' }, { name: '空间分析' }, { name: '江湖河库' }, { name: '路网视频' }, { name: '水库监测' }, { name: '河道监测' }]
+});
+
+const activeName = computed(() => {
+  let name = '';
+  if (!!menuState.menuData && menuState.menuData.length > 0 && !!menuState.menuData[menuState.activeIndex]) {
+    name = menuState.menuData[menuState.activeIndex].name;
+  }
+  return name;
+});
+
+// 点击按钮展开
+const clickExpandBtn = () => {
+  menuState.showMenu = true;
+};
+// 点击收缩展开
+const clickContractMenu = () => {
+  menuState.showMenu = false;
+};
+// 点击菜单
+const clickMenu = (index) => {
+  menuState.activeIndex = index;
+};
+
+// 显示菜单
+const handleMenu = (name) => {
+  let index = menuState.menuData.findIndex((item) => {
+    return item.name === name;
+  });
+  if (index > -1) {
+    clickMenu(index);
+    clickExpandBtn();
+  }
+};
+
+defineExpose({ handleMenu });
+</script>
+
+<style lang="scss" scoped>
+.right-menu {
+  position: absolute;
+  top: 100px;
+  right: 0;
+}
+.expand-btn {
+  width: 70px;
+  height: 240px;
+  font-size: 32px;
+  background: #d7d7d7;
+  padding: 10px 18px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+}
+.menu-container {
+  display: flex;
+  .menu-list {
+    .menu-item {
+      width: 70px;
+      height: 240px;
+      font-size: 32px;
+      background: #d7d7d7;
+      padding: 10px 18px;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      &:hover {
+        background-color: #81d3f8;
+      }
+    }
+    .menu-active {
+      background-color: #81d3f8;
+    }
+    .contract-btn {
+      width: 70px;
+      height: 60px;
+      font-size: 32px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background: #d7d7d7;
+      cursor: pointer;
+    }
+  }
+  .menu-content {
+    width: 800px;
+    height: 1440px;
+    background: #f2f2f2;
+    padding: 15px;
+    font-size: 36px;
+  }
+}
+</style>

+ 3 - 3
src/views/globalMap/SwitchMapTool.vue

@@ -19,14 +19,14 @@
   </div>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup name="SwitchMapTool">
 interface Props {
   activeMap: string;
 }
 
 const props = withDefaults(defineProps<Props>(), {});
 
-const emits = defineEmits(['update:activeMap', 'switchMap']);
+const emits = defineEmits(['switchMap']);
 const mapData = ref([
   { name: '逻辑地图', key: 'logical' },
   { name: '矢量地图', key: 'vectorgraph' },
@@ -36,7 +36,7 @@ const mapData = ref([
 let open = ref(false);
 const selectItem = (key) => {
   if (props.activeMap === key) return;
-  emits('update:activeMap', key);
+  // emits('update:activeMap', key);
   emits('switchMap', key);
 };
 const handleExpand = () => {

+ 31 - 36
src/views/globalMap/index.vue

@@ -39,14 +39,14 @@
       />
       <!--左侧菜单-->
       <LeftMenu
-        :menu-data="menuData"
         style="position: absolute; top: 20px; left: 20px"
-        @add-markers="addMarkers"
         @click-menu="clickMenu"
         @selectSearchMarker="selectSearchMarker"
       />
+      <!--右侧菜单-->
+      <RightMenu ref="rightMenuRef" />
       <!--更换地图类型-->
-      <SwitchMapTool :active-map="activeMap" class="tool-box" @swtich-map="switchMap" />
+      <SwitchMapTool :active-map="activeMap" class="tool-box" @switchMap="switchMap" />
       <!--时间轴-->
       <TimeAxis />
       <DrawTools
@@ -58,7 +58,7 @@
         class="absoluteTool"
         @undo="undo"
       />
-      <AnalyzeDataDialog v-if="analysisSpatialDataShow" :selectedScope="selectedScope" />
+<!--      <AnalyzeDataDialog v-if="analysisSpatialDataShow" :selectedScope="selectedScope" />-->
       <div v-if="showDialog" class="box">
         <div v-for="(item, index) in markerList" :key="index" @click="toAddress(item)">{{item.name}}</div>
       </div>
@@ -83,6 +83,8 @@ import { deepClone } from '@/utils';
 import { getEmergencyExpertNum, getEmergencyExpertNumRound, getRescueMateria } from '@/api/globalMap';
 import { listMenu } from '@/api/system/menu';
 import autofit from 'autofit.js';
+import RightMenu from './RightMenu/index.vue';
+import { getWaterList } from '@/api/globalMap/reservoir';
 
 interface Props {
   isComponent?: boolean;
@@ -94,6 +96,7 @@ const props = withDefaults(defineProps<Props>(), {
   isComponent: false
 });
 
+const rightMenuRef = ref(null);
 const mapData = reactive(logicalData);
 let mapRef = ref(null);
 let map2Ref = ref(null);
@@ -103,30 +106,10 @@ let activeMap = ref('logical');
 const switchMap = (key) => {
   activeMap.value = key;
 };
-let menuData = ref([]);
+
 let showDialog = ref(false);
 let markerList = ref([]);
-const initData = () => {
-  listMenu().then((res: any) => {
-    const data = res.data ? res.data[0]?.children : [];
-    data.forEach((item) => {
-      item.show = true;
-      item.name = item.meta?.title;
-      item.children?.forEach((item2) => {
-        item2.show = true;
-        item2.name = item2.meta?.title;
-        item2.children?.forEach((item3) => {
-          item3.name = item3.meta?.title;
-          if (item3.component === '2' && !!item3.path) {
-            item3.checked = false;
-          }
-        });
-      });
-    });
-    menuData.value = data;
-    console.log(menuData.value);
-  });
-};
+
 const addMarkers = (item) => {
   const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;
   if (dom) {
@@ -166,6 +149,19 @@ const clickMenu = (item) => {
   if (item.path === 'spatial') {
     showDrawTools.value = !showDrawTools.value;
   }
+  if (item.component === '2') {
+    addMarkers(item);
+  }
+  if (['空间分析', '江湖河库', '路网视频', '水库监测', '河道监测'].includes(item.name)) {
+    if (item.checked) {
+      rightMenuRef.value.handleMenu(item.name);
+    }
+    if (item.name === '江湖河库') {
+      getWaterList().then((res) => {
+        debugger;
+      });
+    }
+  }
 };
 // 点击搜索结果,添加标注
 const selectSearchMarker = (item) => {
@@ -275,17 +271,16 @@ const unSelectGraphics = (data) => {
 };
 onMounted(() => {
   if (!props.isComponent) {
-    autofit.init(
-      {
-        dw: 8960,
-        dh: 2520,
-        el: '#globalMap',
-        resize: true
-      },
-      false
-    );
+    // autofit.init(
+    //   {
+    //     dw: 8960,
+    //     dh: 2520,
+    //     el: '#globalMap',
+    //     resize: true
+    //   },
+    //   false
+    // );
   }
-  initData();
 });
 onUnmounted(() => {
   if (autofit) {

Некоторые файлы не были показаны из-за большого количества измененных файлов