shipOwnerDetail.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <template>
  2. <div class="line-container-p24">
  3. <div
  4. class="df aic dib go-back ml8 pointer"
  5. @click="router.replace('/shipOwnerManage/shipOwnerList')"
  6. >
  7. <el-icon class="mr10"><ArrowLeftBold /></el-icon>
  8. <div>返回船员列表</div>
  9. </div>
  10. </div>
  11. <div class="container-title">基本信息</div>
  12. <div class="line-container-p24">
  13. <el-form
  14. :model="shipOwnerForm"
  15. :rules="rules"
  16. ref="shipOwnerFormRef"
  17. label-width="100px"
  18. label-position="left"
  19. >
  20. <el-row :gutter="20">
  21. <el-col :span="8">
  22. <el-form-item label="船员姓名" prop="userName" required>
  23. <el-input
  24. v-model="shipOwnerForm.userName"
  25. placeholder="请输入"
  26. :disabled="!!shipOwnerForm.shipOwnerId"
  27. ></el-input>
  28. </el-form-item>
  29. </el-col>
  30. <el-col :span="8">
  31. <el-form-item label="船员手机号" prop="userPhone" required>
  32. <el-input
  33. v-model="shipOwnerForm.userPhone"
  34. placeholder="请输入"
  35. :disabled="!!shipOwnerForm.shipOwnerId"
  36. @blur="handlePhoneBlur"
  37. ></el-input>
  38. </el-form-item>
  39. </el-col>
  40. </el-row>
  41. <el-row :gutter="20">
  42. <el-col :span="8">
  43. <el-form-item label="船员身份证号">
  44. <el-input
  45. v-model="shipOwnerForm.idcardNo"
  46. placeholder="请输入"
  47. :disabled="!!shipOwnerForm.shipOwnerId"
  48. ></el-input>
  49. </el-form-item>
  50. </el-col>
  51. </el-row>
  52. <el-row :gutter="20">
  53. <el-col :span="8">
  54. <el-form-item label="身份证人像面">
  55. <Uploader
  56. :action-url="
  57. shipOwnerForm.shipOwnerId
  58. ? store.state.shipOwnerUpdateUrl
  59. : store.state.shipOwnerCacheUrl
  60. "
  61. :file-list="idCardFrontFileList"
  62. :params="{ type: 8, shipOwnerId: shipOwnerForm.shipOwnerId }"
  63. @on-upload-file-list="handleIdCardFrontUpload"
  64. @on-remove-file-list="handleIdCardFrontRemove"
  65. :limit="1"
  66. upload-text="点击上传人像面"
  67. :disabled="!!shipOwnerForm.shipOwnerId"
  68. />
  69. </el-form-item>
  70. </el-col>
  71. <el-col :span="8">
  72. <el-form-item label="身份证国徽面">
  73. <Uploader
  74. :action-url="
  75. shipOwnerForm.shipOwnerId
  76. ? store.state.shipOwnerUpdateUrl
  77. : store.state.shipOwnerCacheUrl
  78. "
  79. :file-list="idCardBackFileList"
  80. :params="{ type: 9, shipOwnerId: shipOwnerForm.shipOwnerId }"
  81. @on-upload-file-list="handleIdCardBackUpload"
  82. @on-remove-file-list="handleIdCardBackRemove"
  83. :limit="1"
  84. upload-text="点击上传国徽面"
  85. :disabled="!!shipOwnerForm.shipOwnerId"
  86. />
  87. </el-form-item>
  88. </el-col>
  89. </el-row>
  90. </el-form>
  91. </div>
  92. <div class="container-title">船员证书信息</div>
  93. <div class="line-container-p24">
  94. <el-form
  95. :model="shipOwnerForm.cerificate"
  96. :rules="certificateRules"
  97. ref="certificateFormRef"
  98. label-width="100px"
  99. label-position="left"
  100. >
  101. <el-row :gutter="20">
  102. <el-col :span="8">
  103. <el-form-item label="性别">
  104. <el-select
  105. v-model="shipOwnerForm.cerificate.gender"
  106. placeholder="请选择"
  107. >
  108. <el-option label="男" :value="1"></el-option>
  109. <el-option label="女" :value="2"></el-option>
  110. </el-select>
  111. </el-form-item>
  112. </el-col>
  113. <el-col :span="8">
  114. <el-form-item label="证书编号" prop="certNo" required>
  115. <el-input
  116. v-model="shipOwnerForm.cerificate.certNo"
  117. placeholder="请输入"
  118. ></el-input>
  119. </el-form-item>
  120. </el-col>
  121. </el-row>
  122. <el-row :gutter="20">
  123. <el-col :span="8">
  124. <el-form-item label="证书类型">
  125. <el-input
  126. v-model="shipOwnerForm.cerificate.certType"
  127. placeholder="请输入"
  128. ></el-input>
  129. </el-form-item>
  130. </el-col>
  131. <el-col :span="8">
  132. <el-form-item label="职务资格" prop="postRole" required>
  133. <el-select
  134. v-model="shipOwnerForm.cerificate.postRole"
  135. placeholder="请选择"
  136. >
  137. <el-option label="大副" value="大副"></el-option>
  138. <el-option label="二副" value="二副"></el-option>
  139. <el-option label="三副" value="三副"></el-option>
  140. <el-option label="船长" value="船长"></el-option>
  141. </el-select>
  142. </el-form-item>
  143. </el-col>
  144. </el-row>
  145. <el-row :gutter="20">
  146. <el-col :span="8">
  147. <el-form-item label="发证日期" prop="issuerAt" required>
  148. <el-date-picker
  149. v-model="shipOwnerForm.cerificate.issuerAt"
  150. type="date"
  151. placeholder="选择日期"
  152. format="YYYY/MM/DD"
  153. />
  154. </el-form-item>
  155. </el-col>
  156. <el-col :span="8">
  157. <el-form-item label="截止日期" prop="expiryAt" required>
  158. <el-date-picker
  159. v-model="shipOwnerForm.cerificate.expiryAt"
  160. type="date"
  161. placeholder="选择日期"
  162. format="YYYY/MM/DD"
  163. />
  164. </el-form-item>
  165. </el-col>
  166. </el-row>
  167. <el-row :gutter="20">
  168. <el-col :span="8">
  169. <el-form-item label="签发机构" prop="issuerAuthority" required>
  170. <el-input
  171. v-model="shipOwnerForm.cerificate.issuerAuthority"
  172. placeholder="请输入"
  173. ></el-input>
  174. </el-form-item>
  175. </el-col>
  176. <el-col :span="16">
  177. <el-form-item label="适用限制">
  178. <el-input
  179. type="textarea"
  180. v-model="shipOwnerForm.cerificate.applicableRestrictions"
  181. :rows="4"
  182. placeholder="请输入"
  183. ></el-input>
  184. </el-form-item>
  185. </el-col>
  186. </el-row>
  187. <el-row>
  188. <el-col :span="8">
  189. <el-form-item label="证书照片" prop="certFile" required>
  190. <Uploader
  191. :action-url="
  192. shipOwnerForm.shipOwnerId
  193. ? store.state.shipOwnerUpdateUrl
  194. : store.state.shipOwnerCacheUrl
  195. "
  196. :file-list="certFileList"
  197. :params="{ type: 1, shipOwnerId: shipOwnerForm.shipOwnerId }"
  198. @on-upload-file-list="handleCertUpload"
  199. @on-remove-file-list="handleCertRemove"
  200. :limit="1"
  201. upload-text="点击上传证书"
  202. />
  203. </el-form-item>
  204. </el-col>
  205. </el-row>
  206. </el-form>
  207. </div>
  208. <div class="container-title">其他文件</div>
  209. <div class="line-container-p24">
  210. <div class="mb20">健康证明</div>
  211. <Uploader
  212. :action-url="
  213. shipOwnerForm.shipOwnerId
  214. ? store.state.shipOwnerUpdateUrl
  215. : store.state.shipOwnerCacheUrl
  216. "
  217. :file-list="healthFileList"
  218. :params="{ type: 2, shipOwnerId: shipOwnerForm.shipOwnerId }"
  219. @on-upload-file-list="handleHealthUpload"
  220. @on-remove-file-list="handleHealthRemove"
  221. :limit="2"
  222. upload-text="点击上传"
  223. />
  224. <div class="mb20 mt30">服务簿</div>
  225. <Uploader
  226. :action-url="
  227. shipOwnerForm.shipOwnerId
  228. ? store.state.shipOwnerUpdateUrl
  229. : store.state.shipOwnerCacheUrl
  230. "
  231. :file-list="serviceFileList"
  232. :params="{ type: 3, shipOwnerId: shipOwnerForm.shipOwnerId }"
  233. @on-upload-file-list="handleServiceUpload"
  234. @on-remove-file-list="handleServiceRemove"
  235. :limit="2"
  236. upload-text="点击上传"
  237. />
  238. </div>
  239. <div class="df jcc mt30 mb30">
  240. <el-button @click="router.replace('/shipOwnerManage/shipOwnerList')">
  241. 取消
  242. </el-button>
  243. <el-button type="primary" @click="saveShipOwner">保存</el-button>
  244. </div>
  245. </template>
  246. <script setup>
  247. import { ref, h, reactive, toRefs, onMounted, computed } from "vue";
  248. import { ElNotification, ElMessageBox, ElMessage } from "element-plus";
  249. import store from "../../store";
  250. import router from "../../router";
  251. import md5 from "md5";
  252. import api from "../../apis/fetch";
  253. import { useRoute } from "vue-router";
  254. import _ from "lodash";
  255. const route = useRoute();
  256. const uploadConfig = ref({});
  257. const shipOwnerFormRef = ref(null);
  258. const certificateFormRef = ref(null);
  259. // 表单验证规则
  260. const rules = {
  261. userName: [{ required: true, message: "请输入船员姓名", trigger: "blur" }],
  262. userPhone: [
  263. { required: true, message: "请输入船员手机号", trigger: "blur" },
  264. {
  265. pattern: /^1[3-9]\d{9}$/,
  266. message: "请输入正确的手机号格式",
  267. trigger: "blur",
  268. },
  269. ],
  270. };
  271. // 验证证书照片
  272. const validateCertFile = (rule, value, callback) => {
  273. if (!certFileList.value || certFileList.value.length === 0) {
  274. callback(new Error("请上传证书照片"));
  275. } else {
  276. callback();
  277. }
  278. };
  279. // 验证截止日期
  280. const validateExpiryDate = (rule, value, callback) => {
  281. if (!value) {
  282. callback(new Error("请选择截止日期"));
  283. } else {
  284. if (shipOwnerForm.value.cerificate.issuerAt) {
  285. // 确保截止日期晚于发证日期
  286. if (
  287. new Date(value) <= new Date(shipOwnerForm.value.cerificate.issuerAt)
  288. ) {
  289. callback(new Error("截止日期必须晚于发证日期"));
  290. }
  291. }
  292. callback();
  293. }
  294. };
  295. // 验证发证日期
  296. const validateIssuerDate = (rule, value, callback) => {
  297. if (!value) {
  298. callback(new Error("请选择发证日期"));
  299. } else {
  300. if (shipOwnerForm.value.cerificate.expiryAt) {
  301. // 如果已选择截止日期,确保发证日期早于截止日期
  302. if (
  303. new Date(value) >= new Date(shipOwnerForm.value.cerificate.expiryAt)
  304. ) {
  305. callback(new Error("发证日期必须早于截止日期"));
  306. }
  307. }
  308. callback();
  309. }
  310. };
  311. // 证书表单验证规则
  312. const certificateRules = {
  313. certNo: [{ required: true, message: "请输入证书编号", trigger: "blur" }],
  314. postRole: [{ required: true, message: "请选择职务资格", trigger: "change" }],
  315. issuerAt: [
  316. { required: true, message: "请选择发证日期", trigger: "change" },
  317. { validator: validateIssuerDate, trigger: "change" },
  318. ],
  319. expiryAt: [
  320. { required: true, message: "请选择截止日期", trigger: "change" },
  321. { validator: validateExpiryDate, trigger: "change" },
  322. ],
  323. issuerAuthority: [
  324. { required: true, message: "请输入签发机构", trigger: "blur" },
  325. ],
  326. certFile: [{ validator: validateCertFile, trigger: "change" }],
  327. };
  328. // 初始化船员表单数据结构
  329. const shipOwnerForm = ref({
  330. shipOwnerId: 0, // 船员ID,匹配时返回,未匹配到为0,非0时下方内容无效
  331. userName: "", // 船员姓名(必填)
  332. userPhone: "", // 船员手机(必填)
  333. idcardNo: "", // 船员身份证号(非必填)
  334. idcardFrontFileKey: "", // 船员身份证正面文件key(非必填)
  335. idcardFrontViewUrl: "", // 船员身份证正面文件预览地址(非必填)
  336. idcardFrontDownloadUrl: "", // 船员身份证正面文件下载地址(非必填)
  337. idcardBackFileKey: "", // 船员身份证反面文件key(非必填)
  338. idcardBackViewUrl: "", // 船员身份证反面文件预览地址(非必填)
  339. idcardBackDownloadUrl: "", // 船员身份证反面文件下载地址(非必填)
  340. cerificate: {
  341. gender: "", // 性别(1-男;2-女)
  342. certNo: "", // 证书编号(必填)
  343. certType: "", // 证书类型(非必填)
  344. postRole: "", // 职务资格(必填)
  345. issuerAt: "", // 发证日期(必填)
  346. expiryAt: "", // 截止日期(必填)
  347. issuerAuthority: "", // 签发机构(必填)
  348. applicableRestrictions: "", // 适用限制(非必填)
  349. },
  350. documents: [], // 文件信息数组
  351. });
  352. // 计算属性 - 文件列表
  353. const idCardFrontFileList = computed(() => {
  354. if (
  355. shipOwnerForm.value.idcardFrontFileKey &&
  356. shipOwnerForm.value.idcardFrontViewUrl
  357. ) {
  358. return [
  359. {
  360. fileKey: shipOwnerForm.value.idcardFrontFileKey,
  361. viewUrl: shipOwnerForm.value.idcardFrontViewUrl,
  362. downloadUrl: shipOwnerForm.value.idcardFrontDownloadUrl,
  363. },
  364. ];
  365. }
  366. return [];
  367. });
  368. const idCardBackFileList = computed(() => {
  369. if (
  370. shipOwnerForm.value.idcardBackFileKey &&
  371. shipOwnerForm.value.idcardBackViewUrl
  372. ) {
  373. return [
  374. {
  375. fileKey: shipOwnerForm.value.idcardBackFileKey,
  376. viewUrl: shipOwnerForm.value.idcardBackViewUrl,
  377. downloadUrl: shipOwnerForm.value.idcardBackDownloadUrl,
  378. },
  379. ];
  380. }
  381. return [];
  382. });
  383. const certFileList = computed(() => {
  384. return (
  385. shipOwnerForm.value.documents?.filter((doc) => doc.docType === 1) || []
  386. );
  387. });
  388. const healthFileList = computed(() => {
  389. return (
  390. shipOwnerForm.value.documents?.filter((doc) => doc.docType === 2) || []
  391. );
  392. });
  393. const serviceFileList = computed(() => {
  394. return (
  395. shipOwnerForm.value.documents?.filter((doc) => doc.docType === 3) || []
  396. );
  397. });
  398. async function getShipOwnerDetail() {
  399. let { data } = await api.getShipOwnerDetail({
  400. shipOwnerId: route.query.shipOwnerId,
  401. });
  402. console.log(data);
  403. }
  404. // 手机号失去焦点时检查是否已存在船员信息
  405. const handlePhoneBlur = () => {
  406. if (!shipOwnerForm.value.userPhone) return;
  407. // 如果已经有shipOwnerId,说明已经匹配到了船员,不需要再查询
  408. if (shipOwnerForm.value.shipOwnerId) return;
  409. };
  410. // 身份证人像面上传处理
  411. const handleIdCardFrontUpload = ({ response }) => {
  412. if (response.code === 0) {
  413. shipOwnerForm.value.idcardFrontFileKey = response.data.fileKey;
  414. shipOwnerForm.value.idcardFrontViewUrl = response.data.viewUrl;
  415. shipOwnerForm.value.idcardFrontDownloadUrl = response.data.downloadUrl;
  416. } else {
  417. ElMessage.error(response.msg || "上传失败");
  418. }
  419. };
  420. // 身份证人像面删除处理
  421. const handleIdCardFrontRemove = () => {
  422. shipOwnerForm.value.idcardFrontFileKey = "";
  423. shipOwnerForm.value.idcardFrontViewUrl = "";
  424. shipOwnerForm.value.idcardFrontDownloadUrl = "";
  425. };
  426. // 身份证国徽面上传处理
  427. const handleIdCardBackUpload = ({ response }) => {
  428. if (response.code === 0) {
  429. shipOwnerForm.value.idcardBackFileKey = response.data.fileKey;
  430. shipOwnerForm.value.idcardBackViewUrl = response.data.viewUrl;
  431. shipOwnerForm.value.idcardBackDownloadUrl = response.data.downloadUrl;
  432. } else {
  433. ElMessage.error(response.msg || "上传失败");
  434. }
  435. };
  436. // 身份证国徽面删除处理
  437. const handleIdCardBackRemove = () => {
  438. shipOwnerForm.value.idcardBackFileKey = "";
  439. shipOwnerForm.value.idcardBackViewUrl = "";
  440. shipOwnerForm.value.idcardBackDownloadUrl = "";
  441. };
  442. // 证书上传处理
  443. const handleCertUpload = ({ response }) => {
  444. if (response.code === 0) {
  445. if (!shipOwnerForm.value.documents) {
  446. shipOwnerForm.value.documents = [];
  447. }
  448. shipOwnerForm.value.documents.push({
  449. docType: 1, // 船员适任证书
  450. fileKey: response.data.fileKey,
  451. viewUrl: response.data.viewUrl,
  452. downloadUrl: response.data.downloadUrl,
  453. });
  454. } else {
  455. ElMessage.error(response.msg || "上传失败");
  456. }
  457. };
  458. // 证书删除处理
  459. const handleCertRemove = ({ fileIndex }) => {
  460. const certDocs = shipOwnerForm.value.documents.filter(
  461. (doc) => doc.docType === 1
  462. );
  463. const docToRemove = certDocs[fileIndex];
  464. shipOwnerForm.value.documents = shipOwnerForm.value.documents.filter(
  465. (doc) => doc !== docToRemove
  466. );
  467. };
  468. // 健康证明上传处理
  469. const handleHealthUpload = ({ response }) => {
  470. if (response.code === 0) {
  471. if (!shipOwnerForm.value.documents) {
  472. shipOwnerForm.value.documents = [];
  473. }
  474. shipOwnerForm.value.documents.push({
  475. docType: 2, // 体检表
  476. fileKey: response.data.fileKey,
  477. viewUrl: response.data.viewUrl,
  478. downloadUrl: response.data.downloadUrl,
  479. });
  480. } else {
  481. ElMessage.error(response.msg || "上传失败");
  482. }
  483. };
  484. // 健康证明删除处理
  485. const handleHealthRemove = ({ fileIndex }) => {
  486. const healthDocs = shipOwnerForm.value.documents.filter(
  487. (doc) => doc.docType === 2
  488. );
  489. const docToRemove = healthDocs[fileIndex];
  490. shipOwnerForm.value.documents = shipOwnerForm.value.documents.filter(
  491. (doc) => doc !== docToRemove
  492. );
  493. };
  494. // 服务簿上传处理
  495. const handleServiceUpload = ({ response }) => {
  496. if (response.code === 0) {
  497. if (!shipOwnerForm.value.documents) {
  498. shipOwnerForm.value.documents = [];
  499. }
  500. shipOwnerForm.value.documents.push({
  501. docType: 3, // 服务簿
  502. fileKey: response.data.fileKey,
  503. viewUrl: response.data.viewUrl,
  504. downloadUrl: response.data.downloadUrl,
  505. });
  506. } else {
  507. ElMessage.error(response.msg || "上传失败");
  508. }
  509. };
  510. // 服务簿删除处理
  511. const handleServiceRemove = ({ fileIndex }) => {
  512. const serviceDocs = shipOwnerForm.value.documents.filter(
  513. (doc) => doc.docType === 3
  514. );
  515. const docToRemove = serviceDocs[fileIndex];
  516. shipOwnerForm.value.documents = shipOwnerForm.value.documents.filter(
  517. (doc) => doc !== docToRemove
  518. );
  519. };
  520. // 保存船员信息
  521. const saveShipOwner = () => {
  522. console.log("保存船员信息", shipOwnerForm.value);
  523. // 使用Element Plus表单验证
  524. shipOwnerFormRef.value.validate((valid1) => {
  525. if (!valid1) return;
  526. certificateFormRef.value.validate((valid2) => {
  527. if (!valid2) return;
  528. // 验证证书照片是否上传
  529. if (!certFileList.value.length) {
  530. ElMessage.warning("请上传证书照片");
  531. return;
  532. }
  533. // 表单验证通过,继续保存操作
  534. });
  535. });
  536. // 如果有ID,则更新,否则新增
  537. const apiMethod = route.query.shipOwnerId
  538. ? api.updateShipOwner
  539. : api.addShipOwner;
  540. const successMsg = route.query.shipOwnerId ? "更新成功" : "添加成功";
  541. // 构建保存数据
  542. const saveData = { ...shipOwnerForm.value };
  543. if (route.query.shipOwnerId) {
  544. saveData.id = route.query.shipOwnerId;
  545. }
  546. apiMethod(saveData)
  547. .then((res) => {
  548. if (res.data.code === 0) {
  549. ElMessage.success(successMsg);
  550. router.replace("/shipOwnerManage/shipOwnerList");
  551. } else {
  552. ElMessage.error(res.data.msg || "保存失败");
  553. }
  554. })
  555. .catch((err) => {
  556. console.error(err);
  557. ElMessage.error("保存失败");
  558. });
  559. };
  560. // 初始化数据
  561. onMounted(() => {
  562. // 如果有ID参数,获取船员详情
  563. if (route.query.shipOwnerId) {
  564. shipOwnerForm.value.shipOwnerId = Number(route.query.shipOwnerId);
  565. getShipOwnerDetail();
  566. }
  567. });
  568. </script>
  569. <style scoped>
  570. :deep().el-upload-list__item-thumbnail {
  571. object-fit: contain;
  572. }
  573. .upload-plus-icon {
  574. height: 15%;
  575. color: rgb(139, 147, 156);
  576. line-height: 100px;
  577. font-size: 40px;
  578. font-weight: 200;
  579. }
  580. .upload-text {
  581. height: 25%;
  582. color: rgb(139, 147, 156);
  583. }
  584. :deep().el-upload--picture-card {
  585. border: none;
  586. width: auto;
  587. }
  588. .unit {
  589. width: 40px;
  590. text-align: center;
  591. color: #555;
  592. }
  593. </style>