yyb
10 天以前 856f10107b7681f91114dc48991ebd121a3a8c3f
查询条件分类查询区分
已修改8个文件
358 ■■■■ 文件已修改
src/pages/oa/ApproveManage/approve-list/_components/ApproveInstanceDetailBody.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/ReimburseManage/cost-reimburse/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/ReimburseManage/travel-reimburse/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/_components/ApprovalInstanceListPage.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/_utils/approvalFormField.js 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/_utils/approvalModuleListSearch.js 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/_utils/approvalModuleRegistry.js 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/_utils/approveListUtils.js 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/ApproveManage/approve-list/_components/ApproveInstanceDetailBody.vue
@@ -146,6 +146,7 @@
    computeLeaveDurationDisplay,
    computeOvertimeHoursDisplay,
  } from "../../../_utils/approvalModuleApplyExtras.js";
  import { resolveInstanceFormPayload } from "../../../_utils/approvalModuleListSearch.js";
  import {
    businessStatusTagType,
    businessStatusText,
@@ -161,7 +162,6 @@
    taskStatusTagType,
    taskStatusText,
  } from "../../../_utils/approveListUtils.js";
  import { parseApprovalFormConfig } from "../../../_utils/approvalFormField.js";
  const props = defineProps({
    row: { type: Object, default: () => ({}) },
@@ -186,27 +186,25 @@
  const statusTagType = status =>
    isBusinessModule.value ? businessStatusTagType(status) : instanceStatusTagType(status);
  const displayFields = computed(() =>
    resolveInstanceDisplayFields(props.row?.formConfig)
  );
  const displayFields = computed(() => resolveInstanceDisplayFields(props.row));
  const moduleExtraRows = computed(() => {
    const rows = [];
    const cfg = parseApprovalFormConfig(props.row?.formConfig);
    const payload = {};
    (cfg.fields || []).forEach(f => {
      if (f?.key) payload[f.key] = f.value ?? "";
    const { fields, formPayload } = resolveInstanceFormPayload(props.row);
    const payload = { ...formPayload };
    (fields || []).forEach(f => {
      if (f?.key && payload[f.key] == null) payload[f.key] = f.value ?? "";
    });
    if (props.moduleKey === APPROVAL_MODULE_KEYS.LEAVE) {
      const balance = payload.leaveBalanceDays;
      if (balance != null && balance !== "") {
        rows.push({ label: "假期余额", value: `${balance} 天` });
      }
      const days = computeLeaveDurationDisplay(cfg.fields, payload);
      const days = computeLeaveDurationDisplay(fields, payload);
      if (days) rows.push({ label: "请假时长", value: `${days} 天` });
    }
    if (props.moduleKey === APPROVAL_MODULE_KEYS.OVERTIME) {
      const hours = computeOvertimeHoursDisplay(cfg.fields, payload);
      const hours = computeOvertimeHoursDisplay(fields, payload);
      if (hours) rows.push({ label: "加班时长", value: `${hours} 小时` });
    }
    if (props.moduleKey === APPROVAL_MODULE_KEYS.TRANSFER) {
src/pages/oa/ReimburseManage/cost-reimburse/index.vue
@@ -1,18 +1,11 @@
<!--
  OA / 报销管理 / 费用报销
  路由:/pages/oa/ReimburseManage/cost-reimburse/index
  OA / 报销管理 / 费用报销(审批实例列表)
-->
<template>
  <OaListPage v-if="config"
              :page-key="pageKey"
              :page-config="config" />
  <ApprovalInstanceListPage :module-key="APPROVAL_MODULE_KEYS.COST_REIMBURSE" />
</template>
<script setup>
  /** OA - 报销管理 - 费用报销 */
  import OaListPage from "../../_components/OaListPage.vue";
  import { useOaPage } from "../../_utils/useOaPage.js";
  const pageKey = "ReimburseManage/cost-reimburse";
  const { config } = useOaPage(pageKey);
  import ApprovalInstanceListPage from "../../_components/ApprovalInstanceListPage.vue";
  import { APPROVAL_MODULE_KEYS } from "../../_utils/approvalModuleRegistry.js";
</script>
src/pages/oa/ReimburseManage/travel-reimburse/index.vue
@@ -1,18 +1,11 @@
<!--
  OA / 报销管理 / 差旅报销
  路由:/pages/oa/ReimburseManage/travel-reimburse/index
  OA / 报销管理 / 差旅报销(审批实例列表)
-->
<template>
  <OaListPage v-if="config"
              :page-key="pageKey"
              :page-config="config" />
  <ApprovalInstanceListPage :module-key="APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE" />
</template>
<script setup>
  /** OA - 报销管理 - 差旅报销 */
  import OaListPage from "../../_components/OaListPage.vue";
  import { useOaPage } from "../../_utils/useOaPage.js";
  const pageKey = "ReimburseManage/travel-reimburse";
  const { config } = useOaPage(pageKey);
  import ApprovalInstanceListPage from "../../_components/ApprovalInstanceListPage.vue";
  import { APPROVAL_MODULE_KEYS } from "../../_utils/approvalModuleRegistry.js";
</script>
src/pages/oa/_components/ApprovalInstanceListPage.vue
@@ -119,7 +119,7 @@
  import { fetchApprovalTemplateTypes } from "../_utils/approvalTemplateType.js";
  import {
    getApprovalModuleConfig,
    resolveModuleBusinessType,
    getModuleListBusinessType,
  } from "../_utils/approvalModuleRegistry.js";
  import {
    buildModuleListDto,
@@ -260,15 +260,12 @@
  };
  const initBusinessType = async () => {
    const fixed = getModuleListBusinessType(props.moduleKey);
    businessType.value = fixed != null && fixed !== "" ? fixed : "";
    try {
      typeOptions.value = await fetchApprovalTemplateTypes();
      const resolved = resolveModuleBusinessType(props.moduleKey, typeOptions.value);
      businessType.value =
        resolved != null && resolved !== ""
          ? resolved
          : moduleConfig.value?.approvalType ?? "";
    } catch {
      businessType.value = moduleConfig.value?.approvalType ?? "";
      typeOptions.value = [];
    }
  };
src/pages/oa/_utils/approvalFormField.js
@@ -17,17 +17,25 @@
  datetime: "YYYY-MM-DD HH:mm:ss",
};
/** 解析 formConfig JSON */
/** 解析 formConfig JSON(含嵌套 formPayload,与 Web parseInstanceFormConfig 一致) */
export function parseApprovalFormConfig(raw) {
  if (!raw) return { prompt: "", fields: [] };
  if (!raw) return { prompt: "", fields: [], formPayload: {} };
  try {
    const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
    const payload = obj?.formPayload;
    return {
      prompt: obj?.prompt || "",
      fields: Array.isArray(obj?.fields) ? obj.fields : [],
      prompt: obj?.prompt || obj?.summaryPlaceholder || "",
      summaryPlaceholder: obj?.summaryPlaceholder || "",
      approvalType: obj?.approvalType || "",
      fields: Array.isArray(obj?.fields)
        ? obj.fields
        : Array.isArray(obj?.formFields)
          ? obj.formFields
          : [],
      formPayload: payload && typeof payload === "object" ? payload : {},
    };
  } catch {
    return { prompt: "", fields: [] };
    return { prompt: "", fields: [], formPayload: {} };
  }
}
@@ -45,6 +53,7 @@
      valueMap[field.key] = val;
    }
  });
  Object.assign(valueMap, instance.formPayload || {});
  const baseFields = template.fields.length ? template.fields : instance.fields;
  return {
    prompt: instance.prompt || template.prompt,
src/pages/oa/_utils/approvalModuleListSearch.js
@@ -4,7 +4,10 @@
  getApprovalModuleConfig,
  getModuleMatchingBusinessTypes,
} from "./approvalModuleRegistry.js";
import { parseApprovalFormConfig } from "./approvalFormField.js";
import {
  parseApprovalFormConfig,
  parseDatetimerangeValue,
} from "./approvalFormField.js";
import { matchBusinessTypeValue } from "./approvalTemplateType.js";
/** 与 Web leave-apply LEAVE_TYPE_OPTIONS 一致 */
@@ -49,16 +52,53 @@
  return payload;
}
function guessFieldTypeFromValue(val) {
  if (Array.isArray(val)) return "datetimerange";
  if (typeof val === "number") return "number";
  return "text";
}
/** 解析实例 formConfig / formPayload(与 Web resolveInstanceFormFields 对齐) */
export function resolveInstanceFormPayload(row) {
  const cfg = parseApprovalFormConfig(row?.formConfig);
  const fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || [];
  let fields = (row?.formFieldDefs?.length ? row.formFieldDefs : cfg.fields) || [];
  const formPayload = {
    ...(fields.length ? buildFormPayloadFromFields(fields) : {}),
    ...cfg.formPayload,
    ...(row?.formPayload || {}),
  };
  if (!fields.length && Object.keys(formPayload).length) {
    fields = Object.keys(formPayload)
      .filter(k => k && k !== "summary")
      .map(k => ({
        key: k,
        label: k,
        type: guessFieldTypeFromValue(formPayload[k]),
        required: false,
        options: [],
      }));
  }
  fields = fields.map(field => ({
    ...field,
    value:
      formPayload[field.key] ?? field.value ?? field.defaultValue ?? "",
  }));
  return { fields, formPayload };
}
/** 已知下拉字段 value → 展示文案(模板未带 options 时兜底) */
export function formatKnownSelectLabel(prop, val) {
  if (val === undefined || val === null || val === "") return "";
  const maps = {
    leaveType: LEAVE_TYPE_OPTIONS,
    overtimeType: OVERTIME_TYPE_OPTIONS,
    handoverStatus: HANDOVER_STATUS_OPTIONS,
    handoverType: HANDOVER_TYPE_OPTIONS,
  };
  const options = maps[prop];
  if (!options) return "";
  const hit = options.find(o => String(o.value) === String(val));
  return hit?.label || "";
}
export function getRowPayloadValue(row, keys) {
@@ -69,6 +109,73 @@
    if (formPayload[k] != null && formPayload[k] !== "") return formPayload[k];
  }
  return "";
}
const DATETIME_RANGE_KEYS = [
  "dateRange",
  "leaveTime",
  "overtimeTime",
  "timeRange",
];
function pickDatetimerangeRaw(formPayload, fields = []) {
  for (const key of DATETIME_RANGE_KEYS) {
    const v = formPayload?.[key];
    if (v != null && v !== "") return v;
    const field = (fields || []).find(f => f?.key === key);
    const fv = field?.value ?? field?.defaultValue;
    if (fv != null && fv !== "") return fv;
  }
  const rangeField = (fields || []).find(
    f => String(f?.type || "").toLowerCase() === "datetimerange"
  );
  if (rangeField?.key) {
    const v =
      formPayload?.[rangeField.key] ?? rangeField.value ?? rangeField.defaultValue;
    if (v != null && v !== "") return v;
  }
  return null;
}
function splitRangeValue(val) {
  if (val === undefined || val === null || val === "") {
    return { start: "", end: "" };
  }
  if (Array.isArray(val)) {
    return { start: val[0] || "", end: val[1] || "" };
  }
  return parseDatetimerangeValue(val);
}
/**
 * 列表列 prop 与 formPayload 对齐(请假 startTime/endTime 来自 dateRange)
 */
export function resolveListFieldRawValue(prop, row, fields = [], formPayload = {}) {
  const payload = formPayload || {};
  const direct = payload[prop] ?? row?.[prop];
  if (prop === "startTime" || prop === "endTime") {
    if (direct != null && direct !== "") return direct;
    const altStart =
      payload.start ?? payload.startDate ?? payload.beginTime ?? row?.startTime;
    const altEnd =
      payload.end ?? payload.endDate ?? payload.finishTime ?? row?.endTime;
    if (prop === "startTime" && altStart) return altStart;
    if (prop === "endTime" && altEnd) return altEnd;
    const { start, end } = splitRangeValue(pickDatetimerangeRaw(payload, fields));
    return prop === "startTime" ? start : end;
  }
  if (prop === "overtimeDate") {
    const d = payload.overtimeDate ?? payload.date ?? direct;
    if (d != null && d !== "") return Array.isArray(d) ? d[0] || "" : d;
    const { start } = splitRangeValue(pickDatetimerangeRaw(payload, fields));
    return start || "";
  }
  if (direct != null && direct !== "") return direct;
  const hit = (fields || []).find(f => f?.key === prop);
  return hit?.value ?? hit?.defaultValue ?? "";
}
function pickDateRange(searchForm) {
@@ -95,6 +202,10 @@
      return { applicantKeyword: "", leaveType: "" };
    case APPROVAL_MODULE_KEYS.OVERTIME:
      return { applicantKeyword: "", overtimeType: "" };
    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
      return { applicantKeyword: "" };
    default:
      return {};
  }
@@ -218,6 +329,10 @@
          matchApplicantKeyword(row, sf.applicantKeyword) &&
          matchSelectValue(row, ["overtimeType", "加班类型"], sf.overtimeType)
      );
    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
      return list.filter(row => matchApplicantKeyword(row, sf.applicantKeyword));
    default:
      return list;
  }
@@ -292,6 +407,19 @@
          },
        ],
      };
    case APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE:
    case APPROVAL_MODULE_KEYS.COST_REIMBURSE:
    case APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS:
      return {
        fields: [
          {
            key: "applicantKeyword",
            type: "input",
            label: "申请人",
            placeholder: "姓名或编号",
          },
        ],
      };
    default:
      return { fields: [] };
  }
src/pages/oa/_utils/approvalModuleRegistry.js
@@ -6,12 +6,28 @@
  WORK_HANDOVER: "work_handover",
  LEAVE: "leave",
  OVERTIME: "overtime",
  TRAVEL_REIMBURSE: "travel_reimburse",
  COST_REIMBURSE: "cost_reimburse",
  ENTERPRISE_NEWS: "enterprise_news",
};
/** 审批实例 listPage businessType(与后端约定一致) */
export const APPROVAL_BUSINESS_TYPE = {
  [APPROVAL_MODULE_KEYS.REGULAR]: 10,
  [APPROVAL_MODULE_KEYS.TRANSFER]: 11,
  [APPROVAL_MODULE_KEYS.WORK_HANDOVER]: 13,
  [APPROVAL_MODULE_KEYS.LEAVE]: 14,
  [APPROVAL_MODULE_KEYS.OVERTIME]: 15,
  [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16,
  [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17,
  [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18,
};
export const APPROVAL_MODULE_REGISTRY = {
  [APPROVAL_MODULE_KEYS.REGULAR]: {
    label: "转正申请",
    approvalType: "regular",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.REGULAR],
    typeLabels: ["转正", "转正申请"],
    listFields: [
      { label: "入职日期", prop: "entryDate" },
@@ -21,6 +37,7 @@
  [APPROVAL_MODULE_KEYS.TRANSFER]: {
    label: "调岗申请",
    approvalType: "transfer",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.TRANSFER],
    typeLabels: ["调岗", "调动", "调岗申请", "调动申请"],
    listFields: [
      { label: "原岗位", prop: "fromPost" },
@@ -30,6 +47,7 @@
  [APPROVAL_MODULE_KEYS.WORK_HANDOVER]: {
    label: "工作交接",
    approvalType: "work_handover",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.WORK_HANDOVER],
    typeLabels: ["工作交接", "交接", "工作交接审批"],
    listFields: [
      { label: "交接人", prop: "handoverTo" },
@@ -39,6 +57,7 @@
  [APPROVAL_MODULE_KEYS.LEAVE]: {
    label: "请假申请",
    approvalType: "leave",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.LEAVE],
    typeLabels: ["请假", "请假申请", "请假审批"],
    listFields: [
      { label: "请假类型", prop: "leaveType" },
@@ -49,11 +68,33 @@
  [APPROVAL_MODULE_KEYS.OVERTIME]: {
    label: "加班申请",
    approvalType: "overtime",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.OVERTIME],
    typeLabels: ["加班", "加班申请", "加班审批"],
    listFields: [
      { label: "加班日期", prop: "overtimeDate" },
      { label: "时长(小时)", prop: "hours" },
    ],
  },
  [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: {
    label: "差旅报销",
    approvalType: "travel_reimburse",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE],
    typeLabels: ["差旅", "差旅报销", "出差报销"],
    listFields: [],
  },
  [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: {
    label: "费用报销",
    approvalType: "cost_reimburse",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.COST_REIMBURSE],
    typeLabels: ["费用", "费用报销"],
    listFields: [],
  },
  [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: {
    label: "企业新闻",
    approvalType: "enterprise_news",
    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS],
    typeLabels: ["企业新闻", "新闻", "新闻发布"],
    listFields: [],
  },
};
@@ -66,7 +107,7 @@
  const cfg = getApprovalModuleConfig(moduleKey);
  if (!cfg) return "";
  if (cfg.businessType != null && cfg.businessType !== "") return cfg.businessType;
  return cfg.approvalType || "";
  return APPROVAL_BUSINESS_TYPE[moduleKey] ?? "";
}
function matchBiz(a, b) {
@@ -77,7 +118,9 @@
export function resolveModuleBusinessType(moduleKey, typeOptions = []) {
  const cfg = getApprovalModuleConfig(moduleKey);
  if (!cfg) return null;
  if (cfg.businessType != null && cfg.businessType !== "") return cfg.businessType;
  const fixed = getModuleListBusinessType(moduleKey);
  if (fixed != null && fixed !== "") return fixed;
  const labels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
  const hitByLabel = (typeOptions || []).find(opt => {
@@ -97,17 +140,19 @@
    if (hitByValue?.value != null && hitByValue.value !== "") return hitByValue.value;
  }
  return cfg.approvalType || null;
  return null;
}
export function getModuleMatchingBusinessTypes(moduleKey, typeOptions = []) {
  const cfg = getApprovalModuleConfig(moduleKey);
  if (!cfg) return [];
  const fixed = getModuleListBusinessType(moduleKey);
  if (fixed != null && fixed !== "") return [fixed];
  const values = new Set();
  const primary = resolveModuleBusinessType(moduleKey, typeOptions);
  if (primary != null && primary !== "") values.add(primary);
  if (cfg.approvalType) values.add(cfg.approvalType);
  const labels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
  for (const opt of typeOptions || []) {
@@ -123,13 +168,15 @@
  return [...values];
}
/** 列表页 moduleKey 与路由 pageKey 对照 */
export const PAGE_KEY_TO_MODULE = {
  "HrManage/regular-apply": APPROVAL_MODULE_KEYS.REGULAR,
  "HrManage/transfer-apply": APPROVAL_MODULE_KEYS.TRANSFER,
  "HrManage/work-handover": APPROVAL_MODULE_KEYS.WORK_HANDOVER,
  "AttendManage/leave-apply": APPROVAL_MODULE_KEYS.LEAVE,
  "AttendManage/overtime-apply": APPROVAL_MODULE_KEYS.OVERTIME,
  "ReimburseManage/travel-reimburse": APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE,
  "ReimburseManage/cost-reimburse": APPROVAL_MODULE_KEYS.COST_REIMBURSE,
  "EnterpriseNews/news-manage": APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS,
};
export function getModuleKeyFromPageKey(pageKey) {
src/pages/oa/_utils/approveListUtils.js
@@ -5,6 +5,11 @@
  isSelectField,
  mergeFormConfigForEdit,
} from "./approvalFormField.js";
import {
  formatKnownSelectLabel,
  resolveInstanceFormPayload,
  resolveListFieldRawValue,
} from "./approvalModuleListSearch.js";
export const DETAIL_STORAGE_KEY = "oa_approve_instance_detail_row";
@@ -57,9 +62,19 @@
  return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}") || String(val);
}
/** 解析实例 formConfig 为只读展示字段 */
export function resolveInstanceDisplayFields(formConfig) {
  const merged = mergeFormConfigForEdit("", formConfig);
/** 解析实例为只读展示字段(合并 formPayload,支持传整行或仅 formConfig) */
export function resolveInstanceDisplayFields(formConfigOrRow) {
  const row =
    formConfigOrRow &&
    typeof formConfigOrRow === "object" &&
    (formConfigOrRow.formConfig != null ||
      formConfigOrRow.formPayload != null ||
      formConfigOrRow.formFieldDefs != null)
      ? formConfigOrRow
      : { formConfig: formConfigOrRow };
  const { fields } = resolveInstanceFormPayload(row);
  if (fields.length) return fields.filter(f => f?.key);
  const merged = mergeFormConfigForEdit("", row.formConfig);
  return (merged.fields || []).filter(f => f?.key);
}
@@ -67,10 +82,39 @@
  const val = field.value ?? field.defaultValue;
  if (val === undefined || val === null || val === "") return "-";
  if (isSelectField(field)) {
    return getFieldOptionLabel(field, val) || String(val);
    const fromOptions = getFieldOptionLabel(field, val);
    if (fromOptions && fromOptions !== "-") return fromOptions;
    const known = formatKnownSelectLabel(field.key, val);
    if (known) return known;
    return String(val);
  }
  const known = formatKnownSelectLabel(field?.key, val);
  if (known) return known;
  const shown = formatFieldDisplayValue(field, val);
  return shown || String(val);
}
const DATETIME_LIST_PROPS = new Set([
  "startTime",
  "endTime",
  "overtimeDate",
  "applyTime",
]);
function formatListFieldDisplay(prop, val, field) {
  if (val === undefined || val === null || val === "") return "-";
  if (DATETIME_LIST_PROPS.has(prop)) {
    const shown = formatDateTime(val);
    if (shown && shown !== "-") return shown;
  }
  if (field?.type === "datetimerange") {
    const shown = formatFieldDisplayValue(field, val);
    if (shown) return shown;
  }
  if (field) return displayFieldValue({ ...field, value: val });
  const known = formatKnownSelectLabel(prop, val);
  if (known) return known;
  return String(val);
}
/** 审批记录 result:approved | rejected | pending */
@@ -279,18 +323,23 @@
  };
}
/** 从 formConfig 提取列表展示字段(label + value) */
export function buildFormDisplayRows(formConfig, listFields = []) {
  const fields = resolveInstanceDisplayFields(formConfig);
/** 从实例行提取列表展示字段(label + value,含 formPayload) */
export function buildFormDisplayRows(row, listFields = []) {
  const { fields, formPayload } = resolveInstanceFormPayload(row);
  const fieldByKey = new Map((fields || []).map(f => [f.key, f]));
  const rows = [];
  const propKeys = (listFields || []).map(f => f.prop).filter(Boolean);
  const defs = listFields || [];
  if (propKeys.length) {
    propKeys.forEach(prop => {
      const hit = fields.find(f => f.key === prop);
      if (hit) {
        rows.push({ label: hit.label, value: displayFieldValue(hit) });
      }
  if (defs.length) {
    defs.forEach(def => {
      if (!def?.prop) return;
      const prop = def.prop;
      const hit = fieldByKey.get(prop);
      const raw = resolveListFieldRawValue(prop, row, fields, formPayload);
      rows.push({
        label: def.label || hit?.label || prop,
        value: formatListFieldDisplay(prop, raw, hit),
      });
    });
  } else {
    fields.slice(0, 3).forEach(f => {
@@ -303,17 +352,14 @@
/** 列表行增强(保留原始字段供详情/编辑) */
export function mapInstanceListRow(row, listFields = []) {
  if (!row) return {};
  const displayRows = buildFormDisplayRows(row.formConfig, listFields);
  const displayRows = buildFormDisplayRows(row, listFields);
  const extra = {};
  const formFields = resolveInstanceDisplayFields(row.formConfig);
  const { fields, formPayload } = resolveInstanceFormPayload(row);
  (listFields || []).forEach(def => {
    if (!def?.prop) return;
    const hit = formFields.find(f => f.key === def.prop);
    extra[def.prop] = hit ? displayFieldValue(hit) : "-";
  });
  const formPayload = {};
  formFields.forEach(f => {
    if (f?.key) formPayload[f.key] = f.value ?? f.defaultValue ?? "";
    const hit = fields.find(f => f.key === def.prop);
    const raw = resolveListFieldRawValue(def.prop, row, fields, formPayload);
    extra[def.prop] = formatListFieldDisplay(def.prop, raw, hit);
  });
  return {
    ...row,
@@ -322,6 +368,7 @@
    createTime: formatDateTime(row.applyTime || row.createTime),
    displayRows,
    formPayload,
    formFieldDefs: fields,
    ...extra,
  };
}