fix(user): 修复地图选点打开失败及定位权限处理问题

- 移除旧的定位权限拒绝检测方法,改用微信隐私授权新接口保证兼容
- 新增确保微信隐私授权的函数,适配新版基础库及旧版本放行逻辑
- 增加定位权限预检和引导用户开启权限流程,支持设置页跳转回读授权状态
- 在调用 chooseLocation 时仅传入有效经纬度,避免部分机型失败
- 地图打开失败时增加弹窗提示,支持用户手动选择地区或重试打开地图
- 定位失败且已锁定地区时取消误触发地区选择器,提升用户体验
This commit is contained in:
2026-07-12 21:58:52 +08:00
parent f1da250295
commit b02f0d91b1
2 changed files with 181 additions and 84 deletions

View File

@@ -0,0 +1,15 @@
# 2026-07-12
## address-edit 地图选点「打开地图失败」修复
客户反馈部分手机无法通过选择地图填写地址/所在区,提示「打开地图失败,请重试」。
根因按概率①微信隐私授权未处理新版基础库强制最契合「部分手机」②chooseLocation 传 undefined 经纬度;③权限流程不完整;④失败无兜底。
已修复 `src/pages/user/address-edit.tsx`(四层):
1. 调 chooseLocation 前 `ensurePrivacyAuthorized()`wx.getPrivacySetting/requirePrivacyAuthorize旧基础库无接口则放行
2. 仅当 lat/lng 为有效数字时才传给 chooseLocation否则 `chooseLocation({})`
3. `ensureLocationPermission()`getSetting 预检 + authorize 申请 + openSetting 引导并回读确认。
4. 失败兜底:弹窗「手动选择」(打开所在地区选择器)/「再试一次」(chooseLocation({}))regionLocked 时不再误开选择器。
注意:项目本地 `tsc --noEmit` / `npm run type-check` 在本机因 Taro 类型入口缺失(`@tarojs/taro` 不在 @types)会报错,与本次改动无关;以 `taro build --type weapp` 为准。
另:如仍偶发,需确认微信公众平台后台已配置《隐私保护指引》并发布。

View File

@@ -13,12 +13,6 @@ definePageConfig({
type SelectedLocation = { lng: string; lat: string; name?: string; address?: string } type SelectedLocation = { lng: string; lat: string; name?: string; address?: string }
/** 检查是否为定位权限拒绝错误 */
const isLocationDenied = (e: any) => {
const msg = String(e?.errMsg || e?.message || e || '')
return msg.includes('auth deny') || msg.includes('authorize') || msg.includes('permission') || msg.includes('denied') || msg.includes('scope.userLocation')
}
const isUserCancel = (e: any) => { const isUserCancel = (e: any) => {
const msg = String(e?.errMsg || e?.message || e || '') const msg = String(e?.errMsg || e?.message || e || '')
return msg.includes('cancel') return msg.includes('cancel')
@@ -33,6 +27,71 @@ const hasValidLngLat = (addr?: Partial<ShopUserAddress> | null) => {
return true return true
} }
/** 确保已通过微信隐私授权(定位类接口前置要求);旧基础库无该接口时直接放行 */
const ensurePrivacyAuthorized = (): Promise<void> => {
return new Promise((resolve) => {
const wxAny: any = Taro
if (typeof wxAny.requirePrivacyAuthorize !== 'function') {
resolve()
return
}
if (typeof wxAny.getPrivacySetting === 'function') {
wxAny.getPrivacySetting({
success: (res: any) => {
if (res && res.needAuthorization) {
wxAny.requirePrivacyAuthorize({success: () => resolve(), fail: () => resolve()})
} else {
resolve()
}
},
fail: () => resolve(),
})
} else {
wxAny.requirePrivacyAuthorize({success: () => resolve(), fail: () => resolve()})
}
})
}
/** 预检并申请定位权限;已拒绝则引导去设置,返回最终是否拥有权限 */
const ensureLocationPermission = async (): Promise<boolean> => {
try {
const setting: any = await Taro.getSetting()
const auth = setting?.authSetting?.['scope.userLocation']
if (auth === true) return true
if (auth === false) {
const modal = await Taro.showModal({
title: '需要定位权限',
content: '选择定位需要开启定位权限,请在设置中开启后重试。',
confirmText: '去设置',
})
if (modal.confirm) {
await Taro.openSetting()
const setting2: any = await Taro.getSetting()
return setting2?.authSetting?.['scope.userLocation'] === true
}
return false
}
try {
await Taro.authorize({scope: 'scope.userLocation'})
return true
} catch {
const modal = await Taro.showModal({
title: '需要定位权限',
content: '选择定位需要开启定位权限,请在设置中开启后重试。',
confirmText: '去设置',
})
if (modal.confirm) {
await Taro.openSetting()
const setting2: any = await Taro.getSetting()
return setting2?.authSetting?.['scope.userLocation'] === true
}
return false
}
} catch {
return false
}
}
const AddressEditPage: React.FC = () => { const AddressEditPage: React.FC = () => {
const {id, fromWx} = useRouter().params const {id, fromWx} = useRouter().params
const isEditMode = !!id const isEditMode = !!id
@@ -171,96 +230,119 @@ const AddressEditPage: React.FC = () => {
} }
} }
/** 选择定位 */ /** 将地图选点结果应用到表单 */
const chooseGeoLocation = async () => { const applyChosenLocation = (res: any) => {
const applyChosenLocation = (res: any) => { if (!res) return
if (!res) return if (res.latitude === undefined || res.longitude === undefined) {
if (res.latitude === undefined || res.longitude === undefined) { Taro.showToast({title: '定位信息获取失败', icon: 'none'})
Taro.showToast({title: '定位信息获取失败', icon: 'none'}) return
return
}
const next: SelectedLocation = {
lng: String(res.longitude),
lat: String(res.latitude),
name: res.name,
address: res.address,
}
// 尝试从地图返回的地址文本解析省市区
const regionResult = res?.provinceName || res?.cityName || res?.adName
? {
province: String(res.provinceName || ''),
city: String(res.cityName || ''),
region: String(res.adName || '')
}
: parseRegion(String(res.address || ''))
const province = String(regionResult?.province || '').trim()
const city = String(regionResult?.city || '').trim()
const region = String(regionResult?.region || '').trim()
if (!province || !city || !region) {
Taro.showToast({title: '定位未识别到所在地区,请重新选择定位', icon: 'none'})
return
}
setSelectedLocation(next)
setRegionLocked(true)
// 将地图选点的地址同步到"收货地址"(剥离省市区,避免重复)
const rawAddr = String(res.address || '').trim()
const name = String(res.name || '').trim()
let detail = rawAddr
for (const part of [province, city, region]) {
if (part) detail = detail.replace(part, '')
}
detail = detail.replace(/[,]+/g, ' ').replace(/\s+/g, ' ').trim()
const base = detail || rawAddr
const nextDetailAddress = (() => {
if (!base && !name) return ''
if (!base) return name
if (!name) return base
return base.includes(name) ? base : `${base} ${name}`
})()
setFormData(prev => ({
...prev,
lng: next.lng,
lat: next.lat,
address: nextDetailAddress || prev.address,
province,
city,
region,
}))
setRegionText(`${province} ${city} ${region}`)
} }
const next: SelectedLocation = {
lng: String(res.longitude),
lat: String(res.latitude),
name: res.name,
address: res.address,
}
// 尝试从地图返回的地址文本解析省市区
const regionResult = res?.provinceName || res?.cityName || res?.adName
? {
province: String(res.provinceName || ''),
city: String(res.cityName || ''),
region: String(res.adName || '')
}
: parseRegion(String(res.address || ''))
const province = String(regionResult?.province || '').trim()
const city = String(regionResult?.city || '').trim()
const region = String(regionResult?.region || '').trim()
if (!province || !city || !region) {
Taro.showToast({title: '定位未识别到所在地区,请重新选择定位', icon: 'none'})
return
}
setSelectedLocation(next)
setRegionLocked(true)
// 将地图选点的地址同步到"收货地址"(剥离省市区,避免重复)
const rawAddr = String(res.address || '').trim()
const name = String(res.name || '').trim()
let detail = rawAddr
for (const part of [province, city, region]) {
if (part) detail = detail.replace(part, '')
}
detail = detail.replace(/[,]+/g, ' ').replace(/\s+/g, ' ').trim()
const base = detail || rawAddr
const nextDetailAddress = (() => {
if (!base && !name) return ''
if (!base) return name
if (!name) return base
return base.includes(name) ? base : `${base} ${name}`
})()
setFormData(prev => ({
...prev,
lng: next.lng,
lat: next.lat,
address: nextDetailAddress || prev.address,
province,
city,
region,
}))
setRegionText(`${province} ${city} ${region}`)
}
/** 选择定位 */
const chooseGeoLocation = async () => {
try { try {
// 1. 微信隐私授权(新版基础库强制,未同意会直接拒绝定位接口)
await ensurePrivacyAuthorized()
// 2. 定位权限预检 / 申请
const granted = await ensureLocationPermission()
if (!granted) {
Taro.showToast({title: '未获得定位权限,可手动选择地区', icon: 'none'})
if (!regionLocked) setRegionPickerVisible(true)
return
}
// 3. 仅在有有效经纬度时传入初始化参数,避免 undefined 触发部分机型失败
const initLat = selectedLocation?.lat ? Number(selectedLocation.lat) : undefined const initLat = selectedLocation?.lat ? Number(selectedLocation.lat) : undefined
const initLng = selectedLocation?.lng ? Number(selectedLocation.lng) : undefined const initLng = selectedLocation?.lng ? Number(selectedLocation.lng) : undefined
const latitude = typeof initLat === 'number' && Number.isFinite(initLat) ? initLat : undefined const latitude = typeof initLat === 'number' && Number.isFinite(initLat) ? initLat : undefined
const longitude = typeof initLng === 'number' && Number.isFinite(initLng) ? initLng : undefined const longitude = typeof initLng === 'number' && Number.isFinite(initLng) ? initLng : undefined
const res = await Taro.chooseLocation({latitude, longitude}) const params: any = {}
if (typeof latitude === 'number') params.latitude = latitude
if (typeof longitude === 'number') params.longitude = longitude
const res = await Taro.chooseLocation(params)
applyChosenLocation(res) applyChosenLocation(res)
} catch (e: any) { } catch (e: any) {
if (isUserCancel(e)) return if (isUserCancel(e)) return
if (isLocationDenied(e)) { // 4. 地图失败兜底:引导手动选择地区或重试,避免用户被卡死
try { Taro.showModal({
const modal = await Taro.showModal({ title: '地图打开失败',
title: '需要定位权限', content: '无法调起地图定位,您可以手动选择所在地区并填写详细地址,或稍后重试。',
content: '选择定位需要开启定位权限,请在设置中开启后重试。', confirmText: '手动选择',
confirmText: '去设置', cancelText: '再试一次',
}) }).then((modal) => {
if (modal.confirm) { if (modal.confirm) {
await Taro.openSetting() if (regionLocked) {
const res = await Taro.chooseLocation({}) Taro.showToast({title: '所在地区已由定位确定,修改请重新选择定位', icon: 'none'})
applyChosenLocation(res) } else {
setRegionPickerVisible(true)
} }
} catch (_e) { /* ignore */ } else {
Taro.chooseLocation({})
.then((res: any) => applyChosenLocation(res))
.catch(() => {
if (!regionLocked) setRegionPickerVisible(true)
})
} }
return }).catch(() => {
} if (!regionLocked) setRegionPickerVisible(true)
Taro.showToast({title: '打开地图失败,请重试', icon: 'none'}) })
} }
} }