【实战教程】Uni-App开发:轻松集成高德地图,实现精准位置选点功能

上周接到腾讯客服的电话,说是要收取5万一年的服务费,如果不交,就要停掉我们相关的腾讯地图服务。公司不想交这个钱,就临时决定把项目中的所有腾讯地图改为高德地图(距离项目上线还有3天的时间🙃🙃🙃,当时也没说要收费…)🤢🤮🤮
在这里插入图片描述

没得办法又得加班搞了,😫😫😫心累ing…

先来看看效果图吧

在这里插入图片描述

(本项目是uni-app + vue3 + pinia + js)

完成代码我会放在文章的最下面

正文开始~

引入高德地图小程序插件

我们需要到高德地图微信小程序插件文档入门指南-微信小程序插件|高德地图API (amap.com)里面下载插件amap-wx.js 文件plugin文件夹或者utils文件夹中。

在这里插入图片描述

我们需要稍微修改一下代码否则就会出现下面的问题

在amap-wx.js文件的底部修改一下导出

// module.exports.AMapWX = AMapWX;
export default { AMapWX };

在amap-wx.js文件的上面手动导出一个函数

export function AMapWX(a) {
    this.key = a.key;
    this.requestConfig = { key: a.key, s: 'rsx', platform: 'WXJS', appname: a.key, sdkversion: '1.2.0', logversion: '2.0' };
    this.MeRequestConfig = { key: a.key, serviceName: 'https://restapi.amap.com/rest/me' };
}

如果在开发中还会遇到问题,可以尝试着把amap-wx.js文件中的所有wx.改为uni.(网上的偏方🧑‍⚕️🧑‍⚕️🧑‍⚕️)

在这里插入图片描述

初始化地图

申请高德地图key的方法我就不介绍了,网上有很多的,申请完key后,我们在在vue3的onMounted创建高德地图实例,并调用获取地址描述信息的方法

高德地图文档👇👇👇👇👇👇

基础类-参考手册-微信小程序插件 | 高德地图API (amap.com)

<template>
    <view>
        <view class="map_container">
            <map
                :class="['map']"
                :id="'map'"
                :longitude="longitude"
                :latitude="latitude"
                :scale="14"
                :show-location="true"
                :markers="markers"
                @click="handleMarkerTap"></map>
        </view>
    </view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as amapFile from '../../plugin/amap-wx.130';

const longitude = ref(null); // 经度
const latitude = ref(null); // 纬度
const markers = ref([]); //
const textData = ref({});
const myAmapFun = ref(null);
const key = ref('自己申请的key'); // 高德地图key
const addressList = ref([]); // 周边列表
const searchAddressList = ref([]); // 搜索返回的周边列表
const activeId = ref(-1); // 当前选中的周边id
const isSearch = ref(false); // 是否搜索状态
const searchKey = ref(''); // 用户搜索的内容

onMounted(() => {
    myAmapFun.value = new amapFile.AMapWX({ key: key.value });
    getRegeo(true);
});

const getRegeo = (isUseResAddress) => {
    let location = null;
    if (longitude.value != null && latitude.value != null) {
        location = longitude.value + ',' + latitude.value;
    }

    myAmapFun.value.getRegeo({
        iconPath: '../../static/marker.png',
        iconWidth: 22,
        iconHeight: 32,
        location: location,
        success: (data) => {
            console.log('成功的数据', data[0].regeocodeData);
            if (isUseResAddress) {
               latitude.value = data[0].latitude;
               longitude.value = data[0].longitude;
           }
            
        },
        fail: function (info) {
            wx.showModal({ title: info.errMsg });
        },
    });
};
// 用户点击地图
const handleMarkerTap = (ev) => {
    console.log('点击标记点触发', ev);
    latitude.value = ev.detail.latitude;
    longitude.value = ev.detail.longitude;
    getRegeo();
};
<style lang="scss" scoped>
.map_container {
    position: relative;
    top: 0;
    bottom: 80px; /* 底部留出的空间 */
    left: 0;
    right: 0;
}
.map {
    width: 100%;
    /* 计算总的高度,减去底部空间,然后取45% */
    height: calc((100vh - 80px) * 0.45);
}
</style>

这样你就可以看到地图组件初始化的界面和取得的相关数据了。

在这里插入图片描述
在这里插入图片描述
如果项目中出现了这样的报错:

在这里插入图片描述

那么你需要到manifest.json文件中找到mp-weixin的增加一些配置
在这里插入图片描述

"mp-weixin": {
    "appid": "自己申请的小程序appid",
    "setting": {
        "urlCheck": false
    },
    "permission": {
        "scope.userLocation": {
            "desc": "你的位置信息将用于小程序位置接口的效果展示"
        },
        "scope.userProfile": {
            "desc": "获取用户信息"
        }
    },
    "requiredPrivateInfos": ["chooseLocation", "getLocation"],
    "usingComponents": true,
    "lazyCodeLoading": "requiredComponents",
    "libVersion": "latest"
},

更改后记得重新编译小程序❗❗❗(如果还是没有解决,清空一下微信开发者工具缓存。

根据数据中POI数组渲染周围地址信息

在返回的信息中可以看到pois数组,这个数组就是我们要遍历的显示在下面的数据。
将返回的数据中的pois数组存放起来,在页面中遍历循环这个数据即可
在这里插入图片描述

<scroll-view scroll-y class="address-list">
    <view v-show="!isSearch">
        <view class="search">
            <view class="searchBox" @click="handleSearchTap">
                <text style="font-size: 26rpx; color: gray"> 搜索地点 </text>
            </view>
        </view>
        <view class="address-list-item" v-for="item in addressList" :key="item.id" @click="sureAddress(item)">
            <view>
                <text class="tip-titile">{{ item.name }}</text>
                <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
            </view>
            <view style="margin-right: 30rpx">
                <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
            </view>
        </view>
    </view>
    <view v-show="isSearch">
        <view class="search">
            <view class="searchBox">
                <input type="text" v-model="searchKey" @change="handelSearch" placeholder="请输入搜索内容" />
                <text style="color: #0079ff" @click="handelCancel">取消</text>
            </view>
        </view>
        <view class="address-list-item" v-for="item in searchAddressList" :key="item.id" @click="sureAddress(item)">
            <view>
                <text class="tip-titile">{{ item.name }}</text>
                <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
            </view>
            <view style="margin-right: 30rpx">
                <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="https://rizuwang.cn/rizuwang/images/staticImages/mp/success.png" alt="" />
            </view>
        </view>
    </view>
</scroll-view>

const getRegeo = (isUseResAddress) => {
    let location = null;
    if (longitude.value != null && latitude.value != null) {
        location = longitude.value + ',' + latitude.value;
    }

    myAmapFun.value.getRegeo({
        iconPath: '../../static/marker.png',
        iconWidth: 22,
        iconHeight: 32,
        location: location,
        success: (data) => {
            console.log('成功的数据', data[0].regeocodeData);
            addressList.value = data[0].regeocodeData.pois; // 获取周围poi数据,并渲染在列表中
            if (isUseResAddress) {
                latitude.value = data[0].latitude;
                longitude.value = data[0].longitude;
            }
        },
        fail: function (info) {
            wx.showModal({ title: info.errMsg });
        },
    });
};
<style lang="scss" scoped>

.address-list {
    position: relative;
    top: auto; /* 移除绝对定位 */
    bottom: auto;
    left: 0;
    right: 0;
    /* 同样计算剩余的高度,取剩下的45% */
    height: calc((100vh - 80px) * 0.7);
    overflow-y: auto; /* 添加滚动条 */
    .address-list-item {
        border-top: 1px solid #eee;
        display: flex;
        align-items: center;
        justify-content: space-between;
        .tip-titile {
            font-size: 28rpx;
            color: #333;
            font-weight: 400;
        }
        .tip-address {
            font-size: 20rpx;
            color: gray;
        }
    }
}
</style>

在这里插入图片描述

给列表绑定点击事件取得点信息对象


<view class="address-list-item" v-for="item in addressList" :key="item.id" @click="sureAddress(item)">
    <view>
        <text class="tip-titile">{{ item.name }}</text>
        <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
    </view>
    <view style="margin-right: 30rpx">
        <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
    </view>
</view>

// 用户点击下方列表选项
const sureAddress = (ev) => {
    console.log('周围信息列表', ev);
};

这样我们就可以得到地理位置的相关信息自行保存就好
在这里插入图片描述

自定义搜索获取搜索位置信息

在这里插入图片描述

我们在点击搜索的时候会调用高德地图提供的另外一个方法getPoiAround(获取周边的POI)与上一步渲染周围地址信息类似

<template>
    <view>
        <view class="map_container">
            <map
                :class="['map']"
                :id="'map'"
                :longitude="longitude"
                :latitude="latitude"
                :scale="14"
                :show-location="true"
                :markers="markers"
                @click="handleMarkerTap"></map>
        </view>

        <scroll-view scroll-y class="address-list">
            <view v-show="!isSearch">
                <view class="search">
                    <view class="searchBox" @click="handleSearchTap">
                        <text style="font-size: 26rpx; color: gray"> 搜索地点 </text>
                    </view>
                </view>
                <view class="address-list-item" v-for="item in addressList" :key="item.id" @click="sureAddress(item)">
                    <view>
                        <text class="tip-titile">{{ item.name }}</text>
                        <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
                    </view>
                    <view style="margin-right: 30rpx">
                        <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
                    </view>
                </view>
            </view>
            <view v-show="isSearch">
                <view class="search">
                    <view class="searchBox">
                        <input type="text" v-model="searchKey" @change="handelSearch" placeholder="请输入搜索内容" />
                        <text style="color: #0079ff" @click="handelCancel">取消</text>
                    </view>
                </view>
                <view class="address-list-item" v-for="item in searchAddressList" :key="item.id" @click="sureAddress(item)">
                    <view>
                        <text class="tip-titile">{{ item.name }}</text>
                        <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
                    </view>
                    <view style="margin-right: 30rpx">
                        <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
                    </view>
                </view>
            </view>
        </scroll-view>
    </view>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import * as amapFile from '../../plugin/amap-wx.130';

const longitude = ref(null); // 经度
const latitude = ref(null); // 纬度
const markers = ref([]); //
const textData = ref({});
const myAmapFun = ref(null);
const key = ref('自己申请的key'); // 高德地图key
const addressList = ref([]); // 周边列表
const searchAddressList = ref([]); // 搜索返回的周边列表
const activeId = ref(-1); // 当前选中的周边id
const isSearch = ref(false); // 是否搜索状态
const searchKey = ref(''); // 用户搜索的内容

// 用户点击搜索按钮
const handleSearchTap = () => {
    searchKey.value = ''; // 清空搜索内容
    searchAddressList.value = []; // 清空搜索列表
    isSearch.value = true;
};
// 用户点击取消按钮(搜索列表页)
const handelCancel = () => {
    searchKey.value = ''; // 清空搜索内容
    searchAddressList.value = []; // 清空搜索列表
    isSearch.value = false;
};

// 用户搜索内容
const handelSearch = (ev) => {
    // 只有输入框内有值的时候才会搜索
    if (searchKey.value) {
        myAmapFun.value.getPoiAround({
            iconPath: '标记点图片',
            iconWidth: 22,
            iconHeight: 32,
            location: location,
            querykeywords: searchKey.value,
            success: (data) => {
                if (data.poisData.length == 0) {
                    uni.showModal({
                        title: '提示',
                        content: '没有搜索到结果',
                        showCancel: false,
                    });
                } else {
                    searchAddressList.value = data.poisData; // 将搜索回来的数据放在列表中显示
                }
            },
            fail: (data) => {
                console.log('搜索失败', data);
            },
        });
    }
};
</script>

搜索优化

我们可以使用节流技术来优化一下用户搜索时候的请求,以便于减轻手机的压力。

当用户开始搜索的时候开启一个计时器,只有计时器停止了,才会去发送请求,当用户再次输入时,首先会停止本次计时器,并创建一个新的计时器。再次输入时候也是同样的程序。这样用户在输入完成后的某个时间内才会发送请求。

// 用户搜索内容
const handelSearch = (ev) => {
    if (throttleTimer) {
        clearTimeout(throttleTimer);
    }
    throttleTimer = setTimeout(() => {
        // 只有输入框内有值的时候才会搜索
        if (searchKey.value) {
            myAmapFun.value.getPoiAround({
                iconPath: '../../static/marker.png',
                iconWidth: 22,
                iconHeight: 32,
                location: location,
                querykeywords: searchKey.value,
                success: (data) => {
                    if (data.poisData.length == 0) {
                        uni.showModal({
                            title: '提示',
                            content: '没有搜索到结果',
                            showCancel: false,
                        });
                    } else {
                        searchAddressList.value = data.poisData; // 将搜索回来的数据放在列表中显示
                    }
                },
                fail: (data) => {
                    console.log('搜索失败', data);
                },
            });
        }
    }, 500);
};

设置标记点

在开发过程中我们发现,不管是getRegeo函数还是getPoiAround中的iconPath都没有生效,这就导致,当用户点击一个列表或者滑动地图进行选点的时候没有任何标记位置,会很大程度的降低用户的耐心和平台的信任度,为了解决这个问题,我们可以在点击地图或者点击搜索列表的时候设置一个标记点,将选择的经纬度地点标记出来,这样就可以解决上面的问题了。

在这里插入图片描述

我们需要更改下面两个函数

const handleMarkerTap = (ev) => {
    latitude.value = ev.detail.latitude;
    longitude.value = ev.detail.longitude;
    // 更改标记点
    markers.value = [
        {
            id: 1,
            latitude: ev.detail.latitude,
            longitude: ev.detail.longitude,
        },
    ];
    getRegeo();
};

// 用户点击下方列表选项
const sureAddress = (ev, type) => {
    activeId.value = ev.id;
    // 更改标记点
    markers.value = [
        {
            id: 1,
            latitude: latitude.value, // 正确地使用了latitude变量
            longitude: longitude.value, // 正确地使用了longitude变量
        },
    ];
}

完整项目代码

<template>
    <view>
        <view class="map_text" v-if="textData.name">
            <view>
                <text>您选择的位置:</text>
                <text class="tip-titile">{{ textData.name }}</text>
                <text class="tip-address">{{ textData.address }}</text>
            </view>
            <view @click="choiceAddress" style="color: #0079ff">确定</view>
        </view>
        <view class="map_container">
            <map
                :class="['map']"
                :id="'map'"
                :longitude="longitude"
                :latitude="latitude"
                :scale="14"
                :show-location="true"
                :markers="markers"
                @click="handleMarkerTap"></map>
        </view>

        <scroll-view scroll-y class="address-list">
            <view v-show="!isSearch">
                <view class="search">
                    <view class="searchBox" @click="handleSearchTap">
                        <text style="font-size: 26rpx; color: gray"> 搜索地点 </text>
                    </view>
                </view>
                <view class="address-list-item" v-for="item in addressList" :key="item.id" @click="sureAddress(item)">
                    <view>
                        <text class="tip-titile">{{ item.name }}</text>
                        <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
                    </view>
                    <view style="margin-right: 30rpx">
                        <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
                    </view>
                </view>
            </view>
            <view v-show="isSearch">
                <view class="search">
                    <view class="searchBox">
                        <input type="text" v-model="searchKey" @change="handelSearch" placeholder="请输入搜索内容" />
                        <text style="color: #0079ff" @click="handelCancel">取消</text>
                    </view>
                </view>
                <view class="address-list-item" v-for="item in searchAddressList" :key="item.id" @click="sureAddress(item)">
                    <view>
                        <text class="tip-titile">{{ item.name }}</text>
                        <text class="tip-address">{{ (+item.distance).toFixed(0) }}m | {{ item.address }}</text>
                    </view>
                    <view style="margin-right: 30rpx">
                        <img v-if="activeId == item.id" style="width: 50rpx; height: 50rpx" src="../../static/success.png" alt="" />
                    </view>
                </view>
            </view>
        </scroll-view>
    </view>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import * as amapFile from '../../utils/amap-wx.130';

const longitude = ref(null); // 经度
const latitude = ref(null); // 纬度
const markers = ref([]); // 标记点
const myAmapFun = ref(null);
const key = ref('自己申请的高德地图key'); // 高德地图key
const addressList = ref([]); // 周边列表
const searchAddressList = ref([]); // 搜索返回的周边列表
const activeId = ref(-1); // 当前选中的周边id
const isSearch = ref(false); // 是否搜索状态
const searchKey = ref(''); // 用户搜索的内容
let throttleTimer = null; // 定时器编号
const textData = ref({
    latitude: '', // 纬度
    longitude: '', // 经度
    name: '', // 名称
    address: '', // 详细地址
    province: '', // 省份
    city: '', // 城市
    district: '', // 区县
});

onMounted(() => {
    myAmapFun.value = new amapFile.AMapWX({ key: key.value });
    getRegeo(true);
});

// 获取地址描述信息
const getRegeo = (isUseResAddress) => {
    let location = null;
    if (longitude.value != null && latitude.value != null) {
        location = longitude.value + ',' + latitude.value;
    }

    myAmapFun.value.getRegeo({
        iconPath: '标记坐标图片',
        iconWidth: 22,
        iconHeight: 32,
        location: location,
        success: (data) => {
            console.log('成功的数据', data[0].regeocodeData);
            addressList.value = data[0].regeocodeData.pois;
            if (isUseResAddress) {
                latitude.value = data[0].latitude;
                longitude.value = data[0].longitude;
            }
        },
        fail: function (info) {
            wx.showModal({ title: info.errMsg });
        },
    });
};

// 用户点击地图
const handleMarkerTap = (ev) => {
    latitude.value = ev.detail.latitude;
    longitude.value = ev.detail.longitude;
    // 更改标记点
    markers.value = [
        {
            id: 1,
            latitude: ev.detail.latitude,
            longitude: ev.detail.longitude,
        },
    ];
    getRegeo();
};

// 用户点击下方列表选项
const sureAddress = (ev, type) => {
    activeId.value = ev.id;
    const [longitudeStr, latitudeStr] = ev.location.split(',');
    longitude.value = parseFloat(longitudeStr); // 将字符串转换为浮点数
    latitude.value = parseFloat(latitudeStr);

    // 更改标记点
    markers.value = [
        {
            id: 1,
            latitude: latitude.value, // 正确地使用了latitude变量
            longitude: longitude.value, // 正确地使用了longitude变量
        },
    ];
};
// 用户点击搜索按钮
const handleSearchTap = () => {
    searchKey.value = ''; // 清空搜索内容
    searchAddressList.value = []; // 清空搜索列表
    isSearch.value = true;
};
// 用户点击取消按钮(搜索列表页)
const handelCancel = () => {
    searchKey.value = ''; // 清空搜索内容
    searchAddressList.value = []; // 清空搜索列表
    isSearch.value = false;
};
// 用户搜索内容
const handelSearch = (ev) => {
    if (throttleTimer) {
        clearTimeout(throttleTimer);
    }
    throttleTimer = setTimeout(() => {
        // 只有输入框内有值的时候才会搜索
        if (searchKey.value) {
            myAmapFun.value.getPoiAround({
                iconPath: '标记图片',
                iconWidth: 22,
                iconHeight: 32,
                location: location,
                querykeywords: searchKey.value,
                success: (data) => {
                    if (data.poisData.length == 0) {
                        uni.showModal({
                            title: '提示',
                            content: '没有搜索到结果',
                            showCancel: false,
                        });
                    } else {
                        searchAddressList.value = data.poisData; // 将搜索回来的数据放在列表中显示
                    }
                },
                fail: (data) => {
                    console.log('搜索失败', data);
                },
            });
        }
    }, 500);
};
// 用户选择了位置
const choiceAddress = () => {
    secretStore.addressInfo = textData.value; // 将数据存到内存中
    uni.navigateBack();
};
</script>

<style lang="scss" scoped>
:deep(.uni-easyinput__placeholder-class) {
    background-color: red !important;
}
.container {
    width: 680.56rpx;
    height: 416.67rpx;
    margin: 6.94rpx 0;
    border: 1px solid #e3e3e3;

    .coordinate {
        z-index: 9999;
        position: relative;
        top: 50%;
        left: 50%;
    }
}
.map_container {
    position: relative;
    top: 0;
    bottom: 80px; /* 底部留出的空间 */
    left: 0;
    right: 0;
}
.map {
    width: 100%;
    /* 计算总的高度,减去底部空间,然后取45% */
    height: calc((100vh - 80px) * 0.45);
}
.map_text {
    background: #fff;
    padding: 0 15px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
text {
    margin: 5px 0;
    display: block;
    font-size: 12px;
}
.h1 {
    margin: 15px 0;
    font-size: 15px;
}
.address-list {
    position: relative;
    top: auto; /* 移除绝对定位 */
    bottom: auto;
    left: 0;
    right: 0;
    /* 同样计算剩余的高度,取剩下的45% */
    height: calc((100vh - 80px) * 0.7);
    overflow-y: auto; /* 添加滚动条 */
    .address-list-item {
        border-bottom: 1px solid #eee;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}
.tip-titile {
    font-size: 28rpx;
    color: #333;
    font-weight: 400;
}
.tip-address {
    font-size: 20rpx;
    color: gray;
}
.search {
    width: 750rpx;
    padding: 8px 15px;
    background-color: #fff;
    border-radius: 10rpx;
    border-bottom: 1px solid #eee;

    .searchBox {
        height: 65rpx;
        width: 90%;
        background-color: #ebebeb;
        padding: 10rpx 0;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10rpx;
    }

    .searchInput {
        height: 45rpx;
        padding: 8px 15px;
        outline: none;
        border: 1px solid #d3d3d3;
        border-radius: 5px;
        background-color: #ebebeb;
        margin-top: 10px;
    }
}
</style>

文章的最后说一下为什么uni-app使用的的是高德地图,反而在小程序中显示的腾讯地图呢?

在这里插入图片描述

这是因为我们的uni-app中的 map组件并不支持高德小程序,所以会显示腾讯地图,不过对我们的影响不大,我们已经停掉了腾讯地图的所有业务,高德地图依然支持,所以大家不要担心这个事情。

在这里插入图片描述

文章就到此结束了,大家如果感兴趣可以留言关注,我们一起学习😄😄😄

👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值