高德地图2.0 绘制、编辑多边形覆盖物(电子围栏)

1. 安装

bash 复制代码
npm i @amap/amap-jsapi-loader --save

移步:官方文档

2. map组件封装

javascript 复制代码
<script lang="ts" setup>
import AMapLoader from '@amap/amap-jsapi-loader'
import { onMounted, ref } from 'vue'
import { propTypes } from '@/utils/propTypes'

defineOptions({ name: 'Map' })

const props = defineProps({
  bindId: propTypes.string.def('mapContainer'),
  modelValue: propTypes.array.def([]),
  title: propTypes.string.def(''),
  width: propTypes.string.def('100%'),
  height: propTypes.string.def('600px'),
  polygonPaths: propTypes.array.def([]), // 回显多边形路径 得是<nubmer[]>类型,不然无法编辑
  districtCode: propTypes.string.def('140101') // 行政区划代码
})

watch(
  () => props.districtCode,
  (newVal, oldVal) => {
    if (newVal !== oldVal) {
      drawBounds()
    }
  }
)

window._AMapSecurityConfig = {
  securityJsCode: '4e6ca573a89ac3176f29813d3fcc895e'
}
const mouseTool = ref()
const mapRef = ref(null)
const overlays = ref<object[]>([])
const polyEditor = ref<any>()
const district = ref<any>()
const AMapObj = ref<any>()

// 新建
const createPolygon = () => {
  polyEditor.value.close()
  polyEditor.value.setTarget()
  polyEditor.value.open()
}

const emits = defineEmits(['update:modelValue'])
//获取
const getPolygon = () => {
  let overlays = mapRef.value?.getAllOverlays('polygon')
  let polygonPaths = overlays.map((overlay: any) => overlay.getPath())
  polygonPaths.shift() // 去掉第一个多边形
  console.log('🚀 ~ getPolygon ~ polygonPaths:', polygonPaths)
  const paths: object[] = []
  polygonPaths.forEach((item: any) => {
    const pathItem = item.map((cItem: any) => {
      const lnglat = `${cItem.lng},${cItem.lat}`
      return lnglat
    })
    paths.push(pathItem)
  })
  emits('update:modelValue', paths)
}

defineExpose({ getPolygon })

//清除绘制的多边形
const clearPolygon = () => {
  try {
    closeEditor()
    mapRef.value?.clearMap()
    drawBounds()
  } catch (e) {
    console.log(e)
  }
}

// 开启编辑
const openEditor = () => {
  if (polyEditor.value) {
    polyEditor.value.open()
  }
}

// 关闭编辑
const closeEditor = () => {
  if (polyEditor.value) {
    polyEditor.value.close()
  }
}

//加载行政区划插件
const drawBounds = () => {
  if (!district.value) {
    //实例化DistrictSearch
    var opts = {
      subdistrict: 0, //获取边界不需要返回下级行政区
      extensions: 'all', //返回行政区边界坐标组等具体信息
      level: 'district' //查询行政级别为 市
    }
    district.value = new AMap.DistrictSearch(opts)
  }
  //行政区查询
  district.value.setLevel('district')
  var keyword = props.districtCode
  if (keyword === '') {
    console.warn('行政区划不能为空')
    return
  }
  let polygon = null
  district.value.search(keyword, function (status, result) {
    if (polygon) {
      mapRef.value?.remove(polygon) //清除上次结果
      polygon = null
    }
    if (!result || !result.districtList || !result.districtList[0]) {
      console.warn('请正确填写名称或更新其他名称')
      return
    }
    var bounds = result.districtList[0].boundaries
    if (bounds) {
      //生成行政区划polygon
      for (var i = 0; i < bounds.length; i += 1) {
        //构造MultiPolygon的path
        bounds[i] = [bounds[i]]
      }
      polygon = new AMap.Polygon({
        path: bounds,
        strokeColor: '#F56C6C',
        strokeWeight: 4,
        fillOpacity: 0.1,
        fillColor: '#F56C6C',
        strokeStyle: 'dashed',
        strokeDasharray: [12, 3],
        zIndex: 0
      })
      mapRef.value?.add(polygon)
      mapRef.value?.setFitView(polygon) //视口自适应
    }
  })
}

// 编辑事件处理
const editPolygon = () => {
  polyEditor.value.on('add', function (data) {
    var polygon = data.target
    polyEditor.value.addAdsorbPolygons(polygon)
    polygon.on('dblclick', (e) => {
      polyEditor.value.setTarget(polygon)
      polyEditor.value.open()
    })
  })
  // polyEditor.value.on('adjust', function (data) {
  //   console.log('🚀 ~ polyEditor-adjust', data)
  // })
  // polyEditor.value.on('addnode', function (data) {
  //   console.log('🚀 ~ polyEditor-addnode', data)
  // })
  // polyEditor.value.on('end', function (data) {
  //   console.log('🚀 ~ polyEditor-end', data)
  // })
}

// 回显多边形
const showPolygon = (polygonArr: any) => {
  // const polygonArr = [[116.368904, 39.913423], [116.382122, 39.901176],[116.387271, 39.912501],[116.398258, 39.9046]]
  const polygon = new AMap.Polygon({
    path: polygonArr,
    strokeColor: '#1791fc',
    strokeOpacity: 0.9,
    strokeWeight: 4,
    fillColor: '#1791fc',
    fillOpacity: 0.3,
    strokeStyle: 'solid', //solid, 线样式还支持 'dashed',
    strokeDasharray: [12, 4], // strokeStyle是dashed时有效
    zIndex: 16
  })
  // polygon.on('click', (e) => {
  //   console.log('🚀 ~ polygon.on ~ e:', e)
  // })
  mapRef.value?.add(polygon)
  polyEditor.value.addAdsorbPolygons([polygon])
  polygon.on('dblclick', () => {
    polyEditor.value.setTarget(polygon)
    polyEditor.value.open()
  })
  mapRef.value?.setFitView(polygon) //视口自适应
}

// defineExpose({ showPolygon })

// 加载高德地图
const loader = () => {
  AMapLoader.load({
    key: '947ec8e0b6869f9ef9fc6badda641a06',
    version: '2.0', // 使用合适的版本
    plugins: ['AMap.Scale', 'AMap.MouseTool', 'AMap.PolygonEditor', 'AMap.DistrictSearch']
  })
    .then((AMap) => {
      AMapObj.value = AMap
      // 初始化地图
      mapRef.value = new AMap.Map(props.bindId, {
        zoom: 15,
        center: [116.397428, 39.90923] // 设置地图中心点
      })
      // 加载行政区划插件
      drawBounds()
      // 编辑事件处理
      polyEditor.value = new AMap.PolygonEditor(mapRef.value)
      editPolygon()
      // 回显保存的多边形
      props.polygonPaths.forEach((item: any) => {
        showPolygon(item)
      })

      mapRef.value?.on('click', (e) => {
        console.log('🚀 ~ mapRef.value.on ~ e:', e)
      })
    })
    .catch((error) => {
      console.error('加载高德地图失败', error)
    })
}

onMounted(() => {
  loader()
})
</script>

<template>
  <div class="mb-14px">
    <el-button @click="createPolygon" type="primary">新建</el-button>
    <!-- <el-button @click="openEditor" type="primary">编辑</el-button> -->
    <el-button @click="closeEditor" type="primary">关闭编辑</el-button>
    <el-button @click="clearPolygon" type="danger" plain>清空</el-button>
    <!-- <el-button @click="getPolygon" type="primary">获取</el-button> -->
  </div>

  <div :id="props.bindId" :style="{ width: width, height: height }"></div>
</template>

3. 使用组件

javascript 复制代码
<template>
  <el-drawer v-model="drawer2" :direction="direction" size="75%" :show-close="false">
    <template #header>
      <div>
        <div
          class="w-full flex items-center justify-between border-b border-b-[var(--el-border-color)] border-b-solid pb14px"
        >
          <div class="font-size-4">服务区域</div>
          <el-button type="primary" @click="close" size="small" circle>
            <Icon icon="ep:close-bold" :size="16" @click="cancelClick" />
          </el-button>
        </div>
      </div>
    </template>
    <template #default>
      <div class="flex items-center justify-center pb30px font-size-15px color-#666">
        起始地: {{ curRow.startDistrictName }}
        <el-icon class="mx-20px"><Switch /></el-icon>
        目的地: {{ curRow.endDistrictName }}
      </div>
      <div class="flex justify-center">
        <div class="mx-10px w-50%">
          <div>
            <Map
              bindId="startMap"
              v-model:modelValue="startPolygonPaths"
              v-if="drawer2"
              ref="StartMapRef"
              :polygonPaths="startDetailPaths"
              :districtCode="curRow.startDistrictCode"
            />
          </div>
        </div>
        <div class="mx-10px w-50%">
          <div>
            <Map
              bindId="endMap"
              v-model:modelValue="endPolygonPaths"
              v-if="drawer2"
              ref="EndMapRef"
              :polygonPaths="endDetailPaths"
              :districtCode="curRow.endDistrictCode"
            />
          </div>
        </div>
      </div>
    </template>
    <template #footer>
      <div style="flex: auto">
        <el-button @click="cancelClick">取消</el-button>
        <el-button type="primary" @click="confirmClick">确认</el-button>
      </div>
    </template>
  </el-drawer>
</template>

<script lang="ts" setup>
import { Switch } from '@element-plus/icons-vue'
import { ref, nextTick } from 'vue'
import { Map } from '@/components/Map/index'
import { getLinesServiceAreaList, saveLinesServiceArea } from '@/api/routeManage/routeList/index.ts'
const drawer2 = ref(false)
const direction = ref('rtl')
const curRow = ref<object>({})
const dcistrictCode = ref('')
const startPolygonPaths = ref([])
const endPolygonPaths = ref([])
const startDetailPaths = ref([])
const endDetailPaths = ref([])
const message = useMessage()

// 打开推窗
const open = async (row) => {
  curRow.value = row
  await getDetail()
  drawer2.value = true
}
defineExpose({ open })

const StartMapRef = ref(null)
const EndMapRef = ref(null)
const getDetail = async () => {
  const params = {...}
  try {
    const data = await getLinesServiceAreaList(params)
	...
    startDetailPaths.value =[[112.557711,37.731122],[112.625256,37.734871],[112.558896,37.680496],[112.558896,37.680496]]
    endDetailPaths.value = [[112.676067,36.378644],[112.79624,36.385946],[112.732752,36.29097],[112.732752,36.29097]]
  } finally {
  }
}

// 获取起点数据
const getStartData = () => {
  const linesServiceAreaList = startPolygonPaths.value.map((item) => {
    if (item) {
      return { areaType: 1, pointList: item }
    }
  })
  return linesServiceAreaList || []
}

// 获取终点数据
const getEndData = () => {
  const linesServiceAreaList = endPolygonPaths.value.map((item) => {
    if (item) {
      return { areaType: 1, pointList: item }
    }
  })
  return linesServiceAreaList || []
}

const confirmClick = async () => {
  await StartMapRef.value?.getPolygon()
  await EndMapRef.value?.getPolygon()
  const params = {areaList: [...getStartData(), ...getEndData()]}
  try {
    await saveLinesServiceArea(params)
    message.success('保存成功')
  } finally {
    drawer2.value = false
  }
}

function cancelClick() {
  drawer2.value = false
}
</script>
相关推荐
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235241 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240252 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript
落魄实习生2 小时前
AI应用-本地模型实现AI生成PPT(简易版)
python·ai·vue·ppt
寻找沙漠的人2 小时前
前端知识补充—CSS
前端·css
GISer_Jing3 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245523 小时前
吉利前端、AI面试
前端·面试·职场和发展
理想不理想v3 小时前
webpack最基础的配置
前端·webpack·node.js
pubuzhixing3 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github