效果图:

1、子组件
java
<template>
<div class="tengxunMapPolygon" :style="{ height: `${height}px` }">
<div :id="`container-${mid}`" class="xn-wh"></div>
<div id="toolControl" v-if="showTools">
<div
class="toolItem"
:class="{ active: activeType === 'polygon' }"
@click="setActiveType('polygon')"
title="添加"
>
添加
</div>
<div
class="toolItem"
:class="{ active: activeType === 'polygonEditor' }"
@click="setActiveType('polygonEditor')"
title="编辑"
>
编辑
</div>
</div>
<div id="toolControl" style="top: 0px; left: 200px; width: 60px" v-if="onShow">
<div class="toolItem" :class="{ active: activeType === 'delete' }" @click="setActiveType('delete')" title="删除">
删除
</div>
</div>
</div>
</template>
<script setup name="tengxunMapPolygon">
import { onMounted, onUnmounted, reactive, ref } from 'vue'
const props = defineProps({
mid: {
type: Number,
default: new Date().getTime()
},
height: {
type: Number,
default: 800
},
apiKey: {
type: String,
required: true
},
center: {
type: Array
},
markers: {
type: Array
},
zoom: {
type: Number,
default: 12
},
showTools: {
type: Boolean,
default: false
}
})
const emits = defineEmits(['polygonMapClick', 'drawComplete'])
const onShow = ref(false)
let map
let editor = reactive()
const markers = ref([])
const polygons = ref([])
const infoWindows = ref({})
let activeType = ref('polygon')
let geometryLayers = reactive({
delete: null,
polygon: null,
polygonEditor: null
})
const setActiveType = (type) => {
activeType.value = type
if (editor) {
editor.setActiveOverlay('polygon')
onShow.value = false
if (type === 'polygon') {
editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW)
} else if (type === 'polygonEditor') {
onShow.value = true
editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT)
} else if (type === 'delete') {
const selectedList = editor.getSelectedList()
console.log('selectedList', selectedList)
if (selectedList && selectedList.length > 0) {
const activeOverlay = editor.getActiveOverlay()
console.log(activeOverlay)
if (activeOverlay && activeOverlay.overlay) {
const currentGeometries = activeOverlay.overlay.geometries || []
const newGeometries = currentGeometries.filter(
(geo) => !selectedList.some((selected) => selected.id === geo.id)
)
activeOverlay.overlay.setGeometries(newGeometries)
let allPolygons = [];
let polygonIds = [];
currentGeometries.forEach((geo) => {
if(!polygonIds.includes(geo.id)){
polygonIds.push(geo.id)
let points = [];
//绘制和编辑时数据不一致
let geoPaths = geo.paths
if(Array.isArray(geoPaths[0])){
geoPaths = geoPaths[0]
}
// console.log(geo.paths, geoPaths)
geoPaths.forEach((point) => {
points.push({
lng: point.lng,
lat: point.lat
})
})
allPolygons.push(points)
}
})
emits('drawComplete', {
type: 'polygon',
action: 'delete',
geometry: {
paths: allPolygons
}
})
//清空被选中图形
editor.delete(selectedList[0].id)
}
}
}
}
}
// 初始化地图
const initMap = () => {
if (!window.TMap) {
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://map.qq.com/api/gljs?v=1.exp&key=${props.apiKey}&libraries=tools,service`
script.onload = () => {
createMap()
}
document.head.appendChild(script)
} else {
createMap()
}
}
// 创建地图实例
const createMap = () => {
try {
map = new TMap.Map(document.getElementById(`container-${props.mid}`), {
zoom: props.zoom,
center: new TMap.LatLng(props.center.lat, props.center.lng),
mapStyleId: props.mapStyle
})
if (window.TMap.tools && window.TMap.tools.GeometryEditor) {
editor = new TMap.tools.GeometryEditor({
map: map,
overlayList: [
{
overlay: new TMap.MultiPolygon({
map,
styles: {
default: new TMap.PolygonStyle({
// color: '#d1d7e433',
showBorder: true,
// borderColor: '#417cfa',
borderWidth: 2
}),
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.4)'
})
},
geometries: []
}),
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW,
activeOverlayId: 'polygon',
snappable: true,
selectable: true
})
// 监听绘制完成事件
editor.on('draw_complete', (geometry) => {
const activeOverlay = editor.getActiveOverlay()
if (!activeOverlay || !activeOverlay.overlay) return
// console.log('activeOverlay', activeOverlay)
// 获取现有的几何图形列表
const currentGeometries = activeOverlay.overlay.geometries || []
let allPolygons = [];
let polygonIds = [];
currentGeometries.forEach((geo) => {
if(!polygonIds.includes(geo.id)){
polygonIds.push(geo.id)
let points = [];
let geoPaths = geo.paths
if(Array.isArray(geoPaths[0])){
geoPaths = geoPaths[0]
}
// console.log(geo.paths, geoPaths)
geoPaths.forEach((point) => {
points.push({
lng: point.lng,
lat: point.lat
})
})
allPolygons.push(points)
}
})
emits('drawComplete', {
type: 'polygon',
geometry: {
paths: allPolygons
}
})
})
// 修改编辑完成事件监听
editor.on('adjust_complete', (geometry) => {
try {
const activeOverlay = editor.getActiveOverlay()
if (!activeOverlay || !activeOverlay.overlay) return
// 获取现有的几何图形列表
const currentGeometries = activeOverlay.overlay.geometries || []
currentGeometries.push(geometry)
let allPolygons = [];
let polygonIds = [];
currentGeometries.forEach((geo) => {
if(!polygonIds.includes(geo.id)){
polygonIds.push(geo.id)
let points = [];
let geoPaths = geo.paths
if(Array.isArray(geoPaths[0])){
geoPaths = geoPaths[0]
}
geoPaths.forEach((point) => {
points.push({
lng: point.lng,
lat: point.lat
})
})
allPolygons.push(points)
}
})
// 发送更新后的数据
emits('drawComplete', {
type: 'polygon',
geometry: {
paths: allPolygons
}
})
} catch (error) {
console.error('处理编辑完成事件失败:', error)
}
})
}
// 添加地图点击事件监听
map.on('click', (evt) => {
emits('polygonMapClick', {
lnglat: {
lng: evt.latLng.lng,
lat: evt.latLng.lat
},
type: evt.type
})
})
} catch (error) {
console.error('创建地图失败:', error)
}
}
// 添加标记点
const renderMarkerTx = (dataArr) => {
try {
// console.log('添加标记点,数据:', dataArr)
clearOverlayTx() // 先清除已有标记
if (!map) {
console.error('地图未初始化')
return
}
const markerLayer = new TMap.MultiMarker({
map: map,
styles: {
marker: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 }
})
},
geometries: dataArr.map((item) => ({
id: item.id,
styleId: 'marker',
position: new TMap.LatLng(item.position[1], item.position[0]), // 注意经纬度顺序
properties: {
title: item.title
}
}))
})
markers.value.push(markerLayer)
// console.log('标记点添加成功')
} catch (error) {
console.error('添加标记点失败:', error)
}
}
// 修改多边形绘制方法
const renderPolygon = (dataArr, option = {}) => {
try {
// 等待编辑器初始化完成
if (!editor) {
setTimeout(() => renderPolygon(dataArr, option), 100)
return
}
//
// 获取编辑器中的多边形图层
const activeOverlay = editor.getActiveOverlay()
if (!activeOverlay || !activeOverlay.overlay) {
return
}
// 处理多个多边形
let geometries = dataArr.map((polygonPoints, index) => {
// 确保每个点都是有效的经纬度对象
const validPoints = polygonPoints.filter(
(point) => point && typeof point.lng === 'number' && typeof point.lat === 'number'
)
let paths = validPoints.map((point) => new TMap.LatLng(point.lat, point.lng))
console.log(paths);
return {
paths: [paths]
}
})
// 验证几何图形数据的有效性
const validGeometries = geometries.filter((geo) => geo.paths && geo.paths[0] && geo.paths[0].length >= 3)
if (validGeometries.length === 0) {
// console.error('没有有效的多边形数据')
return
}
// 更新编辑器中的多边形
activeOverlay.overlay.setGeometries(validGeometries)
} catch (error) {
console.error('渲染多边形失败:', error)
console.error('错误详情:', error.stack)
console.error('输入数据:', JSON.stringify(dataArr))
}
}
// 添加信息窗体
const renderInfoWindow = (dataArr) => {
dataArr.forEach((item) => {
const position = new TMap.LatLng(item.position[0], item.position[1])
const info = new TMap.InfoWindow({
map: map,
position,
content: item.content.join('<br>'),
offset: { x: 0, y: -32 }
})
infoWindows.value[item.position.join(',')] = info
info.close() // 默认关闭
})
}
// 打开信息窗体
const openInfoWindow = (position) => {
const key = position.join(',')
if (infoWindows.value[key]) {
infoWindows.value[key].open()
}
}
// 清除覆盖物
const clearOverlayTx = () => {
// console.log('清除。。。。')
markers.value.forEach((marker) => {
marker.setMap(null)
})
markers.value = []
Object.values(infoWindows.value).forEach((info) => {
info.setMap(null)
})
infoWindows.value = {}
}
// 暴露获取地址的方法
const getAddress = async (lnglat) => {
return new Promise((resolve) => {
geocoder.getAddress(lnglat, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
resolve(result.regeocode.formattedAddress)
} else {
resolve(null)
}
})
})
}
onMounted(() => {
setTimeout(() => {
initMap()
}, 100)
})
onUnmounted(() => {
if (map) {
map.destroy()
}
})
defineExpose({
renderMarkerTx,
renderInfoWindow,
openInfoWindow,
clearOverlayTx,
renderPolygon,
getAddress,
setActiveType,
editor: editor
})
</script>
<style lang="less">
.tengxunMapPolygon {
position: relative;
overflow: hidden;
.xn-wh {
width: 100%;
height: 100%;
min-height: 300px;
}
}
#toolControl {
position: absolute;
top: -0px;
left: 0px;
right: 0px;
margin: auto;
width: 110px;
z-index: 1001;
background: #fff;
padding: 5px;
border-radius: 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
display: flex;
}
.toolItem {
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
border: 1px solid #ffffff;
font-size: 14px;
color: #666;
transition: all 0.3s;
&:hover {
border-color: #789cff;
color: #789cff;
}
&.active {
border-color: #d5dff2;
background-color: #d5dff2;
color: #1890ff;
}
}
</style>
2、父组件
java
<template>
<xn-form-container
:title="formData.id ? '编辑车场管理' : '增加车场管理'"
:width="700"
v-model:open="open"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-form-item>
<a-alert message="点击地图开始绘制范围,双击结束绘制" type="warning" closable />
<TengxunMap
:mid="2"
:zoom="17"
:height="300"
:apiKey="props.apiKey"
@polygonMapClick="onPolygonMapClick"
@drawComplete="onDrawComplete"
:center="mapCenter"
:polygons="polygonPoints"
:pitch="30"
:showTools="true"
ref="polygonMapRef"
/>
</a-form-item>
<a-form-item label="车场范围:" name="areaPoints">
<a-textarea v-model:value="formData.areaPoints" placeholder="请输入车场范围" allow-clear :disabled=true />
</a-form-item>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="parkinglotForm">
import campusApi from '@/api/emap/campusApi'
import TengxunMap from '@/components/Map/tengxunMap/polygon.vue'
import { required, rules } from '@/utils/formRules'
import { cloneDeep } from 'lodash-es'
import { computed, nextTick, ref } from 'vue'
// 抽屉状态
const open = ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
// 表单数据
const formData = ref({})
const submitLoading = ref(false)
const typeOptions = ref([])
const orgOptions = ref([])
const disabled = ref(false)
const parkingIconId = ref()
const tengxunMapRef = ref()
const polygonMapRef = ref()
const latitude = ref(32.27686)
const longitude = ref(118.312082)
const mapCenter = computed(() => ({
lat: Number(latitude.value),
lng: Number(longitude.value)
}))
const props = defineProps({
apiKey: {
type: String,
required: true
}
})
// 新增绘制相关的状态
const isDrawing = ref(false)
const polygonPoints = ref([])
// 图标
const getIconFile = (e) => {
parkingIconId.value = e.id
formData.value.parkingIconId = e.id
}
// 打开抽屉
const onOpen = (record) => {
disabled.value = false
open.value = true
formData.value = {
status: false
}
if (record) {
let recordData = cloneDeep(record)
// 先设置表单数据
formData.value = Object.assign({}, recordData)
// 设置地图中心点和标记
if (formData.value.lat && formData.value.lng) {
// 更新中心点坐标
latitude.value = Number(formData.value.lat)
longitude.value = Number(formData.value.lng)
}
// 处理多边形数据
if (formData.value.areaPoints) {
nextTick(() => {
onPolygonMapComplete()
})
}
}
}
// 关闭抽屉
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
open.value = false
}
campusApi.list().then((data) => {
orgOptions.value = data
})
/**
* 第二张地图
* @param e
*/
// 处理第二个地图的点击事件(绘制多边形)
const onPolygonMapClick = (e) => {
// console.log('父组件开始绘制。。', e)
if (!isDrawing.value) {
// 开始绘制
isDrawing.value = true
polygonMapRef.value.setActiveType('polygon')
}
}
// 添加绘制完成事件处理
const onDrawComplete = (data) => {
if (data.type === 'polygon') {
const points = data.geometry.paths.map((point) => ({
lng: point.lng,
lat: point.lat
}))
formData.value.areaPoints = JSON.stringify(data.geometry.paths)
console.log('areaPoints。',formData.value.areaPoints)
isDrawing.value = false
}
}
// 处理第二个地图的加载完成事件
const onPolygonMapComplete = () => {
if (formData.value.areaPoints) {
try {
const points = JSON.parse(formData.value.areaPoints)
polygonPoints.value = points
// 只绘制多边形,不绘制顶点标记
polygonMapRef.value.renderPolygon(points)
} catch (e) {
console.error('解析或渲染多边形数据失败:', e)
console.error('原始数据:', formData.value.areaPoints)
}
}
}
// 默认要校验的
const formRules = {
areaPoints: [required('请输入车场范围')]
}
// 验证并提交数据
const onSubmit = () => {
formRef.value
.validate()
.then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
console.log('formDataParam',formDataParam)
dataApi
.SubmitForm(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
.catch(() => {})
}
// 抛出函数
defineExpose({
onOpen
})
</script>
思路:
editor.getActiveOverlay()获取图层上的所有多边形
editor.getSelectedList()获取当前选中的多边形
//清空被选中图形
editor.delete(selectedList[0].id)
这里遇到的问题是,绘制出来的多边形坐标和编辑时读取的左边层级不一致,需要出来一下,代码里有标注