voyageDetail.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <template>
  2. <div class="line-container-p24">
  3. <i class="el-icon-arrow-left"></i>
  4. <div
  5. class="dib go-back ml8 pointer"
  6. @click="router.replace('/voyage/voyageList')"
  7. >
  8. 返回航次列表
  9. </div>
  10. </div>
  11. <div class="container-title">航次信息</div>
  12. <div class="line-container-p24">
  13. <div class="line">
  14. <div class="info-line">
  15. <div class="info-line-title">航次名称</div>
  16. <el-input
  17. class="info-line-text"
  18. v-model="voyage.voyageName"
  19. disabled
  20. ></el-input>
  21. </div>
  22. <div class="info-line">
  23. <div class="info-line-title">货主</div>
  24. <el-input
  25. class="info-line-text"
  26. v-model="voyage.cargoOwnerName"
  27. disabled
  28. ></el-input>
  29. </div>
  30. </div>
  31. <!-- <div class="line">
  32. <div class="info-line">
  33. <div class="info-line-title">船东</div>
  34. <el-input
  35. class="info-line-text"
  36. v-model="voyage.shipOwnerName"
  37. disabled
  38. ></el-input>
  39. </div>
  40. <div class="info-line">
  41. <div class="info-line-title">船东手机号</div>
  42. <el-input
  43. class="info-line-text"
  44. v-model="voyage.shipOwnerPhone"
  45. disabled
  46. ></el-input>
  47. </div>
  48. </div> -->
  49. <div class="line">
  50. <div class="info-line">
  51. <div class="info-line-title">船舶</div>
  52. <el-input
  53. class="info-line-text"
  54. v-model="voyage.shipName"
  55. disabled
  56. ></el-input>
  57. </div>
  58. <div class="info-line">
  59. <div class="info-line-title">MMSI</div>
  60. <el-input
  61. class="info-line-text"
  62. v-model="voyage.shipMmsi"
  63. disabled
  64. ></el-input>
  65. </div>
  66. </div>
  67. <div id="map-container" class="map-container"></div>
  68. <div class="line" style="margin-top: 30px">
  69. <div class="info-line">
  70. <div class="info-line-title">开始时间</div>
  71. <el-input
  72. class="info-line-text"
  73. v-model="voyage.startTime"
  74. disabled
  75. ></el-input>
  76. </div>
  77. <div class="info-line">
  78. <div class="info-line-title">结束时间</div>
  79. <el-input
  80. class="info-line-text"
  81. v-model="voyage.endTime"
  82. disabled
  83. ></el-input>
  84. </div>
  85. </div>
  86. <div class="line">
  87. <div class="info-line">
  88. <div class="info-line-title">装货港</div>
  89. <el-input
  90. class="info-line-text"
  91. v-model="voyage.loadPort"
  92. disabled
  93. ></el-input>
  94. </div>
  95. <div class="info-line">
  96. <div class="info-line-title">卸货港</div>
  97. <el-input
  98. class="info-line-text"
  99. v-model="voyage.dischargeProt"
  100. disabled
  101. ></el-input>
  102. </div>
  103. </div>
  104. <div class="line">
  105. <div class="info-line">
  106. <div class="info-line-title">货种</div>
  107. <el-input
  108. class="info-line-text"
  109. v-model="voyage.cargo"
  110. disabled
  111. ></el-input>
  112. </div>
  113. <div class="info-line">
  114. <div class="info-line-title">吨位</div>
  115. <el-input
  116. class="info-line-text"
  117. v-model="voyage.tons"
  118. disabled
  119. ></el-input>
  120. </div>
  121. </div>
  122. </div>
  123. <div class="container-title">航次信息</div>
  124. <div class="line-container-p24">
  125. <div class="line">
  126. <div class="info-line">
  127. <div class="info-line-title">运输状态</div>
  128. <el-select
  129. v-model="voyage.transStatus"
  130. placeholder="Select"
  131. class="info-line-text"
  132. :disabled="disabledStatus"
  133. >
  134. <el-option
  135. v-for="item in options"
  136. :key="item.value"
  137. :label="item.label"
  138. :value="item.value"
  139. ></el-option>
  140. </el-select>
  141. </div>
  142. <div class="info-line">
  143. <div class="info-line-title">实际卸货吨位</div>
  144. <el-input
  145. class="info-line-text"
  146. v-model="voyage.actualDischargeTons"
  147. :disabled="disabledStatus"
  148. ></el-input>
  149. </div>
  150. </div>
  151. <div class="line">
  152. <div class="info-line">
  153. <div class="info-line-title">到达装货港时间</div>
  154. <el-date-picker
  155. class="info-line-text"
  156. v-model="voyage.arrivalLoadPortTime"
  157. type="datetime"
  158. format="YYYY/MM/DD HH:mm:ss"
  159. value-format="YYYY/MM/DD HH:mm:ss"
  160. placeholder="到达装货港时间"
  161. :disabled="disabledStatus"
  162. ></el-date-picker>
  163. </div>
  164. <div class="info-line">
  165. <div class="info-line-title">实装吨位</div>
  166. <el-input
  167. class="info-line-text"
  168. v-model="voyage.actualDischargeTons"
  169. :disabled="disabledStatus"
  170. placeholder="实装吨位"
  171. ></el-input>
  172. </div>
  173. </div>
  174. <div class="line">
  175. <div class="info-line">
  176. <div class="info-line-title">装货开始时间</div>
  177. <el-date-picker
  178. class="info-line-text"
  179. v-model="voyage.loadStartTime"
  180. type="datetime"
  181. format="YYYY/MM/DD HH:mm:ss"
  182. value-format="YYYY/MM/DD HH:mm:ss"
  183. placeholder="装货开始时间"
  184. :disabled="disabledStatus"
  185. ></el-date-picker>
  186. </div>
  187. <div class="info-line">
  188. <div class="info-line-title">装货结束时间</div>
  189. <el-date-picker
  190. class="info-line-text"
  191. v-model="voyage.loadEndTime"
  192. type="datetime"
  193. format="YYYY/MM/DD HH:mm:ss"
  194. value-format="YYYY/MM/DD HH:mm:ss"
  195. placeholder="装货开始时间"
  196. :disabled="disabledStatus"
  197. ></el-date-picker>
  198. </div>
  199. </div>
  200. <div class="line">
  201. <div class="info-line">
  202. <div class="info-line-title">开航时间</div>
  203. <el-date-picker
  204. class="info-line-text"
  205. v-model="voyage.setSailTime"
  206. type="datetime"
  207. format="YYYY/MM/DD HH:mm:ss"
  208. value-format="YYYY/MM/DD HH:mm:ss"
  209. placeholder="开航时间"
  210. :disabled="disabledStatus"
  211. ></el-date-picker>
  212. </div>
  213. <div class="info-line">
  214. <div class="info-line-title">预计到港时间</div>
  215. <el-date-picker
  216. class="info-line-text"
  217. v-model="voyage.expectedArrivalTime"
  218. type="datetime"
  219. format="YYYY/MM/DD HH:mm:ss"
  220. value-format="YYYY/MM/DD HH:mm:ss"
  221. placeholder="预计到港时间"
  222. :disabled="disabledStatus"
  223. ></el-date-picker>
  224. </div>
  225. </div>
  226. <div class="line">
  227. <div class="info-line">
  228. <div class="info-line-title">实际到港时间</div>
  229. <el-date-picker
  230. class="info-line-text"
  231. v-model="voyage.actualArrivalTime"
  232. type="datetime"
  233. format="YYYY/MM/DD HH:mm:ss"
  234. value-format="YYYY/MM/DD HH:mm:ss"
  235. placeholder="实际到港时间"
  236. :disabled="disabledStatus"
  237. ></el-date-picker>
  238. </div>
  239. <div class="info-line">
  240. <div class="info-line-title">抵达目的地港时间</div>
  241. <el-date-picker
  242. class="info-line-text"
  243. v-model="voyage.arrivalPortTime"
  244. type="datetime"
  245. format="YYYY/MM/DD HH:mm:ss"
  246. value-format="YYYY/MM/DD HH:mm:ss"
  247. placeholder="抵达目的地港时间"
  248. :disabled="disabledStatus"
  249. ></el-date-picker>
  250. </div>
  251. </div>
  252. <div class="line">
  253. <div class="info-line">
  254. <div class="info-line-title">卸货开始时间</div>
  255. <el-date-picker
  256. class="info-line-text"
  257. v-model="voyage.dischargeStartTime"
  258. type="datetime"
  259. format="YYYY/MM/DD HH:mm:ss"
  260. value-format="YYYY/MM/DD HH:mm:ss"
  261. placeholder="装货开始时间"
  262. :disabled="disabledStatus"
  263. ></el-date-picker>
  264. </div>
  265. <div class="info-line">
  266. <div class="info-line-title">卸货结束时间</div>
  267. <el-date-picker
  268. class="info-line-text"
  269. v-model="voyage.dischargeEndTime"
  270. type="datetime"
  271. format="YYYY/MM/DD HH:mm:ss"
  272. value-format="YYYY/MM/DD HH:mm:ss"
  273. placeholder="装货开始时间"
  274. :disabled="disabledStatus"
  275. ></el-date-picker>
  276. </div>
  277. </div>
  278. <div class="line">
  279. <div class="info-line">
  280. <div class="info-line-title">备注</div>
  281. <el-input
  282. class="info-line-textarea"
  283. v-model="voyage.remark"
  284. autosize
  285. type="textarea"
  286. :disabled="disabledStatus"
  287. ></el-input>
  288. </div>
  289. </div>
  290. <div class="media-content df ffw">
  291. <div class="pic-container">
  292. <div v-for="(item, index) in media" :key="item" class="pic-main">
  293. <div
  294. :class="[
  295. 'box',
  296. index % 2 == 0 ? '' : 'bottom-box',
  297. item.status == 1 ? 'now-box' : '',
  298. ]"
  299. >
  300. <div class="card-note">
  301. {{ item.shipName }} 拍摄于
  302. <br />
  303. {{ item.createTime }}
  304. </div>
  305. <div class="media-box" style="position: relative">
  306. <el-image
  307. v-if="item.mediaType == 1"
  308. style="width: 100%; height: 100%"
  309. fit="contain"
  310. :src="item.downloadUrl"
  311. :preview-src-list="previewSrcList"
  312. ></el-image>
  313. <video
  314. style="width: 100%; height: 100%"
  315. v-else
  316. :src="item.downloadUrl"
  317. ></video>
  318. <img
  319. @click="openVideoModal(item.downloadUrl)"
  320. v-if="item.mediaType == 2"
  321. src="../../assets/icon-player.png"
  322. style="
  323. object-fit: contain;
  324. width: 40px;
  325. height: 40px;
  326. position: absolute;
  327. top: calc(50% - 20px);
  328. left: calc(50% - 20px);
  329. background: #fff;
  330. border-radius: 50%;
  331. "
  332. alt=""
  333. />
  334. </div>
  335. </div>
  336. <div
  337. :class="[
  338. 's-line',
  339. index % 2 == 0 ? '' : 'top210px',
  340. item.status == 1 ? 'now-s-line' : '',
  341. ]"
  342. ></div>
  343. <div :class="['point', item.status == 1 ? '' : 'now-point']"></div>
  344. <div
  345. :class="['l-line', item.status == 1 ? 'now-l-line' : '']"
  346. v-if="index + 1 != media.length"
  347. ></div>
  348. </div>
  349. <el-dialog
  350. v-model="videoModal"
  351. title="视频审核"
  352. width="20%"
  353. :before-close="videoClose"
  354. >
  355. <video
  356. autoplay
  357. controls
  358. style="width: 100%; height: 100%"
  359. :src="currentUrl"
  360. ></video>
  361. </el-dialog>
  362. </div>
  363. </div>
  364. </div>
  365. </template>
  366. <script>
  367. import { onMounted, reactive, ref, toRefs } from "_vue@3.2.20@vue";
  368. import api from "../../apis/fetch";
  369. import { useRoute } from "vue-router";
  370. import _ from "lodash";
  371. import router from "../../router";
  372. import store from "../../store";
  373. import { ElNotification } from "element-plus";
  374. export default {
  375. setup() {
  376. const route = useRoute();
  377. let map = ref();
  378. let voyage = ref({});
  379. let media = ref();
  380. let coordinates = ref();
  381. let previewSrcList = ref([]);
  382. async function getVoyageDetail() {
  383. let res = await api.getVoyageDetail({
  384. type: 1,
  385. voyageId: route.query.id,
  386. });
  387. coordinates.value = res.data.result.coordinates;
  388. voyage.value = res.data.result.voyage;
  389. media.value = res.data.result.medias;
  390. for (let i of media.value) {
  391. previewSrcList.value.push(i.downloadUrl);
  392. }
  393. initMap();
  394. }
  395. function initMap() {
  396. map.value = new AMap.Map("map-container", {
  397. zoom: 11, //级别
  398. center: [121.524761, 31.228721], //中心点坐标
  399. });
  400. let { longitude, latitude } =
  401. coordinates.value[coordinates.value.length - 1];
  402. setShipMarker(longitude, latitude);
  403. }
  404. function setShipMarker(longitude = 121.524761, latitude = 31.228721) {
  405. map.value.setCenter([longitude, latitude]);
  406. var marker = new AMap.Marker({
  407. position: new AMap.LngLat(longitude, latitude),
  408. // offset: new AMap.Pixel(-10, -10),
  409. size: new AMap.Size(80, 80),
  410. icon: "https://hhd-pat-1255802371.cos.ap-shanghai.myqcloud.com/frontend/ship-red-icon.png", // 添加 Icon 图标 URL
  411. title: "北京",
  412. });
  413. map.value.add(marker);
  414. }
  415. let disabledStatus = ref(true);
  416. let updateCache = {};
  417. function changeVoyageInfo() {
  418. updateCache = _.cloneDeep(voyage.value);
  419. disabledStatus.value = false;
  420. }
  421. function cancelVoyageChange() {
  422. voyage.value = updateCache;
  423. disabledStatus.value = true;
  424. }
  425. async function submitVoyageChange() {
  426. let {
  427. id,
  428. transStatus,
  429. loadStartTime,
  430. loadEndTime,
  431. dischargeStartTime,
  432. dischargeEndTime,
  433. actualDischargeTons,
  434. remark,
  435. } = voyage.value;
  436. let res = await api.updateVoyage({
  437. id,
  438. transStatus,
  439. loadStartTime,
  440. loadEndTime,
  441. dischargeStartTime,
  442. dischargeEndTime,
  443. actualDischargeTons,
  444. remark,
  445. });
  446. if (res.data.status == 0) {
  447. ElNotification({
  448. type: "success",
  449. title: res.data.msg,
  450. });
  451. disabledStatus.value = true;
  452. } else {
  453. ElNotification({
  454. type: "error",
  455. title: res.data.msg,
  456. });
  457. console.log(res);
  458. }
  459. }
  460. let options = ref([
  461. { value: 0, label: "请选择" },
  462. {
  463. value: 1,
  464. label: "航行",
  465. },
  466. {
  467. value: 2,
  468. label: "停泊",
  469. },
  470. {
  471. value: 3,
  472. label: "装货",
  473. },
  474. {
  475. value: 4,
  476. label: "运输中",
  477. },
  478. {
  479. value: 5,
  480. label: "卸货",
  481. },
  482. ]);
  483. async function finishVoyage() {
  484. if (!voyage.value.dischargeEndTime) return;
  485. let res = await api.finishVoyage({
  486. voyageId: route.query.id,
  487. });
  488. if (res.data.status == 0) {
  489. voyage.value.voyageStatus = 2;
  490. ElNotification({
  491. type: "success",
  492. title: res.data.msg,
  493. });
  494. } else {
  495. ElNotification({
  496. type: "error",
  497. title: res.data.msg,
  498. });
  499. console.log(res);
  500. }
  501. }
  502. let currentUrl = ref("");
  503. let videoModal = ref(false);
  504. function openVideoModal(url) {
  505. currentUrl.value = url;
  506. videoModal.value = true;
  507. }
  508. async function auditMedia(mediaId, a, index, mediaType) {
  509. console.log(mediaId, a, index, mediaType);
  510. let res = await api.auditMedia({
  511. mediaId,
  512. audit: a,
  513. });
  514. if (res.data.status == 0) {
  515. media.value[index].audit = a;
  516. }
  517. console.log(res);
  518. }
  519. onMounted(() => {
  520. getVoyageDetail();
  521. });
  522. return {
  523. options,
  524. voyage,
  525. coordinates,
  526. media,
  527. disabledStatus,
  528. changeVoyageInfo,
  529. cancelVoyageChange,
  530. submitVoyageChange,
  531. finishVoyage,
  532. openVideoModal,
  533. previewSrcList,
  534. router,
  535. auditMedia,
  536. currentUrl,
  537. videoModal,
  538. };
  539. },
  540. };
  541. </script>
  542. <style scoped>
  543. .map-container {
  544. width: 100%;
  545. height: 500px;
  546. }
  547. .card-note {
  548. height: 30px;
  549. font-size: 12px;
  550. font-family: PingFangSC-Regular, PingFang SC;
  551. font-weight: 400;
  552. color: #777777;
  553. }
  554. .media-box {
  555. width: 200px;
  556. height: 200px;
  557. margin-top: 20px;
  558. }
  559. .checkbox-group {
  560. width: 200px;
  561. height: 50px;
  562. margin-top: 20px;
  563. }
  564. .media-content {
  565. width: 100%;
  566. height: 600px;
  567. background: #f7f7f7;
  568. border-radius: 2px;
  569. }
  570. .pic-container {
  571. width: 100%;
  572. height: 100%;
  573. box-sizing: border-box;
  574. display: flex;
  575. padding: 30px;
  576. overflow-x: scroll;
  577. overflow-y: hidden;
  578. white-space: nowrap;
  579. }
  580. .pic-main {
  581. position: relative;
  582. width: 120px;
  583. }
  584. .box {
  585. position: absolute;
  586. height: 240px;
  587. width: var(--box-width);
  588. border: 5px solid #dddddd;
  589. transition: all 0.5s;
  590. background: #fff;
  591. z-index: 10;
  592. }
  593. .point {
  594. position: relative;
  595. left: 93px;
  596. top: 258px;
  597. width: 16px;
  598. height: 16px;
  599. background-image: url(../../assets/blue-circle.png);
  600. }
  601. .s-line {
  602. position: absolute;
  603. left: 100px;
  604. top: 242px;
  605. height: 20px;
  606. border-left: 2px dashed;
  607. box-sizing: border-box;
  608. border-color: #ddd;
  609. }
  610. .l-line {
  611. position: relative;
  612. bottom: 30px;
  613. left: 111px;
  614. top: 249px;
  615. height: 3px;
  616. width: 100px;
  617. background-color: #dddddd;
  618. }
  619. .bottom-box {
  620. top: 290px;
  621. }
  622. .top210px {
  623. top: 270px;
  624. }
  625. .box:hover {
  626. transform: scale(1.2);
  627. }
  628. .media-box {
  629. width: 80px;
  630. height: 80px;
  631. margin-top: 10px;
  632. }
  633. .card-note {
  634. height: 30px;
  635. font-size: 12px;
  636. font-family: PingFangSC-Regular, PingFang SC;
  637. font-weight: 400;
  638. color: #777777;
  639. padding: 10px 20px;
  640. }
  641. .media-box {
  642. width: 100%;
  643. height: 100px;
  644. margin-top: 20px;
  645. }
  646. .checkbox-group {
  647. width: 180px;
  648. height: 50px;
  649. margin-top: 20px;
  650. }
  651. .el-checkbox {
  652. margin: 0;
  653. }
  654. .now-box {
  655. border: 5px solid #0094fe;
  656. }
  657. .now-l-line {
  658. background-color: #0094fe;
  659. }
  660. .now-s-line {
  661. border-color: #97caf6;
  662. }
  663. .now-point {
  664. filter: grayscale(1);
  665. }
  666. </style>