// app.js const api = require('./api/index.js'); // 注意路径 App({ globalData: { socketTask: null, isConnected: false, reconnectTimer: null, heartbeatTimer: null, currentWorkorderId: '', userInfo: wx.getStorageSync('user') || {} }, onLaunch() { // wx.setStorageSync('user', { userId: 'driver_1001', operationRole: 4 }); this.initWebSocketService(); wx.onAppShow(() => this.handleAppShow()); wx.onAppHide(() => this.handleAppHide()); this.locationService = { isTracking: false, currentWorkorderId: '', // 存储当前跟踪的工单ID isLocationUpdateStarted: false // 标记是否已启动定位服务 }; }, initWebSocketService() { if (!this.wsService) { this.wsService = { connect: (url) => { const that = this; if (that.globalData.socketTask) { that.wsService.close(); } console.log('正在连接WebSocket:', url); const socketTask = wx.connectSocket({ url, header: { 'content-type': 'application/json' } }); socketTask.onOpen(() => { console.log('WebSocket 连接成功'); that.globalData.isConnected = true; that.globalData.socketTask = socketTask; that.clearReconnectTimer(); // that.startHeartbeat(); if (that.globalData.currentWorkorderId) { that.wsService.send({ type: 'subscribe', workorderId: that.globalData.currentWorkorderId }); } wx.$emit('socketOpen'); }); socketTask.onMessage((res) => { try { console.log(res); const message = JSON.parse(res.data); console.log('收到消息:', message); wx.$emit('wsMessage', message); } catch (err) { console.error('消息解析失败', err); } }); socketTask.onClose((res) => { console.log('WebSocket 连接关闭, 状态码:', res.code); that.globalData.isConnected = false; that.globalData.socketTask = null; that.stopHeartbeat(); wx.$emit('socketClose'); if (res.code !== 1000) { that.startReconnect(); } }); socketTask.onError((err) => { console.error('WebSocket 连接错误:', err); that.globalData.isConnected = false; that.globalData.socketTask = null; that.stopHeartbeat(); wx.$emit('socketError', err); }); }, send: (data) => { const that = this; if (that.globalData.isConnected && that.globalData.socketTask) { try { that.globalData.socketTask.send({ data: JSON.stringify(data), fail: (err) => { console.error('消息发送失败:', err); wx.showToast({ title: '消息发送失败', icon: 'none' }); } }); } catch (e) { console.error('消息序列化失败:', e); } } else { console.warn('WebSocket 未连接'); wx.showToast({ title: '网络连接中...', icon: 'none' }); } }, close: (code = 1000, reason = 'normal close') => { const that = this; if (that.globalData.socketTask) { console.log(`关闭WebSocket连接: ${code} - ${reason}`); that.globalData.socketTask.close({ code, reason }); } } }; } }, handleAppShow() { const that = this; console.log(!that.globalData.isConnected); console.log(!that.globalData.reconnectTimer); if (!that.globalData.isConnected && !that.globalData.reconnectTimer) { const { operationId, operationRole } = that.globalData.userInfo; console.log(operationRole); // 修复:startReconnect 中用了 operationId,这里统一字段 if (!operationId) { console.error('用户operationId不存在,无法连接WebSocket'); return; } const wsUrl = `wss://esos-iot.bjdexn.cn:9443/communication/update/${operationId}`; that.wsService.connect(wsUrl); } }, handleAppHide() { // 可以选择在后台关闭连接 // this.wsService.close(1001, 'app enter background'); }, startHeartbeat() { const that = this; that.stopHeartbeat(); that.heartbeatTimer = setInterval(() => { if (that.globalData.isConnected) { that.wsService.send({ type: 'heartbeat', timestamp: Date.now() }); } }, 5000); }, stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } }, startReconnect() { const that = this; that.clearReconnectTimer(); let delay = 1000; that.globalData.reconnectTimer = setInterval(() => { if (!that.globalData.isConnected) { console.log(`尝试重连 in ${delay}ms...`); const { operationId, operationRole } = that.globalData.userInfo; // 修复:用 operationId 而非 userId if (operationId) { const wsUrl = `wss://esos-iot.bjdexn.cn:9443/communication/update/${operationId}`; that.wsService.connect(wsUrl); } delay = Math.min(delay * 2, 8000); } else { that.clearReconnectTimer(); } }, delay); }, clearReconnectTimer() { if (this.globalData.reconnectTimer) { clearInterval(this.globalData.reconnectTimer); this.globalData.reconnectTimer = null; } }, /** * 司机端开始后台定位并上传(单工单) * @param {string} workorderId - 当前工单ID */ startDriverLocationUpload(workorderId) { const service = this.locationService; // 入参校验 if (!workorderId) { console.error('工单ID不能为空'); wx.showToast({ title: '工单ID异常', icon: 'none' }); return; } // 如果已经在为当前订单上传,则忽略 if (service.isTracking && service.currentWorkorderId === workorderId) { console.log(`已经在为订单 ${workorderId} 上传位置`); return; } // 如果已经在为其他订单上传,先停止 if (service.isTracking && service.currentWorkorderId !== workorderId) { console.log(`当前正在为订单 ${service.currentWorkorderId} 上传,切换到 ${workorderId},先停止旧定位`); this.stopDriverLocationUpload(); } // 权限校验 wx.getSetting({ success: (res) => { if (!res.authSetting['scope.userLocationBackground']) { wx.authorize({ scope: 'scope.userLocationBackground', success: () => { this._doStartLocationUpload(workorderId, service); }, fail: () => { wx.showModal({ title: '权限不足', content: '需要开启后台定位权限才能实时上传位置,请前往设置页开启', confirmText: '去设置', success: (modalRes) => { if (modalRes.confirm) { wx.openSetting({ success: (settingRes) => { if (settingRes.authSetting['scope.userLocationBackground']) { this._doStartLocationUpload(workorderId, service); } } }); } } }); } }); } else { this._doStartLocationUpload(workorderId, service); } } }); }, /** * 私有方法:执行真正的定位启动逻辑(核心修复:兼容低版本,改用 wx.onLocationChange) */ // _doStartLocationUpload(workorderId, service) { console.log(`开始为订单 ${workorderId} 启动后台定位上传...`); service.currentWorkorderId = workorderId; // 修复1:先移除旧的定位监听(防止重复监听) this._removeLocationListener(); // 修复2:启动后台定位(兼容低版本,用 startLocationUpdate 兜底) const startLocationApi = wx.startLocationUpdateBackground || wx.startLocationUpdate; startLocationApi({ type: 'gcj02', success: (res) => { console.log('定位服务已启动', res); service.isLocationUpdateStarted = true; // 存储上一次的定位信息 let lastLocation = null; // 距离阈值(单位:米,根据需求调整,5米) const DISTANCE_THRESHOLD = 5; // 修复3:改用低版本支持的 wx.onLocationChange(替代 wx.watchPosition) wx.onLocationChange((location) => { if (!lastLocation) { lastLocation = location; console.log('位置变化,准备上传:', location); this._uploadLocation(location, workorderId); return; } // 计算当前定位与上一次的直线距离(Haversine公式) const distance = this.calculateDistance( lastLocation.latitude, lastLocation.longitude, location.latitude, location.longitude ); console.log(distance); // 只有距离超过阈值,才认为是“有效变化” if (distance >= DISTANCE_THRESHOLD) { lastLocation = location; // 更新上一次定位 this._uploadLocation(location, workorderId); } }); // 标记定位中状态 service.isTracking = true; console.log('位置监听已注册,开始实时上传'); }, fail: (err) => { let errMsg = '启动定位失败'; if (err.errMsg.includes('auth deny')) errMsg = '定位权限被拒绝'; else if (err.errMsg.includes('system')) errMsg = '系统不支持定位'; console.error(errMsg, err); wx.showToast({ title: errMsg, icon: 'none', duration: 2000 }); } }); }, // Haversine公式:计算两点经纬度之间的直线距离(单位:米) calculateDistance(lat1, lng1, lat2, lng2) { const R = 6371000; // 地球半径(米) const radLat1 = (lat1 * Math.PI) / 180; const radLat2 = (lat2 * Math.PI) / 180; const deltaLat = radLat2 - radLat1; const deltaLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // 距离(米) }, /** * 司机端停止定位上传(核心修复:对应移除 wx.onLocationChange 监听) */ stopDriverLocationUpload() { const service = this.locationService; if (!service.isTracking) { return; } console.log(`停止为订单 ${service.currentWorkorderId} 上传位置`); // 1. 移除位置监听(wx.onLocationChange 对应 wx.offLocationChange) this._removeLocationListener(); // 2. 停止定位服务(兼容低版本,用 stopLocationUpdate 兜底) this._stopLocationService(); // 3. 重置状态 service.isTracking = false; service.currentWorkorderId = ''; service.isLocationUpdateStarted = false; }, /** * 私有方法:移除位置监听(兼容低版本) */ _removeLocationListener() { if (wx.offLocationChange) { wx.offLocationChange(); console.log('位置监听已移除'); } else { console.warn('当前基础库不支持 wx.offLocationChange,可能存在重复监听风险'); } }, /** * 私有方法:统一停止定位服务(兼容低版本) */ _stopLocationService() { const stopLocationApi = wx.stopLocationUpdateBackground || wx.stopLocationUpdate; if (stopLocationApi && this.locationService.isLocationUpdateStarted) { stopLocationApi({ success: (res) => { console.log('定位服务已停止', res); }, fail: (err) => { console.error('停止定位服务失败:', err); } }); } }, /** * 私有方法:上传位置到服务器(无改动) */ _uploadLocation(location, workorderId, retryCount = 0) { const maxRetry = 2; const isLatValid = location.latitude >= 3.86 && location.latitude <= 53.55; const isLngValid = location.longitude >= 73.66 && location.longitude <= 135.05; if (!isLatValid || !isLngValid) { console.warn(`无效位置信息(订单: ${workorderId}),跳过上传`, location); return; } const data = { workorderId: workorderId, latitude: location.latitude, longitude: location.longitude, createTime: Date.now(), accuracy: location.accuracy }; api.request(`/sysworkorder/insercoordinateredis`, 'post', data, { isPublic: false }) .then((response) => { if (response.code !== 200) { throw new Error(`响应码异常: ${response.code}`); } console.log(`位置上传成功 (订单: ${workorderId})`, data); }) .catch((err) => { console.error(`位置上传失败(第${retryCount+1}次)(订单: ${workorderId})`, err); if (retryCount < maxRetry) { setTimeout(() => { this._uploadLocation(location, workorderId, retryCount + 1); }, 10000 * (retryCount + 1)); } else { wx.showToast({ title: '位置上传失败,请检查网络', icon: 'none' }); } }); }, }); // 全局事件总线 wx.$on = function (eventName, callback) { if (!this.$events) this.$events = {}; if (!this.$events[eventName]) this.$events[eventName] = []; this.$events[eventName].push(callback); }; wx.$emit = function (eventName, data) { if (!this.$events || !this.$events[eventName]) return; this.$events[eventName].forEach(callback => callback(data)); }; wx.$off = function (eventName, callback) { if (!this.$events || !this.$events[eventName]) return; if (callback) { this.$events[eventName] = this.$events[eventName].filter(cb => cb !== callback); } else { this.$events[eventName] = []; } }; // 司机端增加人脸识别与指纹识别, // 云平台12月份电价整理