Sfoglia il codice sorgente

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

hmm 8 mesi fa
parent
commit
71f3b68c00

+ 6 - 0
src/assets/styles/element-ui.scss

@@ -169,3 +169,9 @@
     background: #081b41;
   }
 }
+
+.custom-color-picker {
+  .el-color-dropdown__link-btn {
+    display: none;
+  }
+}

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

@@ -7,5 +7,6 @@
   width: 8960px;
   height: 256px;
   background: url('@/assets/images/footer.png') no-repeat 100% 100%;
+  pointer-events: none;
 }
 </style>

+ 25 - 8
src/components/Map/YztMap/index.vue

@@ -7,13 +7,13 @@
 <script setup lang="ts">
 import 'ol/ol.css';
 import { olMap } from '@/utils/olMap/olMap';
+import { PointType } from '@/api/globalMap/type';
 
 interface Props {
   activeMap: string;
   drawing: boolean;
-  color: string;
-  drawType: string;
-  graphicsType: string;
+  mouseToolState: MouseTool;
+  pointType: PointType[];
 }
 const containerScale = inject('containerScale');
 const props = withDefaults(defineProps<Props>(), {});
@@ -22,7 +22,7 @@ const emits = defineEmits(['update:drawing', 'selectGraphics']);
 const mapRef = ref(null);
 const mapState = reactive({
   center: [110.93154257997, 21.669064031332],
-  zoom: 9,
+  zoom: 7,
   minZoom: 6,
   maxZoom: 16,
   isThreeDimensional: false,
@@ -46,10 +46,21 @@ watch(
   }
 );
 
+// 监听地图类型变化
+watch(
+  () => props.activeMap,
+  () => {
+    if (!map) return;
+    const id = props.activeMap === 'satellite2' ? ['YZT1715739306532', 'YZT1695608158269'] : ['YZT1708679726700', 'YZT1695608158269'];
+    map.replaceLayers(id);
+  }
+);
+
 const init = () => {
+  const id = props.activeMap === 'satellite2' ? ['YZT1715739306532', 'YZT1695608158269'] : ['YZT1708679726700', 'YZT1695608158269'];
   map = new olMap({
     dom: mapRef.value,
-    id: 'YZT1715739306532',
+    id: id,
     center: mapState.center,
     zoom: mapState.zoom,
     minZoom: mapState.minZoom,
@@ -69,17 +80,19 @@ const init = () => {
             emits('selectGraphics', data);
           }
         });
-
       }
     },
     // 加载完成事件
-    onLoadCompleted: (map) => {
-      yztMap = map;
+    onLoadCompleted: (yMap) => {
+      yztMap = yMap;
       // initMouseTool(map);
       handleResize();
     }
   });
 };
+const addMarker = (points) => {
+  map.addMarker(points);
+};
 // 设置地图层级
 const setMapZoom = (value) => {
   if (!yztMap) return;
@@ -108,6 +121,10 @@ const switchThreeDimensional = () => {
   const pitch = mapState.isThreeDimensional ? 45 : 0;
   view.setPitch(pitch);
 };
+const clearMarker = () => {
+  this.map.clearMarker();
+};
+defineExpose({ addMarker, clearMarker });
 const handleResize = () => {
   const containerWidth = containerRef.value.clientWidth * containerScale().scaleX;
   const containerHeight = containerRef.value.clientHeight * containerScale().scaleY;

+ 8 - 27
src/components/Map/index.vue

@@ -39,9 +39,7 @@ import { pointDetailTemplate } from '@/views/globalMap/data/mapData';
 interface Props {
   activeMap: string;
   drawing: boolean;
-  color: string;
-  drawType: string;
-  graphicsType: string;
+  mouseToolState: MouseTool;
   pointType: PointType[];
 }
 
@@ -68,10 +66,11 @@ const mapState = reactive({
 let AMap, map, scale;
 
 // 鼠标绘制工具
-const { initMouseTool, drawGraphics, setColor, setDrawType, setGraphicsType, closeDraw, handleUndo } = useDrawTool({
-  color: props.color,
-  drawType: props.drawType,
-  graphicsType: props.graphicsType,
+const { initMouseTool, drawGraphics, setOptions, closeDraw, handleUndo } = useDrawTool({
+  color: props.mouseToolState.color,
+  lineWidth: props.mouseToolState.lineWidth,
+  drawType: props.mouseToolState.drawType,
+  graphicsType: props.mouseToolState.graphicsType,
   // 绘制完成事件
   onDrawCompleted: (data, overlaysData, obj) => {
     if (overlaysData.length === 1) {
@@ -89,7 +88,7 @@ const { initMouseTool, drawGraphics, setColor, setDrawType, setGraphicsType, clo
   }
 });
 // 测距工具
-const { initRuler, isRanging, toggleRangingTool } = useRuler();
+const { initRuler } = useRuler();
 // 初始化地图
 const { getAMap, getMap, switchMap, addMarker, addSearchMarker, clearMarker, getMarkers, getScale, showInfo } = useAMap({
   key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
@@ -189,30 +188,12 @@ watch(
   () => props.drawing,
   (value) => {
     if (value) {
-      drawGraphics(props.graphicsType);
+      drawGraphics(props.mouseToolState);
     } else {
       closeDraw();
     }
   }
 );
-watch(
-  () => props.color,
-  () => {
-    setColor(props.color);
-  }
-);
-watch(
-  () => props.drawType,
-  () => {
-    setDrawType(props.drawType);
-  }
-);
-watch(
-  () => props.graphicsType,
-  () => {
-    setGraphicsType(props.graphicsType);
-  }
-);
 // 缩放级别变化
 const zoomChangeHandler = () => {
   mapState.zoom = map.getZoom();

+ 1 - 1
src/hooks/AMap/useAMap.ts

@@ -111,7 +111,7 @@ export function useAMap(options) {
       map, //地图实例
       points, //海量点数据,数据中需包含经纬度信息字段 lnglat
       {
-        gridSize: 20, //数据聚合计算时网格的像素大小
+        gridSize: 30, //数据聚合计算时网格的像素大小
         renderClusterMarker: _renderClusterMarker, //上述步骤的自定义聚合点样式
         renderMarker: _renderMarker //上述步骤的自定义非聚合点样式
       }

+ 10 - 8
src/hooks/AMap/useDrawTool.ts

@@ -6,6 +6,7 @@ import { countCircleArea, countRectangleArea } from '@/utils/geometryUtil';
 interface DrawToolOptions {
   color: string;
   drawType: string;
+  lineWidth: string;
   graphicsType: string;
   onDrawCompleted?: Function;
 }
@@ -13,7 +14,7 @@ export function useDrawTool(options: DrawToolOptions) {
   const drawOptions = {
     strokeColor: options.color,
     strokeOpacity: 1,
-    strokeWeight: 2,
+    strokeWeight: options.lineWidth,
     fillColor: options.color,
     fillOpacity: options.drawType === '1' ? 0 : 0.5,
     strokeStyle: 'solid'
@@ -104,14 +105,15 @@ export function useDrawTool(options: DrawToolOptions) {
     return res;
   };
   // 绘制图形
-  const drawGraphics = (type: string) => {
-    if (type === 'circle') {
+  const drawGraphics = (newOptions: MouseTool) => {
+    options = deepClone(newOptions);
+    if (options.graphicsType === 'circle') {
       // 绘制圆形
       mouseTool.circle(drawOptions);
-    } else if (type === 'rectangle') {
+    } else if (options.graphicsType === 'rectangle') {
       // 绘制矩形
       mouseTool.rectangle(drawOptions);
-    } else if (type === 'polygon') {
+    } else if (options.graphicsType === 'polygon') {
       // 绘制多边形
       mouseTool.polygon(drawOptions);
     }
@@ -159,7 +161,7 @@ export function useDrawTool(options: DrawToolOptions) {
       radius: options.radius, //半径
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'
@@ -176,7 +178,7 @@ export function useDrawTool(options: DrawToolOptions) {
       bounds: bounds,
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'
@@ -194,7 +196,7 @@ export function useDrawTool(options: DrawToolOptions) {
       path: path,
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'

+ 6 - 0
src/types/map.d.ts

@@ -0,0 +1,6 @@
+interface MouseTool {
+  color: string,
+  lineWidth: string,
+  drawType: string,
+  graphicsType: string,
+}

+ 16 - 0
src/utils/index.ts

@@ -360,3 +360,19 @@ export const getTransformScale = (element) => {
   // 如果没有找到scale变换,返回默认的缩放比例
   return { scaleX: 1, scaleY: 1 };
 };
+
+export const setRGBAOpacityToOne = (rgbaString) => {
+  // 使用正则表达式匹配rgba的各个部分
+  const match = rgbaString.match(/^rgba\(\s*(\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\s*\)$/);
+  if (!match) {
+    // 如果匹配失败,返回原始字符串或抛出错误
+    console.error('Invalid RGBA format:', rgbaString);
+    return rgbaString; // 或者抛出错误
+  }
+
+  // 提取出红、绿、蓝和原始透明度
+  const [r, g, b, originalOpacity] = match.slice(1).map(Number);
+
+  // 返回新的RGBA字符串,其中透明度被设置为1
+  return `rgba(${r}, ${g}, ${b}, 1)`;
+}

+ 116 - 8
src/utils/olMap/olMap.ts

@@ -1,15 +1,24 @@
 // 引入OpenLayers的主模块
 import Map from 'ol/Map';
 import View from 'ol/View';
+import Feature from 'ol/Feature';
+import Point from 'ol/geom/Point';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import Style from 'ol/style/Style';
+import Icon from 'ol/style/Icon';
+import Text from 'ol/style/Text';
 import Projection from 'ol/proj/Projection';
 import { getWidth, getTopLeft } from 'ol/extent';
 import TileLayer from 'ol/layer/Tile';
 import WMTS from 'ol/source/WMTS';
 import WMTSTileGrid from 'ol/tilegrid/WMTS';
 import WMTSCapabilities from 'ol/format/WMTSCapabilities';
+import { Fill, Stroke } from 'ol/style';
 import proj4 from 'proj4';
 import { register } from 'ol/proj/proj4';
 import { defaults } from 'ol/control';
+import { fromLonLat } from 'ol/proj';
 import axios from 'axios';
 // import olPlot from 'ol-plot';
 // import { activate } from '../ol-plot/ol-plot'
@@ -31,6 +40,8 @@ for (let z = 2; z < 22; ++z) {
 
 export class olMap {
   private map;
+  private options;
+  private markers = [];
   private drawOptions = {
     graphicsType: 'circle',
     strokeColor: '#f80102',
@@ -45,8 +56,10 @@ export class olMap {
   private overlaysData = [];
   private graphicsType = '';
   private plot;
+  private vectorLayer;
 
   constructor(options) {
+    this.options = options;
     this.map = new Map({
       controls: defaults({
         zoom: false,
@@ -66,13 +79,32 @@ export class olMap {
     if (options.showScale) {
       // map.addControl(new AMap.Scale());
     }
-    if (options.drawTool.use) {
+    if (options.drawTool?.use) {
       this.initMouseTool(options.drawTool);
     }
-    if (typeof options.onLoadCompleted === 'function') {
-      options.onLoadCompleted(this.map);
+    this.initLayer(options);
+  }
+  async initLayer(options) {
+    // 添加新的图层
+    if (Array.isArray(options.id)) {
+      for (const layer of options.id) {
+        await this.formatXml(layer); // 等待当前 layer 处理完成
+      }
+    } else if (options.id) {
+      // 如果 options.id 不是数组,但确实是一个图层,则直接处理
+      await this.formatXml(options.id);
+    }
+    console.log('wan')
+    // 创建Vector层并添加到地图上
+    this.vectorLayer = new VectorLayer({
+      source: new VectorSource({
+        features: []
+      })
+    });
+    this.map.addLayer(this.vectorLayer);
+    if (typeof this.options.onLoadCompleted === 'function') {
+      this.options.onLoadCompleted(this.map);
     }
-    this.formatXml(options.id);
   }
 
   formatXml(code, minZoom, maxZoom, zIndex, visible) {
@@ -80,7 +112,7 @@ export class olMap {
     // axios.post(commonUrl + 'YZT1723547712680', '<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs"service="WFS"version="1.0.0"outputFormat="GeoJson"maxFeatures="99999"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.opengis.net/wfshttp://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd">\n' +
     //   '<wfs:QuerytypeName="GDSMMSZJGZDTJX_2023"userecent="true"/>\n' +
     //   '</wfs:GetFeature>');
-    this.getCapabilities(code).then((lists) => {
+    return this.getCapabilities(code).then((lists) => {
       const mapData = xml.read(lists.data);
       const layerParam = {
         layerName: mapData.Contents.Layer[0].Title,
@@ -91,10 +123,12 @@ export class olMap {
       this.createWmsLayer(code, layerParam, minZoom, maxZoom, zIndex, visible);
     });
   }
+
   // 请求接口获取地图信息
   getCapabilities(code) {
-    return axios.get(commonUrl + code);
+    return axios.get(commonUrl + code + '?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities');
   }
+
   // 请求地图图片加载图层
   createWmsLayer(code, layerParam, minZoom = 0, maxZoom, zIndex = -1, visible = true) {
     const source = new WMTS({
@@ -121,7 +155,9 @@ export class olMap {
     layer.set('layerName', code);
 
     this.map.addLayer(layer);
-  };
+    console.log(code)
+  }
+
   // 初始化绘画工具
   initMouseTool(options) {
     this.drawOptions = {
@@ -140,6 +176,7 @@ export class olMap {
     // });
     // this.plot.plotDraw.on('drawEnd', this.onDrawEnd.bind(this));
   }
+
   // 绘制结束事件
   onDrawEnd(event) {
     const feature = event.feature;
@@ -147,11 +184,12 @@ export class olMap {
     // 继续编辑编辑
     this.drawGraphics(this.drawOptions.graphicsType);
   }
+
   // 绘制图形
   drawGraphics(type: string) {
     if (type === 'circle') {
       // 绘制圆形
-      activate('Circle');
+      // activate('Circle');
     } else if (type === 'rectangle') {
       // 绘制矩形
       this.plot.plotDraw.activate('RectAngle');
@@ -163,8 +201,78 @@ export class olMap {
       this.plot.plotDraw.activate('FreePolygon');
     }
   }
+
   // 关闭绘制
   closeDraw() {
     this.plot.plotDraw.deactivate();
   }
+
+  // 切换图层
+  async replaceLayers(newLayers) {
+    // 遍历当前的所有图层并移除它们
+    this.map.getLayers().forEach((layer) => {
+      this.map.removeLayer(layer);
+    });
+
+    // 添加新的图层
+    if (Array.isArray(newLayers)) {
+      for (const layer of newLayers) {
+        await this.formatXml(layer); // 等待当前 layer 处理完成
+      }
+    } else if (newLayers) {
+      // 如果 options.id 不是数组,但确实是一个图层,则直接处理
+      await this.formatXml(newLayers);
+    }
+    // 创建Vector层并添加到地图上
+    this.vectorLayer = new VectorLayer({
+      source: new VectorSource({
+        features: []
+      })
+    });
+    this.map.addLayer(this.vectorLayer);
+    const point = JSON.parse(JSON.stringify(this.markers));
+    this.markers = [];
+    this.addMarker(point)
+  }
+
+  addMarker(points) {
+
+    points.forEach((point) => {
+      // 创建标注点
+      const feature = new Feature({
+        geometry: new Point([point.longitude, point.latitude]),
+        name: point.name
+      });
+
+      // 定义样式
+      const style = new Style({
+        image: new Icon({
+          anchor: [0.5, point.size[1]],
+          anchorXUnits: 'fraction',
+          anchorYUnits: 'pixels',
+          src: point.image
+        }),
+        text: new Text({
+          text: point.name,
+          fill: new Fill({
+            color: '#000'
+          }),
+          stroke: new Stroke({
+            color: '#fff',
+            width: 3
+          })
+        })
+      });
+
+      feature.setStyle(style);
+      this.markers.push(point);
+      this.vectorLayer.getSource().addFeature(feature);
+    });
+  }
+
+  // 清除所有标加
+  clearMarker(id) {
+    if (!this.vectorLayer) return;
+    this.vectorLayer.getSource().clear();
+  }
 }

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

@@ -21,12 +21,11 @@
             </div>
           </div>
         </div>
-
       </div>
     </div>
   </div>
   <Dialog v-model="showQrCode" title="签到码" width="500px" height="500px">
-    <div style="display: flex; justify-content: center; align-items:center;width: 100%;height: 100%">
+    <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%">
       <img :src="qrCodeUrl" alt="" style="width: 370px; height: 370px" />
     </div>
   </Dialog>

+ 161 - 0
src/views/emergencyCommandMap/RightSection/RightTop.vue

@@ -0,0 +1,161 @@
+<template>
+  <div class="duty-card">
+    <ul class="tabs">
+      <li v-for="(tab, index) in tabs" :key="index" :class="{ active: tab.id === activeTab }" @click="setActiveTab(tab.id)">
+        {{ tab.label }}
+      </li>
+    </ul>
+    <div class="card-content">
+      <div v-if="activeTab === '预案通知'" class="custom-table">
+        <div class="table-content">
+          <div v-for="(notification, index) in notifications" :key="index" class="tr">
+            <div class="td">
+              <div class="unit-date">
+                <span class="unit">{{ notification.unit }}</span>
+                <span class="date">{{ notification.date }}</span>
+                <span :class="['status', { error: notification.status === '发送失败', success: notification.status === '已发送' }]">
+                  {{ notification.status === '发送失败' ? '发送失败' : `接收人:${notification.receiver}` }}
+                </span>
+              </div>
+              <div class="content">{{ notification.content }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive } from 'vue';
+
+const tabs = reactive([
+  { id: '任务追踪', label: '任务追踪' },
+  { id: '预案通知', label: '预案通知' },
+  { id: '资源调度', label: '资源调度' }
+]);
+
+const activeTab = ref('预案通知');
+
+const setActiveTab = (id) => {
+  activeTab.value = id;
+};
+
+const notifications = reactive([
+  {
+    unit: '市委宣传部',
+    date: '2024-7-10 10:03:09',
+    status: '发送失败',
+    content: '《茂名市自然灾害救助应急预案》现已全面启动,特此通知您单位迅速响应,全力做好预案工作要点:负责救灾工作宣传报道协调工作。',
+    receiver: '' // 新增字段
+  },
+  {
+    unit: '市发展和改革局(市粮食局)',
+    date: '2024-7-10 10:03:09',
+    status: '已发送',
+    content:
+      '《茂名市自然灾害救助应急预案》现已全面启动,特此通知您单位迅速响应,全力做好预案工作要点:负责安排重大防灾及灾后重建基建项目,协调落实建设资金;协同民政部门做好救灾粮储备、调配和供应的组织、协调工作。',
+    receiver: '张三' // 新增字段
+  },
+  {
+    unit: '市经济和信息化局',
+    date: '2024-7-10 10:03:09',
+    status: '已发送',
+    content:
+      '《茂名市自然灾害救助应急预案》现已全面启动,特此通知您单位迅速响应,全力做好预案工作要点:负责协调灾区煤电油运及重要原材料、重要消费品的供需衔接和医药、猪肉等市级储备的应急调度工作;负责保障相关无线电电信业务频率的正常使用,对出现的有害无线电干扰予以查处。',
+    receiver: '张三' // 新增字段
+  },
+  {
+    unit: '市教育局',
+    date: '2024-7-10 10:03:09',
+    status: '已发送',
+    content:
+      '《茂名市自然灾害救助应急预案》现已全面启动,特此通知您单位迅速响应,全力做好预案工作要点:负责安排重大防灾及灾后重建基建项目,协调落实建设资金;协同民政部门做好救灾粮储备、调配和供应的组织、协调工作。',
+    receiver: '张三' // 新增字段
+  }
+]);
+</script>
+
+<style lang="scss" scoped>
+.tabs {
+  display: flex;
+  justify-content: flex-start; /* 选项卡靠左对齐 */
+  padding: 0px 0;
+  background: rgba(0, 0, 0, 0.3);
+  .active {
+    border-bottom: 3px solid #00e8ff;
+  }
+  li {
+    cursor: pointer;
+    padding: 20px;
+    font-size: 36px;
+    color: #fff;
+    &:hover {
+      background-color: rgba(0, 0, 0, 0.5);
+    }
+  }
+}
+
+.custom-table {
+  width: 100%;
+  .table-content {
+    height: 600px;
+    overflow-y: auto;
+    .tr {
+      display: flex;
+      align-items: center;
+      .td {
+        flex: 1;
+        padding: 10px;
+        font-size: 36px;
+        .unit-date {
+          display: flex;
+          align-items: center; /* 确保垂直居中 */
+          justify-content: space-between; /* 使 status 靠右对齐 */
+          span {
+            white-space: nowrap;
+            &.unit {
+              font-size: 36px; /* 调整字体大小 */
+              margin-right: 5px; /* 减小单位与日期之间的间距 */
+            }
+            &.date {
+              font-size: 36px; /* 调整字体大小 */
+              margin-right: auto; /* 使用 auto 推动 status 靠右 */
+            }
+            &.status {
+              font-size: 36px; /* 调整字体大小 */
+              text-align: right; /* 可选:如果需要进一步对齐内部文本 */
+            }
+            &.error {
+              color: #ff4d4f; /* 发送失败时使用红色 */
+            }
+            &.success {
+              color: #fff; /* 发送成功时使用黑色 */
+            }
+          }
+        }
+        .content {
+          margin-top: 10px;
+          font-size: 36px; /* 内容字体大小 */
+          line-height: 1.5; /* 增加行高以适应较大的字体 */
+        }
+      }
+    }
+  }
+}
+
+.duty-card {
+  width: 2601px;
+  height: 879px;
+  background: url('@/assets/images/emergencyCommandMap/videoBox1.png') no-repeat 100% 100%;
+  position: relative;
+  color: #fff;
+  .card-content {
+    display: flex;
+    flex-wrap: wrap;
+    padding-top: 10px; /* 减小顶部填充 */
+    padding-left: 100px;
+    width: 2500px;
+  }
+}
+</style>

+ 127 - 0
src/views/emergencyCommandMap/RightSection/SelectPlan.vue

@@ -0,0 +1,127 @@
+<template>
+  <el-dialog ref="formDialogRef" v-model="innerModelValue" :title="props.title || '选择预案'" width="900px" append-to-body @close="closeDialog">
+    <div class="plan-selector">
+      <label for="plan-select">预案名称:</label>
+      <select id="plan-select" v-model="selectedPlan">
+        <option v-for="plan in plans" :key="plan.id" :value="plan.id">{{ plan.name }}</option>
+      </select>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <button class="custom-button" @click="visible = false">取消</button>
+        <button class="custom-button" @click="startPlan">确定并启动预案</button>
+      </span>
+    </template>
+    <StartPlan v-model="startPlanState.show" :title="startPlanState.title" />
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { defineEmits, defineProps, ref, watch } from 'vue';
+import StartPlan from '@/views/emergencyCommandMap/RightSection/StartPlan.vue';
+
+// 假设 plans 是一个包含预案信息的数组
+const plans = [
+  { id: 1, name: '预案 A' },
+  { id: 2, name: '预案 B' }
+  // 更多预案...
+];
+
+const visible = ref(false);
+const selectedPlan = ref<number | null>(null); // 初始值可以根据实际情况设置
+
+interface Props {
+  modelValue: boolean;
+  eventId: string;
+  title: string; // 确保 title 是必需的
+}
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: false,
+  eventId: '',
+  title: '' // 提供一个默认值,如果允许为空
+});
+
+const emits = defineEmits(['update:modelValue']);
+const innerModelValue = ref(props.modelValue);
+
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    innerModelValue.value = newValue;
+    if (newValue) {
+      visible.value = true; // 当对话框打开时显示子内容
+    }
+  }
+);
+
+const closeDialog = () => {
+  emits('update:modelValue', false);
+};
+
+// 当 dialog 关闭时重置 visible
+watch(innerModelValue, (newVal) => {
+  if (!newVal) {
+    visible.value = false; // 当对话框关闭时隐藏子内容
+  }
+});
+//启动预案弹窗
+const startPlanState = reactive({
+  show: false,
+  title: ''
+});
+// 处理选择预案的函数
+const startPlan = () => {
+  console.log('startPlan');
+  startPlanState.title = '启动预案';
+  startPlanState.show = true;
+};
+
+// 调试信息
+console.log('Initial innerModelValue:', innerModelValue.value);
+console.log('Initial visible:', visible.value);
+</script>
+<style scoped>
+.plan-selector {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 20px; /* 可以根据需要调整间距 */
+}
+
+.plan-selector label {
+  margin-right: 10px; /* 为标签和下拉框之间添加间距 */
+}
+
+.plan-selector select {
+  width: 300px; /* 下拉框宽度,可以根据需要调整 */
+  padding: 8px 16px;
+  font-size: 14px;
+  border-radius: 5px;
+  border: 1px solid #ccc;
+}
+
+.custom-button {
+  display: inline-block;
+  padding: 8px 16px;
+  font-size: 14px;
+  border-radius: 5px;
+  color: white;
+  background-color: #409eff;
+  box-shadow: none;
+  border: none;
+  cursor: pointer;
+  transition: all 0.3s ease-in-out;
+}
+
+.custom-button:hover {
+  background-color: #73a2ff;
+}
+
+.custom-button:active {
+  transform: translateY(1px);
+}
+
+.dialog-footer button {
+  margin-left: 10px;
+}
+</style>

+ 77 - 3
src/views/emergencyCommandMap/RightSection/StartPlan.vue

@@ -12,15 +12,70 @@
         </el-col>
         <el-col :span="12" class="buttons-container">
           <el-button type="primary" @click="handleStartPlan">启动预案</el-button>
-          <el-button type="primary" @click="handleTaskDispatch">预案任务下发内容</el-button>
+          <el-button type="primary" @click="taskDelivery">预案任务下发内容</el-button>
         </el-col>
       </el-row>
+      <h2>xxx预案名称</h2>
+      <el-row shadow="hover" class="fixed-width-row">
+        <el-tabs v-model="activeName" type="border-card" class="demo-tabs" @tab-click="handleClick2">
+          <el-tab-pane label="总则" name="first">
+            <div>
+              <el-row>
+                <el-col :span="4">
+                  <el-anchor :container="containerRef" direction="vertical" type="default" :offset="30" @click="handleClick1">
+                    <el-anchor-link href="#part1" title="编制目的" />
+                    <el-anchor-link href="#part2" title="编制依据" />
+                    <el-anchor-link href="#part3" title="适用范围" />
+                    <el-anchor-link href="#part4" title="工作原则" />
+                  </el-anchor>
+                </el-col>
+                <el-col :span="20">
+                  <div ref="containerRef" style="height: 300px; overflow-y: auto">
+                    <div id="part1" style="height: auto; margin-top: 20px; font-size: 14px">
+                      <h3 style="font-weight: 600">1.1 编制目的</h3>
+                      <span style="text-indent: 2em"
+                        >这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容</span
+                      >
+                    </div>
+                    <div id="part2" style="height: auto; margin-top: 15px; font-size: 14px">
+                      <h3 style="font-weight: 600">1.2 编制依据</h3>
+                      <span style="text-indent: 28px"
+                        >这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容</span
+                      >
+                    </div>
+                    <div id="part3" style="height: auto; margin-top: 15px; font-size: 14px">
+                      <h3 style="font-weight: 600">1.3 适用范围</h3>
+                      <span style="text-indent: 28px"
+                        >这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容</span
+                      >
+                    </div>
+                    <div id="part4" style="height: auto; margin-top: 15px; font-size: 14px">
+                      <h3 style="font-weight: 600">1.4 工作原则</h3>
+                      <span style="text-indent: 28px"
+                        >这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容</span
+                      >
+                    </div>
+                  </div>
+                </el-col>
+              </el-row>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane label="组织体系" name="second">组织体系</el-tab-pane>
+          <el-tab-pane label="运行机制" name="third">运行机制</el-tab-pane>
+          <el-tab-pane label="应急保障" name="fourth">应急保障</el-tab-pane>
+          <el-tab-pane label="监督管理" name="fifth">监督管理</el-tab-pane>
+          <el-tab-pane label="附则" name="sixth">附则</el-tab-pane>
+          <el-tab-pane label="附件" name="seventh">附件</el-tab-pane>
+        </el-tabs>
+      </el-row>
     </div>
+    <TaskDelivery v-model="taskDeliveryState.show" :title="taskDeliveryState.title" />
   </el-dialog>
 </template>
 
 <script lang="ts" setup>
 import { defineProps, defineEmits, ref, watch } from 'vue';
+import TaskDelivery from './TaskDelivery.vue';
 const visible = ref(false);
 interface Props {
   modelValue: boolean;
@@ -66,9 +121,25 @@ const handleStartPlan = () => {
   console.log('启动预案');
 };
 
+const containerRef = ref<HTMLElement | null>(null);
+const handleClick1 = (e: MouseEvent) => {
+  e.preventDefault();
+};
+
+const activeName = ref('first');
+const handleClick2 = (tab: TabsPaneContext, event: Event) => {
+  console.log(tab, event);
+};
+// 控制 taskDelivery 弹窗的显示状态
+const taskDeliveryState = reactive({
+  show: false,
+  title: ''
+});
 // 处理预案任务下发内容的函数
-const handleTaskDispatch = () => {
-  console.log('下发预案任务内容');
+const taskDelivery = () => {
+  console.log('taskDelivery');
+  taskDeliveryState.title = '预案任务下发';
+  taskDeliveryState.show = true;
 };
 </script>
 <style scoped>
@@ -97,4 +168,7 @@ const handleTaskDispatch = () => {
   justify-content: flex-end; /* 靠右对齐 */
   gap: 10px; /* 设置按钮之间的间距 */
 }
+h2 {
+  text-align: center;
+}
 </style>

+ 127 - 0
src/views/emergencyCommandMap/RightSection/TaskDelivery.vue

@@ -0,0 +1,127 @@
+<template>
+  <!-- 主对话框 -->
+  <el-dialog ref="formDialogRef" v-model="props.modelValue" :title="props.title" width="900px" @close="closeDialog">
+    <!-- 子对话框 -->
+    <el-dialog v-model="dialogVisible" title="预任务任务下发" width="70%" :append-to-body="true" @close="handleClose">
+      <div class="task-list" v-if="dialogVisible">
+        <!-- 使用 v-for 循环遍历任务列表 -->
+        <div v-for="(task, index) in tasks" :key="index" class="task-item">
+          <p class="unit-name">{{ task.unit }}</p>
+          <p class="task-description">{{ task.description }}</p>
+        </div>
+      </div>
+      <!-- 对话框底部按钮区域 -->
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取 消</el-button>
+          <el-button type="primary" @click="sendTasks">确认发送H5短信</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { defineProps, defineEmits, ref, watch, onMounted } from 'vue';
+import { ElMessage } from 'element-plus'; // 假设使用的是 Element Plus
+
+// 定义组件属性类型
+interface Props {
+  modelValue: boolean;
+  eventId?: string; // 将 eventId 设置为可选属性
+  title: string;
+}
+const props = defineProps<Props>();
+
+// 定义事件发射器
+const emit = defineEmits(['update:modelValue']);
+
+// 内部响应式变量
+const dialogVisible = ref(props.modelValue); // 控制子对话框的显示状态
+const tasks = ref([
+  { unit: '市委宣传部', description: '负责救灾工作宣传报道协调工作。这是一段很长的描述文字,需要自动换行以适应容器的宽度。' },
+  { unit: '市委宣传部1', description: '负责救灾工作宣传报道协调工作。这也是一段很长的描述文字,需要自动换行以适应容器的宽度。' },
+  { unit: '市委宣传部2', description: '负责救灾工作宣传报道协调工作。这是一段很长的描述文字,需要自动换行以适应容器的宽度。' },
+  { unit: '市委宣传部3', description: '负责救灾工作宣传报道协调工作。这也是一段很长的描述文字,需要自动换行以适应容器的宽度。' }
+]); // 任务列表
+
+// 监听 modelValue 变化,确保当外部关闭时,内部对话框也关闭
+watch(
+  () => props.modelValue,
+  (newValue) => {
+    dialogVisible.value = newValue;
+  }
+);
+
+// 在组件挂载后显示对话框(仅用于测试)
+onMounted(() => {
+  // 如果需要,可以在这里进行一些初始化操作
+});
+
+// 当主对话框关闭时,更新父组件的 modelValue
+const closeDialog = () => {
+  emit('update:modelValue', false);
+};
+
+// 当子对话框关闭时,重置选中状态
+const handleClose = () => {
+  dialogVisible.value = false;
+  emit('update:modelValue', false);
+};
+
+// 发送任务时的操作
+const sendTasks = () => {
+  // 模拟发送请求到后端接口
+  simulateBackendRequest(tasks.value).then(() => {
+    console.log('发送H5短信');
+    ElMessage.success('任务已成功发送!');
+    dialogVisible.value = false;
+    emit('update:modelValue', false);
+  });
+};
+
+// 模拟发送请求到后端接口
+const simulateBackendRequest = async (tasks: Array<{ unit: string; description: string }>) => {
+  // 这里可以替换为实际的后端请求逻辑
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true);
+    }, 1000);
+  });
+};
+</script>
+
+<style scoped>
+/* 任务列表容器样式 */
+.task-list {
+  padding: 20px;
+}
+
+/* 单个任务项样式 */
+.task-item {
+  display: block;
+  padding: 10px;
+  margin-bottom: 10px;
+  background-color: #f9f9f9; /* 浅色背景 */
+  border: 1px solid #ddd;
+  border-radius: 4px;
+}
+
+/* 任务单位名称样式 */
+.unit-name {
+  font-weight: bold;
+  margin-bottom: 5px;
+  word-break: break-all; /* 允许在单词中间换行 */
+}
+
+/* 任务描述样式 */
+.task-description {
+  word-break: break-all; /* 允许在单词中间换行 */
+  white-space: pre-wrap; /* 保留空格和换行符 */
+}
+
+/* 对话框底部按钮区域样式 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end; /* 按钮靠右对齐 */
+}
+</style>

+ 61 - 22
src/views/emergencyCommandMap/RightSection/index.vue

@@ -35,21 +35,22 @@
         <div class="btn-text">返回上级</div>
       </div>
     </div>
-    <div class="video-card">
-      <div class="title gradient-text">视频监控</div>
-      <div class="card-content video-list">
-        <div v-for="(item, index) in videoMonitorState.listData" :key="index" class="video-box" @click="showVideoDialog(item)">
-          <div class="video-content">
-            <div style="width: 550px; height: 300px; position: relative">
-              <HKVideo :dot_data="item" :width="550" :height="300" />
-              <div class="video-label">
-                <span class="label">{{ item.name }}</span>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
+    <!--    <div class="video-card">-->
+    <!--      <div class="title gradient-text">视频监控</div>-->
+    <!--      <div class="card-content video-list">-->
+    <!--        <div v-for="(item, index) in videoMonitorState.listData" :key="index" class="video-box" @click="showVideoDialog(item)">-->
+    <!--          <div class="video-content">-->
+    <!--            <div style="width: 550px; height: 300px; position: relative">-->
+    <!--              <HKVideo :dot_data="item" :width="550" :height="300" />-->
+    <!--              <div class="video-label">-->
+    <!--                <span class="label">{{ item.name }}</span>-->
+    <!--              </div>-->
+    <!--            </div>-->
+    <!--          </div>-->
+    <!--        </div>-->
+    <!--      </div>-->
+    <!--    </div>-->
+    <RightTop />
     <JointDuty />
     <!--    <div class="message-card">-->
     <!--      <div class="title gradient-text">动态消息</div>-->
@@ -69,9 +70,10 @@
     <!--      </div>-->
     <!--    </div>-->
   </div>
-  <Dialog v-model="videoMonitorState.showListDialog" title="视频监控">
-    <videoList />
-  </Dialog>
+  <!--  <Dialog v-model="videoMonitorState.showListDialog" title="视频监控">-->
+  <!--    <videoList />-->
+  <!--  </Dialog>-->
+  <SelectPlan v-model="selectPlanState.show" :plans="selectPlanState.plans" @plan-selected="onPlanSelected" />
   <StartPlan v-model="startPlanState.show" :title="startPlanState.title" />
 </template>
 
@@ -79,7 +81,9 @@
 import router from '@/router';
 import { getEmergencyVideoCata } from '@/api/videoMonitor';
 import JointDuty from '@/views/emergencyCommandMap/RightSection/JointDuty.vue';
+import RightTop from '@/views/emergencyCommandMap/RightSection/RightTop.vue';
 import StartPlan from './StartPlan.vue';
+import SelectPlan from './SelectPlan.vue';
 import CloseCommand from '@/views/emergencyCommandMap/LeftSection/CloseCommand.vue';
 const goToHome = () => {
   router.push({ path: '/' });
@@ -89,12 +93,47 @@ const startPlanState = reactive({
   show: false,
   title: ''
 });
-const startPlan = () => {
-  console.log('StartPlan');
-  startPlanState.title = '启动预案';
+
+// 控制 selectPlan 弹窗的显示状态
+const selectPlanState = reactive({
+  show: false,
+  title: ''
+});
+// 处理选择预案的函数
+// const selectPlan = () => {
+//   console.log('selectPlan');
+//   selectPlanState.title = '预案任务下发';
+//   selectPlanState.show = true;
+// };
+
+const startPlan = (eventId) => {
+  if (eventId) {
+    startPlanState.title = '启动预案';
+    startPlanState.show = true;
+  } else {
+    // 如果没有eventId,则显示选择预案的弹窗
+    selectPlanState.show = true;
+    selectPlanState.title = '预案任务下发';
+    // 可能需要加载预案列表
+    loadPlans();
+  }
+};
+const loadPlans = () => {
+  // 这里应该有一个API调用来获取所有可用的预案
+  // 仅作示例,下面的数组是假定的预案数据
+  selectPlanState.plans = [
+    { id: 1, name: '应急预案A' },
+    { id: 2, name: '应急预案B' },
+    { id: 3, name: '应急预案C' }
+  ];
+};
+const onPlanSelected = (plan) => {
+  selectPlanState.selectedPlan = plan;
+  // 当选择了一个预案后,可以关闭选择预案的弹窗并打开启动预案的弹窗
+  selectPlanState.show = false;
+  startPlanState.title = `启动预案 - ${plan.name}`;
   startPlanState.show = true;
 };
-
 // 视频监控
 const videoMonitorState = reactive({
   listData: [],

+ 107 - 0
src/views/globalMap/RightMenu/OnlinePlotting.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="gradient-text title">实时标绘</div>
+  <el-select v-model="mouseToolState.lineWidth" placeholder="Select" style="width: 240px">
+    <el-option
+      v-for="item in lineWidthOptions"
+      :key="item.value"
+      :label="item.name"
+      :value="item.value"
+    >
+      <span >{{ item.name }}</span>
+    </el-option>
+  </el-select>
+  <el-color-picker v-model="mouseToolState.color" popper-class="custom-color-picker" show-alpha size="large" />
+  <el-button @click="changeDrawing" size="large">{{ drawing ? '关闭' : '开启' }}</el-button>
+  <el-tabs
+    v-model="activeName"
+    type="card"
+    class="demo-tabs"
+    @tab-click="handleClick"
+  >
+    <el-tab-pane  v-for="(item, index) in menu" :key="index" :label="User" name="first">User</el-tab-pane>
+  </el-tabs>
+</template>
+
+<script lang="ts" setup>
+interface Props {
+  drawing: boolean;
+  mouseToolState: MouseTool;
+}
+
+const props = withDefaults(defineProps<Props>(), {});
+const emits = defineEmits(['updateDrawing']);
+
+const menu = ref([
+  {
+    name: '标绘工具',
+    value: '1',
+    children: [
+      {
+        name: '基本工具',
+        children: [
+          {
+            name: '直箭头',
+            value: 'straightArrow'
+          },
+          {
+            name: '矩形',
+            value: 'rectangle'
+          },
+          {
+            name: '任意面',
+            value: 'polygon'
+          },
+          {
+            name: '任意线',
+            value: ''
+          },
+          {
+            name: '圆',
+            value: 'circle'
+          },
+          {
+            name: '直线',
+            value: 'straightLine'
+          },
+          {
+            name: '文字',
+            value: 'text'
+          },
+          {
+            name: '面积',
+            value: ''
+          }
+        ]
+      }
+    ]
+  },
+  {
+    name: '历史记录',
+    value: 'history',
+    children: []
+  }
+])
+const lineWidthOptions = reactive([
+  { name: '1像素', value: '1px' },
+  { name: '2像素', value: '2px' },
+  { name: '3像素', value: '3px' }
+])
+
+const changeDrawing = () => {
+  debugger
+  emits('updateDrawing', !props.drawing)
+}
+</script>
+
+<style lang="scss" scoped>
+.title {
+  font-size: 60px;
+  position: absolute;
+  top: 12px;
+  left: 210px;
+}
+
+:deep(.el-color-dropdown__link-btn) {
+  display: none;
+}
+</style>

+ 213 - 105
src/views/globalMap/RightMenu/ReservoirMonitor.vue

@@ -1,46 +1,38 @@
 <template>
-  <div class="title">水库监测</div>
-  <div style="margin-top: 20px">更新时间:{{ ReservoirMonitorData.time }}</div>
-  <div class="flex" style="margin-top: 20px">
-    <div style="margin-right: 20px">
-      <div>超警戒</div>
-      {{ ReservoirMonitorData.statusList[0] ? ReservoirMonitorData.statusList[0]?.value : '-' }}
+  <div class="gradient-text title">水库监测</div>
+  <div class="flex-box">
+    <div class="data-box1">
+      <div class="box-text1">漫坝</div>
+      <div class="box-text2">{{ validateNum(reservoirMonitorData.statusList[0]?.value) }}</div>
     </div>
-    <div style="margin-right: 20px">
-      <div>超保证</div>
-      {{ ReservoirMonitorData.statusList[1] ? ReservoirMonitorData.statusList[1]?.value : '-' }}
+    <div class="data-box2">
+      <div class="box-text1">超保证</div>
+      <div class="box-text2">{{ validateNum(reservoirMonitorData.statusList[1]?.value) }}</div>
     </div>
-    <div>
-      <div>漫坝</div>
-      {{ ReservoirMonitorData.statusList[2] ? ReservoirMonitorData.statusList[2]?.value : '-' }}
+    <div class="data-box3">
+      <div class="box-text1">超警戒</div>
+      <div class="box-text2">{{ validateNum(reservoirMonitorData.statusList[2]?.value) }}</div>
     </div>
   </div>
-  <el-table :data="ReservoirMonitorData.listData" border style="width: 100%">
-    <el-table-column prop="name" label="名称" show-overflow-tooltip align="center">
-      <template #default="scope">
-        <div style="color: #409eff; cursor: pointer" @click="handleShowDialog(scope.row)">
-          {{ scope.row.name }}
-        </div>
-      </template>
-    </el-table-column>
-    <el-table-column prop="area" show-overflow-tooltip align="center">
-      <template #header>
-        <el-select v-model="queryParams.area" placeholder="所属区县" size="large" clearable @change="initData">
-          <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </template>
-    </el-table-column>
-    <el-table-column prop="floodLimit" label="汛限水位(m)" show-overflow-tooltip sortable align="center" />
-    <el-table-column prop="level" label="当前水位(m)" show-overflow-tooltip sortable align="center" />
-    <el-table-column prop="waterDiff" label="差值" show-overflow-tooltip sortable align="center" />
-  </el-table>
-  <pagination
-    v-show="total > queryParams.size"
-    v-model:page="queryParams.current"
-    v-model:limit="queryParams.size"
-    :total="total"
-    @pagination="initData"
-  />
+  <div class="custom-table">
+    <div class="th">
+      <div class="td">名称</div>
+      <div class="td">所属区县</div>
+      <div class="td">汛限水位(m)</div>
+      <div class="td">当前水位(m)</div>
+      <div class="td">差值</div>
+    </div>
+    <div class="table-content">
+      <div v-for="(item, index) in reservoirMonitorData.listData" :key="index" class="tr" @click="handleShowDialog(item)">
+        <div class="td">{{ item.name }}</div>
+        <div class="td">{{ item.area }}</div>
+        <div class="td td-text">{{ item.warningLevel }}</div>
+        <div class="td td-text">{{ item.waterLevel }}</div>
+        <div :class="item.warningLevel > item.waterLevel ? 'td td-text text-green' : 'td td-text text-danger'">{{ item.waterDiff }}</div>
+      </div>
+    </div>
+  </div>
+
   <Dialog v-model="showDialog" title="水库监测" width="2500px" height="1200px">
     <div class="flex">
       <div class="detail-container">
@@ -102,10 +94,10 @@
 </template>
 
 <script lang="ts" setup name="ReservoirMonitor">
-import Dialog from '@/components/Dialog/index.vue';
+import Dialog from './Dialog.vue';
 import { option3 } from './echartOptions';
 import { getReservoirCourseLevel, getReservoirList, getReservoirWaterStatus } from '@/api/globalMap/reservoirMonitor';
-import { parseTime } from '@/utils/ruoyi';
+import { parseTime, validateNum } from '@/utils/ruoyi';
 import { getVideoInfo } from '@/api/globalMap';
 import { deepClone } from '@/utils';
 
@@ -140,7 +132,7 @@ const queryParams = reactive({
 // 总数量
 let total = ref(0);
 // 数据
-const ReservoirMonitorData = reactive({
+const reservoirMonitorData = reactive({
   time: '',
   statusList: [],
   listData: []
@@ -154,10 +146,10 @@ const initData = async () => {
   delete newQueryParams.area;
   getReservoirList(newQueryParams).then((res) => {
     total.value = res.total;
-    ReservoirMonitorData.listData = res.rows;
+    reservoirMonitorData.listData = res.rows;
   });
   getReservoirWaterStatus().then((res) => {
-    ReservoirMonitorData.statusList = res.rows || [];
+    reservoirMonitorData.statusList = res.rows || [];
   });
 };
 
@@ -232,19 +224,58 @@ const getVideoList = () => {
 <style lang="scss" scoped>
 .detail-container {
   font-size: 36px;
-  width: 1000px;
   .dialog-content {
     display: flex;
   }
   .info-box {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    height: 520px;
-    background-color: #d7d7d7;
-    padding: 10px;
+    width: 834px;
+    height: 459px;
+    background: url('@/assets/images/map/rightMenu/box2.png') no-repeat;
+    padding: 11px;
+    .info-header {
+      width: 311px;
+      height: 56px;
+      padding-left: 50px;
+      background: url(@/assets/images/map/rightMenu/titleBox1.png) no-repeat;
+    }
+    .info-content {
+      padding: 0 37px 26px 37px;
+      font-size: 32px;
+      color: #a8ccde;
+    }
     .info-item {
       display: flex;
+      align-items: center;
+      height: 72px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+  .box1 {
+    width: 776px;
+    height: 459px;
+    display: flex;
+    flex-direction: column;
+    margin-left: 94px;
+    margin-right: 110px;
+    .box1-title {
+      width: 372px;
+      height: 54px;
+      background: url('@/assets/images/map/rightMenu/titleBox2.png');
+      padding-left: 65px;
+      font-size: 44px;
+      color: #f4f7fa;
+    }
+  }
+  .box2 {
+    margin-top: 45px;
+    .box2-title {
+      height: 54px;
+      background: url('@/assets/images/map/rightMenu/titleBox2.png') no-repeat;
+      padding-left: 65px;
+      font-size: 44px;
+      color: #f4f7fa;
     }
   }
 }
@@ -252,9 +283,20 @@ const getVideoList = () => {
   flex: 1;
   display: flex;
   flex-direction: column;
-  background-color: #d7d7d7;
-  padding: 10px;
   height: 1009px;
+  .flex-box2 {
+    height: 54px;
+    background: url('@/assets/images/map/rightMenu/titleBox2.png') no-repeat;
+    padding-left: 65px;
+    display: flex;
+    align-items: center;
+    .title2 {
+      font-size: 44px;
+      flex-shrink: 0;
+      margin-right: 30px;
+      color: #f4f7fa;
+    }
+  }
   .video-box {
     margin-top: 30px;
     height: 900px;
@@ -263,67 +305,133 @@ const getVideoList = () => {
   }
 }
 
-.table-wrap {
-  height: 400px;
-  // margin-top: 10px;
-  font-size: 28px;
-  background-color: #102043;
-  color: #fff;
-  .table-title {
-    height: 70px;
+.title {
+  font-size: 60px;
+  position: absolute;
+  top: 12px;
+  left: 210px;
+}
+
+.flex-box {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  .data-box1,
+  .data-box2,
+  .data-box3 {
+    width: 462px;
+    height: 144px;
+    position: relative;
     display: flex;
     align-items: center;
-    border-bottom: 2px solid #465979;
-    .title-item {
-      width: 195px;
-      text-align: center;
-      color: #4c97f6;
+    margin-right: 20px;
+    &:last-child {
+      margin-right: 0;
     }
-  }
-  .content-wrap {
-    height: 92%;
-    overflow-y: auto;
-    .content-item {
-      display: flex;
-      justify-content: space-around;
-      align-items: center;
-      height: 70px;
-      font-size: 28px;
-      margin-bottom: 8.5px;
-      background: #0b2b5a;
-      > div {
-        width: 200px;
-        text-align: center;
+    .box-text1 {
+      color: #fff;
+      font-size: 36px;
+      padding-left: 190px;
+      min-width: 100px;
+      &::after {
+        content: '';
+        width: 1px;
+        height: 48px;
+        background: url('@/assets/images/map/rightMenu/line.png');
+        margin: 0 30px;
       }
-      // .time {
-      //   width: 120px;
-      // }
-      // .value {
-      //   width: 120px;
-      // }
-      // .value1 {
-      //   width: 120px;
-      // }
-      // .value2 {
-      //   width: 120px;
-      // }
-      // .waterunit {
-      //   width: 120px;
-      // }
     }
-    //.content-item:nth-child(2n) {
-    //  background: rgba(11, 43, 90, 0.2);
-    //}
+    .box-text2 {
+      /* 设置字体透明 */
+      color: transparent;
+      /* 设置线性渐变,从红色渐变到蓝色 */
+      background-image: linear-gradient(to bottom, #ffffff 50%, #3075d3 100%);
+      /* 使用 -webkit-background-clip 属性将背景剪裁至文本形状 */
+      -webkit-background-clip: text;
+      /* 非Webkit内核浏览器需要使用标准前缀 */
+      background-clip: text;
+      /* 把当前元素设置为行内块,以便能够应用背景 */
+      display: inline-block;
+      font-family: 'YouSheBiaoTiHei';
+      font-size: 40px;
+    }
   }
-}
 
-.text-green {
-  color: #67c23a;
+  .data-box1 {
+    background: url('@/assets/images/map/rightMenu/dataBox1.png') no-repeat;
+  }
+  .data-box2 {
+    background: url('@/assets/images/map/rightMenu/dataBox2.png') no-repeat;
+  }
+  .data-box3 {
+    background: url('@/assets/images/map/rightMenu/dataBox3.png') no-repeat;
+  }
 }
-.text-warning {
-  color: #e6a23c;
+
+.custom-table {
+  width: 100%;
+  .table-content {
+    height: 880px;
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+  .th {
+    width: 1499px;
+    height: 151px;
+    background: url('@/assets/images/map/rightMenu/th.png') no-repeat;
+    display: flex;
+  }
+  .tr {
+    width: 1499px;
+    height: 139px;
+    background: url('@/assets/images/map/rightMenu/td.png') no-repeat;
+    //margin-left: -23px;
+    display: flex;
+    padding-right: 20px;
+    &:hover {
+      background: url('@/assets/images/map/rightMenu/td_checked.png') no-repeat;
+    }
+  }
+  .td {
+    flex: 1;
+    color: #edfaff;
+    font-size: 38px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+  }
+  .td-text {
+    /* 设置字体透明 */
+    color: transparent;
+    /* 使用 -webkit-background-clip 属性将背景剪裁至文本形状 */
+    -webkit-background-clip: text;
+    /* 非Webkit内核浏览器需要使用标准前缀 */
+    background-clip: text;
+    font-family: 'YouSheBiaoTiHei';
+    /* 设置线性渐变,从红色渐变到蓝色 */
+    background-image: linear-gradient(to bottom, #ffffff 50%, #3075d3 100%);
+    font-size: 48px;
+  }
+  .text-green {
+    background-image: linear-gradient(to bottom, #ffffff 50%, #40c75f 100%);
+  }
+  .text-danger {
+    background-image: linear-gradient(to bottom, #ffffff 50%, #ff2f3c 100%);
+  }
 }
-.text-danger {
-  color: #f56c6c;
+
+.custom-select {
+  width: 148px;
+  height: 38px;
+  line-height: 38px;
+  :deep(.el-select__wrapper) {
+    background-color: #081b41;
+    box-shadow: 0 0 0 1px #126cf7 inset;
+  }
+  :deep(.el-select__placeholder) {
+    font-size: 30px;
+    color: #eaf3fc;
+  }
 }
 </style>

+ 16 - 6
src/views/globalMap/RightMenu/RiverMonitor.vue

@@ -22,12 +22,14 @@
       <div class="td">当前水位(m)</div>
       <div class="td">差值</div>
     </div>
-    <div v-for="(item, index) in riverMonitorData.listData" :key="index" class="tr" @click="handleShowDialog(item)">
-      <div class="td">{{ item.name }}</div>
-      <div class="td">{{ item.area }}</div>
-      <div class="td td-text">{{ item.warningLevel }}</div>
-      <div class="td td-text">{{ item.waterLevel }}</div>
-      <div :class="item.warningLevel > item.waterLevel ? 'td td-text text-green' : 'td td-text text-danger'">{{ item.waterDiff }}</div>
+    <div class="table-content">
+      <div v-for="(item, index) in riverMonitorData.listData" :key="index" class="tr" @click="handleShowDialog(item)">
+        <div class="td">{{ item.name }}</div>
+        <div class="td">{{ item.area }}</div>
+        <div class="td td-text">{{ item.warningLevel }}</div>
+        <div class="td td-text">{{ item.waterLevel }}</div>
+        <div :class="item.warningLevel > item.waterLevel ? 'td td-text text-green' : 'td td-text text-danger'">{{ item.waterDiff }}</div>
+      </div>
     </div>
   </div>
   <Dialog v-model="showDialog" title="河道监测" width="2500px" height="1200px">
@@ -362,6 +364,14 @@ const handleShowDialog = (row) => {
 
 .custom-table {
   width: 100%;
+  height: 1030px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  .table-content {
+    height: 880px;
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
   .th {
     width: 1499px;
     height: 151px;

+ 16 - 6
src/views/globalMap/RightMenu/index.vue

@@ -21,17 +21,19 @@
       </div>
       <div v-show="menuState.showMenu" class="menu-content">
         <!--图层分析-->
-        <LayerAnalysis v-if="menuState.showMenu && menuState.activeIndex === 0" :pointType="pointType" />
+        <LayerAnalysis v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '图层分析'" :pointType="pointType" />
         <!--空间分析-->
-        <SpatialAnalysis v-if="menuState.showMenu && menuState.activeIndex === 1" :location="location" />
+        <SpatialAnalysis v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '空间分析'" :location="location" />
         <!--江湖河库-->
-        <Reservoir v-if="menuState.showMenu && menuState.activeIndex === 2" />
+        <Reservoir v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '江湖河库'" />
         <!--路网视频-->
-        <RoadNetworkVideo v-if="menuState.showMenu && menuState.activeIndex === 3" />
+        <RoadNetworkVideo v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '路网视频'" />
         <!--水库监测-->
-        <ReservoirMonitor v-if="menuState.showMenu && menuState.activeIndex === 4" />
+        <ReservoirMonitor v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '水库监测'" />
         <!--河道监测-->
-        <RiverMonitor v-if="menuState.showMenu && menuState.activeIndex === 5" />
+        <RiverMonitor v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '河道监测'" />
+        <!--实时标绘-->
+        <OnlinePlotting v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '实时标绘'" :mouseToolState="mouseToolState" :drawing="drawing" @updateDrawing="updateDrawing" />
       </div>
     </div>
   </div>
@@ -44,12 +46,16 @@ import RoadNetworkVideo from './RoadNetworkVideo.vue';
 import Reservoir from './Reservoir.vue';
 import SpatialAnalysis from '@/views/globalMap/RightMenu/SpatialAnalysis.vue';
 import LayerAnalysis from '@/views/globalMap/RightMenu/LayerAnalysis.vue';
+import OnlinePlotting from '@/views/globalMap/RightMenu/OnlinePlotting.vue';
 
 interface Props {
+  drawing: boolean;
   pointType: string[];
+  mouseToolState: MouseTool;
 }
 
 const props = withDefaults(defineProps<Props>(), {});
+const emits = defineEmits(['update:drawing']);
 const scrollListRef = ref();
 const menuState = reactive({
   showMenu: false,
@@ -114,6 +120,10 @@ const updateMenu = (type, menu) => {
   }
 };
 
+const updateDrawing = (value: boolean) => {
+  debugger
+  emits('update:drawing', value);
+}
 defineExpose({ handleMenu, clickContractMenu, setAnalysisData, updateMenu });
 </script>
 

+ 2 - 1
src/views/globalMap/SwitchMapTool.vue

@@ -31,7 +31,8 @@ const mapData = ref([
   { name: '逻辑地图', key: 'logical' },
   { name: '矢量地图', key: 'vectorgraph' },
   { name: '卫星地图', key: 'satellite' },
-  { name: '粤政图', key: 'satellite2' }
+  { name: '粤政图', key: 'satellite2' },
+  { name: '粤政图2', key: 'satellite3' },
 ]);
 let open = ref(false);
 const selectItem = (key) => {

+ 15 - 13
src/views/globalMap/index.vue

@@ -3,23 +3,19 @@
     <div :class="isComponent ? 'global-map' : 'global-map bg'">
       <MapLogical v-if="activeMap === 'logical'" :map-data="mapData" />
       <YztMap
-        v-else-if="activeMap === 'satellite2'"
+        v-else-if="['satellite2', 'satellite3'].includes(activeMap)"
         ref="map2Ref"
-        v-model:drawing="mouseToolState.drawing"
+        v-model:drawing="drawing"
+        :mouseToolState="mouseToolState"
         :active-map="activeMap"
-        :color="mouseToolState.color"
-        :draw-type="mouseToolState.drawType"
-        :graphics-type="mouseToolState.graphicsType"
         :pointType="pointType"
         @select-graphics="analysisSpatial"
       />
       <Map
         v-else
         ref="mapRef"
-        v-model:drawing="mouseToolState.drawing"
-        :color="mouseToolState.color"
-        :draw-type="mouseToolState.drawType"
-        :graphics-type="mouseToolState.graphicsType"
+        v-model:drawing="drawing"
+        :mouseToolState="mouseToolState"
         :active-map="activeMap"
         :pointType="pointType"
         @select-graphics="analysisSpatial"
@@ -28,7 +24,7 @@
       <!--左侧菜单-->
       <LeftMenu style="position: absolute; top: 20px; left: 20px" @click-menu="clickMenu" @select-search-marker="selectSearchMarker" />
       <!--右侧菜单-->
-      <RightMenu ref="rightMenuRef" :analysis-data="selectedScope" :pointType="pointType" />
+      <RightMenu ref="rightMenuRef" :analysis-data="selectedScope" v-model:drawing="drawing" :mouseToolState="mouseToolState" :pointType="pointType" />
       <!--更换地图类型-->
       <SwitchMapTool :active-map="activeMap" class="tool-box" @switch-map="switchMap" />
       <!--时间轴-->
@@ -163,14 +159,20 @@ const selectSearchMarker = (item) => {
   dom.addSearchMarker(item2);
 };
 
-const mouseToolState = reactive({
-  drawing: false,
+// 实时标绘
+let drawing = ref(false);
+const mouseToolState = reactive<MouseTool>({
   color: '#f80102',
-  // 1线框 2区域
+  lineWidth: '1px',
+  // 1基本图形 2图标
   drawType: '1',
   // 图形形状  circle圆形 rectangle矩形 polygon多边形
   graphicsType: 'circle'
 });
+// 绘制工具配置变化
+watch(mouseToolState, () => {
+  console.log(mouseToolState);
+}, { immediate: true })
 // 点击撤销
 const undo = () => {
   const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;