接到一个需求,需要实现在地图上标记一些打卡范围点,需要能够编辑范围的大小以及坐标,开始对这个需求有点蒙,去找了高德地图的各种示例才有了思路,最后开干。
一、准备
1.申请 key 和安全密钥,点击 高德操作文档。
2.安装所需包
npm i @amap/amap-jsapi-loader --save
3.高德使用示例及文档链接
https://lbs.amap.com/api/javascript-api-v2/guide/abc/amap-vue
二、画圆(添加圆标记)
1.JS API 的加载和地图初始化
使用 JS API Loader 来加载,引入在控制台申请的 key 和安全密钥
<template>
<div id="mapContainer"></div>
</template>
<script setup lang="ts">
let map = null //地图容器
const polyEditor = ref(null) //当前编辑的矢量图
const polygonPaths = ref([]) //编辑后的多边形对象
const polygonList = ref([]) //绘制的矢量图数组
const inspectionRouteList = ref([]);//巡查点数组,从后端获取
const initMap = () => {//初始化地图
window._AMapSecurityConfig = {
securityJsCode: '你申请的应用密钥',
}
AMapLoader.load({
key: "你申请的应用Key", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.CircleEditor"], //需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
}).then((AMap) => {
map = new AMap.Map("mapContainer", {
// 设置地图容器id
viewMode: "3D", // 是否为3D地图模式
zoom: 17, // 初始化地图级别
center: [105.31082153320312, 27.29510506985128],//初始化地图中心点位置 props.coordinate
});
initPatroMap()//添加圆标记的方法
}).catch((e) => {
console.log(e);
});
}
//页面渲染完成调用初始化
onMounted(() => {
initMap()
});
</script>
2.添加圆标记
const initPatroMap = () => {//添加圆标记
polyEditor.value?.close()//先把当前编辑的对象关闭
map.clearMap()
polyEditor.value = null
polygonList.value = []
inspectionRouteList.value.forEach((item, index) => {
var polygon = null
polygon = new AMap.Circle({
center: item.coordinate.split(","),
radius: item.radius ? item.radius : 50, //半径
borderWeight: 3,
strokeColor: '#1791fc',
strokeOpacity: 1,
strokeWeight: 1,
fillOpacity: 0.4,
strokeStyle: 'dashed',
strokeDasharray: [10, 10],
// 线样式还支持 'dashed'
fillColor: '#1791fc',
zIndex: 50,
visible: true
})
polygon.on("click", () => {//注册圆的点击事件,点击的时候显示编辑
polyEditor.value?.close()
editPoly(index)
})
polygonList.value.push(polygon)
})
map.add(polygonList.value);
}
三、编辑圆
// 选择某一行的巡查点
const handleSelectPatro = (row) => {
var i = row.index
polyEditor.value?.close()
editPoly(i)
}
// 开始编辑某个圆
const editPoly = (i) => {
let patroItem = inspectionRouteList.value[i]
let opts = {
movePoint: {//编辑圆时圆心显示的内容
draggable: true,//是否可拖动
label: {
content: patroItem.name,
offset: new AMap.Pixel(0, 0),
direction: 'right'
}
},
// resizePoint:{draggable: true}
}
if (polygonList.value.length > 0) {
polyEditor.value = new AMap.CircleEditor(map, polygonList.value[i], opts)
} else {
polyEditor.value = new AMap.CircleEditor(map)
}
polyEditor.value.open()
//关闭多边形编辑polygonEditor.close()触发该方法;
polyEditor.value.on('adjust', async (event) => {
inspectionRouteList.value[i].radius = event.radius
inspectionRouteList.value[i].coordinate = event.lnglat.lng + "," + event.lnglat.lat
console.log('inspection' + i, inspectionRouteList.value[i]);
// 调用接口修改
await updateInspectionRoute(inspectionRouteList.value[i])
.catch(async (err) => {
await getList()
initPatroMap()
})
.finally(() => buttonLoading.value = false);
})
// 移动触发事件
polyEditor.value.on('move', async (event) => {
// inspectionRouteList.value[i].radius=event.radius
inspectionRouteList.value[i].coordinate = event.lnglat.lng + "," + event.lnglat.lat
// 调用接口修改
await updateInspectionRoute(inspectionRouteList.value[i])
.catch(async (err) => {
await getList()
initPatroMap()
})
.finally(() => buttonLoading.value = false);
})
}
四、完整示例
<template>
<div id="mapContainer"></div>
<div class="search" v-show="showSearch">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form-item label="巡逻点名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入巡逻点名称" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="NFC编码" prop="code">
<el-input v-model="queryParams.code" placeholder="请输入NFC编码" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb2">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd"
v-auth="['campus:inspectionRoute:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
v-auth="['campus:inspectionRoute:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
v-auth="['campus:inspectionRoute:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport"
v-auth="['campus:inspectionRoute:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Check" @click="polyEditor?.close()"
v-auth="['campus:inspectionRoute:edit']">完成修改</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="inspectionRouteList" @selection-change="handleSelectionChange"
:row-class-name="tableRowClassName">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="巡逻点名称" align="center" prop="name" />
<el-table-column label="中心点坐标" align="center" prop="coordinate" />
<el-table-column label="半径(米)" align="center" prop="radius" />
<el-table-column label="NFC编码" align="center" prop="code" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" @click="handleUpdate(scope.row)"
v-auth="['campus:inspectionRoute:edit']">修改信息</el-button>
<el-tooltip effect="dark" content="拖动白点或圆心进行修改" placement="top-start">
<el-button size="small" type="primary" @click="handleSelectPatro(scope.row)"
v-auth="['campus:inspectionRoute:edit']">修改范围</el-button>
</el-tooltip>
<el-button size="small" type="danger" @click="handleDelete(scope.row)"
v-auth="['campus:inspectionRoute:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</el-card>
<!-- 点击地图的弹出层 -->
<el-dialog title="获取巡更点经纬度" v-model="mapShow" width="50%" @close="false" center destroy-on-close>
<AddPatrolMap @mapClick="mapClick" />
</el-dialog>
<!-- 添加或修改巡更点对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="inspectionRouteFormRef" :model="form" :rules="rules" label-width="auto">
<el-form-item label="巡逻点名称" prop="name">
<el-input v-model="form.name" placeholder="请输入巡逻点名称" />
</el-form-item>
<!-- <el-form-item label="坐标经纬度" prop="coordinate">
<template #default="scope">
<div>
<el-input v-model="form.coordinate" placeholder="请输入坐标经纬度" />
<el-button @click="mapShow = true">打开地图获取</el-button>
</div>
</template>
</el-form-item> -->
<el-form-item label="NFC编码" prop="code">
<el-input v-model="form.code" placeholder="请输入NFC编码" />
</el-form-item>
<!-- <el-form-item label="二维码图片地址" prop="qrCode">
<image-upload v-model="form.qrCode" />
</el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="InspectionRoute" lang="ts">
import {
addInspectionRoute,
getInspectionRoute,
listInspectionRoute,
updateInspectionRoute,
delInspectionRoute
} from '@/api/campus/inspectionRoute';
import AddPatrolMap from "@/components/Map/addPatrolMap.vue";
import { download as fileDownload } from '@/api/index'
import AMapLoader from '@amap/amap-jsapi-loader';
let map = null //地图容器
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const polyEditor = ref(null) //当前编辑的矢量图
const polygonPaths = ref([]) //编辑后的多边形对象
const polygonList = ref([]) //绘制的矢量图数组
const inspectionRouteList = ref(//巡查点数组,从后端获取
[]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const coordinate = ref('');
const mapShow = ref(false);
const queryFormRef = ref();
const inspectionRouteFormRef = ref();
const dialog = reactive({
visible: false,
title: ''
});
const initFormData = {
id: undefined,
name: undefined,
coordinate: "105.308322,27.293105",//新增时的默认坐标
code: undefined,
qrCode: undefined,
radius: 50
}
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined,
coordinate: undefined,
code: undefined,
qrCode: undefined,
params: {
}
},
rules: {
id: [
{ required: true, message: "主键不能为空", trigger: "blur" }
],
name: [
{ required: true, message: "巡逻点名称不能为空", trigger: "blur" }
],
coordinate: [
{ required: true, message: "坐标经纬度不能为空", trigger: "blur" }
],
code: [
{ required: true, message: "NFC编码不能为空", trigger: "blur" }
]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询巡更点列表 */
const getList = async () => {
loading.value = true;
const res = await listInspectionRoute(queryParams.value);
inspectionRouteList.value = res.rows;
total.value = res.total;
loading.value = false;
}
const mapClick = (e) => {
form.value.coordinate = e;
mapShow.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
inspectionRouteFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection) => {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
dialog.visible = true;
dialog.title = "添加巡更点";
nextTick(() => {
reset();
});
}
/** 修改按钮操作 */
const handleUpdate = (row) => {
loading.value = true
dialog.visible = true;
dialog.title = "修改巡更点";
nextTick(async () => {
reset();
const _id = row?.id || ids.value[0]
const res = await getInspectionRoute(_id);
loading.value = false;
Object.assign(form.value, res.data);
});
}
/** 提交按钮 */
const submitForm = () => {
inspectionRouteFormRef.value?.validate(async (valid : boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateInspectionRoute(form.value).finally(() => buttonLoading.value = false);
} else {
await addInspectionRoute(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
initPatroMap()
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除巡更点"' + row?.name + '"?').finally(() => loading.value = false);
await delInspectionRoute(_ids);
proxy?.$modal.msgSuccess("删除成功");
await getList();
initPatroMap()
}
/** 导出按钮操作 */
const handleExport = () => {
fileDownload('campus/inspectionRoute/export', {
...queryParams.value
}, `inspectionRoute_${new Date().getTime()}.xlsx`)
}
const tableRowClassName = ({ row, rowIndex }) => {
row.index = rowIndex;
}
// 选择某一行的巡查点
const handleSelectPatro = (row) => {
var i = row.index
polyEditor.value?.close()
editPoly(i)
}
// 开始编辑某个圆
const editPoly = (i) => {
let patroItem = inspectionRouteList.value[i]
let opts = {
movePoint: {
draggable: true,
label: {
content: patroItem.name,
offset: new AMap.Pixel(0, 0),
direction: 'right'
}
},
// resizePoint:{draggable: true}
}
if (polygonList.value.length > 0) {
polyEditor.value = new AMap.CircleEditor(map, polygonList.value[i], opts)
} else {
polyEditor.value = new AMap.CircleEditor(map)
}
polyEditor.value.open()
//关闭多边形编辑polygonEditor.close()触发该方法;
polyEditor.value.on('adjust', async (event) => {
inspectionRouteList.value[i].radius = event.radius
inspectionRouteList.value[i].coordinate = event.lnglat.lng + "," + event.lnglat.lat
console.log('inspection' + i, inspectionRouteList.value[i]);
// 调用接口修改
await updateInspectionRoute(inspectionRouteList.value[i])
.catch(async (err) => {
await getList()
initPatroMap()
})
.finally(() => buttonLoading.value = false);
})
// 移动触发事件
polyEditor.value.on('move', async (event) => {
// inspectionRouteList.value[i].radius=event.radius
inspectionRouteList.value[i].coordinate = event.lnglat.lng + "," + event.lnglat.lat
// 调用接口修改
await updateInspectionRoute(inspectionRouteList.value[i])
.catch(async (err) => {
await getList()
initPatroMap()
})
.finally(() => buttonLoading.value = false);
})
}
const initPatroMap = () => {
polyEditor.value?.close()
map.clearMap()
polyEditor.value = null
polygonList.value = []
inspectionRouteList.value.forEach((item, index) => {
var polygon = null
polygon = new AMap.Circle({
center: item.coordinate.split(","),
radius: item.radius ? item.radius : 50, //半径
borderWeight: 3,
strokeColor: '#1791fc',
strokeOpacity: 1,
strokeWeight: 1,
fillOpacity: 0.4,
strokeStyle: 'dashed',
strokeDasharray: [10, 10],
// 线样式还支持 'dashed'
fillColor: '#1791fc',
zIndex: 50,
visible: true
})
polygon.on("click", () => {
polyEditor.value?.close()
editPoly(index)
})
polygonList.value.push(polygon)
})
map.add(polygonList.value);
}
const initMap = () => {
window._AMapSecurityConfig = {
securityJsCode: 'b9a0a661b101316ea7aea94f299f9e78',
}
AMapLoader.load({
key: "190c5081f604474144a2b763642f9b37", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.CircleEditor"], //需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
}).then((AMap) => {
map = new AMap.Map("mapContainer", {
// 设置地图容器id
viewMode: "3D", // 是否为3D地图模式
zoom: 17, // 初始化地图级别
center: [105.31082153320312, 27.29510506985128],//初始化地图中心点位置 props.coordinate
});
initPatroMap()
}).catch((e) => {
console.log(e);
});
}
onMounted(async () => {
await getList();
initMap()
});
onUnmounted(() => {
map?.destroy();
});
</script>
<style lang='scss'>
#mapContainer {
width: 100%;
height: 600px;
}
</style>
效果
的标记和编辑,没精力搞了,靠大家去完善了。