vue3+element-plus+typeScript+高德地图实现起始地点标柱,外加途经点标柱,全面更新地图覆盖物
·
地图实例全局唯一化,在上一篇文章中,有需要的可自行查看
先上图!!!



要实现以上功能,请看下面的代码,这个是一个map的子组件,直接在父组件引用就行,里面涉及到的图片可以自行去网上下载,很多的
<script setup lang="ts">
import {FormInstance} from "element-plus";
import StartImg from "@/static/images/start_icon.png"
import EndImg from "@/static/images/end_icon.png"
import WayPointImg from "@/static/images/wayPoint.png"
import {AddLocation, CircleCloseFilled} from "@element-plus/icons-vue";
/**
* 线路规划
*/
interface MapApiKey {
lng: number;
lat: number;
}
interface AddFormVo {
lineName:string,
startAddr:string,
startAddrText:string,
startAddrCode:any[],
endAddr:string,
endAddrText:string,
endAddrCode:any[],
passAreaCode:string,
lineWight:string,
lineRange:string,
lineColor:string,
}
const props = defineProps({
dialogVisible: {
type: Boolean,
default: false
},
openType:{
type:String,
default:'add'
},
detailInfo:{
type:Object,
default:() => {
return {
startAddrCode:[],
endAddrCode:[],
passAreaCode:'',
lineColor:'',
}
}
}
})
const emit = defineEmits(['update:dialogVisible','success'])
const dialogVisible = computed({
get: () => props.dialogVisible,
set: (val) => emit('update:dialogVisible', val)
})
const handleClose = (done) => {
destroyMap();
emit('update:dialogVisible', false)
done();
}
const mapRef = ref() // 对应地图渲染的 ref 元素 <div ref="mapRef" />
const markerData = ref<any[]>([]);
const aMapPromise = inject('aMap') as Promise<unknown>;
const map = ref();
const form = reactive<MapApiKey>({
lng: 116.401889,
lat: 39.923154,
})
const waypointsArr = ref<any[]>([]);
const addFormRef = ref<FormInstance>();
const addForm = reactive<AddFormVo>({
lineName:'',
startAddr:'',
startAddrText:'',
startAddrCode:[],
endAddr:'',
endAddrText:'',
endAddrCode:[],
passAreaCode:'',
lineWight:'',
lineRange:'',
lineColor:'#578bff',
})
const addFormRules = ref({
lineName:[
{ required: true, message: '请输入线路名称', trigger: 'blur' },
],
startAddr:[
{ required: true, message: '请输入起点搜索', trigger: 'change' },
],
endAddr:[
{ required: true, message: '请输入终点搜索', trigger: 'change' },
],
lineColor:[
{ required: true, message: '请选择线路颜色', trigger: 'change' },
]
})
const startLoading = ref(false);
const startAddrOpt = ref<any[]>([]);
const endLoading = ref(false);
const endAddrOpt = ref<any[]>([]);
const mapLoading = ref(false);
watch(() => props.dialogVisible, (val) => {
if(val){
initMapView()
}
})
//创建地图
const initMapView = () => {
mapLoading.value = true;
aMapPromise.then((AMap:any) => {
map.value = new AMap.Map(mapRef.value, {
viewMode: '3D', // 是否为3D地图模式
zoom: 11, // 初始化地图级别
center: [form.lng, form.lat],
resizeEnable: true
});
//开启定位,由于众多浏览器已不再支持非安全域的定位请求,为保位成功率和精度,请升级您的站点到HTTPS。
let geolocation = new AMap.Geolocation({
enableHighAccuracy: true,//是否使用高精度定位,默认:true
timeout: 10000, //超过10秒后停止定位,默认:5s
buttonPosition:'RB', //定位按钮的停靠位置
buttonOffset: new AMap.Pixel(10, 20),//定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点
});
map.value.addControl(geolocation);
mapLoading.value = false;
geolocation.getCurrentPosition(function(status,result){
if(status=='complete'){
console.log(result,'定位成功!');
}else{
console.log(result,'定位失败!');
}
});
//查看详情时,渲染图层
if(props.openType === 'detail'){
addForm.startAddrCode = props.detailInfo.startAddrCode;
addForm.endAddrCode = props.detailInfo.endAddrCode;
addForm.lineColor = props.detailInfo.lineColor;
props.detailInfo.passAreaCode.split(';').forEach((item:any) => {
waypointsArr.value.push({
lng:item.split(',')[0],
lat:item.split(',')[1]
})
})
repeatDrawMap();
}
})
}
//删除地图
const destroyMap = () => {
Object.keys(addForm).forEach(key => {
if(key === 'lineColor'){
addForm[key] = "#578bff";
}else{
addForm[key] = "";
}
//删除动态添加的途经点
if(key.indexOf('wayPoint') > -1){
delete addForm[key];
}
})
startAddrOpt.value = [];
endAddrOpt.value = [];
waypointsArr.value = [];
addFormRef.value?.resetFields();
map.value && map.value.destroy();
}
//重新绘制
const repeatDrawMap = () => {
map.value.clearMap();
markerData.value = [];
if(addForm.startAddrCode.length > 0){
addMarkerFn(addForm.startAddrCode,'start');
}
if(addForm.endAddrCode.length > 0){
addMarkerFn(addForm.endAddrCode,'end');
}
waypointsArr.value.forEach((val,idx) => {
addMarkerFn([val.lng,val.lat],'wayPoint'+idx);
})
if(addForm.startAddrCode.length>0 && addForm.endAddrCode.length>0){
drawLineFn(addForm.startAddrCode,addForm.endAddrCode,waypointsArr.value)
}
}
// 实例化点标记
function addMarkerFn(code:any[],type:string) {
aMapPromise.then((AMap:any) => {
let _icon = null;
let isDrag = false; //标记点是否拖拽
if(type === 'start'){
_icon = StartImg;
}else if(type === 'end'){
_icon = EndImg;
}else if(type.indexOf('wayPoint') > -1){
isDrag = true;
_icon = WayPointImg;
}
const Icon = new AMap.Icon({
size: new AMap.Size(50, 68), //图标尺寸
image: _icon, //Icon 的图像
imageOffset: new AMap.Pixel(-5, 0), //图像相对展示区域的偏移量,适于雪碧图等
imageSize: new AMap.Size(50, 68), //根据所设置的大小拉伸或压缩图片
});
let _maker:any = new AMap.Marker({
icon:Icon,
position: code,
map:map.value,
offset: new AMap.Pixel(-13, -30),
// 设置是否可以拖拽
draggable: isDrag,
raiseOnDrag:isDrag
});
_maker['type2'] = type;
if(isDrag){
_maker.on("dragend",async function (result: any) {
const _type = result.target.type2;
const _data = result.lnglat;
const _ret:any = await getAddrFn([_data.lng, _data.lat]);
waypointsArr.value.forEach((val, idx) => {
let _idx = _type.split('t')[1];
if (Number(_idx) === idx) {
addForm['wayPoint'+idx] = _ret.addr;
val.lng = _data.lng;
val.lat = _data.lat;
}
})
repeatDrawMap();
})
}
map.value.setFitView();
map.value.add(_maker);
markerData.value.push({
type:type,
marker:_maker
})
})
}
//删除标记点
function delMarkerFn(type:string) {
markerData.value.forEach((item,index) => {
if(item.type === type){
map.value.remove(item.marker);
markerData.value.splice(index,1);
}
})
}
//地址转坐标
async function getAddrCodeFn(addr: string, code:string): Promise<{ lng: number; lat: number } | null> {
try {
return new Promise((resolve, reject) => {
aMapPromise.then((AMap:any) => {
const geocoder = new AMap.Geocoder({
city: code // 城市设为北京
});
geocoder.getLocation(addr, (status: string, result: any) => {
if (status === 'complete' && result.geocodes && result.geocodes.length) {
const location = result.geocodes[0].location;
resolve({
lng: parseFloat(location.lng),
lat: parseFloat(location.lat)
});
} else {
ElMessage.error('根据地址查询位置失败');
reject(new Error('根据地址查询位置失败'));
}
});
})
});
} catch (error) {
console.error('AMap加载或地理编码错误:', error);
ElMessage.error('地图服务初始化失败');
return null;
}
}
//坐标转地址
async function getAddrFn(adCode:any[]): Promise<{ addr: string } | null> {
try {
return new Promise((resolve, reject) => {
aMapPromise.then((AMap:any) => {
const geocoder = new AMap.Geocoder({
city: "010", //城市设为北京,默认:“全国”
radius: 1000 //范围,默认:500
});
geocoder.getAddress(adCode, function(status, result) {
if (status === 'complete'&&result.regeocode) {
const address = result.regeocode.formattedAddress;
resolve({
addr:address
})
}else{
ElMessage.error('根据经纬度查询地址失败');
reject(new Error('根据地址查询位置失败'));
}
});
})
});
} catch (error) {
console.error('AMap加载或地理编码错误:', error);
ElMessage.error('地图服务初始化失败');
return null;
}
}
//根据起始坐标画线
function drawLineFn(start:any,end:any, wayPoint?:any[]) {
aMapPromise.then((AMap:any) => {
let wayPointOpt:any[] = [];
//判断是否含有途经点
if(wayPoint && wayPoint.length > 0){
wayPoint.forEach(item => {
wayPointOpt.push(new AMap.LngLat(item.lng, item.lat))
})
}
//构造路线导航类
const driving = new AMap.Driving({
policy: AMap.DrivingPolicy.LEAST_TIME,
ferry: 1, // 是否可以使用轮渡
province: '京', // 车牌省份的汉字缩写
});
// 根据起终点经纬度规划驾车导航路线
driving.search(new AMap.LngLat(start[0], start[1]), new AMap.LngLat(end[0], end[1]),{
waypoints:wayPointOpt
}, function(status:any, result:any) {
if (status === 'complete') {
if (result.routes && result.routes.length) {
// 绘制第一条路线,也可以按需求绘制其它几条路线
let path = parseRouteToPath(result.routes[0]);
addForm.lineRange = String((result.routes[0].distance/1000).toFixed(2));
let routeLine = new AMap.Polyline({
path: path,
isOutline: true,
outlineColor: '#ffeeee',
borderWeight: 2,
strokeWeight: 5,
strokeColor: addForm.lineColor,
strokeOpacity:'1',
lineJoin: 'round'
})
// 调整视野达到最佳显示区域
routeLine.setMap(map.value)
map.value.setFitView([ markerData.value[0].marker, markerData.value[1].marker, routeLine ])
}
} else {
console.log('获取驾车数据失败:' + result)
}
});
})
}
// 解析DrivingRoute对象,构造成AMap.Polyline的path参数需要的格式
function parseRouteToPath(route:any) {
let path:any[] = []
for (let i = 0, l = route.steps.length; i < l; i++) {
let step = route.steps[i]
for (let j = 0, n = step.path.length; j < n; j++) {
path.push(step.path[j])
}
}
return path
}
//起点搜索事件
const handleStartAddrSearch = (val:string) => {
if (val) {
startLoading.value = true;
aMapPromise.then((AMap:any) => {
// 实例化Autocomplete
let autoOptions = {
city: '全国'
}
let autoComplete = new AMap.Autocomplete(autoOptions);
autoComplete.search(val, function(status:any, result:any) {
// 搜索成功时,result即是对应的匹配数据
if(status === 'complete'){
startLoading.value = false;
startAddrOpt.value = result.tips.map((item:any) => {
return {
value:item.id || String(Math.random()*100),
label:item.name,
adCode:item.adcode,
}
})
}
})
})
}else{
startAddrOpt.value = [];
}
}
//起点选中事件
const handleStartAddrChange = async (val:any) => {
let _addr:any = {};
startAddrOpt.value.forEach(item => {
if(item.value === val){
_addr = item.label;
_addr = {addr:item.label,code:item.adCode}
addForm.startAddrText = item.label;
}
})
const result = await getAddrCodeFn(_addr.addr,_addr.code);
if (result) {
addForm.startAddrCode = [result.lng,result.lat];
if(addForm.endAddrCode.length > 0){
repeatDrawMap()
}else{
delMarkerFn('start');
addMarkerFn([result.lng,result.lat],'start');
}
}
}
//途经点添加事件
const handleWayPointClick = () => {
if(addForm.startAddrCode.length === 0 || addForm.endAddrCode.length === 0){
ElMessage.error("请先选择起点和终点!");
return false;
}
addForm['wayPoint'+waypointsArr.value.length] = "";
let _center = map.value.getCenter()
waypointsArr.value.push({
lng:_center.lng,
lat:_center.lat,
});
}
const handleAddPoint = async (idx: any) => {
const _ret:any = await getAddrFn([waypointsArr.value[idx].lng, waypointsArr.value[idx].lat]);
addForm['wayPoint'+idx] = _ret.addr;
repeatDrawMap()
}
const handleRemoveWayPoint = (idx:any) => {
waypointsArr.value.forEach((item,index) => {
if(index === idx) {
waypointsArr.value.splice(index,1);
}
})
repeatDrawMap()
}
//终点搜索事件
const handleEndAddrSearch = (val:string) => {
if (val) {
endLoading.value = true;
aMapPromise.then((AMap:any) => {
// 实例化Autocomplete
let autoOptions = {
city: '全国'
}
let autoComplete = new AMap.Autocomplete(autoOptions);
autoComplete.search(val, function(status:any, result:any) {
// 搜索成功时,result即是对应的匹配数据
if(status === 'complete'){
endLoading.value = false;
endAddrOpt.value = result.tips.map((item:any) => {
return {
value:item.id || String(Math.random()*100),
label:item.name,
adCode:item.adcode,
}
})
}
})
})
}else{
endAddrOpt.value = [];
}
}
//终点选中事件
const handleEndAddrChange = async (val:any) => {
let _addr:any = {};
endAddrOpt.value.forEach(item => {
if(item.value === val){
_addr = item.label;
_addr = {addr:item.label,code:item.adCode};
addForm.endAddrText = item.label;
}
})
const result = await getAddrCodeFn(_addr.addr,_addr.code);
if (result) {
addForm.endAddrCode = [result.lng,result.lat];
if(addForm.startAddrCode.length > 0){
repeatDrawMap()
}else{
delMarkerFn('end');
addMarkerFn([result.lng,result.lat],'end');
}
}
}
//颜色选中事件
const handleLineColorChange = (val:string) => {
repeatDrawMap()
}
//保存
const handleSubmit = () => {
addFormRef.value?.validate(valid => {
if(valid){
console.log(addForm,'打印提交的数据');
}
})
}
const handleCancel = () => {
dialogVisible.value = false;
destroyMap();
}
</script>
<template>
<el-dialog
v-model="dialogVisible"
:title="openType === 'add'?'新增线路':'查看线路'"
width="80%"
top="5vh"
:before-close="handleClose"
>
<div class="map_wrap" v-loading="mapLoading" element-loading-text="加载中...">
<div class="map_search" v-show="openType === 'add'">
<el-form
ref="addFormRef"
:model="addForm"
:rules="addFormRules"
label-width="auto"
>
<el-form-item label="线路名称" prop="lineName">
<el-input v-model="addForm.lineName" placeholder="请输入线路名称" />
</el-form-item>
<el-form-item label="起点" prop="startAddr">
<el-select
v-model="addForm.startAddr"
filterable
remote
reserve-keyword
placeholder="请输入起点搜索"
:remote-method="handleStartAddrSearch"
@change="handleStartAddrChange"
:loading="startLoading"
style="width: 80%"
>
<el-option
v-for="item in startAddrOpt"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="way_point" @click="handleWayPointClick">途</div>
</el-form-item>
<el-form-item v-for="(item,index) in waypointsArr" :key="index" :label="'途经点'+(index+1)" :prop="'wayPoint'+index">
<el-input v-model="addForm['wayPoint'+index]" readonly style="width: 210px" />
<el-button type="primary" style="margin-left: 10px;" @click="handleRemoveWayPoint(index)">
<el-icon><CircleCloseFilled /></el-icon>
</el-button>
<el-button type="primary" @click="handleAddPoint(index)">
<el-icon><AddLocation /></el-icon>
</el-button>
</el-form-item>
<el-form-item label="终点" prop="startAddr">
<el-select
v-model="addForm.endAddr"
filterable
remote
reserve-keyword
placeholder="请输入终点搜索"
:remote-method="handleEndAddrSearch"
@change="handleEndAddrChange"
:loading="endLoading"
style="width: 100%"
>
<el-option
v-for="item in endAddrOpt"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="距离" prop="lineRange">
<el-input v-model="addForm.lineRange" readonly>
<template #append>
<span>公里</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="线路颜色" prop="lineColor">
<el-color-picker v-model="addForm.lineColor" @change="handleLineColorChange" />
</el-form-item>
<div style="display: flex;justify-content: flex-end">
<el-button type="primary" @click="handleSubmit">保存</el-button>
<el-button @click="handleCancel">取消</el-button>
</div>
</el-form>
</div>
<div ref="mapRef" id="container"></div>
</div>
</el-dialog>
</template>
<style scoped lang="scss">
.map_wrap{
width: 100%;
height: calc(100vh - 200px);
background-color: #ffffff;
.map_search{
position: absolute;
top: 80px;
right: 40px;
background-color: #ffffff;
z-index: 2;
box-sizing: border-box;
padding: 10px;
border-radius: 8px;
box-shadow: 4px 4px 4px #e4e4e4;
.way_point{
margin-left: 10px;
box-sizing: border-box;
padding: 0 10px;
background-color: #578bff;
color: #ffffff;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
}
}
#container {
width: 100%;
height: calc(100vh - 200px);
}
}
</style>
更多推荐
所有评论(0)