Prechádzať zdrojové kódy

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

hmm 3 mesiacov pred
rodič
commit
ff9579470c

+ 9 - 0
src/api/globalMap/forestDefenseVideo.ts

@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+export const getPtzInfo = (params) => {
+  return request({
+    url: '/api/videoResource/hkvideo/get_ptz_info',
+    method: 'get',
+    params: params
+  });
+};

+ 42 - 17
src/components/HKVideo/hikvision-h5player.vue

@@ -8,6 +8,7 @@
 
 <script setup lang="ts">
 import { reactive, onMounted, onActivated, nextTick, onBeforeUnmount } from 'vue';
+import { parseTime } from '@/utils/ruoyi';
 
 const play = async (url) => {
   state.wsUrl = url;
@@ -19,30 +20,24 @@ const stop = async () => {
 };
 
 const playback = async (url, startTime, endTime) => {
-  console.log('playback', url, state.mode, startTime, endTime);
+  // console.log('playback', url, state.mode, startTime, endTime);
   startTime += 'Z';
   endTime += 'Z';
   state.player &&
     state.player.JS_Play(url, { playURL: url, mode: 1 }, 0, startTime, endTime).then(
       () => {
-        console.log('playback success 播放成功');
+        // console.log('playback success 播放成功');
         state.player && state.player.JS_Resize();
         state.isLoading = false;
         emits('onPlaying');
       },
       (e) => {
         emits('onPlayError');
-        console.error('playerror', e);
+        // console.error('playerror', e);
       }
     );
 };
 
-defineExpose({
-  play,
-  stop,
-  playback
-});
-
 const emits = defineEmits(['onPlaying', 'onPlayError']);
 
 const state = reactive({
@@ -97,11 +92,11 @@ const createPlayer = () => {
   state.player.JS_SetWindowControlCallback({
     windowEventSelect: function (iWndIndex) {
       //插件选中窗口回调
-      console.log('windowSelect callback: ', iWndIndex);
+      // console.log('windowSelect callback: ', iWndIndex);
     },
     pluginErrorHandler: function (iWndIndex, iErrorCode, oError) {
       //插件错误回调
-      console.log('插件错误回调pluginError callback: ', iWndIndex, iErrorCode, oError);
+      // console.log('插件错误回调pluginError callback: ', iWndIndex, iErrorCode, oError);
       // setTimeout(() => {
       //   realplay(state.wsUrl);
       // }, 100);
@@ -122,18 +117,18 @@ const createPlayer = () => {
     },
     windowFullCcreenChange: function (bFull) {
       //全屏切换回调
-      console.log('全屏切换回调fullScreen callback: ', bFull);
-      if (!bFull) {
-        console.log('退出全屏');
-      }
+      // console.log('全屏切换回调fullScreen callback: ', bFull);
+      // if (!bFull) {
+      //   console.log('退出全屏');
+      // }
     },
     firstFrameDisplay: function (iWndIndex, iWidth, iHeight) {
       //首帧显示回调
-      console.log('firstFrame loaded callback: ', iWndIndex, iWidth, iHeight);
+      // console.log('firstFrame loaded callback: ', iWndIndex, iWidth, iHeight);
     },
     performanceLack: function () {
       //性能不足回调
-      console.log('performanceLack callback: ');
+      // console.log('performanceLack callback: ');
       stopPlay();
     }
   });
@@ -190,6 +185,36 @@ const fullScreen = (type) => {
       }
     );
 };
+
+// 截图功能
+const handleScreenshot = (name) => {
+  const video = document.getElementById(state.id + '_playVideo0'); // 使用保存的引用或直接获取
+  if (!video) {
+    console.error('Video element not found!');
+    return;
+  }
+  const canvas = document.createElement('canvas');
+  canvas.width = video.videoWidth;
+  canvas.height = video.videoHeight;
+  const context = canvas.getContext('2d');
+  context.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+  let time = new Date().getTime();
+  let fileName = name ? name : '';
+  fileName += '截图' + time + '.png';
+  const dataURL = canvas.toDataURL('image/png');
+  const a = document.createElement('a');
+  a.href = dataURL;
+  a.download = fileName;
+  a.click();
+};
+
+defineExpose({
+  play,
+  stop,
+  playback,
+  handleScreenshot
+});
 </script>
 
 <style lang="scss">

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

@@ -7,7 +7,7 @@
         style="width: 100%; height: 100%; object-fit: fill"
         @on-playing="onHkPlaying"
         @on-play-error="onHKPlayError"
-      ></HikvisionPlayer>
+      />
       <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">
@@ -44,11 +44,6 @@ const refresh_data = () => {
   // });
 };
 
-defineExpose({
-  play,
-  refresh_data
-});
-
 const emits = defineEmits(['propClick', 'videoPreviewClick', 'favorClick']);
 
 // https://blog.csdn.net/weixin_49826079/article/details/135147184
@@ -123,7 +118,14 @@ const stop_now = async () => {
   }
   posterVisible.value = true;
 };
-
+const handleScreenshot = (name) => {
+  videoPlayer.value.handleScreenshot(name);
+};
+defineExpose({
+  play,
+  refresh_data,
+  handleScreenshot
+});
 onMounted(() => {
   if (!!props.autoplay) {
     play_now();

+ 1 - 1
src/components/Map/YztMap/index.vue

@@ -73,7 +73,7 @@ watch(
 );
 const mapList = reactive({
   satellite2: ['YZT1715739306532', 'YZT1695608158269'],
-  satellite3: ['tianditu'],
+  satellite3: ['YZT1708679726700', 'YZT1695608158269'],
   imageMap: ['YZT1640925052482', 'YZT1695608158269'],
   imageMap2: ['YZT1640925052482', 'YZT1695608158269']
 });

+ 2 - 3
src/components/Map/index.vue

@@ -55,7 +55,7 @@ const mapState = reactive<MapState>({
   center: [110.925175, 21.978955],
   zoom: 8.5,
   minZoom: 6,
-  maxZoom: 20,
+  maxZoom: 17,
   isThreeDimensional: false,
   // 是否显示比例尺
   showScale: true
@@ -72,6 +72,7 @@ const mapUtils = useAMap({
   version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
   pitch: mapState.isThreeDimensional ? 45 : 0,
   zoom: mapState.zoom,
+  maxZoom: mapState.maxZoom,
   center: [mapState.center[0], mapState.center[1]],
   dragEnable: true,
   scrollWheel: true,
@@ -84,8 +85,6 @@ const mapUtils = useAMap({
     scale = getScale();
     if (!['logical', 'vectorgraph'].includes(props.activeMap)) {
       switchMap(props.activeMap);
-    } else {
-      map.removeLayer();
     }
     placeSearch = new AMap.PlaceSearch({
       pageSize: 30,

+ 54 - 38
src/hooks/AMap/useAMap.ts

@@ -10,6 +10,8 @@ export function useAMap(options) {
   };
   let clickMarker = null;
   let addPoints = [];
+  let layers = [];
+  let defaultLayer;
   // 初始化事件
   const initMap = (options) => {
     window._AMapSecurityConfig = {
@@ -20,27 +22,17 @@ export function useAMap(options) {
       version: !!options.version ? options.version : '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
       plugins: options.plugins
         ? options.plugins
-        : [
-            'AMap.Scale',
-            'AMap.RangingTool',
-            'AMap.MouseTool',
-            'AMap.PolygonEditor',
-            'AMap.MarkerCluster',
-            'AMap.DistrictSearch',
-            'AMap.MoveAnimation',
-            'AMap.Driving',
-            'AMap.Geocoder',
-            'AMap.PlaceSearch',
-            'AMap.GeoJSON'
-          ]
+        : ['AMap.Scale', 'AMap.RangingTool', 'AMap.MouseTool', 'AMap.PolygonEditor', 'AMap.MarkerCluster', 'AMap.DistrictSearch', 'AMap.MoveAnimation', 'AMap.Driving', 'AMap.Geocoder', 'AMap.PlaceSearch', 'AMap.GeoJSON']
     }).then((res) => {
       AMap = res;
+      defaultLayer = AMap.createDefaultLayer();
       map = new AMap.Map(options.el ? options.el : 'aMap', {
         WebGLParams: {
           preserveDrawingBuffer: true
         },
+        layers: [defaultLayer],
         // 是否为3D地图模式
-        viewMode: '3D',
+        // viewMode: '3D',
         pitch: options.pitch,
         // 初始化地图级别
         zoom: options.zoom ? options.zoom : 11,
@@ -49,7 +41,8 @@ export function useAMap(options) {
         // 是否可拖拽
         dragEnable: options.dragEnable,
         // 是否允许通过鼠标滚轮来缩放
-        scrollWheel: options.scrollWheel
+        scrollWheel: options.scrollWheel,
+        maxZoom: options.maxZoom ? options.maxZoom : 20
       });
       // 初始化比例尺
       if (options.showScale) {
@@ -70,30 +63,52 @@ export function useAMap(options) {
   const getScale = () => {
     return scale;
   };
+  const initLayer = (type: string) => {
+    if (defaultLayer) {
+      map.removeLayer(defaultLayer);
+      defaultLayer = null;
+    }
+    let keys = ['http://t0.tianditu.gov.cn/vec_w/wmts', 'http://t0.tianditu.gov.cn/cva_w/wmts'];
+    if (type === 'satellite') {
+      keys = ['http://t0.tianditu.gov.cn/img_w/wmts', 'http://t0.tianditu.gov.cn/cia_w/wmts'];
+    }
+    const layer = new AMap.TileLayer.WMTS({
+      url: keys[0],
+      blend: false,
+      tileSize: 256,
+      params: {
+        Layer: 'img',
+        Version: '1.0.0',
+        Format: 'tiles',
+        TileMatrixSet: 'w',
+        STYLE: 'default',
+        tk: 'a8df87f1695d224d2679aa805c1268d9'
+      }
+    });
+    const layerMark = new AMap.TileLayer.WMTS({
+      url: keys[1],
+      blend: false,
+      tileSize: 256,
+      params: {
+        Layer: type === 'satellite' ? 'cia' : 'cva',
+        Version: '1.0.0',
+        Format: 'tiles',
+        TileMatrixSet: 'w',
+        STYLE: 'default',
+        tk: 'a8df87f1695d224d2679aa805c1268d9'
+      }
+    });
+    return [layer, layerMark];
+  };
   // 切换地图
   const switchMap = (type: string) => {
-    if (type === 'vectorgraph') {
-      map.removeLayer(nowLayer);
-    } else if (type === 'satellite') {
-      const satellite = new AMap.TileLayer.WMTS({
-        // url: 'http://t0.tianditu.gov.cn/img_c/wmts',
-        url: 'http://t4.tianditu.gov.cn/img_w/wmts',
-        blend: false,
-        tileSize: 256,
-        params: {
-          Layer: 'img',
-          Version: '1.0.0',
-          Format: 'tiles',
-          TileMatrixSet: 'w',
-          STYLE: 'default',
-          tk: 'a8df87f1695d224d2679aa805c1268d9' // 申请的天地图开发者key
-        }
-      });
-      satellite.setMap(map);
-      // const satellite = new AMap.TileLayer.Satellite();
-      // map.addLayer(satellite);
-      nowLayer = satellite;
-    }
+    layers.forEach((layer) => {
+      map.removeLayer(layer);
+    });
+    layers = initLayer(type);
+    layers.forEach((layer) => {
+      layer.setMap(map);
+    });
   };
   // 添加搜索的标记的
   const addSearchMarker = (item) => {
@@ -307,7 +322,7 @@ export function useAMap(options) {
         polygon.show();
       });
     } else {
-      data = convertCoordinates(data);
+      // data = convertCoordinates(data);
       // 遮罩部分
       // const outer = [
       //   new AMap.LngLat(-180, 90, true),
@@ -628,6 +643,7 @@ export function useAMap(options) {
     initMap(options);
   });
   return {
+    initMap,
     getAMap,
     getMap,
     switchMap,

+ 84 - 13
src/utils/olMap/olMap.ts

@@ -25,7 +25,7 @@ import { fromLonLat } from 'ol/proj';
 import axios from 'axios';
 import { fromExtent } from 'ol/geom/Polygon';
 import { LinearRing, LineString, Polygon } from 'ol/geom';
-import {Graticule} from "ol/layer";
+import { Graticule } from 'ol/layer';
 import { getPointsCenter, mergeGeoJsonPolygons } from '@/utils/gisUtils';
 import { Cluster } from 'ol/source';
 import CircleStyle from 'ol/style/Circle';
@@ -34,11 +34,15 @@ import { Draw, Select } from 'ol/interaction';
 import { click } from 'ol/events/condition';
 import Circle from 'ol/geom/Circle';
 import { hexToRgba } from '@/utils';
+import { createBox } from 'ol/interaction/Draw';
 
 const tk = 'a8df87f1695d224d2679aa805c1268d9';
 const commonUrl = import.meta.env.VITE_APP_BASE_API2 + 'api/oneShare/proxyHandler/gd/';
 // proj4.defs('EPSG:4490', '+proj=longlat +ellps=GRS80 +no_defs +type=crs');
-proj4.defs('EPSG:4490', 'GEOGCS["China Geodetic Coordinate System 2000",DATUM["China_2000",SPHEROID["CGCS2000",6378137,298.257222101,AUTHORITY["EPSG","1024"]],AUTHORITY["EPSG","1043"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4490"]]');
+proj4.defs(
+  'EPSG:4490',
+  'GEOGCS["China Geodetic Coordinate System 2000",DATUM["China_2000",SPHEROID["CGCS2000",6378137,298.257222101,AUTHORITY["EPSG","1024"]],AUTHORITY["EPSG","1043"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4490"]]'
+);
 // proj4.defs('EPSG:4525', '+proj=tmerc +lat_0=0 +lon_0=111 +k=1 +x_0=37500000 +y_0=0 +ellps=GRS80 +units=m +no_defs +type=crs');
 register(proj4);
 const projection = new Projection({
@@ -173,7 +177,7 @@ export class olMap {
     // 添加新的图层
     if (Array.isArray(options.id)) {
       for (const layer of options.id) {
-        if (typeof layer === "string") {
+        if (typeof layer === 'string') {
           await this.formatXml(layer); // 等待当前 layer 处理完成
         } else {
           await this.formatXml(layer.id, layer.minZoom, layer.maxZoom, layer.zIndex, layer.visible); // 等待当前 layer 处理完成
@@ -211,11 +215,11 @@ export class olMap {
       TILECOL: '{x}',
       TILEROW: '{y}',
       TILEMATRIX: '{z}',
-      tk: key,
+      tk: key
     };
     const newParams = Object.assign({}, params, { LAYER: dataType });
     let paramsStr = '';
-    for (let [k, v] of Object.entries(newParams)) {
+    for (const [k, v] of Object.entries(newParams)) {
       paramsStr += `${k}=${v}&`;
     }
     return `${baseUrl}${dataType}_${projType}/wmts?${paramsStr.slice(0, -1)}`;
@@ -317,9 +321,14 @@ export class olMap {
   drawGraphics2(newOptions: MouseTool) {
     const typeList = {
       circle: 'Circle',
-      // rectangle: 'Rectangle',
+      rectangle: 'Circle',
       polygon: 'Polygon'
     };
+    let geometryFunction = null;
+    if (newOptions.graphicsType === 'rectangle') {
+      // 绘制矩形的方法
+      geometryFunction = createBox();
+    }
     if (!!typeList[newOptions.graphicsType]) {
       this.drawOptions = {
         type: newOptions.graphicsType,
@@ -327,7 +336,7 @@ export class olMap {
         strokeOpacity: 1,
         strokeWeight: 1,
         fillColor: newOptions.color,
-        fillOpacity: newOptions.drawType === '1' ? 0 : 0.5,
+        fillOpacity: newOptions.drawType === '1' ? '0' : '0.5',
         strokeStyle: 'solid'
       };
       this.closeDraw();
@@ -344,6 +353,7 @@ export class olMap {
       this.drawTool = new Draw({
         source: this.drawVector.getSource(),
         type: typeList[newOptions.graphicsType],
+        geometryFunction: geometryFunction,
         style: style
       });
       // 添加绘制交互到地图
@@ -372,7 +382,7 @@ export class olMap {
 
     if (Array.isArray(newLayers)) {
       for (const layer of newLayers) {
-        if (typeof layer === "string") {
+        if (typeof layer === 'string') {
           await this.formatXml(layer); // 等待当前 layer 处理完成
         } else {
           await this.formatXml(layer.id, layer.minZoom, layer.maxZoom, layer.zIndex, layer.visible); // 等待当前 layer 处理完成
@@ -707,12 +717,13 @@ export class olMap {
     const graticule = new Graticule({
       name: 'Graticule',
       showLabels: true, // 为每条刻度线绘制一个带有各自纬度/经度的标签
-      wrapX: false,     // 是否水平重复经纬网
+      wrapX: false, // 是否水平重复经纬网
       targetSize: 230,
       zIndex: 99999,
-      strokeStyle: new Stroke({ // 用于绘制刻度线的样式
+      strokeStyle: new Stroke({
+        // 用于绘制刻度线的样式
         color: '#dcdcdc', // 线条颜色
-        width: 1       // 线条宽度
+        width: 1 // 线条宽度
       }),
       lonLabelStyle: new Text({
         font: '12px Calibri,sans-serif',
@@ -732,9 +743,9 @@ export class olMap {
       })
     });
     if (visible) {
-      this.map.addLayer(graticule)
+      this.map.addLayer(graticule);
     } else {
-      this.removeLayer('Graticule')
+      this.removeLayer('Graticule');
     }
   }
   /**
@@ -750,9 +761,69 @@ export class olMap {
       }
     });
   }
+  // 创建图形
+  createGraphics(data: any) {
+    if (data.type === 'circle') {
+      // 绘制圆形
+      return this.createCircle(data);
+    } else if (['polygon', 'rectangle'].includes(data.type)) {
+      // 绘制矩形、多边形
+      return this.createPolygon(data);
+    }
+    // else if (data.type === 'marker') {
+    //   // 绘制图标
+    //   return createMarker(data);
+    // } else if (data.type === 'measureArea') {
+    //   // 绘制面积
+    //   return createMeasureArea(data);
+    // } else if (data.type === 'text') {
+    //   // 文字
+    //   return addText(data);
+    // } else if (data.type === 'straightLine') {
+    //   // 直线
+    //   return createStraightLine(data);
+    // }
+  }
+  createCircle(data) {
+    const circle = new Circle(data.center, data.radius);
+    const feature = new Feature(circle);
+    feature.setStyle(
+      new Style({
+        stroke: new Stroke({
+          color: hexToRgba(data.strokeColor, data.strokeOpacity),
+          width: data.strokeWeight
+        }),
+        fill: new Fill({
+          color: hexToRgba(data.fillColor, data.fillOpacity)
+        })
+      })
+    );
+    this.drawVector.getSource().addFeature(feature);
+    return feature;
+  }
+  createPolygon(data) {
+    const polygon = new Polygon([data.path]);
+    const feature = new Feature(polygon);
+    feature.setStyle(
+      new Style({
+        stroke: new Stroke({
+          color: hexToRgba(data.strokeColor, data.strokeOpacity),
+          width: data.strokeWeight
+        }),
+        fill: new Fill({
+          color: hexToRgba(data.fillColor, data.fillOpacity)
+        })
+      })
+    );
+    this.drawVector.getSource().addFeature(feature);
+    return feature;
+  }
   getVectorLayer() {
     return this.vectorLayer;
   }
+  getDrawVector() {
+    return this.drawVector;
+  }
   getMap() {
     return this.map;
   }

+ 149 - 72
src/views/globalMap/RightMenu/DrawTools.vue

@@ -52,9 +52,6 @@
     </div>
     <div class="draw-item" @click="openDraw">{{ mouseToolState.drawing ? '关闭绘制' : '开启绘制' }}</div>
     <div class="draw-item" @click="handleUndo">撤销</div>
-    <div id="menu-box">
-      <div class="menu-item">删除</div>
-    </div>
   </div>
 </template>
 
@@ -63,6 +60,7 @@ import { useHistory } from '@/hooks/useHistory';
 import { nanoid } from 'nanoid';
 import { deepClone } from '@/utils';
 import * as turf from '@turf/turf';
+import Overlay from 'ol/Overlay';
 
 interface ListItem {
   label: string;
@@ -74,6 +72,7 @@ const props = defineProps({
 const AMapType = ['vectorgraph', 'satellite'];
 const getDrawTool = inject('getDrawTool');
 const getMap = inject('getMap');
+const getMapUtils = inject('getMapUtils');
 const emits = defineEmits(['handleAnalysisData']);
 const { currentState, commit, undo, history, future } = useHistory();
 const mouseToolState = reactive({
@@ -84,6 +83,13 @@ const mouseToolState = reactive({
   // 图形形状  circle圆形 rectangle矩形 polygon多边形
   graphicsType: 'circle'
 });
+let popup;
+let map = computed(() => {
+  return getMap();
+});
+let mapUtils = computed(() => {
+  return getMapUtils();
+});
 watch(
   mouseToolState,
   () => {
@@ -97,10 +103,14 @@ watch(
         drawTool.getMouseTool().on('draw', onDraw);
       } else {
         drawTool.getMouseTool().on('drawend', onDraw2);
+        map.value.un('click', onMapClick);
       }
     } else {
       mouseToolState.drawing = false;
       drawTool.closeDraw();
+      if (!AMapType.includes(props.activeMap)) {
+        map.value.on('click', onMapClick);
+      }
     }
   },
   {
@@ -234,61 +244,38 @@ const onDraw2 = (event) => {
   const geometryType = geometry.getType();
   const id = nanoid();
   feature.set('id', id);
+  feature.set('dotType', 'analysisSpatial');
   const data: any = {
     id: id,
     type: drawOptions.graphicsType,
-    // color: feature._opts.strokeColor,
-    // drawType: feature._opts.fillOpacity === 0 ? '1' : '2'
+    color: drawOptions.color,
+    drawType: drawOptions.drawType
   };
-  // 获取绘制的几何信息(包括经纬度)
-  if (geometryType === 'Point') {
-    const coordinates = geometry.getCoordinates();
-    console.log('绘制了一个点:', coordinates);
-  } else if (geometryType === 'LineString') {
-    const coordinates = geometry.getCoordinates();
-    console.log('绘制了一条线:', coordinates);
-    // 线的坐标是点的数组,每个点都是一个经纬度数组
-  } else if (geometryType === 'Polygon') {
-    const coordinates = geometry.getCoordinates();
-    console.log('绘制了一个多边形:', coordinates);
-    // 多边形的坐标是环的数组,每个环是点的数组,每个点都是一个经纬度数组
-  } else if (geometryType === 'Circle') {
+  feature.set('extraData', data);
+  if (geometryType === 'Circle') {
     data.center = geometry.getCenter();
     data.radius = geometry.getRadius();
     const data2 = turf.sector(data.center, data.radius, 0, 360);
     const pathArr = data2.geometry.coordinates[0];
     data.path = pathArr;
-    console.log('绘制了一个圆:', data);
+  } else if (geometryType === 'Polygon') {
+    const coordinates = geometry.getCoordinates();
+    data.path = coordinates[0];
   }
-  // const path = geometry.getCoordinates();
-  // debugger
-  // // 将AMap.LngLat对象数组转换为经纬度数组
-  // const pathArr = path.map((lngLat) => {
-  //   // 返回经度和纬度的数组
-  //   return [lngLat.lng, lngLat.lat];
-  // });
-  // data.path = pathArr;
   overlays.push(feature);
   overlaysData.push(data);
   commit(deepClone(overlaysData));
   // 右击进入编辑
-  feature.on('contextmenu', handleRightClick);
+  let map = getMap();
+  map.on('contextmenu', handleRightClick2);
   if (overlaysData.length === 1) {
     analysisSpatial(data);
   }
-  // // 点击空间分析
-  // feature.on('click', function () {
-  //   // 没在编辑时
-  //   if (!mouseToolState.drawing) {
-  //     analysisSpatial(data);
-  //   }
-  // });
 };
 let rightClickObj;
 // 图形右击事件
 let initContextMenu = false;
 const handleRightClick = (event) => {
-  debugger
   rightClickObj = event.target;
   const contextMenu = getDrawTool().getContextMenu();
   if (!initContextMenu) {
@@ -305,13 +292,67 @@ const handleRightClick = (event) => {
   }
   contextMenu.open(getMap(), event.lnglat);
 };
+const handleRightClick2 = (event) => {
+  event.preventDefault(); // 阻止默认的右键菜单弹出
+  map.value.un('click', onMapClick);
+  if (popup) {
+    map.value.removeOverlay(popup);
+    popup = undefined;
+  }
+  rightClickObj = map.value.forEachFeatureAtPixel(event.pixel, (feature2) => {
+    return feature2;
+  });
+  let dotType = rightClickObj.get('dotType');
+  if (!!rightClickObj && dotType === 'analysisSpatial') {
+    const dom = document.createElement('div');
+    const dom2 = document.createElement('div');
+    dom.id = 'analysisSpatial-popup';
+    dom2.className = 'menu-item';
+    dom2.innerHTML = '删除';
+    dom2.onclick = function () {
+      map.value.on('click', onMapClick);
+      deleteGraphics();
+    };
+    dom.appendChild(dom2);
+    popup = new Overlay({
+      element: dom,
+      autoPan: true,
+      positioning: 'bottom-right',
+      stopEvent: false,
+      offset: [0, 0]
+    });
+
+    const geometry = rightClickObj.getGeometry();
+    const geometryType = geometry.getType();
+    let coordinate = [];
+    if (geometryType === 'Circle') {
+      coordinate = geometry.getCenter();
+    } else {
+      const points = geometry.getCoordinates();
+      const features = turf.points(points[0]);
+      const center = turf.center(features);
+      if (center && center.geometry && center.geometry.coordinates) {
+        coordinate = center.geometry.coordinates;
+      }
+    }
+    popup.setPosition(coordinate);
+    map.value.addOverlay(popup);
+  }
+};
 // 删除图形
 const deleteGraphics = () => {
-  const id = rightClickObj.getExtData()?.id;
-  if (id) {
+  let id;
+  if (AMapType.includes(props.activeMap)) {
+    id = rightClickObj.getExtData()?.id;
+  } else {
+    unMapClick();
+    id = rightClickObj.get('id');
+  }
+  if (!!id) {
     for (let i = 0; i < overlays.length; i++) {
       const overlay = Array.isArray(overlays[i]) ? overlays[i][0] : overlays[i];
-      if (overlay?.getExtData().id === id) {
+      const itemId = AMapType.includes(props.activeMap) ? overlay?.getExtData().id : overlay.get('id');
+      if (itemId === id) {
         removeOverlayByIndex(i);
         commit(deepClone(overlaysData));
         rightClickObj = null;
@@ -351,14 +392,21 @@ const handleUndo = () => {
           strokeOpacity: 1,
           strokeWeight: '1',
           fillColor: restoreData.color,
-          fillOpacity: restoreData.drawType === '1' ? 0 : 0.5
+          fillOpacity: restoreData.drawType === '1' ? '0' : '0.5'
         };
         if (restoreData.type === 'circle') {
           newData.center = restoreData.center;
           newData.radius = restoreData.radius;
         }
-        const obj = getDrawTool().createGraphics(newData);
-        overlays.push(obj);
+        let obj;
+        if (AMapType.includes(props.activeMap)) {
+          obj = getDrawTool().createGraphics(newData);
+        } else {
+          obj = getDrawTool().createGraphics(newData);
+        }
+        if (!!obj) {
+          overlays.push(obj);
+        }
       }
     }
     undo();
@@ -369,15 +417,28 @@ const handleUndo = () => {
 // 根据索引移除覆盖物
 const removeOverlayByIndex = (index: number) => {
   const map = getMap();
-  if (Array.isArray(overlays[index])) {
-    overlays[index].forEach((overlay) => {
+  if (AMapType.includes(props.activeMap)) {
+    if (Array.isArray(overlays[index])) {
+      overlays[index].forEach((overlay) => {
+        // 移除地图上覆盖物
+        map.remove(overlay);
+      });
+    } else {
       // 移除地图上覆盖物
-      map.remove(overlay);
-    });
+      map.remove(overlays[index]);
+    }
   } else {
-    // 移除地图上覆盖物
-    map.remove(overlays[index]);
+    if (Array.isArray(overlays[index])) {
+      overlays[index].forEach((overlay) => {
+        // 移除地图上覆盖物
+        mapUtils.value.getDrawVector().getSource().removeFeature(overlay);
+      });
+    } else {
+      // 移除地图上覆盖物
+      mapUtils.value.getDrawVector().getSource().removeFeature(overlays[index]);
+    }
   }
+
   overlays.splice(index, 1);
   overlaysData.splice(index, 1);
 };
@@ -410,30 +471,35 @@ const analysisSpatial = (data) => {
   }
   emits('handleAnalysisData', location);
 };
+const onMapClick = (event) => {
+  // 没在编辑时
+  if (!mouseToolState.drawing && event.pixel) {
+    const feature = map.value.forEachFeatureAtPixel(event.pixel, (feature2) => {
+      return feature2;
+    });
+    const data = feature.get('extraData');
+    analysisSpatial(data);
+  }
+  unMapClick(event);
+};
+const unMapClick = (event) => {
+  const overlayElement = popup?.getElement();
+  const isClickOnOverlay =
+    event && overlayElement && (overlayElement.contains(event.originalEvent.target) || overlayElement === event.originalEvent.target);
+  if (!isClickOnOverlay && popup) {
+    map.value.removeOverlay(popup);
+    popup = undefined;
+  }
+};
 onMounted(() => {
-  // 监听右击事件
-  getMap().on('contextmenu', (event) => {
-    event.preventDefault(); // 阻止默认上下文菜单
-
-    // 获取右击位置的要素
-    const featuresAtPixel = getMap().getFeaturesAtPixel(event.pixel);
-
-    // 检查是否有要素在右击位置
-    if (featuresAtPixel && featuresAtPixel.length > 0) {
-      // 遍历要素并检查条件(这里可以根据需要添加条件)
-      featuresAtPixel.forEach(function(feature) {
-        // 执行你想要的动作,例如显示一个信息框或弹出菜单
-        console.log('右击了要素:', feature.get('id')); // 假设要素有一个'id'属性
-
-        // 你可以在这里显示一个自定义的右击菜单
-        // 例如,创建一个<div>元素,设置其内容和样式,然后添加到DOM中
-        // 并监听菜单项的点击事件来执行相应的动作
-      });
-    }
-  });
-})
+  if (!AMapType.includes(props.activeMap)) {
+    map.value.on('click', onMapClick);
+  }
+});
 onBeforeUnmount(() => {
-  //
+  if (!AMapType.includes(props.activeMap)) {
+    map.value.un('click', onMapClick);
+  }
 });
 </script>
 
@@ -497,9 +563,20 @@ onBeforeUnmount(() => {
 .active {
   background-color: rgba(22, 73, 142, 0.5);
 }
-.menu-box {
+</style>
+<style>
+#analysisSpatial-popup {
+  background-color: #ffffff;
+  border-radius: 4px;
+  cursor: pointer;
+  &:hover {
+    background-color: #f3f3ee;
+  }
   .menu-item {
-    font-size: 32px;
+    font-size: 14px;
+    padding: 0 8px;
+    height: 28px;
+    line-height: 28px;
   }
 }
 </style>

+ 61 - 19
src/views/globalMap/RightMenu/ForestDefenseVideo/DetailDialog.vue

@@ -28,7 +28,7 @@
         </div>
       </div>
       <div class="video-box">
-        <HKVideo :dot_data="detailData" style="height: 100%" />
+        <HKVideo ref="videoRef" :dot_data="detailData" autoplay style="height: 100%" />
       </div>
       <div class="control-container">
         <div class="common-title-box">操作台</div>
@@ -65,7 +65,7 @@
       </div>
       <div class="operate-box">
         <div class="common-btn-primary5">开始巡航</div>
-        <div class="common-btn-primary5">截图</div>
+        <div class="common-btn-primary5" @click="handleScreenshot">截图</div>
         <div class="common-btn-primary5">录像</div>
       </div>
     </div>
@@ -86,6 +86,7 @@ import videoImg from '@/assets/images/dotIcon/33_forest_defense_video.png';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import Polygon from 'ol/geom/Polygon';
+import { getPtzInfo } from '@/api/globalMap/forestDefenseVideo';
 
 const props = defineProps({
   id: String,
@@ -113,24 +114,26 @@ let detailData = ref({
   lat: 0,
   type: '',
   speed: 0,
-  video_code: '',
-  radius: 0,
-  startAngle: 0,
-  endAngle: 0
+  video_code: ''
+});
+let ptzInfo = ref({
+  Distance: 0,
+  AngelH: 0,
+  AzimuthH: 0
 });
 
 const getData = () => {
+  getPtzInfo({ code: props.id }).then((res) => {
+    ptzInfo.value = res.data;
+  });
   detailData.value = {
-    title: '高州市根子镇上炕村委会23211312',
+    title: '高州市根子镇上炕村委会',
     address: '茂名市高州市坡心东南约400米',
     lng: 110.819207,
     lat: 21.711887,
     type: '云台',
     speed: 246,
-    video_code: '44092251001320000009',
-    radius: 10000,
-    startAngle: 0,
-    endAngle: 30
+    video_code: '44090000001321000033',
   };
   initDot();
 };
@@ -205,11 +208,9 @@ const handleFocusControl = (type) => {
 // 控制视角移动
 const handleControl = (type) => {
   if (type === 'left') {
-    detailData.value.startAngle -= 1;
-    detailData.value.endAngle -= 1;
+    ptzInfo.value.AzimuthH -= 10;
   } else if (type === 'right') {
-    detailData.value.startAngle += 1;
-    detailData.value.endAngle += 1;
+    ptzInfo.value.AzimuthH += 10;
   }
   if (visibleRange.value) {
     createSector();
@@ -218,8 +219,9 @@ const handleControl = (type) => {
 // 新增扇形
 const createSector = () => {
   removeSector();
+  const { startAngle, endAngle } = calculateAngle(ptzInfo.value.AzimuthH, ptzInfo.value.AngelH);
   const center = turf.point([detailData.value.lng, detailData.value.lat]);
-  const sectorData = turf.sector(center, detailData.value.radius / 1000, detailData.value.startAngle, detailData.value.endAngle);
+  const sectorData = turf.sector(center, ptzInfo.value.Distance / 1000, startAngle, endAngle);
   if (AMapType.includes(props.activeMap)) {
     sector = new AMap.Polyline({
       path: sectorData.geometry.coordinates,
@@ -282,7 +284,7 @@ const addVisibleRange = () => {
     // 圆形范围
     circle = new AMap.Circle({
       center: new AMap.LngLat(detailData.value.lng, detailData.value.lat),
-      radius: detailData.value.radius,
+      radius: ptzInfo.value.Distance,
       strokeColor: '#ff0000', // 线颜色
       strokeOpacity: 0.35, // 线透明度
       strokeWeight: 1, // 线宽
@@ -292,7 +294,7 @@ const addVisibleRange = () => {
     map.add(circle);
   } else if (YMapType.includes(props.activeMap)) {
     const center = turf.point([detailData.value.lng, detailData.value.lat]);
-    const sectorData = turf.sector(center, detailData.value.radius / 1000, 0, 360);
+    const sectorData = turf.sector(center, ptzInfo.value.Distance / 1000, 0, 360);
 
     circle = new Feature({
       geometry: new Polygon(sectorData.geometry.coordinates)
@@ -312,7 +314,7 @@ const addVisibleRange = () => {
     // const center = [detailData.value.lng, detailData.value.lat];
     // circle = new Feature({
     //   geometry: new Circle(center, 100)
-    //   // geometry: new Circle(center, detailData.value.radius)
+    //   // geometry: new Circle(center, ptzInfo.value.Distance)
     // });
     // circle.setStyle(
     //   new Style({
@@ -354,6 +356,46 @@ const removeSector = () => {
     }
   }
 };
+const calculateAngle = (azimuthH, angelH) => {
+  // Normalize the azimuthH to be within [0, 360) degrees in deci-degrees
+  azimuthH = azimuthH % 3600; // 360 degrees * 10 = 3600 deci-degrees
+
+  // Convert deci-degrees to degrees for calculations (but we'll still use azimuthH_deci for clarity)
+  const azimuthH_deci = azimuthH;
+  const azimuthH_deg = azimuthH / 10;
+
+  // Calculate the start angle in deci-degrees
+  let startAngle_deci = azimuthH_deci - angelH / 2;
+
+  // Ensure the start angle is within [0, 3600) deci-degrees
+  if (startAngle_deci < 0) {
+    startAngle_deci += 3600;
+  }
+
+  // Convert start angle back to degrees
+  const startAngle_deg = startAngle_deci / 10;
+
+  // Calculate the end angle in deci-degrees
+  let endAngle_deci = azimuthH_deci + angelH / 2;
+
+  // Ensure the end angle is within [0, 3600) deci-degrees
+  if (endAngle_deci >= 3600) {
+    endAngle_deci -= 3600;
+  }
+
+  // Convert end angle back to degrees
+  const endAngle_deg = endAngle_deci / 10;
+
+  return {
+    startAngle: startAngle_deg,
+    endAngle: endAngle_deg
+  };
+};
+// 截图
+let videoRef = ref(null);
+const handleScreenshot = () => {
+  videoRef.value.handleScreenshot(detailData.value.title);
+};
 watch(
   () => props.id,
   () => {

+ 10 - 10
src/views/globalMap/RightMenu/ForestDefenseVideo/index.vue

@@ -108,11 +108,11 @@ const getData = () => {
           num1: 5,
           num2: 5,
           children: [
-            { label: '茂南公馆蒲芦塘村', value: 3 },
-            { label: '茂南区羊角镇禄段横山庙1', value: 4 },
-            { label: '茂南区羊角镇禄段横山庙2', value: 5 },
-            { label: '茂南区羊角镇禄段横山庙3', value: 6 },
-            { label: '茂南区羊角镇禄段横山庙4', value: 7 }
+            { label: '茂南公馆蒲芦塘村', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙1', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙2', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙3', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙4', value: '44090000001321000033' }
           ]
         },
         {
@@ -121,11 +121,11 @@ const getData = () => {
           num1: 5,
           num2: 6,
           children: [
-            { label: '茂南公馆蒲芦塘村', value: 9 },
-            { label: '茂南区羊角镇禄段横山庙1', value: 10 },
-            { label: '茂南区羊角镇禄段横山庙2', value: 11 },
-            { label: '茂南区羊角镇禄段横山庙3', value: 12 },
-            { label: '茂南区羊角镇禄段横山庙4', value: 13 }
+            { label: '茂南公馆蒲芦塘村', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙1', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙2', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙3', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙4', value: '44090000001321000033' }
           ]
         }
       ]

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

@@ -164,7 +164,7 @@ const clickMenu = (item, dataList) => {
         '易涝隐患点',
         '无人机',
         '铁塔运行监测',
-        '物资与装备',
+        // '物资与装备',
         '通讯保障',
         '路网视频',
         '江湖河库',