Files
MER-2.2_2601/mer_uniapp/utils/locationManager.js

847 lines
24 KiB
JavaScript
Raw Normal View History

// +----------------------------------------------------------------------
// | 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;