certsManage.vue 20 KB

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