瀏覽代碼

实时标绘 实现直线 矩形 多边形 圆形

Hwf 10 月之前
父節點
當前提交
c31b9e1146

+ 11 - 0
src/components/Map/TextEdit/index.vue

@@ -0,0 +1,11 @@
+<template>
+
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped>
+
+</style>

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

@@ -48,7 +48,7 @@ const props = withDefaults(defineProps<Props>(), {});
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { point_type } = toRefs<any>(proxy?.useDict('point_type'));
 
-const emits = defineEmits(['update:drawing', 'selectGraphics', 'unSelectGraphics']);
+const emits = defineEmits(['update:drawing', 'selectGraphics', 'unSelectGraphics', 'showTextEditBox']);
 const containerRef = ref();
 const width = ref('100%');
 const height = ref('100%');
@@ -66,7 +66,7 @@ const mapState = reactive({
 let AMap, map, scale;
 
 // 鼠标绘制工具
-const { initMouseTool, drawGraphics, setOptions, closeDraw, handleUndo } = useDrawTool({
+const { initMouseTool, drawGraphics, closeDraw, handleUndo } = useDrawTool({
   color: props.mouseToolState.color,
   lineWidth: props.mouseToolState.lineWidth,
   drawType: props.mouseToolState.drawType,
@@ -111,7 +111,7 @@ const { getAMap, getMap, switchMap, addMarker, addSearchMarker, clearMarker, get
       map.removeLayer();
     }
     map.on('zoomchange', zoomChangeHandler);
-    initMouseTool({ map, AMap });
+    initMouseTool({ container: 'aMap', map, AMap });
     initRuler(map, AMap);
     handleResize();
   },
@@ -183,15 +183,28 @@ watch(
     switchMap(props.activeMap);
   }
 );
-// 监听是否开启绘制
+let lnglat;
 watch(
-  () => props.drawing,
-  (value) => {
-    if (value) {
-      drawGraphics(props.mouseToolState);
+  () => props.mouseToolState,
+  () => {
+    if (props.drawing) {
+      if (props.mouseToolState.graphicsType === 'text') {
+        // 监听地图点击事件
+        map.on('click', function (e) {
+          // 获取点击位置的经纬度
+          lnglat = e.lnglat;
+          emits('showTextEditBox', true);
+        });
+        // 绘制文字点击地图触发
+      } else {
+        drawGraphics(props.mouseToolState);
+      }
     } else {
       closeDraw();
     }
+  },
+  {
+    deep: true
   }
 );
 // 缩放级别变化

+ 138 - 14
src/hooks/AMap/useDrawTool.ts

@@ -1,6 +1,6 @@
 import { nanoid } from 'nanoid';
 import { useHistory } from '@/hooks/useHistory';
-import { deepClone } from '@/utils';
+import { deepClone, getRgba } from '@/utils';
 import { countCircleArea, countRectangleArea } from '@/utils/geometryUtil';
 
 interface DrawToolOptions {
@@ -11,22 +11,23 @@ interface DrawToolOptions {
   onDrawCompleted?: Function;
 }
 export function useDrawTool(options: DrawToolOptions) {
-  const drawOptions = {
-    strokeColor: options.color,
+  let drawOptions = {
+    strokeColor: '#f80102',
     strokeOpacity: 1,
-    strokeWeight: options.lineWidth,
-    fillColor: options.color,
-    fillOpacity: options.drawType === '1' ? 0 : 0.5,
+    strokeWeight: '1',
+    fillColor: '#f80102',
+    fillOpacity: 0.5,
     strokeStyle: 'solid'
   };
   const { currentState, commit, undo, history, future } = useHistory();
   const overlays = [];
   const overlaysData = [];
   let graphicsType = options.graphicsType;
-  let mouseTool, contextMenu, map, AMap, rightClickObj;
-  const initMouseTool = (options) => {
-    map = options.map;
-    AMap = options.AMap;
+  let mouseTool, contextMenu, container, map, AMap, rightClickObj;
+  const initMouseTool = (options2) => {
+    container = document.getElementById('aMap');
+    map = options2.map;
+    AMap = options2.AMap;
     // 初始化绘制工具
     mouseTool = new AMap.MouseTool(map);
     // 绘制完成事件
@@ -104,20 +105,143 @@ export function useDrawTool(options: DrawToolOptions) {
     }
     return res;
   };
+  let lnglat = [];
   // 绘制图形
   const drawGraphics = (newOptions: MouseTool) => {
-    options = deepClone(newOptions);
-    if (options.graphicsType === 'circle') {
+    const data = getRgba(newOptions.color);
+    if (['circle', 'rectangle', 'polygon'].includes(newOptions.graphicsType)) {
+      drawOptions = {
+        strokeColor: !!data.color ? data.color : newOptions.color,
+        strokeOpacity: 1,
+        strokeWeight: newOptions.lineWidth,
+        fillColor: data.color,
+        fillOpacity: data.opacity,
+        strokeStyle: 'solid'
+      };
+    } else if (['anyLine', 'straightLine'].includes(newOptions.graphicsType)) {
+      drawOptions = {
+        strokeColor: !!data.color ? data.color : newOptions.color,
+        strokeOpacity: data.opacity,
+        strokeWeight: newOptions.lineWidth,
+        strokeStyle: 'solid'
+      };
+    } else if (newOptions.graphicsType === 'marker') {
+      drawOptions = {
+        strokeColor: !!data.color ? data.color : newOptions.color,
+        strokeOpacity: 1,
+        strokeWeight: newOptions.lineWidth,
+        fillColor: data.color,
+        fillOpacity: data.opacity,
+        strokeStyle: 'solid'
+      };
+    } else if (newOptions.graphicsType === 'text') {
+      drawOptions = {
+        fontSize: newOptions.fontSize,
+        color: newOptions.color
+      };
+    }
+    closeDraw();
+    if (newOptions.graphicsType === 'circle') {
       // 绘制圆形
       mouseTool.circle(drawOptions);
-    } else if (options.graphicsType === 'rectangle') {
+    } else if (newOptions.graphicsType === 'rectangle') {
       // 绘制矩形
       mouseTool.rectangle(drawOptions);
-    } else if (options.graphicsType === 'polygon') {
+    } else if (newOptions.graphicsType === 'polygon') {
       // 绘制多边形
       mouseTool.polygon(drawOptions);
+    } else if (newOptions.graphicsType === 'anyLine') {
+      drawAnyLine(drawOptions);
+    } else if (newOptions.graphicsType === 'straightLine') {
+      // 绘制直线
+      mouseTool.polyline(drawOptions);
+    } else if (newOptions.graphicsType === 'marker') {
+      // 绘制图标
+      mouseTool.marker(drawOptions);
     }
   };
+  const addText = (drawOptions) => {
+    // 文本覆盖物的样式
+    const textStyle = {
+      fontSize: drawOptions.fontSize,
+      color: drawOptions.fontColor,
+      borderColor: 'transparent',
+      backgroundColor: 'transparent',
+      borderWidth: 0,
+      cursor: 'pointer' // 鼠标悬停时显示指针
+    };
+
+    // 创建文本覆盖物
+    const text = new AMap.Text({
+      text: drawOptions.fontText, // 文本内容,可以根据需要自定义
+      position: lnglat, // 文本位置(经纬度)
+      style: textStyle, // 文本样式
+      zIndex: 100, // 文本层级
+      draggable: true // 是否可拖动(可选)
+      // 如果需要,可以在这里添加点击文本的回调事件
+      // events: {
+      //     click: function() {
+      //         // 处理文本点击事件
+      //         alert('您点击了文本!');
+      //     }
+      // }
+    });
+    // 将文本覆盖物添加到地图
+    map.add(text);
+  };
+  let polyline = null;
+  let startPoint = null;
+  let isDrawing = false;
+  let lastPoint = null;
+  // 绘制任意线
+  const drawAnyLine = (drawOptions) => {
+    container.addEventListener('mousedown', drawAnyLineMouseDown);
+
+    container.addEventListener('mousemove', (e) => {
+      e.preventDefault();
+      if (!isDrawing) return;
+
+      const mapPoint = this.map.getContainer().getBoundingClientRect();
+      const lngLat = this.map.getLngLatFromContainerPixel([e.clientX - mapPoint.left, e.clientY - mapPoint.top]);
+
+      // 更新线条(这里需要手动管理polyline的坐标)
+      if (polyline) {
+        polyline.setPath(polyline.getPath().concat([lngLat]));
+      } else {
+        // 首次绘制时创建polyline
+        polyline = new AMap.Polyline({
+          map: this.map,
+          path: [startPoint, lngLat],
+          strokeColor: drawOptions.strokeColor,
+          strokeWeight: drawOptions.strokeWeight,
+          strokeOpacity: drawOptions.strokeOpacity,
+          strokeStyle: 'solid',
+          lineJoin: 'round',
+          lineCap: 'round',
+          zIndex: 50
+        });
+      }
+      lastPoint = lngLat;
+      console.log(lngLat);
+    });
+
+    container.addEventListener('mouseup', (e) => {
+      isDrawing = false;
+      map.setDraggable(true);
+    });
+  };
+  // 任意线 鼠标按下事件
+  const drawAnyLineMouseDown = (e) => {
+    // 阻止默认行为
+    e.preventDefault();
+    // 获取鼠标在地图上的位置
+    const mapPoint = map.getContainer().getBoundingClientRect();
+    const lngLat = map.getLngLatFromContainerPixel([e.clientX - mapPoint.left, e.clientY - mapPoint.top]);
+
+    startPoint = lngLat;
+    isDrawing = true;
+    lastPoint = startPoint;
+  };
   // 设置颜色
   const setColor = (color: string) => {
     drawOptions.strokeColor = color;

+ 7 - 4
src/types/map.d.ts

@@ -1,6 +1,9 @@
 interface MouseTool {
-  color: string,
-  lineWidth: string,
-  drawType: string,
-  graphicsType: string,
+  color: string;
+  lineWidth: string;
+  drawType: string;
+  graphicsType: string;
+  text?: string;
+  fontSize?: number;
+  fontColor?: number;
 }

+ 7 - 6
src/utils/index.ts

@@ -361,18 +361,19 @@ export const getTransformScale = (element) => {
   return { scaleX: 1, scaleY: 1 };
 };
 
-export const setRGBAOpacityToOne = (rgbaString) => {
+export const getRgba = (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; // 或者抛出错误
+    return rgbaString;
   }
 
   // 提取出红、绿、蓝和原始透明度
-  const [r, g, b, originalOpacity] = match.slice(1).map(Number);
+  const [r, g, b, opacity] = match.slice(1).map(Number);
 
   // 返回新的RGBA字符串,其中透明度被设置为1
-  return `rgba(${r}, ${g}, ${b}, 1)`;
+  return {
+    color: 'rgb(' + r + ',' + g + ',' + b + ')',
+    opacity: opacity
+  }
 }

+ 138 - 29
src/views/globalMap/RightMenu/OnlinePlotting.vue

@@ -1,25 +1,57 @@
 <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 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>
+  <el-button size="large" @click="changeDrawing">{{ drawing ? '关闭' : '开启' }}</el-button>
+  <div class="tabs1">
+    <div v-for="(item, index) in menu" :key="index" :class="menuActive1 === index ? 'tab tab_active' : 'tab'" @click="clickTab(index)">
+      {{ item.name }}
+    </div>
+  </div>
+  <div v-show="menuActive1 === 0" class="tab-content">
+    <div class="tabs2">
+      <div
+        v-for="(item, index) in menu[menuActive1].children"
+        :key="index"
+        :class="menuActive2 === index ? 'tab tab_active' : 'tab'"
+        @click="clickTab2(index)"
+      >
+        {{ item.name }}
+      </div>
+      <div class="tab-content2">
+        <div
+          v-for="(item, index) in menu[menuActive1].children[menuActive2].children"
+          :key="index"
+          :class="menuActive2 === index ? 'tab tab_active' : 'tab'"
+          @click="clickTab3(item.value)"
+        >
+          {{ item.name }}
+        </div>
+      </div>
+    </div>
+  </div>
+  <div v-show="menuActive1 === 1" class="tab-content">历史记录</div>
+  <div v-show="textEditState.showTextEdit" class="text-edit-container">
+    <el-input v-model="textEditState.text" :rows="8" type="textarea" />
+    <div class="edit-box">
+      <div class="flex">
+        <div class="text">字号</div>
+        <el-input v-model="textEditState.fonSize" />
+      </div>
+      <div class="flex">
+        <div class="text">颜色</div>
+        <el-color-picker v-model="textEditState.fontColor" popper-class="custom-color-picker" show-alpha size="large" />
+      </div>
+    </div>
+    <div class="edit-btn-box">
+      <el-button size="large" @click="textEditState.show = false">取消</el-button>
+      <el-button type="primary" size="large" @click="addText">确定</el-button>
+    </div>
+  </div>
 </template>
 
 <script lang="ts" setup>
@@ -29,15 +61,17 @@ interface Props {
 }
 
 const props = withDefaults(defineProps<Props>(), {});
-const emits = defineEmits(['updateDrawing']);
+const emits = defineEmits(['updateDrawing', 'updateMouseToolState']);
 
+const menuActive1 = ref(0);
+const menuActive2 = ref(0);
 const menu = ref([
   {
     name: '标绘工具',
-    value: '1',
     children: [
       {
         name: '基本工具',
+        value: 'basicTools',
         children: [
           {
             name: '直箭头',
@@ -53,7 +87,7 @@ const menu = ref([
           },
           {
             name: '任意线',
-            value: ''
+            value: 'anyLine'
           },
           {
             name: '圆',
@@ -72,25 +106,72 @@ const menu = ref([
             value: ''
           }
         ]
-      }
+      },
+      { name: '火点', value: 'firePoint', children: [] },
+      { name: '火线', value: 'firewire', children: [] },
+      { name: '火场', value: 'fireGround', children: [] },
+      { name: '箭头', value: 'arrow', children: [] },
+      { name: '导航', value: 'navigation', children: [] },
+      { name: '扑救队伍', value: 'firefightingTeam', children: [] },
+      { name: '飞机车辆', value: 'aircraftVehicles', children: [] },
+      { name: '基础设置', value: 'basicSetting', children: [] },
+      { name: '其他', value: 'other', children: [] }
     ]
   },
   {
     name: '历史记录',
-    value: 'history',
     children: []
   }
-])
+]);
 const lineWidthOptions = reactive([
-  { name: '1像素', value: '1px' },
-  { name: '2像素', value: '2px' },
-  { name: '3像素', value: '3px' }
-])
+  { name: '1像素', value: '1' },
+  { name: '2像素', value: '2' },
+  { name: '3像素', value: '3' }
+]);
+const textEditState = reactive({
+  showTextEdit: false,
+  fontColor: '#f80102',
+  fontSize: '36',
+  text: ''
+});
 
+// 点击一级菜单
+const clickTab = (value: number) => {
+  menuActive1.value = value;
+};
+// 点击二级菜单
+const clickTab2 = (value: number) => {
+  menuActive2.value = value;
+};
+// 点击三级菜单
+const clickTab3 = (type) => {
+  if (props.mouseToolState.graphicsType === type) {
+    emits('updateDrawing', false);
+    props.mouseToolState.graphicsType = '';
+  } else {
+    emits('updateDrawing', true);
+    props.mouseToolState.graphicsType = type;
+  }
+};
+const handleClick = () => {
+  textEditState.fontColor = props.mouseToolState.color;
+  textEditState.fontSize = props.mouseToolState.fontSize;
+  textEditState.showTextEdit = true;
+};
+const addText = () => {
+  emits('updateMouseToolState', {
+    color: props.mouseToolState.color,
+    lineWidth: props.mouseToolState.lineWidth,
+    drawType: props.mouseToolState.drawType,
+    graphicsType: 'text',
+    fontColor: textEditState.fontColor,
+    fontSize: textEditState.fontSize
+  });
+  textEditState.showTextEdit = false;
+};
 const changeDrawing = () => {
-  debugger
-  emits('updateDrawing', !props.drawing)
-}
+  emits('updateDrawing', !props.drawing);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -104,4 +185,32 @@ const changeDrawing = () => {
 :deep(.el-color-dropdown__link-btn) {
   display: none;
 }
+.text-edit-container {
+  position: fixed;
+  top: 500px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 600px;
+  height: 400px;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 20px;
+  font-size: 36px;
+  .edit-box {
+    margin-top: 20px;
+    display: flex;
+    .flex {
+      flex: 1;
+      margin-right: 20px;
+    }
+    .text {
+      white-space: nowrap;
+      margin-right: 8px;
+    }
+    .edit-btn-box {
+      margin-top: 20px;
+      display: flex;
+    }
+  }
+}
 </style>

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

@@ -121,7 +121,6 @@ const updateMenu = (type, menu) => {
 };
 
 const updateDrawing = (value: boolean) => {
-  debugger
   emits('update:drawing', value);
 }
 defineExpose({ handleMenu, clickContractMenu, setAnalysisData, updateMenu });

+ 33 - 21
src/views/globalMap/index.vue

@@ -20,24 +20,31 @@
         :pointType="pointType"
         @select-graphics="analysisSpatial"
         @un-select-graphics="unSelectGraphics"
+        @showTextEditBox="showTextEditBox"
       />
       <!--左侧菜单-->
       <LeftMenu style="position: absolute; top: 20px; left: 20px" @click-menu="clickMenu" @select-search-marker="selectSearchMarker" />
       <!--右侧菜单-->
-      <RightMenu ref="rightMenuRef" :analysis-data="selectedScope" v-model:drawing="drawing" :mouseToolState="mouseToolState" :pointType="pointType" />
+      <RightMenu
+        ref="rightMenuRef"
+        :analysis-data="selectedScope"
+        v-model:drawing="drawing"
+        :mouseToolState="mouseToolState"
+        :pointType="pointType"
+        @updateMouseToolState="updateMouseToolState"
+      />
       <!--更换地图类型-->
       <SwitchMapTool :active-map="activeMap" class="tool-box" @switch-map="switchMap" />
       <!--时间轴-->
       <TimeAxis />
-      <DrawTools
-        v-if="showDrawTools"
-        v-model:drawing="mouseToolState.drawing"
-        v-model:color="mouseToolState.color"
-        v-model:drawType="mouseToolState.drawType"
-        v-model:graphicsType="mouseToolState.graphicsType"
-        class="absoluteTool"
-        @undo="undo"
-      />
+<!--      <DrawTools-->
+<!--        v-if="showDrawTools"-->
+<!--        v-model:drawing="mouseToolState.drawing"-->
+<!--        v-model:color="mouseToolState.color"-->
+<!--        v-model:graphicsType="mouseToolState.graphicsType"-->
+<!--        class="absoluteTool"-->
+<!--        @undo="undo"-->
+<!--      />-->
     </div>
   </div>
 </template>
@@ -161,18 +168,23 @@ const selectSearchMarker = (item) => {
 
 // 实时标绘
 let drawing = ref(false);
-const mouseToolState = reactive<MouseTool>({
-  color: '#f80102',
-  lineWidth: '1px',
-  // 1基本图形 2图标
-  drawType: '1',
-  // 图形形状  circle圆形 rectangle矩形 polygon多边形
-  graphicsType: 'circle'
+const mouseToolState = ref<MouseTool>({
+  color: 'rgba(248, 1, 2, 1)',
+  lineWidth: '1',
+  graphicsType: '',
+  text: '',
+  fontColor: '#f80102',
+  fontSize: '36'
 });
-// 绘制工具配置变化
-watch(mouseToolState, () => {
-  console.log(mouseToolState);
-}, { immediate: true })
+
+const showTextEditBox = () => {
+  rightMenuRef.value.
+};
+
+const updateMouseToolState = (data) => {
+  mouseToolState.value = data;
+};
+
 // 点击撤销
 const undo = () => {
   const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;

+ 0 - 1
src/views/routineCommandMap/RightSection/checkview.vue

@@ -131,7 +131,6 @@ const getList = async () => {
 
 onMounted(() => {
   getList();
-  debugger;
   getPlanDetail(props.planId).then((res) => {
     detailData.value = res.data;
   });