vue3 高德地图弹窗选址功能
import { defineComponent, h } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import { Input, AutoComplete, Modal, message } from 'ant-design-vue';
// 首先需要引入 Vue3 的 shallowRef 方法(使用 shallowRef 进行非深度监听,因为在 Vue3 所使用的 Proxy 拦截操作会改变 JSAPI 原生对象,所以此处需要区别 Vue2 使用方式对地图对象行非深度监听,否则会出现问题,建议 JSAPI 相关对象采用非响应式的普通对象来存储)。
import { shallowRef } from '@vue/reactivity';
import { debounce } from 'lodash';
import './index.less';
import Service from './service';
export default defineComponent({
name: 'amap',
props: {
visible: {
type: Boolean,
required: true,
},
bindCancel: {
type: Function,
required: true,
},
bindOk: {
type: Function,
required: true,
},
width: {
type: Number,
},
},
components: {
antModal: Modal,
antInput: Input,
antAutoComplete: AutoComplete,
},
data() {
const instance = shallowRef(null);
const map = shallowRef(null);
const marker = shallowRef(null);
const geocoder = shallowRef(null);
const placeSearch = shallowRef(null);
return {
instance,
map,
marker,
geocoder,
placeSearch,
options: [],
value: '',
address: {},
} as any;
},
watch: {
visible(val) {
if (val && !this.instance) {
this.initMap();
}
},
},
methods: {
initMap() {
AMapLoader.load({
key: '******', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '1.4.15', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ['AMap.ToolBar', 'AMap.Scale', 'AMap.PlaceSearch', 'AMap.Geocoder'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
})
.then((AMap) => {
this.instance = AMap;
this.geocoder = new AMap.Geocoder({ extensions: 'all' });
this.placeSearch = new AMap.PlaceSearch({ extensions: 'all', pageSize: 15 });
this.map = new AMap.Map('amap', {
//设置地图容器id
viewMode: '3D', //是否为3D地图模式
zoom: 12, //初始化地图级别
animateEnable: false,
});
const ToolBar = new AMap.ToolBar();
const Scale = new AMap.Scale();
this.map.addControl(ToolBar);
this.map.addControl(Scale);
const marker = new this.instance.Marker({
icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
position: [22, 22],
offset: new this.instance.Pixel(-10, -35),
visible: false,
});
this.marker = marker;
this.map.add(marker);
this.map.on('click', (e) => {
this.setPosition(e.lnglat);
});
})
.catch((e) => {
console.log(e);
});
},
search(str) {
this.placeSearch.search(str, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
this.options = result.poiList.pois.map((ps, idx) => {
const item = (
<div>
<span>
{idx + 1}. {ps.name}
</span>
<div style={{ fontSize: '13px', color: '#999999' }}>
地址:{ps.pname}
{ps.cityname === ps.pname ? undefined : ps.cityname}
{ps.adname}
{ps.address}
</div>
</div>
);
return {
obj: ps,
label: item,
name: ps.name,
value: `${ps.location.lng}-${ps.location.lat}`,
};
});
} else {
this.options = [];
}
//console.log(str, status, result);
});
},
setPosition(posi) {
this.geocoder.getAddress(posi, async (status, result) => {
//console.log(status, result);
if (status === 'complete' && result.info === 'OK') {
const { addressComponent, formattedAddress } = result.regeocode;
const { province, city, district } = addressComponent;
this.address = { province, city, district, detail: formattedAddress };
this.marker.setPosition(posi);
this.marker.show();
const infoWindow = new this.instance.InfoWindow({
anchor: 'bottom-center',
content: `<div style="padding: 10px;">${result.regeocode.formattedAddress}</div>`,
offset: new this.instance.Pixel(0, -38),
});
infoWindow.open(this.map, posi);
this.address = await this.translateAddress(this.address);
//console.log(this.address);
}
});
},
//获取省市区数据源方法,具体看后端接口,可自定义
async getDataSource(params) {
try {
//这一步主要是获取对应省市区的数据源
const res = await Service.getDataSource(params);
return res;
// return (res.records || []).map((r) => {
// return { ...r, label: r.codeName, value: r.code };
// });
} catch (e) {
message.error((e as any).message || '获取数据异常');
return [];
}
},
//根据所选省市区翻译code
async translateAddress(address) {
const { province, city, district, detail } = address || {};
//坑:不判断!city,因为存在直辖市的情况,直辖市的city为空,province===city
if (!province || !district) {
return;
}
const provinceSource = await this.getDataSource({ codeType: 'sheng' });
const provinceCode = (provinceSource.find((ps) => ps.label === province) || {}).value;
const citySource = await this.getDataSource({ codeType: 'shi', sheng: provinceCode });
const cityCode = (citySource.find((ps) => ps.label === (city || province)) || {}).value;
const districtSource = await this.getDataSource({ codeType: 'qu', shi: cityCode });
const districtCode = (districtSource.find((ps) => ps.label === district) || {}).value;
const addressDetail = detail.replace(`${province}${city}${district}`, '');
address = { ...address, provinceCode, cityCode, districtCode, addressDetail };
return address;
},
onCancel() {
this.bindCancel();
},
onOk() {
if (Object.keys(this.address).length === 0) {
message.warn('请选择一个地址');
return;
}
this.bindOk(this.address);
this.onCancel();
},
},
render() {
return (
<antModal
title="电子地图"
visible={this.visible}
onCancel={() => this.onCancel()}
onOk={() => this.onOk()}
style={{ top: '20px' }}
width={this.width || 800}>
<antAutoComplete
allowClear={true}
onSearch={debounce(this.search, 300)}
onChange={(val) => (this.value = val)}
onSelect={async (val, option) => {
this.value = option.name;
const posi = val.split('-');
this.map.setZoomAndCenter(16, posi);
// this.setPosition(posi);
const { pname, cityname, adname, address, name } = option.obj;
this.address = { province: pname, city: cityname, district: adname, detail: address + name };
this.marker.setPosition(posi);
this.marker.show();
const infoWindow = new this.instance.InfoWindow({
anchor: 'bottom-center',
content: `<div style="padding: 10px;">${
pname + (cityname === pname ? '' : cityname) + adname + address + name
}</div>`,
offset: new this.instance.Pixel(0, -38),
});
infoWindow.open(this.map, posi);
this.address = await this.translateAddress(this.address);
console.log(this.address);
}}
value={this.value}
options={this.options}
style={{ width: '100%' }}
/>
<div id="amap"></div>
</antModal>
);
},
});