Vue3管理后台项目使用高德地图选点

6,149 阅读2分钟

前言

最近在做的管理后台项目中有一个业务场景是添加门店的地址和经纬度,地址可以输入,但经纬度这样处理却不合适,最好是让用户在地图上搜索或者直接点击获取点的经纬度等详细信息。因为我们的app中使用了高德地图,所以管理后台我也选用高德地图来实现上面的业务需求,下面来看一下具体的使用流程吧。

获取地图Key

  • 登录高德开放平台
  • 创建应用,添加Key,选择Web端(JS API),生成Key和安全密钥

引入地图 JSAPI

项目中使用了官方推荐的 JSAPI Loader 来加载地图

  • 安装官方 npm 包 @amap/amap-jsapi-loader
  • 配置安全密钥(不安全的方式),其它配置方式在这里
<script setup>
import AMapLoader from '@amap/amap-jsapi-loader';
window._AMapSecurityConfig = {
  securityJsCode: '你申请的安全密钥',
};
</script>

初始化地图

  1. 创建一个id为mapContainer的div元素
  2. 调用initMap方法初始化相关地图插件
<script setup>
const map = shallowRef(null);
let AMapObj;
function initMap() {
   AMapLoader.load({
     key: '你申请的Key', 
     version: '2.0',
   }).then(AMap => {
        AMapObj = AMap;
        map.value = new AMap.Map('mapContainer');
      })
}
</script>

地图选点

项目中提供搜索选点和直接点击地图选点两种方法

  • 搜索选点:使用 element-plus 的 autocomplete 组件结合地图搜索服务实现下拉选择地点
  • 点击选点:借助地图点击事件获取地点的经纬度信息,然后使用地图逆地理编码api解析出地址信息 选择地点之后同步绘制 marker 标记,同时将 marker 移动到地图中心点并设置缩放比例

组件化使用

为了方便一整套逻辑的复用,我将以上流程全部封装到一个组件中,通过 v-model 绑定所选地点的详细信息,方便选择地点之后将信息同步到父组件。

下面贴出组件全部的代码

<template>
<div class="map-wrapper">
    <div id="mapcontainer"></div>
    <div class="search-box">
        <el-autocomplete
            v-model="keyword"
            :fetch-suggestions="handleSearch"
            :trigger-on-focus="false"
            clearable
            placeholder="输入城市+关键字搜索"
            @select="handleSelect"
            style="width: 300px"
        />
        <el-input
            v-model="location.longitude"
            placeholder="点击地图选择经度"
            maxlength="15"
            readonly
            style="width: 150px; margin: 0 5px"
        ></el-input>
        <el-input
            v-model="location.latitude"
            placeholder="点击地图选择纬度"
            maxlength="15"
            readonly
            style="width: 150px"
        ></el-input>
    </div>
</div>
</template>

<script setup>
import AMapLoader from '@amap/amap-jsapi-loader';
window._AMapSecurityConfig = {
    securityJsCode: '你申请的安全密钥',
};
const props = defineProps({
    modelValue: {
        type: Object,
        default() {
            return {};
        },
    },
});
const emit = defineEmits(['update:modelValue']);
const map = shallowRef(null);
// 地点
const location = computed({
    get() {
       return props.modelValue;
    },
    set(val) {
       emit('update:modelValue', val);
    },
});
watch(location, (val) => {
    if (val.longitude && val.latitude) {
        drawMarker();
    }
  }
);
const keyword = ref('');
let placeSearch, AMapObj, marker, geocoder;
function initMap() {
    AMapLoader.load({
    key: '', // 申请好的Web端Key,首次调用 load 时必填
    version: '2.0'
    }).then(AMap => {
        AMapObj = AMap;
        map.value = new AMap.Map('mapcontainer');
        // 添加点击事件
        map.value.on('click', onMapClick);
        if (location.value.longitude) {
          drawMarker();
        }
        AMap.plugin(
            ['AMap.ToolBar','AMap.Scale','AMap.Geolocation','AMap.PlaceSearch', 'AMap.Geocoder'],
            () => {
            // 缩放条
            const toolbar = new AMap.ToolBar();
            // 比例尺
            const scale = new AMap.Scale();
            // 定位
            const geolocation = new AMap.Geolocation({
            enableHighAccuracy: true, //是否使用高精度定位,默认:true
            timeout: 10000, //超过10秒后停止定位,默认:5s
            position: 'RT', //定位按钮的停靠位置
            buttonOffset: new AMap.Pixel(10, 20), //定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
            zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点
        });
        geocoder = new AMap.Geocoder({
            city: '全国',
        });
        map.value.addControl(geolocation);
        map.value.addControl(toolbar);
        map.value.addControl(scale);
        placeSearch = new AMap.PlaceSearch({
            map: map.value,
            city: '',
            pageSize: 30, // 单页显示结果条数
            pageIndex: 1, // 页码
            citylimit: false, // 是否强制限制在设置的城市内搜索
            autoFitView: true,
        });
      }
    );
})
}
onMounted(() => {
    initMap();
});
// 搜索地图
function handleSearch(queryString, cb) {
    placeSearch.search(queryString, (status, result) => {
        if (result && typeof result === 'object' && result.poiList) {
            const list = result.poiList.pois;
            list.forEach(item => {
                item.value = item.name;
                item.label = item.name;
            });
            cb(list);
        } else {cb([])}
    });
}
// 点击地图
function onMapClick(e) {
    const { lng, lat } = e.lnglat;
    // 逆地理编码
    geocoder.getAddress([lng, lat], (status, result) => {
        if (status === 'complete' && result.info === 'OK') {
            const { addressComponent, formattedAddress } = result.regeocode;
            let { city, province, district } = addressComponent;
            if (!city) {
                // 直辖市
                city = province;
            }
            location.value = {
                longitude: lng,
                latitude: lat,
                address: formattedAddress,
                zone: [province, city, district],
            };
        }
    });
}
// 点击搜索项
function handleSelect(item) {
    const { pname, cityname, adname, address, name } = item;
    const { lng, lat } = item.location;
    location.value = {
        longitude: lng,
        latitude: lat,
        address,
        zone: [pname, cityname, adname],
        name,
    };
    map.value.setZoomAndCenter(16, [lng, lat]);
}
// 绘制地点marker
function drawMarker(val) {
    const { longitude, latitude } = location.value || val;
    if (marker) {
       marker.setMap(null);
    }
    marker = new AMapObj.Marker({
        position: new AMapObj.LngLat(longitude, latitude),
        anchor: 'bottom-center',
    });
    map.value.add(marker);
    map.value.setZoomAndCenter(16, [longitude, latitude]);
}
</script>

<style lang="scss" scoped>
.map-wrapper {
    position: relative;
    width: 100%;
    height: 400px;
    #mapcontainer {
        width: 100%;
        height: 100%;
    }
    .search-box {
        position: absolute;
        top: 10px;
        left: 10px;
        z-index: 1;
        display: flex;
        align-items: center;
    }
}
</style>

拓展

如果系统适配了暗黑模式,可以通过监听当前暗黑模式状态,来动态切换地图浅色主题和深色主题,从而实现地图暗黑模式的适配,这就留给大家自行探索了。

本文完结,感谢您的阅读,欢迎评论指正。