847 lines
24 KiB
JavaScript
847 lines
24 KiB
JavaScript
|
|
// +----------------------------------------------------------------------
|
|||
|
|
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
|||
|
|
// +----------------------------------------------------------------------
|
|||
|
|
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
|||
|
|
// +----------------------------------------------------------------------
|
|||
|
|
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
|||
|
|
// +----------------------------------------------------------------------
|
|||
|
|
// | Author: CRMEB Team <admin@crmeb.com>
|
|||
|
|
// +----------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
import permision from "./permission.js"
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 全局定位权限管理和数据缓存
|
|||
|
|
*/
|
|||
|
|
class LocationManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.locationData = null;
|
|||
|
|
this.hasLocationPermission = false;
|
|||
|
|
this.permissionStatus = 0; // 0: 未检查, 1: 已允许, 2: 拒绝, 3: 系统未开启
|
|||
|
|
this.isChecking = false;
|
|||
|
|
this.listeners = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化定位管理器
|
|||
|
|
*/
|
|||
|
|
init() {
|
|||
|
|
// 从缓存中恢复定位数据
|
|||
|
|
this.restoreLocationData();
|
|||
|
|
// 检查权限状态
|
|||
|
|
this.checkInitialPermission();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从缓存中恢复定位数据
|
|||
|
|
*/
|
|||
|
|
restoreLocationData() {
|
|||
|
|
try {
|
|||
|
|
// 优先使用新的对象缓存格式
|
|||
|
|
const cachedLocationInfo = uni.getStorageSync('location_info');
|
|||
|
|
if (cachedLocationInfo && typeof cachedLocationInfo === 'object') {
|
|||
|
|
this.locationData = {
|
|||
|
|
latitude: parseFloat(cachedLocationInfo.latitude),
|
|||
|
|
longitude: parseFloat(cachedLocationInfo.longitude),
|
|||
|
|
name: cachedLocationInfo.name || '',
|
|||
|
|
address: cachedLocationInfo.address || '',
|
|||
|
|
updateTime: cachedLocationInfo.updateTime || 0,
|
|||
|
|
source: cachedLocationInfo.source || '', // 恢复数据来源标识
|
|||
|
|
type: cachedLocationInfo.type || '' // 恢复位置类型
|
|||
|
|
};
|
|||
|
|
this.hasLocationPermission = cachedLocationInfo.hasPermission || false;
|
|||
|
|
this.permissionStatus = cachedLocationInfo.hasPermission ? 1 : 0;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 兼容旧版本的独立缓存项
|
|||
|
|
const latitude = uni.getStorageSync('user_latitude');
|
|||
|
|
const longitude = uni.getStorageSync('user_longitude');
|
|||
|
|
const locationName = uni.getStorageSync('user_location_name');
|
|||
|
|
const locationAddress = uni.getStorageSync('user_location_address');
|
|||
|
|
const updateTime = uni.getStorageSync('location_update_time');
|
|||
|
|
const hasPermission = uni.getStorageSync('has_location_permission');
|
|||
|
|
|
|||
|
|
if (latitude && longitude) {
|
|||
|
|
this.locationData = {
|
|||
|
|
latitude: parseFloat(latitude),
|
|||
|
|
longitude: parseFloat(longitude),
|
|||
|
|
name: locationName || '',
|
|||
|
|
address: locationAddress || '',
|
|||
|
|
updateTime: updateTime || 0
|
|||
|
|
};
|
|||
|
|
this.hasLocationPermission = !!hasPermission;
|
|||
|
|
this.permissionStatus = hasPermission ? 1 : 0;
|
|||
|
|
|
|||
|
|
// 迁移到新格式并清除旧缓存
|
|||
|
|
this.migrateToNewFormat();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 迁移旧格式缓存到新格式
|
|||
|
|
*/
|
|||
|
|
migrateToNewFormat() {
|
|||
|
|
try {
|
|||
|
|
|
|||
|
|
// 保存到新格式
|
|||
|
|
this.saveLocationDataToCache();
|
|||
|
|
|
|||
|
|
// 清除旧格式缓存
|
|||
|
|
uni.removeStorageSync('user_latitude');
|
|||
|
|
uni.removeStorageSync('user_longitude');
|
|||
|
|
uni.removeStorageSync('user_location_name');
|
|||
|
|
uni.removeStorageSync('user_location_address');
|
|||
|
|
uni.removeStorageSync('location_update_time');
|
|||
|
|
uni.removeStorageSync('has_location_permission');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 保存位置数据到缓存(使用对象格式)
|
|||
|
|
*/
|
|||
|
|
saveLocationDataToCache() {
|
|||
|
|
try {
|
|||
|
|
const locationInfo = {
|
|||
|
|
latitude: this.locationData?.latitude || 0,
|
|||
|
|
longitude: this.locationData?.longitude || 0,
|
|||
|
|
name: this.locationData?.name || '',
|
|||
|
|
address: this.locationData?.address || '',
|
|||
|
|
updateTime: this.locationData?.updateTime || Date.now(),
|
|||
|
|
hasPermission: this.hasLocationPermission,
|
|||
|
|
permissionStatus: this.permissionStatus,
|
|||
|
|
source: this.locationData?.source || '', // 保存数据来源标识
|
|||
|
|
type: this.locationData?.type || '', // 保存位置类型
|
|||
|
|
version: '2.0' // 版本标识
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
uni.setStorageSync('location_info', locationInfo);
|
|||
|
|
} catch (error) {
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查初始权限状态
|
|||
|
|
*/
|
|||
|
|
async checkInitialPermission() {
|
|||
|
|
if (this.isChecking) return this.permissionStatus;
|
|||
|
|
|
|||
|
|
this.isChecking = true;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let status = 0;
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
status = permision.isIOS ?
|
|||
|
|
await permision.requestIOS('location') :
|
|||
|
|
await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef MP
|
|||
|
|
status = await this.getSetting();
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5环境下通过尝试获取定位来判断权限
|
|||
|
|
try {
|
|||
|
|
await this.testLocationAccess();
|
|||
|
|
status = 1;
|
|||
|
|
} catch (error) {
|
|||
|
|
status = 2;
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
this.permissionStatus = status;
|
|||
|
|
this.hasLocationPermission = status === 1;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
this.permissionStatus = 2;
|
|||
|
|
this.hasLocationPermission = false;
|
|||
|
|
} finally {
|
|||
|
|
this.isChecking = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.permissionStatus;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 测试定位访问权限(H5环境)
|
|||
|
|
*/
|
|||
|
|
testLocationAccess() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// #ifdef H5
|
|||
|
|
if (navigator.geolocation) {
|
|||
|
|
navigator.geolocation.getCurrentPosition(
|
|||
|
|
(position) => resolve(position),
|
|||
|
|
(error) => {
|
|||
|
|
reject(error);
|
|||
|
|
},
|
|||
|
|
{ timeout: 10000, enableHighAccuracy: false, maximumAge: 60000 }
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
reject(new Error('Geolocation not supported'));
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifndef H5
|
|||
|
|
// 非H5环境直接resolve,由其他逻辑处理
|
|||
|
|
resolve(true);
|
|||
|
|
// #endif
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取小程序定位权限设置
|
|||
|
|
*/
|
|||
|
|
getSetting() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
// #ifdef MP
|
|||
|
|
uni.getSetting({
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.authSetting['scope.userLocation'] === undefined) {
|
|||
|
|
resolve(0); // 未询问
|
|||
|
|
} else if (res.authSetting['scope.userLocation']) {
|
|||
|
|
resolve(1); // 已允许
|
|||
|
|
} else {
|
|||
|
|
resolve(2); // 已拒绝
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: () => resolve(2)
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifndef MP
|
|||
|
|
resolve(0);
|
|||
|
|
// #endif
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 实时检查系统权限状态(不依赖缓存)
|
|||
|
|
*/
|
|||
|
|
async checkRealTimePermission() {
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let status = 0;
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
status = permision.isIOS ?
|
|||
|
|
await permision.requestIOS('location') :
|
|||
|
|
await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef MP
|
|||
|
|
status = await this.getSetting();
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5环境下通过实际尝试获取定位来检查权限
|
|||
|
|
try {
|
|||
|
|
await this.testLocationAccess();
|
|||
|
|
status = 1;
|
|||
|
|
} catch (error) {
|
|||
|
|
status = 2;
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
return status;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
return 2; // 检查失败视为无权限
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 请求定位权限并获取位置信息
|
|||
|
|
*/
|
|||
|
|
async requestLocationPermission(showTip = true) {
|
|||
|
|
if (this.isChecking) return false;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const permissionStatus = await this.checkInitialPermission();
|
|||
|
|
|
|||
|
|
if (permissionStatus === 1) {
|
|||
|
|
// 权限已获得,尝试获取定位
|
|||
|
|
try {
|
|||
|
|
const locationData = await this.getCurrentLocation();
|
|||
|
|
if (locationData) {
|
|||
|
|
await this.saveLocationData(locationData);
|
|||
|
|
this.notifyListeners('permission_granted', locationData);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
} catch (locationError) {
|
|||
|
|
if (showTip) {
|
|||
|
|
this.showLocationErrorDialog(locationError.userMessage);
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
} else if (permissionStatus === 2) {
|
|||
|
|
// 权限被拒绝
|
|||
|
|
if (showTip) {
|
|||
|
|
this.showLocationPermissionDeniedDialog();
|
|||
|
|
}
|
|||
|
|
this.notifyListeners('permission_denied');
|
|||
|
|
return false;
|
|||
|
|
} else if (permissionStatus === 0) {
|
|||
|
|
// 未询问权限,尝试获取定位
|
|||
|
|
try {
|
|||
|
|
const locationData = await this.getCurrentLocation();
|
|||
|
|
if (locationData) {
|
|||
|
|
await this.saveLocationData(locationData);
|
|||
|
|
this.permissionStatus = 1;
|
|||
|
|
this.hasLocationPermission = true;
|
|||
|
|
this.notifyListeners('permission_granted', locationData);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
} catch (locationError) {
|
|||
|
|
// 首次获取失败可能是用户拒绝,更新状态
|
|||
|
|
this.permissionStatus = 2;
|
|||
|
|
this.hasLocationPermission = false;
|
|||
|
|
if (showTip) {
|
|||
|
|
this.showLocationPermissionDeniedDialog();
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
if (showTip) {
|
|||
|
|
this.showLocationErrorDialog();
|
|||
|
|
}
|
|||
|
|
this.notifyListeners('permission_error', error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前位置信息
|
|||
|
|
*/
|
|||
|
|
getCurrentLocation() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// 小程序定位参数优化
|
|||
|
|
const locationOptions = {
|
|||
|
|
// #ifdef MP
|
|||
|
|
type: 'wgs84', // 小程序推荐使用wgs84,兼容性更好
|
|||
|
|
isHighAccuracy: false, // 降低精度要求,提高成功率
|
|||
|
|
timeout: 15000, // 设置超时时间15秒
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5 || APP-PLUS
|
|||
|
|
type: 'gcj02',
|
|||
|
|
isHighAccuracy: true,
|
|||
|
|
timeout: 10000,
|
|||
|
|
// #endif
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 开始获取位置
|
|||
|
|
|
|||
|
|
uni.getLocation({
|
|||
|
|
...locationOptions,
|
|||
|
|
success: (res) => {
|
|||
|
|
const locationData = {
|
|||
|
|
latitude: res.latitude,
|
|||
|
|
longitude: res.longitude,
|
|||
|
|
accuracy: res.accuracy,
|
|||
|
|
altitude: res.altitude,
|
|||
|
|
speed: res.speed,
|
|||
|
|
updateTime: Date.now()
|
|||
|
|
};
|
|||
|
|
resolve(locationData);
|
|||
|
|
},
|
|||
|
|
fail: (error) => {
|
|||
|
|
// 获取定位失败
|
|||
|
|
|
|||
|
|
// 根据具体错误提供更详细的信息
|
|||
|
|
let errorMessage = '获取位置失败';
|
|||
|
|
if (error.errMsg) {
|
|||
|
|
if (error.errMsg.includes('timeout')) {
|
|||
|
|
errorMessage = '定位超时,请检查网络连接和GPS设置';
|
|||
|
|
} else if (error.errMsg.includes('system permission denied')) {
|
|||
|
|
errorMessage = '系统定位权限被拒绝,请在系统设置中开启定位服务';
|
|||
|
|
} else if (error.errMsg.includes('location service unavailable')) {
|
|||
|
|
errorMessage = '定位服务不可用,请确保GPS已开启';
|
|||
|
|
} else if (error.errMsg.includes('network')) {
|
|||
|
|
errorMessage = '网络定位失败,请检查网络连接';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
error.userMessage = errorMessage;
|
|||
|
|
reject(error);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 保存定位数据到缓存
|
|||
|
|
* @param {Object} locationData 位置数据
|
|||
|
|
* @param {String} locationName 位置名称
|
|||
|
|
* @param {String} locationAddress 位置地址
|
|||
|
|
* @param {Boolean} skipPermissionCheck 是否跳过权限检查(用于地图选择场景)
|
|||
|
|
*/
|
|||
|
|
async saveLocationData(locationData, locationName = '', locationAddress = '', skipPermissionCheck = false) {
|
|||
|
|
|
|||
|
|
let canSave = false;
|
|||
|
|
|
|||
|
|
if (skipPermissionCheck) {
|
|||
|
|
// 地图选择场景,跳过权限检查
|
|||
|
|
canSave = true;
|
|||
|
|
} else {
|
|||
|
|
// GPS定位场景,需要检查权限
|
|||
|
|
const realTimePermissionStatus = await this.checkRealTimePermission();
|
|||
|
|
canSave = realTimePermissionStatus === 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (canSave) {
|
|||
|
|
this.locationData = {
|
|||
|
|
...locationData,
|
|||
|
|
name: locationName,
|
|||
|
|
address: locationAddress,
|
|||
|
|
source: skipPermissionCheck ? 'manual_select' : 'gps', // 根据场景设置数据来源
|
|||
|
|
type: skipPermissionCheck ? 'business' : 'gps' // 根据场景设置位置类型
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 设置位置选择成功标识
|
|||
|
|
this.hasLocationPermission = true;
|
|||
|
|
this.permissionStatus = 1;
|
|||
|
|
|
|||
|
|
// 使用新的对象格式保存到缓存
|
|||
|
|
this.saveLocationDataToCache();
|
|||
|
|
|
|||
|
|
} else {
|
|||
|
|
// 清除可能过期的权限标识和位置数据
|
|||
|
|
this.hasLocationPermission = false;
|
|||
|
|
this.permissionStatus = 2;
|
|||
|
|
this.clearLocationData();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除定位数据
|
|||
|
|
*/
|
|||
|
|
clearLocationData() {
|
|||
|
|
|
|||
|
|
this.locationData = null;
|
|||
|
|
this.hasLocationPermission = false;
|
|||
|
|
this.permissionStatus = 2;
|
|||
|
|
|
|||
|
|
// 清除新格式缓存
|
|||
|
|
uni.removeStorageSync('location_info');
|
|||
|
|
|
|||
|
|
// 兼容性:清除可能存在的旧格式缓存
|
|||
|
|
uni.removeStorageSync('user_latitude');
|
|||
|
|
uni.removeStorageSync('user_longitude');
|
|||
|
|
uni.removeStorageSync('user_location_name');
|
|||
|
|
uni.removeStorageSync('user_location_address');
|
|||
|
|
uni.removeStorageSync('location_update_time');
|
|||
|
|
uni.removeStorageSync('has_location_permission');
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理历史圈层缓存(小程序首次启动时使用)
|
|||
|
|
*/
|
|||
|
|
clearHistoricalAreaCache() {
|
|||
|
|
try {
|
|||
|
|
// 清除areas_info缓存
|
|||
|
|
uni.removeStorageSync('areas_info');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('清理历史圈层缓存失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查是否有定位权限(实时检查)
|
|||
|
|
*/
|
|||
|
|
async hasPermission() {
|
|||
|
|
|
|||
|
|
// 检查是否已有用户选择的地址,如果有就不要覆盖
|
|||
|
|
const cachedLocationInfo = uni.getStorageSync('location_info');
|
|||
|
|
if (cachedLocationInfo && (cachedLocationInfo.source === 'manual_select' || cachedLocationInfo.type === 'business')) {
|
|||
|
|
// 直接返回权限状态,不触发GPS定位
|
|||
|
|
this.permissionStatus = 1;
|
|||
|
|
this.hasLocationPermission = true;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 进行实时权限检查而不是依赖缓存
|
|||
|
|
const realTimeStatus = await this.checkRealTimePermission();
|
|||
|
|
|
|||
|
|
// 更新内部状态
|
|||
|
|
this.permissionStatus = realTimeStatus;
|
|||
|
|
this.hasLocationPermission = realTimeStatus === 1;
|
|||
|
|
|
|||
|
|
// 如果权限被撤销,清除相关缓存
|
|||
|
|
if (realTimeStatus !== 1) {
|
|||
|
|
this.clearLocationData();
|
|||
|
|
} else {
|
|||
|
|
// 权限有效,更新缓存标识(但不覆盖位置数据)
|
|||
|
|
if (!cachedLocationInfo) {
|
|||
|
|
this.saveLocationDataToCache();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return this.hasLocationPermission;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取缓存的定位数据(实时从缓存读取)
|
|||
|
|
*/
|
|||
|
|
getLocationData() {
|
|||
|
|
// 实时从缓存读取最新数据,而不是返回内存中的旧数据
|
|||
|
|
const cachedLocationInfo = uni.getStorageSync('location_info');
|
|||
|
|
if (cachedLocationInfo && typeof cachedLocationInfo === 'object') {
|
|||
|
|
return {
|
|||
|
|
latitude: parseFloat(cachedLocationInfo.latitude),
|
|||
|
|
longitude: parseFloat(cachedLocationInfo.longitude),
|
|||
|
|
name: cachedLocationInfo.name || '',
|
|||
|
|
address: cachedLocationInfo.address || '',
|
|||
|
|
updateTime: cachedLocationInfo.updateTime || 0,
|
|||
|
|
source: cachedLocationInfo.source || '',
|
|||
|
|
type: cachedLocationInfo.type || ''
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有缓存,返回内存中的数据(兼容旧逻辑)
|
|||
|
|
return this.locationData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取完整的位置信息(包括缓存状态)
|
|||
|
|
*/
|
|||
|
|
getFullLocationInfo() {
|
|||
|
|
const cachedLocationInfo = uni.getStorageSync('location_info');
|
|||
|
|
return {
|
|||
|
|
locationData: this.locationData,
|
|||
|
|
hasPermission: this.hasLocationPermission,
|
|||
|
|
permissionStatus: this.permissionStatus,
|
|||
|
|
isExpired: this.isLocationDataExpired(),
|
|||
|
|
cachedInfo: cachedLocationInfo,
|
|||
|
|
cacheExists: !!cachedLocationInfo
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查缓存是否存在且有效
|
|||
|
|
*/
|
|||
|
|
isCacheValid() {
|
|||
|
|
const cachedLocationInfo = uni.getStorageSync('location_info');
|
|||
|
|
if (!cachedLocationInfo || typeof cachedLocationInfo !== 'object') {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查必要字段
|
|||
|
|
if (!cachedLocationInfo.latitude || !cachedLocationInfo.longitude) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否过期
|
|||
|
|
if (this.isLocationDataExpired()) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 判断定位数据是否过期(默认30分钟)
|
|||
|
|
*/
|
|||
|
|
isLocationDataExpired(maxAge = 30 * 60 * 1000) {
|
|||
|
|
if (!this.locationData || !this.locationData.updateTime) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (Date.now() - this.locationData.updateTime) > maxAge;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 显示定位权限被拒绝的对话框
|
|||
|
|
*/
|
|||
|
|
showLocationPermissionDeniedDialog() {
|
|||
|
|
// #ifdef MP
|
|||
|
|
// 小程序:提供详细的权限开启步骤
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '需要位置权限',
|
|||
|
|
content: '为了为您推荐附近商圈,需要获取位置权限。\n\n您可以:\n• 点击"去设置"在小程序设置中开启\n• 或重新进入页面时选择"允许"',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再说',
|
|||
|
|
confirmText: '去设置',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
this.openLocationSetting();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5:浏览器权限引导
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '需要位置权限',
|
|||
|
|
content: '为了为您推荐附近商圈,需要获取位置权限。\n\n请在浏览器中:\n• 点击地址栏左侧的位置图标\n• 选择"始终允许"\n• 刷新页面重试',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '知道了',
|
|||
|
|
confirmText: '我已设置',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
// H5 中重新尝试获取权限
|
|||
|
|
location.reload();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// APP:系统权限引导
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '需要位置权限',
|
|||
|
|
content: '为了为您推荐附近商圈,需要获取位置权限。\n\n请在系统设置中:\n• 找到本应用\n• 开启"位置信息"权限\n• 返回应用重试',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再说',
|
|||
|
|
confirmText: '去设置',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
this.openLocationSetting();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 显示定位错误对话框
|
|||
|
|
*/
|
|||
|
|
showLocationErrorDialog(customMessage) {
|
|||
|
|
if (customMessage) {
|
|||
|
|
// 使用自定义错误消息
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '定位失败',
|
|||
|
|
content: customMessage,
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再试',
|
|||
|
|
confirmText: '重新定位',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
this.requestLocationPermission(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// #ifdef MP
|
|||
|
|
// 小程序:针对性的问题排查
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '定位失败',
|
|||
|
|
content: '获取位置信息失败,请检查:\n\n• 手机GPS定位是否开启\n• 网络连接是否正常\n• 微信位置权限是否已授权\n\n建议重新进入页面再试',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再试',
|
|||
|
|
confirmText: '重新定位',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
this.requestLocationPermission(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5:浏览器特有问题
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '定位失败',
|
|||
|
|
content: '获取位置信息失败,请检查:\n\n• 浏览器位置权限是否已开启\n• 网络连接是否正常\n• 是否允许HTTPS访问位置\n\n建议刷新页面重试',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再试',
|
|||
|
|
confirmText: '刷新重试',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
location.reload();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// APP:系统级问题
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '定位失败',
|
|||
|
|
content: '获取位置信息失败,请检查:\n\n• 系统位置服务是否开启\n• 应用位置权限是否已授权\n• 网络连接是否正常\n\n建议检查系统设置后重试',
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '稍后再试',
|
|||
|
|
confirmText: '重新定位',
|
|||
|
|
confirmColor: '#f55850',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
this.requestLocationPermission(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 打开定位设置
|
|||
|
|
*/
|
|||
|
|
openLocationSetting() {
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
if (permision) {
|
|||
|
|
permision.gotoAppSetting();
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef MP
|
|||
|
|
uni.openSetting({
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.authSetting && res.authSetting['scope.userLocation']) {
|
|||
|
|
this.requestLocationPermission(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '请在浏览器设置中允许位置访问',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 3000
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 决定跳转路径
|
|||
|
|
*/
|
|||
|
|
getLocationPagePath() {
|
|||
|
|
if (this.hasLocationPermission && this.locationData) {
|
|||
|
|
return '/pages/circle/select';
|
|||
|
|
} else {
|
|||
|
|
return '/pages/circle/index';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 智能跳转到定位页面
|
|||
|
|
*/
|
|||
|
|
navigateToLocationPage(params = {}) {
|
|||
|
|
const path = this.getLocationPagePath();
|
|||
|
|
const url = Object.keys(params).length > 0 ?
|
|||
|
|
`${path}?${Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')}` :
|
|||
|
|
path;
|
|||
|
|
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: url,
|
|||
|
|
fail: () => {
|
|||
|
|
// 如果跳转失败,尝试重定向
|
|||
|
|
uni.redirectTo({ url: url });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 添加监听器
|
|||
|
|
*/
|
|||
|
|
addListener(callback) {
|
|||
|
|
this.listeners.push(callback);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移除监听器
|
|||
|
|
*/
|
|||
|
|
removeListener(callback) {
|
|||
|
|
const index = this.listeners.indexOf(callback);
|
|||
|
|
if (index > -1) {
|
|||
|
|
this.listeners.splice(index, 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 通知所有监听器
|
|||
|
|
*/
|
|||
|
|
notifyListeners(event, data) {
|
|||
|
|
this.listeners.forEach(callback => {
|
|||
|
|
try {
|
|||
|
|
callback(event, data);
|
|||
|
|
} catch (error) {
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 刷新定位数据 - 改为使用地图选择
|
|||
|
|
*/
|
|||
|
|
async refreshLocationData(showLoading = true) {
|
|||
|
|
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// 使用当前位置作为地图中心点(如果有的话)
|
|||
|
|
const chooseLocationOptions = {};
|
|||
|
|
if (this.locationData) {
|
|||
|
|
chooseLocationOptions.latitude = this.locationData.latitude;
|
|||
|
|
chooseLocationOptions.longitude = this.locationData.longitude;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 直接打开系统地图选择位置
|
|||
|
|
uni.chooseLocation({
|
|||
|
|
...chooseLocationOptions,
|
|||
|
|
success: async (res) => {
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 保存用户选择的位置(跳过权限检查)
|
|||
|
|
await this.saveLocationData({
|
|||
|
|
latitude: res.latitude,
|
|||
|
|
longitude: res.longitude,
|
|||
|
|
updateTime: Date.now()
|
|||
|
|
}, res.name, res.address, true);
|
|||
|
|
|
|||
|
|
if (showLoading) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '位置已更新',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
resolve(this.locationData);
|
|||
|
|
} catch (error) {
|
|||
|
|
if (showLoading) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '保存位置失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
reject(error);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (error) => {
|
|||
|
|
|
|||
|
|
// 如果不是用户取消,显示错误提示
|
|||
|
|
if (showLoading && error.errMsg && !error.errMsg.includes('cancel')) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '位置选择失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 用户取消也算是一种处理结果,返回null而不是reject
|
|||
|
|
resolve(null);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建全局单例
|
|||
|
|
const locationManager = new LocationManager();
|
|||
|
|
|
|||
|
|
export default locationManager;
|