fix(user): 修复地图选点打开失败及定位权限处理问题
- 移除旧的定位权限拒绝检测方法,改用微信隐私授权新接口保证兼容 - 新增确保微信隐私授权的函数,适配新版基础库及旧版本放行逻辑 - 增加定位权限预检和引导用户开启权限流程,支持设置页跳转回读授权状态 - 在调用 chooseLocation 时仅传入有效经纬度,避免部分机型失败 - 地图打开失败时增加弹窗提示,支持用户手动选择地区或重试打开地图 - 定位失败且已锁定地区时取消误触发地区选择器,提升用户体验
This commit is contained in:
15
.workbuddy/memory/2026-07-12.md
Normal file
15
.workbuddy/memory/2026-07-12.md
Normal 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` 为准。
|
||||||
|
另:如仍偶发,需确认微信公众平台后台已配置《隐私保护指引》并发布。
|
||||||
@@ -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'})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user