Skip to content

产品说「附近的店」,听起来像调一次定位 ​

首页要出附近商圈。点一下顶栏位置,问的是定位权限。

管这件事的类 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 种。

所以「调一次定位」这句话,排期的时候就错了。用户点一下,最少是三套问法,安卓是四套。

javascript
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。只接住了安卓那一种。

我的判断:三端权限不要收成一个数字。 各自映射成「没问过 / 允许 / 拒绝 / 系统关了 / 永久拒绝」,再决定弹哪张窗、跳哪一页。

javascript
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 比。

「用户必须授权才能进首页。」

这套里弹窗有「稍后再说」。选了它,首页还在。附近可以空,别把整页卡住。


这条链路上的其他几篇

签名-A

大粽子