detail.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. <template>
  2. <main class="safety-platform-container__main">
  3. <el-alert
  4. v-if="rejectReason && (Number(approvalStatus) === 3)"
  5. type="error"
  6. :title="'不通过原因:' + rejectReason"
  7. show-icon
  8. class="detail-reject-alert"
  9. />
  10. <div class="detail-reject-alert">
  11. <div class="detail-reject-alert-name">
  12. 填表说明:
  13. </div>
  14. <div class="detail-reject-alert-content">
  15. 4张(事故报告、委托书、地址确认书、申请表)工伤认定表格需下载模版,手填,上传相关材料给吴剑希审批(驳回或通过)+判定工伤类别(责任事故/非责任事故);通过后状态变为申请加盖公章及材料申请;用印申请后编写一段告知通知(内容先把告知单信息先编辑进去),给申请人。
  16. </div>
  17. </div>
  18. <BasicForm
  19. ref="basicFormRef"
  20. :formData="ruleFormData"
  21. :formRules="isViewMode ? undefined : formRules"
  22. :formConfig="computedFormConfig"
  23. >
  24. <template #approvalTemplateId>
  25. <el-select
  26. class="select-box--item__select"
  27. v-model="ruleFormData.approvalTemplateId"
  28. placeholder="审批流程"
  29. filterable
  30. :disabled="isViewMode || isAuditMode"
  31. popper-class="el-scrollbar--custom"
  32. >
  33. <el-option v-for="item in approvalList" :key="item.id" :label="item.templateName" :value="item.id" />
  34. </el-select>
  35. </template>
  36. <template #reviewDepartmentId>
  37. <el-cascader
  38. v-model="ruleFormData.departmentCode"
  39. ref="cascaderRef"
  40. :options="firstLevelDepts"
  41. :props="cascaderProp"
  42. :show-all-levels="false"
  43. placeholder="部门名称"
  44. filterable
  45. :disabled="isViewMode || isAuditMode"
  46. @change="handleChangeDept"
  47. />
  48. </template>
  49. <template #accidentReport>
  50. <UploadFiles
  51. v-if="!isViewMode && !isAuditMode"
  52. label="上传事故报告"
  53. :maxCount="1"
  54. :file-list="accidentCertUrl"
  55. :disabled="isViewMode || isAuditMode"
  56. @uploadSuccess="handleAccidentReportUploadSuccess"
  57. />
  58. <div class="file-list" v-else>
  59. <div v-if="accidentCertUrl && accidentCertUrl.length > 0">
  60. <div class="file-item" v-for="file in accidentCertUrl" :key="file.fileId">
  61. <span class="file-item--name">{{ file.fileName }}</span>
  62. <div class="file-item--footer">
  63. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  64. >预览</el-button
  65. >
  66. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  67. >下载</el-button
  68. >
  69. </div>
  70. </div>
  71. </div>
  72. <div v-else class="no-attachment">暂无附件</div>
  73. </div>
  74. </template>
  75. <template #powerOfAttorney>
  76. <UploadFiles
  77. v-if="!isViewMode && !isAuditMode"
  78. label="上传委托书"
  79. :maxCount="1"
  80. :file-list="powerAttorneyUrl"
  81. :disabled="isViewMode || isAuditMode"
  82. @uploadSuccess="handlePowerOfAttorneyUploadSuccess"
  83. />
  84. <div class="file-list" v-else>
  85. <div v-if="powerAttorneyUrl && powerAttorneyUrl.length > 0">
  86. <div class="file-item" v-for="file in powerAttorneyUrl" :key="file.fileId">
  87. <span class="file-item--name">{{ file.fileName }}</span>
  88. <div class="file-item--footer">
  89. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  90. >预览</el-button
  91. >
  92. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  93. >下载</el-button
  94. >
  95. </div>
  96. </div>
  97. </div>
  98. <div v-else class="no-attachment">暂无附件</div>
  99. </div>
  100. </template>
  101. <template #addressConfirmation>
  102. <UploadFiles
  103. v-if="!isViewMode && !isAuditMode"
  104. label="上传地址确认书"
  105. :maxCount="1"
  106. :file-list="addressConfirmUrl"
  107. :disabled="isViewMode || isAuditMode"
  108. @uploadSuccess="handleAddressConfirmationUploadSuccess"
  109. />
  110. <div class="file-list" v-else>
  111. <div v-if="addressConfirmUrl && addressConfirmUrl.length > 0">
  112. <div class="file-item" v-for="file in addressConfirmUrl" :key="file.fileId">
  113. <span class="file-item--name">{{ file.fileName }}</span>
  114. <div class="file-item--footer">
  115. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  116. >预览</el-button
  117. >
  118. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  119. >下载</el-button
  120. >
  121. </div>
  122. </div>
  123. </div>
  124. <div v-else class="no-attachment">暂无附件</div>
  125. </div>
  126. </template>
  127. <template #applicationForm>
  128. <UploadFiles
  129. v-if="!isViewMode && !isAuditMode"
  130. label="上传申请表"
  131. :maxCount="1"
  132. :file-list="applicationFormUrl"
  133. :disabled="isViewMode || isAuditMode"
  134. @uploadSuccess="handleApplicationFormUploadSuccess"
  135. />
  136. <div class="file-list" v-else>
  137. <div v-if="applicationFormUrl && applicationFormUrl.length > 0">
  138. <div class="file-item" v-for="file in applicationFormUrl" :key="file.fileId">
  139. <span class="file-item--name">{{ file.fileName }}</span>
  140. <div class="file-item--footer">
  141. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  142. >预览</el-button
  143. >
  144. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  145. >下载</el-button
  146. >
  147. </div>
  148. </div>
  149. </div>
  150. <div v-else class="no-attachment">暂无附件</div>
  151. </div>
  152. </template>
  153. <template #idCard>
  154. <UploadFiles
  155. v-if="!isViewMode && !isAuditMode"
  156. label="上传身份证正反面"
  157. :maxCount="1"
  158. :file-list="idCardUrl"
  159. :disabled="isViewMode || isAuditMode"
  160. @uploadSuccess="handleIdCardUploadSuccess"
  161. />
  162. <div class="file-list" v-else>
  163. <div v-if="idCardUrl && idCardUrl.length > 0">
  164. <div class="file-item" v-for="file in idCardUrl" :key="file.fileId">
  165. <span class="file-item--name">{{ file.fileName }}</span>
  166. <div class="file-item--footer">
  167. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  168. >预览</el-button
  169. >
  170. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  171. >下载</el-button
  172. >
  173. </div>
  174. </div>
  175. </div>
  176. <div v-else class="no-attachment">暂无附件</div>
  177. </div>
  178. </template>
  179. <template #laborContract>
  180. <UploadFiles
  181. v-if="!isViewMode && !isAuditMode"
  182. label="上传劳动合同"
  183. :maxCount="1"
  184. :file-list="laborContractUrl"
  185. :disabled="isViewMode || isAuditMode"
  186. @uploadSuccess="handleLaborContractUploadSuccess"
  187. />
  188. <div class="file-list" v-else>
  189. <div v-if="laborContractUrl && laborContractUrl.length > 0">
  190. <div class="file-item" v-for="file in laborContractUrl" :key="file.fileId">
  191. <span class="file-item--name">{{ file.fileName }}</span>
  192. <div class="file-item--footer">
  193. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  194. >预览</el-button
  195. >
  196. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  197. >下载</el-button
  198. >
  199. </div>
  200. </div>
  201. </div>
  202. <div v-else class="no-attachment">暂无附件</div>
  203. </div>
  204. </template>
  205. <template #initialMedicalCertificate>
  206. <UploadFiles
  207. v-if="!isViewMode && !isAuditMode"
  208. label="上传初次医疗证明"
  209. :maxCount="1"
  210. :file-list="initialMedicalCertUrl"
  211. :disabled="isViewMode || isAuditMode"
  212. @uploadSuccess="handleInitialMedicalCertificateUploadSuccess"
  213. />
  214. <div class="file-list" v-else>
  215. <div v-if="initialMedicalCertUrl && initialMedicalCertUrl.length > 0">
  216. <div class="file-item" v-for="file in initialMedicalCertUrl" :key="file.fileId">
  217. <span class="file-item--name">{{ file.fileName }}</span>
  218. <div class="file-item--footer">
  219. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  220. >预览</el-button
  221. >
  222. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  223. >下载</el-button
  224. >
  225. </div>
  226. </div>
  227. </div>
  228. <div v-else class="no-attachment">暂无附件</div>
  229. </div>
  230. </template>
  231. <template #agentIdCard>
  232. <UploadFiles
  233. v-if="!isViewMode && !isAuditMode"
  234. label="上传被委托人员身份证正反面"
  235. :maxCount="1"
  236. :file-list="trusteeIdCardUrl"
  237. :disabled="isViewMode || isAuditMode"
  238. @uploadSuccess="handleAgentIdCardUploadSuccess"
  239. />
  240. <div class="file-list" v-else>
  241. <div v-if="trusteeIdCardUrl && trusteeIdCardUrl.length > 0">
  242. <div class="file-item" v-for="file in trusteeIdCardUrl" :key="file.fileId">
  243. <span class="file-item--name">{{ file.fileName }}</span>
  244. <div class="file-item--footer">
  245. <el-button link type="primary" @click="previewOnline(file.fileUrl, file.fileType)"
  246. >预览</el-button
  247. >
  248. <el-button link type="primary" @click.stop="downloadFile(file.fileUrl, file.fileName)"
  249. >下载</el-button
  250. >
  251. </div>
  252. </div>
  253. </div>
  254. <div v-else class="no-attachment">暂无附件</div>
  255. </div>
  256. </template>
  257. <template #status>
  258. <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
  259. <el-radio label="ENABLE">启用</el-radio>
  260. <el-radio label="DISABLE">禁用</el-radio>
  261. </el-radio-group>
  262. </template>
  263. </BasicForm>
  264. <PreviewOnline ref="previewOnlineRef" />
  265. <BasicDialog ref="basicDialogRef" title="提交审批" @refresh="closeDialog">
  266. <template #form>
  267. <div class="form">
  268. <el-form ref="approvalFormRef" :model="approvalForm">
  269. <el-form-item label="审批描述:" label-position="top">
  270. <el-input v-model="approvalForm.description" placeholder="请输入审批描述" type="textarea" />
  271. </el-form-item>
  272. <div class="form-item">
  273. <span>审批流程:</span>
  274. <template v-for="item in approvalNodeList" :key="item.id">
  275. <el-form-item
  276. :label="`第${item.approvalOrder}步:${item.nodeDescription}(${APPROVAL_TYPE_MAP[item.approvalType]})`"
  277. label-position="top"
  278. :prop="item.approverType !== APPROVER_TYPE.FIX ? `approvers.${item.id}` : ''"
  279. :rules="{ required: true, message: '请选择审批人员', trigger: 'change' }"
  280. >
  281. <el-input
  282. v-if="item.approverType === APPROVER_TYPE.FIX"
  283. :model-value="item.approverInfoList.map((info) => info.approverName).join(',')"
  284. disabled
  285. ></el-input>
  286. <el-select
  287. v-else
  288. v-model="approvalForm.approvers[item.id]"
  289. placeholder="请选择审批人员"
  290. value-key="id"
  291. filterable
  292. remote
  293. collapse-tags
  294. collapse-tags-tooltip
  295. :max-collapse-tags="2"
  296. :remote-method="remoteMethod"
  297. :loading="loading"
  298. multiple
  299. >
  300. <el-option
  301. v-for="option in userOptions"
  302. :key="option.id"
  303. :label="`${option.realname}(${option.username})${option.deptName}`"
  304. :value="option.id"
  305. />
  306. </el-select>
  307. </el-form-item>
  308. </template>
  309. </div>
  310. </el-form>
  311. </div>
  312. </template>
  313. <template #footer>
  314. <el-button type="primary" @click="handleSubmitApproval">提交</el-button>
  315. <el-button @click="basicDialogRef.closeDialog">取消</el-button>
  316. </template>
  317. </BasicDialog>
  318. <!-- 审核不通过原因 -->
  319. <el-dialog
  320. v-model="showRejectDialog"
  321. title="审核不通过"
  322. width="400px"
  323. destroy-on-close
  324. @close="rejectReason = ''"
  325. >
  326. <el-radio-group v-model="rejectReason" v-if="auditType">
  327. <el-radio value="1">责任事故</el-radio>
  328. <el-radio value="2">非责任事故</el-radio>
  329. <el-radio value="3">交通事故</el-radio>
  330. </el-radio-group>
  331. <el-input
  332. v-else
  333. v-model="rejectReason"
  334. type="textarea"
  335. :rows="3"
  336. placeholder="请输入审核不通过原因"
  337. />
  338. <template #footer>
  339. <el-button @click="showRejectDialog = false">取消</el-button>
  340. <el-button type="danger" :loading="auditSubmitting" @click="handleAuditReject">
  341. 确定
  342. </el-button>
  343. </template>
  344. </el-dialog>
  345. </main>
  346. <footer class="safety-platform-container__footer">
  347. <el-button @click="router.back()">返回</el-button>
  348. <template v-if="isCreateMode || isEditMode">
  349. <el-button type="primary" :loading="submitting" @click="handleSubmit">
  350. {{ isCreateMode ? '提交' : '保存' }}
  351. </el-button>
  352. </template>
  353. <template v-if="isAuditMode">
  354. <el-button type="success" :loading="auditSubmitting" @click="showRejectDialog = true;auditType = true">
  355. 审核通过
  356. </el-button>
  357. <el-button type="danger" :loading="auditSubmitting" @click="showRejectDialog = true;auditType = false">
  358. 审核不通过
  359. </el-button>
  360. </template>
  361. </footer>
  362. </template>
  363. <script setup lang="ts">
  364. import { computed, onMounted, ref , reactive } from 'vue';
  365. import { useRoute, useRouter } from 'vue-router';
  366. import { ElMessage } from 'element-plus';
  367. import BasicForm from '@/components/BasicForm.vue';
  368. import { useFormConfigHook } from '@/hooks/useFormConfigHook';
  369. import { INVENTORY_FORM_CONFIG, INVENTORY_FORM_DATA, INVENTORY_FORM_RULES } from '../configs/form';
  370. import {
  371. queryWorkInjuryApplyDetail,
  372. saveBusinessInformation,
  373. updateInventory ,
  374. submitApprovalProcess,
  375. auditPurchaseApply,
  376. updateWorkInjuryApply,
  377. } from '@/api/inventory';
  378. import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
  379. import type { FileItem } from '@/components/UploadFiles/types';
  380. import { formatAttachmentList } from '@/components/UploadFiles/utils';
  381. import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
  382. import { getAllDepartments } from '@/api/auth/dept';
  383. import BasicDialog from '@/components/BasicDialog.vue';
  384. import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
  385. import type { ApprovalNodeInstanceType } from '@/views/system/approval/types';
  386. import { APPROVAL_TYPE_MAP, APPROVER_TYPE } from '../configs/constant';
  387. import { useEmergencySuppliesHook } from '@/views/emergency/emergency-supplies/src/hook';
  388. import { useEmergencyHook } from '@/views/emergency/src/hoos';
  389. import { getApprovalNodeInstanceList } from '@/api/approval/approval';
  390. import { template } from 'lodash-es';
  391. import { downloadFile } from '@/views/disaster/utils';
  392. const router = useRouter();
  393. const route = useRoute();
  394. const cascaderProp = {
  395. expandTrigger: 'click',
  396. checkStrictly: true,
  397. // emitPath: false,
  398. value: 'id',
  399. label: 'deptName',
  400. };
  401. const { approvalList, getApprovalList } = useEmergencyHook()
  402. const operate = computed(() => (route.query.operate as string) || 'inventory-create');
  403. const currentId = computed(() => Number(route.query.id));
  404. const newFormId = ref('');
  405. const firstLevelDepts = ref<any[]>([]);
  406. const isCreateMode = computed(() => operate.value === 'inventory-create');
  407. const isEditMode = computed(() => operate.value === 'inventory-edit');
  408. const isViewMode = computed(() => operate.value === 'inventory-view');
  409. const isAuditMode = computed(() => operate.value === 'inventory-audit');
  410. const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
  411. const cascaderRef = ref();
  412. const rejectReason = ref<string>(route.query.rejectReason as string || '');
  413. const approvalOrder = ref<string>(route.query.approvalOrder as string || '');
  414. const approvalTemplateId = ref<string>(route.query.approvalTemplateId as string || '');
  415. const approvalStatus = ref<string>(route.query.approvalStatus as string || '');
  416. const auditType = ref(true);
  417. const basicDialogRef = ref();
  418. const approvalFormRef = ref();
  419. const approvalForm = reactive({
  420. description: '',
  421. approvers: {} as Record<number, any[]>,
  422. });
  423. const approvalNodeList = ref<ApprovalNodeInstanceType[]>([]);
  424. const { userOptions, loading, remoteMethod } = useEmergencySuppliesHook();
  425. const submitting = ref(false);
  426. const auditSubmitting = ref(false);
  427. const showRejectDialog = ref(false);
  428. const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
  429. useFormConfigHook(INVENTORY_FORM_CONFIG, INVENTORY_FORM_DATA, INVENTORY_FORM_RULES);
  430. // 查看模式下,所有字段设为只读
  431. const viewFormConfig = ref(
  432. INVENTORY_FORM_CONFIG.map((item) => ({
  433. ...item,
  434. componentProps: {
  435. ...item.componentProps,
  436. disabled: true,
  437. },
  438. })),
  439. );
  440. const computedFormConfig = computed(() => {
  441. if (isViewMode.value) {
  442. return viewFormConfig.value;
  443. }
  444. if (isAuditMode.value) {
  445. return viewFormConfig.value;
  446. }
  447. return ruleFormConfig.value;
  448. });
  449. const basicFormRef = ref<InstanceType<typeof BasicForm>>();
  450. const handleValidate = async () => {
  451. if (!basicFormRef.value) return;
  452. const res = await basicFormRef.value.validateForm();
  453. return res;
  454. };
  455. const getDetail = async () => {
  456. if (!currentId.value) return;
  457. try {
  458. const res = await queryWorkInjuryApplyDetail(currentId.value);
  459. if (res) {
  460. // 映射接口字段到表单字段
  461. ruleFormData.itemName = res.applicantName || ''; // 申请人姓名
  462. ruleFormData.applicantCode = res.applicantCode || ''; // 工号
  463. ruleFormData.warehouseDate = res.injuryTime ? res.injuryTime.split('T')[0] : ''; // 受伤时间
  464. ruleFormData.status = res.status ? 'ENABLE' : 'DISABLE'; // 状态
  465. ruleFormData.remarks = res.injuryReason || ''; // 受伤原因
  466. ruleFormData.accidentCertUrl = res.accidentCertUrl || ''; // 事故报告
  467. ruleFormData.powerAttorneyUrl = res.powerAttorneyUrl || ''; // 委托书
  468. ruleFormData.addressConfirmUrl = res.addressConfirmUrl || ''; // 地址确认书
  469. ruleFormData.applicationFormUrl = res.applicationFormUrl || ''; // 申请表
  470. ruleFormData.idCardUrl = res.idCardUrl || ''; // 身份证正反面
  471. ruleFormData.laborContractUrl = res.laborContractUrl || ''; // 劳动合同
  472. ruleFormData.initialMedicalCertUrl = res.initialMedicalCertUrl || ''; // 初次医疗证明
  473. ruleFormData.trusteeIdCardUrl = res.trusteeIdCardUrl || ''; // 被委托人员身份证正反面
  474. ruleFormData.departmentCode = JSON.parse(res.departmentCode || '[]') || ''; // 部门编码
  475. ruleFormData.departmentName = res.departmentName || ''; // 添加这一行,设置部门名称
  476. ruleFormData.approvalTemplateId = res.templateId || ''; // 审批模板ID
  477. }
  478. cloneRuleFormData();
  479. } catch (e) {
  480. console.error('获取物品库存详情失败:', e);
  481. ElMessage.error('获取详情失败');
  482. }
  483. };
  484. const handleSubmit = async () => {
  485. const res = await handleValidate();
  486. if (!res) return;
  487. try {
  488. const basePayload = {
  489. applicantName: ruleFormData.itemName,
  490. applicantCode: ruleFormData.applicantCode,
  491. injuryTime: ruleFormData.warehouseDate
  492. ? new Date(ruleFormData.warehouseDate).toISOString()
  493. : '',
  494. status: ruleFormData.status === 'ENABLE',
  495. injuryReason: ruleFormData.remarks || '',
  496. accidentCertUrl: ruleFormData.accidentCertUrl,
  497. powerAttorneyUrl: ruleFormData.powerAttorneyUrl,
  498. addressConfirmUrl: ruleFormData.addressConfirmUrl,
  499. applicationFormUrl: ruleFormData.applicationFormUrl,
  500. idCardUrl: ruleFormData.idCardUrl,
  501. laborContractUrl: ruleFormData.laborContractUrl,
  502. initialMedicalCertUrl: ruleFormData.initialMedicalCertUrl,
  503. trusteeIdCardUrl: ruleFormData.trusteeIdCardUrl,
  504. departmentName: ruleFormData.departmentName || '',
  505. departmentCode: ruleFormData.departmentCode || '',
  506. templateId: Number(ruleFormData.approvalTemplateId),
  507. };
  508. if (isCreateMode.value) {
  509. await saveBusinessInformation(basePayload).then((res)=>{
  510. newFormId.value = res || '';
  511. });
  512. ElMessage.success('创建成功');
  513. await getApprovalNode(Number(ruleFormData.approvalTemplateId));
  514. basicDialogRef.value.openDialog();
  515. return;
  516. } else if (isEditMode.value && currentId.value) {
  517. await updateWorkInjuryApply({
  518. id: currentId.value,
  519. ...basePayload,
  520. });
  521. ElMessage.success('保存成功');
  522. await getApprovalNode(Number(ruleFormData.approvalTemplateId));
  523. basicDialogRef.value.openDialog();
  524. return;
  525. }
  526. cloneRuleFormData();
  527. router.back();
  528. } catch (e) {
  529. console.error('保存物品库存失败:', e);
  530. ElMessage.error('保存失败,请重试');
  531. }
  532. };
  533. const handlePreview = (url: string) => {
  534. if (url) {
  535. // 根据文件扩展名判断文件类型
  536. const extension = url.split('.').pop()?.toLowerCase() || '';
  537. let fileType: 'pdf' | 'word' | 'excel' | 'ppt' = 'pdf';
  538. if (extension === 'doc' || extension === 'docx') {
  539. fileType = 'word';
  540. } else if (extension === 'xls' || extension === 'xlsx') {
  541. fileType = 'excel';
  542. } else if (extension === 'ppt' || extension === 'pptx') {
  543. fileType = 'ppt';
  544. }
  545. previewOnlineRef.value?.open(url, fileType);
  546. }
  547. };
  548. /** 附件 JSON [{file_name, url}] 转 FileItem 列表,供 UploadFiles 使用(新增/编辑/查看/审核统一展示) */
  549. function convertAttachmentJsonToFileItems(attachmentStr: string): FileItem[] {
  550. if (!attachmentStr || !String(attachmentStr).trim()) return [];
  551. try {
  552. const arr = JSON.parse(attachmentStr);
  553. if (!Array.isArray(arr)) return [];
  554. return arr.map((item: any, index: number) => {
  555. const fileName = item.file_name || item.fileName || `附件${index + 1}`;
  556. const url = item.url || item.fileUrl || '';
  557. const ext = (fileName || '').split('.').pop()?.toLowerCase() || '';
  558. let fileType = 'pdf';
  559. if (['doc', 'docx'].includes(ext)) fileType = 'word';
  560. else if (['xls', 'xlsx'].includes(ext)) fileType = 'excel';
  561. else if (['ppt', 'pptx'].includes(ext)) fileType = 'ppt';
  562. return { fileId: index + 1, fileName, fileType, fileSize: '0', fileUrl: url };
  563. });
  564. } catch {
  565. return [];
  566. }
  567. }
  568. const accidentCertUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.accidentCertUrl || ''));
  569. const powerAttorneyUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.powerAttorneyUrl || ''));
  570. const addressConfirmUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.addressConfirmUrl || ''));
  571. const applicationFormUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.applicationFormUrl || ''));
  572. const idCardUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.idCardUrl || ''));
  573. const laborContractUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.laborContractUrl || ''));
  574. const initialMedicalCertUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.initialMedicalCertUrl || ''));
  575. const trusteeIdCardUrl = computed(() => convertAttachmentJsonToFileItems(ruleFormData.trusteeIdCardUrl || ''));
  576. async function handleAccidentReportUploadSuccess(files: FileItem[]) {
  577. if (!files?.length) {
  578. ruleFormData.accidentCertUrl = '';
  579. return;
  580. }
  581. try {
  582. const list = await formatAttachmentList(files);
  583. const jsonArr = (list || []).map((r) => ({
  584. fileName: r.fileName,
  585. fileId: r.fileId || '',
  586. fileSize: r.fileSize || '',
  587. fileType: r.fileType || '',
  588. fileUrl: r.fileUrl || '',
  589. })).filter((x) => x.fileId);
  590. ruleFormData.accidentCertUrl = JSON.stringify(jsonArr);
  591. } catch (e) {
  592. console.error('事故报告上传失败:', e);
  593. ElMessage.error(e?.message || e?.data || '事故报告上传失败,请重试');
  594. }
  595. }
  596. async function handlePowerOfAttorneyUploadSuccess(files: FileItem[]) {
  597. if (!files?.length) {
  598. ruleFormData.powerAttorneyUrl = '';
  599. return;
  600. }
  601. try {
  602. const list = await formatAttachmentList(files);
  603. const jsonArr = (list || []).map((r) => ({
  604. fileName: r.fileName,
  605. fileId: r.fileId || '',
  606. fileSize: r.fileSize || '',
  607. fileType: r.fileType || '',
  608. fileUrl: r.fileUrl || '',
  609. })).filter((x) => x.fileId);
  610. ruleFormData.powerAttorneyUrl = JSON.stringify(jsonArr);
  611. } catch (e) {
  612. console.error('委托书上传失败:', e);
  613. ElMessage.error(e?.message || e?.data || '委托书上传失败,请重试');
  614. }
  615. }
  616. async function handleAddressConfirmationUploadSuccess(files: FileItem[]) {
  617. if (!files?.length) {
  618. ruleFormData.addressConfirmUrl = '';
  619. return;
  620. }
  621. try {
  622. const list = await formatAttachmentList(files);
  623. const jsonArr = (list || []).map((r) => ({
  624. file_name: r.fileName,
  625. fileId: r.fileId || '',
  626. fileSize: r.fileSize || '',
  627. fileType: r.fileType || '',
  628. fileUrl: r.fileUrl || '',
  629. })).filter((x) => x.fileId);
  630. ruleFormData.addressConfirmUrl = JSON.stringify(jsonArr);
  631. } catch (e) {
  632. console.error('地址确认书上传失败:', e);
  633. ElMessage.error(e?.message || e?.data || '地址确认书上传失败,请重试');
  634. }
  635. }
  636. async function handleApplicationFormUploadSuccess(files: FileItem[]) {
  637. if (!files?.length) {
  638. ruleFormData.applicationFormUrl = '';
  639. return;
  640. }
  641. try {
  642. const list = await formatAttachmentList(files);
  643. const jsonArr = (list || []).map((r) => ({
  644. file_name: r.fileName,
  645. fileId: r.fileId || '',
  646. fileSize: r.fileSize || '',
  647. fileType: r.fileType || '',
  648. fileUrl: r.fileUrl || '',
  649. })).filter((x) => x.fileId);
  650. ruleFormData.applicationFormUrl = JSON.stringify(jsonArr);
  651. } catch (e) {
  652. console.error('申请表上传失败:', e);
  653. ElMessage.error(e?.message || e?.data || '申请表上传失败,请重试');
  654. }
  655. }
  656. async function handleIdCardUploadSuccess(files: FileItem[]) {
  657. if (!files?.length) {
  658. ruleFormData.idCardUrl = '';
  659. return;
  660. }
  661. try {
  662. const list = await formatAttachmentList(files);
  663. const jsonArr = (list || []).map((r) => ({
  664. fileName: r.fileName,
  665. fileId: r.fileId || '',
  666. fileSize: r.fileSize || '',
  667. fileType: r.fileType || '',
  668. fileUrl: r.fileUrl || '',
  669. })).filter((x) => x.fileId);
  670. ruleFormData.idCardUrl = JSON.stringify(jsonArr);
  671. } catch (e) {
  672. console.error('身份证正反面上传失败:', e);
  673. ElMessage.error(e?.message || e?.data || '身份证正反面上传失败,请重试');
  674. }
  675. }
  676. async function handleLaborContractUploadSuccess(files: FileItem[]) {
  677. if (!files?.length) {
  678. ruleFormData.laborContractUrl = '';
  679. return;
  680. }
  681. try {
  682. const list = await formatAttachmentList(files);
  683. const jsonArr = (list || []).map((r) => ({
  684. file_name: r.fileName,
  685. fileId: r.fileId || '',
  686. fileSize: r.fileSize || '',
  687. fileType: r.fileType || '',
  688. fileUrl: r.fileUrl || '',
  689. })).filter((x) => x.fileId);
  690. ruleFormData.laborContractUrl = JSON.stringify(jsonArr);
  691. } catch (e) {
  692. console.error('劳动合同上传失败:', e);
  693. ElMessage.error(e?.message || e?.data || '劳动合同上传失败,请重试');
  694. }
  695. }
  696. async function handleInitialMedicalCertificateUploadSuccess(files: FileItem[]) {
  697. if (!files?.length) {
  698. ruleFormData.initialMedicalCertUrl = '';
  699. return;
  700. }
  701. try {
  702. const list = await formatAttachmentList(files);
  703. const jsonArr = (list || []).map((r) => ({
  704. file_name: r.fileName,
  705. fileId: r.fileId || '',
  706. fileSize: r.fileSize || '',
  707. fileType: r.fileType || '',
  708. fileUrl: r.fileUrl || '',
  709. })).filter((x) => x.fileId);
  710. ruleFormData.initialMedicalCertUrl = JSON.stringify(jsonArr);
  711. } catch (e) {
  712. console.error('初次医疗证明上传失败:', e);
  713. ElMessage.error(e?.message || e?.data || '初次医疗证明上传失败,请重试');
  714. }
  715. }
  716. async function handleAgentIdCardUploadSuccess(files: FileItem[]) {
  717. if (!files?.length) {
  718. ruleFormData.trusteeIdCardUrl = '';
  719. return;
  720. }
  721. try {
  722. const list = await formatAttachmentList(files);
  723. const jsonArr = (list || []).map((r) => ({
  724. file_name: r.fileName,
  725. fileId: r.fileId || '',
  726. fileSize: r.fileSize || '',
  727. fileType: r.fileType || '',
  728. fileUrl: r.fileUrl || '',
  729. })).filter((x) => x.fileId);
  730. ruleFormData.trusteeIdCardUrl = JSON.stringify(jsonArr);
  731. } catch (e) {
  732. console.error('被委托人员身份证正反面上传失败:', e);
  733. ElMessage.error(e?.message || e?.data || '被委托人员身份证正反面上传失败,请重试');
  734. }
  735. }
  736. const getDeptData = () => {
  737. getAllDepartments().then((res) => {
  738. firstLevelDepts.value = formatDeptTree(res);
  739. });
  740. };
  741. const handleChangeDept = () => {
  742. const deptInfo = cascaderRef.value?.getCheckedNodes();
  743. if (deptInfo?.[0]) {
  744. ruleFormData.departmentName = deptInfo[0].label;
  745. ruleFormData.departmentCode = deptInfo[0].pathValues;
  746. }
  747. };
  748. const refreshForm = () => {
  749. approvalFormRef.value?.resetFields();
  750. approvalForm.description = '';
  751. };
  752. const closeDialog = () => {
  753. refreshForm();
  754. basicDialogRef.value.closeDialog();
  755. };
  756. const handleSubmitApproval = () => {
  757. approvalFormRef.value.validate(async (valid: boolean) => {
  758. if (valid) {
  759. // 构造后端需要的数据格式
  760. const approvalData = {
  761. id: currentId.value || newFormId.value || '',
  762. templateId: ruleFormData.approvalTemplateId,
  763. approvalDescription: approvalForm.description,
  764. approvalInfoList: approvalNodeList.value.map((node) => {
  765. let approverIdList: number[] = [];
  766. if (node.approverType === APPROVER_TYPE.FIX) {
  767. approverIdList = node.approverInfoList.map((info) => info.approverId);
  768. } else if (approvalForm.approvers[node.id]) {
  769. approverIdList = approvalForm.approvers[node.id];
  770. }
  771. return {
  772. approvalOrder: node.approvalOrder,
  773. approverIdList,
  774. };
  775. }),
  776. };
  777. await submitApprovalProcess(approvalData);
  778. ElMessage.success('提交成功');
  779. basicDialogRef.value.closeDialog();
  780. cloneRuleFormData();
  781. router.back();
  782. }
  783. });
  784. };
  785. const getApprovalNode = async (id: number) => {
  786. const res = await getApprovalNodeInstanceList(id);
  787. approvalNodeList.value = res.approvalNodeInfoList || [];
  788. };
  789. async function handleAuditReject() {
  790. if (!rejectReason.value?.trim()) {
  791. ElMessage.warning('请选择审核不通过原因');
  792. return;
  793. }
  794. if (!currentId.value) return;
  795. auditSubmitting.value = true;
  796. try {
  797. await auditPurchaseApply({ id: currentId.value, status: auditType.value === true ? 2 : 3, rejectReason: rejectReason.value.trim(), approvalOrder: Number(approvalOrder.value),templateId: approvalTemplateId.value,code: rejectReason.value });
  798. ElMessage.success('审核通过');
  799. showRejectDialog.value = false;
  800. router.back();
  801. } catch (e) {
  802. console.error('审核失败:', e);
  803. ElMessage.error('审核失败,请重试');
  804. } finally {
  805. auditSubmitting.value = false;
  806. }
  807. }
  808. const previewOnline = (url: string | undefined, type) => {
  809. if (url) {
  810. previewOnlineRef.value?.open(url, type);
  811. }
  812. };
  813. onMounted(() => {
  814. cloneRuleFormData();
  815. beforeRouteLeave();
  816. getDeptData();
  817. getApprovalList();
  818. if (isEditMode.value || isViewMode.value || isAuditMode.value) {
  819. getDetail();
  820. }
  821. });
  822. </script>
  823. <style scoped lang="scss">
  824. @use '@/styles/page-details-layout.scss' as *;
  825. .detail-reject-alert{
  826. margin-bottom: 20px;
  827. }
  828. .detail-reject-alert-name{
  829. color: #aaa;
  830. margin-bottom: 10px;
  831. }
  832. .detail-reject-alert-content{
  833. color: #aaa;
  834. }
  835. </style>