产品说「附近的店」,听起来像调一次定位
首页要出附近商圈。点一下顶栏位置,问的是定位权限。
管这件事的类 848 行。App 另有一份 253 行的原生权限。安卓在系统弹窗前面,还要自己先弹一次说明,那份工具 113 行。顶栏那个位置文字,自己又是 485 行。
调的是 uni.getLocation。问完之后,三端拿到的不是同一个东西。
一单在三端上长什么样
用户点顶栏位置。有权限,进选点页。没权限,代码里写的是再跳一页去引导。
前端顶栏不是一行 text。小程序用 CSS 把地址滚过去,H5 和 App 用 JS 挪 translateX。同一句「选择位置」,动画都拆开了。
后端不参与这一下。经纬度进缓存,过期时间 30 分钟。附近商圈用的是这组坐标,不是用户表上的收货地址。
测试不能只测「允许」。要测「稍后再说」。弹窗取消之后,首页还得能逛。安卓还多一截:自定义说明取消,系统框根本不该出来。
三端各问一次
小程序走 uni.getSetting,看 scope.userLocation。三种结果:undefined 没问过、true 允许、false 拒绝。坐标系用 wgs84,超时 15 秒,高精度关掉。
H5 走 navigator.geolocation。超时 10 秒。弹窗文案写的是点地址栏左边的图标,确认键是「我已设置」,然后 location.reload()。不是 HTTPS,这步直接没了。
App 不走这两条。iOS 去问 CLLocationManager,系统定位关了返回 2。安卓去问 ACCESS_FINE_LOCATION,永久拒绝返回 -1。坐标系用 gcj02,高精度打开。
安卓还有第四下:系统框出来之前,先弹「定位权限说明」。文案写着不授权就没法用附近商圈和收货地址。这一份说明里,定位、相册、相机、电话一共 4 种。
所以「调一次定位」这句话,排期的时候就错了。用户点一下,最少是三套问法,安卓是四套。
uni.getLocation({
// #ifdef MP
type: 'wgs84',
timeout: 15000,
// #endif
// #ifdef H5 || APP-PLUS
type: 'gcj02',
// #endif
success: save
});wgs84 和 gcj02 不是精度问题。同一组数字,附近商圈会偏出去。
两套状态码对不齐
原生那份权限,注释写的是:null 未请求、1 允许、0 拒绝、2 系统未开。安卓永久拒绝另给 -1。
管缓存的那份,注释是另一套:0 未检查、1 允许、2 拒绝、3 系统未开。
拒绝之后要弹「去设置」的分支,写的是 === -1。
安卓永久拒绝能进这支。iOS 拒绝是 0,系统未开是 2,进不去。小程序拒绝是 false,也进不去。
注释改过一次。旧判断 === 2 被注掉,换成 -1。只接住了安卓那一种。
我的判断:三端权限不要收成一个数字。 各自映射成「没问过 / 允许 / 拒绝 / 系统关了 / 永久拒绝」,再决定弹哪张窗、跳哪一页。
function mapSt(raw, end) {
if (end === 'mp') {
if (raw == null) return 'unasked';
if (raw === true) return 'ok';
if (raw === false) return 'deny';
return 'unknown';
}
if (raw == null) return 'unasked';
if (end === 'and' && raw === -1) {
return 'forever';
}
if (end === 'ios' && raw === 2) {
return 'sys_off';
}
if (raw === 1) return 'ok';
if (raw === 0) return 'deny';
return 'unknown';
}数字可以再转一次。不要让 -1、2、false 直接进同一条 if。
这里最容易漏的是小程序的 true。它和原生返回的 1 都表示允许,但 true === 1 是假。只在最后统一判断 raw === 1,用户明明点了允许,也会掉进拒绝分支。所以示例先按端解释返回值,再交给页面决定下一步。上面只列了小程序和原生权限的映射,H5 的成功、失败回调要另接。
没认出来的值也要留一个出口。unknown 表示映射还没覆盖,不能直接当成用户拒绝,否则后面排查只看见「没权限」,看不见是哪一端的返回值没接住。我的验收清单会把输入和动作写成一对:小程序允许后能继续选点,iOS 系统未开时给对应提示,安卓永久拒绝时引导设置。先核对状态,再检查页面有没有走对。
没权限时跳哪
有坐标、有权限,去选位置。否则去引导。选位置、选店铺分组,这两页在路由里登记了。引导那一页,代码写了路径,路由表里没有这一条。
测试点「拒绝」。跳转失败的话,不是定位 API 的错,是没权限的去处没登记。
我的判断:拒绝必须能继续逛。 「稍后再说」是合法答案。附近商圈可以空,首页不能白。缺的那一页要先补上,或者改跳已经登记的选点页,让用户手选一个点。
手选走 uni.chooseLocation。用户在地图上点一下,坐标照样能进那 30 分钟的缓存。这条路不依赖系统定位开着。
常见问题对照表
| 现象 | 原因 | 处理 |
|---|---|---|
小程序 getLocation 失败 | 权限拒绝,或隐私协议没声明位置 | 走引导,不要重试死循环 |
| H5 无反应 | 不是 HTTPS,或浏览器拦了 | 文案指向地址栏图标 |
| iOS 弹窗不出来 | 系统定位关了,返回 2,拒绝分支只接 -1 | 按「系统关了」单独引导 |
| 安卓只能去设置 | 永久拒绝是 -1 | 打开应用权限页 |
| 附近的店偏了 | 小程序 wgs84,另外两端 gcj02 | 入库前转成同一套 |
| 拒绝后跳转失败 | 引导页没进路由表 | 先改路径,再改文案 |
| 顶栏文字小程序会滚、H5 不动 | 滚动实现拆成了 CSS 和 JS | 分端测,不要只截一处 |
反对意见
「UniApp 不是有 uni.getLocation 吗。」
有。上面那 848 行不是因为 API 不够,是因为问法、拒绝、设置页、坐标系四件事三端不是一份。
「先做小程序,H5 和 App 后补。」
可以。后补的时候把映射表留下。不要把小程序的 false 直接拿去跟安卓的 -1 比。
「用户必须授权才能进首页。」
这套里弹窗有「稍后再说」。选了它,首页还在。附近可以空,别把整页卡住。
这条链路上的其他几篇
- 1021 深 小程序调通了,打包成 H5 一条路直接断
- 1026 深 登录页就一个按钮,库里有三条不同的身份
