Vue3地图选点组件
<template>
<div style="width: 100%; height: 500px">
<div class="search-container">
<el-autocomplete
v-model="suggestionKeyWord"
class="search-container__input"
clearable
:fetch-suggestions="searchSuggestions"
placeholder="输入关键字搜索"
@select="onSuggestionChoose"
>
<template #default="{ item }">
<div class="value">{{ item.name }}</div>
<span class="link">{{ item.address }}</span>
</template>
</el-autocomplete>
<el-button type="primary" class="search-container__button" @click="doneMap"> 确定 </el-button>
</div>
<div class="map-body">
<div id="container" class="map-body__left"></div>
<img :class="iconClass" :src="markerSrc" alt="" />
<!-- poi數據 -->
<div class="map-body__right ele-map-picker-poi-list">
<div
v-for="(poi, index) in poiData"
:key="index"
:class="[
'ele-map-picker-poi-item',
{ 'ele-map-picker-poi-item-active': index === chooseIndex },
]"
@click="choose(index)"
>
<el-icon class="ele-map-picker-poi-item-icon el-icon-location-outline"
><Location
/></el-icon>
<!-- <icon-ep-location class="ele-map-picker-poi-item-icon el-icon-location-outline" /> -->
<div class="ele-map-picker-poi-item-title">{{ poi.name }}</div>
<div v-if="poi.address" class="ele-map-picker-poi-item-address">
{{ poi.address }}
</div>
<el-icon v-if="index === chooseIndex" class="ele-map-picker-poi-item-check"
><Check
/></el-icon>
<!-- <icon-park-check-small
v-if="index === chooseIndex"
class="ele-map-picker-poi-item-check"
/> -->
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import AMapLoader from '@amap/amap-jsapi-loader';
import markerSrc from '@/assets/images/location.png';
import type { Poi } from './type';
// const props = defineProps({});
const emit = defineEmits(['done-map']);
// 中心点位置
let location: any = reactive([116.4074, 39.9042]);
// 地图缩放比例
const chooseZoom = 15;
// 搜索关键字
const suggestionKeyWord = ref('');
// 搜索建议列表
let suggestionData = reactive([]);
// 地图实例
let map: any;
// 输入建议实例
let autoComplete = reactive({});
// 选中的建议
let chooseSuggestion = reactive<any>({});
// 地图中心标记点
let centerMarker = reactive({});
// poi检索实例
let placeSearch = reactive({});
// poi检索的数据
const poiData = ref<Poi[]>([]);
// 选中的数据
const chooseIndex = ref<any>(null);
// 是否是点击poi列表移动地图
let isSelMove = false;
// 图标是否显示跳动动画
const showIconAnim = ref(false);
const iconClass = computed(() => {
return ['ele-map-picker-main-icon', { 'ele-map-picker-anim-bounce': showIconAnim.value }];
});
/**
* @description: 初始化地图
* @param {*} local
* @return {*}
*/
const initMap = (local: any) => {
AMapLoader.load({
key: 'xxxxxxxxxxxxx', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ['AMap.Geocoder', 'AMap.PlaceSearch', 'AMap.AutoComplete'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
}).then((AMap) => {
map = new AMap.Map('container', {
zoom: chooseZoom,
center: location,
});
// 输入建议实例
autoComplete = new AMap.AutoComplete({
city: '全国',
});
// marker实例
centerMarker = new AMap.Marker({
icon: new AMap.Icon({
image: markerSrc,
size: new AMap.Size(26, 36.5),
imageSize: new AMap.Size(26, 36.5),
}),
offset: new AMap.Pixel(-13, -36.5),
});
addMarker(location[0], location[1]);
// 获取poi检索实例
placeSearch = new AMap.PlaceSearch({
type: '', // poi检索兴趣点类别
pageSize: 30, // poi检索每页数量
pageIndex: 1,
extensions: 'all',
});
// 地图加载完成事件
map.on('complete', () => {
chooseIndex.value = null;
const center = map.getCenter();
searchNearBy(center.lat, center.lng, true);
});
// 地图移动结束事件
map.on('moveend', () => {
const center = map.getCenter();
addMarker(center.lng, center.lat);
if (isSelMove) {
// poi列表点击的移动
isSelMove = false;
} else {
// 拖动或搜索建议的移动
showIconAnim.value = false;
nextTick(() => {
setTimeout(() => {
showIconAnim.value = true;
}, 0);
});
searchNearBy(center.lat, center.lng);
}
});
});
};
/**
* @description: poi检索
* @param {*} lat
* @param {*} lng
* @param {*} force
* @return {*}
*/
const searchNearBy = (lat: any, lng: any) => {
if (!placeSearch) {
return;
}
// this.poiLoading = true;
placeSearch.searchNearBy('', [lng, lat], 1000, (status: any, result: any) => {
// this.poiLoading = false;
if (status === 'complete') {
const data = result.poiList.pois.filter((p: any) => p.location !== undefined);
if (chooseSuggestion) {
// 如果选中的搜索建议不在poi列表中则添加
if (data.length === 0 || data[0].name !== chooseSuggestion.name) {
data.unshift({ ...chooseSuggestion });
}
chooseSuggestion = null;
} else {
chooseIndex.value = null;
}
poiData.value = data;
// v3.17 标准地址库-地址拼接省市区
poiData.value.forEach((item) => {
item.pname = item.pname || '';
item.cityname = item.cityname || '';
item.adname = item.adname || '';
item.address = item.address || '';
item.address = `${item.pname}${item.cityname}${item.adname}${item.address}`;
});
}
});
};
/**
* @description: poi列表选中
* @param {*} index
* @return {*}
*/
const choose = (index: number) => {
chooseIndex.value = index;
isSelMove = true;
// this.showIconAnim = false;
// nextTick(() => {
// setTimeout(() => {
// this.showIconAnim = true;
// }, 0);
// });
const point = poiData.value[index].location;
map.setZoomAndCenter(chooseZoom, [point.lng, point.lat]);
};
/**
* @description: 添加marker
* @param {*} lng
* @param {*} lat
* @return {*}
*/
const addMarker = (lng: string, lat: string) => {
// centerMarker.setMap(map);
centerMarker.setPosition([lng, lat]);
map.add(centerMarker);
};
/**
* @description: 获取搜索数据
* @param {*} keywords
* @param {*} callback
* @return {*}
*/
const searchSuggestions = (keywords: string, callback: any) => {
if (!keywords) {
return callback(suggestionData);
}
autoComplete.search(keywords, (status: any, result: any) => {
if (status === 'complete') {
suggestionData = result.tips.filter((item) => item.location);
suggestionData.forEach((item: any) => {
item.address = item.address || '';
item.district = item.district || '';
item.address = `${item.district}${item.address}`;
});
callback(suggestionData);
}
});
};
/**
* @description: 点击选择
* @param {*} item
* @return {*}
*/
const onSuggestionChoose = (item: any) => {
suggestionKeyWord.value = item.name;
chooseSuggestion = item;
chooseIndex.value = 0;
const point = item.location;
if (point) {
map.setZoomAndCenter(chooseZoom, [point.lng, point.lat]);
addMarker(point.lng, point.lat);
}
};
/**
* @description: 确定
* @return {*}
*/
const doneMap = () => {
// 地图中心点
// const center = { ...map.getCenter() };
// getByLatLng({ lat: center.lat, lng: center.lng }).then((res) => {
// // console.log('接口获取的值', res);
// if (res.result) {
// location = {
// country: res.result?.country?.i18nName,
// province: res.result?.province?.i18nName || '',
// city: res.result?.city?.i18nName,
// district: res.result?.district?.i18nName,
// address: res.result.raw?.formattedAddress,
// lat: center.lat,
// lng: center.lng,
// };
// }
// // 选中则取高德地图返回的address
// if (chooseIndex.value || chooseIndex.value === 0) {
// location.address = poiData.value[chooseIndex.value].address || '';
// }
// suggestionKeyWord.value = '';
// emit('done-map', location);
// });
// TODO 由于数据规范性,需获取经纬度后重新请求三级地址
if (chooseIndex.value || chooseIndex.value === 0) {
location.address = poiData.value[chooseIndex.value].address || '';
}
console.log('选中的地址', location);
suggestionKeyWord.value = '';
emit('done-map', location);
};
onMounted(() => {
setTimeout(() => {
initMap(location);
}, 200);
});
</script>
<style scoped lang="scss">
#container {
margin: 0;
padding: 0;
width: 100%;
height: calc(100% - 50px);
}
.search-container {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
:deep(.el-autocomplete) {
width: 80%;
}
}
.map-body {
display: flex;
height: 450px;
&__left {
width: 70% !important;
height: 100% !important;
}
&__right {
flex: 1;
}
}
/* 地图图标跳动动画 */
.ele-map-picker-anim-bounce {
animation: elePickerAnimBounce 500ms;
animation-direction: alternate;
}
@keyframes elePickerAnimBounce {
0%,
60%,
75%,
90%,
to {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0%,
to {
transform: translate3d(0, 0, 0);
}
25% {
transform: translate3d(0, -10px, 0);
}
50% {
transform: translate3d(0, -20px, 0);
}
75% {
transform: translate3d(0, -10px, 0);
}
}
.ele-map-picker-main-icon {
width: 26px;
position: absolute;
left: 50%;
bottom: 50%;
margin-left: -13px;
}
/* poi列表 */
.ele-map-picker-poi-list {
overflow: auto;
width: 300px;
}
.ele-map-picker-poi-item {
position: relative;
padding: 8px 30px 8px 44px;
border-bottom: 1px solid hsl(0deg 0% 60% / 15%);
cursor: pointer;
}
.ele-map-picker-poi-item:hover {
background-color: hsl(0deg 0% 60% / 5%);
}
.ele-map-picker-poi-item-icon {
position: absolute;
top: 50%;
left: 14px;
transform: translateY(-50%);
font-size: 20px;
opacity: 0.4;
}
.ele-map-picker-poi-item-title {
font-size: 14px;
}
.ele-map-picker-poi-item-address {
margin-top: 2px;
font-size: 12px;
opacity: 0.6;
}
.ele-map-picker-poi-item .ele-map-picker-poi-item-check {
position: absolute;
top: 50%;
right: 7px;
display: none;
font-size: 16px;
color: #3b74ff;
transform: translateY(-50%);
}
.ele-map-picker-poi-item-active .ele-map-picker-poi-item-check {
display: block;
}
</style>
<style lang="scss">
.map-body {
.amap-icon {
display: none;
}
}
</style>