fix(visit): 修复客户拜访页定位时好时坏
根因:定位链路用区县「中文名」查 sys_area,重名区县(白云区=广州/贵阳、
朝阳区=北京/长春、南山区=深圳/鹤岗…)命中多条,后端 getOne 默认
throwEx=true 抛 TooManyResultsException,页面表现为「有时能定位、有时不能」
——取决于用户当前所在区县名是否唯一。
改动:
- 新增 util/geo.js(纯函数):buildGeocodeUrl / normalizeAdcode /
parseGeocodeResult / formatLocationText
· 天地图 postStr 补 URL 编码:实测未编码时接口返回 HTTP 400
「参数格式错误」(admin 端 merchantEdit.vue 已是正确写法)
· 严格校验响应结构,限流(429)/参数错误(400) 一律返回 null
· 补直辖市 city 为空时用省名兜底(天地图对上海/北京/天津/重庆返回 city:"")
- util/index.js:抽出 locateOnce / fetchGeocode / resolveAreaInfo 共用
· 清除两处模块作用域的 this.areaId 误用(严格模式下抛 TypeError 会让
Promise 永久悬空,locating 再也回不到 false,即「反复点没反应」)
· 优先按天地图 county_code(去 156 前缀 = sys_area.id)精确查询,
从根上绕开重名;按中文名仅作降级,并注释其歧义风险
· 行政区划查不到不再阻断定位:返回 null,仍给出经纬度与地址
(area_id/city_id/province_id 全仓库无消费方)
- api/request.js:get/del 不再吞掉非 2xx 的 reject,避免调用方状态永久悬空
- pages/visit/index.vue:fetchLocation 加 15s 超时兜底 + 运行号丢弃迟到结果,
保证失败后仍可重试;静默失败也给出可见提示
- 新增 util/geo.test.mjs(10 个单测),package.json 补 test 脚本
验证:前端单测 10/10;真实天地图 + 生产后端端到端 5 个重名区县全部可正常
定位(后端修复尚未部署即已生效)。
This commit is contained in:
+6
-12
@@ -76,21 +76,15 @@ const post = (url, formData, options) => {
|
||||
}
|
||||
|
||||
export const get = (url, options) => {
|
||||
// console.log(options)
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(url, options).then(res => {
|
||||
resolve(res.data)
|
||||
})
|
||||
})
|
||||
// 直接返回,不再用 new Promise 包一层纯转发:
|
||||
// http.get 成功时给出 {data},失败(非 2xx,如天地图 400/429)时 reject,
|
||||
// 链式 then 会让错误自然向下传递。若吞掉 reject,调用方的 loading /
|
||||
// locating 等状态将再也回不到初始值,表现为「点了没反应」。
|
||||
return http.get(url, options).then(res => res.data)
|
||||
}
|
||||
|
||||
export const del = (url, options) => {
|
||||
// console.log(options)
|
||||
return new Promise((resolve, reject) => {
|
||||
http.delete(url, options).then(res => {
|
||||
resolve(res.data)
|
||||
})
|
||||
})
|
||||
return http.delete(url, options).then(res => res.data)
|
||||
}
|
||||
|
||||
export const put = (url, formData, options) => {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "node --test \"util/*.test.mjs\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
+25
-5
@@ -281,6 +281,8 @@ export default {
|
||||
showVisitDatePicker: false,
|
||||
loading: false,
|
||||
locating: false,
|
||||
// 定位运行号:用于丢弃超时后迟到的旧定位结果(见 fetchLocation)
|
||||
locateRun: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -373,29 +375,47 @@ export default {
|
||||
return `${label}:${value}`
|
||||
},
|
||||
async fetchLocation(silent = false, force = false) {
|
||||
// 兜底:即使底层定位 Promise 因意外情况迟迟不结算,也不会把 locating
|
||||
// 永久卡在 true(那正是「反复点定位没反应」的成因)。超时后释放标志,
|
||||
// 用户可再次点击重试。
|
||||
if (this.locating) return
|
||||
if (!force && this.visitForm.location && this.visitForm.lat && this.visitForm.lng) return
|
||||
this.locating = true
|
||||
// 每次定位分配一个运行号:超时释放后用户可能立刻重试,此时上一次调用
|
||||
// 仍在后台跑(Promise.race 无法取消它)。用运行号丢弃迟到结果,
|
||||
// 避免旧结果覆盖新定位、或重复弹出失败提示。
|
||||
const runId = ++this.locateRun
|
||||
if (!silent) {
|
||||
uni.showLoading({
|
||||
title: '定位中'
|
||||
})
|
||||
}
|
||||
let timeoutId = null
|
||||
try {
|
||||
const res = await getLocationOnly()
|
||||
const res = await Promise.race([
|
||||
getLocationOnly(),
|
||||
new Promise((resolve, reject) => {
|
||||
timeoutId = setTimeout(() => reject(new Error('定位超时')), 15000)
|
||||
})
|
||||
])
|
||||
if (runId !== this.locateRun) return
|
||||
if (!res) throw new Error('定位结果为空')
|
||||
this.visitForm.lat = res.latitude
|
||||
this.visitForm.lng = res.longitude
|
||||
this.visitForm.location = res.full || [res.province_str, res.city_str, res.area_str].filter(Boolean).join(' ')
|
||||
} catch (err) {
|
||||
if (!silent) {
|
||||
if (runId !== this.locateRun) return
|
||||
// 静默失败(进页面自动定位)也要给出可见提示,否则用户只看到空白
|
||||
// 定位栏,不知道发生了什么、也不知道可以点。
|
||||
uni.showToast({
|
||||
title: '定位失败',
|
||||
title: silent ? '定位失败,请点击定位信息重试' : '定位失败,请点击重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
if (timeoutId) clearTimeout(timeoutId)
|
||||
if (!silent) uni.hideLoading()
|
||||
this.locating = false
|
||||
// 仅在仍是当前运行号时释放,避免把新一轮定位的 locating 提前清掉
|
||||
if (runId === this.locateRun) this.locating = false
|
||||
}
|
||||
},
|
||||
async uploadPhotos() {
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 定位相关的纯函数集合。
|
||||
*
|
||||
* 这里刻意不 import uni / 任何 API 封装:定位链路最容易出错的正是
|
||||
* 「URL 编码」与「行政区划编码归一化」这两段纯逻辑,抽出来才能被
|
||||
* node --test 直接覆盖(见 geo.test.mjs)。
|
||||
*
|
||||
* 背景(客户拜访页「有时能定位、有时不能」):
|
||||
* - 天地图 geocoder 的 postStr 未编码时返回 HTTP 400「参数格式错误」;
|
||||
* - 用区县中文名去查 sys_area 时,重名区县(白云区=广州/贵阳、
|
||||
* 朝阳区=北京/长春…)会命中多条并抛 TooManyResultsException。
|
||||
* 而天地图返回的 county_code 去掉 156 前缀即国标 adcode(= sys_area.id),
|
||||
* 可直接精确命中,绕开重名。
|
||||
*/
|
||||
|
||||
/** 天地图 geocoder 逆地理编码接口。tk 与 admin 端(merchantEdit.vue)保持同一体系。 */
|
||||
const TIANDITU_GEOCODER = 'https://api.tianditu.gov.cn/geocoder'
|
||||
const TIANDITU_TK = '1099ec280e86f4e9b632173326cde90b'
|
||||
|
||||
/** 中国行政区划编码的国别前缀(天地图返回形如 156440104)。 */
|
||||
const CHINA_PREFIX = '156'
|
||||
|
||||
/**
|
||||
* 构造天地图逆地理编码请求 URL。
|
||||
*
|
||||
* postStr 必须整体 encodeURIComponent:实测未编码时该接口返回
|
||||
* HTTP 400 {"msg":"参数格式错误"},因为其中的 { } ' : 会破坏查询串结构。
|
||||
*
|
||||
* @param {number|string} longitude 经度
|
||||
* @param {number|string} latitude 纬度
|
||||
* @returns {string} 完整请求 URL
|
||||
*/
|
||||
export const buildGeocodeUrl = (longitude, latitude) => {
|
||||
const postStr = `{'lon':${longitude},'lat':${latitude},'ver':1}`
|
||||
return `${TIANDITU_GEOCODER}?postStr=${encodeURIComponent(postStr)}&type=geocode&tk=${TIANDITU_TK}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化行政区划编码:去掉天地图的 156 前缀,得到 6 位国标 adcode。
|
||||
*
|
||||
* 说明:156 是天地图返回的国别码(中国)。实测大陆与港澳的 county_code
|
||||
* 均带该前缀(越秀区 156440104、香港油尖旺区 156810109),而境外坐标
|
||||
* 天地图不返回 county,因此调用方只会在中国境内走到这里。
|
||||
* 本函数只做「长度 + 数字」校验,不做国别语义推断:无法识别时返回空串,
|
||||
* 由调用方降级为按名字查,绝不把脏值当成合法编码去查库。
|
||||
*
|
||||
* @param {string|number} value county_code(勿传 province_code/city_code:
|
||||
* 它们同样能被解析成 6 位码,但那不是区县,会导致命中省市级)
|
||||
* @returns {string} 6 位 adcode;无法识别时返回 ''
|
||||
*/
|
||||
export const normalizeAdcode = value => {
|
||||
if (value === null || value === undefined) return ''
|
||||
const raw = String(value).trim()
|
||||
if (!raw) return ''
|
||||
const digits = raw.startsWith(CHINA_PREFIX) ? raw.slice(CHINA_PREFIX.length) : raw
|
||||
return /^\d{6}$/.test(digits) ? digits : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析天地图逆地理编码响应。
|
||||
*
|
||||
* 天地图在失败时并不总是返回非 2xx:限流返回 429、参数错误返回 400,
|
||||
* 也见过 HTTP 200 但 body 是 {"msg":"参数格式错误"} 的情况。
|
||||
* 因此这里严格校验结构,任何异常一律返回 null,绝不让调用方把错误当成定位成功。
|
||||
*
|
||||
* @param {object} payload 接口响应体(已 JSON 解析)
|
||||
* @returns {{province:string,city:string,county:string,adcode:string,
|
||||
* address:string}|null}
|
||||
*/
|
||||
export const parseGeocodeResult = payload => {
|
||||
if (!payload || typeof payload !== 'object') return null
|
||||
const result = payload.result
|
||||
if (!result || typeof result !== 'object') return null
|
||||
const comp = result.addressComponent
|
||||
if (!comp || typeof comp !== 'object') return null
|
||||
|
||||
const province = comp.province || ''
|
||||
const county = comp.county || ''
|
||||
// 必须有区县名才算有效结果:调用方按名字降级查询时依赖它
|
||||
if (!county) return null
|
||||
|
||||
// 实测天地图对直辖市(上海/北京/天津/重庆)返回 city:"",
|
||||
// 而 sys_area 中这些区县的 city 是「上海市」这类省名。用省名兜底,
|
||||
// 否则 currentCity 会变成空串,影响后续按城市筛选。
|
||||
const city = comp.city || province
|
||||
|
||||
return {
|
||||
province,
|
||||
city,
|
||||
county,
|
||||
adcode: normalizeAdcode(comp.county_code),
|
||||
address: result.formatted_address || ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装用于展示与落库的地址文本。
|
||||
* 优先用天地图的完整地址,退化为「省+市+区县」拼接。
|
||||
*
|
||||
* @param {object} geo parseGeocodeResult 的返回值
|
||||
* @returns {string}
|
||||
*/
|
||||
export const formatLocationText = geo => {
|
||||
if (!geo) return ''
|
||||
if (geo.address) return geo.address
|
||||
return [geo.province, geo.city, geo.county].filter(Boolean).join('')
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import {
|
||||
buildGeocodeUrl,
|
||||
normalizeAdcode,
|
||||
parseGeocodeResult
|
||||
} from './geo.js'
|
||||
|
||||
/**
|
||||
* 回归测试:客户拜访页定位链路的两处确定性缺陷。
|
||||
*
|
||||
* 1) 天地图 geocoder 的 postStr 未编码 —— 实测同一 key:
|
||||
* 原样 {'lon':..} -> HTTP 400 参数格式错误
|
||||
* 仅编码引号 {%27lon%27:..} -> HTTP 400
|
||||
* 花括号/冒号编码(encodeURI) -> HTTP 200
|
||||
* 全编码(admin 端写法) -> HTTP 200
|
||||
* 所以必须确保 postStr 经过完整 URL 编码。
|
||||
*
|
||||
* 2) 行政区划按「中文名」查询在重名区县上必然失败(白云区=广州/贵阳、
|
||||
* 朝阳区=北京/长春…)。天地图已直接返回 county_code,其去 156 前缀
|
||||
* 即 sys_area.id(实测:越秀区 156440104→440104 与库中 id 一致),
|
||||
* 可直接精确命中,绕开重名。
|
||||
*/
|
||||
|
||||
// ==================== buildGeocodeUrl ====================
|
||||
|
||||
test('buildGeocodeUrl:postStr 必须被 URL 编码(不得残留裸花括号,否则接口返回 400)', () => {
|
||||
const url = buildGeocodeUrl(113.2644, 23.1291)
|
||||
|
||||
const query = url.slice(url.indexOf('?') + 1)
|
||||
const encodedPostStr = query.split('&').find(p => p.startsWith('postStr='))
|
||||
|
||||
// 解码后应还原为天地图要求的原始形态
|
||||
assert.equal(
|
||||
decodeURIComponent(encodedPostStr.slice('postStr='.length)),
|
||||
"{'lon':113.2644,'lat':23.1291,'ver':1}"
|
||||
)
|
||||
|
||||
// 关键:裸的 { } 必须被编码。未编码时接口实测返回 HTTP 400「参数格式错误」。
|
||||
// 注意单引号 ' 按 RFC 3986 属 unreserved 之外的子分隔符,encodeURIComponent
|
||||
// 不会编码它;实测该形态(花括号/冒号已编码、引号保留)接口返回 HTTP 200,
|
||||
// 因此这里只断言花括号与冒号必须被编码。
|
||||
assert.ok(!/[{}]/.test(encodedPostStr), `花括号未被编码: ${encodedPostStr}`)
|
||||
assert.ok(!encodedPostStr.includes(':'), `冒号未被编码: ${encodedPostStr}`)
|
||||
})
|
||||
|
||||
test('buildGeocodeUrl:经纬度与 tk 参数齐全', () => {
|
||||
const url = buildGeocodeUrl(121.4737, 31.2304)
|
||||
|
||||
assert.match(url, /^https:\/\/api\.tianditu\.gov\.cn\/geocoder\?/)
|
||||
assert.ok(url.includes('type=geocode'))
|
||||
assert.ok(url.includes('tk='))
|
||||
assert.ok(decodeURIComponent(url).includes("'lon':121.4737"))
|
||||
assert.ok(decodeURIComponent(url).includes("'lat':31.2304"))
|
||||
})
|
||||
|
||||
test('buildGeocodeUrl:负数/小数坐标不应破坏参数结构', () => {
|
||||
const url = buildGeocodeUrl(-73.9857, 40.7484)
|
||||
const query = url.slice(url.indexOf('?') + 1)
|
||||
|
||||
// 恰好 3 个参数,说明坐标里的符号没有把它撑开
|
||||
assert.equal(query.split('&').length, 3)
|
||||
assert.ok(decodeURIComponent(url).includes("'lon':-73.9857"))
|
||||
})
|
||||
|
||||
// ==================== normalizeAdcode ====================
|
||||
|
||||
test('normalizeAdcode:去掉天地图的 156(中国)前缀,得到 6 位 adcode', () => {
|
||||
assert.equal(normalizeAdcode('156440104'), '440104') // 广州越秀区
|
||||
assert.equal(normalizeAdcode('156520113'), '520113') // 贵阳白云区
|
||||
assert.equal(normalizeAdcode('156310101'), '310101') // 上海黄浦区
|
||||
})
|
||||
|
||||
test('normalizeAdcode:已是 6 位裸码时原样返回', () => {
|
||||
assert.equal(normalizeAdcode('440104'), '440104')
|
||||
assert.equal(normalizeAdcode(440104), '440104')
|
||||
assert.equal(normalizeAdcode(' 440104 '), '440104')
|
||||
})
|
||||
|
||||
test('normalizeAdcode:非法值返回空串,绝不产出错误编码', () => {
|
||||
for (const bad of [null, undefined, '', ' ', 'abc', '156', '12345', {}, []]) {
|
||||
assert.equal(normalizeAdcode(bad), '', `应拒绝非法输入: ${JSON.stringify(bad)}`)
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== parseGeocodeResult ====================
|
||||
|
||||
const successPayload = {
|
||||
status: '0',
|
||||
msg: 'ok',
|
||||
result: {
|
||||
formatted_address: '广东省广州市越秀区北京街道禺山社区北京路374号',
|
||||
addressComponent: {
|
||||
province: '广东省',
|
||||
city: '广州市',
|
||||
county: '越秀区',
|
||||
county_code: '156440104',
|
||||
city_code: '156440100',
|
||||
province_code: '156440000'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('parseGeocodeResult:正常响应解析出省/市/区县/adcode/地址', () => {
|
||||
const geo = parseGeocodeResult(successPayload)
|
||||
|
||||
assert.equal(geo.province, '广东省')
|
||||
assert.equal(geo.city, '广州市')
|
||||
assert.equal(geo.county, '越秀区')
|
||||
assert.equal(geo.adcode, '440104')
|
||||
assert.equal(geo.address, '广东省广州市越秀区北京街道禺山社区北京路374号')
|
||||
})
|
||||
|
||||
test('parseGeocodeResult:直辖市 city 为空时用省级名兜底(与 sys_area 一致)', () => {
|
||||
// 实测天地图对上海/北京/天津/重庆返回 city="" ,而 sys_area 中黄浦区的 city 是「上海市」
|
||||
const shanghai = {
|
||||
status: '0',
|
||||
result: {
|
||||
formatted_address: '上海市黄浦区人民广场',
|
||||
addressComponent: {
|
||||
province: '上海市',
|
||||
city: '',
|
||||
county: '黄浦区',
|
||||
county_code: '156310101',
|
||||
city_code: '',
|
||||
province_code: '156310000'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const geo = parseGeocodeResult(shanghai)
|
||||
|
||||
assert.equal(geo.city, '上海市', '直辖市应以省名兜底,避免 city_str 为空导致筛选失效')
|
||||
assert.equal(geo.county, '黄浦区')
|
||||
assert.equal(geo.adcode, '310101')
|
||||
})
|
||||
|
||||
test('parseGeocodeResult:天地图的各类错误响应一律返回 null(不得当成成功)', () => {
|
||||
const failures = [
|
||||
{ msg: '参数格式错误', status: 400 }, // postStr 未编码
|
||||
{ code: 301012, msg: '权限类型错误' }, // key 类型不符
|
||||
{ code: 302010, msg: '该tk已限流' }, // 限流
|
||||
{ status: '0' }, // 缺 result
|
||||
{ result: {} }, // 缺 addressComponent
|
||||
{},
|
||||
null,
|
||||
undefined
|
||||
]
|
||||
|
||||
for (const payload of failures) {
|
||||
assert.equal(parseGeocodeResult(payload), null,
|
||||
`错误响应必须返回 null: ${JSON.stringify(payload)}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('parseGeocodeResult:adcode 缺失时不崩,且不产生假编码', () => {
|
||||
const geo = parseGeocodeResult({
|
||||
status: '0',
|
||||
result: {
|
||||
formatted_address: '某地',
|
||||
addressComponent: { province: '某省', city: '某市', county: '某区' }
|
||||
}
|
||||
})
|
||||
|
||||
assert.equal(geo.adcode, '', '缺 county_code 时应为空串,由调用方降级为按名字查')
|
||||
assert.equal(geo.county, '某区')
|
||||
})
|
||||
+97
-75
@@ -2,6 +2,7 @@ import {getLocation, getUserInfo, setLocation} from "@/util/user";
|
||||
import {API, MAIN_API} from "@/config";
|
||||
import {get} from "@/api/request";
|
||||
import {areaInfoByNameReq, areaInfoReq} from "@/api/common";
|
||||
import {buildGeocodeUrl, formatLocationText, parseGeocodeResult} from "./geo";
|
||||
import dayjs from "dayjs";
|
||||
import md5 from "md5";
|
||||
|
||||
@@ -117,41 +118,93 @@ export const uploadFileToAliOss = async (filePath) => {
|
||||
}
|
||||
|
||||
|
||||
export const getLocationAction = (save = true) => {
|
||||
/**
|
||||
* 调天地图逆地理编码,返回结构化结果(见 util/geo.js)。
|
||||
* 网络失败 / 限流(429) / 参数错误(400) 一律 reject,不再静默吞掉。
|
||||
*/
|
||||
const fetchGeocode = async (longitude, latitude) => {
|
||||
const body = await get(buildGeocodeUrl(longitude, latitude))
|
||||
const geo = parseGeocodeResult(body)
|
||||
if (!geo) throw new Error('天地图逆地理编码返回异常')
|
||||
return geo
|
||||
}
|
||||
|
||||
/**
|
||||
* 把天地图结果映射到 sys_area,取 area_id / city_id / province_id。
|
||||
*
|
||||
* 关键修复:**优先按 adcode 精确查**(天地图 county_code 去 156 前缀即
|
||||
* sys_area.id),从根上绕开区县重名问题。
|
||||
*
|
||||
* 降级说明:拿不到 adcode 时才按中文名查,此时若区县重名(如「南山区」
|
||||
* 深圳/鹤岗都有),后端只能稳定返回 id 最小的那条,**可能不是用户实际
|
||||
* 所在的那个区县**。因此这里的结果仅用于 area_id/city_id/province_id;
|
||||
* 用于展示的省市区文本一律取天地图原始字段(geo.province/city/county),
|
||||
* 不受该歧义影响。
|
||||
*
|
||||
* 无论哪种方式查不到都返回 null 而不是让整条定位失败——页面提交只需要
|
||||
* 经纬度与地址文本,行政区划编码缺失不应阻断用户。
|
||||
*/
|
||||
const resolveAreaInfo = async geo => {
|
||||
if (geo.adcode) {
|
||||
const byCode = await areaInfoByNameReq({id: Number(geo.adcode)}).catch(() => null)
|
||||
if (byCode && byCode.data) return byCode.data
|
||||
}
|
||||
if (geo.county) {
|
||||
// 有歧义风险(见上),仅作为 adcode 缺失时的兜底
|
||||
const byName = await areaInfoByNameReq({name: geo.county}).catch(() => null)
|
||||
if (byName && byName.data) return byName.data
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 引导用户开启定位授权。用户此前已拒绝过时 authorize 会直接失败,这里兜底提示。 */
|
||||
const showLocationSettingGuide = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '为了更好的提供服务,请开启定位服务',
|
||||
success: ({confirm}) => {
|
||||
if (confirm) uni.openSetting({})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位一次并返回结果:授权 → 取经纬度 → 逆地理编码 → 映射行政区划。
|
||||
*
|
||||
* 所有失败路径都显式 reject,保证调用方的 await 一定结算。
|
||||
* (原实现在 catch 回调里用了模块作用域的 this.areaId,在严格模式下会抛
|
||||
* TypeError 并让 Promise 永久悬空,调用方的 locating 标志再也回不到 false,
|
||||
* 表现为「点定位没反应、反复点也没用」。)
|
||||
*
|
||||
* @param {boolean} save 是否写入本地缓存(首页等复用缓存时用)
|
||||
*/
|
||||
const locateOnce = ({save = false} = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const res = getLocation()
|
||||
if (res.area_id) resolve(res)
|
||||
else {
|
||||
uni.authorize({
|
||||
scope: 'scope.userLocation',
|
||||
success: () => {
|
||||
uni.getLocation({
|
||||
isHighAccuracy: true,
|
||||
success: async ({longitude, latitude}) => {
|
||||
const {result} = await get(`https://api.tianditu.gov.cn/geocoder?postStr={'lon':${longitude},'lat':${latitude},'ver':1}&type=geocode&tk=1099ec280e86f4e9b632173326cde90b`)
|
||||
const areaRes = await areaInfoByNameReq({name: result.addressComponent.county}).catch(() => {
|
||||
if (!this.areaId) reject(false)
|
||||
})
|
||||
if (save) setLocation({
|
||||
try {
|
||||
const geo = await fetchGeocode(longitude, latitude)
|
||||
const area = await resolveAreaInfo(geo)
|
||||
const payload = {
|
||||
longitude,
|
||||
latitude,
|
||||
province_str: result.addressComponent.province,
|
||||
city_str: result.addressComponent.city,
|
||||
area_str: result.addressComponent.county,
|
||||
area_id: areaRes.data.id,
|
||||
city_id: areaRes.data.fid,
|
||||
province_id: areaRes.data.city.fid
|
||||
})
|
||||
resolve({
|
||||
longitude,
|
||||
latitude,
|
||||
area_id: areaRes.data.id,
|
||||
city_id: areaRes.data.fid,
|
||||
province_id: areaRes.data.city.fid,
|
||||
province_str: result.addressComponent.province,
|
||||
city_str: result.addressComponent.city,
|
||||
area_str: result.addressComponent.county,
|
||||
})
|
||||
area_id: area ? area.id : '',
|
||||
city_id: area ? area.fid : '',
|
||||
province_id: area && area.city ? area.city.fid : '',
|
||||
province_str: geo.province,
|
||||
city_str: geo.city,
|
||||
area_str: geo.county,
|
||||
full: formatLocationText(geo)
|
||||
}
|
||||
if (save) setLocation(payload)
|
||||
resolve(payload)
|
||||
} catch (error) {
|
||||
reject(false)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
reject(false)
|
||||
@@ -159,17 +212,20 @@ export const getLocationAction = (save = true) => {
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '为了更好的提供服务,请开启定位服务',
|
||||
success: ({confirm}) => {
|
||||
if (confirm) uni.openSetting()
|
||||
showLocationSettingGuide()
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const getLocationAction = (save = true) => {
|
||||
// 保持原有判据:缓存中已有 area_id 才复用。刻意不改判据,避免悄悄改变
|
||||
// 首页/业绩/分享/我的这 4 个调用方的刷新行为(它们只消费 city_str,但
|
||||
// 「有坐标就跳过重定位」会让跨城移动后的 currentCity 长期陈旧)。
|
||||
const cached = getLocation()
|
||||
if (cached.area_id) return Promise.resolve(cached)
|
||||
return locateOnce({save})
|
||||
}
|
||||
|
||||
export const chooseVideo = (sourceType = ['album', 'camera']) => {
|
||||
@@ -221,48 +277,14 @@ export const chooseVideo = (sourceType = ['album', 'camera']) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const getLocationOnly = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.authorize({
|
||||
scope: 'scope.userLocation',
|
||||
success: () => {
|
||||
uni.getLocation({
|
||||
isHighAccuracy: true,
|
||||
success: async ({longitude, latitude}) => {
|
||||
const {result} = await get(`https://api.tianditu.gov.cn/geocoder?postStr={'lon':${longitude},'lat':${latitude},'ver':1}&type=geocode&tk=1099ec280e86f4e9b632173326cde90b`)
|
||||
const {data} = await areaInfoByNameReq({name: result.addressComponent.county}).catch(() => {
|
||||
if (!this.areaId) reject(false)
|
||||
})
|
||||
resolve({
|
||||
longitude,
|
||||
latitude,
|
||||
area_id: data.id,
|
||||
city_id: data.fid,
|
||||
province_id: data.city.fid,
|
||||
province_str: result.addressComponent.province,
|
||||
city_str: result.addressComponent.city,
|
||||
area_str: result.addressComponent.county,
|
||||
full: result.formatted_address
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.setStorageSync("no_more_realtime_location", 1)
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: err => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '为了更好的提供服务,请开启定位服务',
|
||||
success: ({confirm}) => {
|
||||
if (confirm) uni.openSetting()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 强制实时定位(不读缓存),供拜访页等「每次都要当前位置」的场景使用。
|
||||
*
|
||||
* 原实现的问题:把天地图 URL 的 postStr 裸拼在查询串里(实测接口返回
|
||||
* HTTP 400),且 catch 回调里误用 this.areaId 导致 Promise 可能永不结算。
|
||||
* 现已统一走 locateOnce,保证一定 reject。
|
||||
*/
|
||||
export const getLocationOnly = () => locateOnce({save: false})
|
||||
|
||||
|
||||
export const getExpressMode = () => parseInt(uni.getStorageSync("express_mode"))
|
||||
|
||||
Reference in New Issue
Block a user