detail.vue 33 KB

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