huminmin
3 天以前 31acf8d8830c5b8b33e0599018544aa4d68e351b
Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
已添加2个文件
已修改8个文件
759 ■■■■ 文件已修改
multiple/assets/favicon/HQJCfavicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/HQJCLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue 180 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceFormConfigTable.js 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalInstanceListSearch.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/HrManage/staff-contract/components/formDia.vue 151 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/contractManagement/components/formDia.vue 151 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/HQJCfavicon.ico
multiple/assets/logo/HQJCLogo.png
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": "旭晨电器管理系统",
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: [],
  };
}
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>
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 生成主表动态列(label 取自模板字段 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",
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;
  });
}
src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -1,5 +1,3 @@
import { matchBusinessTypeValue } from "../approve-list/approveListConstants.js";
/**
 * 各业务模块与审批模板类型的映射(配置化入口)
 * businessType 与后端 TypeEnums / listPage 约定一致(写死枚举值)
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>
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>