Просмотр исходного кода

修复了合同附件不展示的问题

lihaiting 2 дней назад
Родитель
Сommit
b5663374cd

+ 26 - 0
.workbuddy/memory/2026-10-15.md

@@ -30,3 +30,29 @@
 - fileMain type="view" 模式:单文件直接 window.open 预览,多文件打开列表弹窗(含浏览/下载)
 - 附件 fileId 来源:录用审批用 attachments[].fileId/id,其余审批单用 approvalDocumentFileId 字段
 - 离职交接接口无适配层,用 pick 兼容 approvalDocumentFileId / attachmentFileId 两种字段名
+
+---
+
+## 二次修复(统一附件解析)— 同上问题复查
+
+用户反馈:合同签署明明有附件(接口返回 `attachmentIds: "2109912865529982977"`)却显示"暂无附件"。
+经复查,之前"合同/调动/转正/入职代码OK"的判断**错误**:这些详情取附件的源字段(`files` 数组 / `approvalDocumentFileId` 单值)与后端实际返回不一致——后端统一返回 `attachmentIds` 字符串,但前端全代码从未处理过该字段。
+
+### 根因
+7 个审批详情组件取附件的源字段各不相同,而后端实际返回 `attachmentIds`(字符串或逗号分隔),前端从未解析它,导致合同等取不到附件。
+
+### 统一修复方案
+新建 `src/utils/attachment.js`,导出 `parseAttachmentIds(raw)`,兼容所有来源:
+`attachmentIds` / `attachmentFileIds` / `approvalDocumentFileId` / `attachmentFileId` / `resumeAttachmentFileIds` / `files`(数组) / `attachments`(数组),去重后返回字符串 ID 数组。
+
+### 改动
+- `src/utils/attachment.js`(新增)— 通用解析函数
+- `src/api/bpm/components/offerApproval/index.js` — adaptHiringApproval 的 attachmentFileIds 改用 parseAttachmentIds(raw)
+- `src/views/bpm/handleTask/components/hrContractManagement/hrContractDetail.vue` — adaptContract 用 parseAttachmentIds(item),删除原 collectAttachmentFileIds
+- `src/views/bpm/handleTask/components/hiringApproval/hiringApprovalDetail.vue` — resumeFileIds = parseAttachmentIds(detail)
+- `src/views/bpm/handleTask/components/regularizationHandling/regularizationHandlingDetail.vue` — approvalFileIds = parseAttachmentIds(regularization)
+- `src/views/bpm/handleTask/components/positionTransfer/positionTransferDetail.vue` — approvalFileIds = parseAttachmentIds(record)
+- `src/views/bpm/handleTask/components/resignationApplication/resignationApplicationDetail.vue` — approvalFileIds = parseAttachmentIds(resignation)
+- `src/views/bpm/handleTask/components/resignationHandoverOrder/resignationHandoverOrderDetail.vue` — approvalFileIds = parseAttachmentIds(detail)
+
+说明:传入 parseAttachmentIds 的变量均为接口原始返回(含后端全部字段),其中办理入职的 getHiringApprovalById(@/api/hr/hiringApproval.js)的 adaptHiringApproval 有 `...raw` 展开,故 detail 含原始附件字段。

+ 2 - 3
src/api/bpm/components/offerApproval/index.js

@@ -1,4 +1,5 @@
 import request from '@/utils/request';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 function dateOnly(value) {
   if (!value) return '';
@@ -76,9 +77,7 @@ export function adaptHiringApproval(raw = {}) {
     status: HIRING_STATUS_LABELS[statusCode] || raw.approvalStatus || '-',
     remark: raw.remark || raw.approvalComment || '',
     attachments,
-    attachmentFileIds: attachments
-      .map((item) => item.fileId || item.id)
-      .filter((id) => id != null && id !== ''),
+    attachmentFileIds: parseAttachmentIds(raw),
     attachmentNames: attachments
       .map((item) => item.fileName || item.name)
       .filter(Boolean),

+ 41 - 0
src/utils/attachment.js

@@ -0,0 +1,41 @@
+/**
+ * 从审批 / 业务详情的原始接口数据中,兼容多种附件字段来源,
+ * 解析出文件 ID 数组(去重、字符串化)。
+ *
+ * 兼容的字段来源(按常见命名):
+ *  - attachmentIds            字符串(逗号分隔)或字符串数组
+ *  - attachmentFileIds        字符串(逗号分隔)或字符串数组
+ *  - approvalDocumentFileId   单个 ID 或逗号分隔的字符串或数组
+ *  - attachmentFileId         同上
+ *  - resumeAttachmentFileIds  同上
+ *  - files / attachments      对象数组,元素含 fileId / id,或本身就是 ID 字符串
+ *
+ * 这样无论后端接口用哪种字段返回附件,前端都能正确拿到可预览的文件 ID 列表。
+ */
+export function parseAttachmentIds(raw = {}) {
+  const ids = [];
+  const collect = (value) => {
+    if (value == null || value === '') return;
+    const list = Array.isArray(value)
+      ? value
+      : String(value)
+          .split(',')
+          .map((s) => s.trim());
+    list.forEach((item) => {
+      if (item == null || item === '') return;
+      const id =
+        typeof item === 'object' ? item.fileId || item.id : String(item).trim();
+      if (id && !ids.includes(String(id))) ids.push(String(id));
+    });
+  };
+
+  collect(raw.attachmentIds);
+  collect(raw.attachmentFileIds);
+  collect(raw.approvalDocumentFileId);
+  collect(raw.attachmentFileId);
+  collect(raw.resumeAttachmentFileIds);
+  if (Array.isArray(raw.files)) collect(raw.files);
+  if (Array.isArray(raw.attachments)) collect(raw.attachments);
+
+  return ids;
+}

+ 4 - 5
src/views/bpm/handleTask/components/hiringApproval/hiringApprovalDetail.vue

@@ -221,11 +221,11 @@
 <script>
 import { getPositionById, getPositionPage } from '@/api/hr';
 import {
-  getHiringApprovalById,
-  parseResumeAttachmentFileIds
+  getHiringApprovalById
 } from '@/api/hr/hiringApproval';
 import { listOrganizations } from '@/api/organization';
 import { getDictByCode } from '@/api/sys';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 const emptyForm = () => ({
   id: null,
@@ -425,9 +425,8 @@ export default {
         specialTerms: detail.specialTerms || detail.remark || '',
         specialApprovalReason: detail.specialApprovalReason || ''
       };
-      this.resumeFileIds = parseResumeAttachmentFileIds(
-        detail.resumeAttachmentFileIds
-      );
+      // 兼容多种附件字段来源(attachmentIds / resumeAttachmentFileIds / files 等)
+      this.resumeFileIds = parseAttachmentIds(detail);
     },
     async loadDictOptions() {
       try {

+ 3 - 11
src/views/bpm/handleTask/components/hrContractManagement/hrContractDetail.vue

@@ -180,6 +180,7 @@ import {
   contractFlowSignStatusLabel,
   pickFlowSignStatus
 } from '@/api/hr';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 const CONTRACT_TYPES = [
   { value: 'FIXED', label: '固定期限劳动合同' },
@@ -213,16 +214,7 @@ function signMethodLabel(value) {
   return value || '';
 }
 
-function collectAttachmentFileIds(files) {
-  const list = (Array.isArray(files) ? files : []).filter(Boolean);
-  const attach = list.filter((item) =>
-    ['ATTACHMENT', 'SCAN', 'ARCHIVE'].includes(item.filePurpose)
-  );
-  const source = attach.length ? attach : list;
-  return source
-    .map((item) => item.fileId || item.id)
-    .filter((id) => id != null && id !== '');
-}
+// 附件 ID 解析已统一由 src/utils/attachment.js 的 parseAttachmentIds 处理
 
 function adaptContract(item = {}) {
   const files = item.files || [];
@@ -263,7 +255,7 @@ function adaptContract(item = {}) {
     probationMonths: item.probationMonths,
     createdAt: formatDateTime(item.createTime),
     probationEnd: formatDate(item.probationEndDate),
-    attachmentFileIds: collectAttachmentFileIds(files),
+    attachmentFileIds: parseAttachmentIds(item),
     level: item.positionLevel || item.level || '',
     manager: item.managerName || item.manager || '',
     archived:

+ 3 - 2
src/views/bpm/handleTask/components/positionTransfer/positionTransferDetail.vue

@@ -79,6 +79,7 @@
 import { getUserChangeById } from '@/api/main';
 import { listOrganizations, adaptEmployee, getEmployeeById } from '@/api/organization';
 import { getDictByCode } from '@/api/sys';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 /** 异动类型(与接口文档 changeType 枚举对齐) */
 const CHANGE_TYPE_LABELS = {
@@ -266,8 +267,8 @@ export default {
           this.loadPositionNatureOptions()
         ]);
         this.form = this.mapForm(record, employee);
-        const fileId = this.form.approvalDocumentFileId;
-        this.approvalFileIds = fileId ? [fileId] : [];
+        // 兼容多种附件字段来源(attachmentIds / approvalDocumentFileId 等)
+        this.approvalFileIds = parseAttachmentIds(record);
       } catch (error) {
         this.form = empty();
         this.approvalFileIds = [];

+ 3 - 3
src/views/bpm/handleTask/components/regularizationHandling/regularizationHandlingDetail.vue

@@ -175,6 +175,7 @@
 <script>
 import { getRegularizationById } from '@/api/main';
 import { getEmployeeById } from '@/api/organization';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 const empty = () => ({
   id: '',
@@ -391,9 +392,8 @@ export default {
         }
         this.employeeInfo = this.mapEmployeeInfo(regularization, employee);
 
-        // 转正单 swagger 只有单个 approvalDocumentFileId,统一转成数组给 fileMain
-        const fileId = this.approvalDocumentFileId;
-        this.approvalFileIds = fileId ? [fileId] : [];
+        // 兼容多种附件字段来源(attachmentIds / approvalDocumentFileId 等)
+        this.approvalFileIds = parseAttachmentIds(regularization);
       } catch (error) {
         this.form = empty();
         this.employeeInfo = {};

+ 3 - 2
src/views/bpm/handleTask/components/resignationApplication/resignationApplicationDetail.vue

@@ -101,6 +101,7 @@
 <script>
 import { getResignationById } from '@/api/main';
 import { getEmployeeById } from '@/api/organization';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 const empty = () => ({
   id: '',
@@ -308,8 +309,8 @@ export default {
           }
         }
         this.employeeInfo = this.mapEmployeeInfo(resignation, employee);
-        const fileId = this.form.approvalDocumentFileId;
-        this.approvalFileIds = fileId ? [fileId] : [];
+        // 兼容多种附件字段来源(attachmentIds / approvalDocumentFileId 等)
+        this.approvalFileIds = parseAttachmentIds(resignation);
       } catch (error) {
         this.form = empty();
         this.employeeInfo = {};

+ 3 - 2
src/views/bpm/handleTask/components/resignationHandoverOrder/resignationHandoverOrderDetail.vue

@@ -202,6 +202,7 @@
 <script>
 import { getHandoverById, getLatestItemReceipt } from '@/api/main';
 import { getEmployeeById } from '@/api/organization';
+import { parseAttachmentIds } from '@/utils/attachment';
 
 /** 总体交接状态 */
 const HANDOVER_STATUS_LABELS = {
@@ -368,8 +369,8 @@ export default {
         const detail = await getHandoverById(id);
         this.form = this.mapForm(detail);
         this.items = this.mapItems(detail);
-        const attachId = pick(detail, ['approvalDocumentFileId', 'attachmentFileId']);
-        this.approvalFileIds = attachId ? [attachId] : [];
+        // 兼容多种附件字段来源(attachmentIds / approvalDocumentFileId / attachmentFileId 等)
+        this.approvalFileIds = parseAttachmentIds(detail);
         await Promise.all([
           this.loadEmployee(this.form.userId),
           this.loadReceivers(detail),