shipDetail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <template>
  2. <div class="line-container-p24">
  3. <div
  4. class="df aic dib go-back ml8 pointer"
  5. @click="router.replace('/shipManage/shipList')"
  6. >
  7. <el-icon class="mr10"><ArrowLeftBold /></el-icon>
  8. <div>返回船舶列表</div>
  9. </div>
  10. </div>
  11. <div id="map-container" class="map-container"></div>
  12. <ShipInfo :shipInfos="shipInfos"></ShipInfo>
  13. <div class="df aic jcsb pr20" v-if="shipInfos.length">
  14. <div class="container-title">船员信息</div>
  15. <CrewInfo
  16. :shipCode="route.query.shipCode"
  17. :shipname="shipInfos[0].shipname"
  18. @onSubmit="getCrewList"
  19. ></CrewInfo>
  20. </div>
  21. <el-table border :data="crewList" stripe style="width: 1000px">
  22. <el-table-column align="center" type="index" label="序号" width="80" />
  23. <el-table-column
  24. align="center"
  25. prop="crewName"
  26. label="船员姓名"
  27. min-width="120"
  28. />
  29. <el-table-column
  30. align="center"
  31. prop="crewCertExpiryDate"
  32. label="有效期"
  33. min-width="120"
  34. >
  35. <template v-slot="scope">
  36. {{ subTimeStr(scope.row.crewCertExpiryDate) }}
  37. </template>
  38. </el-table-column>
  39. <el-table-column align="center" label="详情" min-width="120">
  40. <template #default="scope">
  41. <div class="df aic jcc">
  42. <el-button
  43. class="mr10"
  44. type="primary"
  45. @click="goToCrewDetail(scope.row)"
  46. >
  47. 详情
  48. </el-button>
  49. <CrewInfo
  50. class="mr10"
  51. :shipCode="route.query.shipCode"
  52. :shipname="shipInfos[0].shipname"
  53. :crewId="scope.row.id"
  54. :crewInfo="scope.row"
  55. @onSubmit="getCrewList"
  56. ></CrewInfo>
  57. <el-button type="danger" @click="deleteCrew(scope.row)">
  58. 删除
  59. </el-button>
  60. </div>
  61. </template>
  62. </el-table-column>
  63. </el-table>
  64. <div class="df aic jcfe mt40 mr20">
  65. <el-pagination
  66. :current-page="crewCurrentPage"
  67. @current-change="crewPageChange"
  68. background
  69. layout="prev, pager, next"
  70. :total="crewTotal"
  71. />
  72. </div>
  73. <div class="container-title">船舶图片</div>
  74. <div v-if="medias.length" class="medias-content df ffw">
  75. <div class="pic-container">
  76. <div v-for="(item, index) in medias" :key="item" class="pic-main">
  77. <div :class="['box', index % 2 == 0 ? '' : 'bottom-box']">
  78. <div class="card-note">
  79. {{ item.shipName }} 拍摄于
  80. <br />
  81. {{ item.createTime }}
  82. <br />
  83. 天气 : {{ item.weather?.weather }} - 气温 :
  84. {{ item.weather?.temperature }}℃
  85. </div>
  86. <div class="medias-box mb10" style="position: relative">
  87. <el-image
  88. v-if="item.mediaType == 1"
  89. style="width: 100%; height: 100%"
  90. fit="contain"
  91. :src="item.downloadUrl"
  92. @click="openMediaModal(item.downloadUrl, 1, '图片查看')"
  93. ></el-image>
  94. </div>
  95. <el-button
  96. v-if="item.audit == 0"
  97. style="display: block; margin: 0 auto"
  98. size="small"
  99. type="primary"
  100. @click="auditAbnormalShip(item.id)"
  101. >
  102. 审核通过
  103. </el-button>
  104. </div>
  105. <div :class="['s-line', index % 2 == 0 ? '' : 'top210px']"></div>
  106. <div class="point"></div>
  107. <div class="l-line" v-if="index + 1 != medias.length"></div>
  108. </div>
  109. </div>
  110. <el-dialog v-model="mediaModal" :title="modalTitle">
  111. <el-image
  112. v-if="modalType == 1"
  113. style="height: 60vh; display: flex"
  114. fit="contain"
  115. :src="currentUrl"
  116. :preview-src-list="modalPreview"
  117. ></el-image>
  118. <video
  119. v-else
  120. autoplay
  121. controls
  122. style="width: 100%; height: 60vh"
  123. :src="currentUrl"
  124. ></video>
  125. </el-dialog>
  126. </div>
  127. </template>
  128. <script setup>
  129. import { ref, h, reactive, toRefs, onMounted } from "vue";
  130. import { ElNotification, ElMessageBox, ElMessage } from "element-plus";
  131. import store from "../../store";
  132. import router from "../../router";
  133. import md5 from "md5";
  134. import api from "../../apis/fetch";
  135. import { useRoute } from "vue-router";
  136. import _ from "lodash";
  137. import { subTimeStr } from "../../utils/utils";
  138. const route = useRoute();
  139. let shipInfos = ref([{}]);
  140. let medias = ref([]);
  141. let mediaCoors = ref([]);
  142. let shipCoors = ref([]);
  143. let shipCoorsPolyline = ref([]);
  144. let mediaCoorsPolyline = ref([]);
  145. async function getShipDetail(shipCode) {
  146. let { data } = await api.getShipDetail({ shipCode });
  147. for (const i of data.result.shipCerts) {
  148. i.date = [i.startValidTime, i.endValidTime];
  149. for (let j of i.certs) {
  150. j.url = j.viewUrl;
  151. }
  152. }
  153. data.result.disabled = true;
  154. medias.value = data.result.medias;
  155. mediaCoors.value = data.result.mediaCoors;
  156. shipCoors.value = data.result.shipCoors;
  157. shipInfos.value = [data.result];
  158. initMap();
  159. getCrewList();
  160. }
  161. let crewCurrentPage = ref(1);
  162. let crewTotal = ref(0);
  163. let crewList = ref([]);
  164. async function getCrewList() {
  165. let { data } = await api.getCrewList({
  166. shipCode: route.query.shipCode,
  167. currentPage: crewCurrentPage.value,
  168. size: 10,
  169. });
  170. if (data.status === 0) {
  171. crewList.value = data.result;
  172. crewTotal.value = data.total;
  173. } else {
  174. crewList.value = [];
  175. crewTotal.value = 0;
  176. }
  177. }
  178. function crewPageChange(e) {
  179. crewCurrentPage.value = e;
  180. getCrewList();
  181. }
  182. function goToCrewDetail(item) {
  183. router.push({
  184. path: "/crewManage/crewDetail",
  185. query: {
  186. shipCode: route.query.shipCode,
  187. shipCrewId: item ? item.id : "",
  188. },
  189. });
  190. }
  191. let currentUrl = ref("");
  192. let mediaModal = ref(false);
  193. let modalType = ref(1);
  194. let modalTitle = ref();
  195. let modalPreview = ref([]);
  196. function openMediaModal(url, type, title) {
  197. modalPreview.value = [url];
  198. modalTitle.value = title;
  199. modalType.value = type;
  200. currentUrl.value = url;
  201. mediaModal.value = true;
  202. }
  203. const map = ref({});
  204. function initMap() {
  205. map.value = new AMap.Map("map-container", {
  206. zoom: 9, //级别
  207. zooms: [4, 9],
  208. center: [120.557894, 31.887504], //中心点坐标
  209. mapStyle: "amap://styles/f48d96805f5fa7f5aada657c5ee37017",
  210. zoomEnable: true,
  211. dragEnable: true,
  212. scrollWheel: false,
  213. });
  214. let toolBar = new AMap.ToolBar({
  215. position: {
  216. top: "40px",
  217. right: "40px",
  218. },
  219. });
  220. let hawkEye = new AMap.HawkEye({
  221. opened: false,
  222. });
  223. map.value.addControl(toolBar);
  224. map.value.addControl(hawkEye);
  225. let markers = [];
  226. if (shipCoors.value.length) {
  227. for (let i of shipCoors.value) {
  228. let content = `<div style='width:160px'>
  229. <img src='https://frontend-1255802371.cos.ap-shanghai.myqcloud.com/ship-green.png' style='display:block;width:20px;height:20px;margin:6px auto'/
  230. </div>`;
  231. let marker = new AMap.Marker({
  232. content,
  233. position: new AMap.LngLat(i.longitude, i.latitude),
  234. offset: new AMap.Pixel(-80, -20),
  235. });
  236. marker.setLabel({
  237. direction: "top",
  238. offset: new AMap.Pixel(0, 0), //设置文本标注偏移量
  239. content: `${i.createTime.substring(0, 10)}`, //设置文本标注内容
  240. style: "",
  241. });
  242. markers.push(marker);
  243. }
  244. }
  245. if (mediaCoors.value.length) {
  246. for (let i of mediaCoors.value) {
  247. let content = `<div style='width:160px'>
  248. <img src='https://frontend-1255802371.cos.ap-shanghai.myqcloud.com/ship-red.png' style='display:block;width:20px;height:20px;margin:6px auto'/
  249. </div>`;
  250. let marker = new AMap.Marker({
  251. content,
  252. position: new AMap.LngLat(i.longitude, i.latitude),
  253. offset: new AMap.Pixel(-80, -20),
  254. });
  255. marker.setLabel({
  256. direction: "top",
  257. offset: new AMap.Pixel(0, 0), //设置文本标注偏移量
  258. content: `${i.createTime.substring(0, 10)}`, //设置文本标注内容
  259. style: "",
  260. });
  261. markers.push(marker);
  262. }
  263. }
  264. let overlayGroups = new AMap.OverlayGroup(markers);
  265. map.value.add(overlayGroups);
  266. map.value.setFitView(markers, true, [200, 50, 50, 50], 18);
  267. // if (shipCoors.value.length) {
  268. // let path = [];
  269. // for (let i of shipCoors.value) {
  270. // path.push(new AMap.LngLat(Number(i.longitude), Number(i.latitude)));
  271. // }
  272. // shipCoorsPolyline.value = new AMap.Polyline({
  273. // path: path,
  274. // borderWeight: 2, // 线条宽度,默认为 1
  275. // strokeColor: "green", // 线条颜色
  276. // lineJoin: "round", // 折线拐点连接处样式
  277. // });
  278. // }
  279. // if (mediaCoors.value.length) {
  280. // let path = [];
  281. // for (let i of mediaCoors.value) {
  282. // path.push(new AMap.LngLat(Number(i.longitude), Number(i.latitude)));
  283. // }
  284. // mediaCoorsPolyline.value = new AMap.Polyline({
  285. // path: path,
  286. // borderWeight: 2, // 线条宽度,默认为 1
  287. // strokeColor: "red", // 线条颜色
  288. // lineJoin: "round", // 折线拐点连接处样式
  289. // });
  290. // }
  291. // map.value.add([shipCoorsPolyline.value, mediaCoorsPolyline.value]);
  292. // map.value.setFitView();
  293. }
  294. async function auditAbnormalShip(mediaId) {
  295. let { data } = await api.auditAbnormalShip({
  296. mediaId,
  297. shipCode: route.query.shipCode,
  298. });
  299. if (data.status == 0) {
  300. ElMessage({
  301. message: data.msg,
  302. type: "success",
  303. });
  304. getShipDetail(route.query.shipCode);
  305. } else {
  306. ElMessage({
  307. message: data.msg,
  308. type: "error",
  309. });
  310. }
  311. }
  312. onMounted(() => {
  313. getShipDetail(route.query.shipCode);
  314. });
  315. </script>
  316. <style scoped>
  317. .medias-content {
  318. width: 100%;
  319. height: 620px;
  320. background: #f7f7f7;
  321. border-radius: 2px;
  322. }
  323. .pic-container {
  324. width: 100%;
  325. height: 100%;
  326. box-sizing: border-box;
  327. display: flex;
  328. padding: 30px;
  329. overflow-x: scroll;
  330. overflow-y: hidden;
  331. white-space: nowrap;
  332. }
  333. .pic-main {
  334. position: relative;
  335. width: 120px;
  336. }
  337. .box {
  338. position: absolute;
  339. height: 260px;
  340. width: var(--box-width);
  341. border: 5px solid #dddddd;
  342. transition: all 0.5s;
  343. background: #fff;
  344. z-index: 10;
  345. }
  346. .point {
  347. position: relative;
  348. left: 93px;
  349. top: 268px;
  350. width: 16px;
  351. height: 16px;
  352. background-image: url(../../assets/blue-circle.png);
  353. }
  354. .s-line {
  355. position: absolute;
  356. left: 100px;
  357. top: 262px;
  358. height: 20px;
  359. border-left: 2px dashed;
  360. box-sizing: border-box;
  361. border-color: #ddd;
  362. }
  363. .l-line {
  364. position: relative;
  365. bottom: 30px;
  366. left: 111px;
  367. top: 259px;
  368. height: 3px;
  369. width: 100px;
  370. background-color: #dddddd;
  371. }
  372. .bottom-box {
  373. top: 300px;
  374. }
  375. .top210px {
  376. top: 280px;
  377. }
  378. .box:hover {
  379. transform: scale(1.1);
  380. }
  381. .card-note {
  382. height: 30px;
  383. font-size: 12px;
  384. font-family: PingFangSC-Regular, PingFang SC;
  385. font-weight: 400;
  386. color: #777777;
  387. padding: 10px 20px;
  388. }
  389. .medias-box {
  390. width: 100%;
  391. height: 140px;
  392. margin-top: 40px;
  393. }
  394. .checkbox-group {
  395. width: 180px;
  396. height: 50px;
  397. margin-top: 20px;
  398. }
  399. .el-checkbox {
  400. margin: 0;
  401. }
  402. .now-box {
  403. border: 5px solid #0094fe;
  404. }
  405. .now-l-line {
  406. background-color: #0094fe;
  407. }
  408. .now-s-line {
  409. border-color: #97caf6;
  410. }
  411. .now-point {
  412. filter: grayscale(1);
  413. }
  414. .info-line-text-table {
  415. width: 180px !important;
  416. }
  417. .upload-plus-icon {
  418. height: 15%;
  419. color: rgb(139, 147, 156);
  420. line-height: 100px;
  421. font-size: 40px;
  422. font-weight: 200;
  423. }
  424. .upload-text {
  425. height: 25%;
  426. color: rgb(139, 147, 156);
  427. }
  428. .info-gap {
  429. width: 40px;
  430. font-size: 14px;
  431. overflow: visible;
  432. white-space: nowrap;
  433. color: #006ebc;
  434. cursor: pointer;
  435. }
  436. .map-container {
  437. width: 98%;
  438. max-width: 1200px;
  439. height: 500px;
  440. }
  441. </style>