如何基于ElectronJS与Svelte实现用户位置越界(超出设定半径)检测?
基于Svelte+Electron实现定期位置检查与半径判断
1. 配置Electron地理位置权限
首先确保Electron允许渲染进程获取地理位置,在主进程(main.js/main.ts)的窗口配置中开启相关权限:
const { app, BrowserWindow } = require('electron'); function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { contextIsolation: true, preload: './preload.js', // 如需Node API交互,用preload脚本隔离 geolocation: true, // 允许地理位置访问 backgroundThrottling: false // 可选:防止后台时定时器被系统节流 } }); mainWindow.loadURL('http://localhost:5173'); // 本地开发地址,打包时替换为build后的index.html路径 } app.whenReady().then(createWindow);
打包时需在系统层面申请权限,在package.json的build字段添加声明:
"build": { "mac": { "entitlements": "entitlements.mac.plist", "entitlementsInherit": "entitlements.mac.plist" }, "win": { "permissions": ["geolocation"] } }
macOS对应的entitlements.mac.plist内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.personal-information.location</key> <true/> </dict> </plist>
2. 实现位置获取与距离计算
2.1 球面距离计算函数(Haversine公式)
用这个函数计算两点经纬度之间的直线距离(单位:米):
// 角度转弧度 function degreesToRadians(degrees) { return degrees * Math.PI / 180; } // 计算两点距离(米) function calculateDistance(lat1, lon1, lat2, lon2) { const earthRadius = 6371000; // 地球平均半径,单位米 const dLat = degreesToRadians(lat2 - lat1); const dLon = degreesToRadians(lon2 - lon1); const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(degreesToRadians(lat1)) * Math.cos(degreesToRadians(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return earthRadius * c; }
2.2 定期检查逻辑(Svelte组件内实现)
在Svelte组件中通过定时器定期获取位置,与预设的中心点、半径对比:
<script> import { onMount, onDestroy } from 'svelte'; // 预设监控参数(可从配置/后端动态获取) const centerLat = 31.2304; // 示例中心点纬度(上海) const centerLon = 121.4737; // 示例中心点经度(上海) const radiusLimit = 1000; // 半径限制,单位米(1公里) let checkInterval; let monitorStatus = '等待初始化...'; // 单次位置检查 function checkLocation() { return new Promise((resolve, reject) => { if (!navigator.geolocation) { reject('当前环境不支持地理位置服务'); return; } navigator.geolocation.getCurrentPosition( (position) => { const { latitude, longitude } = position.coords; const distance = calculateDistance(centerLat, centerLon, latitude, longitude); resolve({ isOutOfRange: distance > radiusLimit, distance: Math.round(distance), latitude, longitude }); }, (error) => { let errMsg = ''; switch(error.code) { case error.PERMISSION_DENIED: errMsg = '用户拒绝位置权限'; break; case error.POSITION_UNAVAILABLE: errMsg = '位置信息不可用'; break; case error.TIMEOUT: errMsg = '获取位置超时'; break; } reject(errMsg); }, { enableHighAccuracy: false, // 普通场景关闭高精度,节省电量 timeout: 8000, maximumAge: 10000 // 允许使用10秒内的缓存位置 } ); }); } // 启动定时监控 function startMonitoring() { // 每30秒检查一次(可根据需求调整间隔) checkInterval = setInterval(async () => { try { const result = await checkLocation(); if (result.isOutOfRange) { monitorStatus = `⚠️ 已超出范围!当前距离中心点${result.distance}米`; // 此处添加超出范围后的逻辑:弹窗提示、上报后端等 } else { monitorStatus = `✅ 在范围内,距离中心点${result.distance}米`; } } catch (err) { monitorStatus = `❌ 检查失败:${err}`; } }, 30000); } onMount(async () => { // 页面加载后先执行一次检查 try { const result = await checkLocation(); monitorStatus = result.isOutOfRange ? `⚠️ 已超出范围!当前距离中心点${result.distance}米` : `✅ 在范围内,距离中心点${result.distance}米`; } catch (err) { monitorStatus = `❌ 初始化检查失败:${err}`; } startMonitoring(); }); onDestroy(() => { // 组件销毁时清除定时器,避免内存泄漏 if (checkInterval) clearInterval(checkInterval); }); // 引入距离计算函数 function degreesToRadians(degrees) { return degrees * Math.PI / 180; } function calculateDistance(lat1, lon1, lat2, lon2) { const earthRadius = 6371000; const dLat = degreesToRadians(lat2 - lat1); const dLon = degreesToRadians(lon2 - lon1); const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(degreesToRadians(lat1)) * Math.cos(degreesToRadians(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return earthRadius * c; } </script> <div class="monitor-container"> <h3>位置监控状态</h3> <p class="status-text">{monitorStatus}</p> </div> <style> .monitor-container { padding: 20px; border-radius: 8px; background: #f5f5f5; } .status-text { font-size: 16px; margin-top: 10px; } </style>
3. 关键注意事项
- 权限引导:第一次打开应用时系统会弹出权限申请,需在UI中提示用户允许;若用户拒绝,提供重新申请权限的入口(可通过
navigator.permissions.query检查权限状态)。 - 性能平衡:检查间隔不宜过短(建议10-60秒),避免频繁定位导致设备耗电过高;高精度定位仅在必要场景开启。
- 后台运行:若需应用后台持续监控,需确保
backgroundThrottling设为false,同时在macOS等系统中配置后台运行权限。 - 异常处理:必须覆盖定位失败的所有场景,避免应用无响应或崩溃。
内容的提问来源于stack exchange,提问作者Omar Alhussani
相关产品推荐
相关产品推荐

