电速宝
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. // app.js
  2. const api = require('./api/index.js'); // 注意路径
  3. App({
  4. globalData: {
  5. socketTask: null,
  6. isConnected: false,
  7. reconnectTimer: null,
  8. heartbeatTimer: null,
  9. currentWorkorderId: '',
  10. userInfo: wx.getStorageSync('user') || {}
  11. },
  12. onLaunch() {
  13. // wx.setStorageSync('user', { userId: 'driver_1001', operationRole: 4 });
  14. this.initWebSocketService();
  15. wx.onAppShow(() => this.handleAppShow());
  16. wx.onAppHide(() => this.handleAppHide());
  17. this.locationService = {
  18. isTracking: false,
  19. currentWorkorderId: '', // 存储当前跟踪的工单ID
  20. isLocationUpdateStarted: false // 标记是否已启动定位服务
  21. };
  22. },
  23. initWebSocketService() {
  24. if (!this.wsService) {
  25. this.wsService = {
  26. connect: (url) => {
  27. const that = this;
  28. if (that.globalData.socketTask) {
  29. that.wsService.close();
  30. }
  31. console.log('正在连接WebSocket:', url);
  32. const socketTask = wx.connectSocket({ url, header: { 'content-type': 'application/json' } });
  33. socketTask.onOpen(() => {
  34. console.log('WebSocket 连接成功');
  35. that.globalData.isConnected = true;
  36. that.globalData.socketTask = socketTask;
  37. that.clearReconnectTimer();
  38. // that.startHeartbeat();
  39. if (that.globalData.currentWorkorderId) {
  40. that.wsService.send({ type: 'subscribe', workorderId: that.globalData.currentWorkorderId });
  41. }
  42. wx.$emit('socketOpen');
  43. });
  44. socketTask.onMessage((res) => {
  45. try {
  46. console.log(res);
  47. const message = JSON.parse(res.data);
  48. console.log('收到消息:', message);
  49. wx.$emit('wsMessage', message);
  50. } catch (err) { console.error('消息解析失败', err); }
  51. });
  52. socketTask.onClose((res) => {
  53. console.log('WebSocket 连接关闭, 状态码:', res.code);
  54. that.globalData.isConnected = false;
  55. that.globalData.socketTask = null;
  56. that.stopHeartbeat();
  57. wx.$emit('socketClose');
  58. if (res.code !== 1000) { that.startReconnect(); }
  59. });
  60. socketTask.onError((err) => {
  61. console.error('WebSocket 连接错误:', err);
  62. that.globalData.isConnected = false;
  63. that.globalData.socketTask = null;
  64. that.stopHeartbeat();
  65. wx.$emit('socketError', err);
  66. });
  67. },
  68. send: (data) => {
  69. const that = this;
  70. if (that.globalData.isConnected && that.globalData.socketTask) {
  71. try {
  72. that.globalData.socketTask.send({
  73. data: JSON.stringify(data),
  74. fail: (err) => {
  75. console.error('消息发送失败:', err);
  76. wx.showToast({ title: '消息发送失败', icon: 'none' });
  77. }
  78. });
  79. } catch (e) { console.error('消息序列化失败:', e); }
  80. } else {
  81. console.warn('WebSocket 未连接');
  82. wx.showToast({ title: '网络连接中...', icon: 'none' });
  83. }
  84. },
  85. close: (code = 1000, reason = 'normal close') => {
  86. const that = this;
  87. if (that.globalData.socketTask) {
  88. console.log(`关闭WebSocket连接: ${code} - ${reason}`);
  89. that.globalData.socketTask.close({ code, reason });
  90. }
  91. }
  92. };
  93. }
  94. },
  95. handleAppShow() {
  96. const that = this;
  97. console.log(!that.globalData.isConnected);
  98. console.log(!that.globalData.reconnectTimer);
  99. if (!that.globalData.isConnected && !that.globalData.reconnectTimer) {
  100. const { operationId, operationRole } = that.globalData.userInfo;
  101. console.log(operationRole);
  102. // 修复:startReconnect 中用了 operationId,这里统一字段
  103. if (!operationId) {
  104. console.error('用户operationId不存在,无法连接WebSocket');
  105. return;
  106. }
  107. const wsUrl = `wss://esos-iot.bjdexn.cn:9443/communication/update/${operationId}`;
  108. that.wsService.connect(wsUrl);
  109. }
  110. },
  111. handleAppHide() {
  112. // 可以选择在后台关闭连接
  113. // this.wsService.close(1001, 'app enter background');
  114. },
  115. startHeartbeat() {
  116. const that = this;
  117. that.stopHeartbeat();
  118. that.heartbeatTimer = setInterval(() => {
  119. if (that.globalData.isConnected) {
  120. that.wsService.send({ type: 'heartbeat', timestamp: Date.now() });
  121. }
  122. }, 5000);
  123. },
  124. stopHeartbeat() {
  125. if (this.heartbeatTimer) {
  126. clearInterval(this.heartbeatTimer);
  127. this.heartbeatTimer = null;
  128. }
  129. },
  130. startReconnect() {
  131. const that = this;
  132. that.clearReconnectTimer();
  133. let delay = 1000;
  134. that.globalData.reconnectTimer = setInterval(() => {
  135. if (!that.globalData.isConnected) {
  136. console.log(`尝试重连 in ${delay}ms...`);
  137. const { operationId, operationRole } = that.globalData.userInfo; // 修复:用 operationId 而非 userId
  138. if (operationId) {
  139. const wsUrl = `wss://esos-iot.bjdexn.cn:9443/communication/update/${operationId}`;
  140. that.wsService.connect(wsUrl);
  141. }
  142. delay = Math.min(delay * 2, 8000);
  143. } else {
  144. that.clearReconnectTimer();
  145. }
  146. }, delay);
  147. },
  148. clearReconnectTimer() {
  149. if (this.globalData.reconnectTimer) {
  150. clearInterval(this.globalData.reconnectTimer);
  151. this.globalData.reconnectTimer = null;
  152. }
  153. },
  154. /**
  155. * 司机端开始后台定位并上传(单工单)
  156. * @param {string} workorderId - 当前工单ID
  157. */
  158. startDriverLocationUpload(workorderId) {
  159. const service = this.locationService;
  160. // 入参校验
  161. if (!workorderId) {
  162. console.error('工单ID不能为空');
  163. wx.showToast({ title: '工单ID异常', icon: 'none' });
  164. return;
  165. }
  166. // 如果已经在为当前订单上传,则忽略
  167. if (service.isTracking && service.currentWorkorderId === workorderId) {
  168. console.log(`已经在为订单 ${workorderId} 上传位置`);
  169. return;
  170. }
  171. // 如果已经在为其他订单上传,先停止
  172. if (service.isTracking && service.currentWorkorderId !== workorderId) {
  173. console.log(`当前正在为订单 ${service.currentWorkorderId} 上传,切换到 ${workorderId},先停止旧定位`);
  174. this.stopDriverLocationUpload();
  175. }
  176. // 权限校验
  177. wx.getSetting({
  178. success: (res) => {
  179. if (!res.authSetting['scope.userLocationBackground']) {
  180. wx.authorize({
  181. scope: 'scope.userLocationBackground',
  182. success: () => {
  183. this._doStartLocationUpload(workorderId, service);
  184. },
  185. fail: () => {
  186. wx.showModal({
  187. title: '权限不足',
  188. content: '需要开启后台定位权限才能实时上传位置,请前往设置页开启',
  189. confirmText: '去设置',
  190. success: (modalRes) => {
  191. if (modalRes.confirm) {
  192. wx.openSetting({
  193. success: (settingRes) => {
  194. if (settingRes.authSetting['scope.userLocationBackground']) {
  195. this._doStartLocationUpload(workorderId, service);
  196. }
  197. }
  198. });
  199. }
  200. }
  201. });
  202. }
  203. });
  204. } else {
  205. this._doStartLocationUpload(workorderId, service);
  206. }
  207. }
  208. });
  209. },
  210. /**
  211. * 私有方法:执行真正的定位启动逻辑(核心修复:兼容低版本,改用 wx.onLocationChange)
  212. */
  213. _doStartLocationUpload(workorderId, service) {
  214. console.log(`开始为订单 ${workorderId} 启动后台定位上传...`);
  215. service.currentWorkorderId = workorderId;
  216. // 修复1:先移除旧的定位监听(防止重复监听)
  217. this._removeLocationListener();
  218. // 修复2:启动后台定位(兼容低版本,用 startLocationUpdate 兜底)
  219. const startLocationApi = wx.startLocationUpdateBackground || wx.startLocationUpdate;
  220. startLocationApi({
  221. type: 'gcj02',
  222. success: (res) => {
  223. console.log('定位服务已启动', res);
  224. service.isLocationUpdateStarted = true;
  225. // 修复3:改用低版本支持的 wx.onLocationChange(替代 wx.watchPosition)
  226. wx.onLocationChange((location) => {
  227. console.log('位置变化,准备上传:', location);
  228. this._uploadLocation(location, workorderId);
  229. });
  230. // 标记定位中状态
  231. service.isTracking = true;
  232. console.log('位置监听已注册,开始实时上传');
  233. },
  234. fail: (err) => {
  235. let errMsg = '启动定位失败';
  236. if (err.errMsg.includes('auth deny')) errMsg = '定位权限被拒绝';
  237. else if (err.errMsg.includes('system')) errMsg = '系统不支持定位';
  238. console.error(errMsg, err);
  239. wx.showToast({ title: errMsg, icon: 'none', duration: 2000 });
  240. }
  241. });
  242. },
  243. /**
  244. * 司机端停止定位上传(核心修复:对应移除 wx.onLocationChange 监听)
  245. */
  246. stopDriverLocationUpload() {
  247. const service = this.locationService;
  248. if (!service.isTracking) {
  249. return;
  250. }
  251. console.log(`停止为订单 ${service.currentWorkorderId} 上传位置`);
  252. // 1. 移除位置监听(wx.onLocationChange 对应 wx.offLocationChange)
  253. this._removeLocationListener();
  254. // 2. 停止定位服务(兼容低版本,用 stopLocationUpdate 兜底)
  255. this._stopLocationService();
  256. // 3. 重置状态
  257. service.isTracking = false;
  258. service.currentWorkorderId = '';
  259. service.isLocationUpdateStarted = false;
  260. },
  261. /**
  262. * 私有方法:移除位置监听(兼容低版本)
  263. */
  264. _removeLocationListener() {
  265. if (wx.offLocationChange) {
  266. wx.offLocationChange();
  267. console.log('位置监听已移除');
  268. } else {
  269. console.warn('当前基础库不支持 wx.offLocationChange,可能存在重复监听风险');
  270. }
  271. },
  272. /**
  273. * 私有方法:统一停止定位服务(兼容低版本)
  274. */
  275. _stopLocationService() {
  276. const stopLocationApi = wx.stopLocationUpdateBackground || wx.stopLocationUpdate;
  277. if (stopLocationApi && this.locationService.isLocationUpdateStarted) {
  278. stopLocationApi({
  279. success: (res) => {
  280. console.log('定位服务已停止', res);
  281. },
  282. fail: (err) => {
  283. console.error('停止定位服务失败:', err);
  284. }
  285. });
  286. }
  287. },
  288. /**
  289. * 私有方法:上传位置到服务器(无改动)
  290. */
  291. _uploadLocation(location, workorderId, retryCount = 0) {
  292. const maxRetry = 2;
  293. const isLatValid = location.latitude >= 3.86 && location.latitude <= 53.55;
  294. const isLngValid = location.longitude >= 73.66 && location.longitude <= 135.05;
  295. if (!isLatValid || !isLngValid) {
  296. console.warn(`无效位置信息(订单: ${workorderId}),跳过上传`, location);
  297. return;
  298. }
  299. const data = {
  300. workorderId: workorderId,
  301. latitude: location.latitude,
  302. longitude: location.longitude,
  303. createTime: Date.now(),
  304. accuracy: location.accuracy
  305. };
  306. api.request(`/sysworkorder/insercoordinateredis`, 'post', data, { isPublic: false })
  307. .then((response) => {
  308. if (response.code !== 200) {
  309. throw new Error(`响应码异常: ${response.code}`);
  310. }
  311. console.log(`位置上传成功 (订单: ${workorderId})`, data);
  312. })
  313. .catch((err) => {
  314. console.error(`位置上传失败(第${retryCount+1}次)(订单: ${workorderId})`, err);
  315. if (retryCount < maxRetry) {
  316. setTimeout(() => {
  317. this._uploadLocation(location, workorderId, retryCount + 1);
  318. }, 10000 * (retryCount + 1));
  319. } else {
  320. wx.showToast({ title: '位置上传失败,请检查网络', icon: 'none' });
  321. }
  322. });
  323. },
  324. });
  325. // 全局事件总线
  326. wx.$on = function (eventName, callback) {
  327. if (!this.$events) this.$events = {};
  328. if (!this.$events[eventName]) this.$events[eventName] = [];
  329. this.$events[eventName].push(callback);
  330. };
  331. wx.$emit = function (eventName, data) {
  332. if (!this.$events || !this.$events[eventName]) return;
  333. this.$events[eventName].forEach(callback => callback(data));
  334. };
  335. wx.$off = function (eventName, callback) {
  336. if (!this.$events || !this.$events[eventName]) return;
  337. if (callback) {
  338. this.$events[eventName] = this.$events[eventName].filter(cb => cb !== callback);
  339. } else {
  340. this.$events[eventName] = [];
  341. }
  342. };
  343. // 司机端增加人脸识别与指纹识别,
  344. // 云平台12月份电价整理