checkFireSafetyExamine.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <template>
  2. <el-card class="pl30 pt20 mt30" style="width: 1000px">
  3. <div class="df jcsb" v-if="templateDetail.id">
  4. <div>
  5. <div class="df aic jcsb mb10">
  6. <div class="df aic">
  7. <div class="c6 mr30">消防安检名称:</div>
  8. <div class="c6 mr30">{{ templateDetail.securityCheckName }}</div>
  9. </div>
  10. </div>
  11. <div class="df aic mb10">
  12. <div class="c6 mr30">消防安检目标:</div>
  13. <div class="c6 mr30">{{ templateDetail.securityCheckTarget }}</div>
  14. </div>
  15. <div class="df aic">
  16. <div class="c6 mr30">消防安检重点:</div>
  17. <div class="c6 mr30">{{ templateDetail.securityCheckFocus }}</div>
  18. </div>
  19. </div>
  20. <div>
  21. <div class="c6 mr30 mb10">
  22. <span class="mr10">总分:</span>
  23. {{ templateDetail.totalScore }}
  24. <span class="ml30 mr10">实际得分:</span>
  25. {{ templateDetail.actualScore }}
  26. </div>
  27. <div class="c6 mr30">
  28. <span class="mr20">通过项目:</span>
  29. {{ templateDetail.finishCheckItem }} /
  30. {{ templateDetail.totalCheckItem }}
  31. </div>
  32. </div>
  33. </div>
  34. <el-divider />
  35. <div>
  36. <div class="c6 mb20">船舶信息</div>
  37. <div class="df aic mb20">
  38. <div class="ship-label">船名</div>
  39. <div class="ship-text">{{ shipDetail.shipname }}</div>
  40. <div class="ship-label">船东姓名</div>
  41. <div class="ship-text">{{ shipDetail.shipOwnerName }}</div>
  42. <div class="ship-label">船东手机号</div>
  43. <div class="ship-text">{{ shipDetail.shipOwnerPhone }}</div>
  44. <div class="ship-label">MMSI</div>
  45. <div class="ship-text">{{ shipDetail.mmsi }}</div>
  46. <div class="ship-label">IMO</div>
  47. <div class="ship-text">{{ shipDetail.imo }}</div>
  48. </div>
  49. <div class="df aic mb20">
  50. <div class="ship-label">船龄</div>
  51. <div class="ship-text">
  52. {{ shipDetail.age }}
  53. <span class="unit">年</span>
  54. </div>
  55. <div class="ship-label">船长</div>
  56. <div class="ship-text">
  57. {{ shipDetail.length }}
  58. <span class="unit">米</span>
  59. </div>
  60. <div class="ship-label">船宽</div>
  61. <div class="ship-text">
  62. {{ shipDetail.breadth }}
  63. <span class="unit">米</span>
  64. </div>
  65. <div class="ship-label">吨位</div>
  66. <div class="ship-text">
  67. {{ shipDetail.loadTons }}
  68. <span class="unit">吨</span>
  69. </div>
  70. <div class="ship-label">满载吃水</div>
  71. <div class="ship-text">
  72. {{ shipDetail.draught }}
  73. <span class="unit">米</span>
  74. </div>
  75. </div>
  76. </div>
  77. <el-divider />
  78. <div v-if="status != 1">
  79. <div class="df aic">
  80. <div class="mr20">选择安全检查员(可多选):</div>
  81. <el-select
  82. class="mr20"
  83. v-model="checkedUsers"
  84. value-key="key"
  85. multiple
  86. style="width: 300px"
  87. placeholder="请选择检查员"
  88. clearable
  89. >
  90. <el-option
  91. v-for="item in checkUsers"
  92. :key="item.key"
  93. :label="item.value"
  94. :value="item"
  95. />
  96. </el-select>
  97. <el-button type="primary" @click="saveFireSafetyCheckUser()">
  98. 保存
  99. </el-button>
  100. </div>
  101. <el-divider />
  102. </div>
  103. <div :id="mapId" class="map-container"></div>
  104. <el-divider />
  105. <div class="mt40 fs16 c6">安检项目</div>
  106. <div
  107. class="mb20 mt30 fs14 c6"
  108. v-for="item in templateDetail.fileInspectionItems"
  109. >
  110. <div class="df aic">
  111. <div class="ml20 mr10 item-title">检查项目名称:</div>
  112. <div class="mr30 item-text">{{ item.checkItemName }}</div>
  113. <div class="mr10 item-title">检查项目类型:</div>
  114. <div class="mr30 item-text">{{ item.checkItemTypeName }}</div>
  115. <div class="mr10 item-title">检查项目备注:</div>
  116. <div class="mr10 item-text">{{ item.checkItemRemark }}</div>
  117. </div>
  118. <div class="df aic">
  119. <div class="mt10 ml20">
  120. <div v-if="item.viewUrl">
  121. <el-image
  122. v-if="isImage(item.fileKey)"
  123. style="width: 200px; height: 200px"
  124. :src="item.viewUrl"
  125. :preview-src-list="[item.viewUrl]"
  126. fit="cover"
  127. ></el-image>
  128. <div class="video-box" v-else>
  129. <img
  130. class="play-icon"
  131. style="width: 50px; height: 50px"
  132. src="../../assets/play.png"
  133. alt=""
  134. @click="showModal(item)"
  135. />
  136. <video
  137. style="width: 200px; height: 200px"
  138. :src="item.viewUrl"
  139. ></video>
  140. </div>
  141. </div>
  142. <el-empty
  143. v-else
  144. style="width: 220px"
  145. class="p10"
  146. :image-size="100"
  147. description="暂无图片"
  148. />
  149. <div
  150. class="df aic jcsa mt10"
  151. style="width: 200px"
  152. v-if="item.auditStatus == 0"
  153. >
  154. <el-button
  155. @click="checkFireSafetyItem(item.id, 1)"
  156. class="ml10"
  157. size="small"
  158. type="primary"
  159. :disabled="!item.viewUrl"
  160. >
  161. 通过
  162. </el-button>
  163. <el-button
  164. @click="checkFireSafetyItem(item.id, 2)"
  165. size="small"
  166. type="danger"
  167. :disabled="!item.viewUrl"
  168. >
  169. 不通过
  170. </el-button>
  171. </div>
  172. <div v-else class="df aic jcsa mt10" style="width: 200px">
  173. <el-tag
  174. class="ml-2"
  175. :type="item.auditStatus == 1 ? 'success' : 'danger'"
  176. >
  177. {{ item.auditStatus == 1 ? "已通过" : "未通过" }}
  178. </el-tag>
  179. </div>
  180. </div>
  181. <div class="mt10 ml20" v-for="item1 in item.histories">
  182. <div v-if="item1.viewUrl">
  183. <el-image
  184. v-if="isImage(item1.fileKey)"
  185. style="width: 200px; height: 200px"
  186. :src="item1.viewUrl"
  187. :preview-src-list="[item1.viewUrl]"
  188. fit="cover"
  189. ></el-image>
  190. <div class="video-box" v-else>
  191. <img
  192. class="play-icon"
  193. style="width: 50px; height: 50px"
  194. src="../../assets/play.png"
  195. alt=""
  196. @click="showModal(item1)"
  197. />
  198. <video
  199. style="width: 200px; height: 200px"
  200. :src="item1.viewUrl"
  201. ></video>
  202. </div>
  203. </div>
  204. <el-empty
  205. v-else
  206. style="width: 220px"
  207. class="p10"
  208. :image-size="100"
  209. description="暂无图片"
  210. />
  211. <div class="df aic jcsa mt10" style="width: 200px">
  212. <el-tag
  213. class="ml-2"
  214. :type="item1.auditStatus == 1 ? 'success' : 'danger'"
  215. >
  216. {{ item1.auditStatus == 1 ? "已通过" : "未通过" }}
  217. </el-tag>
  218. </div>
  219. </div>
  220. </div>
  221. <el-divider />
  222. </div>
  223. <el-dialog
  224. v-model="isModalVisable"
  225. destroy-on-close
  226. title="视频查看"
  227. style="width: 400px"
  228. >
  229. <div class="video-mark-box">
  230. <video
  231. style="width: 100%; height: auto"
  232. autoplay
  233. controls
  234. :src="currentItem.downloadUrl"
  235. ></video>
  236. <div class="video-mark">
  237. <div class="mb10">{{ shipDetail.shipname }}</div>
  238. <div class="mb10">{{ currentItem.uploadTime }}</div>
  239. <div class="mb10">
  240. {{ currentItem.province }}·{{ currentItem.city }}·{{
  241. currentItem.district
  242. }}
  243. </div>
  244. <div class="mb10">
  245. {{ currentItem.week }} {{ currentItem.weather }}
  246. {{ currentItem.temperature }} ℃
  247. </div>
  248. <div class="mb10">真实 实时 精准</div>
  249. </div>
  250. </div>
  251. </el-dialog>
  252. <el-form
  253. ref="ruleFormRef"
  254. :model="ruleForm"
  255. :rules="rules"
  256. label-width="auto"
  257. class="demo-ruleForm"
  258. status-icon
  259. >
  260. <!-- <el-form-item label="Activity time">
  261. <el-col :span="11">
  262. <el-form-item prop="date1">
  263. <el-date-picker
  264. v-model="ruleForm.date1"
  265. type="date"
  266. label="Pick a date"
  267. placeholder="Pick a date"
  268. style="width: 100%"
  269. />
  270. </el-form-item>
  271. </el-col>
  272. <el-col class="text-center" :span="2">
  273. <span class="text-gray-500">-</span>
  274. </el-col>
  275. <el-col :span="11">
  276. <el-form-item prop="date2">
  277. <el-time-picker
  278. v-model="ruleForm.date2"
  279. label="Pick a time"
  280. placeholder="Pick a time"
  281. style="width: 100%"
  282. />
  283. </el-form-item>
  284. </el-col>
  285. </el-form-item> -->
  286. <el-form-item label="问题整改" prop="problems">
  287. <el-input
  288. v-model="ruleForm.problems"
  289. type="textarea"
  290. :disabled="status == 1"
  291. />
  292. </el-form-item>
  293. <el-form-item label="整改意见" prop="rectificationOpinions">
  294. <el-input v-model="ruleForm.rectificationOpinions" type="textarea" />
  295. </el-form-item>
  296. <el-form-item>
  297. <div>
  298. <div class="df aic">
  299. <div class="mr10 item-title">安全检查员:</div>
  300. <div class="mr30">
  301. {{ fireInspectionInfo.promiseeShippingAccountNames }}
  302. </div>
  303. </div>
  304. <div class="df aic">
  305. <div class="mr10 item-title">承诺人:</div>
  306. <div class="mr30">
  307. {{ shipDetail.shipOwnerName }}
  308. {{
  309. fireInspectionInfo.promiseeShippingAccountNames
  310. ? `,${fireInspectionInfo.promiseeShippingAccountNames}`
  311. : ""
  312. }}
  313. </div>
  314. </div>
  315. <div class="df aic">
  316. <div class="mr10 item-title">承诺整改时间:</div>
  317. <div class="mr10 item-text">
  318. {{ fireInspectionInfo.rectificationTime || "即查即改" }}
  319. </div>
  320. </div>
  321. </div>
  322. <div class="df aic jcfe" style="width: 100%">
  323. <!-- <el-button @click="resetForm(ruleFormRef)">重置</el-button> -->
  324. <el-button
  325. type="primary"
  326. @click="saveFireSafetyCheckRectification(ruleFormRef)"
  327. >
  328. 保存整改意见
  329. </el-button>
  330. </div>
  331. </el-form-item>
  332. </el-form>
  333. <el-divider />
  334. </el-card>
  335. </template>
  336. <script setup>
  337. import { ref, h, reactive, toRefs, onMounted } from "vue";
  338. import { ElNotification, ElMessageBox, ElMessage } from "element-plus";
  339. import store from "../../store";
  340. import router from "../../router";
  341. import md5 from "md5";
  342. import api from "../../apis/fetch";
  343. import { useRoute } from "vue-router";
  344. import _ from "lodash";
  345. import { subTimeStr } from "../../utils/utils";
  346. import { Picture as IconPicture } from "@element-plus/icons-vue";
  347. const route = useRoute();
  348. let templateDetail = ref({
  349. items: [],
  350. });
  351. let shipDetail = ref({});
  352. const fireInspectionInfo = ref({});
  353. async function getFireSafetyCheckDetail(shipSecurityCheckId) {
  354. let { data } = await api.getFireSafetyCheckDetail({
  355. shipSecurityCheckId,
  356. });
  357. if (data.status == 0) {
  358. status.value = data.result.status;
  359. templateDetail.value = data.result;
  360. shipDetail.value = data.result.ship;
  361. coordinates.value = data.result.coordinates;
  362. fireInspectionInfo.value = data.result.fireInspectionInfo;
  363. ruleForm.value.problems = fireInspectionInfo.value.problems;
  364. ruleForm.value.rectificationOpinions =
  365. fireInspectionInfo.value.rectificationOpinions;
  366. }
  367. initMap();
  368. }
  369. async function checkFireSafetyItem(shipSecurityCheckItemId, auditStatus) {
  370. let shipSecurityCheckId = route.query.id;
  371. const loading = ElLoading.service({
  372. lock: true,
  373. text: "正在提交...",
  374. background: "rgba(0, 0, 0, 0.7)",
  375. });
  376. let { data } = await api.checkFireSafetyItem({
  377. shipSecurityCheckId,
  378. shipSecurityCheckItemId,
  379. auditStatus,
  380. });
  381. loading.close();
  382. if (data.status == 0) {
  383. ElNotification({
  384. title: "成功",
  385. message: data.msg,
  386. type: "success",
  387. duration: 1500,
  388. });
  389. getFireSafetyCheckDetail(shipSecurityCheckId);
  390. }
  391. }
  392. function isImage(url) {
  393. let imgArr = ["jpg", "jpeg", "png", "gif"];
  394. let lastIndex = url.lastIndexOf(".");
  395. return imgArr.indexOf(url.substring(lastIndex + 1, url.length)) != -1;
  396. }
  397. let isModalVisable = ref(false);
  398. let currentItem = ref({});
  399. function showModal(item) {
  400. currentItem.value = item;
  401. isModalVisable.value = true;
  402. }
  403. const weeks = ref([
  404. "星期一",
  405. "星期二",
  406. "星期三",
  407. "星期四",
  408. "星期五",
  409. "星期六",
  410. "星期日",
  411. ]);
  412. const checkUsers = ref([]);
  413. const checkedUsers = ref([]);
  414. async function getFireSafetyCheckUser() {
  415. let { data } = await api.getFireSafetyCheckUser({});
  416. checkUsers.value = data.result;
  417. }
  418. async function saveFireSafetyCheckUser() {
  419. if (checkedUsers.value.length === 0) {
  420. ElMessage({
  421. message: "请至少选择一名人员",
  422. type: "warning",
  423. });
  424. return;
  425. }
  426. let checkedUserIds = checkedUsers.value.map((item) => item.key);
  427. let checkedUserNames = checkedUsers.value.map((item) => item.value);
  428. const loading = ElLoading.service({
  429. lock: true,
  430. text: "正在保存...",
  431. background: "rgba(0, 0, 0, 0.7)",
  432. });
  433. let { data } = await api.saveFireSafetyCheckUser({
  434. shipSecurityCheckId: route.query.id,
  435. shippingAccountIds: checkedUserIds.join(","),
  436. shippingAccountNames: checkedUserNames.join(","),
  437. });
  438. loading.close();
  439. if (data.status == 0) {
  440. ElNotification({
  441. title: "成功",
  442. message: data.msg,
  443. type: "success",
  444. duration: 1500,
  445. });
  446. getFireSafetyCheckDetail(route.query.id);
  447. } else {
  448. ElNotification({
  449. title: "失败",
  450. message: data.msg,
  451. type: "error",
  452. duration: 1500,
  453. });
  454. }
  455. console.log(data);
  456. }
  457. let map = ref({});
  458. let mapId = ref(`map${route.query.id}`);
  459. const coordinates = ref([]);
  460. function initMap() {
  461. let c;
  462. let longitude = 121.524761;
  463. let latitude = 31.228721;
  464. // if (medias.value.length) {
  465. // c = Math.floor(medias.value.length / 2);
  466. // longitude = medias.value[c].longitude;
  467. // latitude = medias.value[c].latitude;
  468. // }
  469. map.value = new AMap.Map(mapId.value, {
  470. zoom: 16, //级别
  471. center: [longitude, latitude], //中心点坐标
  472. mapStyle: "amap://styles/f48d96805f5fa7f5aada657c5ee37017",
  473. zoomEnable: false,
  474. dragEnable: false,
  475. });
  476. let toolBar = new AMap.ToolBar({
  477. position: {
  478. top: "40px",
  479. right: "40px",
  480. },
  481. });
  482. let hawkEye = new AMap.HawkEye({
  483. opened: false,
  484. });
  485. map.value.addControl(toolBar);
  486. map.value.addControl(hawkEye);
  487. let markers = [];
  488. for (let i of coordinates.value) {
  489. let content = `<div style='width:160px'>
  490. <img src='https://frontend-1255802371.cos.ap-shanghai.myqcloud.com/green-circle.png' style='display:block;width:20px;height:20px;margin:6px auto'/
  491. </div>`;
  492. let marker = new AMap.Marker({
  493. content,
  494. zIndex: 5,
  495. position: new AMap.LngLat(i.longitude, i.latitude),
  496. offset: new AMap.Pixel(-75, i.audit == 1 ? -195 : -30),
  497. });
  498. markers.push(marker);
  499. }
  500. let overlayGroups = new AMap.OverlayGroup(markers);
  501. map.value.on("complete", function () {
  502. let t = setTimeout(() => {
  503. map.value.add(overlayGroups);
  504. map.value.setFitView(markers, true, [200, 50, 0, 0], 18);
  505. clearTimeout(t);
  506. }, 2000);
  507. });
  508. }
  509. const ruleFormRef = ref(null);
  510. const ruleForm = ref({
  511. problems: "",
  512. rectificationOpinions: "",
  513. });
  514. const rules = ref({
  515. problems: [
  516. {
  517. required: true,
  518. message: "请填写问题",
  519. trigger: "blur",
  520. },
  521. ],
  522. rectificationOpinions: [
  523. {
  524. required: true,
  525. message: "请填写整改意见",
  526. trigger: "blur",
  527. },
  528. ],
  529. });
  530. const saveFireSafetyCheckRectification = async (formEl) => {
  531. if (!formEl) return;
  532. formEl.validate(async (valid, fields) => {
  533. if (valid) {
  534. const loading = ElLoading.service({
  535. lock: true,
  536. text: "正在保存...",
  537. background: "rgba(0, 0, 0, 0.7)",
  538. });
  539. let { data } = await api.saveFireSafetyCheckRectification({
  540. ...ruleForm.value,
  541. shipSecurityCheckId: route.query.id,
  542. });
  543. loading.close();
  544. if (data.status == 0) {
  545. ElNotification.success({
  546. title: "成功",
  547. duration: 1500,
  548. message: data.msg,
  549. });
  550. } else {
  551. ElNotification.error({
  552. title: "失败",
  553. duration: 2000,
  554. message: data.msg,
  555. });
  556. }
  557. getFireSafetyCheckDetail(route.query.id);
  558. } else {
  559. console.log("error submit!", fields);
  560. }
  561. });
  562. };
  563. const resetForm = (formEl) => {
  564. if (!formEl) return;
  565. formEl.resetFields();
  566. };
  567. const status = ref(0);
  568. onMounted(() => {
  569. getFireSafetyCheckDetail(route.query.id);
  570. getFireSafetyCheckUser();
  571. });
  572. </script>
  573. <style scoped>
  574. .ship-label {
  575. width: 80px;
  576. color: #666;
  577. font-size: 14px;
  578. text-align: right;
  579. margin-right: 10px;
  580. }
  581. .ship-text {
  582. width: 100px;
  583. color: #333;
  584. font-size: 14px;
  585. }
  586. .item-title {
  587. width: 100px;
  588. }
  589. .item-text {
  590. width: 120px;
  591. }
  592. .video-box {
  593. position: relative;
  594. width: 200px;
  595. height: 200px;
  596. }
  597. .play-icon {
  598. position: absolute;
  599. z-index: 100;
  600. top: calc(50% - 25px);
  601. left: calc(50% - 25px);
  602. transition: all 0.5s;
  603. }
  604. .play-icon:hover {
  605. transform: scale(1.2);
  606. }
  607. .video-mark-box {
  608. position: relative;
  609. }
  610. .video-mark {
  611. position: absolute;
  612. bottom: 70px;
  613. left: 15px;
  614. font-size: 16px;
  615. font-weight: 700;
  616. z-index: 100;
  617. color: #fff;
  618. }
  619. .map-container {
  620. width: 100%;
  621. height: 500px;
  622. }
  623. </style>