电速宝
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. const api = require('../../../api/index.js');
  2. Page({
  3. data: {
  4. // 地图中心坐标
  5. longitude: 0,
  6. latitude: 0,
  7. // 标记点(包含起点、终点)
  8. markers: [],
  9. // 轨迹线(实际轨迹)- 核心修改:将颜色改为红色(#ff0000)
  10. polyline: [{
  11. points: [],
  12. color: "#999999", // 已走过的路线颜色:灰色
  13. width: 6,
  14. dottedLine: false
  15. }],
  16. // 规划路线 - 优化:改为灰色(#999999),与已走路线形成对比
  17. plannedRoute: [{
  18. points: [],
  19. color: "#2f693c", // 未走的规划路线颜色:绿色
  20. width: 4,
  21. dottedLine: false,
  22. arrowLine: true
  23. }],
  24. // 追踪状态
  25. isTracking: false,
  26. // 信息提示文本
  27. infoText: "",
  28. // 位置更新计时器
  29. locationTimer: null,
  30. // 终点位置
  31. destination: null,
  32. // 路线规划状态
  33. routePlanned: false,
  34. // 腾讯位置服务Key
  35. qqMapKey: 'VRGBZ-ZFRHB-SKIUP-NHHJ3-TXGJT-ZIFG3',
  36. // 偏离阈值(米)和偏离状态
  37. deviationThreshold: 50, // 超过50米视为偏离
  38. isDeviated: false,
  39. // 重新规划相关
  40. isReplanning: false, // 是否正在重新规划
  41. deviationCount: 0, // 连续偏离计数
  42. replanThreshold: 3, // 连续偏离多少次后重新规划
  43. orderdata:{},
  44. optionsid:'',
  45. showVerification: false,
  46. phoneNumber: "138****6789",
  47. userdata:[]
  48. },
  49. onLoad(options) {
  50. this.setData({
  51. userdata:wx.getStorageSync('user')
  52. })
  53. console.log(options);
  54. this.setData({
  55. optionsid:options.id
  56. })
  57. // 初始化地图,获取当前位置
  58. this.getworkorder()
  59. // 引入SDK核心类
  60. this.qqmapsdk = require('../../../libs/qqmap-wx-jssdk.min.js');
  61. // 初始化SDK
  62. this.mapSdk = new this.qqmapsdk({
  63. key: this.data.qqMapKey
  64. });
  65. },
  66. getworkorder(){
  67. let data = {
  68. workorderId:this.data.optionsid
  69. }
  70. api.request(`/sysworkorder/selectworkorderId`, 'post',data,{ isPublic: false })
  71. .then((data) => {
  72. console.log(data.data);
  73. if (data.code==200) {
  74. this.setData({
  75. orderdata:data.data
  76. })
  77. this.initMap();
  78. }
  79. })
  80. .catch((err) => {
  81. console.error('请求失败:', err);
  82. });
  83. },
  84. onUnload() {
  85. // 页面卸载时停止追踪
  86. this.stopTracking();
  87. },
  88. // 初始化地图
  89. initMap() {
  90. const that = this;
  91. // 获取用户当前位置
  92. wx.getLocation({
  93. type: 'gcj02', // 腾讯地图坐标体系
  94. success(res) {
  95. const longitude = res.longitude;
  96. const latitude = res.latitude;
  97. // 设置地图中心为当前位置
  98. that.setData({
  99. longitude,
  100. latitude,
  101. // 初始化起点标记
  102. markers: [{
  103. id: 0,
  104. longitude,
  105. latitude,
  106. iconPath: "https://esos-iot.bjdexn.cn/myminio/project/0b0593293af54ea097b168cea479c25c.png",
  107. width: 30,
  108. height: 30,
  109. title: "起点"
  110. }],
  111. // 初始化轨迹线起点(颜色已在data中设置为红色)
  112. polyline: [{
  113. points: [{longitude, latitude}],
  114. color: "#ff0000", // 与data中保持一致,确保初始颜色正确
  115. width: 6,
  116. dottedLine: false
  117. }]
  118. });
  119. that.showInfo("已获取当前位置");
  120. that.setDestination();
  121. },
  122. fail(err) {
  123. console.error("获取位置失败:", err);
  124. that.showInfo("请授权位置权限以使用地图功能");
  125. // 引导用户开启权限
  126. wx.openSetting({
  127. success(res) {
  128. if (res.authSetting['scope.userLocation']) {
  129. that.initMap();
  130. }
  131. }
  132. });
  133. }
  134. });
  135. },
  136. // 规划路线 - 通用方法,可被初始规划和重新规划调用
  137. planRoute(isReplan = false) {
  138. const that = this;
  139. const { longitude, latitude, destination } = this.data;
  140. if (!destination) {
  141. this.showInfo("请先设置终点");
  142. return;
  143. }
  144. // 如果是重新规划,更新状态
  145. if (isReplan) {
  146. this.setData({ isReplanning: true });
  147. this.showInfo("正在重新规划路线...");
  148. } else {
  149. this.showInfo("正在规划路线...");
  150. }
  151. // 调用腾讯地图路线规划API
  152. this.mapSdk.direction({
  153. mode: 'driving', // 驾车模式,可选值:'driving', 'walking', 'transit'
  154. policy:'REAL_TRAFFIC',
  155. from: {
  156. latitude: latitude,
  157. longitude: longitude
  158. },
  159. to: {
  160. latitude: destination.latitude,
  161. longitude: destination.longitude
  162. },
  163. success(res) {
  164. console.log("路线规划结果:", res);
  165. if (res.status === 0 && res.result.routes.length > 0) {
  166. var result = res.result
  167. var route = result.routes[0]
  168. // 提取路线点
  169. var coors = route.polyline, pl = [];
  170. //坐标解压(返回的点串坐标,通过前向差分进行压缩)
  171. var kr = 1000000;
  172. for (var i = 2; i < coors.length; i++) {
  173. coors[i] = Number(coors[i - 2]) + Number(coors[i]) / kr;
  174. }
  175. //将解压后的坐标放入点串数组pl中
  176. for (var i = 0; i < coors.length; i += 2) {
  177. pl.push({ latitude: coors[i], longitude: coors[i + 1] })
  178. }
  179. // 更新路线数据(规划路线颜色已设为灰色)
  180. that.setData({
  181. plannedRoute: [{
  182. name: that.data.orderdata.addressName,
  183. points: pl,
  184. color: '#2f693c', // 与data中保持一致,确保规划路线颜色正确
  185. width: 6,
  186. borderColor: '#2f693c',
  187. borderWidth: 1
  188. }],
  189. routePlanned: true,
  190. isReplanning: false,
  191. deviationCount: 0 // 重置偏离计数
  192. });
  193. // 显示规划结果
  194. const message = isReplan
  195. ? `已重新规划路线,距离${(result.routes[0].distance/1000).toFixed(1)}公里,约${Math.ceil(result.routes[0].duration)}分钟`
  196. : `路线规划完成,距离${(result.routes[0].distance/1000).toFixed(1)}公里,约${Math.ceil(result.routes[0].duration)}分钟`;
  197. that.showInfo(message);
  198. } else {
  199. that.setData({ isReplanning: false });
  200. that.showInfo("路线规划失败,请重试");
  201. }
  202. },
  203. fail(err) {
  204. console.error("路线规划失败:", err);
  205. that.setData({ isReplanning: false });
  206. that.showInfo("路线规划失败,请检查网络");
  207. }
  208. });
  209. },
  210. // 设置终点位置
  211. setDestination() {
  212. // 更新终点标记
  213. const newMarkers = [...this.data.markers.filter(marker => marker.id !== 1)];
  214. newMarkers.push({
  215. name: this.data.orderdata.addressName,
  216. id: 1,
  217. latitude: this.data.orderdata.latitude, // 终点纬度
  218. longitude: this.data.orderdata.longitude, // 终点经度
  219. iconPath: "https://esos-iot.bjdexn.cn/myminio/project/6dc37b15321b462f9ab59c47263dd224.png",
  220. width: 30,
  221. height: 30,
  222. title: "终点"
  223. });
  224. this.setData({
  225. markers: newMarkers,
  226. destination: { longitude: this.data.orderdata.longitude, latitude: this.data.orderdata.latitude }
  227. });
  228. this.showInfo("已设置终点位置");
  229. this.planRoute(); // 初始规划路线
  230. // this.startTracking(); // 实时监控位置
  231. },
  232. // 显示信息提示
  233. showInfo(text) {
  234. this.setData({ infoText: text });
  235. // 3秒后自动隐藏非状态类信息
  236. if (!text.includes("正在") && !text.includes("距离") && !text.includes("偏离")) {
  237. setTimeout(() => {
  238. this.setData({ infoText: "" });
  239. }, 3000);
  240. }
  241. },
  242. // 导航功能
  243. navigation(){
  244. // 使用在腾讯位置服务申请的key
  245. const key = this.data.qqMapKey;
  246. // 调用插件的app的名称
  247. const referer = '晟运智慧运维';
  248. // 是否启用智能规划
  249. const enableAI = true;
  250. // 是否开启导航功能
  251. const navigation = 1;
  252. // 终点(建议替换为orderdata中的实际终点,当前为示例值)
  253. const endPoint = JSON.stringify({
  254. name: this.data.orderdata.addressName || '目的地',
  255. latitude: this.data.orderdata.latitude,
  256. longitude: this.data.orderdata.longitude,
  257. });
  258. // 个性化图层
  259. const layerStyle = 1;
  260. wx.navigateTo({
  261. url: `plugin://route-plan/index?key=${key}&referer=${referer}&endPoint=${endPoint}&enableAI=${enableAI}&navigation=${navigation}&layerStyle=${layerStyle}`,
  262. });
  263. },
  264. // 开始追踪位置
  265. startTracking() {
  266. if (this.data.isTracking) return;
  267. const that = this;
  268. this.setData({ isTracking: true });
  269. // 定期获取位置并检查是否偏离路线
  270. this.setData({
  271. locationTimer: setInterval(() => {
  272. wx.getLocation({
  273. type: 'gcj02',
  274. success(res) {
  275. const newPoint = {
  276. longitude: res.longitude,
  277. latitude: res.latitude
  278. };
  279. // 更新当前位置和轨迹(颜色保持红色)
  280. const updatedPolyline = [...that.data.polyline];
  281. updatedPolyline[0].points.push(newPoint);
  282. // 更新起点标记位置
  283. const updatedMarkers = [...that.data.markers];
  284. updatedMarkers[0] = {
  285. ...updatedMarkers[0],
  286. longitude: res.longitude,
  287. latitude: res.latitude
  288. };
  289. that.setData({
  290. longitude: res.longitude,
  291. latitude: res.latitude,
  292. polyline: updatedPolyline,
  293. markers: updatedMarkers
  294. });
  295. // 检查是否偏离规划路线
  296. if (that.data.routePlanned && !that.data.isReplanning) {
  297. that.checkDeviation(newPoint);
  298. }
  299. },
  300. fail(err) {
  301. console.error("获取位置失败:", err);
  302. that.showInfo("位置获取失败,请检查权限");
  303. }
  304. });
  305. }, 3000) // 每3秒更新一次位置
  306. });
  307. this.showInfo("开始追踪位置");
  308. },
  309. // 停止追踪
  310. stopTracking() {
  311. if (this.data.locationTimer) {
  312. clearInterval(this.data.locationTimer);
  313. this.setData({
  314. locationTimer: null,
  315. isTracking: false
  316. });
  317. this.showInfo("已停止追踪");
  318. }
  319. },
  320. // 检查是否偏离路线
  321. checkDeviation(currentPoint) {
  322. const { plannedRoute, deviationThreshold, deviationCount, replanThreshold } = this.data;
  323. const routePoints = plannedRoute[0].points;
  324. if (!routePoints || routePoints.length < 2) return;
  325. // 计算当前位置到路线的最短距离
  326. let minDistance = Infinity;
  327. // 检查当前点到路线上每一段线段的距离
  328. for (let i = 0; i < routePoints.length - 1; i++) {
  329. const distance = this.calculateDistanceToLine(
  330. currentPoint,
  331. routePoints[i],
  332. routePoints[i + 1]
  333. );
  334. if (distance < minDistance) {
  335. minDistance = distance;
  336. }
  337. }
  338. // 判断是否偏离
  339. const isDeviated = minDistance > deviationThreshold;
  340. if (isDeviated) {
  341. // 增加连续偏离计数
  342. const newDeviationCount = deviationCount + 1;
  343. this.setData({
  344. isDeviated,
  345. deviationCount: newDeviationCount
  346. });
  347. // 显示偏离信息
  348. this.showInfo(`已偏离路线 ${minDistance.toFixed(1)} 米,连续偏离 ${newDeviationCount}/${replanThreshold} 次`);
  349. // 播放提示音
  350. wx.playBackgroundAudio({
  351. dataUrl: 'http://ws.stream.qqmusic.qq.com/M500001VfvsJ21xFqb.mp3?guid=ffffffff82def4af4b12b3cd9337d38&uin=346897220&vkey=6292F51C65348166B851686B2C059D76205E4241C44E5823713D649EF6471BA681DC27D5269A1E&fromtag=46',
  352. title: '偏离提醒',
  353. coverImgUrl: ''
  354. });
  355. // 达到重新规划阈值,且不在重新规划中
  356. if (newDeviationCount >= replanThreshold && !this.data.isReplanning) {
  357. this.planRoute(true); // 重新规划路线,传入true标识
  358. }
  359. } else {
  360. // 回到路线上,重置偏离计数
  361. this.setData({
  362. isDeviated,
  363. deviationCount: 0
  364. });
  365. if (this.data.isDeviated) {
  366. this.showInfo("已回到规划路线");
  367. }
  368. }
  369. },
  370. // 计算点到线段的距离(米)
  371. calculateDistanceToLine(point, lineStart, lineEnd) {
  372. // 经纬度转弧度
  373. const toRadians = (degree) => degree * Math.PI / 180;
  374. // 地球半径(米)
  375. const R = 6371000;
  376. // 转换为弧度
  377. const lat1 = toRadians(point.latitude);
  378. const lon1 = toRadians(point.longitude);
  379. const lat2 = toRadians(lineStart.latitude);
  380. const lon2 = toRadians(lineStart.longitude);
  381. const lat3 = toRadians(lineEnd.latitude);
  382. const lon3 = toRadians(lineEnd.longitude);
  383. // 计算线段长度
  384. const a = this.haversineDistance(lat1, lon1, lat2, lon2);
  385. const b = this.haversineDistance(lat1, lon1, lat3, lon3);
  386. const c = this.haversineDistance(lat2, lon2, lat3, lon3);
  387. // 如果点在线段延长线上,返回最近端点的距离
  388. if (b * b >= a * a + c * c) {
  389. return a;
  390. }
  391. if (a * a >= b * b + c * c) {
  392. return b;
  393. }
  394. // 计算点到线段的垂直距离
  395. const s = (a + b + c) / 2;
  396. const area = Math.sqrt(Math.abs(s * (s - a) * (s - b) * (s - c)));
  397. return (2 * area) / c;
  398. },
  399. // 使用haversine公式计算两点间距离(米)
  400. haversineDistance(lat1, lon1, lat2, lon2) {
  401. const R = 6371000; // 地球半径(米)
  402. const dLat = lat2 - lat1;
  403. const dLon = lon2 - lon1;
  404. const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  405. Math.cos(lat1) * Math.cos(lat2) *
  406. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  407. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  408. return R * c;
  409. },
  410. // 显示验证码弹窗
  411. showVerificationPopup() {
  412. this.setData({ showVerification: true });
  413. },
  414. // 关闭弹窗
  415. onPopupClose() {
  416. this.setData({ showVerification: false });
  417. setTimeout(() => {
  418. const slideComponent = this.selectComponent("#mySlideConfirm");
  419. // 2. 调用组件的reset方法
  420. slideComponent.reset();
  421. }, 1000);
  422. },
  423. // 确认验证码
  424. onCodeConfirm(e) {
  425. console.log("确认验证码:", e.detail.code);
  426. // 验证逻辑...
  427. this.setData({ showVerification: false });
  428. },
  429. // 重新发送验证码
  430. onResendCode() {
  431. console.log("重新发送验证码");
  432. // 重新发送逻辑...
  433. },
  434. // 验证码输入完成
  435. onCodeComplete(e) {
  436. console.log("验证码输入完成:", e.detail.code);
  437. // 自动验证逻辑...
  438. let data = {
  439. carId:this.data.optionsid,
  440. verifyCode: e.detail.code,
  441. workorderId:this.data.orderdata.workorderId
  442. }
  443. api.request(`/sysworkorder/submitworkorder`, 'post',data,{ isPublic: false })
  444. .then((data) => {
  445. console.log(data.data);
  446. if (data.code==200) {
  447. this.getworkorder();
  448. setTimeout(() => {
  449. const slideComponent = this.selectComponent("#mySlideConfirm");
  450. // 2. 调用组件的reset方法
  451. slideComponent.reset();
  452. }, 5000);
  453. }
  454. })
  455. .catch((err) => {
  456. setTimeout(() => {
  457. const slideComponent = this.selectComponent("#mySlideConfirm");
  458. // 2. 调用组件的reset方法
  459. slideComponent.reset();
  460. }, 5000);
  461. console.error('请求失败:', err);
  462. });
  463. },
  464. onSlideSuccess(e){
  465. console.log(e.currentTarget.dataset.type);
  466. // 3. 更新页面状态,隐藏按钮和提示
  467. let data = {
  468. carId:this.data.optionsid,
  469. verifyCode:'MNBJ4H',
  470. workorderId:this.data.orderdata.workorderId
  471. }
  472. api.request(`/sysworkorder/submitworkorder`, 'post',data,{ isPublic: false })
  473. .then((data) => {
  474. console.log(data.data);
  475. if (data.code==200) {
  476. if (e.currentTarget.dataset.type==2) {
  477. let objdata ={
  478. workorderId:this.data.orderdata.workorderId
  479. }
  480. api.request(`/sysworkorder/createverify`, 'post',objdata,{ isPublic: false })
  481. .then((data) => {
  482. console.log(data.data);
  483. if (data.code==200) {
  484. }
  485. })
  486. .catch((err) => {
  487. console.error('请求失败:', err);
  488. });
  489. }
  490. this.getworkorder();
  491. setTimeout(() => {
  492. const slideComponent = this.selectComponent("#mySlideConfirm");
  493. // 2. 调用组件的reset方法
  494. slideComponent.reset();
  495. }, 5000);
  496. }
  497. })
  498. .catch((err) => {
  499. setTimeout(() => {
  500. const slideComponent = this.selectComponent("#mySlideConfirm");
  501. // 2. 调用组件的reset方法
  502. slideComponent.reset();
  503. }, 5000);
  504. console.error('请求失败:', err);
  505. });
  506. },
  507. ontelephone(e){
  508. console.log(e.currentTarget.dataset.phone);
  509. const phoneNumber = e.currentTarget.dataset.phone;
  510. wx.showModal({
  511. title: '确认拨打电话',
  512. content: '是否拨打' + phoneNumber + '?',
  513. success: (res) => {
  514. if (res.confirm) {
  515. wx.makePhoneCall({
  516. phoneNumber: phoneNumber
  517. });
  518. }
  519. }
  520. });
  521. }
  522. });