locationManager.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. // +----------------------------------------------------------------------
  2. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  3. // +----------------------------------------------------------------------
  4. // | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
  5. // +----------------------------------------------------------------------
  6. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  7. // +----------------------------------------------------------------------
  8. // | Author: CRMEB Team <admin@crmeb.com>
  9. // +----------------------------------------------------------------------
  10. // #ifdef APP-PLUS
  11. import permision from "./permission.js"
  12. // #endif
  13. /**
  14. * 全局定位权限管理和数据缓存
  15. */
  16. class LocationManager {
  17. constructor() {
  18. this.locationData = null;
  19. this.hasLocationPermission = false;
  20. this.permissionStatus = 0; // 0: 未检查, 1: 已允许, 2: 拒绝, 3: 系统未开启
  21. this.isChecking = false;
  22. this.listeners = [];
  23. }
  24. /**
  25. * 初始化定位管理器
  26. */
  27. init() {
  28. // 从缓存中恢复定位数据
  29. this.restoreLocationData();
  30. // 检查权限状态
  31. this.checkInitialPermission();
  32. }
  33. /**
  34. * 从缓存中恢复定位数据
  35. */
  36. restoreLocationData() {
  37. try {
  38. // 优先使用新的对象缓存格式
  39. const cachedLocationInfo = uni.getStorageSync('location_info');
  40. if (cachedLocationInfo && typeof cachedLocationInfo === 'object') {
  41. this.locationData = {
  42. latitude: parseFloat(cachedLocationInfo.latitude),
  43. longitude: parseFloat(cachedLocationInfo.longitude),
  44. name: cachedLocationInfo.name || '',
  45. address: cachedLocationInfo.address || '',
  46. updateTime: cachedLocationInfo.updateTime || 0,
  47. source: cachedLocationInfo.source || '', // 恢复数据来源标识
  48. type: cachedLocationInfo.type || '' // 恢复位置类型
  49. };
  50. this.hasLocationPermission = cachedLocationInfo.hasPermission || false;
  51. this.permissionStatus = cachedLocationInfo.hasPermission ? 1 : 0;
  52. return;
  53. }
  54. // 兼容旧版本的独立缓存项
  55. const latitude = uni.getStorageSync('user_latitude');
  56. const longitude = uni.getStorageSync('user_longitude');
  57. const locationName = uni.getStorageSync('user_location_name');
  58. const locationAddress = uni.getStorageSync('user_location_address');
  59. const updateTime = uni.getStorageSync('location_update_time');
  60. const hasPermission = uni.getStorageSync('has_location_permission');
  61. if (latitude && longitude) {
  62. this.locationData = {
  63. latitude: parseFloat(latitude),
  64. longitude: parseFloat(longitude),
  65. name: locationName || '',
  66. address: locationAddress || '',
  67. updateTime: updateTime || 0
  68. };
  69. this.hasLocationPermission = !!hasPermission;
  70. this.permissionStatus = hasPermission ? 1 : 0;
  71. // 迁移到新格式并清除旧缓存
  72. this.migrateToNewFormat();
  73. }
  74. } catch (error) {
  75. }
  76. }
  77. /**
  78. * 迁移旧格式缓存到新格式
  79. */
  80. migrateToNewFormat() {
  81. try {
  82. // 保存到新格式
  83. this.saveLocationDataToCache();
  84. // 清除旧格式缓存
  85. uni.removeStorageSync('user_latitude');
  86. uni.removeStorageSync('user_longitude');
  87. uni.removeStorageSync('user_location_name');
  88. uni.removeStorageSync('user_location_address');
  89. uni.removeStorageSync('location_update_time');
  90. uni.removeStorageSync('has_location_permission');
  91. } catch (error) {
  92. }
  93. }
  94. /**
  95. * 保存位置数据到缓存(使用对象格式)
  96. */
  97. saveLocationDataToCache() {
  98. try {
  99. const locationInfo = {
  100. latitude: this.locationData?.latitude || 0,
  101. longitude: this.locationData?.longitude || 0,
  102. name: this.locationData?.name || '',
  103. address: this.locationData?.address || '',
  104. updateTime: this.locationData?.updateTime || Date.now(),
  105. hasPermission: this.hasLocationPermission,
  106. permissionStatus: this.permissionStatus,
  107. source: this.locationData?.source || '', // 保存数据来源标识
  108. type: this.locationData?.type || '', // 保存位置类型
  109. version: '2.0' // 版本标识
  110. };
  111. uni.setStorageSync('location_info', locationInfo);
  112. } catch (error) {
  113. }
  114. }
  115. /**
  116. * 检查初始权限状态
  117. */
  118. async checkInitialPermission() {
  119. if (this.isChecking) return this.permissionStatus;
  120. this.isChecking = true;
  121. try {
  122. let status = 0;
  123. // #ifdef APP-PLUS
  124. status = permision.isIOS ?
  125. await permision.requestIOS('location') :
  126. await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
  127. // #endif
  128. // #ifdef MP
  129. status = await this.getSetting();
  130. // #endif
  131. // #ifdef H5
  132. // H5环境下通过尝试获取定位来判断权限
  133. try {
  134. await this.testLocationAccess();
  135. status = 1;
  136. } catch (error) {
  137. status = 2;
  138. }
  139. // #endif
  140. this.permissionStatus = status;
  141. this.hasLocationPermission = status === 1;
  142. } catch (error) {
  143. this.permissionStatus = 2;
  144. this.hasLocationPermission = false;
  145. } finally {
  146. this.isChecking = false;
  147. }
  148. return this.permissionStatus;
  149. }
  150. /**
  151. * 测试定位访问权限(H5环境)
  152. */
  153. testLocationAccess() {
  154. return new Promise((resolve, reject) => {
  155. // #ifdef H5
  156. if (navigator.geolocation) {
  157. navigator.geolocation.getCurrentPosition(
  158. (position) => resolve(position),
  159. (error) => {
  160. reject(error);
  161. },
  162. { timeout: 10000, enableHighAccuracy: false, maximumAge: 60000 }
  163. );
  164. } else {
  165. reject(new Error('Geolocation not supported'));
  166. }
  167. // #endif
  168. // #ifndef H5
  169. // 非H5环境直接resolve,由其他逻辑处理
  170. resolve(true);
  171. // #endif
  172. });
  173. }
  174. /**
  175. * 获取小程序定位权限设置
  176. */
  177. getSetting() {
  178. return new Promise((resolve) => {
  179. // #ifdef MP
  180. uni.getSetting({
  181. success: (res) => {
  182. if (res.authSetting['scope.userLocation'] === undefined) {
  183. resolve(0); // 未询问
  184. } else if (res.authSetting['scope.userLocation']) {
  185. resolve(1); // 已允许
  186. } else {
  187. resolve(2); // 已拒绝
  188. }
  189. },
  190. fail: () => resolve(2)
  191. });
  192. // #endif
  193. // #ifndef MP
  194. resolve(0);
  195. // #endif
  196. });
  197. }
  198. /**
  199. * 实时检查系统权限状态(不依赖缓存)
  200. */
  201. async checkRealTimePermission() {
  202. try {
  203. let status = 0;
  204. // #ifdef APP-PLUS
  205. status = permision.isIOS ?
  206. await permision.requestIOS('location') :
  207. await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
  208. // #endif
  209. // #ifdef MP
  210. status = await this.getSetting();
  211. // #endif
  212. // #ifdef H5
  213. // H5环境下通过实际尝试获取定位来检查权限
  214. try {
  215. await this.testLocationAccess();
  216. status = 1;
  217. } catch (error) {
  218. status = 2;
  219. }
  220. // #endif
  221. return status;
  222. } catch (error) {
  223. return 2; // 检查失败视为无权限
  224. }
  225. }
  226. /**
  227. * 请求定位权限并获取位置信息
  228. */
  229. async requestLocationPermission(showTip = true) {
  230. if (this.isChecking) return false;
  231. try {
  232. const permissionStatus = await this.checkInitialPermission();
  233. if (permissionStatus === 1) {
  234. // 权限已获得,尝试获取定位
  235. try {
  236. const locationData = await this.getCurrentLocation();
  237. if (locationData) {
  238. await this.saveLocationData(locationData);
  239. this.notifyListeners('permission_granted', locationData);
  240. return true;
  241. }
  242. } catch (locationError) {
  243. if (showTip) {
  244. this.showLocationErrorDialog(locationError.userMessage);
  245. }
  246. return false;
  247. }
  248. } else if (permissionStatus === 2) {
  249. // 权限被拒绝
  250. if (showTip) {
  251. this.showLocationPermissionDeniedDialog();
  252. }
  253. this.notifyListeners('permission_denied');
  254. return false;
  255. } else if (permissionStatus === 0) {
  256. // 未询问权限,尝试获取定位
  257. try {
  258. const locationData = await this.getCurrentLocation();
  259. if (locationData) {
  260. await this.saveLocationData(locationData);
  261. this.permissionStatus = 1;
  262. this.hasLocationPermission = true;
  263. this.notifyListeners('permission_granted', locationData);
  264. return true;
  265. }
  266. } catch (locationError) {
  267. // 首次获取失败可能是用户拒绝,更新状态
  268. this.permissionStatus = 2;
  269. this.hasLocationPermission = false;
  270. if (showTip) {
  271. this.showLocationPermissionDeniedDialog();
  272. }
  273. return false;
  274. }
  275. }
  276. } catch (error) {
  277. if (showTip) {
  278. this.showLocationErrorDialog();
  279. }
  280. this.notifyListeners('permission_error', error);
  281. }
  282. return false;
  283. }
  284. /**
  285. * 获取当前位置信息
  286. */
  287. getCurrentLocation() {
  288. return new Promise((resolve, reject) => {
  289. // 小程序定位参数优化
  290. const locationOptions = {
  291. // #ifdef MP
  292. type: 'wgs84', // 小程序推荐使用wgs84,兼容性更好
  293. isHighAccuracy: false, // 降低精度要求,提高成功率
  294. timeout: 15000, // 设置超时时间15秒
  295. // #endif
  296. // #ifdef H5 || APP-PLUS
  297. type: 'gcj02',
  298. isHighAccuracy: true,
  299. timeout: 10000,
  300. // #endif
  301. };
  302. // 开始获取位置
  303. uni.getLocation({
  304. ...locationOptions,
  305. success: (res) => {
  306. const locationData = {
  307. latitude: res.latitude,
  308. longitude: res.longitude,
  309. accuracy: res.accuracy,
  310. altitude: res.altitude,
  311. speed: res.speed,
  312. updateTime: Date.now()
  313. };
  314. resolve(locationData);
  315. },
  316. fail: (error) => {
  317. // 获取定位失败
  318. // 根据具体错误提供更详细的信息
  319. let errorMessage = '获取位置失败';
  320. if (error.errMsg) {
  321. if (error.errMsg.includes('timeout')) {
  322. errorMessage = '定位超时,请检查网络连接和GPS设置';
  323. } else if (error.errMsg.includes('system permission denied')) {
  324. errorMessage = '系统定位权限被拒绝,请在系统设置中开启定位服务';
  325. } else if (error.errMsg.includes('location service unavailable')) {
  326. errorMessage = '定位服务不可用,请确保GPS已开启';
  327. } else if (error.errMsg.includes('network')) {
  328. errorMessage = '网络定位失败,请检查网络连接';
  329. }
  330. }
  331. error.userMessage = errorMessage;
  332. reject(error);
  333. }
  334. });
  335. });
  336. }
  337. /**
  338. * 保存定位数据到缓存
  339. * @param {Object} locationData 位置数据
  340. * @param {String} locationName 位置名称
  341. * @param {String} locationAddress 位置地址
  342. * @param {Boolean} skipPermissionCheck 是否跳过权限检查(用于地图选择场景)
  343. */
  344. async saveLocationData(locationData, locationName = '', locationAddress = '', skipPermissionCheck = false) {
  345. let canSave = false;
  346. if (skipPermissionCheck) {
  347. // 地图选择场景,跳过权限检查
  348. canSave = true;
  349. } else {
  350. // GPS定位场景,需要检查权限
  351. const realTimePermissionStatus = await this.checkRealTimePermission();
  352. canSave = realTimePermissionStatus === 1;
  353. }
  354. if (canSave) {
  355. this.locationData = {
  356. ...locationData,
  357. name: locationName,
  358. address: locationAddress,
  359. source: skipPermissionCheck ? 'manual_select' : 'gps', // 根据场景设置数据来源
  360. type: skipPermissionCheck ? 'business' : 'gps' // 根据场景设置位置类型
  361. };
  362. // 设置位置选择成功标识
  363. this.hasLocationPermission = true;
  364. this.permissionStatus = 1;
  365. // 使用新的对象格式保存到缓存
  366. this.saveLocationDataToCache();
  367. } else {
  368. // 清除可能过期的权限标识和位置数据
  369. this.hasLocationPermission = false;
  370. this.permissionStatus = 2;
  371. this.clearLocationData();
  372. }
  373. }
  374. /**
  375. * 清除定位数据
  376. */
  377. clearLocationData() {
  378. this.locationData = null;
  379. this.hasLocationPermission = false;
  380. this.permissionStatus = 2;
  381. // 清除新格式缓存
  382. uni.removeStorageSync('location_info');
  383. // 兼容性:清除可能存在的旧格式缓存
  384. uni.removeStorageSync('user_latitude');
  385. uni.removeStorageSync('user_longitude');
  386. uni.removeStorageSync('user_location_name');
  387. uni.removeStorageSync('user_location_address');
  388. uni.removeStorageSync('location_update_time');
  389. uni.removeStorageSync('has_location_permission');
  390. }
  391. /**
  392. * 清理历史圈层缓存(小程序首次启动时使用)
  393. */
  394. clearHistoricalAreaCache() {
  395. try {
  396. // 清除areas_info缓存
  397. uni.removeStorageSync('areas_info');
  398. } catch (error) {
  399. console.warn('清理历史圈层缓存失败:', error);
  400. }
  401. }
  402. /**
  403. * 检查是否有定位权限(实时检查)
  404. */
  405. async hasPermission() {
  406. // 检查是否已有用户选择的地址,如果有就不要覆盖
  407. const cachedLocationInfo = uni.getStorageSync('location_info');
  408. if (cachedLocationInfo && (cachedLocationInfo.source === 'manual_select' || cachedLocationInfo.type === 'business')) {
  409. // 直接返回权限状态,不触发GPS定位
  410. this.permissionStatus = 1;
  411. this.hasLocationPermission = true;
  412. return true;
  413. }
  414. // 进行实时权限检查而不是依赖缓存
  415. const realTimeStatus = await this.checkRealTimePermission();
  416. // 更新内部状态
  417. this.permissionStatus = realTimeStatus;
  418. this.hasLocationPermission = realTimeStatus === 1;
  419. // 如果权限被撤销,清除相关缓存
  420. if (realTimeStatus !== 1) {
  421. this.clearLocationData();
  422. } else {
  423. // 权限有效,更新缓存标识(但不覆盖位置数据)
  424. if (!cachedLocationInfo) {
  425. this.saveLocationDataToCache();
  426. }
  427. }
  428. return this.hasLocationPermission;
  429. }
  430. /**
  431. * 获取缓存的定位数据(实时从缓存读取)
  432. */
  433. getLocationData() {
  434. // 实时从缓存读取最新数据,而不是返回内存中的旧数据
  435. const cachedLocationInfo = uni.getStorageSync('location_info');
  436. if (cachedLocationInfo && typeof cachedLocationInfo === 'object') {
  437. return {
  438. latitude: parseFloat(cachedLocationInfo.latitude),
  439. longitude: parseFloat(cachedLocationInfo.longitude),
  440. name: cachedLocationInfo.name || '',
  441. address: cachedLocationInfo.address || '',
  442. updateTime: cachedLocationInfo.updateTime || 0,
  443. source: cachedLocationInfo.source || '',
  444. type: cachedLocationInfo.type || ''
  445. };
  446. }
  447. // 如果没有缓存,返回内存中的数据(兼容旧逻辑)
  448. return this.locationData;
  449. }
  450. /**
  451. * 获取完整的位置信息(包括缓存状态)
  452. */
  453. getFullLocationInfo() {
  454. const cachedLocationInfo = uni.getStorageSync('location_info');
  455. return {
  456. locationData: this.locationData,
  457. hasPermission: this.hasLocationPermission,
  458. permissionStatus: this.permissionStatus,
  459. isExpired: this.isLocationDataExpired(),
  460. cachedInfo: cachedLocationInfo,
  461. cacheExists: !!cachedLocationInfo
  462. };
  463. }
  464. /**
  465. * 检查缓存是否存在且有效
  466. */
  467. isCacheValid() {
  468. const cachedLocationInfo = uni.getStorageSync('location_info');
  469. if (!cachedLocationInfo || typeof cachedLocationInfo !== 'object') {
  470. return false;
  471. }
  472. // 检查必要字段
  473. if (!cachedLocationInfo.latitude || !cachedLocationInfo.longitude) {
  474. return false;
  475. }
  476. // 检查是否过期
  477. if (this.isLocationDataExpired()) {
  478. return false;
  479. }
  480. return true;
  481. }
  482. /**
  483. * 判断定位数据是否过期(默认30分钟)
  484. */
  485. isLocationDataExpired(maxAge = 30 * 60 * 1000) {
  486. if (!this.locationData || !this.locationData.updateTime) {
  487. return true;
  488. }
  489. return (Date.now() - this.locationData.updateTime) > maxAge;
  490. }
  491. /**
  492. * 显示定位权限被拒绝的对话框
  493. */
  494. showLocationPermissionDeniedDialog() {
  495. // #ifdef MP
  496. // 小程序:提供详细的权限开启步骤
  497. uni.showModal({
  498. title: '需要位置权限',
  499. content: '为了为您推荐附近商圈,需要获取位置权限。\n\n您可以:\n• 点击"去设置"在小程序设置中开启\n• 或重新进入页面时选择"允许"',
  500. showCancel: true,
  501. cancelText: '稍后再说',
  502. confirmText: '去设置',
  503. confirmColor: '#f55850',
  504. success: (res) => {
  505. if (res.confirm) {
  506. this.openLocationSetting();
  507. }
  508. }
  509. });
  510. // #endif
  511. // #ifdef H5
  512. // H5:浏览器权限引导
  513. uni.showModal({
  514. title: '需要位置权限',
  515. content: '为了为您推荐附近商圈,需要获取位置权限。\n\n请在浏览器中:\n• 点击地址栏左侧的位置图标\n• 选择"始终允许"\n• 刷新页面重试',
  516. showCancel: true,
  517. cancelText: '知道了',
  518. confirmText: '我已设置',
  519. confirmColor: '#f55850',
  520. success: (res) => {
  521. if (res.confirm) {
  522. // H5 中重新尝试获取权限
  523. location.reload();
  524. }
  525. }
  526. });
  527. // #endif
  528. // #ifdef APP-PLUS
  529. // APP:系统权限引导
  530. uni.showModal({
  531. title: '需要位置权限',
  532. content: '为了为您推荐附近商圈,需要获取位置权限。\n\n请在系统设置中:\n• 找到本应用\n• 开启"位置信息"权限\n• 返回应用重试',
  533. showCancel: true,
  534. cancelText: '稍后再说',
  535. confirmText: '去设置',
  536. confirmColor: '#f55850',
  537. success: (res) => {
  538. if (res.confirm) {
  539. this.openLocationSetting();
  540. }
  541. }
  542. });
  543. // #endif
  544. }
  545. /**
  546. * 显示定位错误对话框
  547. */
  548. showLocationErrorDialog(customMessage) {
  549. if (customMessage) {
  550. // 使用自定义错误消息
  551. uni.showModal({
  552. title: '定位失败',
  553. content: customMessage,
  554. showCancel: true,
  555. cancelText: '稍后再试',
  556. confirmText: '重新定位',
  557. confirmColor: '#f55850',
  558. success: (res) => {
  559. if (res.confirm) {
  560. this.requestLocationPermission(false);
  561. }
  562. }
  563. });
  564. return;
  565. }
  566. // #ifdef MP
  567. // 小程序:针对性的问题排查
  568. uni.showModal({
  569. title: '定位失败',
  570. content: '获取位置信息失败,请检查:\n\n• 手机GPS定位是否开启\n• 网络连接是否正常\n• 微信位置权限是否已授权\n\n建议重新进入页面再试',
  571. showCancel: true,
  572. cancelText: '稍后再试',
  573. confirmText: '重新定位',
  574. confirmColor: '#f55850',
  575. success: (res) => {
  576. if (res.confirm) {
  577. this.requestLocationPermission(false);
  578. }
  579. }
  580. });
  581. // #endif
  582. // #ifdef H5
  583. // H5:浏览器特有问题
  584. uni.showModal({
  585. title: '定位失败',
  586. content: '获取位置信息失败,请检查:\n\n• 浏览器位置权限是否已开启\n• 网络连接是否正常\n• 是否允许HTTPS访问位置\n\n建议刷新页面重试',
  587. showCancel: true,
  588. cancelText: '稍后再试',
  589. confirmText: '刷新重试',
  590. confirmColor: '#f55850',
  591. success: (res) => {
  592. if (res.confirm) {
  593. location.reload();
  594. }
  595. }
  596. });
  597. // #endif
  598. // #ifdef APP-PLUS
  599. // APP:系统级问题
  600. uni.showModal({
  601. title: '定位失败',
  602. content: '获取位置信息失败,请检查:\n\n• 系统位置服务是否开启\n• 应用位置权限是否已授权\n• 网络连接是否正常\n\n建议检查系统设置后重试',
  603. showCancel: true,
  604. cancelText: '稍后再试',
  605. confirmText: '重新定位',
  606. confirmColor: '#f55850',
  607. success: (res) => {
  608. if (res.confirm) {
  609. this.requestLocationPermission(false);
  610. }
  611. }
  612. });
  613. // #endif
  614. }
  615. /**
  616. * 打开定位设置
  617. */
  618. openLocationSetting() {
  619. // #ifdef APP-PLUS
  620. if (permision) {
  621. permision.gotoAppSetting();
  622. }
  623. // #endif
  624. // #ifdef MP
  625. uni.openSetting({
  626. success: (res) => {
  627. if (res.authSetting && res.authSetting['scope.userLocation']) {
  628. this.requestLocationPermission(false);
  629. }
  630. }
  631. });
  632. // #endif
  633. // #ifdef H5
  634. uni.showToast({
  635. title: '请在浏览器设置中允许位置访问',
  636. icon: 'none',
  637. duration: 3000
  638. });
  639. // #endif
  640. }
  641. /**
  642. * 决定跳转路径
  643. */
  644. getLocationPagePath() {
  645. if (this.hasLocationPermission && this.locationData) {
  646. return '/pages/circle/select';
  647. } else {
  648. return '/pages/circle/index';
  649. }
  650. }
  651. /**
  652. * 智能跳转到定位页面
  653. */
  654. navigateToLocationPage(params = {}) {
  655. const path = this.getLocationPagePath();
  656. const url = Object.keys(params).length > 0 ?
  657. `${path}?${Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')}` :
  658. path;
  659. uni.navigateTo({
  660. url: url,
  661. fail: () => {
  662. // 如果跳转失败,尝试重定向
  663. uni.redirectTo({ url: url });
  664. }
  665. });
  666. }
  667. /**
  668. * 添加监听器
  669. */
  670. addListener(callback) {
  671. this.listeners.push(callback);
  672. }
  673. /**
  674. * 移除监听器
  675. */
  676. removeListener(callback) {
  677. const index = this.listeners.indexOf(callback);
  678. if (index > -1) {
  679. this.listeners.splice(index, 1);
  680. }
  681. }
  682. /**
  683. * 通知所有监听器
  684. */
  685. notifyListeners(event, data) {
  686. this.listeners.forEach(callback => {
  687. try {
  688. callback(event, data);
  689. } catch (error) {
  690. }
  691. });
  692. }
  693. /**
  694. * 刷新定位数据 - 改为使用地图选择
  695. */
  696. async refreshLocationData(showLoading = true) {
  697. return new Promise((resolve, reject) => {
  698. // 使用当前位置作为地图中心点(如果有的话)
  699. const chooseLocationOptions = {};
  700. if (this.locationData) {
  701. chooseLocationOptions.latitude = this.locationData.latitude;
  702. chooseLocationOptions.longitude = this.locationData.longitude;
  703. }
  704. // 直接打开系统地图选择位置
  705. uni.chooseLocation({
  706. ...chooseLocationOptions,
  707. success: async (res) => {
  708. try {
  709. // 保存用户选择的位置(跳过权限检查)
  710. await this.saveLocationData({
  711. latitude: res.latitude,
  712. longitude: res.longitude,
  713. updateTime: Date.now()
  714. }, res.name, res.address, true);
  715. if (showLoading) {
  716. uni.showToast({
  717. title: '位置已更新',
  718. icon: 'success'
  719. });
  720. }
  721. resolve(this.locationData);
  722. } catch (error) {
  723. if (showLoading) {
  724. uni.showToast({
  725. title: '保存位置失败',
  726. icon: 'none'
  727. });
  728. }
  729. reject(error);
  730. }
  731. },
  732. fail: (error) => {
  733. // 如果不是用户取消,显示错误提示
  734. if (showLoading && error.errMsg && !error.errMsg.includes('cancel')) {
  735. uni.showToast({
  736. title: '位置选择失败',
  737. icon: 'none'
  738. });
  739. }
  740. // 用户取消也算是一种处理结果,返回null而不是reject
  741. resolve(null);
  742. }
  743. });
  744. });
  745. }
  746. }
  747. // 创建全局单例
  748. const locationManager = new LocationManager();
  749. export default locationManager;