健康同学微信公众号h5项目
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.

2204 lines
72KB

  1. <template>
  2. <div class="trajectory">
  3. <div class="trajectory-con">
  4. <div class="top">
  5. <van-nav-bar title="历史轨迹" left-arrow @click-left="onNavBack" left-text="返回" />
  6. <!-- 右边固定的时间选项 -->
  7. <div class="time-picker" :style="{ width: `${timePickWidth}%` }">
  8. <div class="time-picker-left" :style="{ width: `${timeWidth}%` }" @click="onTimeClick">
  9. <van-icon name="arrow-left" v-if="isLeft" />
  10. <van-icon name="arrow" v-if="!isLeft" />
  11. <span>时间</span>
  12. </div>
  13. <div class="time-picker-right" v-if="isPopup">
  14. <span @click="onStartClick" v-show="true">{{ initTimePick.initStartTime }}</span>
  15. <label class="labelTime">至</label>
  16. <span @click="onEndClick" v-show="true">{{ initTimePick.initEndTime }}</span>
  17. <van-datetime-picker
  18. v-model="timePickData.selectStartTime"
  19. v-show="timePickData.timePickStartShow"
  20. class="datetimePicker"
  21. type="time"
  22. title="选择开始时间"
  23. :min-hour="timePickData.defaultStartTime.slice(0, 2)"
  24. :max-hour="timePickData.defaultEndTime.slice(0, 2)"
  25. confirm-button-text="确定"
  26. @confirm="onStartTimeConfirm"
  27. @cancel="onStartTimeCancel"
  28. :item-height="itemHeight"
  29. />
  30. <van-datetime-picker
  31. v-model="timePickData.selecttEndTime"
  32. v-show="timePickData.timePickEndShow"
  33. class="datetimePicker"
  34. type="time"
  35. title="选择结束时间"
  36. :min-hour="timePickData.defaultStartTime.slice(0, 2)"
  37. :max-hour="timePickData.defaultEndTime.slice(0, 2)"
  38. @confirm="onEndTimeConfirm"
  39. confirm-button-text="确定"
  40. @cancel="onEndTimeCancel"
  41. :item-height="itemHeight"
  42. />
  43. </div>
  44. <div class="time-picker-right-confirm" @click="onConfirm" v-if="isPopup">
  45. <div class="right-confirm-text">
  46. <span>查看</span>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="dateArea">
  51. <i class="left active" @click="dateClick"></i>
  52. <span @click="dateAreaClick">{{ new Date(date).Format('yyyy年MM月dd日') }}</span>
  53. <i :class="['right', { active: count < 0 }]" @click="dateClick('add')"></i>
  54. </div>
  55. <div class="calendar" v-show="calendarShow">
  56. <van-calendar
  57. v-model="calendarShow"
  58. :min-date="minDate"
  59. :max-date="maxDate"
  60. :default-date="defaultDate"
  61. :round="false"
  62. :poppable="false"
  63. :show-confirm="false"
  64. :show-title="false"
  65. :show-subtitle="false"
  66. @select="onSelect"
  67. />
  68. </div>
  69. </div>
  70. <!-- 轨迹地图部分 -->
  71. <div id="trajectory_map" class="index_map" style="width: 100%; height: 100vh"></div>
  72. <!-- 底部部分 -->
  73. <div class="bottom">
  74. <div class="conArea" v-show="bottomContentShow">
  75. <!-- 右边的速度滑块 -->
  76. <div class="slider-bar">
  77. <!--<span>速度</span>-->
  78. <span>慢</span>
  79. <div class="bar">
  80. <van-slider :min="min" :max="max" :step="step" v-model="leftCode" vertical />
  81. </div>
  82. <!--<div>{{leftCode}}</div>-->
  83. <!--<div>km/h</div>-->
  84. <span>快</span>
  85. </div>
  86. <!-- 下面的地址 时间 -->
  87. <div class="con">
  88. <span
  89. class="title"
  90. v-if="
  91. decoratedAddressList === null || (decoratedAddressList !== null && decoratedAddressList.length === 0)
  92. "
  93. >暂无地址信息</span
  94. >
  95. <div class="content-list" v-if="decoratedAddressList !== null && decoratedAddressList.length > 0">
  96. <div class="content-item" v-for="(item, index) in decoratedAddressList" :key="index">
  97. <p class="title">{{ item.address ? item.address + '(附近)' : '暂无位置信息' }}</p>
  98. <div class="states">
  99. <span v-if="item.s_time != item.repeat_last_time">
  100. {{ item.s_time.split(' ')[1] }}
  101. ~
  102. {{ item.repeat_last_time.split(' ')[1] }}
  103. </span>
  104. <span v-if="item.s_time == item.repeat_last_time">
  105. {{ item.time.split(' ')[1] }}
  106. </span>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. <!-- 播放按钮 -->
  112. <div class="player">
  113. <van-icon name="play-circle-o" v-show="!playFlag" @click="onPlay" />
  114. <van-icon name="pause-circle-o" v-show="playFlag" @click="onPause" />
  115. </div>
  116. </div>
  117. <!--<overlay-loading :showOverlay="showOverlay"></overlay-loading>-->
  118. </div>
  119. </div>
  120. </div>
  121. </template>
  122. <script>
  123. import MapLoader from '@/common/amap.js';
  124. import DialogService from '../../services/dialog-service';
  125. import ToastService from '../../services/toast-service';
  126. import { ErrorAMapMsgModel } from '../../config/models';
  127. import APIDevice from '../../api/device';
  128. export default {
  129. data() {
  130. const ms_TO_kmh = 3.6;
  131. return {
  132. ms_TO_kmh: ms_TO_kmh,
  133. showOverlay: true,
  134. lineArr: [],
  135. lineArrCopy: [],
  136. allLineArr: [],
  137. // map: null,
  138. marker: null,
  139. playFlag: false, //播放状态
  140. count: 0, //日期count
  141. date: this.$own.getNowFormatDate(0),
  142. clickFlag: true, //左右日期按钮是否给予点击
  143. bottomContentShow: true,
  144. address: '',
  145. time: '',
  146. calendarShow: false,
  147. minDate: /* new Date(2020, 0, 1) */ new Date(
  148. this.$dayjs()
  149. .month(this.$dayjs().month() - 1)
  150. .hour(0)
  151. .minute(0)
  152. .second(0)
  153. .format()
  154. ),
  155. maxDate: /* new Date() */ new Date(this.$dayjs().hour(0).minute(0).second(0).format()),
  156. defaultDate: /* new Date() */ new Date(this.$dayjs().hour(0).minute(0).second(0).format()),
  157. num: 0,
  158. playState: 'off',
  159. max: 8000,
  160. min: 100,
  161. leftCode: 100,
  162. step: 100,
  163. map: {
  164. instance: null,
  165. position: null,
  166. zoom: 15,
  167. marker: null,
  168. markerNew: null,
  169. polyline: null,
  170. polylineNew: null,
  171. passedPolyline: null,
  172. passedPolylineNew: null,
  173. graspRoad: null,
  174. walking: null,
  175. riding: null,
  176. transfer: null,
  177. city: null
  178. },
  179. speedTypes: {
  180. // 步行1m/S;骑车3m/S;汽车11m/S~16m/S;高铁16m/S~80m/S
  181. walking: 1.7 * ms_TO_kmh,
  182. riding: [1.7 * ms_TO_kmh, 6.6 * ms_TO_kmh],
  183. driving: [6.6 * ms_TO_kmh, 16.7 * ms_TO_kmh],
  184. // highRailway: [ 16.8 * ms_TO_kmh, 80 * ms_TO_kmh ],
  185. highRailway: [16.8 * ms_TO_kmh, 55.5 * ms_TO_kmh]
  186. },
  187. serviceTypes: {
  188. walking: 'walking',
  189. riding: 'riding',
  190. driving: 'driving',
  191. transfer: 'transfer',
  192. overTime: 'overTime',
  193. overSpeed: 'overSpeed',
  194. sameAddress: 'sameAddress'
  195. },
  196. dataList: [],
  197. path: [],
  198. polylineList: [],
  199. lngLatOnMoving: [],
  200. movingIndexOfPolylineList: 0,
  201. circleList: [],
  202. limitDistance: 50, // 聚集点距离
  203. limitSecond: 60 * 6, // 相邻点的时间差(作为直连线/上报缺点的依据)
  204. limitSpeed: 120, // km/h 超速(直连线)
  205. thresholdTime: 60 * 15, // 15 分钟;判断两个 driving 时间差是否符合该阀值
  206. decoratedAddressList: null,
  207. collectionStraightPoints: [], // 直连线的点的记录
  208. isPopup: false, //右上快捷时间选择器是否显示
  209. timePickWidth: 20, //时间选择器初始的百分比宽度
  210. timeWidth: 100, //时间选择器最大的百分比宽度
  211. isLeft: true, //时间选择器是否在左边.默认在左边
  212. itemHeight: Number(((document.body.clientWidth / 750) * 75).toFixed()), //时间选择器item的高度
  213. timePickData: {
  214. defaultStartTime: '00:00',
  215. defaultEndTime: '23:59',
  216. timePickStartShow: false,
  217. timePickEndShow: false,
  218. selectStartTime: '00:00',
  219. selecttEndTime: '23:59'
  220. }, //时间选择器
  221. initTimePick: {
  222. initStartTime: '00:00',
  223. initEndTime: '23:59'
  224. }, //初始化接口第一次获取的时间
  225. dialogShow: false, //轨迹对比弹窗是否显示
  226. dialogInput: '', //弹窗输入的json数据
  227. dataListNew: [],
  228. pathNew: [],
  229. polylineListNew: [],
  230. lngLatOnMovingNew: [],
  231. collectionStraightPointsNew: [],
  232. circleListNew: [],
  233. isProduction: process.env.NODE_ENV === 'production'
  234. };
  235. },
  236. mounted() {
  237. // this.getAmap();
  238. this.getMap();
  239. this.toggleBottom(true);
  240. },
  241. methods: {
  242. onNavBack() {
  243. this.$router.push({
  244. name: 'location'
  245. });
  246. },
  247. onPlay() {
  248. if (this.$own.isNull(this.polylineList)) {
  249. ToastService.fail({ message: '暂无信息' });
  250. return;
  251. }
  252. this.map.marker.moveAlong(this.polylineList, this.leftCode);
  253. console.log('dataList:::', this.dataList);
  254. },
  255. onPause() {
  256. this.map.marker.pauseMove();
  257. },
  258. // 去重、补上【开始时间、结束时间、重复项】
  259. deduplicatedANDDecorateList(dataList) {
  260. let result = [],
  261. repeatNum = 1;
  262. for (let i = 0; i < dataList.length; i++) {
  263. let preItem = dataList[i - 1 >= 0 ? i - 1 : 0];
  264. const currentItem = dataList[i];
  265. if (typeof currentItem === 'object' && Array.isArray(currentItem)) {
  266. // 兼容纯经纬度数组
  267. if (i === 0) {
  268. result.push(preItem);
  269. continue;
  270. }
  271. if (
  272. currentItem[0] === 0 ||
  273. currentItem[1] === 0 ||
  274. (currentItem[0] === preItem[0] && currentItem[1] === preItem[1])
  275. ) {
  276. result[result.length - 1] = currentItem;
  277. }
  278. } else {
  279. // 对象数组
  280. if (i === 0) {
  281. result.push({
  282. ...preItem,
  283. s_time: preItem.time.replace(new RegExp(/(-)/g), '/'),
  284. e_time: preItem.time.replace(new RegExp(/(-)/g), '/'),
  285. repeat_last_time: preItem.time.replace(new RegExp(/(-)/g), '/'),
  286. repeatNum: repeatNum
  287. });
  288. continue;
  289. }
  290. let last = result[result.length - 1];
  291. if (
  292. currentItem.lng === 0 ||
  293. currentItem.lat === 0 ||
  294. (currentItem.lng === preItem.lng && currentItem.lat === preItem.lat)
  295. ) {
  296. last = {
  297. ...last,
  298. // s_time: last.s_time,
  299. // e_time: last.time,
  300. repeat_last_time: currentItem.time.replace(new RegExp(/(-)/g), '/'), //正则 接口返回的时间数据(yyyy-MM-dd)ios系统不能识别,所以我们将它-变成/ (yyyy/MM/dd)
  301. repeatNum: ++repeatNum,
  302. distance: last.distance
  303. };
  304. result[result.length - 1] = last;
  305. } else {
  306. repeatNum = 1;
  307. const d = this.calculateDistanceByPoints([last.lng, last.lat], [currentItem.lng, currentItem.lat]);
  308. result.push({
  309. ...currentItem,
  310. s_time: currentItem.time.replace(new RegExp(/(-)/g), '/'),
  311. e_time: currentItem.time.replace(new RegExp(/(-)/g), '/'),
  312. repeat_last_time: currentItem.time.replace(new RegExp(/(-)/g), '/'),
  313. repeatNum: repeatNum,
  314. distance: d
  315. });
  316. }
  317. }
  318. }
  319. return result;
  320. },
  321. // 米
  322. calculateDistanceByPoints(s_lngLat, e_lngLat) {
  323. // eslint-disable-next-line
  324. return parseInt(AMap.GeometryUtil.distance(s_lngLat, e_lngLat));
  325. },
  326. // 筛选出聚集点
  327. decorateListCircle(dataList) {
  328. try {
  329. if (this.$own.isNull(dataList)) return [];
  330. if (dataList.length === 1) return [{ ...dataList[0], circle: false, clusterSize: 1 }];
  331. let result = [];
  332. let stack = [];
  333. for (let i = 0; i < dataList.length; i++) {
  334. const current = dataList[i];
  335. if (current.distance < this.limitDistance) {
  336. // 成为聚集点
  337. stack.push(current);
  338. } else {
  339. // 将之前的聚集点出栈
  340. let mid = stack[Math.floor(stack.length / 2)];
  341. result.push({
  342. ...mid,
  343. circle: stack.length > 1, //
  344. s_time: stack[0].s_time,
  345. e_time: stack[stack.length - 1].e_time,
  346. repeat_last_time: stack[stack.length - 1].repeat_last_time,
  347. distance: stack[stack.length - 1].distance,
  348. clusterSize: stack.length,
  349. skipService: false
  350. });
  351. // update栈
  352. stack = [{ ...current, circle: false }];
  353. }
  354. }
  355. if (stack.length > 0) {
  356. // 最后一项出栈
  357. if (stack.length > 1) {
  358. // 聚集点
  359. let mid = stack[Math.floor(stack.length / 2)];
  360. result.push({
  361. ...mid,
  362. circle: true,
  363. s_time: stack[0].s_time,
  364. e_time: stack[stack.length - 1].e_time,
  365. repeat_last_time: stack[stack.length - 1].repeat_last_time,
  366. distance: stack[stack.length - 1].distance,
  367. clusterSize: stack.length,
  368. skipService: false
  369. });
  370. } else {
  371. // length === 0
  372. result.push({ ...stack[0], circle: false, clusterSize: stack.length, skipService: false });
  373. }
  374. }
  375. return result;
  376. } catch (e) {
  377. console.log('decorateListCircle():::', e);
  378. }
  379. },
  380. decorateListAddress(dataList) {
  381. if (this.$own.isNull(dataList)) {
  382. return [];
  383. }
  384. if (dataList.length === 1) {
  385. return dataList;
  386. }
  387. let result = [];
  388. let temp = dataList[0] || null;
  389. for (let i = 1; i < dataList.length; i++) {
  390. const current = dataList[i];
  391. if (this.$own.isNotNull(temp) && temp.address === current.address) {
  392. temp = {
  393. ...current,
  394. s_time: temp.s_time
  395. };
  396. if (i === dataList.length - 1) {
  397. result.push(temp);
  398. }
  399. } else {
  400. result.push(temp);
  401. temp = current;
  402. if (i === dataList.length - 1) {
  403. result.push(current);
  404. }
  405. }
  406. }
  407. return result;
  408. },
  409. decorateListSpeed(dataList) {
  410. if (dataList.length === 1) return [{ ...dataList[0], sp: 0 }];
  411. let result = [],
  412. temp = null;
  413. result = dataList.map((item, i) => {
  414. if (i === 0) return { ...item, sp: 0 };
  415. temp = dataList[i - 1];
  416. const sp = (item.distance / this.calculateTime(temp.e_time, item.s_time)) * this.ms_TO_kmh; // km/h
  417. return {
  418. ...item,
  419. sp
  420. };
  421. });
  422. return result;
  423. },
  424. calculateTime(start, end) {
  425. // 计算两个时间之差(second)
  426. let s = new Date(start).getTime();
  427. let e = new Date(end).getTime();
  428. let dif = Math.abs(e - s) / 1000;
  429. return dif;
  430. },
  431. /**
  432. * 该函数目的在将路线数组标记上出行方式 walking riding driving transfer
  433. * 操作即从点到线段的层面
  434. */
  435. serviceTypeFilter(data) {
  436. const result = [];
  437. // 1、循环经纬度速度数组
  438. for (let i = 0; i < data.length; i++) {
  439. if (i === 0) {
  440. result.push(data[i]);
  441. continue;
  442. }
  443. const preItem = data[i - 1];
  444. const item = data[i];
  445. const timeDif = this.calculateTime(preItem.e_time, item.s_time);
  446. const address = preItem.address;
  447. if (timeDif >= this.limitSecond) {
  448. // 当相邻项的时间相隔超过规定时,直连线
  449. result.push({ ...item, serviceType: this.serviceTypes.overTime });
  450. continue;
  451. } else if (this.checkAddress(address, item.address)) {
  452. // 当相邻项的中文地址相同、包含时,直连线
  453. result.push({ ...item, serviceType: this.serviceTypes.sameAddress });
  454. continue;
  455. } else {
  456. // 根据速度分类出行方式 type
  457. result.push({ ...item, serviceType: this.getServiceType(item) });
  458. }
  459. } // for() {} done
  460. return result;
  461. },
  462. /**
  463. * 根据出行方式,而在规定时间区间内,若驾车方式中间夹着其他方式,则将其他方式修正为驾车(方便使用驾车的轨迹纠偏服务)
  464. * eg. p0-driving p1-walking p2-walking p3-walking p4-driving
  465. * p0 - p4 时间间隔不超过规定时间 如 15 分钟,则将 p1~p3 的方式改为 driving
  466. */
  467. changeToDriving(data) {
  468. let result = [];
  469. let nextDrivingIdx = -1;
  470. for (let i = 0; i < data.length; i++) {
  471. if (i === 0) {
  472. result.push(data[i]);
  473. continue;
  474. }
  475. if (data[i].serviceType !== this.serviceTypes.driving && nextDrivingIdx === -1) {
  476. result.push(data[i]);
  477. } else if (nextDrivingIdx - i > 0) {
  478. // 此时出现两个 driving 中间夹着其他方式,将一律改为 driving
  479. result.push({ ...data[i], serviceType: this.serviceTypes.driving });
  480. } else if (i < data.length - 2) {
  481. result.push(data[i]);
  482. nextDrivingIdx = this.findNextDriving(data, i + 1);
  483. if (!this.checkTimeDiff(data[i].time, data[nextDrivingIdx].time, this.thresholdTime)) {
  484. nextDrivingIdx = -1;
  485. }
  486. } else {
  487. result.push(data[i]);
  488. }
  489. }
  490. return result;
  491. },
  492. findNextDriving(data, start) {
  493. let end = 0;
  494. for (let i = start; i < data.length; i++) {
  495. if (data[i].serviceType !== this.serviceTypes.driving) {
  496. continue;
  497. } else {
  498. end = i;
  499. break;
  500. }
  501. }
  502. return end;
  503. },
  504. // 单位秒;判断两个时间之差是否在 threshold(阈值) 之内
  505. checkTimeDiff(start, end, threshold) {
  506. const startTm = new Date(start).getTime();
  507. const endTm = new Date(end).getTime();
  508. return (endTm - startTm) / 1000 <= threshold;
  509. },
  510. // 通过速度判断类型
  511. getServiceType(data) {
  512. if (data.address.indexOf('高速') >= 0)
  513. // 包含'高速'使用driving
  514. return 'driving';
  515. const sp = data.sp;
  516. return sp - this.limitSpeed >= 0
  517. ? 'overSpeed'
  518. : sp - this.speedTypes.highRailway[0] > 0
  519. ? 'transfer'
  520. : sp - this.speedTypes.driving[0] >= 0
  521. ? 'driving'
  522. : sp - this.speedTypes.riding[0] > 0
  523. ? 'riding'
  524. : 'walking';
  525. },
  526. calculateByContext(dataList) {
  527. let result = [];
  528. if (this.$own.isNull(dataList)) {
  529. return [];
  530. }
  531. if (dataList.length === 1) {
  532. return dataList;
  533. }
  534. let preWeight = 0;
  535. const maxWeight = 1;
  536. for (let i = 0; i < dataList.length; i++) {
  537. let weight = 0;
  538. let startP = dataList[i];
  539. let endP = i === dataList.length - 1 ? null : dataList[i + 1];
  540. if (endP === null) {
  541. result.push({ ...dataList[i], weight: preWeight || weight });
  542. continue;
  543. }
  544. const distance = this.calculateDistanceByPoints([startP.lng, startP.lat], [endP.lng, endP.lat]);
  545. const time = this.calculateTime(startP.e_time, endP.s_time);
  546. const speed = (distance / time) * this.ms_TO_kmh; // km/h
  547. if (speed <= this.speedTypes.highRailway[1]) {
  548. weight++;
  549. }
  550. result.push({ ...dataList[i], weight: weight || preWeight });
  551. preWeight = weight;
  552. }
  553. return result.filter(o => o.weight === maxWeight) || [];
  554. },
  555. // 通过步行服务获取经纬度
  556. getPathByWalkingService(data) {
  557. const routes = data.routes;
  558. let pathes = [];
  559. /*routes.forEach(route => {
  560. route.steps.forEach(step => {
  561. step.path.forEach(path => {
  562. pathes.push([ path.lng, path.lat ]);
  563. });
  564. });
  565. });*/
  566. // 默认拿第一个路线
  567. routes[0].steps.forEach(step => {
  568. step.path.forEach(path => {
  569. pathes.push([path.lng, path.lat]);
  570. });
  571. });
  572. return pathes;
  573. },
  574. // 通过公交服务获取经纬度
  575. getPathByTransferService(data) {
  576. let path = [];
  577. /*data.plans.forEach(plan => {
  578. path = path.concat(plan.path.map(item => {
  579. return [ item.lng, item.lat ];
  580. }));
  581. });*/
  582. // 默认拿第一个路线
  583. path = path.concat(
  584. data.plans[0].path.map(item => {
  585. return [item.lng, item.lat];
  586. })
  587. );
  588. return path;
  589. },
  590. // 通过驾车服务获取经纬度
  591. getPathByDrivingService(data) {
  592. const routes = data.routes;
  593. let pathes = [];
  594. /*routes.forEach(route => {
  595. route.steps.forEach(step => {
  596. step.path.forEach(path => {
  597. pathes.push([ path.lng, path.lat ]);
  598. });
  599. });
  600. });*/
  601. // 默认拿第一个路线
  602. routes[0].steps.forEach(step => {
  603. step.path.forEach(path => {
  604. pathes.push([path.lng, path.lat]);
  605. });
  606. });
  607. return pathes;
  608. },
  609. walkingService(location, walkingInstance, callback) {
  610. return new Promise(res => {
  611. walkingInstance.search(location[0], location[1], function (status, result) {
  612. if (status === 'complete') {
  613. // 使用服务返回的经纬度集合
  614. res(callback(result, true));
  615. } else {
  616. // 使用直连线
  617. res(callback(result, false));
  618. console.log('步行路线数据查询失败' + result);
  619. }
  620. });
  621. });
  622. },
  623. transferService(location, transferInstance, callback) {
  624. // 公交地铁
  625. return new Promise(res => {
  626. transferInstance.search(location[0], location[1], function (status, result) {
  627. if (status === 'complete') {
  628. // 使用服务返回的经纬度集合
  629. if (result.info && result.info.indexOf(ErrorAMapMsgModel.NO_DATA) >= 0) res(callback(result, false));
  630. else res(callback(result, true));
  631. } else {
  632. // 使用直连线
  633. res(callback(result, false));
  634. console.log(`公交路线数据查询失败:::${location}`);
  635. console.log(result);
  636. }
  637. });
  638. });
  639. },
  640. drivingService(location, drivingInstance, callback) {
  641. // 驾车
  642. return new Promise(res => {
  643. drivingInstance.search(location[0], location[1], function (status, result) {
  644. if (status === 'complete') {
  645. // 使用服务返回的经纬度集合
  646. res(callback(result, true));
  647. } else {
  648. // 使用直连线
  649. res(callback(result, false));
  650. console.log(`驾车路线数据查询失败:::${location}`);
  651. console.log(result);
  652. }
  653. });
  654. });
  655. },
  656. getData_API() {
  657. ToastService.loading({ message: '数据加载中...', forbidClick: false, getContainer: '#trajectory_map' });
  658. this.dataList = [];
  659. this.path = [];
  660. APIDevice.historyDetailListByIMEI({
  661. deviceid: this.$store.getters.serialNo,
  662. mapType: 'gaode',
  663. start: new Date(this.date).Format('yyyy-MM-dd'),
  664. end: new Date(this.date).Format('yyyy-MM-dd')
  665. })
  666. .then(result => {
  667. if (result.data.stateCode !== 1) {
  668. ToastService.clear();
  669. DialogService.confirm({ title: result.data.message });
  670. } else {
  671. // 2022.7. 8 修复 ios端 时间格式 - 不识别的问题,备注: 用正则将接口中时间格式替换成/
  672. let newData = result.data.data.item[0].map(item => {
  673. return {
  674. ...item,
  675. time: item.time.replace(new RegExp(/(-)/g), '/')
  676. };
  677. });
  678. const filterData = this.deduplicatedANDDecorateList(newData);
  679. console.log('filterData', filterData);
  680. const circleData = this.decorateListCircle(filterData);
  681. console.log('circleData', circleData);
  682. const speedData = this.decorateListSpeed(circleData);
  683. console.log('speedData', speedData);
  684. const filterSpeedData = this.calculateByContext(speedData);
  685. console.log('filterSpeedData:::', filterSpeedData);
  686. this.decoratedAddressList = this.decorateListAddress(filterSpeedData);
  687. this.resetMarker();
  688. this.clearMap();
  689. this.setAddressANDTime(filterData[0]);
  690. // todo 循环经算出各路径的出行方式,将连续 driving 的路径使用轨迹纠偏
  691. const serviceTypedData = this.serviceTypeFilter(filterSpeedData);
  692. const drivingFilterData = this.changeToDriving(serviceTypedData);
  693. console.log('drivingFilterData:::', drivingFilterData);
  694. this.createServiceV2(drivingFilterData);
  695. }
  696. })
  697. .catch(e => {
  698. console.log(e);
  699. })
  700. .finally(() => () => ToastService.clear());
  701. },
  702. async getMap() {
  703. ToastService.loading({ message: '地图加载中...', forbidClick: false, getContainer: '#trajectory_map' });
  704. let oldData,
  705. oldStateCode = 0;
  706. // 获取头像
  707. try {
  708. oldData = await this.getData();
  709. oldStateCode = oldData.data.stateCode;
  710. this.imagePath = oldData.data.imagePath;
  711. } catch (e) {
  712. ToastService.clear();
  713. }
  714. /*const oldData = await this.getData();
  715. let oldStateCode = 1;
  716. if (oldData.data.stateCode === 0) {
  717. oldStateCode = 0;
  718. }
  719. this.imagePath = oldData.data.imagePath;*/
  720. const plugins = [
  721. 'AMap.Geocoder',
  722. 'AMap.GraspRoad',
  723. 'AMap.GeometryUtil',
  724. 'AMap.CitySearch',
  725. 'AMap.Walking',
  726. 'AMap.Riding',
  727. 'AMap.Transfer',
  728. 'AMap.Driving',
  729. 'AMap.ToolBar'
  730. ];
  731. MapLoader(false, plugins).then(
  732. AMap => {
  733. // 地图实例
  734. this.map.instance = new AMap.Map('trajectory_map', {
  735. center: null,
  736. resizeEnable: true,
  737. zoom: this.map.zoom
  738. });
  739. if (process.env.NODE_ENV !== 'production') {
  740. const toolBar = new AMap.ToolBar({ offset: new AMap.Pixel(10, 400), position: 'LT' });
  741. this.map.instance.addControl(toolBar);
  742. }
  743. ToastService.clear();
  744. if (oldStateCode) {
  745. this.getData_API();
  746. }
  747. },
  748. e => {
  749. console.log('加载地图失败,下面是报错数据');
  750. console.log(e);
  751. }
  752. );
  753. },
  754. /* todo 轨迹纠偏 start */
  755. graspRoadService(locationList, drivingInstance, callback) {
  756. // 轨迹纠偏
  757. return new Promise(res => {
  758. drivingInstance.driving(locationList, function (error, result) {
  759. if (!error) {
  760. let path2 = [];
  761. let newPath = result.data.points;
  762. for (let i = 0; i < newPath.length; i++) {
  763. const location = [parseFloat(newPath[i].x.toFixed(6)), parseFloat(newPath[i].y.toFixed(6))];
  764. path2.push(location);
  765. }
  766. res(callback(newPath, true));
  767. } else {
  768. res(callback(result, false));
  769. console.log(`轨迹纠偏查询失败:::`, locationList);
  770. console.log(result);
  771. }
  772. });
  773. });
  774. },
  775. /* todo 轨迹纠偏 end */
  776. async createServiceV2(data) {
  777. ToastService.loading({ message: '正在加载...' });
  778. if (data.length === 0) {
  779. ToastService.clear();
  780. return;
  781. }
  782. let drivingCounts = 0; // 驾车路线的数量
  783. let wrongItemCounts = 0; // 调用了路线规划后验证此点不符合速度要求的点的数量
  784. for (let i = 0; i < data.length; i++) {
  785. if (i === 0) {
  786. this.path.push([data[i].lng, data[i].lat]);
  787. continue;
  788. }
  789. let preItem = data[i - 1 - wrongItemCounts];
  790. const item = data[i];
  791. let location = [
  792. [preItem.lng, preItem.lat],
  793. [item.lng, item.lat]
  794. ];
  795. let address = preItem.address;
  796. let timeDif = this.calculateTime(preItem.e_time, item.s_time);
  797. if (item.serviceType === this.serviceTypes.driving && i !== data.length - 1) {
  798. // 属于驾车时,累计驾车服务数量
  799. drivingCounts++;
  800. continue;
  801. } else {
  802. // 当终点服务类型为 非驾车 && drivingCounts > 0 时,调取轨迹纠偏
  803. // todo 轨迹纠偏
  804. if (drivingCounts > 0) {
  805. try {
  806. const start = i - drivingCounts - 1 - wrongItemCounts,
  807. end = i - 1;
  808. const locationList = this.getGraspServiceParamsReady(data, start, end);
  809. console.log(`共${drivingCounts + 1}项使用了轨迹纠偏:::start_${start}, end_${end}`);
  810. console.log(`start:::`, data[start]);
  811. console.log(`end:::`, data[end]);
  812. console.log(`轨迹纠偏参数:::`, locationList);
  813. this.serviceLog(preItem.serviceType, data[end]); // 打印 log
  814. // todo 轨迹纠偏的最后一个参数的 sp 需要归零
  815. if (locationList.length) {
  816. locationList[locationList.length - 1].sp = 0;
  817. }
  818. await this.graspRoadService(
  819. locationList,
  820. // eslint-disable-next-line
  821. new AMap.GraspRoad(),
  822. (result, useService) => {
  823. if (useService) {
  824. this.path = this.path.concat(
  825. result.map(item => {
  826. return [parseFloat(item.x.toFixed(6)), parseFloat(item.y.toFixed(6))];
  827. })
  828. );
  829. wrongItemCounts = 0;
  830. } else {
  831. this.addPathByStraight([item.lng, item.lat]);
  832. wrongItemCounts = 0;
  833. }
  834. }
  835. );
  836. } catch (e) {
  837. console.log('纠偏报错:::', e);
  838. }
  839. // 由于driving服务改为了轨迹纠偏,是累计方式,那么只有循环到非driving方式才调取轨迹纠偏
  840. // 所以其他服务需要用到的变量需要重新赋值
  841. preItem = data[i - 1];
  842. location = [
  843. [preItem.lng, preItem.lat],
  844. [item.lng, item.lat]
  845. ];
  846. address = preItem.address;
  847. timeDif = this.calculateTime(preItem.e_time, item.s_time);
  848. // 归零
  849. drivingCounts = 0;
  850. }
  851. this.serviceLog(item.serviceType, item); // 打印 log
  852. // todo 路径规划
  853. // 调取其他出行方式的路径规划服务
  854. const isSameAddress = this.checkAddress(address, item.address);
  855. if (
  856. item.serviceType === this.serviceTypes.overTime ||
  857. item.serviceType === this.serviceTypes.overSpeed ||
  858. isSameAddress ||
  859. (item.serviceType === this.serviceTypes.sameAddress && wrongItemCounts) ||
  860. item.serviceType === this.serviceTypes.transfer /* todo 高德暂无高铁服务,改为直连线 */
  861. ) {
  862. // 直连线:超时、超速、地址相同/包含
  863. if (i === 1) {
  864. this.path = this.path.concat(location);
  865. this.collectionStraightPoints = this.collectionStraightPoints.concat(location);
  866. continue;
  867. }
  868. let point = [item.lng, item.lat];
  869. this.path.push(point);
  870. this.collectionStraightPoints.push(point);
  871. wrongItemCounts = 0; // 归零
  872. drivingCounts = 0; // 归零
  873. continue;
  874. } else if (item.serviceType === this.serviceTypes.walking || item.serviceType === this.serviceTypes.riding) {
  875. // 步行、骑行
  876. await this.walkingService(
  877. location,
  878. // eslint-disable-next-line
  879. new AMap.Walking({ map: this.map.instance, autoFitView: i === data.length - 1, hideMarkers: true }),
  880. (result, useService) => {
  881. if (useService) {
  882. console.log(`服务返回路程:${result.routes[0].distance} 米`);
  883. if (this.checkSpeedByServiceType(result.routes[0].distance / timeDif, item.serviceType)) {
  884. this.path = this.path.concat(this.getPathByWalkingService(result));
  885. wrongItemCounts = 0;
  886. } else if (item.circle) {
  887. // 当前点为聚集点
  888. this.addPathByStraight([item.lng, item.lat]);
  889. wrongItemCounts = 0;
  890. console.log('上面的服务不符合,但为聚集点,直连');
  891. } else {
  892. wrongItemCounts++; // 保留起点
  893. item.skipService = true;
  894. console.log('上面的服务不符合,跳过');
  895. }
  896. } else {
  897. this.addPathByStraight([item.lng, item.lat]);
  898. wrongItemCounts = 0;
  899. console.log(`步行:::`, result);
  900. }
  901. }
  902. );
  903. // eslint-disable-next-line no-dupe-else-if
  904. } else if (item.serviceType === this.serviceTypes.transfer) {
  905. // 公交、地铁
  906. await this.transferService(
  907. location,
  908. // eslint-disable-next-line
  909. new AMap.Transfer({
  910. map: this.map.instance,
  911. autoFitView: i === data.length - 1,
  912. hideMarkers: true,
  913. city: preItem.cityCode,
  914. cityd: item.cityCode
  915. }),
  916. (result, useService) => {
  917. if (useService) {
  918. console.log(`服务返回路程:${result.plans[0].distance} 米`);
  919. if (this.checkSpeedByServiceType(result.plans[0].distance / timeDif, item.serviceType)) {
  920. this.path = this.path.concat(this.getPathByTransferService(result));
  921. wrongItemCounts = 0;
  922. } else if (item.circle) {
  923. // 当前点为聚集点
  924. this.addPathByStraight([item.lng, item.lat]);
  925. wrongItemCounts = 0;
  926. console.log('上面的服务不符合,但为聚集点,直连');
  927. } else {
  928. wrongItemCounts++; // 保留起点
  929. item.skipService = true;
  930. console.log('上面的服务不符合,跳过');
  931. }
  932. } else {
  933. this.addPathByStraight([item.lng, item.lat]);
  934. wrongItemCounts = 0;
  935. console.log(`公交:::`, result);
  936. }
  937. }
  938. );
  939. }
  940. }
  941. }
  942. // for() {} done
  943. console.log('path::::', this.path);
  944. ToastService.clear();
  945. this.clearMap();
  946. this.polylineList = this.path.map(item => item);
  947. this.createMarker(this.path);
  948. this.createPolyline(this.path);
  949. this.createCircles(data);
  950. },
  951. async createServiceV2New(data) {
  952. // 如果 已经绘制了新的轨迹 则把坐标数组清空 防止第二次以后绘制出现直连线的问题
  953. if (this.pathNew) {
  954. this.pathNew = [];
  955. }
  956. ToastService.loading({ message: '正在加载...' });
  957. if (data.length === 0) {
  958. ToastService.clear();
  959. return;
  960. }
  961. let drivingCounts = 0; // 驾车路线的数量
  962. let wrongItemCounts = 0; // 调用了路线规划后验证此点不符合速度要求的点的数量
  963. for (let i = 0; i < data.length; i++) {
  964. if (i === 0) {
  965. this.pathNew.push([data[i].lng, data[i].lat]);
  966. continue;
  967. }
  968. let preItem = data[i - 1 - wrongItemCounts];
  969. const item = data[i];
  970. let location = [
  971. [preItem.lng, preItem.lat],
  972. [item.lng, item.lat]
  973. ];
  974. let address = preItem.address;
  975. let timeDif = this.calculateTime(preItem.e_time, item.s_time);
  976. if (item.serviceType === this.serviceTypes.driving && i !== data.length - 1) {
  977. // 属于驾车时,累计驾车服务数量
  978. drivingCounts++;
  979. continue;
  980. } else {
  981. // 当终点服务类型为 非驾车 && drivingCounts > 0 时,调取轨迹纠偏
  982. // todo 轨迹纠偏
  983. if (drivingCounts > 0) {
  984. try {
  985. const start = i - drivingCounts - 1 - wrongItemCounts,
  986. end = i - 1;
  987. const locationList = this.getGraspServiceParamsReady(data, start, end);
  988. console.log(`共${drivingCounts + 1}项使用了轨迹纠偏:::start_${start}, end_${end}`);
  989. console.log(`start:::`, data[start]);
  990. console.log(`end:::`, data[end]);
  991. console.log(`轨迹纠偏参数:::`, locationList);
  992. this.serviceLog(preItem.serviceType, data[end]); // 打印 log
  993. // todo 轨迹纠偏的最后一个参数的 sp 需要归零
  994. if (locationList.length) {
  995. locationList[locationList.length - 1].sp = 0;
  996. }
  997. await this.graspRoadService(
  998. locationList,
  999. // eslint-disable-next-line
  1000. new AMap.GraspRoad(),
  1001. (result, useService) => {
  1002. if (useService) {
  1003. this.pathNew = this.pathNew.concat(
  1004. result.map(item => {
  1005. return [parseFloat(item.x.toFixed(6)), parseFloat(item.y.toFixed(6))];
  1006. })
  1007. );
  1008. wrongItemCounts = 0;
  1009. } else {
  1010. this.addPathByStraight([item.lng, item.lat]);
  1011. wrongItemCounts = 0;
  1012. }
  1013. }
  1014. );
  1015. } catch (e) {
  1016. console.log('纠偏报错:::', e);
  1017. }
  1018. // 由于driving服务改为了轨迹纠偏,是累计方式,那么只有循环到非driving方式才调取轨迹纠偏
  1019. // 所以其他服务需要用到的变量需要重新赋值
  1020. preItem = data[i - 1];
  1021. location = [
  1022. [preItem.lng, preItem.lat],
  1023. [item.lng, item.lat]
  1024. ];
  1025. address = preItem.address;
  1026. timeDif = this.calculateTime(preItem.e_time, item.s_time);
  1027. // 归零
  1028. drivingCounts = 0;
  1029. }
  1030. this.serviceLog(item.serviceType, item); // 打印 log
  1031. // todo 路径规划
  1032. // 调取其他出行方式的路径规划服务
  1033. const isSameAddress = this.checkAddress(address, item.address);
  1034. if (
  1035. item.serviceType === this.serviceTypes.overTime ||
  1036. item.serviceType === this.serviceTypes.overSpeed ||
  1037. isSameAddress ||
  1038. (item.serviceType === this.serviceTypes.sameAddress && wrongItemCounts) ||
  1039. item.serviceType === this.serviceTypes.transfer /* todo 高德暂无高铁服务,改为直连线 */
  1040. ) {
  1041. // 直连线:超时、超速、地址相同/包含
  1042. if (i === 1) {
  1043. this.pathNew = this.pathNew.concat(location);
  1044. this.collectionStraightPointsNew = this.collectionStraightPointsNew.concat(location);
  1045. continue;
  1046. }
  1047. let point = [item.lng, item.lat];
  1048. this.pathNew.push(point);
  1049. this.collectionStraightPointsNew.push(point);
  1050. wrongItemCounts = 0; // 归零
  1051. drivingCounts = 0; // 归零
  1052. continue;
  1053. } else if (item.serviceType === this.serviceTypes.walking || item.serviceType === this.serviceTypes.riding) {
  1054. // 步行、骑行
  1055. await this.walkingService(
  1056. location,
  1057. // eslint-disable-next-line
  1058. new AMap.Walking({ map: this.map.instance, autoFitView: i === data.length - 1, hideMarkers: true }),
  1059. (result, useService) => {
  1060. if (useService) {
  1061. console.log(`服务返回路程:${result.routes[0].distance} 米`);
  1062. if (this.checkSpeedByServiceType(result.routes[0].distance / timeDif, item.serviceType)) {
  1063. this.pathNew = this.pathNew.concat(this.getPathByWalkingService(result));
  1064. wrongItemCounts = 0;
  1065. } else if (item.circle) {
  1066. // 当前点为聚集点
  1067. this.addPathByStraight([item.lng, item.lat]);
  1068. wrongItemCounts = 0;
  1069. console.log('上面的服务不符合,但为聚集点,直连');
  1070. } else {
  1071. wrongItemCounts++; // 保留起点
  1072. item.skipService = true;
  1073. console.log('上面的服务不符合,跳过');
  1074. }
  1075. } else {
  1076. this.addPathByStraight([item.lng, item.lat]);
  1077. wrongItemCounts = 0;
  1078. console.log(`步行:::`, result);
  1079. }
  1080. }
  1081. );
  1082. // eslint-disable-next-line no-dupe-else-if
  1083. } else if (item.serviceType === this.serviceTypes.transfer) {
  1084. // 公交、地铁
  1085. await this.transferService(
  1086. location,
  1087. // eslint-disable-next-line
  1088. new AMap.Transfer({
  1089. map: this.map.instance,
  1090. autoFitView: i === data.length - 1,
  1091. hideMarkers: true,
  1092. city: preItem.cityCode,
  1093. cityd: item.cityCode
  1094. }),
  1095. (result, useService) => {
  1096. if (useService) {
  1097. console.log(`服务返回路程:${result.plans[0].distance} 米`);
  1098. if (this.checkSpeedByServiceType(result.plans[0].distance / timeDif, item.serviceType)) {
  1099. this.pathNew = this.pathNew.concat(this.getPathByTransferService(result));
  1100. wrongItemCounts = 0;
  1101. } else if (item.circle) {
  1102. // 当前点为聚集点
  1103. this.addPathByStraight([item.lng, item.lat]);
  1104. wrongItemCounts = 0;
  1105. console.log('上面的服务不符合,但为聚集点,直连');
  1106. } else {
  1107. wrongItemCounts++; // 保留起点
  1108. item.skipService = true;
  1109. console.log('上面的服务不符合,跳过');
  1110. }
  1111. } else {
  1112. this.addPathByStraight([item.lng, item.lat]);
  1113. wrongItemCounts = 0;
  1114. console.log(`公交:::`, result);
  1115. }
  1116. }
  1117. );
  1118. }
  1119. }
  1120. }
  1121. // for() {} done
  1122. ToastService.clear();
  1123. this.resetMarkerNew();
  1124. this.polylineList = this.pathNew.map(item => item);
  1125. this.createMarkerNew(this.pathNew);
  1126. this.createPolylineNew(this.pathNew);
  1127. this.createCirclesNew(data);
  1128. },
  1129. // 组装轨迹纠偏参数
  1130. getGraspServiceParamsReady(data, start, end) {
  1131. const tm0 = Math.round(new Date(data[start].s_time).getTime() / 1000);
  1132. return data.slice(start, end + 1).map((item, i) => {
  1133. const tmI = Math.round(new Date(item.e_time).getTime() / 1000) - tm0;
  1134. return {
  1135. x: item.lng,
  1136. y: item.lat,
  1137. sp: item.sp,
  1138. ag: 0,
  1139. tm: i === 0 ? tm0 : tmI
  1140. };
  1141. });
  1142. },
  1143. serviceLog(type, item) {
  1144. console.log(`服务::::${type}`);
  1145. console.log('终点::::', item);
  1146. console.count('------------------');
  1147. },
  1148. // 验证路径规划服务的结果是否符合情景
  1149. // 根据服务结果计算出的速度是否符合设定的区间
  1150. checkSpeedByServiceType(sp, type) {
  1151. sp = sp * this.ms_TO_kmh; // m/s 转 km/h
  1152. let result = true;
  1153. switch (type) {
  1154. case 'walking':
  1155. case 'riding':
  1156. result = sp <= this.speedTypes.riding[1];
  1157. break;
  1158. case 'driving':
  1159. result = sp <= this.speedTypes.driving[1] && sp > this.speedTypes.driving[0];
  1160. break;
  1161. case 'transfer':
  1162. result = sp <= this.speedTypes.highRailway[1] && sp >= this.speedTypes.highRailway[0];
  1163. break;
  1164. }
  1165. if (!result) console.log(`服务返回速度结果不符合预设区间,服务类型:${type},速度:${sp} km/h`);
  1166. return result;
  1167. },
  1168. // 检查中文地址是否相同、包含
  1169. checkAddress(addA, addB) {
  1170. return addA === addB || addA.indexOf(addB) >= 0 || addB.indexOf(addA) >= 0;
  1171. },
  1172. addPathByStraight(destinationLngLat) {
  1173. this.path.push(destinationLngLat);
  1174. this.collectionStraightPoints.push(destinationLngLat);
  1175. },
  1176. // 清除地图的覆盖物
  1177. clearMap() {
  1178. this.map.instance.clearMap();
  1179. },
  1180. resetMarker() {
  1181. this.map.marker = {};
  1182. this.map.polyline = {};
  1183. this.map.passedPolyline = {};
  1184. },
  1185. resetMarkerNew() {
  1186. this.map.marker = {};
  1187. this.map.polylineNew = {};
  1188. this.map.passedPolylineNew = {};
  1189. },
  1190. assemblePolylineList(lngLatList) {
  1191. this.polylineList = lngLatList.map(item => item);
  1192. },
  1193. createCircles(dataList) {
  1194. dataList.forEach((item, i) => {
  1195. if (i === dataList.length - 1) {
  1196. let center = [item.lng, item.lat];
  1197. // eslint-disable-next-line
  1198. let circle = new AMap.Circle({
  1199. center,
  1200. radius: this.limitDistance, // 半径
  1201. borderWeight: 3,
  1202. strokeColor: '#2599ff',
  1203. strokeWeight: 4,
  1204. strokeOpacity: 0.4,
  1205. fillOpacity: 0.2,
  1206. fillColor: '#2599ff',
  1207. zIndex: 10
  1208. });
  1209. circle.setMap(this.map.instance);
  1210. this.circleList.push(circle);
  1211. return;
  1212. }
  1213. if (item.circle /* && !item.skipService*/) {
  1214. let center = [item.lng, item.lat];
  1215. // eslint-disable-next-line
  1216. let circle = new AMap.Circle({
  1217. center,
  1218. radius: this.limitDistance, // 半径
  1219. borderWeight: 3,
  1220. strokeColor: '#fa796e',
  1221. strokeWeight: 4,
  1222. strokeOpacity: 0.4,
  1223. fillOpacity: 0.2,
  1224. // fillColor: "#2599ff",
  1225. fillColor: '#fa796e',
  1226. zIndex: 10
  1227. });
  1228. circle.setMap(this.map.instance);
  1229. this.circleList.push(circle);
  1230. }
  1231. });
  1232. },
  1233. createCirclesNew(dataList) {
  1234. dataList.forEach((item, i) => {
  1235. if (i === dataList.length - 1) {
  1236. let center = [item.lng, item.lat];
  1237. // eslint-disable-next-line
  1238. let circle = new AMap.Circle({
  1239. center,
  1240. radius: this.limitDistance, // 半径
  1241. borderWeight: 3,
  1242. strokeColor: '#2599ff',
  1243. strokeWeight: 4,
  1244. strokeOpacity: 0.4,
  1245. fillOpacity: 0.2,
  1246. fillColor: '#2599ff',
  1247. zIndex: 10
  1248. });
  1249. circle.setMap(this.map.instance);
  1250. this.circleListNew.push(circle);
  1251. return;
  1252. }
  1253. if (item.circle /* && !item.skipService*/) {
  1254. let center = [item.lng, item.lat];
  1255. // eslint-disable-next-line
  1256. let circle = new AMap.Circle({
  1257. center,
  1258. radius: this.limitDistance, // 半径
  1259. borderWeight: 3,
  1260. strokeColor: '#fa796e',
  1261. strokeWeight: 4,
  1262. strokeOpacity: 0.4,
  1263. fillOpacity: 0.2,
  1264. // fillColor: "#2599ff",
  1265. fillColor: '#fa796e',
  1266. zIndex: 10
  1267. });
  1268. circle.setMap(this.map.instance);
  1269. this.circleListNew.push(circle);
  1270. }
  1271. });
  1272. },
  1273. createMarker(dataList) {
  1274. let center;
  1275. if (dataList[0].lng && dataList[0].lat) {
  1276. center = [dataList[0].lng, dataList[0].lat];
  1277. } else {
  1278. center = [dataList[0][0], dataList[0][1]];
  1279. }
  1280. let content = `<div class="imageArea">
  1281. <img class="image_area" src="${this.imagePath}" />
  1282. <em></em>
  1283. </div>`;
  1284. /*let content = `<div class="img">
  1285. <div class="image_area">
  1286. <img src="${this.imagePath}" />
  1287. <i></i>
  1288. </div>
  1289. <div class="circel"></div>
  1290. </div>`;*/
  1291. // eslint-disable-next-line no-undef
  1292. this.map.marker = new AMap.Marker({ position: center, content, offset: new AMap.Pixel(-29, -76) });
  1293. this.map.instance.add(this.map.marker);
  1294. this.map.instance.setCenter(center);
  1295. },
  1296. createMarkerNew(dataList) {
  1297. let center;
  1298. if (dataList[0].lng && dataList[0].lat) {
  1299. center = [dataList[0].lng, dataList[0].lat];
  1300. } else {
  1301. center = [dataList[0][0], dataList[0][1]];
  1302. }
  1303. let content = `<div class="imageArea">
  1304. <img class="image_area" src="${this.imagePath}" />
  1305. <em></em>
  1306. </div>`;
  1307. /*let content = `<div class="img">
  1308. <div class="image_area">
  1309. <img src="${this.imagePath}" />
  1310. <i></i>
  1311. </div>
  1312. <div class="circel"></div>
  1313. </div>`;*/
  1314. // eslint-disable-next-line no-undef
  1315. this.map.marker = new AMap.Marker({ position: center, content, offset: new AMap.Pixel(-58, -152) });
  1316. this.map.instance.add(this.map.marker);
  1317. this.map.instance.setCenter(center);
  1318. },
  1319. createPolyline(lngLatList) {
  1320. const data = lngLatList.map(item => item);
  1321. // eslint-disable-next-line
  1322. this.map.polyline = new AMap.Polyline({
  1323. map: this.map.instance,
  1324. path: data,
  1325. strokeColor: '#43598a', //线颜色
  1326. strokeOpacity: 0.8, //线透明度
  1327. strokeWeight: 12, //线宽
  1328. // strokeStyle: "solid", //线样式
  1329. lineJoin: 'round',
  1330. lineCap: 'round',
  1331. showDir: true,
  1332. geodesic: true
  1333. });
  1334. // eslint-disable-next-line
  1335. this.map.passedPolyline = new AMap.Polyline({
  1336. map: this.map.instance,
  1337. strokeColor: '#ffc554', //线颜色
  1338. strokeOpacity: 0.9, //线透明度
  1339. strokeWeight: 10, //线宽
  1340. lineJoin: 'round',
  1341. lineCap: 'round',
  1342. showDir: true,
  1343. geodesic: true
  1344. });
  1345. let start_idx = 0;
  1346. this.map.marker.on('moving', e => {
  1347. try {
  1348. this.map.passedPolyline.setPath(e.passedPath);
  1349. this.lngLatOnMoving = e.passedPath.map(o => {
  1350. return [o.lng, o.lat];
  1351. });
  1352. if (start_idx >= this.dataList.length) return;
  1353. const length = this.lngLatOnMoving.length;
  1354. const radius = 40; // 距离条件
  1355. const idx = start_idx - 1 < 0 ? start_idx : start_idx - 1;
  1356. const circleCenterLngLat = [this.dataList[idx].lng, this.dataList[idx].lat];
  1357. const isLess = this.checkDistanceLessThanRadius(circleCenterLngLat, this.lngLatOnMoving[length - 1], radius);
  1358. if (!isLess) {
  1359. this.setAddressANDTime(this.dataList[idx]);
  1360. } else {
  1361. start_idx++;
  1362. this.setAddressANDTime(this.dataList[start_idx]);
  1363. }
  1364. } catch (e) {
  1365. console.log('fail::moving::::', e);
  1366. }
  1367. });
  1368. },
  1369. // 绘制 一条对比的轨迹
  1370. createPolylineNew(lngLatList) {
  1371. console.log('lngLatList', lngLatList);
  1372. const data = lngLatList.map(item => item);
  1373. console.log('data', data);
  1374. // eslint-disable-next-line
  1375. this.map.polylineNew = new AMap.Polyline({
  1376. map: this.map.instance,
  1377. path: data,
  1378. strokeColor: 'red', //线颜色
  1379. strokeOpacity: 0.8, //线透明度
  1380. strokeWeight: 12, //线宽
  1381. // strokeStyle: "solid", //线样式
  1382. lineJoin: 'round',
  1383. lineCap: 'round',
  1384. showDir: true,
  1385. geodesic: true
  1386. });
  1387. // eslint-disable-next-line
  1388. this.map.passedPolylineNew = new AMap.Polyline({
  1389. map: this.map.instance,
  1390. strokeColor: '#ffc554', //线颜色
  1391. strokeOpacity: 0.9, //线透明度
  1392. strokeWeight: 10, //线宽
  1393. lineJoin: 'round',
  1394. lineCap: 'round',
  1395. showDir: true,
  1396. geodesic: true
  1397. });
  1398. let start_idx = 0;
  1399. this.map.marker.on('moving', e => {
  1400. try {
  1401. this.map.passedPolylineNew.setPath(e.passedPath);
  1402. this.lngLatOnMovingNew = e.passedPath.map(o => {
  1403. return [o.lng, o.lat];
  1404. });
  1405. if (start_idx >= this.dataListNew.length) return;
  1406. const length = this.lngLatOnMovingNew.length;
  1407. const radius = 40; // 距离条件
  1408. const idx = start_idx - 1 < 0 ? start_idx : start_idx - 1;
  1409. const circleCenterLngLat = [this.dataListNew[idx].lng, this.dataListNew[idx].lat];
  1410. const isLess = this.checkDistanceLessThanRadius(
  1411. circleCenterLngLat,
  1412. this.lngLatOnMovingNew[length - 1],
  1413. radius
  1414. );
  1415. if (!isLess) {
  1416. this.setAddressANDTime(this.dataListNew[idx]);
  1417. } else {
  1418. start_idx++;
  1419. this.setAddressANDTime(this.dataListNew[start_idx]);
  1420. }
  1421. } catch (e) {
  1422. console.log('fail::moving::::', e);
  1423. }
  1424. });
  1425. },
  1426. // 比较两点的距离是否小于半径
  1427. checkDistanceLessThanRadius(circleCenterLngLat, targetLngLat, radius) {
  1428. // eslint-disable-next-line no-undef
  1429. const distance = parseInt(AMap.GeometryUtil.distance(circleCenterLngLat, targetLngLat));
  1430. return radius - distance > 0;
  1431. },
  1432. setAddressANDTime(o) {
  1433. if (this.$own.isNotNull(o)) {
  1434. this.address = o.address;
  1435. this.time = o.time;
  1436. }
  1437. },
  1438. toggleBottom(bool) {
  1439. this.bottomContentShow = bool;
  1440. },
  1441. // 旧版本 start
  1442. getData() {
  1443. return new Promise((resolve, reject) => {
  1444. /* let url = `/api/Location/History`;
  1445. let jsonData = {
  1446. deviceId: this.$store.getters.deviceId,
  1447. dateTime: new Date(this.date),
  1448. mapType: "gaode",
  1449. timeOffset: -new Date().getTimezoneOffset() / 60
  1450. }; */
  1451. /* this.$axios
  1452. .post(url, jsonData) */
  1453. APIDevice.history({
  1454. deviceId: this.$store.getters.deviceId,
  1455. dateTime: new Date(this.date),
  1456. mapType: 'gaode',
  1457. timeOffset: -new Date().getTimezoneOffset() / 60
  1458. })
  1459. .then(res => {
  1460. resolve(res);
  1461. if (res.data.stateCode === 0 && res.data.list == null) {
  1462. DialogService.confirm({ title: res.data.message /* className: 'device_confirm' */ });
  1463. ToastService.clear();
  1464. }
  1465. })
  1466. .catch(err => {
  1467. reject(err);
  1468. // Dialog({ title: '您的网络异常', message: '请稍后重试', className: 'device_confirm' });
  1469. })
  1470. .finally(() => (this.showOverlay = false));
  1471. });
  1472. },
  1473. // 旧版本 end
  1474. //逆地理编码
  1475. geocoder(value, type) {
  1476. //value是坐标(数组) type传入的boolean
  1477. let that = this;
  1478. new Promise((resolve, reject) => {
  1479. let MGeocoder = null;
  1480. let obj = {};
  1481. that.map.plugin(['AMap.Geocoder'], () => {
  1482. // eslint-disable-next-line no-undef
  1483. MGeocoder = new AMap.Geocoder({
  1484. radius: 1000,
  1485. extensions: 'all'
  1486. });
  1487. MGeocoder.getAddress(value, (status, result) => {
  1488. if (status == 'complete' && result.info == 'OK') {
  1489. let address = result.regeocode.formattedAddress;
  1490. obj = { address };
  1491. resolve(obj);
  1492. } else {
  1493. reject(obj);
  1494. }
  1495. });
  1496. });
  1497. })
  1498. .then(res => {
  1499. if (type) return true;
  1500. that.bottomContentShow = true;
  1501. that.address = res.address;
  1502. })
  1503. .catch(err => {
  1504. // that.bottomContentShow = false;
  1505. that.address = '无效的地理位置';
  1506. console.log(err);
  1507. if (type) {
  1508. return false;
  1509. } else {
  1510. console.log('坐标参数错误');
  1511. // that.dialog("坐标参数错误");
  1512. }
  1513. });
  1514. },
  1515. playerControl() {
  1516. this.playFlag = !this.playFlag;
  1517. if (this.playFlag) {
  1518. this.num = 0;
  1519. this.marker.moveAlong(this.lineArr, this.leftCode);
  1520. console.log('开始了!');
  1521. } else {
  1522. this.marker.pauseMove();
  1523. console.log('暂停了!再次点击将重来');
  1524. }
  1525. },
  1526. dateClick(type) {
  1527. this.playFlag = false;
  1528. this.calendarShow = false;
  1529. this.initTime();
  1530. if (type == 'add' && this.clickFlag) {
  1531. if (this.count >= 0) {
  1532. return;
  1533. }
  1534. this.count++;
  1535. this.date = this.$own.getNowFormatDate(this.count);
  1536. this.handleMinDate(this.date);
  1537. this.defaultDate = new Date(this.date.replace(/-/g, '/'));
  1538. // this.getData();
  1539. // this.getAmap();
  1540. this.getData_API();
  1541. } else {
  1542. if (this.clickFlag) {
  1543. this.count--;
  1544. this.date = this.$own.getNowFormatDate(this.count);
  1545. this.handleMinDate(this.date);
  1546. this.defaultDate = new Date(this.date.replace(/-/g, '/'));
  1547. // this.getData();
  1548. // this.getAmap();
  1549. this.getData_API();
  1550. }
  1551. }
  1552. },
  1553. handleMinDate() {
  1554. /* let getYear = new Date(date).getFullYear();
  1555. let getMonth = new Date(date).getMonth(); */
  1556. // TODO #BUG 取消点击选择日期时最小日期自动变化
  1557. /* this.minDate = new Date(getYear, getMonth, 1); */
  1558. },
  1559. dateAreaClick() {
  1560. this.calendarShow = !this.calendarShow;
  1561. },
  1562. onSelect(val) {
  1563. this.date = new Date(val).Format('yyyy-MM-dd');
  1564. this.handleMinDate(val);
  1565. let nowTime = new Date(new Date().Format('yyyy-MM-dd')).getTime();
  1566. let newTime = new Date(this.date).getTime();
  1567. this.count = (newTime - nowTime) / (60 * 60 * 24 * 1000);
  1568. this.calendarShow = false;
  1569. this.initTime();
  1570. this.closeTimePicker();
  1571. // this.getData();
  1572. // this.getAmap();
  1573. this.getData_API();
  1574. },
  1575. // 根据时间端选择轨迹
  1576. historyDetailListByIMEI() {
  1577. ToastService.loading({
  1578. message: '数据加载中...',
  1579. forbidClick: false,
  1580. getContainer: '#trajectory_map'
  1581. });
  1582. this.dataList = [];
  1583. this.path = [];
  1584. let date = new Date(this.date).Format('yyyy-MM-dd');
  1585. let startTime = date + ' ' + this.initTimePick.initStartTime;
  1586. let endTime = date + ' ' + this.initTimePick.initEndTime;
  1587. APIDevice.historyDetailListByIMEI({
  1588. deviceid: this.$store.getters.serialNo,
  1589. mapType: 'gaode',
  1590. start: startTime,
  1591. end: endTime
  1592. })
  1593. .then(result => {
  1594. if (result.data.stateCode !== 1) {
  1595. ToastService.clear();
  1596. DialogService.confirm({ title: result.data.message });
  1597. } else {
  1598. const filterData = this.deduplicatedANDDecorateList(result.data.data.item[0]);
  1599. console.log('filterData', filterData);
  1600. const circleData = this.decorateListCircle(filterData);
  1601. console.log('circleData', circleData);
  1602. const speedData = this.decorateListSpeed(circleData);
  1603. console.log('speedData', speedData);
  1604. const filterSpeedData = this.calculateByContext(speedData);
  1605. console.log('filterSpeedData:::', filterSpeedData);
  1606. this.decoratedAddressList = this.decorateListAddress(filterSpeedData);
  1607. this.resetMarker();
  1608. this.clearMap();
  1609. this.setAddressANDTime(filterData[0]);
  1610. // todo 循环经算出各路径的出行方式,将连续 driving 的路径使用轨迹纠偏
  1611. const serviceTypedData = this.serviceTypeFilter(filterSpeedData);
  1612. const drivingFilterData = this.changeToDriving(serviceTypedData);
  1613. console.log('drivingFilterData:::', drivingFilterData);
  1614. this.createServiceV2(drivingFilterData);
  1615. }
  1616. })
  1617. .catch(e => {
  1618. console.log('错误::', e);
  1619. ToastService.clear();
  1620. })
  1621. .finally(() => {
  1622. ToastService.clear();
  1623. });
  1624. },
  1625. // 点击'时间'快捷方式
  1626. onTimeClick() {
  1627. console.log('点击了时间');
  1628. this.timePickWidth = 80;
  1629. this.timeWidth = 20;
  1630. this.isLeft = false;
  1631. this.isPopup = !this.isPopup;
  1632. if (!this.isPopup) {
  1633. this.timePickWidth = 20;
  1634. this.timeWidth = 100;
  1635. this.isLeft = true;
  1636. }
  1637. },
  1638. // 点击开始时间
  1639. onStartClick() {
  1640. this.timePickData.timePickStartShow = !this.timePickData.timePickStartShow;
  1641. this.timePickData.timePickEndShow = false;
  1642. this.calendarShow = false;
  1643. },
  1644. // 点击结束时间
  1645. onEndClick() {
  1646. this.timePickData.timePickEndShow = !this.timePickData.timePickEndShow;
  1647. this.timePickData.timePickStartShow = false;
  1648. this.calendarShow = false;
  1649. },
  1650. // 点击确认开始时间
  1651. onStartTimeConfirm(startTime) {
  1652. this.initTimePick.initStartTime = startTime;
  1653. console.log('startTime', startTime);
  1654. this.onCloseTimePicker();
  1655. },
  1656. // 点击取消开始时间
  1657. onStartTimeCancel() {
  1658. this.timePickData.timePickStartShow = false;
  1659. },
  1660. // 点击确认结束时间
  1661. onEndTimeConfirm(endTime) {
  1662. this.initTimePick.initEndTime = endTime;
  1663. console.log('结束时间', endTime);
  1664. this.timePickData.timePickEndShow = false;
  1665. },
  1666. // 点击取消结束时间
  1667. onEndTimeCancel() {
  1668. this.timePickData.timePickEndShow = false;
  1669. },
  1670. // 关闭开始时间选择器,开始结束时间选择器
  1671. onCloseTimePicker() {
  1672. this.timePickData.timePickStartShow = false;
  1673. this.timePickData.timePickEndShow = true;
  1674. },
  1675. // 查看轨迹
  1676. onConfirm() {
  1677. this.closeTimePicker();
  1678. this.historyDetailListByIMEI();
  1679. },
  1680. // // 关闭时间选择器
  1681. closeTimePicker() {
  1682. this.timePickWidth = 20;
  1683. this.timeWidth = 80;
  1684. this.isLeft = false;
  1685. this.isPopup = false;
  1686. },
  1687. // 初始化时间选择器的时间
  1688. initTime() {
  1689. this.initTimePick.initStartTime = '00:00';
  1690. this.initTimePick.initEndTime = '23:59';
  1691. this.timePickData.selectStartTime = '00:00'; //初始化选择器内的时间
  1692. this.timePickData.selecttEndTime = '23:59';
  1693. },
  1694. // 判断字符串是否可以转化为json数据
  1695. isJsonString(str) {
  1696. try {
  1697. if (typeof JSON.parse(str) == 'object') {
  1698. return true;
  1699. }
  1700. } catch (e) {
  1701. /* empty */
  1702. }
  1703. return false;
  1704. },
  1705. onDialogCancel() {
  1706. this.dialogInput = '';
  1707. }
  1708. }
  1709. };
  1710. </script>
  1711. <style lang="scss">
  1712. @import './trajectory.scss';
  1713. .player {
  1714. i {
  1715. color: #fff;
  1716. }
  1717. }
  1718. .img {
  1719. position: relative;
  1720. height: 84px;
  1721. display: flex;
  1722. flex-flow: column;
  1723. justify-content: flex-start;
  1724. align-items: center;
  1725. border-radius: 50%;
  1726. z-index: 3;
  1727. }
  1728. .img .circel {
  1729. position: absolute;
  1730. bottom: 0;
  1731. width: 90px;
  1732. height: 90px;
  1733. /*background: rgba(255, 150, 37, 0.2);*/
  1734. border-radius: 50%;
  1735. display: flex;
  1736. justify-content: center;
  1737. align-items: center;
  1738. }
  1739. .img .circel:before {
  1740. content: '';
  1741. position: absolute;
  1742. width: 30px;
  1743. height: 30px;
  1744. background: #fff;
  1745. border-radius: 50%;
  1746. z-index: 3;
  1747. }
  1748. .img .circel:after {
  1749. content: ' ';
  1750. position: absolute;
  1751. width: 24px;
  1752. height: 24px;
  1753. background: #ff9625;
  1754. border-radius: 50%;
  1755. z-index: 4;
  1756. }
  1757. .img .image_area {
  1758. position: relative;
  1759. width: 92px;
  1760. height: 92px;
  1761. border-radius: 50%;
  1762. background: #fff;
  1763. display: flex;
  1764. justify-content: center;
  1765. align-items: center;
  1766. z-index: 4;
  1767. }
  1768. .img .image_area:after {
  1769. content: '';
  1770. position: absolute;
  1771. bottom: -10px;
  1772. width: 0;
  1773. height: 0;
  1774. border-top: 12px solid #fff;
  1775. border-left: 10px solid transparent;
  1776. border-right: 10px solid transparent;
  1777. }
  1778. .img .image_area img {
  1779. width: 84px;
  1780. height: 84px;
  1781. border-radius: 50%;
  1782. object-fit: cover;
  1783. }
  1784. .img.online .image_area:after {
  1785. border-top-color: #5fcc0e;
  1786. }
  1787. .img.online .image_area {
  1788. background: #5fcc0e;
  1789. }
  1790. .img.unline .image_area:after {
  1791. border-top-color: #b5b5b5;
  1792. }
  1793. .img.unline .image_area {
  1794. background: #b5b5b5;
  1795. }
  1796. .imageArea {
  1797. position: relative;
  1798. display: flex;
  1799. flex-flow: column;
  1800. justify-content: center;
  1801. align-items: center;
  1802. width: 60px;
  1803. height: 80px;
  1804. background: url(../../assets/img/tooltip-sizing.png);
  1805. }
  1806. .imageArea img {
  1807. position: absolute;
  1808. top: 17px;
  1809. width: 84px;
  1810. height: 84px;
  1811. border-radius: 50%;
  1812. background: rgb(197, 94, 47);
  1813. object-fit: cover;
  1814. }
  1815. .imageArea em {
  1816. position: absolute;
  1817. top: 128px;
  1818. display: flex;
  1819. justify-content: center;
  1820. align-items: center;
  1821. width: 30px;
  1822. height: 30px;
  1823. background: #fff;
  1824. border-radius: 50%;
  1825. }
  1826. .imageArea em:after {
  1827. content: ' ';
  1828. width: 24px;
  1829. height: 24px;
  1830. background: #ff9625;
  1831. border-radius: 50%;
  1832. }
  1833. .circleArea {
  1834. width: 80px;
  1835. height: 80px;
  1836. background: rgba(37, 153, 255, 0.3);
  1837. border-radius: 50%;
  1838. display: flex;
  1839. justify-content: center;
  1840. align-items: center;
  1841. }
  1842. .circleArea i {
  1843. display: flex;
  1844. justify-content: center;
  1845. align-items: center;
  1846. width: 30px;
  1847. height: 30px;
  1848. background: #fff;
  1849. border-radius: 50%;
  1850. }
  1851. .circleArea i:after {
  1852. content: ' ';
  1853. width: 24px;
  1854. height: 24px;
  1855. background: #2599ff;
  1856. border-radius: 50%;
  1857. }
  1858. .c {
  1859. width: 14px;
  1860. height: 14px;
  1861. border: 3px solid #3296fa;
  1862. border-radius: 50%;
  1863. background: #fff;
  1864. display: flex;
  1865. justify-content: center;
  1866. align-items: center;
  1867. }
  1868. .c:before {
  1869. content: '';
  1870. width: 10px;
  1871. height: 10px;
  1872. background: #3296fa;
  1873. border-radius: 50%;
  1874. }
  1875. .end {
  1876. width: 50px;
  1877. height: 50px;
  1878. background-color: #ff0;
  1879. background: transparent url(../../assets/icon.png) no-repeat;
  1880. background-size: 400px 400px;
  1881. background-position: -250px -150px;
  1882. }
  1883. .trajectory {
  1884. height: 100vh;
  1885. position: relative;
  1886. overflow: hidden;
  1887. .trajectory-con {
  1888. position: relative;
  1889. /* height: calc(100vh - 100px); */
  1890. overflow: scroll;
  1891. .top {
  1892. width: 100%;
  1893. position: absolute;
  1894. left: 0;
  1895. top: 0;
  1896. z-index: 999;
  1897. background: #fff;
  1898. .dateArea {
  1899. padding: 0 30px;
  1900. height: 100px;
  1901. display: flex;
  1902. justify-content: space-between;
  1903. align-items: center;
  1904. z-index: 999;
  1905. i {
  1906. @include center();
  1907. width: 62px;
  1908. height: 62px;
  1909. border-radius: 50%;
  1910. background: rgba(207, 207, 207, 0.36);
  1911. &:before {
  1912. content: '';
  1913. width: 50px;
  1914. height: 50px;
  1915. @include icon();
  1916. @include icon_position(50, 50, 100, 0);
  1917. }
  1918. &.right {
  1919. &:before {
  1920. @include icon_position(50, 50, 100, 50);
  1921. }
  1922. &.active {
  1923. background: #c8e5ff;
  1924. &:before {
  1925. transform: rotate(180deg);
  1926. @include icon_position(50, 50, 100, 0);
  1927. }
  1928. }
  1929. }
  1930. &.active {
  1931. background: #c8e5ff;
  1932. }
  1933. }
  1934. span {
  1935. @include colorAndFont(#2599ff, 36);
  1936. @include center();
  1937. &:after {
  1938. content: '';
  1939. margin: 6px 0 0 8px;
  1940. width: 0;
  1941. height: 0;
  1942. border-top: 12px solid $blue;
  1943. border-left: 13px solid transparent;
  1944. border-right: 13px solid transparent;
  1945. border-radius: 10px;
  1946. }
  1947. }
  1948. }
  1949. }
  1950. // 时间段选择项
  1951. .time-picker {
  1952. position: absolute;
  1953. right: -20px;
  1954. top: 250px;
  1955. height: 80px;
  1956. display: flex;
  1957. justify-content: flex-start;
  1958. background-color: #ffff;
  1959. border-radius: 10px;
  1960. box-shadow: 0 0 10px rgba(185, 185, 185, 0.9);
  1961. z-index: 99;
  1962. .time-picker-left {
  1963. height: 80px;
  1964. width: 30%;
  1965. line-height: 60px;
  1966. .van-icon-arrow-left,
  1967. .van-icon-arrow {
  1968. height: 10px;
  1969. width: 20px;
  1970. line-height: 40px;
  1971. font-size: 14px;
  1972. }
  1973. span {
  1974. font-size: 32px;
  1975. }
  1976. }
  1977. .time-picker-right {
  1978. height: 80px;
  1979. width: 50%;
  1980. line-height: 70px;
  1981. text-align: center;
  1982. span,
  1983. label {
  1984. font-size: 32px;
  1985. }
  1986. span {
  1987. color: #2599ff;
  1988. }
  1989. label {
  1990. padding: 10px;
  1991. }
  1992. }
  1993. .datetimePicker {
  1994. position: relative;
  1995. top: 8px;
  1996. right: 120px;
  1997. width: 580px;
  1998. }
  1999. .time-picker-right-confirm {
  2000. height: 80px;
  2001. width: auto;
  2002. display: flex;
  2003. justify-content: center;
  2004. align-items: center;
  2005. line-height: 80px;
  2006. text-align: center;
  2007. padding-right: 20px;
  2008. color: white;
  2009. .right-confirm-text {
  2010. height: 60px;
  2011. width: 120px;
  2012. line-height: 50px;
  2013. background-color: #2599ff;
  2014. border-radius: 10px;
  2015. span {
  2016. font-size: 32px;
  2017. }
  2018. }
  2019. }
  2020. }
  2021. // 添加轨迹对比
  2022. .bottom {
  2023. width: 100%;
  2024. position: fixed;
  2025. left: 0;
  2026. bottom: 0;
  2027. z-index: 600;
  2028. box-shadow: 0 0 20px rgba(185, 185, 185, 0.4);
  2029. .conArea {
  2030. position: relative;
  2031. }
  2032. .con {
  2033. padding: 20px;
  2034. background: #fff;
  2035. border-radius: 24px 24px 0 0;
  2036. .content-list {
  2037. min-height: 100px;
  2038. max-height: 200px;
  2039. overflow: scroll;
  2040. padding: 30px 0 0 30px;
  2041. .content-item {
  2042. margin-bottom: 10px;
  2043. }
  2044. }
  2045. .title {
  2046. width: 580px;
  2047. @include colorAndFont(#333, 34);
  2048. /*margin-bottom: px2rem(36);*/
  2049. }
  2050. .adr {
  2051. @include colorAndFont(#999, 28);
  2052. margin-bottom: px2rem(18);
  2053. span {
  2054. margin-left: 15px;
  2055. padding: 0 8px;
  2056. height: 30px;
  2057. @include colorAndFont(#999, 20);
  2058. background: #fafafa;
  2059. border: 1px solid #b5b5b5;
  2060. border-radius: 4px;
  2061. }
  2062. }
  2063. .states {
  2064. display: flex;
  2065. justify-content: flex-start;
  2066. align-items: center;
  2067. span {
  2068. display: flex;
  2069. justify-content: flex-start;
  2070. align-items: center;
  2071. @include colorAndFont(#999, 24);
  2072. }
  2073. }
  2074. }
  2075. .player {
  2076. position: absolute;
  2077. right: 40px;
  2078. top: 50%;
  2079. margin-top: -42px;
  2080. width: 84px;
  2081. height: 84px;
  2082. background: $blue;
  2083. border-radius: 50%;
  2084. box-shadow: 0 0 30px rgba(37, 153, 255, 0.4);
  2085. @include center();
  2086. i.play-icon {
  2087. position: relative;
  2088. width: 50px;
  2089. height: 50px;
  2090. @include center;
  2091. @include icon();
  2092. @include icon_position(50, 50, 100, 100);
  2093. &.stop {
  2094. background: unset;
  2095. &:before,
  2096. &:after {
  2097. content: '';
  2098. width: 8px;
  2099. height: 32px;
  2100. background-color: #fff;
  2101. border-radius: 4px;
  2102. }
  2103. &:before {
  2104. margin-right: 14px;
  2105. }
  2106. }
  2107. }
  2108. }
  2109. }
  2110. .slider-bar {
  2111. position: absolute;
  2112. right: 30px;
  2113. top: -450px;
  2114. padding: 60px 10px;
  2115. width: 60px;
  2116. height: 300px;
  2117. background-color: #fff;
  2118. border-radius: 10px;
  2119. @include center();
  2120. flex-flow: column;
  2121. @include colorAndFont(#666, 16);
  2122. .bar {
  2123. height: 100%;
  2124. margin: 20px 0;
  2125. }
  2126. .van-slider__button {
  2127. width: 30px;
  2128. height: 30px;
  2129. }
  2130. span {
  2131. font-size: 20px;
  2132. }
  2133. }
  2134. }
  2135. }
  2136. </style>