| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- import baseConfig from '@/config/baseUrl.js';
- const {
- TIANDITU_KEY
- } = baseConfig;
- import {
- requestAndroidPermission
- } from '@/uni_modules/x-perm-apply-instr/js_sdk/index.js';
- class LocationService {
- /**
- * 初始化定位 (主入口)
- * 策略:优先 GPS -> 失败则 IP兜底 -> 统一逆地理编码
- * @returns {Promise<Object>} 返回最终的定位数据
- */
- async initLocation(type = 'gps') {
- console.log('--- 开始初始化定位 ---');
- try {
- let perId = 'android.permission.ACCESS_FINE_LOCATION',
- perm = uni.getStorageSync(`permision_${perId}`);
- // status 权限申请结果 (1:已获得权限, 0:拒绝本次申请, -1:永久拒绝申请)
- const perResult = [0, 1, -1].indexOf(perm) === -1 && type === 'gps' ? await requestAndroidPermission(
- perId, {
- title: '获取定位权限申请说明',
- content: '为了根据您的位置展示信息, 我们需要申请您设备的位置权限',
- }, true) : (perm || 0);
- // 通过权限结果走不通流程
- let gpsResult;
- if (perResult === 1) {
- // 优先尝试 GPS 精准定位, 竞速机制避免超时
- gpsResult = await Promise.race([this.getGPSLocation(), this.timePromise()]);
- } else {
- gpsResult = {
- status: 'fail'
- }
- }
- // 如果 GPS 定位失败(如用户拒绝权限或超时),则降级使用 IP 定位
- if (gpsResult.status !== 'success') {
- console.log('GPS定位失败或被拒绝,降级使用 IP 定位');
- await this.getIPLocation();
- }
- // 等待逆地理编码完成(获取城市名),没有cityCode才去查
- if (!uni.getStorageSync('cityCode')) {
- await this.updateGeocode();
- }
- // 确保数据已更新到 Storage,返回最新数据
- const finalLocation = uni.getStorageSync('positioning');
- return finalLocation;
- } catch (error) {
- console.error('定位流程异常:', error);
- // 即使报错,也尝试返回缓存中的旧数据,避免页面崩溃
- return uni.getStorageSync('positioning') || {};
- }
- }
- // 获取 IP 定位
- getIPLocation() {
- return new Promise(async (resolve) => {
- if (process.env.NODE_ENV === 'development') {
- let url = 'https://ip9.com.cn/get';
- // #ifdef H5
- // 正则匹配规则:覆盖 localhost、127.0.0.1、192.168.x.x、::1(IPv6回环)
- const localReg =
- /^(localhost|::1|127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})|192\.168\.(\d{1,3})\.(\d{1,3}))$/;
- if (localReg.test(window.location.hostname.toLowerCase())) {
- url = '/getIp/get';
- }
- // #endif
- uni.request({
- url: url,
- method: 'GET',
- success: (res) => {
- console.log(res);
- if (res.data.ret == 200) {
- this.updateData({
- latitude: res.data.data.lat,
- longitude: res.data.data.lng,
- source: 'ip',
- isRefuse: 1
- });
- }
- resolve();
- },
- fail: () => resolve() // 失败也 resolve,不阻断流程
- });
- } else {
- }
- });
- }
- // 获取 GPS 原生定位
- getGPSLocation() {
- return new Promise((resolve) => {
- uni.getLocation({
- // #ifdef H5
- type: 'wgs84',
- // #endif
- // #ifndef H5
- type: 'gcj02',
- // #endif
- geocode: true,
- isHighAccuracy: true,
- success: async (res) => {
- console.log('GPS 定位成功', res);
- this.updateData({
- latitude: res.latitude,
- longitude: res.longitude,
- source: 'gps',
- isRefuse: ''
- });
- resolve({
- status: 'success',
- data: res
- });
- },
- fail: (err) => {
- console.log('GPS 定位失败或拒绝', err);
- // 记录拒绝状态
- this.updateData({
- isRefuse: 1
- });
- resolve({
- status: 'fail',
- error: err
- });
- }
- });
- });
- }
- // 超时Promise
- timePromise(wait = 1000) {
- return new Promise((resolve) => {
- // 2秒超时阈值
- setTimeout(() => {
- // 超时后返回「超时标识」,方便后续区分
- resolve({
- status: 'fail',
- error: 'GPS定位超时'
- });
- }, wait);
- })
- }
- // 更新storage
- updateData(data) {
- const current = uni.getStorageSync('positioning') || {};
- uni.setStorageSync('positioning', {
- ...current,
- ...data
- });
- }
- // 天地图逆地理编码 (经纬度 -> 城市信息),e为搜索时传值
- updateGeocode(e) {
- return new Promise((resolve) => {
- let location = e || uni.getStorageSync('positioning');
- if (!location || !location.longitude || !location.latitude) {
- return resolve();
- }
- let postStr = {
- 'lon': location.longitude,
- 'lat': location.latitude,
- var: 1,
- };
- uni.request({
- url: `https://api.tianditu.gov.cn/geocoder?postStr=${encodeURIComponent(JSON.stringify(postStr))}&type=geocode&tk=${TIANDITU_KEY}`,
- success: (res) => {
- if (res.statusCode == 200 && res.data.status == 0) {
- let obj = res.data.result.addressComponent;
- // 经纬度所属城市,没有e说明是上面定位流程过来的
- if (!e) this.updateData({
- provinceCode: obj.province_code.slice(3),
- provinceName: obj.province,
- cityCode: obj.city_code.slice(3),
- cityName: obj.city,
- districtCode: obj.county_code.slice(3),
- districtName: obj.county,
- });
- console.log('逆地理编码更新成功:', obj.city);
- }
- resolve(res);
- },
- fail: () => resolve()
- });
- });
- }
- // 天地图地理编码 (城市信息 -> 经纬度)
- searchGeocode(e) {
- return new Promise((resolve) => {
- if (!e) {
- return resolve();
- }
- let postStr = {
- 'keyWord': e
- };
- uni.request({
- url: `https://api.tianditu.gov.cn/geocoder?ds=${encodeURIComponent(JSON.stringify(postStr))}&tk=${TIANDITU_KEY}`,
- success: (res) => {
- if (res.statusCode == 200 && res.data.status == 0) {
- let obj = res.data.location;
- this.updateGeocode({
- latitude: obj.lat,
- longitude: obj.lon,
- });
- console.log('地理编码更新成功:', obj.keyWord);
- }
- resolve();
- },
- fail: () => resolve()
- });
- });
- }
- // 跳转到对应平台的系统设置页面
- openSystemSettings() {
- console.log('--- 尝试打开系统设置,准备重新定位 ---');
- // #ifdef APP-PLUS
- // App 端:直接调用 uni.openAppAuthorizeSetting
- uni.openAppAuthorizeSetting({
- success(res) {
- console.log('App: 成功打开授权设置界面', res);
- },
- fail(err) {
- console.error('App: 打开授权设置界面失败', err);
- },
- complete: async () => {
- // 确保 App 重新获得焦点且系统状态已更新
- await new Promise(r => setTimeout(r, 500));
- // 重新执行完整的定位流程
- console.log('App: 从系统设置返回,重新执行 initLocation');
- await this.initLocation();
- }
- });
- // #endif
- // #ifdef MP
- // 小程序端:提示用户进入设置页。实际的小程序权限设置需要用户在页面内点击 open-type="openSetting" 按钮触发。
- uni.showModal({
- title: '授权提示',
- content: '请在小程序设置页中手动开启“位置信息”权限。',
- confirmText: '去设置',
- success: (res) => {
- if (res.confirm) {
- // 注意:uni.openSetting 在部分小程序平台可能已被废弃,且需要在用户交互后才能调用。
- // 最佳实践是引导用户去一个包含 <button open-type="openSetting"></button> 的页面。
- uni.openSetting({
- success: async (settingRes) => {
- console.log('MP: 用户完成设置', settingRes);
- if (settingRes.authSetting['scope.userLocation']) {
- // 权限开启,重新定位
- await this.initLocation();
- }
- },
- fail: () => {
- console.log('MP: 打开设置页失败');
- }
- });
- }
- }
- });
- // #endif
- // #ifdef H5
- // H5 端:通常无法直接打开系统设置,只能提示用户手动操作
- uni.showModal({
- title: '授权提示',
- content: '请在浏览器或系统设置中手动开启定位权限。',
- showCancel: false,
- confirmText: '知道了'
- });
- // #endif
- }
- }
- export default new LocationService();
|