Преглед изворни кода

进尺工作面来压模拟,视屏墙修改,设备集控分组-提交

lxh пре 4 месеци
родитељ
комит
4f534c5264

+ 1 - 0
src/assets/icons/select-control.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766023711253" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13367" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 512m-469.333333 0a469.333333 469.333333 0 1 0 938.666666 0 469.333333 469.333333 0 1 0-938.666666 0Z" fill="#32CBCB" p-id="13368"></path><path d="M512 1002.666667c-268.8 0-490.666667-221.866667-490.666667-490.666667s221.866667-490.666667 490.666667-490.666667 490.666667 221.866667 490.666667 490.666667-221.866667 490.666667-490.666667 490.666667z m0-938.666667c-247.466667 0-448 200.533333-448 448s200.533333 448 448 448 448-200.533333 448-448-200.533333-448-448-448z" fill="#32CBCB" p-id="13369"></path><path d="M443.733333 725.333333c-12.8 0-25.6-4.266667-34.133333-12.8l-183.466667-187.733333c-17.066667-17.066667-17.066667-46.933333 0-68.266667 17.066667-17.066667 46.933333-17.066667 64 0l149.333334 157.866667 290.133333-298.666667c17.066667-17.066667 46.933333-17.066667 64 0 17.066667 17.066667 17.066667 46.933333 0 68.266667l-320 332.8c-8.533333 4.266667-17.066667 8.533333-29.866667 8.533333" fill="#FFFFFF" p-id="13370"></path></svg>

+ 508 - 0
src/hooks/system/useCameraPianation.ts

@@ -0,0 +1,508 @@
+import { defHttp } from '/@/utils/http/axios';
+import { render, h, nextTick, VNode, ref } from 'vue';
+import { useDrag } from '../event/useDrag';
+import Player, { I18N } from 'xgplayer';
+import HlsPlugin from 'xgplayer-hls';
+import FlvPlugin from 'xgplayer-flv';
+import 'xgplayer/dist/index.min.css';
+import ZH from 'xgplayer/es/lang/zh-cn';
+import videojs from 'video.js';
+import { createPlayerVNode } from '../component/createPlayer';
+I18N.use(ZH);
+
+export function useCamera() {
+  const cameraList = (params) => defHttp.get({ url: '/safety/ventanalyCamera/listNew', params });
+  const cameraAddrList = (params) => defHttp.post({ url: '/monitor/camera/info', params });
+  const cameraAddr = (params) => defHttp.get({ url: '/monitor/camera/queryByCameraCode', params });
+
+
+  let webRtcServer = <any[]>[];
+  const playerList = <any[]>[];
+  const playerDoms = <(HTMLVideoElement | undefined | null)[]>[];
+  const videoParentDomList: (HTMLElement | [string, { name: string; addr: string; cameraRate: number; devicekind: string }])[] = [];
+  let Total = ref(0)
+
+  
+
+  async function getCamera(deviceid, parentPlayerDom, renderPlayer, pagination, cameraData?, devKind?, isCustom = false) {
+    await removeCameraRef(parentPlayerDom, renderPlayer);
+    let res;
+    if (!devKind) {
+      res = await cameraList({ deviceid, pageNo: pagination.current, pageSize: pagination.pageSize, });
+    }else {
+      res = await cameraList({ sysId: deviceid, devKind, pageNo: pagination.current, pageSize: pagination.pageSize, });
+    }
+    Total.value = !cameraData ?  res.total : 0
+    const cameras: [] = !cameraData ? res.records || [] : cameraData;
+    console.log(cameras,'cameras===')
+    const cameraAddrs: any[] = [],
+      cameraNames: any[] = [];
+    if (cameras.length > 0) {
+      for (let i = 0; i < cameras.length; i++) {
+        const item = cameras[i];
+        if (
+          item['devicekind'] === 'toHKRtsp' ||
+          item['devicekind'] === 'toHKHLs' ||
+          item['devicekind'] === 'HLL' ||
+          item['devicekind'] === 'YZG_URL'
+        ) {
+          // 从海康平台接口获取视频流
+          const videoType = item['devicekind'] === 'toHKRtsp' ? 'rtsp' : '';
+          try {
+            const data = await cameraAddr({ devicekind: item['devicekind'], cameraCode: item['addr'], videoType });
+            if (data && data['url']) {
+              cameraAddrs.push({ name: item['name'], addr: data['url'], cameraRate: item['cameraRate'], devicekind: item['devicekind'] });
+            }
+            // // 从海康平台接口获取视频流测试
+            // cameraAddrs.push({
+            //   name: item['name'],
+            //   addr: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.mp4/.m3u8',
+            // });
+          } catch (error) { }
+        } else if (item['devicekind'] == 'toHKR') {
+          cameraNames.push({ name: item['name'], cameraRate: item['cameraRate'] });
+        } else {
+          cameraAddrs.push({ name: item['name'], addr: item['addr'], cameraRate: item['cameraRate'], devicekind: item['devicekind'] });
+        }
+      }
+    }
+    if (cameraNames.length > 0) {
+      // 请求接口从装备院拿数据
+      const cameraNameList = cameraNames.map((item) => item.name);
+
+      const addrs: string[] = await cameraAddrList({ cameraNameList: cameraNameList });
+      for (let i = 0; i < addrs.length; i++) {
+        cameraAddrs.push({ name: '摄像头' + i, addr: addrs[i] });
+      }
+    }
+    if (isCustom) {
+      return cameraAddrs;
+    } else {
+      await deviceCameraInit1(cameraAddrs, parentPlayerDom);
+    }
+  }
+
+  function deviceCameraInit(cameraAddrs, player: HTMLElement) {
+    const newWebRtcServer = [];
+    let livePlayerDiv: HTMLElement | null = document.getElementById('LivePlayerBox');
+    if (livePlayerDiv) {
+      livePlayerDiv.remove();
+      livePlayerDiv = null;
+    }
+    if (!livePlayerDiv) {
+      const dom = document.createElement('div');
+      dom.setAttribute('id', 'LivePlayerBox');
+      dom.setAttribute('class', 'live-player-box');
+      livePlayerDiv = dom;
+      player.appendChild(livePlayerDiv);
+    }
+
+    return new Promise((resolve) => {
+      const playCamrea = () => {
+        if (cameraAddrs.length > 0) {
+          const promiseList: Promise<any>[] = [];
+          // debugger;
+          cameraAddrs.forEach(async (cameraUrl: { name: string; addr: string; cameraRate: number; devicekind: string }, index) => {
+            const promise = new Promise(async (childResolve) => {
+              let cameraNameDom: null | HTMLElement = null;
+              console.log('摄像头地址--------->', cameraUrl, cameraUrl.addr.startsWith('rtsp://'), livePlayerDiv);
+              if (cameraUrl.addr.includes('0.0.0.0')) {
+                cameraUrl.addr = cameraUrl.addr.replace('0.0.0.0', window.location.hostname);
+              }
+              if (cameraUrl.addr && cameraUrl.addr.startsWith('rtsp://')) {
+                const server = webRtcServer?.shift();
+                let videoDom: HTMLVideoElement | null = null;
+                if (server) {
+                  try {
+                    videoDom = server.videoElement as HTMLVideoElement;
+                    videoDom.volume = 0;
+                    const cameraNameDom = videoDom.parentElement?.getElementsByClassName('video-name')[0];
+                    if (cameraNameDom) cameraNameDom.innerText = cameraUrl.name;
+                    playerDoms.unshift(videoDom);
+                    newWebRtcServer.unshift(server);
+                    // videoParentDomList.unshift()
+                    await server.connect(cameraUrl['addr']);
+                    videoDom.play();
+                    childResolve(null);
+                  } catch (error) {
+                    playerDoms.unshift(undefined);
+                    childResolve(null);
+                  }
+                } else {
+                  videoDom = document.createElement('video');
+                  videoDom.volume = 0;
+                  videoDom.setAttribute('class', 'rtspVideo');
+                  videoDom.setAttribute('muted', 'muted');
+                  videoDom.setAttribute('poster', '/src/assets/images/vent/noSinge.png');
+                  videoDom.autoplay = true;
+
+                  try {
+                    const server = new window['WebRtcStreamer'](
+                      videoDom,
+                      VUE_APP_URL.webRtcUrl.startsWith('/') ? location.protocol + VUE_APP_URL.webRtcUrl : VUE_APP_URL.webRtcUrl
+                    );
+                    newWebRtcServer.unshift(server);
+                    await server.connect(cameraUrl.addr);
+                    videoDom.play();
+                    playerDoms.unshift(videoDom);
+                    childResolve(null);
+                  } catch (error) {
+                    console.log('WebRtcStreamer 抛出异常!!!!!!');
+                    playerDoms.unshift(null);
+                    childResolve(null);
+                  }
+                }
+                if (videoDom) {
+                  const videoParentDom: HTMLElement = document.createElement('div');
+                  videoParentDom.setAttribute('class', 'video-parent');
+                  videoParentDom.appendChild(videoDom);
+                  cameraNameDom = document.createElement('div');
+                  cameraNameDom.setAttribute('class', 'video-name');
+                  cameraNameDom.innerText = cameraUrl.name;
+                  videoParentDom.appendChild(cameraNameDom);
+                  videoParentDom.addEventListener('dblclick', () => {
+                    if (videoDom?.requestFullscreen) {
+                      videoDom.requestFullscreen();
+                      videoDom.play();
+                    }
+                  });
+
+                  videoParentDomList.push(videoParentDom);
+                }
+              } else {
+                videoParentDomList.push(['player' + index, cameraUrl]);
+                childResolve(null);
+              }
+            });
+            promiseList.push(promise);
+          });
+          Promise.all(promiseList).then(() => {
+            resolve(null);
+          });
+        } else {
+          resolve(null);
+        }
+      };
+      playCamrea();
+    }).then(() => {
+      videoParentDomList.forEach((videoParent) => {
+        if (typeof videoParent[0] === 'string' && livePlayerDiv) {
+          const videoParentDom: HTMLElement = document.createElement('div');
+          videoParentDom.setAttribute('class', 'liveVideo');
+          livePlayerDiv?.appendChild(videoParentDom);
+          useDrag(videoParentDom);
+
+          if (videoParent[1] && videoParent[1].addr) {
+            let player;
+            const fileExtension = videoParent[1].addr.split('.').pop();
+            if (fileExtension === 'flv' || videoParent[1].devicekind == 'flv') {
+              const videoDom: HTMLElement = document.createElement('div');
+              videoDom.setAttribute('id', videoParent[0]);
+              videoDom.style = 'width: 100%; height: 100%;';
+              videoParentDom.appendChild(videoDom);
+              player = new Player({
+                lang: 'zh',
+                id: videoParent[0],
+                url: videoParent[1].addr,
+                width: 294,
+                height: 188,
+                poster: '/src/assets/images/vent/noSinge.png',
+                plugins: [FlvPlugin],
+                fluid: true,
+                autoplay: true,
+                isLive: true,
+                playsinline: true,
+                screenShot: true,
+                whitelist: [''],
+                ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+                closeVideoClick: true,
+                customConfig: {
+                  isClickPlayBack: false,
+                },
+                controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+                defaultPlaybackRate: videoParent[1].cameraRate || 1,
+                flv: {
+                  retryCount: 3, // 重试 3 次,默认值
+                  retryDelay: 1000, // 每次重试间隔 1 秒,默认值
+                  loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+                  fetchOptions: {
+                    // 该参数会透传给 fetch,默认值为 undefined
+                    mode: 'cors',
+                  },
+                  targetLatency: 10, // 直播目标延迟,默认 10 秒
+                  maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+                  disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
+                  maxJumpDistance: 10,
+                },
+              });
+            } else if (fileExtension === 'm3u8' || videoParent[1].devicekind == 'm3u8') {
+              const videoDom: HTMLElement = document.createElement('video');
+              videoDom.setAttribute('id', videoParent[0]);
+              videoDom.style = 'width: 100%; height: 100%;';
+              videoParentDom.appendChild(videoDom);
+              if (videoDom.canPlayType('application/vnd.apple.mpegurl')) {
+                // 原生支持 hls 播放
+                player = new Player({
+                  lang: 'zh',
+                  id: videoParent[0],
+                  url: videoParent[1].addr,
+                  width: 294,
+                  height: 188,
+                  isLive: true,
+                  autoplay: true,
+                  autoplayMuted: true,
+                  cors: true,
+                  ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+                  poster: '/src/assets/images/vent/noSinge.png',
+                  defaultPlaybackRate: videoParent[1].cameraRate || 1,
+                  controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+                  hls: {
+                    retryCount: 10, // 重试 3 次,默认值
+                    retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+                    loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+                    disconnectTime: 30, //直播断流时间,
+                    fetchOptions: {
+                      // 该参数会透传给 fetch,默认值为 undefined
+                      mode: 'cors',
+                    },
+                    targetLatency: 10, // 直播目标延迟,默认 10 秒
+                    maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+                    maxJumpDistance: 10,
+                  },
+                });
+              } else if (HlsPlugin.isSupported()) {
+                // 第一步
+                player = new Player({
+                  lang: 'zh',
+                  id: videoParent[0],
+                  url: videoParent[1].addr,
+                  width: 294,
+                  height: 188,
+                  isLive: true,
+                  autoplay: true,
+                  autoplayMuted: true,
+                  ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+                  plugins: [HlsPlugin], // 第二步
+                  poster: '/src/assets/images/vent/noSinge.png',
+                  defaultPlaybackRate: videoParent[1].cameraRate || 1,
+                  controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+                  hls: {
+                    retryCount: 10, // 重试 3 次,默认值
+                    retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+                    loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+                    disconnectTime: 30,
+                    fetchOptions: {
+                      // 该参数会透传给 fetch,默认值为 undefined
+                      mode: 'cors',
+                    },
+                    targetLatency: 10, // 直播目标延迟,默认 10 秒
+                    maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+                    maxJumpDistance: 10,
+                  },
+                });
+              }
+            } else {
+              const videoDom: HTMLElement = document.createElement('video');
+              videoDom.setAttribute('id', videoParent[0]);
+              videoDom.style = 'width: 100%; height: 100%;';
+              videoParentDom.appendChild(videoDom);
+              player = videojs(
+                videoParent[0],
+                {
+                  controls: false,
+                  autoplay: true,
+                  preload: 'auto',
+                  muted: true,
+                  // src: cameraUrl,
+                  sources: [
+                    {
+                      src: videoParent[1].addr,
+                      type: 'application/x-mpegURL',
+                    },
+                  ],
+                },
+                () => {
+                  videojs.log('播放器准备好了!');
+                  // this.play();
+                }
+              );
+              player.play();
+            }
+            if (player) playerList.push(player);
+          }
+        } else {
+          useDrag(videoParent as HTMLElement);
+          livePlayerDiv?.appendChild(videoParent as Node);
+        }
+      });
+
+      const players = livePlayerDiv?.getElementsByClassName('player');
+      if (players && players.length) {
+        for (let i = 0; i < players.length; i++) {
+          try {
+            const isCanPlayer = !players[i].getAttribute('id')?.startsWith('onPlayer');
+            const dom = players[i].getElementsByTagName('video')[0];
+            if (dom && isCanPlayer) {
+              playerDoms.unshift(dom);
+            } else {
+              playerDoms.unshift(null);
+            }
+          } catch (error) {
+            console.log('可以捕获到异常吗?????');
+            playerDoms.unshift(null);
+          }
+        }
+      }
+      if (webRtcServer.length > 0) {
+        for (let i = 0; i < webRtcServer.length; i++) {
+          webRtcServer[i].videoElement.parentElement.remove();
+          webRtcServer[i].disconnect();
+          webRtcServer[i] = undefined;
+        }
+      }
+      webRtcServer.length = 0;
+      webRtcServer = newWebRtcServer;
+    });
+  }
+
+  function deviceCameraInit1(cameraAddrs, player) {
+    const vnode = createPlayerVNode(cameraAddrs);
+    render(vnode, player.value);
+  }
+
+  function getPlayer(fileExtension, playerDomId, camerakind, cameraUrl, cameraRate, option = { width: '100%', height: '100%' }) {
+    let player;
+    if (fileExtension === 'flv' || camerakind == 'flv') {
+      player = new Player({
+        lang: 'zh',
+        id: playerDomId,
+        url: cameraUrl,
+        width: option.width,
+        height: option.height,
+        poster: '/src/assets/images/vent/noSinge.png',
+        plugins: [FlvPlugin],
+        fluid: true,
+        autoplay: true,
+        isLive: true,
+        playsinline: true,
+        screenShot: true,
+        whitelist: [''],
+        ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+        closeVideoClick: true,
+        customConfig: {
+          isClickPlayBack: false,
+        },
+        controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+        defaultPlaybackRate: cameraRate || 1,
+        flv: {
+          retryCount: 3, // 重试 3 次,默认值
+          retryDelay: 1000, // 每次重试间隔 1 秒,默认值
+          loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+          fetchOptions: {
+            // 该参数会透传给 fetch,默认值为 undefined
+            mode: 'cors',
+          },
+          targetLatency: 10, // 直播目标延迟,默认 10 秒
+          maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+          disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
+          maxJumpDistance: 10,
+        },
+      });
+
+      // playerList.push(player);
+    }
+    if (fileExtension === 'm3u8' || camerakind == 'm3u8') {
+      if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
+        // 原生支持 hls 播放
+        player = new Player({
+          lang: 'zh',
+          id: playerDomId,
+          url: cameraUrl,
+          width: option.width,
+          height: option.height,
+          isLive: true,
+          autoplay: true,
+          autoplayMuted: true,
+          cors: true,
+          ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+          poster: '/src/assets/images/vent/noSinge.png',
+          defaultPlaybackRate: cameraRate || 1,
+          controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+          hls: {
+            retryCount: 10, // 重试 3 次,默认值
+            retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+            loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+            disconnectTime: 30, //直播断流时间,
+            fetchOptions: {
+              // 该参数会透传给 fetch,默认值为 undefined
+              mode: 'cors',
+            },
+            targetLatency: 10, // 直播目标延迟,默认 10 秒
+            maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+            maxJumpDistance: 10,
+          },
+        });
+      } else if (HlsPlugin.isSupported()) {
+        // 第一步
+        player = new Player({
+          lang: 'zh',
+          id: playerDomId,
+          url: cameraUrl,
+          width: option.width,
+          height: option.height,
+          isLive: true,
+          autoplay: true,
+          autoplayMuted: true,
+          ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
+          plugins: [HlsPlugin], // 第二步
+          poster: '/src/assets/images/vent/noSinge.png',
+          defaultPlaybackRate: cameraRate || 1,
+          controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
+          hls: {
+            retryCount: 10, // 重试 3 次,默认值
+            retryDelay: 30000, // 每次重试间隔 1 秒,默认值
+            loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
+            disconnectTime: 30,
+            fetchOptions: {
+              // 该参数会透传给 fetch,默认值为 undefined
+              mode: 'cors',
+            },
+            targetLatency: 10, // 直播目标延迟,默认 10 秒
+            maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
+            maxJumpDistance: 10,
+          },
+        });
+      }
+    }
+    return player;
+  }
+
+  function removeCamera(playerRef) {
+    if (playerRef.value) {
+      render(null, playerRef.value);
+    }
+  }
+  function removeCameraRef(playerRef, isShowPlayerRef) {
+    return new Promise((resolve) => {
+      isShowPlayerRef.value = false;
+      if (playerRef.value) {
+        render(null, playerRef.value);
+      }
+      nextTick(() => {
+        isShowPlayerRef.value = true;
+        resolve(null);
+      });
+    });
+  }
+
+  return {
+    getCamera,
+    webRtcServer,
+    playerDoms,
+    deviceCameraInit,
+    removeCamera,
+    getPlayer,
+    removeCameraRef,
+    Total,
+  };
+}

+ 1 - 1
src/views/vent/monitorManager/airDoor/airdoor.api.ts

@@ -16,7 +16,7 @@ GetSyncRuleExecLog='/ventanaly-device/synccontrol/upcoming/GetSyncRuleExecLog',
 list='/safety/ventanalyDeviceInfo/list',
 edit='/ventanaly-device/safety/ventanalyDeviceInfo/batchEdit',
 manualTimeSync='/ventanaly-device/monitor/timeSync/manualTimeSync',
-isPasswordCurrect='/ventanaly-device/monitor/timeSync/isPasswordCurrect'
+isPasswordCurrect='/ventanaly-device/monitor/timeSync/isPasswordCurrect',
 
 }
 

+ 3 - 2
src/views/vent/monitorManager/airDoor/components/Modal.vue

@@ -1,7 +1,7 @@
 <template>
   <a-modal
     ref="modalRef"
-    width="1100px"
+    :width="props.widthV"
     :visible="props.visible"
     :wrap-style="{ overflow: 'hidden' }"
     @ok="handleOk"
@@ -31,6 +31,7 @@
 
   // 定义Props接口,描述组件的属性
   interface Props {
+    widthV?:string
     title?: string; // 对话框的标题,默认为"提示"
     footer?: null; // 对话框的页脚,默认为null
     visible: boolean; // 对话框是否可见
@@ -41,7 +42,7 @@
   }
 
   // 设置props的默认值
-  const props = withDefaults(defineProps<Props>(), { title: '摄像头信息', visible: false, destroyOnClose: false, maskClosable: true });
+  const props = withDefaults(defineProps<Props>(), { title: '摄像头信息',widthV:'1100px', visible: false, destroyOnClose: false, maskClosable: true });
 
   // 创建ref引用modalTitleRef,用于引用组件中的标题元素
   const modalTitleRef = ref<HTMLElement | null | any>(null);

+ 115 - 0
src/views/vent/monitorManager/airDoor/components/cameraDetailModal.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="camera-modal">
+    <div class="camrea-area">
+      <div v-if="renderPlayer" ref="playerRef" style="
+     display: flex;
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+      pointer-events: none;
+    ">
+      </div>
+    </div>
+    <div class="pagination-area">
+      <Pagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize"
+        :total="cameraData.length" @change="onChange" />
+    </div>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref, reactive, onBeforeUnmount, computed } from 'vue';
+import { useCamera } from '/@/hooks/system/useCameraPianation';
+import { Pagination, Empty } from 'ant-design-vue';
+
+let props = defineProps({
+  cameraData: {
+    type: Array,
+    default: () => {
+      return []
+    }
+  },
+});
+const { getCamera, removeCamera, } = useCamera();
+const playerRef = ref();
+const renderPlayer = ref(true);
+let pagination = reactive({
+  current: 1,
+  pageSize: 6,
+})
+
+
+let cameraD: any = computed(() => {
+  let cameras: any[] = []
+  if (props.cameraData.length <= pagination.pageSize) {
+    cameras = props.cameraData
+  } else {
+    const size = pagination.pageSize;
+    const result: any = [];
+    for (let i = 0; i < props.cameraData.length; i += size) {
+      result.push(props.cameraData.slice(i, i + size));
+    }
+    cameras = result[pagination.current - 1]
+  }
+  return cameras
+})
+
+
+
+//分页
+async function onChange(page) {
+  pagination.current = page
+  await getCamera('', playerRef, renderPlayer, '', cameraD.value);
+}
+
+onMounted(async () => {
+  await getCamera('', playerRef, renderPlayer, '', cameraD.value);
+});
+
+onBeforeUnmount(() => {
+  removeCamera(playerRef);
+});
+</script>
+
+<style lang="less" scoped>
+.camera-modal {
+  width: 100%;
+
+  .camrea-area {
+    width: 100%;
+    height: 600px;
+  }
+
+  .pagination-area {
+    width: 100%;
+    height: 60px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+}
+
+:deep(#LivePlayerBox) {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  flex-wrap: wrap;
+  pointer-events: none;
+
+  .liveVideo {
+    width: 462px !important;
+    height: 276px !important;
+    padding: 15px !important;
+    align-self: auto !important;
+    margin: 10px 8px !important;
+    background-size: 100% 100% !important;
+  }
+
+  .video-parent {
+    pointer-events: auto !important;
+    align-self: auto !important;
+  }
+}
+</style>

+ 1 - 1
src/views/vent/monitorManager/airDoor/components/deviceControl.vue

@@ -61,7 +61,7 @@ function confirmChoice() {
     margin: 4px;
     padding: 5px 8px;
     box-sizing: border-box;
-    background-color: rgba(54, 198, 254, .5);
+    background-color: rgba(9, 23, 44, .95);
   }
 
   .check-box {

+ 78 - 23
src/views/vent/monitorManager/airDoor/components/door-content-r.vue

@@ -5,7 +5,7 @@
         <SvgIcon class="icon-style" size="14" color="#30b3fc" name="open-door" />
         同步开启
       </a-button>
-      <a-button type="primary" style="margin: 0px 10px" @click="handlerOpenOrClose('close')">
+      <a-button type="primary" class="btn-control" @click="handlerOpenOrClose('close')">
         <SvgIcon class="icon-style" size="14" color="#30b3fc" name="close-door" />
         同步关闭
       </a-button>
@@ -13,22 +13,26 @@
         <SvgIcon class="icon-style" size="14" color="#30b3fc" name="time-setting-door" />
         定时设置
       </a-button>
-      <a-button style="margin: 0px 10px" type="primary" @click="handlerOperation">操作日志</a-button>
+      <a-button class="btn-control" type="primary" @click="handlerOperation">操作日志</a-button>
       <a-button type="primary" @click="handlerClock">同步PLC时钟</a-button>
+      <a-button class="btn-control" type="primary" @click="handlerCameraDetail">视频墙详情</a-button>
+      <a-button :class="index % 2 == 1 ? 'btn-control' : ''" v-for="(item, index) in dictCodes" :key="index"
+        type="primary" @click="handlerFz(item)">{{ item.title }}</a-button>
       <a-button class="device-control-btn" type="primary" @click="handlerDeviceChoice">集控设备选择</a-button>
+
+
+
     </div>
     <div class="content-r-container">
       <div class="content-r-box" v-for="(item, index) in infoDatas" :key="index">
         <div class="box-title">
+          <SvgIcon v-show="item.iconControl" class="title-icon" size="16"  name="select-control" />
           <div class="title-text">{{ item.strinstallpos }}</div>
           <div class="title-detail" @click="handlerDetail(item.deviceID)">详情</div>
         </div>
         <div class="box-content">
           <!-- plc时钟显示 -->
-          <!-- <div class="timePlcNow">2025-12-4 13:23:24</div> -->
-          <div class="timePlcNow">{{ item.readData.PLCyear ?
-            `20${item.readData.PLCyear}-${item.readData.PLCmonth}-${item.readData.PLCday}
-            ${item.readData.PLChour}:${item.readData.PLCminute}:${item.readData.PLCsecond}` : '' }}</div>
+          <div class="timePlcNow">{{ item.nowTime }}</div>
           <!-- 二三维信息 -->
           <gateSVG v-if="item.gateStyle == 'fmSp1' || item.gateStyle == 'fm_fc_ssl'"
             :ref="(el) => setChildRef(el, index)" :identify="index">
@@ -40,19 +44,15 @@
         <div class="setting-time">
           <div>
             <span>定时开启时间:</span>
-            <!-- <span class="set-time">13:23:24</span> -->
-            <span class="set-time">{{ item.readData.time_on_hour ?
-              `${item.readData.time_on_hour}:${item.readData.time_on_min}` : '--' }}</span>
+            <span class="set-time">{{ item.doorOpenTime }}</span>
           </div>
           <div>
             <span>启用状态:</span>
-            <span class="set-time">{{ item.readData.timerSwitch == '1' ? '启用' : '禁用' }}</span>
+            <span class="set-time">{{ item.timerSwitch == '1' ? '启用' : '禁用' }}</span>
           </div>
           <div>
             <span>定时关闭时间:</span>
-            <!-- <span class="set-time">13:23:24</span> -->
-            <span class="set-time">{{ item.readData.time_off_hour ?
-              `${item.readData.time_off_hour}:${item.readData.time_off_min}` : '--' }}</span>
+            <span class="set-time">{{ item.doorCloseTime }}</span>
           </div>
         </div>
       </div>
@@ -76,6 +76,11 @@
     <!-- 集控设备选择 -->
     <DeviceControl v-if="visibleDeviceControl" :deviceControlData="gateControlData" @confirmChoice="handlerChoice">
     </DeviceControl>
+    <!-- 视屏墙详情 -->
+    <Modal :title="cameraDetailTitle" :widthV="'1450px'" :visible="cameraDetailVisible" :centered="true"
+      :destroyOnClose="true" @cancel="handleCancelCameraDetail">
+      <CameraDetailModal :cameraData="cameraData"></CameraDetailModal>
+    </Modal>
   </div>
 </template>
 
@@ -84,6 +89,7 @@ import { reactive, ref, inject, watch, nextTick, onMounted } from 'vue';
 import syncModal from './syncModal.vue';
 import timeSetModal from './timeSetModal.vue';
 import CameraModal from './cameraModal.vue';
+import CameraDetailModal from './cameraDetailModal.vue'
 import Modal from './Modal.vue';
 import tipModal from './tipModal.vue'
 import gateDualSVG from './gateDualSVG.ssl.vue'
@@ -91,12 +97,12 @@ import gateSVG from './gateSVG.ssl.vue'
 import operationModal from './operationModal.vue'
 import DeviceControl from './deviceControl.vue'
 import { useRouter } from 'vue-router';
-import { devicecontrol, insertSyncRule, GetSyncRule, controlList, confirmChoice, manualTimeSync, isPasswordCurrect } from '../airdoor.api'
+import { devicecontrol, insertSyncRule, GetSyncRule, controlList, confirmChoice, manualTimeSync, isPasswordCurrect, dictList } from '../airdoor.api'
 import { doorStatus } from '../airdoor.data'
 // import { getModelComponent } from '../airdoor.data'
 import { useMessage } from '/@/hooks/web/useMessage';
 import { SvgIcon } from '/@/components/Icon';
-// import { getDictItemsByCode } from '/@/utils/dict';
+import { getDictItemsByCode } from '/@/utils/dict';
 
 let props = defineProps({
   infoData: {
@@ -130,6 +136,13 @@ let gateControlData = ref<any[]>([])
 //集控设备弹窗显示
 let visibleDeviceControl = ref(false)
 let tooltipText = ref('')
+//视屏墙详情
+let cameraDetailVisible = ref(false)
+let cameraDetailTitle = ref('视屏墙详情信息')
+let cameraData = ref<any[]>([])
+//风门集控类型-字典
+const dictCodes = getDictItemsByCode('gateCentralizedControl');
+let $emit=defineEmits(['toggleInfoData']) 
 
 const setChildRef = (el, index) => {
   childRefs.value[index] = el
@@ -159,7 +172,7 @@ async function handleOk(param) {
     }
   } else {
     let res = await isPasswordCurrect({ password: param.pass })
-    if (res===true) {
+    if (res === true) {
       visible.value = param.visib
       visibleDeviceControl.value = true
     } else {
@@ -254,15 +267,45 @@ async function handlerClock() {
     createMessage.success('PLC时钟同步成功!');
   }
 }
+//视屏详情-点击
+function handlerCameraDetail() {
+  cameraData.value.length = 0
+  infoDatas.value.forEach(el => {
+    if (el.cameras.length) {
+      cameraData.value.push(...el.cameras)
+    }
+  })
+  cameraDetailVisible.value = true
+}
+//视屏墙详情关闭
+function handleCancelCameraDetail(param) {
+  cameraDetailVisible.value = param
+}
+
+//设备集控-分组控制
+function handlerFz(item) {
+  infoDatas.value.forEach(el => {
+    if (el.gateCentralizedControl == item.value) {
+      el.iconControl = true
+    } else {
+      el.iconControl = false
+    }
+  })
+  $emit('toggleInfoData',item.value)
+}
+
 watch(() => props.infoData, (newV, oldV) => {
   infoDatas.value = newV
-  if (newV.length) {
-    nextTick(() => {
-      newV.forEach((el: any, index: number) => {
-        el = Object.assign(el, el.readData)
-        monitorAnimation(el, index);
-      })
+  if (infoDatas.value.length) {
+    infoDatas.value.forEach((el: any, index: number) => {
+      el = Object.assign(el, el.readData)
+      el.iconControl = false
+      el.nowTime = el.PLCyear ? `20${el.PLCyear}-${el.PLCmonth}-${el.PLCday} ${el.PLChour}:${el.PLCminute}:${el.PLCsecond}` : ''
+      el.doorOpenTime = el.time_on_hour ? `${el.time_on_hour.length != 1 ? el.time_on_hour : 0 + el.time_on_hour}:${el.time_on_min.length != 1 ? el.time_on_min : 0 + el.time_on_min}` : '--'
+      el.doorCloseTime = el.time_off_hour ? `${el.time_off_hour.length != 1 ? el.time_off_hour : 0 + el.time_off_hour}:${el.time_off_hour.length != 1 ? el.time_off_hour : 0 + el.time_off_hour}` : '--'
+      monitorAnimation(el, index);
     })
+
   }
 })
 
@@ -334,6 +377,12 @@ onMounted(() => {
         background-color: rgba(51, 211, 255, 0.2);
       }
 
+      .title-icon {
+        position: absolute;
+        left: 10px;
+        top: 7px;
+      }
+
       .title-detail {
         position: absolute;
         right: 12px;
@@ -367,13 +416,15 @@ onMounted(() => {
         bottom: -25px;
         left: 0;
         display: flex;
-        justify-content: space-between;
+        justify-content: space-around;
         color: #fff;
         font-size: 12px;
+        padding: 0px 25px;
       }
 
       .set-time {
         color: rgba(60, 242, 255);
+        margin: 0px 5px;
       }
 
       img {
@@ -386,6 +437,10 @@ onMounted(() => {
       }
     }
   }
+
+  .btn-control {
+    margin: 0px 10px;
+  }
 }
 
 ::v-deep .zxm-btn-primary {

+ 7 - 2
src/views/vent/monitorManager/airDoor/index.vue

@@ -6,7 +6,7 @@
         <doorMenuL :menuData="menuData" />
       </div>
       <div class="container-right">
-        <doorContentR :infoData="menuData" :visibleTs30="visibleTs30" />
+        <doorContentR :infoData="menuData" :visibleTs30="visibleTs30" @toggleInfoData="toggleInfoData" />
       </div>
     </div>
   </div>
@@ -22,6 +22,7 @@ import { getDevice, upcoming } from './airdoor.api'
 let menuData = ref<any[]>([]);
 //控制定时设置提示弹窗显示/隐藏
 let visibleTs30 = ref(false);
+let gateCentralizedControl = ref('')
 
 // https获取监测数据
 let timer: null | NodeJS.Timeout = null;
@@ -45,7 +46,7 @@ async function getMenuList() {
   let res = await getDevice({ devicetype: "gate", pagetype: "normal" })
   console.log(res, 'menuList')
   if (res.msgTxt[0].datalist.length) {
-    menuData.value = res.msgTxt[0].datalist.filter(v => v.isAutosync == '1')
+    menuData.value = gateCentralizedControl.value && gateCentralizedControl.value!='0' ? res.msgTxt[0].datalist.filter(v => v.isAutosync == '1' && v.gateCentralizedControl == gateCentralizedControl.value) : res.msgTxt[0].datalist.filter(v => v.isAutosync == '1')
   } else {
     menuData.value = []
   }
@@ -59,6 +60,10 @@ async function upcomingList() {
     visibleTs30.value = false
   }
 }
+//分组设备集控
+function toggleInfoData(param) {
+  gateCentralizedControl.value = param
+}
 
 onMounted(() => {
   getMenuList()

+ 205 - 182
src/views/vent/monitorManager/camera/index.vue

@@ -16,220 +16,243 @@
       </cameraTree>
     </div>
     <div class="right-area">
-      <div v-if="renderPlayer" ref="playerRef" style="display: flex; width: 100%; height: 100%; overflow-y: auto; pointer-events: none"> </div>
+      <div v-if="renderPlayer" ref="playerRef"
+        style="display: flex; width: 100%; height: calc(100% - 60px); overflow-y: auto; pointer-events: none"> </div>
       <div class="camera-box" v-else>
         <Empty />
       </div>
+      <div class="pagination">
+        <Pagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="Total"
+          @change="onChange" />
+      </div>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, onUnmounted, ref, reactive, computed, onBeforeUnmount } from 'vue';
-  import { useRouter } from 'vue-router';
-  import { list, cameraAddr, getCameraDevKind, getDevice, getVentanalyCamera } from './camera.api';
-  import cameraTree from './common/cameraTree.vue';
-  import { SvgIcon } from '/@/components/Icon';
-  import treeIcon from './common/Icon/treeIcon.vue';
-  import { useCamera } from '/@/hooks/system/useCamera';
-
-  const { getCamera, removeCamera } = useCamera();
-  const playerRef = ref();
-  const renderPlayer = ref(true);
-  //当前选中树节点
-  let selected = reactive<any>({
-    id: null,
-    pid: null,
-    title: '',
-    isFolder: false,
-  });
-  //tree菜单列表
-  let listArr = reactive<any[]>([]);
-  let searchParam = reactive({
-    devKind: '',
-    strType: '',
-  });
+import { onMounted, onUnmounted, ref, reactive, computed, onBeforeUnmount } from 'vue';
+import { useRouter } from 'vue-router';
+import { list, cameraAddr, getCameraDevKind, getDevice, getVentanalyCamera } from './camera.api';
+import cameraTree from './common/cameraTree.vue';
+import { SvgIcon } from '/@/components/Icon';
+import treeIcon from './common/Icon/treeIcon.vue';
+import { useCamera } from '/@/hooks/system/useCameraPianation';
+import { Pagination, Empty } from 'ant-design-vue';
 
-  let router = useRouter(); //路由
+const { getCamera, removeCamera, Total } = useCamera();
+const playerRef = ref();
+const renderPlayer = ref(true);
+//当前选中树节点
+let selected = reactive<any>({
+  id: null,
+  pid: null,
+  title: '',
+  isFolder: false,
+});
+//tree菜单列表
+let listArr = reactive<any[]>([]);
+let searchParam = reactive({
+  devKind: '',
+  strType: '',
+});
+let pagination = reactive({
+  current: 1,
+  pageSize: 6,
+})
+let router = useRouter(); //路由
 
-  async function getCameraDevKindList() {
-    let res = await getCameraDevKind();
-    if (res.length != 0) {
-      listArr.length = 0;
-      listArr.push({
-        pid: 'root',
-        isFolder: true,
-        expanded: true,
-        title: '全部',
-        id: 0,
-        children: [],
-      });
-      res.forEach((el) => {
-        el.pid = 0;
-        el.isFolder = true;
-        el.expanded = false;
-        el.title = el.itemText;
-        el.id = el.subDictId;
-        el.children = [];
-        listArr[0].children.push(el);
-      });
-      selected.id = listArr[0].id;
-      selected.pid = listArr[0].pid;
-      selected.title = listArr[0].title;
-      selected.isFolder = listArr[0].isFolder;
-    }
+async function getCameraDevKindList() {
+  let res = await getCameraDevKind();
+  if (res.length != 0) {
+    listArr.length = 0;
+    listArr.push({
+      pid: 'root',
+      isFolder: true,
+      expanded: true,
+      title: '全部',
+      id: 0,
+      children: [],
+    });
+    res.forEach((el) => {
+      el.pid = 0;
+      el.isFolder = true;
+      el.expanded = false;
+      el.title = el.itemText;
+      el.id = el.subDictId;
+      el.children = [];
+      listArr[0].children.push(el);
+    });
+    selected.id = listArr[0].id;
+    selected.pid = listArr[0].pid;
+    selected.title = listArr[0].title;
+    selected.isFolder = listArr[0].isFolder;
   }
+}
 
-  //点击目录
-  async function onClick(node) {
-    if (selected.title === node.title && selected.id === node.id) return;
-    selected.id = node.id;
-    selected.pid = node.pid;
-    selected.title = node.title;
-    selected.isFolder = node.isFolder;
-    if (node.pid != 'root') {
-      if (node.isFolder) {
-        let types, devicetype;
-        if (node.itemValue.indexOf('&') != -1) {
-          types = node.itemValue.substring(node.itemValue.indexOf('&') + 1);
-          devicetype = node.itemValue.substring(0, node.itemValue.indexOf('&'));
-        } else {
-          types = '';
-          devicetype = '';
-        }
-        let res = await getDevice({ ids: types, devicetype: devicetype });
-        if (res.msgTxt.length != 0) {
-          res.msgTxt[0].datalist.forEach((el) => {
-            el.pid = node.id;
-            el.isFolder = false;
-            el.title = el.strinstallpos;
-            el.id = el.deviceID;
-          });
-          listArr[0].children.forEach((v) => {
-            if (v.id == node.id) {
-              v.children = res.msgTxt[0].datalist;
-            }
-          });
-        }
-        searchParam.devKind = node.itemValue;
-        searchParam.strType = '';
-        let paramKind = searchParam.devKind.substring(0, searchParam.devKind.indexOf('&'));
-        await getCamera('', playerRef, renderPlayer, paramKind);
+//点击目录
+async function onClick(node) {
+  if (selected.title === node.title && selected.id === node.id) return;
+  selected.id = node.id;
+  selected.pid = node.pid;
+  selected.title = node.title;
+  selected.isFolder = node.isFolder;
+  if (node.pid != 'root') {
+    if (node.isFolder) {
+      let types, devicetype;
+      if (node.itemValue.indexOf('&') != -1) {
+        types = node.itemValue.substring(node.itemValue.indexOf('&') + 1);
+        devicetype = node.itemValue.substring(0, node.itemValue.indexOf('&'));
       } else {
-        await getCamera(node.deviceID, playerRef, renderPlayer);
+        types = '';
+        devicetype = '';
       }
-    } else {
-      searchParam.devKind = '';
+      let res = await getDevice({ ids: types, devicetype: devicetype });
+      if (res.msgTxt.length != 0) {
+        res.msgTxt[0].datalist.forEach((el) => {
+          el.pid = node.id;
+          el.isFolder = false;
+          el.title = el.strinstallpos;
+          el.id = el.deviceID;
+        });
+        listArr[0].children.forEach((v) => {
+          if (v.id == node.id) {
+            v.children = res.msgTxt[0].datalist;
+          }
+        });
+      }
+      searchParam.devKind = node.itemValue;
       searchParam.strType = '';
-      await getCamera('', playerRef, renderPlayer);
+      let paramKind = searchParam.devKind.substring(0, searchParam.devKind.indexOf('&'));
+      await getCamera('', playerRef, renderPlayer, pagination,'', paramKind);
+    } else {
+      await getCamera(node.deviceID, playerRef, renderPlayer, pagination);
     }
+  } else {
+    searchParam.devKind = '';
+    searchParam.strType = '';
+    await getCamera('', playerRef, renderPlayer, pagination);
   }
+}
 
-  //点击详情跳转
-  function onDetail(node) {
-    let str = listArr[0].children.filter((v) => v.id == node.pid)[0].itemValue;
-    let type = str.indexOf('&') != -1 ? str.substring(0, str.indexOf('&')) : '';
-    console.log(type, 'type--------');
-    switch (type) {
-      case 'pulping': //注浆
-        router.push('/grout-home');
-        break;
-      case 'window': //自动风窗
-        router.push('/monitorChannel/monitor-window?id=' + node.deviceID);
-        break;
-      case 'gate': //自动风门
-        router.push('/monitorChannel/monitor-gate?id=' + node.deviceID + '&deviceType=' + node.deviceType);
-        break;
-      case 'fanlocal': //局部风机
-        router.push('/monitorChannel/monitor-fanlocal?id=' + node.deviceID + '&deviceType=fanlocal');
-        break;
-      case 'fanmain': //主风机
-        router.push('/monitorChannel/monitor-fanmain?id=' + node.deviceID);
-        break;
-      case 'forcFan': //压风机
-        router.push('/forcFan/home');
-        break;
-      case 'pump': //瓦斯抽采泵
-        router.push('/monitorChannel/gasPump-home');
-        break;
-      case 'nitrogen': //制氮
-        router.push('/nitrogen-home');
-        break;
-    }
+//点击详情跳转
+function onDetail(node) {
+  let str = listArr[0].children.filter((v) => v.id == node.pid)[0].itemValue;
+  let type = str.indexOf('&') != -1 ? str.substring(0, str.indexOf('&')) : '';
+  console.log(type, 'type--------');
+  switch (type) {
+    case 'pulping': //注浆
+      router.push('/grout-home');
+      break;
+    case 'window': //自动风窗
+      router.push('/monitorChannel/monitor-window?id=' + node.deviceID);
+      break;
+    case 'gate': //自动风门
+      router.push('/monitorChannel/monitor-gate?id=' + node.deviceID + '&deviceType=' + node.deviceType);
+      break;
+    case 'fanlocal': //局部风机
+      router.push('/monitorChannel/monitor-fanlocal?id=' + node.deviceID + '&deviceType=fanlocal');
+      break;
+    case 'fanmain': //主风机
+      router.push('/monitorChannel/monitor-fanmain?id=' + node.deviceID);
+      break;
+    case 'forcFan': //压风机
+      router.push('/forcFan/home');
+      break;
+    case 'pump': //瓦斯抽采泵
+      router.push('/monitorChannel/gasPump-home');
+      break;
+    case 'nitrogen': //制氮
+      router.push('/nitrogen-home');
+      break;
   }
+}
+
+//分页
+async function onChange(page) {
+  pagination.current = page
+  await getCamera('', playerRef, renderPlayer, pagination);
+}
 
-  onMounted(async () => {
-    await getCameraDevKindList();
-    await getCamera('', playerRef, renderPlayer);
-  });
-  onBeforeUnmount(() => {
-    removeCamera(playerRef);
-  });
+onMounted(async () => {
+  await getCameraDevKindList();
+  await getCamera('', playerRef, renderPlayer, pagination);
+});
+onBeforeUnmount(() => {
+  removeCamera(playerRef);
+});
 </script>
 <style lang="less" scoped>
-  @import '/@/design/theme.less';
+@import '/@/design/theme.less';
 
-  @{theme-deepblue} {
-    .camera-container {
-      --image-camera_bg: url('/@/assets/images/themify/deepblue/vent/camera_bg.png');
-    }
+@{theme-deepblue} {
+  .camera-container {
+    --image-camera_bg: url('/@/assets/images/themify/deepblue/vent/camera_bg.png');
   }
+}
 
-  .camera-container {
-    --image-camera_bg: url('/@/assets/images/vent/camera_bg.png');
-    position: relative;
-    width: calc(100% - 30px);
-    height: calc(100% - 84px);
-    display: flex;
-    margin: 15px;
-    justify-content: space-between;
-    align-items: center;
+.camera-container {
+  --image-camera_bg: url('/@/assets/images/vent/camera_bg.png');
+  position: relative;
+  width: calc(100% - 30px);
+  height: calc(100% - 84px);
+  display: flex;
+  margin: 15px;
+  justify-content: space-between;
+  align-items: center;
 
-    .left-area {
-      width: 15%;
-      height: 100%;
-      padding: 20px;
-      border: 1px solid #99e8ff66;
-      background: #27546e1a;
-      box-shadow: 0px 0px 20px 7px rgba(145, 233, 254, 0.7) inset;
-      -moz-box-shadow: 0px 0px 20px 7px rgba(145, 233, 254, 0.7) inset;
-      -webkit-box-shadow: 0px 0px 50px 1px rgb(149 235 255 / 5%) inset;
-      box-sizing: border-box;
+  .left-area {
+    width: 15%;
+    height: 100%;
+    padding: 20px;
+    border: 1px solid #99e8ff66;
+    background: #27546e1a;
+    box-shadow: 0px 0px 20px 7px rgba(145, 233, 254, 0.7) inset;
+    -moz-box-shadow: 0px 0px 20px 7px rgba(145, 233, 254, 0.7) inset;
+    -webkit-box-shadow: 0px 0px 50px 1px rgb(149 235 255 / 5%) inset;
+    box-sizing: border-box;
 
-      // lxh
-      .iconfont {
-        color: #fff;
-        font-size: 12px;
-        margin-left: 5px;
-      }
+    // lxh
+    .iconfont {
+      color: #fff;
+      font-size: 12px;
+      margin-left: 5px;
     }
+  }
 
-    .right-area {
-      width: 85%;
-      height: 100%;
-      padding: 0px 0px 0px 15px;
-      box-sizing: border-box;
-    }
+  .right-area {
+    width: 85%;
+    height: 100%;
+    padding: 0px 0px 0px 15px;
+    box-sizing: border-box;
   }
 
-  :deep(#LivePlayerBox) {
+  .pagination {
+    width: 100%;
+    height: 60px;
     display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    flex-wrap: wrap;
-    pointer-events: none;
+    justify-content: center;
+    align-items: center;
+  }
+}
 
-    .liveVideo {
-      width: 510px !important;
-      height: 320px !important;
-      align-self: auto !important;
-      margin: 10px 8px !important;
-      background-size: 100% 100% !important;
-      padding: 15px !important;
-    }
+:deep(#LivePlayerBox) {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  flex-wrap: wrap;
+  pointer-events: none;
+
+  .liveVideo {
+    width: 510px !important;
+    height: 320px !important;
+    align-self: auto !important;
+    margin: 10px 8px !important;
+    background-size: 100% 100% !important;
+    padding: 15px !important;
+  }
 
-    // .video-parent {
-    //   pointer-events: auto !important;
-    //   align-self: auto !important;
-    // }
+  .video-parent {
+    pointer-events: auto !important;
+    align-self: auto !important;
   }
+}
 </style>

+ 29 - 13
src/views/vent/monitorManager/footageMonitor/components/moduleCommon.vue

@@ -7,10 +7,10 @@
         </template>
         <template #container>
           <div class="container-t">
-            <ModuleHead  :timeDate="timeDate" @changeTime="changeTime"></ModuleHead>
+            <ModuleHead :timeDate="timeDate" @changeTime="changeTime"></ModuleHead>
           </div>
           <div class="container-b">
-            <SingLineArea :option="option" :chartData="chartData" height="280px"></SingLineArea>
+            <SingLineArea :option="Option" :chartData="chartData" height="280px"></SingLineArea>
           </div>
         </template>
       </ventBox1>
@@ -20,8 +20,8 @@
         </template>
         <template #container>
           <div class="container-t">
-            <ModuleHead :isShowSelect="true" :menuData="gasMenuList" :timeDate="timeDate" :devLabel="gasDevLabel" @changeMenu="changeGasMenu"
-              @changeTime="changeTime"></ModuleHead>
+            <ModuleHead :isShowSelect="true" :menuData="gasMenuList" :timeDate="timeDate" :devLabel="gasDevLabel"
+              @changeMenu="changeGasMenu" @changeTime="changeTime"></ModuleHead>
           </div>
           <div class="container-b">
             <BarAndLine class="echarts-line" :xAxisPropType="xAxisPropType" :dataSource="gasList" height="280px"
@@ -36,7 +36,7 @@
       <div class="item-box sensor-container">
         <ventBox1>
           <template #title>
-            <div>工作面进尺及瓦斯涌出量</div>
+            <div>{{ echartTitle }}</div>
           </template>
           <template #container>
             <SingLineArea :option="optionGas" :chartData="chartGasData" height="280px"></SingLineArea>
@@ -51,19 +51,35 @@
 
 <script setup lang="ts">
 
-import {  ref, onMounted, onUnmounted, reactive, defineProps,watchEffect } from 'vue';
+import { ref, onMounted, onUnmounted, reactive, defineProps, watchEffect } from 'vue';
 import ventBox1 from '/@/components/vent/ventBox1.vue'
 import SingLineArea from '@/components/chart/SingLineArea.vue'
 import BarAndLine from '@/components/chart/BarAndLine.vue';
 import ModuleHead from './moduleHead.vue'
-import { option, gasMenuList, chartsColumns, echatsOption, optionGas } from '../footage.data'
+import { gasMenuList, chartsColumns, echatsOption,  } from '../footage.data'
 import { list, getCurveGraphData } from '../footage.api';
 import dayjs from 'dayjs';
 
-let props=defineProps({
-  optionValue:{
-    type:String,
-    default:''
+let props = defineProps({
+  optionValue: {
+    type: String,
+    default: ''
+  },
+  echartTitle: {
+    type: String,
+    default: ''
+  },
+  Option:{
+    type:Object,
+    default:()=>{
+      return {}
+    }
+  },
+  optionGas:{
+    type:Object,
+    default:()=>{
+      return {}
+    }
   }
 })
 let paramData = ref<any[]>([])
@@ -171,8 +187,8 @@ function changeTime(param) {
   getCurveGraphDataList()
 }
 
-watchEffect(()=>{
-  props.optionValue &&  getCurveGraphDataList()
+watchEffect(() => {
+  props.optionValue && getCurveGraphDataList()
 })
 onMounted(async () => {
   await getMonitor()

+ 287 - 0
src/views/vent/monitorManager/footageMonitor/footage.data.ts

@@ -153,6 +153,158 @@ export const option = {
     },
   ]
 };
+export const optionLy = {
+  grid: {
+    top: '14%',
+    left: '12%',
+    bottom: '12%',
+    right: '10%',
+    //   containLabel: true,
+  },
+  xAxis: {
+    type: 'category',
+    boundaryGap: false,
+    axisLabel: {
+      // formatter: '{value}',
+      fontSize: 12,
+      margin: 10,
+      textStyle: {
+        color: '#b3b8cc',
+      },
+      // interval: 0,
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#244a94',
+      },
+    },
+    splitLine: {
+      show: true,
+      lineStyle: {
+        color: '#0d2973',
+        type: 'dashed',
+      },
+    },
+    axisTick: {
+      show: false,
+    },
+
+  },
+  yAxis: {
+    boundaryGap: false,
+    name: '',
+    type: 'value',
+    // max: props.maxY,
+    // min:props.minY,
+    axisLabel: {
+      textStyle: {
+        color: '#b3b8cc',
+      },
+    },
+    nameTextStyle: {
+      color: '#fff',
+      fontSize: 12,
+      lineHeight: 5,
+    },
+    splitLine: {
+      lineStyle: {
+        color: '#0d2973',
+        type: 'dashed',
+      },
+    },
+    axisLine: {
+      show: true,
+      lineStyle: {
+        color: '#244a94',
+      },
+    },
+    axisTick: {
+      show: false,
+    },
+  },
+  series: [
+    {
+      name: '',
+      type: 'line',
+      smooth: false,
+      showSymbol: true,
+      zlevel: 3,
+      itemStyle: {
+        color: '#4874cb',
+        borderColor: 'red',
+      },
+      lineStyle: {
+        normal: {
+          width: 2,
+          color: '#4874cb',
+        },
+      },
+      data: 'val',
+    },
+    {
+      name: '',
+      type: 'line',
+      smooth: false,
+      showSymbol: true,
+      zlevel: 3,
+      itemStyle: {
+        color: '#ee822f',
+        borderColor: '#a3c8d8',
+      },
+      lineStyle: {
+        normal: {
+          width: 2,
+          color: '#ee822f',
+        },
+      },
+
+      data: 'val1',
+    },
+    {
+      name: '',
+      type: 'line',
+      smooth: false,
+      showSymbol: true,
+      zlevel: 3,
+      itemStyle: {
+        color: '#f2ba02',
+        borderColor: '#a3c8d8',
+      },
+      lineStyle: {
+        normal: {
+          width: 2,
+          color: '#f2ba02',
+        },
+      },
+      areaStyle: {
+        normal: {
+          color: new echarts.graphic.LinearGradient(
+            0,
+            0,
+            0,
+            1,
+            [
+              {
+                offset: 0,
+                color: 'rgba(242, 186, 2,0.8)',
+              },
+              {
+                offset: 0.5,
+                color: 'rgba(242, 186, 2,0.4)',
+              },
+              {
+                offset: 0.9,
+                color: 'rgba(242, 186, 2,0)',
+              },
+            ],
+            false
+          ),
+        },
+      },
+      data: 'val2',
+    },
+  ]
+};
 export const optionGas = {
   grid: {
     top: '14%',
@@ -288,6 +440,141 @@ export const optionGas = {
   ]
 };
 
+export const optionGasLy = {
+  grid: {
+    top: '14%',
+    left: '12%',
+    bottom: '12%',
+    right: '10%',
+    //   containLabel: true,
+  },
+  xAxis: {
+    type: 'category',
+    boundaryGap: false,
+    axisLabel: {
+      // formatter: '{value}',
+      fontSize: 12,
+      margin: 10,
+      textStyle: {
+        color: '#b3b8cc',
+      },
+      // interval: 0,
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#244a94',
+      },
+    },
+    splitLine: {
+      show: true,
+      lineStyle: {
+        color: '#0d2973',
+        type: 'dashed',
+      },
+    },
+    axisTick: {
+      show: false,
+    },
+
+  },
+  yAxis: {
+    boundaryGap: false,
+    name: '',
+    type: 'value',
+    // max: props.maxY,
+    // min:props.minY,
+    axisLabel: {
+      textStyle: {
+        color: '#b3b8cc',
+      },
+    },
+    nameTextStyle: {
+      color: '#fff',
+      fontSize: 12,
+      lineHeight: 5,
+    },
+    splitLine: {
+      lineStyle: {
+        color: '#0d2973',
+        type: 'dashed',
+      },
+    },
+    axisLine: {
+      show: true,
+      lineStyle: {
+        color: '#244a94',
+      },
+    },
+    axisTick: {
+      show: false,
+    },
+  },
+  series: [
+    {
+      name: '工作面来压',
+      type: 'line',
+      smooth: false,
+      showSymbol: true,
+      zlevel: 3,
+      itemStyle: {
+        color: '#4874cb',
+        borderColor: 'red',
+      },
+      lineStyle: {
+        normal: {
+          width: 2,
+          color: '#4874cb',
+        },
+      },
+      data: 'val',
+    },
+
+    {
+      name: '瓦斯浓度',
+      type: 'line',
+      smooth: false,
+      showSymbol: true,
+      zlevel: 3,
+      itemStyle: {
+        color: '#f2ba02',
+        borderColor: '#a3c8d8',
+      },
+      lineStyle: {
+        normal: {
+          width: 2,
+          color: '#f2ba02',
+        },
+      },
+      areaStyle: {
+        normal: {
+          color: new echarts.graphic.LinearGradient(
+            0,
+            0,
+            0,
+            1,
+            [
+              {
+                offset: 0,
+                color: 'rgba(242, 186, 2,0.8)',
+              },
+              {
+                offset: 0.5,
+                color: 'rgba(242, 186, 2,0.4)',
+              },
+              {
+                offset: 0.9,
+                color: 'rgba(242, 186, 2,0)',
+              },
+            ],
+            false
+          ),
+        },
+      },
+      data: 'val1',
+    },
+  ]
+};
+
 export let gasMenuList = [
   { label: 'T0(上隅角)', value: '1' },
   { label: 'T1(回风流距工作面割煤线10m范围内)', value: '2' },

+ 5 - 1
src/views/vent/monitorManager/footageMonitor/index.vue

@@ -8,7 +8,7 @@
       >进尺与瓦斯涌出分析</customHeader
     >
     <div class="box-container">
-      <ModuleCommon :optionValue="optionValue" />
+      <ModuleCommon :optionValue="optionValue" :echartTitle="echartTitle" :Option="option" :optionGas="optionGas"/>
     </div>
     <!-- 巷道模型组件 -->
     <VentModal />
@@ -23,11 +23,13 @@
   import { useRouter, useRoute } from 'vue-router';
   import VentModal from '/@/components/vent/micro/ventModal.vue';
   import { unmountMicroApps } from '/@/qiankun';
+  import { option,optionGas } from './footage.data'
 
   let options = ref<any[]>([]);
   let optionValue = ref('');
   let router = useRouter(); //路由
   const currentRoute = useRoute();
+  const echartTitle=ref('工作面进尺及瓦斯涌出量')
 
   async function getSysDataSource() {
     let res = await list({ devicekind: 'footageGas' });
@@ -70,8 +72,10 @@
     height: 100%;
 
     .box-container {
+      position: relative;
       margin-top: 80px;
       height: calc(100% - 80px);
+      z-index: 999;
     }
   }
 </style>

+ 55 - 56
src/views/vent/monitorManager/footageMonitor/indexDhz.vue

@@ -1,14 +1,9 @@
 <template>
   <div class="footage-box">
-    <customHeader
-      :fieldNames="{ label: 'label', value: 'value', options: 'children' }"
-      :options="options"
-      @change="getSelectRow"
-      :optionValue="optionValue"
-      >工作面来压与瓦斯涌出分析</customHeader
-    >
+    <customHeader :fieldNames="{ label: 'label', value: 'value', options: 'children' }" :options="options"
+      @change="getSelectRow" :optionValue="optionValue">工作面来压与瓦斯涌出分析</customHeader>
     <div class="box-container">
-      <ModuleCommon :optionValue="optionValue" />
+      <ModuleCommon :optionValue="optionValue" :echartTitle="echartTitle" :Option="optionLy" :optionGas="optionGasLy" />
     </div>
     <!-- 巷道模型组件 -->
     <VentModal />
@@ -16,62 +11,66 @@
 </template>
 
 <script setup lang="ts">
-  import { onBeforeMount, ref, onMounted, onUnmounted, nextTick, provide, onBeforeUnmount } from 'vue';
-  import customHeader from '/@/components/vent/customHeader.vue';
-  import ModuleCommon from './components/moduleCommon.vue';
-  import { list } from './footage.api';
-  import { useRouter, useRoute } from 'vue-router';
-  import VentModal from '/@/components/vent/micro/ventModal.vue';
-  import { unmountMicroApps } from '/@/qiankun';
+import { onBeforeMount, ref, onMounted, onUnmounted, nextTick, provide, onBeforeUnmount } from 'vue';
+import customHeader from '/@/components/vent/customHeader.vue';
+import ModuleCommon from './components/moduleCommon.vue';
+import { list } from './footage.api';
+import { useRouter, useRoute } from 'vue-router';
+import VentModal from '/@/components/vent/micro/ventModal.vue';
+import { unmountMicroApps } from '/@/qiankun';
+import { optionLy,optionGasLy } from './footage.data'
 
-  let options = ref<any[]>([]);
-  let optionValue = ref('');
-  let router = useRouter(); //路由
-  const currentRoute = useRoute();
+let options = ref<any[]>([]);
+let optionValue = ref('');
+let router = useRouter(); //路由
+const currentRoute = useRoute();
+const echartTitle=ref('工作面来压及瓦斯涌出量')
 
-  async function getSysDataSource() {
-    let res = await list({ devicekind: 'footageGas' });
-    options.value =
-      res.records.map((el) => {
-        return {
-          label: el.strname,
-          value: el.id,
-        };
-      }) || [];
-    if (!optionValue.value) {
-      optionValue.value = options.value[0]['value'];
-      nextTick(() => {
-        getSelectRow(optionValue.value);
-      });
-    }
+async function getSysDataSource() {
+  let res = await list({ devicekind: 'footageGas' });
+  options.value =
+    res.records.map((el) => {
+      return {
+        label: el.strname,
+        value: el.id,
+      };
+    }) || [];
+  if (!optionValue.value) {
+    optionValue.value = options.value[0]['value'];
+    nextTick(() => {
+      getSelectRow(optionValue.value);
+    });
   }
+}
 
-  // 切换检测数据
-  async function getSelectRow(deviceID) {
-    console.log(deviceID, '选项切换');
-    optionValue.value = deviceID;
-    router.push(`${currentRoute.path}?deviceType=default&sysID=${optionValue.value}`);
-  }
+// 切换检测数据
+async function getSelectRow(deviceID) {
+  console.log(deviceID, '选项切换');
+  optionValue.value = deviceID;
+  router.push(`${currentRoute.path}?deviceType=default&sysID=${optionValue.value}`);
+}
 
-  onMounted(async () => {
-    await getSysDataSource();
-  });
-  onBeforeUnmount(() => {
-    unmountMicroApps(['/micro-vent-3dModal']);
-  });
+onMounted(async () => {
+  await getSysDataSource();
+});
+onBeforeUnmount(() => {
+  unmountMicroApps(['/micro-vent-3dModal']);
+});
 </script>
 <style lang="less" scoped>
-  @import '/@/design/theme.less';
-  @ventSpace: zxm;
+@import '/@/design/theme.less';
+@ventSpace: zxm;
 
-  .footage-box {
-    position: relative;
-    width: 100%;
-    height: 100%;
+.footage-box {
+  position: relative;
+  width: 100%;
+  height: 100%;
 
-    .box-container {
-      margin-top: 80px;
-      height: calc(100% - 80px);
-    }
+  .box-container {
+    position: relative;
+    margin-top: 80px;
+    height: calc(100% - 80px);
+    z-index: 999;
   }
+}
 </style>