// app.js const api = require('./api/index.js'); App({ globalData: { socketTask: null, isConnected: false, reconnectTimer: null, heartbeatTimer: null, currentWorkorderId: '', userInfo: wx.getStorageSync('user') || {} }, onLaunch() { this.initWebSocketService(); wx.onAppShow(() => this.handleAppShow()); wx.onAppHide(() => this.handleAppHide()); this.locationService = { isTracking: false, currentWorkorderId: '', isLocationUpdateStarted: false }; // 司机自动启动【普通实时定位上传】 const { operationRole } = this.globalData.userInfo; if (operationRole === 4) { this.startDriverRealTimeLocation(); } }, initWebSocketService() { if (!this.wsService) { this.wsService = { connect: (url) => { const that = this; if (that.globalData.socketTask) { that.wsService.close(); } const socketTask = wx.connectSocket({ url, header: { 'content-type': 'application/json' } }); socketTask.onOpen(() => { that.globalData.isConnected = true; that.globalData.socketTask = socketTask; that.clearReconnectTimer(); if (that.globalData.currentWorkorderId) { that.wsService.send({ type: 'subscribe', workorderId: that.globalData.currentWorkorderId }); } wx.$emit('socketOpen'); }); socketTask.onMessage((res) => { try { const message = JSON.parse(res.data); wx.$emit('wsMessage', message); } catch (err) { } }); socketTask.onClose((res) => { that.globalData.isConnected = false; that.globalData.socketTask = null; that.stopHeartbeat(); if (res.code !== 1000) that.startReconnect(); }); socketTask.onError((err) => { that.globalData.isConnected = false; that.globalData.socketTask = null; that.stopHeartbeat(); }); }, send: (data) => { const that = this; if (that.globalData.isConnected && that.globalData.socketTask) { that.globalData.socketTask.send({ data: JSON.stringify(data), fail: (err) => console.error('发送失败', err) }); } }, close: (code = 1000, reason = 'normal close') => { const that = this; if (that.globalData.socketTask) { that.globalData.socketTask.close({ code, reason }); } } }; } }, handleAppShow() { const that = this; if (!that.globalData.isConnected && !that.globalData.reconnectTimer) { const { operationId } = that.globalData.userInfo; if (!operationId) return; const wsUrl = `wss://esos-iot.com:9443/communication/update/${operationId}`; that.wsService.connect(wsUrl); } }, handleAppHide() { }, 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) { const { operationId } = that.globalData.userInfo; if (operationId) { const wsUrl = `wss://esos-iot.com: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; } }, /**************************************************************************** * 1. 司机【普通实时定位】(一直上传,不绑定工单) ***************************************************************************/ startDriverRealTimeLocation() { const { operationRole } = this.globalData.userInfo; if (operationRole !== 4) return; const service = this.locationService; this._removeLocationListener(); const startLocationApi = wx.startLocationUpdateBackground || wx.startLocationUpdate; startLocationApi({ type: 'gcj02', success: () => { service.isLocationUpdateStarted = true; let lastLocation = null; const DISTANCE_THRESHOLD = 5; wx.onLocationChange((location) => { // 距离过滤 if (lastLocation) { const distance = this.calculateDistance( lastLocation.latitude, lastLocation.longitude, location.latitude, location.longitude ); if (distance < DISTANCE_THRESHOLD) return; } lastLocation = location; // 统一上传:实时位置 + 有工单就工单一起传 this._uploadRealTimeLocation(location); if (service.currentWorkorderId) { this._uploadWorkorderLocation(location, service.currentWorkorderId); } }); } }); }, /**************************************************************************** * 2. 司机【开始执行工单】(绑定工单,额外上传工单位置) ***************************************************************************/ startDriverWorkorderLocation(workorderId) { if (!workorderId) return; const service = this.locationService; service.currentWorkorderId = workorderId; }, /**************************************************************************** * 3. 停止工单定位(只停止工单,不影响实时定位上传) ***************************************************************************/ stopDriverWorkorderLocation() { const service = this.locationService; service.currentWorkorderId = ''; }, /**************************************************************************** * 上传:司机实时位置(一直传) ***************************************************************************/ _uploadRealTimeLocation(location) { const { operationRole } = this.globalData.userInfo; if (operationRole !== 4) return; const latOk = location.latitude >= 3.86 && location.latitude <= 53.55; const lngOk = location.longitude >= 73.66 && location.longitude <= 135.05; if (!latOk || !lngOk) return; const data = { userType: operationRole, latitude: location.latitude, longitude: location.longitude, accuracy: location.accuracy, createTime: Date.now() }; api.request('/sysoperation/inseroperationlocation', 'post', data, { isPublic: false }) .catch(err => console.error('实时位置上传失败', err)); }, /**************************************************************************** * 上传:工单位置(执行工单时才传) ***************************************************************************/ _uploadWorkorderLocation(location, workorderId) { const latOk = location.latitude >= 3.86 && location.latitude <= 53.55; const lngOk = location.longitude >= 73.66 && location.longitude <= 135.05; if (!latOk || !lngOk) 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 }) .catch(err => console.error('工单位置上传失败', err)); }, calculateDistance(lat1, lng1, lat2, lng2) { const R = 6371000; const radLat1 = lat1 * Math.PI / 180; const radLat2 = lat2 * Math.PI / 180; const dLat = radLat2 - radLat1; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(dLng / 2) ** 2; return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); }, _removeLocationListener() { if (wx.offLocationChange) wx.offLocationChange(); } }); // 全局事件总线 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(cb => cb(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] = []; };