uni-app实现附近门店导航:高德地图插件从零移植到原生APP

2026-09-02 0 984

上周接了个需求,要在公司已有的uni-app项目里加一个“附近门店”功能。用户点进来以后能看到离他最近的一些门店卡片,点击卡片直接唤起导航找路。听起来很简单对吧?真做起来发现坑比想象中多。今天把这个过程完整写出来,给后面做类似功能的朋友趟个路。

我用的开发环境是HBuilderX 4.36 + uni-app(Vue3编译到APP端),地图SDK选的是高德。为什么没选腾讯或百度?因为公司之前的高德key申请好了,就这么简单。

第一步:创建uni-app项目并配置高德

如果你是从老项目改的,可以跳过创建这一步。从我这次经验看,新项目反而好弄,因为很多权限配置在manifest里需要手动勾选。

// 在manifest.json->App模块配置中,手动勾选Maps
// 然后还需要在App SDK配置里找到高德地图,填入你的key
{"app-plus" : {
    "modules" : {
        "Maps" : {}
    },
    "distribute" : {
        "sdkConfigs" : {
            "maps" : {
                "amap" : {
                    "key_android" : "你的安卓key",
                    "key_ios" : "你的ios key"
                }
            }
        }
    }
}}

注意安卓key的签名要和打包证书一致。我这边就是只填了发布版key,忘了加测试包key,导致真机调试白屏,后来两个key都填上才正常。

第二步:申请定位权限

uni-app里定位权限需要两个东西:一个是manifest里的App权限配置,另一个是代码里调uni.getLocation时触发权限请求。

// manifest.json里加上这些权限声明
"permissions" : {
    "Android" : {
        "android.permission.ACCESS_COARSE_LOCATION" : {},
        "android.permission.ACCESS_FINE_LOCATION" : {}
    },
    "iOS" : {
        "NSLocationWhenInUseUsageDescription" : "我们需要获取位置信息以显示附近门店",
        "NSLocationAlwaysAndWhenInUseUsageDescription" : "后台定位用于导航播报"
    }
}

然后代码里获取定位的时候,最好是先检查一下授权状态,免得用户拒绝以后没有回调。

export function getCurrentPosition() {
    return new Promise((resolve, reject) => {
        uni.getLocation({
            type: 'gcj02',
            isHighAccuracy: true,
            highAccuracyExpireTime: 3000,
            success: (res) => {
                resolve({ latitude: res.latitude, longitude: res.longitude });
            },
            fail: (err) => {
                // 如果有权限失败,引导用户去设置页打开
                if (err.errMsg.indexOf('auth deny') > -1 || err.errMsg.indexOf('authorize') > -1) {
                    uni.showModal({
                        title: '提示',
                        content: '需要定位权限才能使用附近门店功能,是否前往设置打开?',
                        success: (r) => {
                            if (r.confirm) {
                                uni.openAppAuthorizeSetting();
                            }
                        }
                    });
                }
                reject(err);
            }
        });
    });
}

这里我用的type: ‘gcj02’,因为高德地图需要的坐标系就是gcj02,很多人直接拿wgs84坐标丢给高德,那地图上的偏移能大到一条街去。

第三步:在页面中渲染高德地图

uni-app的map组件原生支持高德。用起来很简单,但要注意的是,map组件的markers更新,需要绑定一个数组,并且通过变化数组长度或者key来触发重新渲染。

<template>
    <view class="page">
        <map
            id="map"
            :latitude="lat"
            :longitude="lng"
            :markers="storeMarkers"
            :scale="14"
            @markertap="onMarkerTap"
            @tap="onMapTap"
        ></map>
        <!-- 底部门店列表,滑动切换 -->
        <scroll-view scroll-y class="store-list">
            <view
                class="store-card"
                v-for="(item, idx) in storeList"
                :key="item.id"
                @click="selectStore(item, idx)"
            >
                <text>{{ item.name }}</text>
                <text>距离 {{ item.distanceText }}</text>
            </view>
        </scroll-view>
    </view>
</template>

很多人以为把接口返回的经纬度填进markers就行,其实还要转一下坐标。如果你的后台返回的是百度坐标或者原始GPS坐标,map组件虽然自带convertPoint,但那个是异步的,而且一次性转多个点容易乱。稳妥做法是后端直接存gcj02,或者前端遍历转一下。我这次就是后端给的坐标是百度bd09ll,差点翻车。

第四步:添加自定义marker图标

默认的红色大头针太丑了,我换成了门店风格的图标。markers的iconPath字段,本地路径或者网络路径都行,但要注意APP端不支持直接写远程链接,需要先下载到本地。我把图标放到了static/images/store-pin.png,然后在marker数据里iconPath写相对路径。

function buildMarker(store) {
    return {
        id: store.id,
        latitude: store.lat,
        longitude: store.lng,
        iconPath: '/static/images/store-pin.png',
        width: 28,
        height: 34,
        callout: {
            content: store.name,
            display: 'BYCLICK',
            borderRadius: 4,
            padding: 4,
            fontSize: 12,
            color: '#333333',
        }
    };
}

这里有个小坑:callout里内容不能包含emoji,否则某些安卓机器上会崩溃。我在门店名字后面加了个emoji,现场就挂了,去掉以后正常。

第五步:附近门店的排序和距离计算

接口返回的门店列表通常是无序的,需要基于当前定位算距离排序。我写好了一个纯函数,用来计算两个经纬度之间的距离(单位米),然后再转成“500m”、“1.2km”这样。

function getDistance(lat1, lng1, lat2, lng2) {
    const rad = Math.PI / 180;
    const dLat = (lat2 - lat1) * rad;
    const dLng = (lng2 - lng1) * rad;
    const a = Math.sin(dLat / 2) ** 2 +
        Math.cos(lat1 * rad) * Math.cos(lat2 * rad) * Math.sin(dLng / 2) ** 2;
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return 6371000 * c; // 地球半径取6371km
}

storeList.forEach(item => {
    item.distance = getDistance(currentLat, currentLng, item.lat, item.lng);
    if (item.distance < 1000) {
        item.distanceText = Math.round(item.distance) + 'm';
    } else {
        item.distanceText = (item.distance / 1000).toFixed(1) + 'km';
    }
});
storeList.sort((a, b) => a.distance - b.distance);

一开始我用了个更复杂的算法,后来发现高德在gcj02坐标系下,地球上两点距离用这个简化公式误差可以接受。如果门店之间跨度特别大(比如不同城市),那还是得用高德Web服务API的distance接口,但客户只要附近3公里,所以没问题。

第六步:点击门店图标或卡片,调起导航

导航的实现在uni-app里可以直接用uni.openLocation,这个API底层会打开系统自带地图,但有些国产ROM并没有标配高德/百度地图,体验不一。所以最好还是打开高德地图App,如果没有安装则跳转浏览器高德网页导航。我封装了这样一个函数:

function goNavigation(store) {
    // 判断是高德地图客户端是否安装
    uni.getAppBaseInfo().then(info => {
        if (plus.os.name === 'Android') {
            plus.runtime.isApplicationExist({
                action: 'androidamap://',
                success: exist => {
                    if (exist) {
                        // 用URL Scheme唤起高德并规划驾车路线
                        const daddr = store.lat + ',' + store.lng;
                        const uri = 'androidamap://navi?sourceApplication=uni-app&lat=' +
                            store.lat + '&lon=' + store.lng + '&dev=1';
                        plus.runtime.openURL(uri);
                    } else {
                        openWebAmap(store);
                    }
                },
                fail: () => openWebAmap(store)
            });
        } else {
            // iOS直接采用原生地图,因为苹果自带地图调起体验更好
            uni.openLocation({
                latitude: store.lat,
                longitude: store.lng,
                name: store.name,
                address: store.address
            });
        }
    });
}

function openWebAmap(store) {
    const url = 'https://uri.amap.com/navigation?from=' + currentLat + ',' + currentLng +
        '&to=' + store.lat + ',' + store.lng + '&mode=car&src=myapp';
    plus.runtime.openURL(url);
}

说下为什么iOS直接用uni.openLocation。因为高德自己的iOS URL Scheme经常被Apple限制,而且iOS13以后跳转第三方App需要URL Scheme白名单,需要额外配置,用系统自带地图最省心。

第七步:解决地图容器高度问题

map组件在APP端如果高度设置为100%,有时候会变成0高度。你需要在style里给map设一个固定的px值,或者用flex布局包裹起来给个计算高度。

<view class="map-container">
    <map
        class="map-content"
        ...
    ></map>
</view>

// css内不能使用行内样式,所以写在style里
.map-container {
    height: 65vh;
}
.map-content {
    width: 100%;
    height: 100%;
}

这一点特别坑,我在H5端跑得好好的,打包到APP后白屏,查了半天发现就是height:100%的问题。

第八步:真机调试时的常见报错

1. getLocation一直返回fail,errMsg包含“getLocation:fail:ERROR_NOCONFIG”。这是因为manifest里没填key,或者填错了,重新检查高德App Key。

2. 地图显示空白,而且控制台报“AMapError: invalid map key”。同样的,去高德开放平台核对key,特别注意包名和签名。

3. 点击marker没反应。marker里的id字段必须为number,如果接口返回的是字符串,就会触发这个bug。用parseInt转一下。

4. 门店列表滚动时,地图marker卡顿。因为我在滚动事件里setData了整个storeMarkers数组,后面改成只更新当前高亮marker的callout,流畅多了。

总结一下这个方案的结构

整体流程并不复杂:拿到定位,后台给门店坐标,前端算距离排序,map组件打点,点击后调起导航。但涉及地图SDK时,细节和坑真的多。我发现只要用的不是H5平台,就经常会碰到坐标系、权限、key配置这些乱七八糟的东西。多花点时间看高德开放平台的官方文档,比东搜西找强。

如果你也被“附近门店”这个需求折磨,希望这篇能帮你避开我踩过的坑。以后大家可以在评论区交流一下,看有没有更优雅的跳转导航方式。

uni-app实现附近门店导航:高德地图插件从零移植到原生APP
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uni-app实现附近门店导航:高德地图插件从零移植到原生APP https://www.taomawang.com/web/uniapp/2694.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务