From 31acf8d8830c5b8b33e0599018544aa4d68e351b Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期一, 25 五月 2026 14:12:24 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro

---
 src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js          |    2 
 multiple/config.json                                                                              |    9 
 src/views/personnelManagement/contractManagement/components/formDia.vue                           |  151 +++++-----
 src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js      |   82 ++++--
 src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js |   58 +---
 src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue                  |  151 +++++-----
 multiple/assets/favicon/HQJCfavicon.ico                                                           |    0 
 multiple/assets/logo/HQJCLogo.png                                                                 |    0 
 src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js              |  126 ++-------
 src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue    |  180 ++++++++++---
 10 files changed, 385 insertions(+), 374 deletions(-)

diff --git a/multiple/assets/favicon/HQJCfavicon.ico b/multiple/assets/favicon/HQJCfavicon.ico
new file mode 100644
index 0000000..65e6942
--- /dev/null
+++ b/multiple/assets/favicon/HQJCfavicon.ico
Binary files differ
diff --git a/multiple/assets/logo/HQJCLogo.png b/multiple/assets/logo/HQJCLogo.png
new file mode 100644
index 0000000..5e21e9c
--- /dev/null
+++ b/multiple/assets/logo/HQJCLogo.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
index 635f59a..accb76e 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -51,6 +51,15 @@
     "logo": "logo/QXYLogo.png",
     "favicon": "favicon/QXYfavicon.ico"
   },
+  "HQJC": {
+    "env": {
+      "VITE_APP_TITLE": "鍗庡己寤烘潗绠$悊绯荤粺",
+      "VITE_BASE_API": "http://36.134.77.64:9001",
+      "VITE_JAVA_API": "http://36.134.77.64:9000"
+    },
+    "logo": "logo/HQJCLogo.png",
+    "favicon": "favicon/HQJCfavicon.ico"
+  },
   "XCDQ": {
     "env": {
       "VITE_APP_TITLE": "鏃櫒鐢靛櫒绠$悊绯荤粺",
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
index 938a787..3251f0c 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -1,21 +1,7 @@
-import {
-  createEmptyNode,
-  formatDisplayTime,
-  mapNodesFromApi,
-  mapSignModeFromApi,
-  mapSignModeToApi,
-  normalizeFlowNodes,
-  nodeSignModeLabel,
-} from "../approve-template/approveTemplateConstants.js";
+import { createEmptyNode, formatDisplayTime, mapNodesFromApi, mapSignModeFromApi, mapSignModeToApi, normalizeFlowNodes, nodeSignModeLabel } from "../approve-template/approveTemplateConstants.js";
 import { buildFormPayloadFromFields, parseFormConfigToData } from "../approve-template/formConfigUtils.js";
-import {
-  isDynamicOptionSource,
-  resolveSelectDisplayLabel,
-} from "../approve-template/selectOptionSource.js";
-import {
-  appendDotNotationQuery,
-  buildApprovalInstanceSearchDto,
-} from "../approve-shared/approvalInstanceListSearch.js";
+import { isDynamicOptionSource, resolveSelectDisplayLabel } from "../approve-template/selectOptionSource.js";
+import { appendDotNotationQuery, buildApprovalInstanceSearchDto } from "../approve-shared/approvalInstanceListSearch.js";
 
 /** 瀹℃壒绫诲瀷锛堜笌鍚庣瀛楁 approvalType 瀵归綈锛屽悗鏈熷彲鍚屾锛� */
 export const APPROVAL_TYPE_OPTIONS = [
@@ -80,24 +66,11 @@
   if (upper === "APPROVED" || upper === "APPROVE" || upper === "PASS" || upper === "AGREE") {
     return "approved";
   }
-  if (
-    upper === "REJECTED" ||
-    upper === "REJECT" ||
-    upper === "REFUSE" ||
-    upper === "REFUSED" ||
-    upper === "DENIED"
-  ) {
+  if (upper === "REJECTED" || upper === "REJECT" || upper === "REFUSE" || upper === "REFUSED" || upper === "DENIED") {
     return "rejected";
   }
   if (upper === "CANCELLED" || upper === "CANCEL" || upper === "REVOKED") return "cancelled";
-  if (
-    upper === "PENDING" ||
-    upper === "IN_PROGRESS" ||
-    upper === "PROCESSING" ||
-    upper === "RUNNING" ||
-    upper === "WAIT" ||
-    upper === "WAITING"
-  ) {
+  if (upper === "PENDING" || upper === "IN_PROGRESS" || upper === "PROCESSING" || upper === "RUNNING" || upper === "WAIT" || upper === "WAITING") {
     return "pending";
   }
   if (s.includes("鑽夌")) return "draft";
@@ -134,13 +107,9 @@
   }
   const tasks = row.tasks;
   if (Array.isArray(tasks) && tasks.length) {
-    const rejected = tasks.some((t) =>
-      normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected"
-    );
+    const rejected = tasks.some(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "rejected");
     if (rejected) return "REJECTED";
-    const allApproved = tasks.every((t) =>
-      normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved"
-    );
+    const allApproved = tasks.every(t => normalizeApprovalStatusKey(t?.status ?? t?.taskStatus) === "approved");
     if (allApproved) return "APPROVED";
   }
   return "";
@@ -175,7 +144,7 @@
 /** 鍚庣 records 鈫� 鏃堕棿绾垮睍绀虹粨鏋� */
 export function mapRecordsFromApi(records) {
   const list = Array.isArray(records) ? records : [];
-  return list.map((r) => ({
+  return list.map(r => ({
     id: r.id,
     operatorName: r.approverName || r.operatorName || r.createUserName || "",
     result: mapRecordResultFromApi(r.approveAction ?? r.action ?? r.status),
@@ -198,7 +167,7 @@
   const list = Array.isArray(tasks) ? tasks : [];
   if (!list.length) return [];
   const byLevel = new Map();
-  list.forEach((t) => {
+  list.forEach(t => {
     const level = Number(t.levelNo ?? t.taskLevel ?? t.nodeOrder ?? 1);
     if (!byLevel.has(level)) {
       byLevel.set(level, {
@@ -226,16 +195,14 @@
       node.signMode = mapSignModeFromApi(t.approveType);
     }
   });
-  return [...byLevel.entries()]
-    .sort(([a], [b]) => a - b)
-    .map(([, node]) => node);
+  return [...byLevel.entries()].sort(([a], [b]) => a - b).map(([, node]) => node);
 }
 
 /** 椤甸潰 flowNodes 鈫� 鍚庣 tasks */
 export function mapFlowNodesToTasks(flowNodes, { instanceId, templateId } = {}) {
   const nodes = normalizeFlowNodes(flowNodes);
   const tasks = [];
-  nodes.forEach((n) => {
+  nodes.forEach(n => {
     const levelNo = n.nodeOrder ?? 1;
     const approveType = mapSignModeToApi(n.signMode);
     n.approvers.forEach((a, idx) => {
@@ -278,7 +245,7 @@
     return String(val);
   }
   if (field?.type === "select" && field.options?.length) {
-    const hit = field.options.find((o) => String(o.value) === String(val));
+    const hit = field.options.find(o => String(o.value) === String(val));
     return hit?.label || String(val);
   }
   if (Array.isArray(val)) return val.join(" 鑷� ");
@@ -298,8 +265,8 @@
   };
   if (!fields.length && Object.keys(formPayload).length) {
     fields = Object.keys(formPayload)
-      .filter((k) => k && k !== "summary")
-      .map((k) => ({
+      .filter(k => k && k !== "summary")
+      .map(k => ({
         key: k,
         label: k,
         type: guessFieldTypeFromValue(formPayload[k]),
@@ -366,11 +333,7 @@
 export function buildInstanceDto({ submitForm, activeTemplate, userStore, flowNodes, existingRow }) {
   const payload = submitForm?.formPayload || {};
   const tpl = activeTemplate || {};
-  const title =
-    String(payload.summary || payload.title || "").trim() ||
-    tpl.label ||
-    submitForm?.templateName ||
-    "瀹℃壒鐢宠";
+  const title = String(payload.summary || payload.title || "").trim() || tpl.label || submitForm?.templateName || "瀹℃壒鐢宠";
   const templateId = submitForm?.templateId || tpl.templateId;
   const instanceId = existingRow?.id ?? submitForm?.instanceId;
   const taskList = mapFlowNodesToTasks(flowNodes || submitForm?.flowNodes, {
@@ -388,20 +351,13 @@
     tasks: taskList,
   };
 
-  const attachments =
-    (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length
-      ? submitForm.storageBlobDTOs
-      : null) || tpl.storageBlobDTOs;
+  const attachments = (Array.isArray(submitForm?.storageBlobDTOs) && submitForm.storageBlobDTOs.length ? submitForm.storageBlobDTOs : null) || tpl.storageBlobDTOs;
   if (attachments?.length) dto.storageBlobDTOs = attachments;
 
   if (isUpdate) {
     dto.id = existingRow?.id ?? submitForm?.instanceId;
     dto.instanceNo = existingRow?.instanceNo ?? submitForm?.instanceNo ?? "";
-    dto.status =
-      submitForm?.saveStatusApi ||
-      existingRow?.statusRaw ||
-      mapInstanceStatusToApi(existingRow?.approvalStatus) ||
-      "PENDING";
+    dto.status = submitForm?.saveStatusApi || existingRow?.statusRaw || mapInstanceStatusToApi(existingRow?.approvalStatus) || "PENDING";
     dto.currentLevel = existingRow?.currentLevel ?? submitForm?.currentLevel ?? 1;
     dto.applicantId = existingRow?.applicantId ?? existingRow?.applicantNo;
     dto.applicantName = existingRow?.applicantName || "";
@@ -440,7 +396,7 @@
 /** 椤甸潰 approvalStatus 鈫� 鍚庣 status */
 export function mapInstanceStatusToApi(approvalStatus) {
   const key = normalizeApprovalStatusKey(approvalStatus);
-  const hit = APPROVAL_STATUS_OPTIONS.find((x) => x.value === key);
+  const hit = APPROVAL_STATUS_OPTIONS.find(x => x.value === key);
   return hit?.api || "PENDING";
 }
 
@@ -463,9 +419,7 @@
   const resolved = resolveInstanceFormFields(row);
   const { fields, formPayload, templateSnapshot } = resolved;
   const tasks = Array.isArray(row.tasks) ? row.tasks : [];
-  const flowNodes = tasks.length
-    ? mapTasksToFlowNodes(tasks)
-    : mapNodesFromApi(row.nodes || row.flowNodes);
+  const flowNodes = tasks.length ? mapTasksToFlowNodes(tasks) : mapNodesFromApi(row.nodes || row.flowNodes);
   const approvalRecords = mapRecordsFromApi(row.records);
   return {
     id: row.id,
@@ -496,12 +450,13 @@
     templateSnapshot,
     tasks,
     records: Array.isArray(row.records) ? row.records : [],
+    storageBlobVOList: row.storageBlobVOList || [],
+    storageBlobDTOs: row.storageBlobVOList || row.storageBlobDTOs || [],
     flowNodes,
     approvalFlowNodes: [],
     currentNodeIndex: 0,
     approvalRecords,
-    rejectReason:
-      approvalRecords.find((r) => r.result === "rejected")?.opinion || "",
+    rejectReason: approvalRecords.find(r => r.result === "rejected")?.opinion || "",
   };
 }
 
@@ -524,12 +479,7 @@
   };
 }
 
-export function buildApprovalInstanceListParams({
-  page,
-  searchForm,
-  businessType,
-  extraParams,
-}) {
+export function buildApprovalInstanceListParams({ page, searchForm, businessType, extraParams }) {
   const dto = buildApprovalInstanceSearchDto(searchForm, extraParams);
   const bizType = businessType ?? searchForm?.businessType;
   if (bizType != null && bizType !== "") {
@@ -548,11 +498,11 @@
 }
 
 export function approvalTypeLabel(v) {
-  return APPROVAL_TYPE_OPTIONS.find((x) => x.value === v)?.label || v || "鈥�";
+  return APPROVAL_TYPE_OPTIONS.find(x => x.value === v)?.label || v || "鈥�";
 }
 
 export function approvalTypeStyle(v) {
-  const hit = APPROVAL_TYPE_OPTIONS.find((x) => x.value === v);
+  const hit = APPROVAL_TYPE_OPTIONS.find(x => x.value === v);
   if (!hit) return {};
   return {
     backgroundColor: hit.cellBg,
@@ -563,7 +513,7 @@
 
 export function approvalStatusLabel(v) {
   const key = normalizeApprovalStatusKey(v);
-  return APPROVAL_STATUS_OPTIONS.find((x) => x.value === key)?.label || "鈥�";
+  return APPROVAL_STATUS_OPTIONS.find(x => x.value === key)?.label || "鈥�";
 }
 
 /** 涓氬姟鐢宠椤电姸鎬佹枃妗堬細PENDING鈫掕繘琛屼腑 APPROVED鈫掑凡瀹屾垚 REJECTED鈫掑凡椹冲洖 */
@@ -582,9 +532,7 @@
  * 杩涜涓�(PENDING)銆佸凡瀹屾垚(APPROVED) 涓嶅彲淇敼锛涘凡椹冲洖銆佸凡鎾ら攢绛夊彲淇敼
  */
 export function canEditBusinessInstanceRow(row) {
-  const key = normalizeApprovalStatusKey(
-    row?.approvalStatus ?? row?.statusRaw ?? row?.status
-  );
+  const key = normalizeApprovalStatusKey(row?.approvalStatus ?? row?.statusRaw ?? row?.status);
   return key !== "pending" && key !== "approved";
 }
 
@@ -609,12 +557,8 @@
 /** 鍒楄〃琛� 鈫� 缂栬緫琛ㄥ崟锛堜粎鐢ㄨ鏁版嵁鍥炴樉锛� */
 export function buildEditFormFromInstanceRow(row) {
   const { fields, formPayload, templateSnapshot } = resolveInstanceFormFields(row);
-  const normalized = normalizeFlowNodes(
-    row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks)
-  );
-  const flowNodes = normalized.length
-    ? JSON.parse(JSON.stringify(normalized))
-    : [createEmptyNode(1)];
+  const normalized = normalizeFlowNodes(row?.flowNodes?.length ? row.flowNodes : mapTasksToFlowNodes(row?.tasks));
+  const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)];
 
   return {
     templateKey: String(row?.templateId || ""),
@@ -631,9 +575,7 @@
     formPayload,
     flowNodes,
     templateAttachments: initTemplateAttachmentsFromSnapshot(templateSnapshot),
-    storageBlobDTOs: row?.storageBlobDTOs?.length
-      ? JSON.parse(JSON.stringify(row.storageBlobDTOs))
-      : [],
+    storageBlobDTOs: (row?.storageBlobDTOs?.length ? row.storageBlobDTOs : row?.storageBlobVOList || []).map(f => JSON.parse(JSON.stringify(f))),
   };
 }
 
@@ -641,9 +583,7 @@
   const tpl = templateOverride || null;
   const payload = tpl?.fields?.length ? buildFormPayloadFromFields(tpl.fields) : { summary: "" };
   const normalized = normalizeFlowNodes(flowNodesOverride);
-  const flowNodes = normalized.length
-    ? JSON.parse(JSON.stringify(normalized))
-    : [createEmptyNode(1)];
+  const flowNodes = normalized.length ? JSON.parse(JSON.stringify(normalized)) : [createEmptyNode(1)];
   return {
     templateKey: templateKey || "",
     templateId: tpl?.templateId || "",
@@ -658,9 +598,7 @@
     formFieldDefs: tpl?.fields || [],
     formPayload: payload,
     flowNodes,
-    templateAttachments: tpl?.storageBlobDTOs
-      ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs))
-      : [],
+    templateAttachments: tpl?.storageBlobDTOs ? JSON.parse(JSON.stringify(tpl.storageBlobDTOs)) : [],
     storageBlobDTOs: [],
   };
 }
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
index f54c167..721a5c2 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
@@ -3,83 +3,165 @@
   <div class="approve-detail-panel">
     <div class="detail-block">
       <div class="detail-block-title">鍩烘湰淇℃伅</div>
-      <el-descriptions :column="2" border>
+      <el-descriptions :column="2"
+                       border>
         <el-descriptions-item label="涓氬姟鍗曞彿">{{ row.bizId || row.id || "鈥�" }}</el-descriptions-item>
         <el-descriptions-item label="瀹℃壒鐘舵��">
-          <el-tag :type="approvalStatusTagType(row.approvalStatus)" size="small" effect="plain">
+          <el-tag :type="approvalStatusTagType(row.approvalStatus)"
+                  size="small"
+                  effect="plain">
             {{ approvalStatusLabel(row.approvalStatus) }}
           </el-tag>
         </el-descriptions-item>
         <el-descriptions-item label="瀹℃壒绫诲瀷">
-          <span class="approve-type-cell" :style="approvalTypeStyle(row.approvalType)">
+          <span class="approve-type-cell"
+                :style="approvalTypeStyle(row.approvalType)">
             {{ approvalTypeLabel(row.approvalType) }}
           </span>
         </el-descriptions-item>
         <el-descriptions-item label="鐢宠浜虹紪鍙�">{{ row.applicantNo || "鈥�" }}</el-descriptions-item>
         <el-descriptions-item label="鐢宠浜哄悕绉�">{{ row.applicantName || "鈥�" }}</el-descriptions-item>
         <el-descriptions-item label="鐢宠鎽樿">{{ row.summary || "鈥�" }}</el-descriptions-item>
-        <el-descriptions-item v-if="row.rejectReason" label="椹冲洖鍘熷洜" :span="2">
+        <el-descriptions-item v-if="row.rejectReason"
+                              label="椹冲洖鍘熷洜"
+                              :span="2">
           <span class="reject-text">{{ row.rejectReason }}</span>
         </el-descriptions-item>
-        <el-descriptions-item label="鍒涘缓鏃堕棿" :span="2">
+        <el-descriptions-item label="鍒涘缓鏃堕棿"
+                              :span="2">
           {{ formatDisplayTime(row.createTime) }}
         </el-descriptions-item>
       </el-descriptions>
     </div>
-
     <div class="detail-block">
       <div class="detail-block-title">濉姤鍐呭</div>
-      <FormPayloadFields
-        :fields="formResolved.fields"
-        :form-payload="formResolved.formPayload"
-        readonly
-      />
+      <FormPayloadFields :fields="formResolved.fields"
+                         :form-payload="formResolved.formPayload"
+                         readonly />
+    </div>
+    <div v-if="attachmentList.length"
+         class="detail-block">
+      <div class="detail-block-title">闄勪欢鍒楄〃</div>
+      <div class="attachment-list">
+        <div v-for="file in attachmentList"
+             :key="file.id"
+             class="attachment-item">
+          <el-icon class="file-icon">
+            <Paperclip />
+          </el-icon>
+          <span class="file-name"
+                :title="file.name || file.originalFilename">
+            {{ file.name || file.originalFilename }}
+          </span>
+          <div class="file-actions">
+            <el-link v-if="file.previewURL || file.url"
+                     type="primary"
+                     :underline="false"
+                     @click="openFile(file.previewURL || file.url)">棰勮</el-link>
+            <el-divider v-if="(file.previewURL || file.url) && file.downloadURL"
+                        direction="vertical" />
+            <el-link v-if="file.downloadURL"
+                     type="primary"
+                     :underline="false"
+                     @click="openFile(file.downloadURL)">涓嬭浇</el-link>
+          </div>
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { computed } from "vue";
-import { formatDisplayTime } from "../../approve-template/approveTemplateConstants.js";
-import {
-  approvalTypeLabel,
-  approvalTypeStyle,
-  approvalStatusLabel,
-  approvalStatusTagType,
-  resolveInstanceFormFields,
-} from "../approveListConstants.js";
-import FormPayloadFields from "./FormPayloadFields.vue";
+  import { computed } from "vue";
+  import { Paperclip } from "@element-plus/icons-vue";
+  import { formatDisplayTime } from "../../approve-template/approveTemplateConstants.js";
+  import {
+    approvalTypeLabel,
+    approvalTypeStyle,
+    approvalStatusLabel,
+    approvalStatusTagType,
+    resolveInstanceFormFields,
+  } from "../approveListConstants.js";
+  import FormPayloadFields from "./FormPayloadFields.vue";
 
-const props = defineProps({
-  row: { type: Object, default: () => ({}) },
-});
+  const props = defineProps({
+    row: { type: Object, default: () => ({}) },
+  });
 
-const formResolved = computed(() => resolveInstanceFormFields(props.row));
+  const formResolved = computed(() => resolveInstanceFormFields(props.row));
+
+  const attachmentList = computed(() => {
+    const list = props.row.storageBlobVOList || props.row.storageBlobDTOs || [];
+    return Array.isArray(list) ? list : [];
+  });
+
+  function openFile(url) {
+    if (!url) return;
+    window.open(url, "_blank");
+  }
 </script>
 
 <style scoped>
-.approve-detail-panel {
-  display: flex;
-  flex-direction: column;
-  gap: 20px;
-}
-.detail-block-title {
-  font-size: 14px;
-  font-weight: 600;
-  color: var(--el-text-color-primary);
-  margin: 0 0 12px;
-  padding-left: 10px;
-  border-left: 3px solid var(--el-color-primary);
-  line-height: 1.4;
-}
-.approve-type-cell {
-  display: inline-block;
-  padding: 2px 10px;
-  border-radius: 4px;
-  font-size: 13px;
-  line-height: 1.5;
-}
-.reject-text {
-  color: var(--el-color-danger);
-}
+  .approve-detail-panel {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+  .detail-block-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--el-text-color-primary);
+    margin: 0 0 12px;
+    padding-left: 10px;
+    border-left: 3px solid var(--el-color-primary);
+    line-height: 1.4;
+  }
+  .approve-type-cell {
+    display: inline-block;
+    padding: 2px 10px;
+    border-radius: 4px;
+    font-size: 13px;
+    line-height: 1.5;
+  }
+  .reject-text {
+    color: var(--el-color-danger);
+  }
+
+  .attachment-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+    gap: 12px;
+  }
+  .attachment-item {
+    display: flex;
+    align-items: center;
+    padding: 10px 12px;
+    background-color: var(--el-fill-color-light);
+    border-radius: 6px;
+    border: 1px solid var(--el-border-color-lighter);
+    transition: all 0.3s;
+  }
+  .attachment-item:hover {
+    border-color: var(--el-color-primary-light-5);
+    background-color: var(--el-color-primary-light-9);
+  }
+  .file-icon {
+    font-size: 18px;
+    color: var(--el-text-color-secondary);
+    margin-right: 10px;
+  }
+  .file-name {
+    flex: 1;
+    font-size: 13px;
+    color: var(--el-text-color-primary);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin-right: 12px;
+  }
+  .file-actions {
+    display: flex;
+    align-items: center;
+    flex-shrink: 0;
+  }
 </style>
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
index 0251647..d6bec18 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js
@@ -1,25 +1,9 @@
 import { computed } from "vue";
-import {
-  businessApprovalStatusLabel,
-  businessApprovalStatusTagType,
-  formatFieldDisplayValue,
-  resolveInstanceFormFields,
-} from "../approve-list/approveListConstants.js";
-import {
-  INSTANCE_NO_SEARCH_MODULE_KEYS,
-  INSTANCE_NO_TABLE_COLUMN,
-} from "./approvalInstanceListSearch.js";
+import { businessApprovalStatusLabel, businessApprovalStatusTagType, formatFieldDisplayValue, resolveInstanceFormFields } from "../approve-list/approveListConstants.js";
+import { INSTANCE_NO_SEARCH_MODULE_KEYS, INSTANCE_NO_TABLE_COLUMN } from "./approvalInstanceListSearch.js";
 
 /** 鍒楄〃/璇︽儏涓嶅洖鏄句负鐙珛鍒楃殑濉姤椤� key锛堥伩鍏嶈鐩栧疄渚嬬郴缁熷瓧娈碉級 */
-const DEFAULT_EXCLUDE_KEYS = new Set([
-  "summary",
-  "status",
-  "approvalStatus",
-  "approvalstatus",
-  "instanceStatus",
-  "publishStatus",
-  "newsStatus",
-]);
+const DEFAULT_EXCLUDE_KEYS = new Set(["summary", "status", "approvalStatus", "approvalstatus", "instanceStatus", "publishStatus", "newsStatus"]);
 
 /** enrich 鍚庡繀椤讳繚鐣欑殑瀹炰緥瀛楁锛堜笉琚� formConfig 閾哄钩瑕嗙洊锛� */
 const PRESERVE_INSTANCE_FIELDS = [
@@ -44,6 +28,8 @@
   "unread",
   "currentLevel",
   "newsStatus",
+  "storageBlobVOList",
+  "storageBlobDTOs",
 ];
 
 /**
@@ -64,14 +50,8 @@
     if (!f?.key || DEFAULT_EXCLUDE_KEYS.has(f.key)) continue;
     const val = formPayload[f.key];
     let text = formatFieldDisplayValue(f, val, caches);
-    if (
-      text === String(val) &&
-      row?.applicantName &&
-      (f.label === "鐢宠浜�" || f.key === "applicant" || f.key === "applicantName")
-    ) {
-      const idMatch =
-        String(val) === String(row.applicantId) ||
-        String(val) === String(row.applicantNo);
+    if (text === String(val) && row?.applicantName && (f.label === "鐢宠浜�" || f.key === "applicant" || f.key === "applicantName")) {
+      const idMatch = String(val) === String(row.applicantId) || String(val) === String(row.applicantNo);
       if (idMatch) text = row.applicantName;
     }
     formDisplay[f.key] = text;
@@ -89,10 +69,8 @@
  * 浠庡垪琛ㄩ琛� formConfig 鐢熸垚涓昏〃鍔ㄦ�佸垪锛坙abel 鍙栬嚜妯℃澘瀛楁 label锛�
  */
 export function getFormConfigFieldColumns(firstRow, { excludeKeys = DEFAULT_EXCLUDE_KEYS } = {}) {
-  const fields = (firstRow?.formFieldDefs || []).filter(
-    (f) => f?.key && !excludeKeys.has(f.key)
-  );
-  return fields.map((f) => ({
+  const fields = (firstRow?.formFieldDefs || []).filter(f => f?.key && !excludeKeys.has(f.key));
+  return fields.map(f => ({
     label: f.label || f.key,
     prop: f.key,
     minWidth: f.type === "textarea" ? 200 : f.type === "datetimerange" ? 160 : 120,
@@ -104,19 +82,9 @@
  * 涓氬姟鐢宠涓昏〃鍒楋細鍥哄畾鍒� + formConfig 鍔ㄦ�佸垪 + 瀹℃壒鐘舵�� + 鎿嶄綔
  */
 export function buildInstanceTableColumns(tableDataRef, buildTableActions, options = {}) {
-  const {
-    moduleKey,
-    excludeKeys = DEFAULT_EXCLUDE_KEYS,
-    beforeFormColumns = [],
-    extraColumns = [],
-    afterFormColumns = [],
-    actionWidth = 260,
-  } = options;
+  const { moduleKey, excludeKeys = DEFAULT_EXCLUDE_KEYS, beforeFormColumns = [], extraColumns = [], afterFormColumns = [], actionWidth = 260 } = options;
 
-  const leadingCols =
-    moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey)
-      ? [INSTANCE_NO_TABLE_COLUMN]
-      : [];
+  const leadingCols = moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey) ? [INSTANCE_NO_TABLE_COLUMN] : [];
 
   return computed(() => {
     const formCols = getFormConfigFieldColumns(tableDataRef.value?.[0], { excludeKeys });
@@ -132,8 +100,8 @@
         prop: "approvalStatus",
         width: 110,
         dataType: "tag",
-        formatData: (v) => businessApprovalStatusLabel(v),
-        formatType: (v) => businessApprovalStatusTagType(v),
+        formatData: v => businessApprovalStatusLabel(v),
+        formatType: v => businessApprovalStatusTagType(v),
       },
       {
         dataType: "action",
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js
index 3042d0c..80dda5c 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js
@@ -1,3 +1,4 @@
+import dayjs from "dayjs";
 import { APPROVAL_MODULE_KEYS } from "./approvalModuleRegistry.js";
 
 /** 鏀寔瀹℃壒鍗曞彿鏌ヨ/涓昏〃灞曠ず鐨勫鎵圭敵璇锋ā鍧� */
@@ -48,16 +49,29 @@
   return no ? { instanceNo: no } : {};
 }
 
-/** 缁勮 approvalInstanceDto 鏌ヨ鐗囨锛堢敵璇蜂汉 + 瀹℃壒鍗曞彿锛� */
+/** 缁勮 approvalInstanceDto 鏌ヨ鐗囨锛堢敵璇蜂汉 + 瀹℃壒鍗曞彿 + 鐘舵�� + 鏃堕棿鑼冨洿锛� */
 export function buildApprovalInstanceSearchDto(searchForm = {}, extraParams = {}) {
   const dto = {
     ...(extraParams && typeof extraParams === "object" ? extraParams : {}),
   };
   Object.assign(dto, pickApplicantFromSearchForm(searchForm));
   Object.assign(dto, pickInstanceNoFromSearchForm(searchForm));
+
+  // 瀹℃壒鐘舵��
+  if (searchForm?.status) {
+    dto.status = searchForm.status;
+  }
+
+  // 鍒涘缓鏃堕棿鑼冨洿
+  const range = searchForm?.createTimeRange;
+  if (Array.isArray(range) && range[0]) {
+    dto.createTimeStart = range[0] + (range[0].includes(":") ? "" : " 00:00:00");
+  }
+  if (Array.isArray(range) && range[1]) {
+    dto.createTimeEnd = range[1] + (range[1].includes(":") ? "" : " 23:59:59");
+  }
+
   delete dto.createTime;
-  delete dto.createTimeStart;
-  delete dto.createTimeEnd;
   return dto;
 }
 
@@ -74,26 +88,17 @@
 function matchApplicantKeyword(row, keyword) {
   const kw = (keyword || "").trim().toLowerCase();
   if (!kw) return true;
-  const parts = [
-    row?.applicantName,
-    row?.applicantNo,
-    row?.applicantId,
-    getRowPayloadValue(row, ["applicant", "applicantName", "applicantId"]),
-  ]
-    .filter((v) => v != null && v !== "")
-    .map((v) => String(v).toLowerCase());
-  return parts.some((p) => p.includes(kw));
+  const parts = [row?.applicantName, row?.applicantNo, row?.applicantId, getRowPayloadValue(row, ["applicant", "applicantName", "applicantId"])]
+    .filter(v => v != null && v !== "")
+    .map(v => String(v).toLowerCase());
+  return parts.some(p => p.includes(kw));
 }
 
 function matchApplicantId(row, applicantId) {
   if (applicantId == null || applicantId === "") return true;
   const id = String(applicantId);
   if (row?.applicantId != null && String(row.applicantId) === id) return true;
-  const payloadApplicant = getRowPayloadValue(row, [
-    "applicant",
-    "applicantId",
-    "applicantUserId",
-  ]);
+  const payloadApplicant = getRowPayloadValue(row, ["applicant", "applicantId", "applicantUserId"]);
   return String(payloadApplicant) === id;
 }
 
@@ -106,31 +111,48 @@
 function matchInstanceNo(row, instanceNo) {
   const kw = (instanceNo || "").trim().toLowerCase();
   if (!kw) return true;
-  const parts = [row?.instanceNo, row?.bizId]
-    .filter((v) => v != null && v !== "")
-    .map((v) => String(v).toLowerCase());
-  return parts.some((p) => p.includes(kw));
+  const parts = [row?.instanceNo, row?.bizId].filter(v => v != null && v !== "").map(v => String(v).toLowerCase());
+  return parts.some(p => p.includes(kw));
 }
 
-/** 鏄惁瀛樺湪鍒楄〃绛涢�夋潯浠讹紙鐢宠浜� / 瀹℃壒鍗曞彿锛� */
+/** 鏄惁瀛樺湪鍒楄〃绛涢�夋潯浠讹紙鐢宠浜� / 瀹℃壒鍗曞彿 / 鐘舵�� / 鏃堕棿鑼冨洿锛� */
 export function hasActiveModuleSearch(moduleKey, searchForm = {}) {
   const sf = searchForm || {};
   if ((sf.instanceNo || "").trim()) return true;
   if ((sf.applicantKeyword || "").trim()) return true;
   if ((sf.applicantName || "").trim()) return true;
-  return sf.applicantId != null && sf.applicantId !== "";
+  if (sf.applicantId != null && sf.applicantId !== "") return true;
+  if (sf.status) return true;
+  if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) return true;
+  return false;
 }
 
-/** 鎸夌敵璇蜂汉銆佸鎵瑰崟鍙峰仛鍓嶇鍏滃簳绛涢�� */
+/** 鎸夌敵璇蜂汉銆佸鎵瑰崟鍙枫�佺姸鎬併�佹椂闂磋寖鍥村仛鍓嶇鍏滃簳绛涢�� */
 export function filterInstanceRowsByModuleSearch(moduleKey, rows, searchForm = {}) {
   const sf = searchForm || {};
   const list = Array.isArray(rows) ? rows : [];
   if (!hasActiveModuleSearch(moduleKey, sf)) return list;
 
-  return list.filter(
-    (row) =>
-      matchInstanceNo(row, sf.instanceNo) &&
-      matchApplicantId(row, sf.applicantId) &&
-      matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName)
-  );
+  return list.filter(row => {
+    // 瀹℃壒鍗曞彿
+    if (!matchInstanceNo(row, sf.instanceNo)) return false;
+    // 鐢宠浜�
+    if (!matchApplicantId(row, sf.applicantId)) return false;
+    if (!matchApplicantKeyword(row, sf.applicantKeyword || sf.applicantName)) return false;
+    // 鐘舵��
+    if (sf.status && String(row.statusRaw || row.status).toUpperCase() !== String(sf.status).toUpperCase()) {
+      return false;
+    }
+    // 鏃堕棿鑼冨洿
+    if (Array.isArray(sf.createTimeRange) && sf.createTimeRange.length === 2) {
+      const rowTime = row.createTime || row.applyTime;
+      if (rowTime) {
+        const t = dayjs(rowTime);
+        const start = dayjs(sf.createTimeRange[0] + " 00:00:00");
+        const end = dayjs(sf.createTimeRange[1] + " 23:59:59");
+        if (t.isBefore(start) || t.isAfter(end)) return false;
+      }
+    }
+    return true;
+  });
 }
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
index a85cf9c..fd56eca 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -1,5 +1,3 @@
-import { matchBusinessTypeValue } from "../approve-list/approveListConstants.js";
-
 /**
  * 鍚勪笟鍔℃ā鍧椾笌瀹℃壒妯℃澘绫诲瀷鐨勬槧灏勶紙閰嶇疆鍖栧叆鍙o級
  * businessType 涓庡悗绔� TypeEnums / listPage 绾﹀畾涓�鑷达紙鍐欐鏋氫妇鍊硷級
diff --git a/src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue b/src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue
index 54b2ef9..3db1bee 100644
--- a/src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue
+++ b/src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue
@@ -1,96 +1,93 @@
 <template>
   <div>
-    <el-dialog
-        v-model="dialogFormVisible"
-        title="璇︽儏"
-        width="70%"
-        @close="closeDia"
-    >
-      <PIMTable
-          rowKey="id"
-          :column="tableColumn"
-          :tableData="tableData"
-          :tableLoading="tableLoading"
-          height="600"
-      ></PIMTable>
+    <el-dialog v-model="dialogFormVisible"
+               title="璇︽儏"
+               width="70%"
+               @close="closeDia">
+      <PIMTable rowKey="id"
+                :column="tableColumn"
+                :tableData="tableData"
+                :tableLoading="tableLoading"
+                height="600"></PIMTable>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="closeDia">鍙栨秷</el-button>
         </div>
       </template>
     </el-dialog>
-    <Files ref="filesDia"></Files>
+    <FileList v-if="fileDialogVisible"
+              v-model:visible="fileDialogVisible"
+              :record-type="'staff_contract'"
+              :record-id="recordId" />
   </div>
 </template>
 
 <script setup>
-import {ref} from "vue";
-import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js";
-const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue"));
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-const filesDia = ref()
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const tableColumn = ref([
-  {
-    label: "鍚堝悓骞撮檺",
-    prop: "contractTerm",
-  },
-  {
-    label: "鍚堝悓寮�濮嬫棩鏈�",
-    prop: "contractStartTime",
-  },
-  {
-    label: "鍚堝悓缁撴潫鏃ユ湡",
-    prop: "contractEndTime",
-  },
-  {
-    dataType: "action",
-    label: "鎿嶄綔",
-    align: "center",
-    fixed: 'right',
-    width: 120,
-    operation: [
-      {
-        name: "涓婁紶闄勪欢",
-        type: "text",
-        clickFun: (row) => {
-          filesDia.value.openDialog( row,'鍚堝悓')
+  import { ref, defineAsyncComponent, getCurrentInstance } from "vue";
+  import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js";
+  const FileList = defineAsyncComponent(() =>
+    import("@/components/Dialog/FileList.vue")
+  );
+  const { proxy } = getCurrentInstance();
+  const emit = defineEmits(["close"]);
+  const fileDialogVisible = ref(false);
+  const recordId = ref(0);
+  const dialogFormVisible = ref(false);
+  const operationType = ref("");
+  const tableColumn = ref([
+    {
+      label: "鍚堝悓骞撮檺",
+      prop: "contractTerm",
+    },
+    {
+      label: "鍚堝悓寮�濮嬫棩鏈�",
+      prop: "contractStartTime",
+    },
+    {
+      label: "鍚堝悓缁撴潫鏃ユ湡",
+      prop: "contractEndTime",
+    },
+    {
+      dataType: "action",
+      label: "鎿嶄綔",
+      align: "center",
+      fixed: "right",
+      width: 120,
+      operation: [
+        {
+          name: "闄勪欢",
+          type: "text",
+          clickFun: row => {
+            recordId.value = row.id;
+            fileDialogVisible.value = true;
+          },
         },
-      }
-    ],
-  },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
+      ],
+    },
+  ]);
+  const tableData = ref([]);
+  const tableLoading = ref(false);
 
-// 鎵撳紑寮规
-const openDialog = (type, row) => {
-  operationType.value = type;
-  dialogFormVisible.value = true;
-  if (operationType.value === 'edit') {
-    findStaffContractListPage({staffOnJobId: row.id}).then(res => {
-      tableData.value = res.data.records
-    })
-  }
-}
+  // 鎵撳紑寮规
+  const openDialog = (type, row) => {
+    operationType.value = type;
+    dialogFormVisible.value = true;
+    if (operationType.value === "edit") {
+      findStaffContractListPage({ staffOnJobId: row.id }).then(res => {
+        tableData.value = res.data.records;
+      });
+    }
+  };
 
-const openUploadFile = (row) => {
-  filesDia.value.open = true
-  filesDia.value.row = row
-}
-
-// 鍏抽棴寮规
-const closeDia = () => {
-  dialogFormVisible.value = false;
-  emit('close')
-};
-defineExpose({
-  openDialog,
-});
+  // 鍏抽棴寮规
+  const closeDia = () => {
+    dialogFormVisible.value = false;
+    emit("close");
+  };
+  defineExpose({
+    openDialog,
+  });
 </script>
 
 <style scoped>
-
 </style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/contractManagement/components/formDia.vue b/src/views/personnelManagement/contractManagement/components/formDia.vue
index 54b2ef9..3db1bee 100644
--- a/src/views/personnelManagement/contractManagement/components/formDia.vue
+++ b/src/views/personnelManagement/contractManagement/components/formDia.vue
@@ -1,96 +1,93 @@
 <template>
   <div>
-    <el-dialog
-        v-model="dialogFormVisible"
-        title="璇︽儏"
-        width="70%"
-        @close="closeDia"
-    >
-      <PIMTable
-          rowKey="id"
-          :column="tableColumn"
-          :tableData="tableData"
-          :tableLoading="tableLoading"
-          height="600"
-      ></PIMTable>
+    <el-dialog v-model="dialogFormVisible"
+               title="璇︽儏"
+               width="70%"
+               @close="closeDia">
+      <PIMTable rowKey="id"
+                :column="tableColumn"
+                :tableData="tableData"
+                :tableLoading="tableLoading"
+                height="600"></PIMTable>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="closeDia">鍙栨秷</el-button>
         </div>
       </template>
     </el-dialog>
-    <Files ref="filesDia"></Files>
+    <FileList v-if="fileDialogVisible"
+              v-model:visible="fileDialogVisible"
+              :record-type="'staff_contract'"
+              :record-id="recordId" />
   </div>
 </template>
 
 <script setup>
-import {ref} from "vue";
-import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js";
-const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue"));
-const { proxy } = getCurrentInstance()
-const emit = defineEmits(['close'])
-const filesDia = ref()
-const dialogFormVisible = ref(false);
-const operationType = ref('')
-const tableColumn = ref([
-  {
-    label: "鍚堝悓骞撮檺",
-    prop: "contractTerm",
-  },
-  {
-    label: "鍚堝悓寮�濮嬫棩鏈�",
-    prop: "contractStartTime",
-  },
-  {
-    label: "鍚堝悓缁撴潫鏃ユ湡",
-    prop: "contractEndTime",
-  },
-  {
-    dataType: "action",
-    label: "鎿嶄綔",
-    align: "center",
-    fixed: 'right',
-    width: 120,
-    operation: [
-      {
-        name: "涓婁紶闄勪欢",
-        type: "text",
-        clickFun: (row) => {
-          filesDia.value.openDialog( row,'鍚堝悓')
+  import { ref, defineAsyncComponent, getCurrentInstance } from "vue";
+  import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js";
+  const FileList = defineAsyncComponent(() =>
+    import("@/components/Dialog/FileList.vue")
+  );
+  const { proxy } = getCurrentInstance();
+  const emit = defineEmits(["close"]);
+  const fileDialogVisible = ref(false);
+  const recordId = ref(0);
+  const dialogFormVisible = ref(false);
+  const operationType = ref("");
+  const tableColumn = ref([
+    {
+      label: "鍚堝悓骞撮檺",
+      prop: "contractTerm",
+    },
+    {
+      label: "鍚堝悓寮�濮嬫棩鏈�",
+      prop: "contractStartTime",
+    },
+    {
+      label: "鍚堝悓缁撴潫鏃ユ湡",
+      prop: "contractEndTime",
+    },
+    {
+      dataType: "action",
+      label: "鎿嶄綔",
+      align: "center",
+      fixed: "right",
+      width: 120,
+      operation: [
+        {
+          name: "闄勪欢",
+          type: "text",
+          clickFun: row => {
+            recordId.value = row.id;
+            fileDialogVisible.value = true;
+          },
         },
-      }
-    ],
-  },
-]);
-const tableData = ref([]);
-const tableLoading = ref(false);
+      ],
+    },
+  ]);
+  const tableData = ref([]);
+  const tableLoading = ref(false);
 
-// 鎵撳紑寮规
-const openDialog = (type, row) => {
-  operationType.value = type;
-  dialogFormVisible.value = true;
-  if (operationType.value === 'edit') {
-    findStaffContractListPage({staffOnJobId: row.id}).then(res => {
-      tableData.value = res.data.records
-    })
-  }
-}
+  // 鎵撳紑寮规
+  const openDialog = (type, row) => {
+    operationType.value = type;
+    dialogFormVisible.value = true;
+    if (operationType.value === "edit") {
+      findStaffContractListPage({ staffOnJobId: row.id }).then(res => {
+        tableData.value = res.data.records;
+      });
+    }
+  };
 
-const openUploadFile = (row) => {
-  filesDia.value.open = true
-  filesDia.value.row = row
-}
-
-// 鍏抽棴寮规
-const closeDia = () => {
-  dialogFormVisible.value = false;
-  emit('close')
-};
-defineExpose({
-  openDialog,
-});
+  // 鍏抽棴寮规
+  const closeDia = () => {
+    dialogFormVisible.value = false;
+    emit("close");
+  };
+  defineExpose({
+    openDialog,
+  });
 </script>
 
 <style scoped>
-
 </style>
\ No newline at end of file

--
Gitblit v1.9.3