电速宝
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

index.js 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. const api = require('../../../api/index.js');
  2. const app = getApp(); // 在文件顶部获取app实例
  3. Page({
  4. data: {
  5. // 地图中心坐标
  6. longitude: 0,
  7. latitude: 0,
  8. // 标记点(包含起点、终点)
  9. markers: [],
  10. // 轨迹线(实际轨迹)
  11. polyline: [{
  12. points: [],
  13. color: "#ff0000", // 已走过的路线颜色:红色
  14. width: 6,
  15. dottedLine: false
  16. }],
  17. // 规划路线
  18. plannedRoute: [{
  19. points: [],
  20. color: "#2f693c", // 未走的规划路线颜色:绿色
  21. width: 4,
  22. dottedLine: false,
  23. arrowLine: true
  24. }],
  25. // 追踪状态
  26. isTracking: false,
  27. // 信息提示文本
  28. infoText: "",
  29. // 位置更新计时器
  30. locationTimer: null, // 这个计时器现在只由司机端后台定位使用
  31. // 终点位置
  32. destination: null,
  33. // 路线规划状态
  34. routePlanned: false,
  35. // 腾讯位置服务Key
  36. qqMapKey: 'VRGBZ-ZFRHB-SKIUP-NHHJ3-TXGJT-ZIFG3',
  37. // 偏离阈值(米)和偏离状态
  38. deviationThreshold: 50, // 超过50米视为偏离
  39. isDeviated: false,
  40. // 重新规划相关
  41. isReplanning: false, // 是否正在重新规划
  42. deviationCount: 0, // 连续偏离计数
  43. replanThreshold: 3, // 连续偏离多少次后重新规划
  44. orderdata: [],
  45. optionsid: '',
  46. showVerification: false,
  47. phoneNumber: "138****6789",
  48. userdata: [],
  49. moretype: false,
  50. // 拖动
  51. cardHeight: 'min', // 初始高度档位:min/mid/max
  52. currentHeight: 0, // 当前实际高度(px)
  53. startY: 0, // 触摸起始Y坐标
  54. maskOpacity: 0.3, // 遮罩透明度
  55. windowHeight: 0, // 屏幕可用高度(px)
  56. // 三个固定高度(max改为屏幕高度)
  57. heightConfig: {
  58. min: 320, // 最低高度(仅显示价格和路线简要)
  59. mid: 520, // 中间高度(显示行程详情)
  60. max: 0 // 占位,将在onLoad中设置为屏幕高度
  61. },
  62. costdetails: false,
  63. trackPoints: [],
  64. distance: '',
  65. obtainindex: 0,
  66. driverLocation: {}, // 司机位置(对象)
  67. mapInitialized: false, // 标记地图是否初始化
  68. isDriver: false // 标记是否为司机角色
  69. },
  70. onReady() {
  71. // 创建地图上下文
  72. this.mapCtx = wx.createMapContext('map');
  73. },
  74. onLoad(options) {
  75. // 监听WebSocket消息
  76. wx.$on('wsMessage', this.detailsWsMessage);
  77. // 获取屏幕可用高度(不含导航栏)
  78. const systemInfo = wx.getSystemInfoSync();
  79. const windowHeight = systemInfo.windowHeight;
  80. // 更新heightConfig,max设为屏幕高度
  81. this.setData({
  82. windowHeight,
  83. heightConfig: {
  84. ...this.data.heightConfig,
  85. max: windowHeight
  86. },
  87. currentHeight: this.data.heightConfig.min, // 初始高度为min档
  88. isDriver: wx.getStorageSync('user').operationRole == 4 // 初始化司机角色标记
  89. });
  90. this.setData({
  91. userdata: wx.getStorageSync('user') || {},
  92. optionsid: options.id
  93. });
  94. console.log('司机端模式:', this.data.isDriver);
  95. // 引入SDK核心类
  96. this.qqmapsdk = require('../../../libs/qqmap-wx-jssdk.min.js');
  97. // 初始化SDK
  98. this.mapSdk = new this.qqmapsdk({
  99. key: this.data.qqMapKey
  100. });
  101. // 初始化地图,获取当前位置和工单信息
  102. this.getoneworkorderdetailss();
  103. this.getoneworkorderdetails();
  104. },
  105. onUnload() {
  106. // 移除WebSocket消息监听
  107. wx.$off('wsMessage', this.detailsWsMessage);
  108. // 页面卸载时清理资源
  109. if (this.data.locationTimer) {
  110. clearInterval(this.data.locationTimer);
  111. }
  112. // 如果是司机角色,停止后台定位
  113. if (this.data.isDriver) {
  114. // wx.stopLocationUpdateBackground().catch(err => console.error('停止后台定位失败', err));
  115. // wx.offLocationChange(); // 移除位置监听
  116. }
  117. },
  118. /**
  119. * 处理接收到的 WebSocket 消息
  120. * @param {Object} message 从 app.js 广播过来的消息对象
  121. */
  122. detailsWsMessage(message) {
  123. console.log('收到 WebSocket 消息:', message);
  124. if (!message || !message.api) {
  125. console.warn('无效的WebSocket消息:', message);
  126. return;
  127. }
  128. // 根据消息的api字段进行不同的处理
  129. switch (message.api) {
  130. case '/sysworkorder/selectworkorderId':
  131. // 收到工单列表更新的消息,刷新当前工单信息
  132. // 这个消息通常意味着工单状态可能发生了变化(如分配、完成等)
  133. this.getoneworkorderdetails();
  134. break;
  135. case '/sysworkorder/selectworkorderredis':
  136. // 收到坐标上传的消息,意味着司机位置有更新
  137. // 这个消息可以由司机自己发出,也可以由服务器推送给乘客
  138. // 为了数据一致性,直接刷新轨迹
  139. this.historytrajectory();
  140. break;
  141. case '/sysworkorder/selectworkorderId':
  142. // 收到工单状态更新的消息(例如,乘客确认上车、到达目的地等)
  143. // 刷新工单信息以获取最新状态
  144. this.getoneworkorderdetails();
  145. break;
  146. // 你可以根据实际的业务需求,添加更多的case
  147. // case 'other_api_path':
  148. // // 处理其他类型的消息
  149. // break;
  150. default:
  151. // 对于未知类型的消息,可以打印日志以便调试
  152. console.log('收到未知类型的WebSocket消息, api:', message.api);
  153. // 作为一个备选方案,也可以默认刷新工单信息,确保数据最新
  154. // this.getoneworkorderdetails();
  155. break;
  156. }
  157. },
  158. getoneworkorderdetailss() {
  159. const data = {
  160. workorderId: this.data.optionsid
  161. };
  162. api.request(`/sysworkorder/selectworkorderId`, 'post', data, { isPublic: false })
  163. .then((data) => {
  164. console.log('工单数据:', data.data);
  165. if (data.code == 200) {
  166. // 保存旧的工单类型,用于比较状态变化
  167. const oldWorkorderType = this.data.orderdata.workorderType;
  168. // **核心修改点:调用 app.js 的方法来控制定位上传**
  169. if (this.data.isDriver) {
  170. // 如果工单状态变为进行中 (1-4),且之前未在上传,则启动上传
  171. if (data.data.workorderType >= 1 && data.data.workorderType <= 2) {
  172. app.startDriverLocationUpload(this.data.optionsid);
  173. this.showInfo('已开启实时位置上传');
  174. }
  175. this.initMap();
  176. }
  177. }
  178. })
  179. .catch((err) => {
  180. this.showInfo('获取工单信息失败');
  181. });
  182. },
  183. getoneworkorderdetails() {
  184. const data = {
  185. workorderId: this.data.optionsid
  186. };
  187. api.request(`/sysworkorder/selectworkorderId`, 'post', data, { isPublic: false })
  188. .then((data) => {
  189. console.log('工单数据:', data.data);
  190. if (data.code == 200) {
  191. // 保存旧的工单类型,用于比较状态变化
  192. this.setData({
  193. orderdata: data.data
  194. });
  195. this.setDestination();
  196. // this.setData({
  197. // markers: [],
  198. // polyline: [{ points: [] }],
  199. // plannedRoute: [{ points: [] }],
  200. // driverLocation: {}
  201. // });
  202. }
  203. })
  204. .catch((err) => {
  205. console.error('获取工单信息失败:', err);
  206. this.showInfo('获取工单信息失败');
  207. });
  208. },
  209. // 初始化地图(优化司机端逻辑)
  210. initMap() {
  211. // 获取用户当前位置(司机端优先使用自身位置作为地图中心)
  212. wx.getLocation({
  213. type: 'gcj02', // 腾讯地图坐标体系
  214. success: (res) => {
  215. const { longitude, latitude } = res;
  216. this.setData({
  217. longitude,
  218. latitude,
  219. });
  220. // 司机端逻辑:仅在工单已分配时开启后台定位(workorderType >= 1)
  221. if (this.data.isDriver && this.data.orderdata.workorderType >= 1) {
  222. this.setData({
  223. driverLocation: { longitude, latitude } // 司机端初始化自身位置
  224. })
  225. this.historytrajectory(); // 主动加载历史轨迹
  226. }
  227. this.setDestination();
  228. },
  229. fail: (err) => {
  230. console.error("获取位置失败:", err);
  231. this.showInfo(this.data.isDriver ? "司机定位权限未授权,请开启" : "请授权位置权限以使用地图功能");
  232. // 引导用户开启权限
  233. wx.openSetting({
  234. success: (res) => {
  235. if (res.authSetting['scope.userLocation']) {
  236. this.initMap();
  237. }
  238. }
  239. });
  240. }
  241. });
  242. },
  243. // 设置终点位置(核心修改:未分配工单时隐藏司机标记和轨迹)
  244. setDestination() {
  245. const ICON_PATH = {
  246. start: "https://esos-iot.bjdexn.cn/myminio/project/0b0593293af54ea097b168cea479c25c.png",
  247. end: "https://esos-iot.bjdexn.cn/myminio/project/6dc37b15321b462f9ab59c47263dd224.png",
  248. driver: "https://esos-iot.bjdexn.cn/myminio/project/f6fba6c83b2d4864a73f5f1d83cda416.png"
  249. };
  250. const MARKER_SIZE = { width: 30, height: 30 };
  251. const MAP_PADDING = [100, 100, 100, 100];
  252. const ROUTE_COLOR = "#ff0000";
  253. const ROUTE_WIDTH = 6;
  254. const requestData = {
  255. workorderId: this.data.optionsid
  256. };
  257. api.request(`/sysworkorder/selectworkorderredis`, 'post', requestData, { isPublic: false })
  258. .then((response) => {
  259. console.log('Redis轨迹数据:', response.data);
  260. if (response.code !== 200) {
  261. this.showInfo("获取轨迹数据失败,请重试");
  262. return;
  263. }
  264. if (!this.data.orderdata) {
  265. this.showInfo("订单信息加载中...");
  266. return;
  267. }
  268. // 关键判断:工单是否处于进行中状态(1-4)
  269. const isWorkorderAssigned = this.data.orderdata.workorderType >= 1 && this.data.orderdata.workorderType <= 4;
  270. // 初始化司机位置为 null
  271. let driverPoint = null;
  272. // 如果工单处于进行中状态
  273. if (isWorkorderAssigned) {
  274. // 优先从最新的响应数据中获取司机位置
  275. // 只有当 response.data 是一个非空数组时,才取第一个元素
  276. if (response.data && Array.isArray(response.data) && response.data.length > 0) {
  277. driverPoint = response.data[0];
  278. } else {
  279. // 如果响应数据为空或格式不正确,则使用页面上已缓存的司机位置
  280. driverPoint = null;
  281. }
  282. }else{
  283. const markers = [];
  284. const endPoint = this.data.orderdata;
  285. // 已分配工单时才显示起点
  286. markers.push({
  287. id: 0,
  288. longitude: response.data[response.data.length - 1].longitude,
  289. latitude: response.data[response.data.length - 1].latitude,
  290. iconPath: ICON_PATH.start,
  291. ...MARKER_SIZE,
  292. title: "起点"
  293. });
  294. // 始终添加终点标记
  295. markers.push({
  296. id: 1,
  297. longitude: endPoint.longitude,
  298. latitude: endPoint.latitude,
  299. iconPath: ICON_PATH.end,
  300. ...MARKER_SIZE,
  301. title: "终点",
  302. name: endPoint.poiName
  303. });
  304. this.setData({
  305. markers,
  306. plannedRoute: [{
  307. points: response.data,
  308. color: "#2f693c", // 未走的规划路线颜色:绿色
  309. width: 4,
  310. dottedLine: false,
  311. arrowLine: true
  312. }]
  313. })
  314. this.mapCtx.includePoints({
  315. points: response.data,
  316. padding: MAP_PADDING
  317. });
  318. return
  319. }
  320. // 对于工单状态为 5 (已完成) 或其他状态,driverPoint 保持为 null
  321. const startPoint = isWorkorderAssigned ? (response.data && response.data.length > 0 ? response.data[response.data.length - 1] : null) : null;
  322. const endPoint = this.data.orderdata;
  323. console.log(driverPoint);
  324. // 构建标记点(未分配时不添加司机标记)
  325. const markers = [];
  326. // 已分配工单时才显示起点
  327. if (isWorkorderAssigned && startPoint) {
  328. markers.push({
  329. id: 0,
  330. longitude: startPoint.longitude,
  331. latitude: startPoint.latitude,
  332. iconPath: ICON_PATH.start,
  333. ...MARKER_SIZE,
  334. title: "起点"
  335. });
  336. }
  337. // 始终添加终点标记
  338. markers.push({
  339. id: 1,
  340. longitude: endPoint.longitude,
  341. latitude: endPoint.latitude,
  342. iconPath: ICON_PATH.end,
  343. ...MARKER_SIZE,
  344. title: "终点",
  345. name: endPoint.poiName
  346. });
  347. console.log(driverPoint);
  348. // 已分配工单且有司机位置时,才添加司机标记
  349. if (isWorkorderAssigned && driverPoint) {
  350. markers.push({
  351. id: 2,
  352. longitude: driverPoint.longitude,
  353. latitude: driverPoint.latitude,
  354. iconPath: ICON_PATH.driver,
  355. width: 35,
  356. height: 35,
  357. title: this.data.isDriver ? "我的位置" : "司机正在赶来",
  358. callout: {
  359. content: this.data.isDriver ? "我的位置" : "司机正在赶来",
  360. display: "BYCLICK"
  361. }
  362. });
  363. }
  364. // 构建轨迹线(未分配时不显示轨迹线)
  365. const polyline = [];
  366. if (isWorkorderAssigned) {
  367. if (response.data && response.data.length > 1) {
  368. const trackPoints = response.data.map(point => ({
  369. longitude: point.longitude,
  370. latitude: point.latitude
  371. }));
  372. polyline.push({
  373. points: trackPoints,
  374. color: ROUTE_COLOR,
  375. width: ROUTE_WIDTH,
  376. dottedLine: false
  377. });
  378. } else if (startPoint && endPoint) {
  379. polyline.push({
  380. points: [
  381. { longitude: startPoint.longitude, latitude: startPoint.latitude },
  382. { longitude: endPoint.longitude, latitude: endPoint.latitude }
  383. ],
  384. color: ROUTE_COLOR,
  385. width: ROUTE_WIDTH,
  386. dottedLine: false
  387. });
  388. }
  389. }
  390. // 更新页面数据
  391. this.setData({
  392. markers,
  393. startPoint: isWorkorderAssigned ? startPoint : null,
  394. destination: { longitude: endPoint.longitude, latitude: endPoint.latitude },
  395. polyline
  396. });
  397. // 提示信息区分工单状态
  398. // if (isWorkorderAssigned) {
  399. // if (startPoint) {
  400. // this.showInfo(this.data.isDriver ? "已加载起点、终点和我的位置" : "已设置起点和终点位置");
  401. // } else {
  402. // this.showInfo(this.data.isDriver ? "已加载终点和我的位置(等待起点更新)" : "已加载终点位置(等待起点更新)");
  403. // }
  404. // } else {
  405. // this.showInfo("工单未分配,暂未显示司机信息");
  406. // }
  407. // 地图视野初始化(未分配时仅显示终点)
  408. if (this.mapCtx && !this.data.mapInitialized) {
  409. const includePoints = [{
  410. longitude: endPoint.longitude,
  411. latitude: endPoint.latitude
  412. }];
  413. // 已分配工单时才包含起点和司机位置
  414. if (isWorkorderAssigned) {
  415. if (startPoint) includePoints.push({ longitude: startPoint.longitude, latitude: startPoint.latitude });
  416. if (driverPoint) includePoints.push({ longitude: driverPoint.longitude, latitude: driverPoint.latitude });
  417. }
  418. console.log(includePoints);
  419. this.mapCtx.includePoints({
  420. points: includePoints,
  421. padding: MAP_PADDING
  422. });
  423. this.setData({ mapInitialized: true });
  424. }
  425. // 已分配工单且有起点终点时,才规划路线
  426. if (isWorkorderAssigned && startPoint && endPoint && !this.data.routePlanned) {
  427. this.planRoute();
  428. }
  429. })
  430. .catch((err) => {
  431. console.error('请求轨迹数据失败:', err);
  432. if (this.data.orderdata) {
  433. const endPoint = this.data.orderdata;
  434. const isWorkorderAssigned = this.data.orderdata.workorderType >= 1;
  435. const markers = [
  436. {
  437. id: 1,
  438. longitude: endPoint.longitude,
  439. latitude: endPoint.latitude,
  440. iconPath: ICON_PATH.end,
  441. ...MARKER_SIZE,
  442. title: "终点",
  443. name: endPoint.poiName
  444. }
  445. ];
  446. // 已分配工单时才显示司机位置
  447. if (isWorkorderAssigned && this.data.driverLocation.longitude) {
  448. markers.push({
  449. id: 2,
  450. longitude: this.data.driverLocation.longitude,
  451. latitude: this.data.driverLocation.latitude,
  452. iconPath: ICON_PATH.driver,
  453. width: 35,
  454. height: 35,
  455. title: "我的位置"
  456. });
  457. }
  458. this.setData({
  459. markers,
  460. startPoint: isWorkorderAssigned ? null : null,
  461. destination: { longitude: endPoint.longitude, latitude: endPoint.latitude },
  462. polyline: [] // 异常时不显示轨迹线
  463. });
  464. if (this.mapCtx && !this.data.mapInitialized) {
  465. const includePoints = [{ longitude: endPoint.longitude, latitude: endPoint.latitude }];
  466. if (isWorkorderAssigned && this.data.driverLocation.longitude) {
  467. includePoints.push({
  468. longitude: this.data.driverLocation.longitude,
  469. latitude: this.data.driverLocation.latitude
  470. });
  471. }
  472. this.mapCtx.includePoints({ points: includePoints, padding: MAP_PADDING });
  473. this.setData({ mapInitialized: true });
  474. }
  475. // 异常提示区分工单状态
  476. if (isWorkorderAssigned) {
  477. this.showInfo(this.data.isDriver ? "已加载终点和我的位置(轨迹数据加载失败)" : "已加载终点位置(等待起点更新)");
  478. } else {
  479. this.showInfo("工单未分配,暂未显示司机信息");
  480. }
  481. } else {
  482. this.showInfo("网络请求失败,请检查网络");
  483. }
  484. });
  485. },
  486. planRoute(isReplan = false) {
  487. const { startPoint, destination } = this.data;
  488. if (!startPoint || !destination) {
  489. this.showInfo("无法规划路线:起点或终点信息不完整");
  490. return;
  491. }
  492. if (isReplan) {
  493. this.setData({ isReplanning: true });
  494. this.showInfo("正在重新规划路线...");
  495. } else {
  496. this.showInfo("正在规划路线...");
  497. }
  498. this.mapSdk.direction({
  499. mode: 'driving',
  500. policy: 'REAL_TRAFFIC',
  501. from: { latitude: startPoint.latitude, longitude: startPoint.longitude },
  502. to: { latitude: destination.latitude, longitude: destination.longitude },
  503. success: (res) => {
  504. console.log("路线规划结果:", res);
  505. if (res.status == 0 && res.result.routes.length > 0) {
  506. const result = res.result;
  507. const route = result.routes[0];
  508. // 计算预计到达时间
  509. const durationMinutes = Math.ceil(result.routes[0].duration);
  510. const now = new Date();
  511. now.setMinutes(now.getMinutes() + durationMinutes);
  512. // 格式化时间(HH:MM)
  513. const estimatedArrivalTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
  514. const estimatedDuration = `${durationMinutes}分钟`;
  515. // 提取路线点
  516. const coors = route.polyline;
  517. const pl = [];
  518. // 坐标解压(返回的点串坐标,通过前向差分进行压缩)
  519. const kr = 1000000;
  520. for (let i = 2; i < coors.length; i++) {
  521. coors[i] = Number(coors[i - 2]) + Number(coors[i]) / kr;
  522. }
  523. // 将解压后的坐标放入点串数组pl中
  524. for (let i = 0; i < coors.length; i += 2) {
  525. pl.push({
  526. latitude: coors[i],
  527. longitude: coors[i + 1]
  528. });
  529. }
  530. // 更新路线数据
  531. const distance = (result.routes[0].distance / 1000).toFixed(1);
  532. this.setData({
  533. estimatedArrivalTime,
  534. estimatedDuration,
  535. plannedRoute: [{
  536. name: this.data.orderdata.poiName,
  537. points: pl,
  538. color: '#2f693c',
  539. width: 6,
  540. borderColor: '#2f693c',
  541. borderWidth: 1,
  542. arrowLine: true
  543. }],
  544. routePlanned: true,
  545. isReplanning: false,
  546. deviationCount: 0,
  547. distance
  548. });
  549. // 显示规划结果(区分司机/用户端提示)
  550. const message = this.data.isDriver
  551. ? (isReplan ? `已重新规划路线,距离${distance}公里,预计${estimatedDuration}后到达` : `距离${distance}公里,预计${estimatedDuration}后到达`)
  552. : (isReplan ? `已重新规划路线,距离${distance}公里,约${Math.ceil(result.routes[0].duration)}分钟` : `距离${distance}公里,约${Math.ceil(result.routes[0].duration)}分钟`);
  553. console.log(message);
  554. this.showInfo(message);
  555. } else {
  556. this.setData({ isReplanning: false });
  557. this.showInfo("路线规划失败,请重试");
  558. }
  559. },
  560. fail: (err) => {
  561. console.error("路线规划失败:", err);
  562. this.setData({ isReplanning: false });
  563. this.showInfo("路线规划失败,请检查网络");
  564. }
  565. });
  566. },
  567. // 获取历史轨迹线(司机端主动更新自身轨迹)
  568. historytrajectory() {
  569. // 工单未分配时不获取轨迹
  570. if (this.data.orderdata.workorderType < 1) return;
  571. const data = {
  572. workorderId: this.data.optionsid
  573. };
  574. api.request(`/sysworkorder/selectworkorderredis`, 'post', data, { isPublic: false })
  575. .then((response) => {
  576. console.log('历史轨迹:', response.data);
  577. // 兼容处理:检查返回数据是否有效
  578. if (!response.data || response.data.length === 0) {
  579. console.log('未获取到轨迹数据或轨迹数据为空');
  580. this.handleEmptyTrajectory();
  581. return;
  582. }
  583. if (response.code == 200 && response.data.length > 0) {
  584. // 司机端和用户端统一从[0]获取最新位置
  585. this.updateDriverLocation(response.data[0]);
  586. // 如果有多个轨迹点,更新轨迹线
  587. if (response.data.length > 1) {
  588. const trackPoints = response.data.map(point => ({
  589. longitude: point.longitude*1,
  590. latitude: point.latitude*1
  591. }));
  592. this.setData({
  593. polyline: [{
  594. points: trackPoints,
  595. color: "#ff0000",
  596. width: 6,
  597. dottedLine: false
  598. }]
  599. });
  600. console.log(trackPoints);
  601. if (trackPoints.length<3) {
  602. this.mapCtx.includePoints({
  603. points:[this.data.destination,...trackPoints],
  604. padding: [100, 100, 100, 100] // 适配屏幕边距
  605. });
  606. }
  607. } else {
  608. // 如果只有一个轨迹点(通常是司机的当前位置),则只更新司机位置,不绘制轨迹线
  609. console.log('只有一个轨迹点,不绘制轨迹线');
  610. this.setData({
  611. polyline: [{ points: [] }] // 清空轨迹线
  612. });
  613. }
  614. } else {
  615. // 服务器返回非200状态码或数据格式不正确
  616. // console.warn('获取轨迹数据失败,服务器返回:', response);
  617. this.handleEmptyTrajectory();
  618. }
  619. })
  620. .catch((err) => {
  621. console.error('获取历史轨迹失败:', err);
  622. // 司机端额外提示
  623. if (this.data.isDriver) {
  624. this.showInfo("轨迹更新失败,已自动重试");
  625. }
  626. });
  627. },
  628. /**
  629. * 处理空轨迹数据的情况
  630. */
  631. handleEmptyTrajectory() {
  632. // 清空轨迹线,避免显示旧的轨迹
  633. this.setData({
  634. polyline: [{ points: [] }]
  635. });
  636. // 根据角色给出不同的提示
  637. if (this.data.isDriver) {
  638. // 司机端:可能是刚接单,还未开始上传位置
  639. this.showInfo("尚未记录轨迹,请开始行驶或检查定位");
  640. } else {
  641. // 用户端:司机可能还未出发或未上传位置
  642. this.showInfo("司机尚未开始行驶或位置信息未更新");
  643. }
  644. },
  645. updateDriverLocation(newDriverLocation) {
  646. // 工单未分配时不更新司机位置
  647. if (this.data.orderdata.workorderType < 1) return;
  648. if (!newDriverLocation) return;
  649. // 更新司机位置数据
  650. this.setData({
  651. driverLocation: newDriverLocation
  652. });
  653. // 获取当前标记数组
  654. const markers = [...this.data.markers];
  655. // 找到司机标记(id:2)并更新位置
  656. const driverMarkerIndex = markers.findIndex(marker => marker.id == 2);
  657. if (driverMarkerIndex !== -1) {
  658. // 更新司机标记位置
  659. markers[driverMarkerIndex] = {
  660. ...markers[driverMarkerIndex],
  661. longitude: newDriverLocation.longitude,
  662. latitude: newDriverLocation.latitude
  663. };
  664. this.setData({ markers });
  665. } else {
  666. // 如果司机标记不存在,很可能是地图还没初始化好
  667. // 可以选择重新设置目的地来创建标记,或者给出提示
  668. console.warn('司机标记不存在,尝试重新设置目的地');
  669. // 这里可以加一个防抖(debounce),避免短时间内多次调用
  670. if (!this.setDestinationTimer) {
  671. this.setDestinationTimer = setTimeout(() => {
  672. this.setDestination();
  673. this.setDestinationTimer = null;
  674. }, 500);
  675. }
  676. }
  677. },
  678. // 显示信息提示(区分司机/用户端)
  679. showInfo(text) {
  680. this.setData({ infoText: text });
  681. // 3秒后自动隐藏非状态类信息(司机端状态类信息保留更久)
  682. const delay = this.data.isDriver && (text.includes("定位") || text.includes("轨迹") || text.includes("到达")) ? 5000 : 3000;
  683. if (!text.includes("正在") && !text.includes("距离") && !text.includes("偏离")) {
  684. setTimeout(() => {
  685. if (this.data.infoText == text) {
  686. this.setData({ infoText: "" });
  687. }
  688. }, delay);
  689. }
  690. },
  691. // 导航功能(司机端默认开启导航)
  692. navigation() {
  693. if (!this.data.destination) {
  694. this.showInfo("目的地尚未设置");
  695. return;
  696. }
  697. // 使用在腾讯位置服务申请的key
  698. const key = this.data.qqMapKey;
  699. const referer = '电速宝';
  700. const enableAI = true;
  701. const navigation = 1; // 强制开启导航
  702. const endPoint = JSON.stringify({
  703. name: this.data.orderdata.poiName || '目的地',
  704. latitude: this.data.orderdata.latitude,
  705. longitude: this.data.orderdata.longitude,
  706. });
  707. const layerStyle = 1;
  708. wx.navigateTo({
  709. url: `plugin://route-plan/index?key=${key}&referer=${referer}&endPoint=${endPoint}&enableAI=${enableAI}&navigation=${navigation}&layerStyle=${layerStyle}`,
  710. fail: (err) => {
  711. console.error('导航启动失败', err);
  712. this.showInfo(this.data.isDriver ? "导航启动失败,请手动开启" : "导航启动失败");
  713. }
  714. });
  715. },
  716. // 显示验证码弹窗(司机端无需验证码,直接执行操作)
  717. showVerificationPopup() {
  718. // 用户端正常显示验证码
  719. const data = { workorderId: this.data.optionsid };
  720. api.request(`/sysworkorder/createverify`, 'post', data, { isPublic: false })
  721. .then((data) => {
  722. if (data.code == 200) {
  723. wx.showToast({ title: data.msg || '验证码已发送', icon: 'none' });
  724. this.setData({ showVerification: true });
  725. } else {
  726. this.showInfo(data.msg || '获取验证码失败');
  727. }
  728. })
  729. .catch((err) => {
  730. console.error('获取验证码失败:', err);
  731. this.showInfo('获取验证码失败');
  732. });
  733. },
  734. // 关闭弹窗
  735. onPopupClose() {
  736. this.setData({ showVerification: false });
  737. setTimeout(() => {
  738. const slideComponent = this.selectComponent("#mySlideConfirm");
  739. if (slideComponent) {
  740. slideComponent.reset();
  741. }
  742. }, 1000);
  743. },
  744. // 确认验证码(仅用户端有效)
  745. onCodeConfirm(e) {
  746. if (!this.data.isDriver) {
  747. console.log("确认验证码:", e.detail.code);
  748. this.setData({ showVerification: false });
  749. }
  750. },
  751. // 重新发送验证码(仅用户端有效)
  752. onResendCode() {
  753. if (!this.data.isDriver) {
  754. console.log("重新发送验证码");
  755. this.showVerificationPopup();
  756. }
  757. },
  758. // 验证码输入完成
  759. onCodeComplete(e) {
  760. const code = e.detail.code;
  761. if (!code || code.length < 4) {
  762. this.showInfo('请输入有效的验证码');
  763. return;
  764. }
  765. const data = {
  766. carId: this.data.carId,
  767. verifyCode: code,
  768. workorderId: this.data.orderdata.workorderId
  769. };
  770. api.request(`/sysworkorder/submitworkorder`, 'post', data, { isPublic: false })
  771. .then((data) => {
  772. if (data.code == 200) {
  773. wx.showToast({ title: data.msg || '验证成功', icon: 'none' });
  774. this.setData({ showVerification: false });
  775. } else {
  776. this.showInfo(data.msg || '验证失败');
  777. }
  778. })
  779. .catch((err) => {
  780. console.error('验证请求失败:', err);
  781. this.showInfo('验证请求失败');
  782. setTimeout(() => {
  783. const slideComponent = this.selectComponent("#mySlideConfirm");
  784. if (slideComponent) {
  785. slideComponent.reset();
  786. }
  787. }, 5000);
  788. });
  789. },
  790. onrecognize(e){
  791. console.log(1111111);
  792. const that = this;
  793. // 检查设备是否支持生物认证--
  794. wx.checkIsSupportSoterAuthentication({
  795. success(res) {
  796. console.log(res.supportMode.includes('facial'));
  797. console.log(res.supportMode.includes('fingerPrint'));
  798. if (res.supportMode.includes('facial')) {
  799. // 支持人脸识别(FaceID)
  800. wx.startSoterAuthentication({
  801. requestAuthModes: ['facial'],
  802. challenge:e.currentTarget.dataset.type,
  803. authContent: '请进行人脸识别验证',
  804. success(res) {
  805. console.log('验证成功', res);
  806. that.onSlideSuccess()
  807. },
  808. fail(err) {
  809. setTimeout(() => {
  810. const slideComponent = that.selectComponent("#mySlideConfirm");
  811. if (slideComponent) {
  812. slideComponent.reset();
  813. }
  814. }, 5000);
  815. }
  816. });
  817. }
  818. if(res.supportMode.includes('fingerPrint')) {
  819. // 支持人脸识别(FaceID)
  820. wx.startSoterAuthentication({
  821. requestAuthModes: ['fingerPrint'],
  822. challenge:e.currentTarget.dataset.type,
  823. authContent: '请进行指纹识别验证',
  824. success(res) {
  825. console.log('验证成功', res);
  826. that.onSlideSuccess()
  827. },
  828. fail(err) {
  829. console.log(err);
  830. setTimeout(() => {
  831. const slideComponent = that.selectComponent("#mySlideConfirm");
  832. if (slideComponent) {
  833. slideComponent.reset();
  834. }
  835. }, 5000);
  836. }
  837. });
  838. }
  839. }
  840. });
  841. },
  842. onarrivalposition(){
  843. const data = {
  844. carId: this.data.orderdata.carId,
  845. verifyCode: '',
  846. workorderId: this.data.orderdata.workorderId
  847. };
  848. api.request(`/sysworkorder/submitworkorder`, 'post', data, { isPublic: false })
  849. .then((data) => {
  850. if (data.code == 200) {
  851. app.stopDriverLocationUpload(this.data.optionsid);
  852. setTimeout(() => {
  853. const slideComponent = this.selectComponent("#mySlideConfirm");
  854. if (slideComponent) {
  855. slideComponent.reset();
  856. }
  857. }, 5000);
  858. } else {
  859. this.showInfo(data.msg || '操作失败');
  860. }
  861. })
  862. .catch((err) => {
  863. console.error('操作请求失败:', err);
  864. this.showInfo('操作请求失败');
  865. setTimeout(() => {
  866. const slideComponent = this.selectComponent("#mySlideConfirm");
  867. if (slideComponent) {
  868. slideComponent.reset();
  869. }
  870. }, 5000);
  871. });
  872. },
  873. onSlideSuccess() {
  874. // console.log(e.currentTarget.dataset.type);
  875. // const type = e.currentTarget.dataset.type;
  876. const data = {
  877. carId: this.data.orderdata.carId,
  878. verifyCode: '',
  879. workorderId: this.data.orderdata.workorderId
  880. };
  881. api.request(`/sysworkorder/submitworkorder`, 'post', data, { isPublic: false })
  882. .then((data) => {
  883. if (data.code == 200) {
  884. // if (this.data.isDriver && type == 4) {
  885. // wx.stopLocationUpdateBackground({
  886. // success: (res) => {
  887. // console.log('后台定位已停止', res);
  888. // this.showInfo(this.data.isDriver ? '已停止定位追踪' : '已停止位置追踪');
  889. // },
  890. // fail: (err) => {
  891. // console.error('停止后台定位失败', err);
  892. // },
  893. // });
  894. // wx.offLocationChange();
  895. // }
  896. setTimeout(() => {
  897. const slideComponent = this.selectComponent("#mySlideConfirm");
  898. if (slideComponent) {
  899. slideComponent.reset();
  900. }
  901. }, 5000);
  902. } else {
  903. this.showInfo(data.msg || '操作失败');
  904. }
  905. })
  906. .catch((err) => {
  907. console.error('操作请求失败:', err);
  908. this.showInfo('操作请求失败');
  909. setTimeout(() => {
  910. const slideComponent = this.selectComponent("#mySlideConfirm");
  911. if (slideComponent) {
  912. slideComponent.reset();
  913. }
  914. }, 5000);
  915. });
  916. },
  917. ontelephone(e) {
  918. const phoneNumber = e.currentTarget.dataset.phone;
  919. if (!phoneNumber) {
  920. this.showInfo('电话号码无效');
  921. return;
  922. }
  923. wx.showModal({
  924. title: '确认拨打电话',
  925. content: `是否拨打${phoneNumber}?`,
  926. success: (res) => {
  927. if (res.confirm) {
  928. wx.makePhoneCall({
  929. phoneNumber: phoneNumber,
  930. fail: (err) => {
  931. console.error('拨打电话失败', err);
  932. this.showInfo('拨打电话失败');
  933. }
  934. });
  935. }
  936. }
  937. });
  938. },
  939. onmore() {
  940. this.setData({ moretype: !this.data.moretype });
  941. },
  942. oncostdetails() {
  943. this.setData({ costdetails: !this.data.costdetails });
  944. },
  945. // 拖动功能(司机端默认展开行程详情)
  946. handleTouchStart(e) {
  947. this.setData({ startY: e.changedTouches[0].clientY });
  948. },
  949. handleTouchMove(e) {
  950. const currentY = e.changedTouches[0].clientY;
  951. const deltaY = currentY - this.data.startY;
  952. const { min, max } = this.data.heightConfig;
  953. let newHeight = this.data.currentHeight - deltaY;
  954. newHeight = Math.max(min, Math.min(max, newHeight));
  955. let maskOpacity = this.data.maskOpacity;
  956. if (newHeight < max && newHeight > min) {
  957. maskOpacity = 0.3 + (newHeight - min) / (this.data.heightConfig.mid - min) * 0.2;
  958. } else {
  959. maskOpacity = 0;
  960. }
  961. this.setData({ currentHeight: newHeight, maskOpacity, startY: currentY });
  962. },
  963. handleTouchEnd() {
  964. const { currentHeight } = this.data;
  965. const { min, mid, max } = this.data.heightConfig;
  966. const distToMin = Math.abs(currentHeight - min);
  967. const distToMid = Math.abs(currentHeight - mid);
  968. const distToMax = Math.abs(currentHeight - max);
  969. let targetHeight, targetMode;
  970. if (distToMax <= 80) {
  971. targetHeight = max;
  972. targetMode = 'max';
  973. } else if (distToMin <= 50) {
  974. targetHeight = min;
  975. targetMode = 'min';
  976. } else if (distToMid <= 60) {
  977. targetHeight = mid;
  978. targetMode = 'mid';
  979. } else {
  980. const minDist = Math.min(distToMin, distToMid, distToMax);
  981. if (minDist == distToMin) {
  982. targetHeight = min;
  983. targetMode = 'min';
  984. } else if (minDist == distToMid) {
  985. targetHeight = mid;
  986. targetMode = 'mid';
  987. } else {
  988. targetHeight = max;
  989. targetMode = 'max';
  990. }
  991. }
  992. this.setData({
  993. currentHeight: targetHeight,
  994. cardHeight: targetMode,
  995. maskOpacity: targetMode == 'mid' ? 0.5 : 0
  996. });
  997. },
  998. closeCard() {
  999. this.setData({
  1000. currentHeight: this.data.heightConfig.min,
  1001. cardHeight: 'min',
  1002. maskOpacity: 0
  1003. });
  1004. },
  1005. closeFullscreen() {
  1006. this.setData({
  1007. currentHeight: this.data.heightConfig.min,
  1008. cardHeight: 'min',
  1009. maskOpacity: 0
  1010. });
  1011. },
  1012. // 编辑订单
  1013. editorder(){
  1014. wx.navigateTo({
  1015. url: `/package-order/pages/createorder/index?order=${this.data.optionsid}`,
  1016. })
  1017. },
  1018. cancel(){
  1019. let _this = this
  1020. // 在页面的 js 文件中
  1021. wx.showModal({
  1022. title: '提示',
  1023. content: '确定要取消订单吗?',
  1024. success (res) {
  1025. if (res.confirm) {
  1026. console.log('用户点击了确定')
  1027. // 在这里执行确认后的操作
  1028. let data ={
  1029. workorderId:_this.data.optionsid,
  1030. workorderDeletetype:1
  1031. }
  1032. api.request(`/sysworkorder/updateworkorder`, 'post', data, { isPublic: false })
  1033. .then((data) => {
  1034. if (data.code == 200) {
  1035. wx.switchTab({
  1036. url: '/pages/tool/index',
  1037. })
  1038. }
  1039. })
  1040. .catch((err) => {
  1041. this.showInfo('操作失败');
  1042. });
  1043. } else if (res.cancel) {
  1044. this.showInfo('取消操作');
  1045. }
  1046. }
  1047. })
  1048. }
  1049. });
  1050. //