Vue3对接高德地图POI搜索
1.第一步
申请高德地图API,参考视频
准备-如何申请一个免费的高德地图key?_哔哩哔哩_bilibili
2.第二步
创建vue3项目
【带小白做毕设】01. 前端Vue3 框架的快速搭建以及项目工程的讲解_哔哩哔哩_bilibili
3.第三步
在index.html中添加高德地图API密钥及服务
<!--高德地图API密钥-用于渲染旅游线路的地图--><script>window._AMapSecurityConfig = {securityJsCode: "你的密钥",};</script><script src="https://webapi.amap.com/maps?v=2.0&key=fe04d3154c17c9f3439d51118fbec8ae&plugin=AMap.Geocoder,AMap.PlaceSearch"></script>
4.第四步
编写前端代码,该功能采用高德地图API,运用了输入提示和POI搜索插件,在输入地点及查询的关键字时进行提示,用户点击搜索后会对输入框进行非空检查,有内容调用placeSearch.search()方法发起搜索请求,然后对搜索结果进行标记marker,创建信息窗体和分页显示。里面先默认展示几个结果,使页面看起来美观。
<template><!-- 地图容器 --><div id="map_container"><!-- 左侧内容区域 --><div class="left-panel"><!-- 搜索结果列表和分页 --><div id="custom-panel" class="custom-list"><div class="default-header"><h3>热门景点推荐</h3><p>输入城市和关键词开始搜索</p><!-- 搜索输入框 --><div class="search-input"><inputtype="text"id="city_input"v-model="cityInput"placeholder="请先输入地名:"@keyup.enter="executeSearch"><inputtype="text"id="search_input"v-model="searchInput"placeholder="请再输入关键字:"@keyup.enter="executeSearch"><button @click="executeSearch">搜索</button></div></div><!-- 默认内容(未搜索时显示) --><div v-if="!searchExecuted" class="default-content"><div v-for="poi in defaultPlaces" :key="poi.id" class="result-item" @click="setCenterOnDefaultPoi(poi)"><div class="item-content"><div class="item-image"><img :src="poi.photos[0].url" :alt="poi.name" class="single-image" /></div><div class="item-details"><a :href="'https://www.mafengwo.cn/search/q.php?q=' + encodeURIComponent(poi.cityname) + encodeURIComponent(poi.name)" target="_blank"><strong>{{ poi.name }}</strong></a><div class="web"><a :href="`https://${poi.website}`" target="_blank" rel="noopener noreferrer">{{ poi.website}}</a></div><div>地址:{{poi.pname}}{{poi.cityname}}{{poi.adname}}{{ poi.address }}</div><div>类型:{{poi.type}}</div><div>电话:{{ poi.tel || '无电话' }}</div></div></div></div></div><!-- 搜索结果列表 --><div v-for="poi in pois" :key="poi.id" class="result-item" @click="setCenterOnPoi(poi)"><div class="item-content"><!--轮播图样式--><div class="item-image"><el-carouselv-if="poi.photos && poi.photos.length > 0":interval="5000"height="160px"indicator-position="outside"><el-carousel-item v-for="(photo, index) in poi.photos" :key="index"><img :src="photo.url || defaultImage" :alt="poi.name" class="carousel-image" /></el-carousel-item></el-carousel><img v-else :src="defaultImage" :alt="poi.name" class="single-image" /></div><div class="item-details"><a :href="'https://www.mafengwo.cn/search/q.php?q=' + encodeURIComponent(poi.cityname) + encodeURIComponent(poi.name)" target="_blank"><strong>{{ poi.name }}</strong></a><div class="web"><a :href="`https://${poi.website}`" target="_blank" rel="noopener noreferrer">{{ poi.website}}</a></div><div>地址:{{poi.pname}}{{poi.cityname}}{{poi.adname}}{{ poi.address }}</div><div>类型:{{poi.type}}</div><div>电话:{{ poi.tel || '无电话' }}</div></div></div></div><!-- 分页栏 --><div class="pagination" v-if="totalPages > 0 && pois.length > 0"><button @click="changePage(currentPage - 1)" :disabled="currentPage === 1">上一页</button><span>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span><button @click="changePage(currentPage + 1)" :disabled="currentPage === totalPages">下一页</button></div><div v-if="searchExecuted && pois.length === 0" class="no-results">没有找到相关结果</div></div></div><!-- 右侧地图区域 --><div class="right-panel"><!-- 地图显示区域 --><div id="container"></div><!-- 地图控件开关 --><div class='input-card'><div class="input-item"><input type="checkbox" @click="toggleScale($event.target)" checked/> 比例尺</div><div class="input-item"><input type="checkbox" @click="toggleToolBar($event.target)" checked/> 工具条</div><div class="input-item"><input type="checkbox" @click="toggleGeolocation($event.target)" checked/> 定位控件</div></div></div></div>
</template><script lang="ts" setup>
import { onMounted, onUnmounted, ref } from "vue";
import {ElMessage} from "element-plus";// 地图相关变量声明
let map: AMap.Map | null = null;
let markers: AMap.Marker[] = [];
let toolbar: AMap.ToolBar | null = null;
let scale: AMap.Scale | null = null;
let geolocation: AMap.Geolocation | null = null;// 响应式数据
const cityInput = ref('');
const searchInput = ref('');
const pois = ref<any[]>([]);
const currentPage = ref(1);
const totalPages = ref(0);
const pageSize = ref(6);
const searchExecuted = ref(false);
const defaultImage = 'https://img.js.design/assets/compImg/JoR2el00cacd6ed83a3fe7e63b88afd022cbb5.png';
// 在响应式数据部分添加
const defaultPlaces = ref([{id: 'default1',name: '北京故宫',cityname: '北京市',pname: '北京市',adname: '东城区',address: '景山前街4号',type: '名胜古迹',tel: '010-85007421',website: 'www.dpm.org.cn',photos: [{ url: 'https://images.unsplash.com/photo-1547981609-4b6bfe67ca0b?w=400&h=300' }]},{id: 'default2',name: '上海外滩',cityname: '上海市',pname: '上海市',adname: '黄浦区',address: '中山东一路',type: '城市景观',tel: '021-12345678',website: 'www.shanghai.gov.cn',photos: [{ url: 'https://dimg04.c-ctrip.com/images/0106h120008srd15c6500_R_228_10000.jpg' }]},{id: 'default3',name: '广州塔',cityname: '广州市',pname: '广东省',adname: '海珠区',address: '阅江西路222号',type: '地标建筑',tel: '020-89338222',website: 'www.cantontower.com',photos: [{ url: 'https://www.cantontower.com/Uploads/ueditor/image/20190619/6369656758462028578901253.jpg' }]},{id: 'default4',name: '成都大熊猫繁育研究基地',cityname: '成都市',pname: '四川省',adname: '成华区',address: '熊猫大道1375号',type: '动物园',tel: '028-83510033',website: 'www.panda.org.cn',photos: [{ url: 'https://p1-q.mafengwo.net/s9/M00/99/04/wKgBs1fYx3qADOEbAAtEDC3KXNo01.jpeg?imageMogr2%2Fthumbnail%2F%21305x183r%2Fgravity%2FCenter%2Fcrop%2F%21305x183%2Fquality%2F100' }]},{id: 'default5',name: '西安兵马俑',cityname: '西安市',pname: '陕西省',adname: '临潼区',address: '秦陵北路',type: '历史遗址',tel: '029-81399001',website: 'www.bmy.com.cn',photos: [{ url: 'https://sales.mafengwo.net/mfs/s17/M00/89/6A/CoUBXl-7NmWALIABABF73vKZaXI08.jpeg?imageMogr2%2Fthumbnail%2F%21440x260r%2Fgravity%2FCenter%2Fcrop%2F%21440x260%2Fquality%2F100' }]},{id: 'default6',name: '杭州西湖',cityname: '杭州市',pname: '浙江省',adname: '西湖区',address: '西湖风景名胜区',type: '自然景观',tel: '0571-87179617',website: 'westlake.hangzhou.gov.cn',photos: [{ url: 'https://p1-q.mafengwo.net/s13/M00/DA/7D/wKgEaVysAG-AXtKuAASdij17Z_A50.jpeg?imageMogr2%2Fthumbnail%2F%21296x156r%2Fgravity%2FCenter%2Fcrop%2F%21296x156%2Fquality%2F100' }]},
]);// 组件挂载时初始化地图
onMounted(() => {if (window.AMap) {initMap();} else {console.error("AMap未加载");// 可以在这里添加AMap的CDN动态加载逻辑}
});
// 定位到默认POI
const setCenterOnDefaultPoi = (poi: any) => {// 这里可以使用一些默认坐标const defaultCoords: Record<string, [number, number]> = {'default1': [116.397, 39.916], // 北京故宫'default2': [121.490, 31.239], // 上海外滩'default3': [113.324, 23.106], // 广州塔'default4': [104.147, 30.741], // 成都大熊猫基地'default5': [109.277, 34.385], // 西安兵马俑'default6': [120.155, 30.274] // 杭州西湖};if (map && defaultCoords[poi.id]) {map.setCenter(defaultCoords[poi.id]);map.setZoom(16);}
};/*** 初始化地图*/
const initMap = () => {map = new AMap.Map("container", {viewMode: "2D",zoom: 15,center: [116.397428, 39.90923],resizeEnable: true,});AMap.plugin(["AMap.ToolBar","AMap.Scale","AMap.Geolocation","AMap.AutoComplete","AMap.PlaceSearch"], () => {initControls();initSearch();});
};/*** 初始化地图控件*/
const initControls = () => {if (!map) return;toolbar = new AMap.ToolBar({visible: true,position: { bottom: '80px', right: '20px' },});map.addControl(toolbar);scale = new AMap.Scale({visible: true,position: { bottom: '40px', right: '15px' },});map.addControl(scale);geolocation = new AMap.Geolocation({visible: true,enableHighAccuracy: true,timeout: 10000,position: { bottom: '150px', right: '20px' },zoomToAccuracy: true,});map.addControl(geolocation);
};/*** 初始化搜索功能*/
const initSearch = () => {if (!map) return;// 城市输入自动完成const autocityInput = new AMap.AutoComplete({ city: "", input: "city_input" });autocityInput.on("select", (e) => {cityInput.value = e.poi.name;if (searchInput.value) {executeSearch();}});// 关键字输入自动完成const autocomplete = new AMap.AutoComplete({ city: "", input: "search_input" });autocomplete.on("select", (e) => {searchInput.value = e.poi.name;executeSearch();});
};/*** 执行搜索*/
const executeSearch = () => {if (!searchInput.value){ElMessage.error('您还没有输入搜索内容!');return;}searchExecuted.value = true;currentPage.value = 1; // 重置为第一页const placeSearch = new AMap.PlaceSearch({pageSize: pageSize.value,city: cityInput.value,citylimit: true,map: map,autoFitView: true,pageIndex: currentPage.value});placeSearch.search(searchInput.value, (status, result) => {console.log('搜索结果:', { status, result });if (status === 'complete' && result.info === 'OK') {pois.value = result.poiList.pois;totalPages.value = Math.max(1, Math.ceil(result.poiList.count / pageSize.value));clearMarkers();createMarkers(result.poiList.pois);} else {pois.value = [];totalPages.value = 0;}});
};/*** 切换页码*/
const changePage = (page: number) => {if (page < 1 || page > totalPages.value) return;currentPage.value = page;const placeSearch = new AMap.PlaceSearch({pageSize: pageSize.value,city: cityInput.value,citylimit: true,map: map,autoFitView: true,pageIndex: currentPage.value});placeSearch.search(searchInput.value, (status, result) => {if (status === 'complete' && result.info === 'OK') {pois.value = result.poiList.pois;totalPages.value = Math.max(1, Math.ceil(result.poiList.count / pageSize.value));clearMarkers();createMarkers(result.poiList.pois);// 滚动到列表顶部const panel = document.getElementById('custom-panel');if (panel) panel.scrollTop = 0;}});
};/*** 创建标记点*/
const createMarkers = (pois: any[]) => {if (!map) return;pois.forEach(poi => {if (!poi.location) return;const marker = new AMap.Marker({position: poi.location,title: poi.name,extData: { poi }});const infoWindow = createInfoWindow(poi);marker.setExtData({ ...marker.getExtData(), infoWindow });marker.setMap(map);markers.push(marker);marker.on('click', () => {map?.clearInfoWindow();infoWindow.open(map!, marker.getPosition()!);});});
};/*** 创建信息窗体*/
const createInfoWindow = (poi: any) => {return new AMap.InfoWindow({content: `<div class="custom-info-window"><h3>${poi.name}</h3><div class="info-content"><p><i class="icon-address"></i>${poi.address || '无地址信息'}</p>${poi.tel ? `<p><i class="icon-tel"></i> ${poi.tel}</p>` : ''}</div></div>`,offset: new AMap.Pixel(0, -30),size: new AMap.Size(250, 'auto'),border: 'none',closeWhenClickMap: true});
};/*** 清除所有标记点*/
const clearMarkers = () => {if (!map) return;markers.forEach(marker => map.remove(marker));markers = [];
};/*** 定位到指定POI*/
const setCenterOnPoi = (poi: any) => {if (!poi.location || !map) return;const { lng, lat } = poi.location;map.setCenter([lng, lat]);map.setZoom(17);const marker = markers.find(m => {const pos = m.getPosition();return pos && pos.lng === lng && pos.lat === lat;});if (marker) {const extData = marker.getExtData();if (extData.infoWindow) {map.clearInfoWindow();extData.infoWindow.open(map, marker.getPosition()!);}}
};/*** 切换控件显示状态*/
const toggleToolBar = (checkbox: EventTarget) => {const input = checkbox as HTMLInputElement;input.checked ? toolbar?.show() : toolbar?.hide();
};const toggleScale = (checkbox: EventTarget) => {const input = checkbox as HTMLInputElement;input.checked ? scale?.show() : scale?.hide();
};const toggleGeolocation = (checkbox: EventTarget) => {const input = checkbox as HTMLInputElement;input.checked ? geolocation?.show() : geolocation?.hide();
};// 组件卸载时清理
onUnmounted(() => {map?.destroy();
});
</script><style scoped>
/* 基础布局 */
#map_container {display: flex;width: 100%;height: 665px;margin: 0;padding: 0;overflow: hidden;
}/* 左侧面板 */
.left-panel {width: 50%;height: 100%;margin-bottom: 100px;background: #f5f5f5;overflow-y: auto;display: flex;flex-direction: column;
}/* 右侧面板 */
.right-panel {flex: 1;position: relative;display: flex;justify-content: center;margin: 30px 30px;align-items: center;background: #eaeaea;
}/* 地图容器 */
#container {width: 100%;height: 100%;z-index: 1;
}/* 搜索框样式 */
.search-input {display: flex;gap: 10px;margin: 20px ;
}.search-input input {padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;flex: 1;
}.search-input button {padding: 8px 16px;background: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;white-space: nowrap;
}.search-input button:hover {background: #45a049;
}/* 默认内容样式 */
.default-content {padding: 15px;
}.default-header {text-align: center;margin-bottom: 20px;padding: 10px;background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);border-radius: 8px;
}.default-header h3 {color: #2c3e50;margin-bottom: 5px;
}.default-header p {color: #7f8c8d;font-size: 14px;
}
/* 搜索结果面板 */
.custom-list {flex: 1;background: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);overflow-y: scroll; /* 修改为scroll确保滚动功能 */display: flex;flex-direction: column;scrollbar-width: none; /* Firefox */-ms-overflow-style: none; /* IE 和 Edge */
}.custom-list::-webkit-scrollbar {display: none; /* Chrome, Safari 和 Opera */
}
/* 单个结果项 */
.result-item {padding: 12px 15px;cursor: pointer;border-bottom: 1px solid #f0f0f0;transition: background 0.5s;
}.result-item:hover {background: #f9f9f9;
}.item-content {display: flex;gap: 12px;
}/* 轮播图样式 */
.item-image {width: 240px;height: 160px;flex-shrink: 0;background: #f5f5f5;border-radius: 4px;overflow: hidden;
}.carousel-image {width: 100%;height: 100%;object-fit: cover;
}.single-image {width: 100%;height: 100%;object-fit: cover;
}/* 调整轮播指示器样式 */
:deep(.el-carousel__indicators) {bottom: -25px;
}:deep(.el-carousel__button) {width: 8px;height: 8px;border-radius: 50%;background-color: #c0c4cc;
}.item-image img {width: 100%;height: 100%;object-fit: cover;
}.item-details {flex: 1;min-width: 0;
}
/*名称链接*/
.item-details a {font-size: 18px;font-weight: 500;color: #1a73e8;text-decoration: none;display: block;margin-bottom: 4px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;
}
.item-content:hover .item-details a{color: #189500;text-decoration: underline;
}
/*网站*/
.item-details .web a {
}
.item-content:hover .item-details .web a{color: #FF9E01;text-decoration: underline;
}.item-details a:hover {text-decoration: underline;
}.item-details div {font-size: 14px;color: #666;line-height: 1.4;margin: 2px 0;
}/* 分页样式 */
.pagination {padding: 15px;background: #f9f9f9;border-top: 1px solid #eee;display: flex;justify-content: center;align-items: center;gap: 15px;flex-wrap: wrap;position: sticky;bottom: 0;
}.pagination button {min-width: 80px;padding: 6px 12px;background: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;transition: background 0.2s;
}.pagination button:hover:not(:disabled) {background: #3d8b40;
}.pagination button:disabled {background: #cccccc;cursor: not-allowed;
}.pagination span {font-size: 14px;color: #666;
}/* 无结果提示 */
.no-results {padding: 20px;text-align: center;color: #666;
}/* 控件开关面板 */
.input-card {position: absolute;top: 20px;right: 10px;z-index: 10;background: white;padding: 10px;border-radius: 4px;box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}.input-item {display: flex;align-items: center;gap: 6px;margin: 8px 0;font-size: 14px;color: #666;
}/* 信息窗口样式 */
:deep(.custom-info-window) {padding: 12px;font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}:deep(.custom-info-window h3) {margin: 0 0 8px 0;font-size: 16px;color: #1a73e8;padding-bottom: 6px;border-bottom: 1px solid #f0f0f0;
}:deep(.custom-info-window .info-content) {font-size: 14px;
}:deep(.custom-info-window p) {margin: 6px 0;display: flex;align-items: center;
}
</style>
效果展示