高德地图poi关键字搜索-vue+ant-design
最近有个需求,需要输入上车点,下车点,然后输入上车点的时候还要在下方显示地图附近的车辆。百度了一波之后,完全莫得头绪,很多代码也都用不了,即便改了之后也不怎么生效。我用的是jeecg-boot.最后静下心,还是老老实实的调用api接口请求数据,完成了这个需求。效果如下:
1.刚点开弹框时:
2.当输入上车地点的时候,提示对应的地址,以及详细地址
3.当选中了地址后,显示地图,并定位到选择的位置,同时显示附近车辆
4.选择下车点,同理,不过下车点不会弹地图,没有地图操作,单纯的一个关键字搜索操作
下面说说具体的实现过程。前提说明,涉及到前后端代码联动。
前端方面:
我就直接丢代码了:首先是我这个弹窗的代码:
<template>
<j-modal
:title="title"
:width="1000"
:visible="visible"
:confirmLoading="confirmLoading"
switchFullscreen
@ok="handleOk"
@cancel="handleCancel"
cancelText="关闭">
<a-spin :spinning="confirmLoading">
<a-form :form="form">
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="上车点">
<a-input placeholder="请输入上车点" @change="getAboard" v-model="aboard" />
<div id="result" class="result">
<div class="amap_lib_placeSearch_list amap-pl-pc" v-for="(item,index) in address"
@click="openMarkerTipById1(index,$event)"
@mouseout="onmouseout_MarkerStyle(index+1,$event)"
:key="index">
<div class="poibox" style="border-bottom: 1px solid #eaeaea">
<div class="amap_lib_placeSearch_poi poibox-icon" :class="index==selectedIndex?\'selected\':\'\'">{{index+1}}</div>
<h3 class="poi-title" >
<span class="poi-name">{{item.name}}</span>
</h3>
<div class="poi-info">
<p class="poi-addr">地址:{{item.pname}}{{item.cityname}}{{item.adname}}{{item.address}}</p>
</div>
<div class="clear"></div>
</div>
</div>
</div>
</a-form-item>
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="下车点">
<a-input placeholder="请输入下车点" @change="getDeBus" v-model="debus" />
<div id="tips" class="result">
<div class="amap_lib_placeSearch_list amap-pl-pc" v-for="(item,index) in deBusAddress"
@click="openMarkerTipById2(index,$event)"
@mouseout="onmouseout_MarkerStyle(index+1,$event)"
:key="index">
<div class="poibox" style="border-bottom: 1px solid #eaeaea">
<div class="amap_lib_placeSearch_poi poibox-icon" :class="index==selectedIndex?\'selected\':\'\'">{{index+1}}</div>
<h3 class="poi-title" >
<span class="poi-name">{{item.name}}</span>
</h3>
<div class="poi-info">
<p class="poi-addr">地址:{{item.pname}}{{item.cityname}}{{item.adname}}{{item.address}}</p>
</div>
<div class="clear"></div>
</div>
</div>
</div>
</a-form-item>
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="乘客手机号">
<a-input placeholder="请输入乘客手机号" v-decorator="[\'farePhone\', {}]" />
</a-form-item>
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="乘客名称">
<a-input placeholder="请输入乘客名称" v-decorator="[\'fareName\', {}]" />
</a-form-item>
<a-form-item
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="用车时间">
<a-date-picker showTime format=\'YYYY-MM-DD HH:mm:ss\' v-decorator="[ \'forwardTime\', {}]" />
</a-form-item>
<div v-if="showMap===1">
<AMapTemplate></AMapTemplate>
</div>
</a-form>
</a-spin>
</j-modal>
</template>
<script>
import { httpAction, getAction } from \'@/api/manage\'
import pick from \'lodash.pick\'
import moment from "moment"
import AMapTemplate from \'@/components/AMap/AMapTemplate\'
import Utils from \'@/components/_util/utils\'
export default {
name: "OrderModal",
components: {
AMapTemplate
},
data () {
return {
title:"操作",
visible: false,
aboard: \'\',
debus: \'\',
showMap: 0,
record: {},
upGnote: \'\',
downGnote: \'\',
address: [],
deBusAddress: [],
selectedIndex: -1,
map:\'\',//保存地址的经纬度
poiArr: [],//左边搜索出来的数组
windowsArr: [],//信息窗口的数组
marker: [],
mapObj: "",//地图对象
model: {},
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
confirmLoading: false,
form: this.$form.createForm(this),
validatorRules:{
},
url: {
add: "/order/order/add",
edit: "/order/order/edit",
},
}
},
created () {
},
methods: {
openMarkerTipById1(pointid, thiss) {
this.aboard = this.address[pointid].name;
this.upGnote = this.address[pointid].location;
localStorage.setItem("aboard", this.upGnote);
Utils.$emit(\'mapAboard\');
this.showMap = 1;
//根据id 打开搜索结果点tip
thiss.currentTarget.style.background = "#CAE1FF";
this.selectedIndex = pointid
this.map = this.marker[pointid]
window.removeEventListener("click", this.demo);
// this.windowsArr[pointid].open(this.mapObj, this.marker[pointid])
window.addEventListener("click", this.demo);
this.address = [];
document.getElementById("result").classList.remove("amap_lib_placeSearch");
},
openMarkerTipById2(pointid, thiss) {
this.debus = this.deBusAddress[pointid].name;
this.downGnote = this.deBusAddress[pointid].location;
//根据id 打开搜索结果点tip
thiss.currentTarget.style.background = "#CAE1FF";
this.selectedIndex = pointid
this.map = this.marker[pointid]
window.removeEventListener("click", this.demo);
// this.windowsArr[pointid].open(this.mapObj, this.marker[pointid])
window.addEventListener("click", this.demo);
this.deBusAddress = [];
document.getElementById("tips").classList.remove("amap_lib_placeSearch");
},
onmouseout_MarkerStyle(pointid, thiss) {
//鼠标移开后点样式恢复
thiss.currentTarget.style.background = ""
},
close () {
this.$emit(\'close\');
this.visible = false;
},
handleOk () {
const that = this;
// 触发表单验证
this.form.validateFields((err, values) => {
if (!err) {
that.confirmLoading = true;
let formData = Object.assign(this.model, values);
formData.aboard = this.aboard;
formData.debus = this.debus;
formData.id = this.record.id;
formData.upGnote = this.upGnote;
formData.downGnote = this.downGnote;
//时间格式化
formData.forwardTime = formData.forwardTime?formData.forwardTime.format(\'YYYY-MM-DD HH:mm:ss\'):null;
console.log(formData)
httpAction(\'/order/order/sendOrder\',formData,\'post\').then((res)=>{
if(res.success){
that.$message.success(res.message);
that.$emit(\'ok\');
}else{
that.$message.warning(res.message);
}
}).finally(() => {
that.confirmLoading = false;
that.close();
})
}
})
},
handleCancel () {
this.close()
},
getAboard() {
if (!this.aboard) {
document.getElementById("result").classList.remove("amap_lib_placeSearch");
}else {
document.getElementById("result").classList.add("amap_lib_placeSearch");
}
let parameter = {keyword: this.aboard}
getAction(\'/order/order/getAddressItem\',parameter, \'get\').then((res)=>{
if(res.success){
this.address = res.result;
}else{
this.$message.warning(res.message);
}
})
},
getDeBus() {
if (!this.debus) {
document.getElementById("tips").classList.remove("amap_lib_placeSearch");
}else {
document.getElementById("tips").classList.add("amap_lib_placeSearch");
}
let parameter = {keyword: this.debus}
getAction(\'/order/order/getAddressItem\',parameter, \'get\').then((res)=>{
if(res.success){
this.deBusAddress = res.result;
}else{
this.$message.warning(res.message);
}
})
}
}
}
</script>
<style lang="less" scoped>
.amap_lib_placeSearch {
width: 100%;
position: relative;
height: 300px;
overflow-y: scroll;
border-right: 1px solid #ccc;
}
.amap_lib_placeSearch_page {
position: absolute;
bottom: 0;
width: 100%;
}
#me {
border-top: 1px solid #ccc;
margin-top: 6px;
padding-top: 6px;
width: 100%;
display: block;
}
.amap_lib_placeSearch .poibox {
border-bottom: 1px solid #eaeaea;
cursor: pointer;
padding: 5px 0 5px 10px;
position: relative;
min-height: 35px;
}
.amap_lib_placeSearch_poi {
background: url(https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png)
no-repeat;
height: 31px;
width: 19px;
cursor: pointer;
left: -1px;
text-align: center;
color: #fff;
font: 12px arial, simsun, sans-serif;
padding-top: 3px;
}
.amap_lib_placeSearch .poibox .poi-title {
margin-left: 25px;
font-size: 13px;
overflow: hidden;
}
.amap_lib_placeSearch .amap_lib_placeSearch_poi {
position: absolute;
}
.amap_lib_placeSearch .poibox .poi-info {
word-break: break-all;
margin: 0 0 0 25px;
overflow: hidden;
}
.amap_lib_placeSearch .poibox .poi-info p {
color: #999;
font-family: Tahoma;
line-height: 20px;
font-size: 12px;
}
.amap_lib_placeSearch .poibox .poibox-icon {
margin-left: 7px;
margin-top: 4px;
}
.amap-pl-pc .poi-img {
float: right;
margin: 3px 8px 0;
width: 90px;
height: 56px;
overflow: hidden;
}
.poibox {
cursor: pointer;
}
.poibox:hover {
background: #f6f6f6;
}
.selected {
background-image: url(https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png) !important;
}
.amap-info-content {
width: 200px !important;
}
</style>
里边涉及到两个文件引入,一个是AmapTemplate,一个是utils,这两个引入都是用来显示地图的,如果只是想要搜索关键字,不用管这两个文件
AmapTemplate.vue按照上边导入的路径存放就好
<template lang="html">
<div style="width:100%;height:400px;">
<div class="container">
<!-- <div class="search-box">-->
<!-- -->
<!-- <a-input-->
<!-- v-model="searchKey"-->
<!-- type="search"-->
<!-- id="search">-->
<!-- </a-input>-->
<!-- <a-button @click="searchByHand">搜索</a-button>-->
<!-- <div class="tip-box" id="searchTip"></div>-->
<!-- -->
<!-- </div>-->
<!--
amap-manager: 地图管理对象
vid:地图容器节点的ID
zooms: 地图显示的缩放级别范围,在PC上,默认范围[3,18],取值范围[3-18];在移动设备上,默认范围[3-19],取值范围[3-19]
center: 地图中心点坐标值
plugin:地图使用的插件
events: 事件
-->
<el-amap class="amap-box"
:amap-manager="amapManager"
:vid="\'amap-vue\'"
:zoom="zoom"
:plugin="plugin"
:center="center"
:events="events"
>
<!-- 标记 -->
<el-amap-marker v-for="(marker, index) in carmarkers" :key="index" :position="marker.position" :vid="index" :content="marker.content" :events="marker.events"></el-amap-marker>
<el-amap-info-window
:position="currentWindow.position"
:content="currentWindow.content"
:visible="currentWindow.visible"
:events="currentWindow.events">
</el-amap-info-window>
</el-amap>
</div>
</div>
</template>
<script>
import { AMapManager, lazyAMapApiLoaderInstance } from \'vue-amap\'
import { httpAction } from \'@/api/manage\'
import Utils from \'@/components/_util/utils\'
let amapManager = new AMapManager()
export default {
name:\'AMapTemplate\',
data() {
let self = this
return {
carmarkers :[],
title: \'\',
visible: false,
confirmLoading: false,
dictItem: \'\',
aboard: \'\',
userType: \'\',
carIds: \'\',
currentWindow: {
position: \'\',
content: \'111\',
events: {},
visible: false
},
carAddrs: [],
address: null,
searchKey: \'\',
amapManager,
markers: [],
searchOption: {
city: \'全国\',
citylimit: true
},
center: \'\',
zoom: 17,
lng: 0,
lat: 0,
loaded: false,
events: {
init() {
lazyAMapApiLoaderInstance.load().then(() => {
self.initSearch()
})
},
// 点击获取地址的数据
click(e) {
self.markers = []
let { lng, lat } = e.lnglat
self.lng = lng
self.lat = lat
self.center = [lng, lat]
self.markers.push([lng, lat])
// 这里通过高德 SDK 完成。
let geocoder = new AMap.Geocoder({
radius: 1000,
extensions: \'all\'
})
console.log(lng+","+lat) //控制台打印坐标
geocoder.getAddress([lng, lat], function(status, result) {
if (status === \'complete\' && result.info === \'OK\') {
if (result && result.regeocode) {
console.log(result.regeocode.formattedAddress) //控制台打印地址
self.address = result.regeocode.formattedAddress
self.searchKey = result.regeocode.formattedAddress
self.$nextTick()
}
}
})
}
},
// 一些工具插件
plugin: [
{
pName: \'Geocoder\',
events: {
init (o) {
//console.log("一些工具插件--地址"+o.getAddress())
}
}
},
{
// 定位
pName: \'Geolocation\',
events: {
init(o) {
// o是高德地图定位插件实例
o.getCurrentPosition((status, result) => {
if (result && result.position) {
// 设置经度
self.lng = result.position.lng
// 设置维度
self.lat = result.position.lat
// 设置坐标
self.center = [self.lng, self.lat]
self.markers.push([self.lng, self.lat])
// load
self.loaded = true
// 页面渲染好后
self.$nextTick()
}
})
}
}
},
{
// 工具栏
pName: \'ToolBar\',
events: {
init(instance) {
//console.log("工具栏:"+instance);
}
}
},
{
// 鹰眼
pName: \'OverView\',
events: {
init(instance) {
//console.log("鹰眼:"+instance);
}
}
},
{
// 地图类型
pName: \'MapType\',
defaultType: 0,
events: {
init(instance) {
//console.log("地图类型:"+instance);
}
}
},
{
// 搜索
pName: \'PlaceSearch\',
events: {
init(instance) {
//console.log("搜索:"+instance)
}
}
}
]
}
},
created() {
var aboard = localStorage.getItem("aboard");
var arr = aboard.split(",")
this.aboard = arr.map(Number);
this.center = this.aboard;
this.currentWindow.position = this.aboard;
this.dictItem = "sl_car_info, car_no, id, delete_status=1";
this.getCarAddress();
},
mounted(){
var that = this;
Utils.$on(\'mapAboard\', function () {
that.getMapAboard();
})
},
methods: {
getMapAboard() {
var aboard = localStorage.getItem("aboard");
var arr = aboard.split(",")
this.aboard = arr.map(Number);
console.log("aboard", this.aboard);
this.center = this.aboard;
this.currentWindow.position = this.aboard;
this.getCarAddress();
},
handleCancel () {
this.close()
},
close () {
this.$emit(\'close\');
this.visible = false;
},
handleOk () {
console.log(this.searchKey);
this.close();
},
initSearch() {
let vm = this
let map = this.amapManager.getMap()
AMapUI.loadUI([\'misc/PoiPicker\'], function(PoiPicker) {
var poiPicker = new PoiPicker({
input: \'search\',
placeSearchOptions: {
map: map,
pageSize: 10
},
suggestContainer: \'searchTip\',
searchResultsContainer: \'searchTip\'
})
vm.poiPicker = poiPicker
// 监听poi选中信息
poiPicker.on(\'poiPicked\', function(poiResult) {
// console.log(poiResult)
let source = poiResult.source
let poi = poiResult.item
if (source !== \'search\') {
poiPicker.searchByKeyword(poi.name)
} else {
poiPicker.clearSearchResults()
vm.markers = []
let lng = poi.location.lng
let lat = poi.location.lat
let address = poi.cityname + poi.adname + poi.name
vm.center = [lng, lat]
vm.markers.push([lng, lat])
vm.lng = lng
vm.lat = lat
vm.address = address
vm.searchKey = address
}
})
})
},
searchByHand() {
if (this.searchKey !== \'\') {
this.poiPicker.searchByKeyword(this.searchKey)
}
},
onChangeCar(){
//根据id查询对应车辆信息
},
getCarAddress() {
this.carmarkers = [];
let formData = {};
formData.location=this.aboard;
formData.maxArea = 1000;
httpAction(\'/car/carInfo/getCarAddress\',formData,\'post\').then((res)=>{
if(res.success){
this.carAddrs = res.result;
this.positionByHand(this.carAddrs);
}else{
this.$message.warning(res.message);
}
})
},
positionByHand(carAddrs) {
this.currentWindow = {
position: this.aboard,
events: {
close: (e) => {
this.currentWindow.visible = false
}
},
visible: true }
this.carmarkers.push({
position:this.aboard,
content: `<div><img style="width: 40px; height: 40px" src=${window._CONFIG[\'domianURL\']}/img/address.png/></div>`,
events: {
click: (e) => {
this.currentWindow = {
position: this.aboard,
content: `<div style="color: #1c84c6;">起点</div>`,
events: {
close: (e) => {
this.currentWindow.visible = false
}
},
visible: true }
}
}
})
carAddrs.forEach((item,index)=> {
console.log(item);
this.carmarkers.push({
position:item.location,
content: `<div><img style="width: 40px; height: 40px" src="http://localhost:8099/sltx/upload/temp/car.png"/></div>`,
events: {
click: (e) => {
this.currentWindow = {
position: item.location,
content: `<div style="color: #1c84c6;">`+item.carNo+`</div>`,
events: {
close: (e) => {
this.currentWindow.visible = false
}
},
visible: true }
}
}
})
})
}
}
}
</script>
<style lang="css">
.container {
width: 100%;
height: 100%;
position: relative;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
border: 1px solid #999;
}
.search-box {
position: absolute;
z-index: 5;
width: 30%;
left: 13%;
top: 10px;
height: 30px;
}
.search-box input {
float: left;
width: 80%;
height: 100%;
border: 1px solid #30ccc1;
padding: 0 8px;
outline: none;
}
.search-box button {
float: left;
width: 20%;
height: 100%;
background: #30ccc1;
border: 1px solid #30ccc1;
color: #fff;
outline: none;
}
.tip-box {
width: 100%;
max-height: 260px;
position: absolute;
top: 30px;
overflow-y: auto;
background-color: #fff;
}
</style>
utils.js
import Vue from \'vue\'
export default new Vue
对,没错,只有这两行。
剩下的就是后台java的操作了,相关代码如下:
controller:
在对应的controller添加这么一个接口
/**
* 根据关键字查询地理信息
*
* @param keyword
* @return
*/
@AutoLog(value = "订单管理-根据关键字查询地理信息")
@ApiOperation(value="订单管理-根据关键字查询地理信息", notes="订单管理-根据关键字查询地理信息")
@GetMapping(value = "/getAddressItem")
public Result<?> getAddressItem(@RequestParam(name="keyword",required=true) String keyword) {
List<AddressVo> addressVoList = orderService.getAddressItem(keyword);
return Result.ok(addressVoList);
}
新建AddressVo类
import lombok.Data;
@Data
public class AddressVo {
private String name;
private String pname;
private String cityname;
private String adname;
private String address;
private String location;
}
然后是对应的service实现,我写的比较糟糕,你们用的时候可以适当改进一下:
@Override
public List<AddressVo> getAddressItem(String keyword) {
String parameter = "key="+key+"&"+"keywords="+keyword+"&offset=15&city=广州";
String result = HttpRequest.sendGet("https://restapi.amap.com/v3/place/text", parameter);
Map<String, Object> map = new HashMap<String, Object>();
Gson gson = new Gson();
map = gson.fromJson(result, map.getClass());
Object pois = map.get("pois");
List<AddressVo> list = JSON.parseArray(JSON.toJSONString(pois), AddressVo.class);
return list;
}
没有的依赖可以引入对应的pom依赖。
另外涉及到一个请求发送的工具包
HttpRequest.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
/**
* 网络请求(GET和POST)
* @编写时间 2018年8月14日 上午9:36:57
* @文件名 HttpRequest.java
* @类名 HttpRequest
*/
public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
// System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
大致就是这些了,运行有问题的也可以底下评论,毕竟我知道被逼疯的痛苦。不过最好是一样使用jeecg-boot这一套的,比较熟悉