certsManage.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <template>
  2. <div class="full-container-p24">
  3. <div class="mb20" style="margin-left: 200px">
  4. <el-button-group class="mr30">
  5. <el-button
  6. :type="type == 2 ? 'primary' : ''"
  7. @click="(currentPage = 1), (type = 2), getCertList()"
  8. >
  9. {{ nextMonthStr }}
  10. </el-button>
  11. <el-button
  12. :type="type == 8 ? 'primary' : ''"
  13. @click="(currentPage = 1), (type = 8), getCertList()"
  14. >
  15. {{ nextNextMonthStr }}
  16. </el-button>
  17. <el-button
  18. :type="type == 9 ? 'primary' : ''"
  19. @click="(currentPage = 1), (type = 9), getCertList()"
  20. >
  21. {{ nextNextNextMonthStr }}
  22. </el-button>
  23. <el-button
  24. :type="type == 0 ? 'primary' : ''"
  25. @click="(currentPage = 1), (type = 0), getCertList()"
  26. >
  27. 已过期
  28. </el-button>
  29. <el-button
  30. :type="type == 3 ? 'primary' : ''"
  31. @click="(currentPage = 1), (type = 3), getCertList()"
  32. >
  33. 全部
  34. </el-button>
  35. </el-button-group>
  36. </div>
  37. <div class="df">
  38. <div class="btns mr20 mt50">
  39. <div v-for="(item, index) in certTypes" :key="item.id">
  40. <el-button
  41. :type="certType == item.type ? 'primary' : ''"
  42. @click="
  43. (currentPage = 1),
  44. (certType = item.type),
  45. (currentTypeIndex = index),
  46. (validType = 1);
  47. getCertList();
  48. "
  49. class="btn"
  50. >
  51. {{ item.typeName }}
  52. </el-button>
  53. </div>
  54. </div>
  55. <div>
  56. <el-select
  57. style="width: 240px"
  58. v-if="currentTypeIndex == 1"
  59. v-model="validType"
  60. @change="changeSelect"
  61. class="mb10 tac"
  62. placeholder="有效期类型"
  63. >
  64. <el-option
  65. v-for="item in certTypes[currentTypeIndex].validTypes"
  66. :key="item.type"
  67. :label="item.typeName"
  68. :value="item.type"
  69. />
  70. </el-select>
  71. <el-table border :data="tableData" stripe style="width: 900px">
  72. <el-table-column
  73. align="center"
  74. type="index"
  75. label="序号"
  76. width="80"
  77. />
  78. <el-table-column
  79. align="center"
  80. prop="shipname"
  81. label="船名"
  82. min-width="120"
  83. />
  84. <el-table-column
  85. align="center"
  86. prop="shipRegistryProvince"
  87. label="船籍"
  88. min-width="80"
  89. />
  90. <el-table-column
  91. v-if="certType == 6"
  92. align="center"
  93. prop="crewName"
  94. label="船员姓名"
  95. min-width="120"
  96. />
  97. <el-table-column
  98. v-if="certType != 6"
  99. align="center"
  100. prop="shipOwnerName"
  101. label="船员姓名"
  102. min-width="120"
  103. />
  104. <el-table-column
  105. v-if="certType != 6"
  106. align="center"
  107. prop="shipOwnerPhone"
  108. label="手机号"
  109. min-width="140"
  110. />
  111. <el-table-column
  112. align="center"
  113. prop="endValidTime"
  114. label="有效期"
  115. min-width="120"
  116. >
  117. <template v-slot="scope">
  118. {{ subTimeStr(scope.row.endValidTime) }}
  119. </template>
  120. </el-table-column>
  121. <el-table-column align="center" label="操作" width="160">
  122. <template #default="scope">
  123. <div v-if="certType == 6">
  124. <el-button
  125. type="primary"
  126. text
  127. @click="goToShipOwnerDetail(scope.row.id)"
  128. >
  129. 详情
  130. </el-button>
  131. <el-button type="primary" text @click="handleEdit(scope.row)">
  132. 更新
  133. </el-button>
  134. </div>
  135. <div v-else>
  136. <el-button
  137. type="primary"
  138. text
  139. @click="goToShipDetail(scope.row.shipCode)"
  140. >
  141. 详情
  142. </el-button>
  143. <el-button type="primary" text @click="handleEdit(scope.row)">
  144. 更新
  145. </el-button>
  146. </div>
  147. </template>
  148. </el-table-column>
  149. </el-table>
  150. <div class="df aic jcfe mt40 mr20">
  151. <el-pagination
  152. :current-page="currentPage"
  153. @current-change="pageChange"
  154. background
  155. layout="prev, pager, next"
  156. :total="total"
  157. />
  158. </div>
  159. </div>
  160. <el-dialog :title="dialogTypeName" v-model="ruleFormVisible" width="800">
  161. <el-form
  162. class="pt20"
  163. ref="ruleFormRef"
  164. :model="ruleForm"
  165. label-width="120px"
  166. >
  167. <el-form-item :label="ruleForm.typeName" v-if="dialogType != 6">
  168. <el-date-picker
  169. style="width: 140px; font-size: 13px"
  170. v-model="ruleForm.startValidTime"
  171. type="date"
  172. placeholder="有效期开始时间"
  173. value-format="YYYY/MM/DD"
  174. format="YYYY/MM/DD"
  175. />
  176. <div style="margin: 0 10px">-</div>
  177. <el-date-picker
  178. style="width: 140px; font-size: 13px"
  179. v-model="ruleForm.endValidTime"
  180. type="date"
  181. placeholder="有效期结束时间"
  182. value-format="YYYY/MM/DD"
  183. format="YYYY/MM/DD"
  184. />
  185. </el-form-item>
  186. <div v-if="dialogType == 6">
  187. <el-row :gutter="20">
  188. <el-col :span="10">
  189. <el-form-item label="船员姓名">
  190. {{ ruleForm.fullName }}
  191. </el-form-item>
  192. </el-col>
  193. </el-row>
  194. <el-row :gutter="20">
  195. <el-col :span="10">
  196. <el-form-item label="船员性别" prop="gender">
  197. <el-select
  198. v-model="ruleForm.gender"
  199. placeholder="请选择"
  200. class="w300"
  201. >
  202. <el-option label="男" :value="1"></el-option>
  203. <el-option label="女" :value="2"></el-option>
  204. </el-select>
  205. </el-form-item>
  206. </el-col>
  207. <el-col :span="10">
  208. <el-form-item label="证书编号" prop="certNo">
  209. <el-input
  210. v-model="ruleForm.certNo"
  211. placeholder="请输入"
  212. class="w300"
  213. ></el-input>
  214. </el-form-item>
  215. </el-col>
  216. </el-row>
  217. <el-row :gutter="20">
  218. <el-col :span="10">
  219. <el-form-item label="专业类别" prop="majorId">
  220. <el-select
  221. v-model="ruleForm.majorId"
  222. placeholder="请选择专业类别"
  223. class="w300"
  224. @change="handleMajorChange($event, 1)"
  225. >
  226. <el-option
  227. v-for="item in certTypeOptions"
  228. :key="item.key"
  229. :label="item.value"
  230. :value="item.key"
  231. ></el-option>
  232. </el-select>
  233. </el-form-item>
  234. </el-col>
  235. <el-col :span="10">
  236. <el-form-item label="证书类别" prop="categoryId">
  237. <el-select
  238. v-model="ruleForm.categoryId"
  239. placeholder="请选择证书类别"
  240. class="w300"
  241. @change="handleCategoryChange($event, 1)"
  242. >
  243. <el-option
  244. v-for="item in categoryOptions"
  245. :key="item.key"
  246. :label="item.value"
  247. :value="item.key"
  248. ></el-option>
  249. </el-select>
  250. </el-form-item>
  251. </el-col>
  252. </el-row>
  253. <el-row :gutter="20">
  254. <el-col :span="10">
  255. <el-form-item label="职务类别" prop="postId">
  256. <el-select
  257. v-model="ruleForm.postId"
  258. placeholder="请选择职务类别"
  259. class="w300"
  260. @change="handlePostChange"
  261. >
  262. <el-option
  263. v-for="item in postOptions"
  264. :key="item.key"
  265. :label="item.value"
  266. :value="item.key"
  267. ></el-option>
  268. </el-select>
  269. </el-form-item>
  270. </el-col>
  271. </el-row>
  272. <el-row :gutter="20">
  273. <el-col :span="10">
  274. <el-form-item label="发证日期" prop="issuerAt">
  275. <el-date-picker
  276. v-model="ruleForm.issuerAt"
  277. type="date"
  278. placeholder="选择日期"
  279. format="YYYY-MM-DD"
  280. />
  281. </el-form-item>
  282. </el-col>
  283. <el-col :span="10">
  284. <el-form-item label="截止日期" prop="expiryAt">
  285. <el-date-picker
  286. v-model="ruleForm.expiryAt"
  287. type="date"
  288. placeholder="选择日期"
  289. format="YYYY-MM-DD"
  290. class="w300"
  291. />
  292. </el-form-item>
  293. </el-col>
  294. </el-row>
  295. <el-row :gutter="20">
  296. <el-col :span="10">
  297. <el-form-item label="签发机构" prop="issuerAuthority">
  298. <el-input
  299. v-model="ruleForm.issuerAuthority"
  300. placeholder="请输入"
  301. class="w300"
  302. ></el-input>
  303. </el-form-item>
  304. </el-col>
  305. <el-col :span="16">
  306. <el-form-item label="适用限制">
  307. <el-input
  308. type="textarea"
  309. v-model="ruleForm.applicableRestrictions"
  310. :rows="4"
  311. placeholder="请输入"
  312. ></el-input>
  313. </el-form-item>
  314. </el-col>
  315. </el-row>
  316. </div>
  317. <el-form-item label="证书图片">
  318. <div class="df">
  319. <Uploader
  320. class="pb20"
  321. :uploaderId="'certsId' + 'country'"
  322. :params="dialogType === 6 ? shipOwnerParams : shipParams"
  323. :actionUrl="
  324. dialogType === 6
  325. ? store.state.shipOwnerUpdateUrl
  326. : store.state.shipCertUpdateUrl
  327. "
  328. :fileList="dialogType === 6 ? certFiles : certs"
  329. @onUploadFileList="uploadSuccess($event)"
  330. @onRemoveFileList="removeSuccess($event)"
  331. ></Uploader>
  332. </div>
  333. </el-form-item>
  334. <el-form-item label="办理方式">
  335. <el-radio-group
  336. v-model="ruleForm.processMethod"
  337. @change="handleProcessMethodChange"
  338. >
  339. <el-radio :value="1">自办</el-radio>
  340. <el-radio :value="2">代办</el-radio>
  341. </el-radio-group>
  342. </el-form-item>
  343. <el-form-item
  344. label="证书代办机构"
  345. v-if="ruleForm.processMethod === 2"
  346. >
  347. <RemoteSelect
  348. class="w300"
  349. api="getCertServAuthSelect"
  350. v-model="ruleForm.certServAuthName"
  351. placeholder="请选择"
  352. @selectItem="selectCertServer($event)"
  353. ></RemoteSelect>
  354. </el-form-item>
  355. <el-form-item label="备注">
  356. <el-input
  357. class="w300"
  358. type="textarea"
  359. :rows="5"
  360. v-model="ruleForm.processRemark"
  361. placeholder="请填写"
  362. ></el-input>
  363. </el-form-item>
  364. </el-form>
  365. <div class="df aic jcc mt50 pb20">
  366. <el-button type="primary" @click="submit">提交</el-button>
  367. <el-button @click="ruleFormVisible = false">取消</el-button>
  368. </div>
  369. </el-dialog>
  370. </div>
  371. </div>
  372. </template>
  373. <script setup>
  374. import { ref, h, reactive, toRefs, onMounted } from "vue";
  375. import { ElNotification, ElMessageBox, ElMessage } from "element-plus";
  376. import store from "../../store";
  377. import router from "../../router";
  378. import md5 from "md5";
  379. import api from "../../apis/fetch";
  380. import { useRoute } from "vue-router";
  381. import _ from "lodash";
  382. import { subTimeStr } from "../../utils/utils";
  383. const route = useRoute();
  384. let type = ref(2);
  385. let certType = ref(1);
  386. let tableData = ref([]);
  387. let total = ref(0);
  388. let currentPage = ref(1);
  389. const monthNames = [
  390. "一月",
  391. "二月",
  392. "三月",
  393. "四月",
  394. "五月",
  395. "六月",
  396. "七月",
  397. "八月",
  398. "九月",
  399. "十月",
  400. "十一月",
  401. "十二月",
  402. ];
  403. function getMonthName(monthOffset) {
  404. const currentMonth = new Date().getMonth();
  405. const newMonth = (currentMonth + monthOffset) % 12;
  406. return monthNames[newMonth] + "到期";
  407. }
  408. const nextMonthStr = ref(getMonthName(1));
  409. const nextNextMonthStr = ref(getMonthName(2));
  410. const nextNextNextMonthStr = ref(getMonthName(3));
  411. async function getCertList() {
  412. const loading = ElLoading.service({
  413. lock: true,
  414. text: "正在获取数据...",
  415. background: "rgba(0, 0, 0, 0.7)",
  416. });
  417. let { data } = await api.getCertList({
  418. type: type.value,
  419. certType: certType.value,
  420. validType: validType.value,
  421. currentPage: currentPage.value,
  422. size: 10,
  423. });
  424. loading.close();
  425. if (data.status == 0) {
  426. total.value = data.total;
  427. tableData.value = data.result;
  428. } else {
  429. total.value = 0;
  430. tableData.value = [];
  431. }
  432. }
  433. function pageChange(e) {
  434. currentPage.value = e;
  435. getCertList();
  436. }
  437. function goToShipDetail(shipCode) {
  438. router.push({
  439. path: "/shipManage/shipDetail",
  440. query: {
  441. shipCode,
  442. },
  443. });
  444. }
  445. function goToShipOwnerDetail(shipOwnerId) {
  446. router.push({
  447. path: "/shipOwnerManage/shipOwnerDetail",
  448. query: {
  449. shipOwnerId,
  450. },
  451. });
  452. }
  453. let certTypes = ref([
  454. {
  455. validTypes: [],
  456. },
  457. ]);
  458. async function getCertListType() {
  459. let { data } = await api.getCertListType({});
  460. certTypes.value = data.result.filter((item) => {
  461. return item.typeName != "船舶保险";
  462. });
  463. }
  464. let currentTypeIndex = ref(0);
  465. let validType = ref(1);
  466. function changeSelect(e) {
  467. validType.value = e;
  468. getCertList();
  469. }
  470. const ruleFormVisible = ref(false);
  471. const ruleFormRef = ref(null);
  472. const certTypeOptions = ref([]); // 专业类别选项
  473. const categoryOptions = ref([]); // 证书类别选项
  474. const postOptions = ref([]); // 职务类别选项
  475. const certTypeData = ref([]); // 存储完整的证书类型数据
  476. const ruleForm = ref({
  477. gender: "",
  478. certNo: "",
  479. });
  480. const certs = ref([]);
  481. const certFiles = ref([]);
  482. const dialogType = ref(0);
  483. const dialogTypeName = ref("");
  484. const shipParams = ref({});
  485. const shipOwnerParams = ref({ type: 1 });
  486. async function handleEdit(row) {
  487. const postData = {
  488. certType: certType.value,
  489. };
  490. if (certType.value == 6) {
  491. postData["shipOwnerId"] = row.id;
  492. ruleForm.value.shipOwnerId = row.id;
  493. shipOwnerParams.value.shipOwnerId = row.id;
  494. } else {
  495. postData["certValidityPeriodId"] = row.id;
  496. ruleForm.value.certValidityPeriodId = row.id;
  497. shipParams.value.shipCode = row.shipCode;
  498. }
  499. ruleFormVisible.value = true;
  500. let { data } = await api.getCertProcessDetail(postData);
  501. dialogType.value = data.result.type;
  502. dialogTypeName.value = data.result.typeName;
  503. ruleForm.value.processMethod = 1;
  504. ruleForm.value.certServAuthId = "";
  505. ruleForm.value.certServAuthName = "";
  506. ruleForm.value.processRemark = "";
  507. if (data.result.type === 6) {
  508. Object.assign(ruleForm.value, data.result.certDetail);
  509. certFiles.value = data.result.certFiles;
  510. if (ruleForm.value.majorId) {
  511. handleMajorChange(ruleForm.value.majorId);
  512. if (ruleForm.value.categoryId) {
  513. handleCategoryChange(ruleForm.value.categoryId);
  514. }
  515. }
  516. } else {
  517. Object.assign(ruleForm.value, data.result.certValids[0]);
  518. certs.value = data.result.certs;
  519. shipParams.value.type = data.result.type;
  520. }
  521. }
  522. function uploadSuccess({ response, file, list }, index) {
  523. if (response.status == 0) {
  524. let { key, fileKey, viewUrl, downloadUrl, id } = response.result;
  525. let data = {
  526. fileKey: fileKey || key,
  527. viewUrl,
  528. downloadUrl,
  529. id,
  530. url: viewUrl,
  531. };
  532. if (dialogType.value === 6) {
  533. certFiles.value.push(data);
  534. } else {
  535. certs.value.push(data);
  536. }
  537. }
  538. }
  539. async function removeSuccess({ file, fileIndex }) {
  540. let { data } = await api[
  541. dialogType.value === 6 ? "deleteShipOwnerCert" : "deleteShipCert"
  542. ]({ [dialogType.value === 6 ? "docId" : "shipCertId"]: file.id });
  543. if (data.status == 0) {
  544. ElMessage({
  545. message: "删除成功!",
  546. type: "success",
  547. });
  548. if (dialogType.value === 6) {
  549. certFiles.value.splice(fileIndex, 1);
  550. } else {
  551. certs.value.splice(fileIndex, 1);
  552. }
  553. }
  554. }
  555. function handleProcessMethodChange(value) {
  556. if (value === 1) {
  557. // 自办时清空代办机构
  558. ruleForm.value.certServAuthId = "";
  559. ruleForm.value.certServAuthName = "";
  560. }
  561. }
  562. const handleMajorChange = (majorId, isChange) => {
  563. if (isChange) {
  564. ruleForm.value.categoryId = "";
  565. ruleForm.value.postId = "";
  566. }
  567. const selectedMajor = certTypeData.value.find((item) => item.key === majorId);
  568. if (selectedMajor && selectedMajor.children) {
  569. categoryOptions.value = selectedMajor.children.map((item) => ({
  570. key: item.key,
  571. value: item.value,
  572. }));
  573. } else {
  574. categoryOptions.value = [];
  575. }
  576. postOptions.value = [];
  577. };
  578. const handleCategoryChange = (categoryId, isChange) => {
  579. if (isChange) {
  580. ruleForm.value.postId = "";
  581. }
  582. const selectedMajor = certTypeData.value.find(
  583. (item) => item.key === ruleForm.value.majorId
  584. );
  585. if (selectedMajor && selectedMajor.children) {
  586. const selectedCategory = selectedMajor.children.find(
  587. (item) => item.key === categoryId
  588. );
  589. if (selectedCategory && selectedCategory.children) {
  590. postOptions.value = selectedCategory.children.map((item) => ({
  591. key: item.key,
  592. value: item.value,
  593. }));
  594. } else {
  595. postOptions.value = [];
  596. }
  597. } else {
  598. postOptions.value = [];
  599. }
  600. };
  601. function handlePostChange() {}
  602. const getCertTypeData = async () => {
  603. try {
  604. const { data } = await api.getShipOwnerCertTypeSelect();
  605. if (data.status === 0 && data.result) {
  606. certTypeData.value = data.result;
  607. certTypeOptions.value = data.result.map((item) => ({
  608. key: item.key,
  609. value: item.value,
  610. }));
  611. } else {
  612. ElMessage.error(data.msg || "获取证书类型数据失败");
  613. }
  614. } catch (error) {
  615. console.error("获取证书类型数据出错:", error);
  616. ElMessage.error("获取证书类型数据出错");
  617. }
  618. };
  619. function selectCertServer(e) {
  620. console.log(e);
  621. if (e && e.value) {
  622. ruleForm.value.certServAuthId = e.value;
  623. ruleForm.value.certServAuthName = e.key;
  624. } else {
  625. ruleForm.value.certServAuthId = "";
  626. ruleForm.value.certServAuthName = "";
  627. }
  628. }
  629. async function submit() {
  630. console.log(ruleForm.value);
  631. let { data } = await api[
  632. dialogType.value === 6 ? "saveShipOwnerProcess" : "saveShipProcess"
  633. ](ruleForm.value);
  634. }
  635. onMounted(() => {
  636. getCertList();
  637. getCertListType();
  638. getCertTypeData();
  639. });
  640. </script>
  641. <style scoped>
  642. .btn {
  643. height: 50px;
  644. width: 180px;
  645. border-radius: 0;
  646. }
  647. .w300 {
  648. width: 300px !important;
  649. }
  650. </style>