gaoluyang
2026-05-28 0333d66e4b397c161c6a44ce1e2a121c2cc41082
src/pages/oa/_utils/approvalModuleListSearch.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,456 @@
import {
  APPROVAL_MODULE_KEYS,
  APPROVAL_MODULE_REGISTRY,
  getApprovalModuleConfig,
  getModuleMatchingBusinessTypes,
} from "./approvalModuleRegistry.js";
import {
  parseApprovalFormConfig,
  parseDatetimerangeValue,
} from "./approvalFormField.js";
import { matchBusinessTypeValue } from "./approvalTemplateType.js";
/** ä¸Ž Web leave-apply LEAVE_TYPE_OPTIONS ä¸€è‡´ */
export const LEAVE_TYPE_OPTIONS = [
  { label: "年假", value: "annual" },
  { label: "病假", value: "sick" },
  { label: "事假", value: "personal" },
  { label: "婚假", value: "marriage" },
  { label: "产假", value: "maternity" },
  { label: "哺乳假", value: "nursing" },
  { label: "慰唁假", value: "condolence" },
  { label: "调休", value: "compensatory" },
];
/** ä¸Ž Web overtime-apply OVERTIME_TYPE_OPTIONS ä¸€è‡´ */
export const OVERTIME_TYPE_OPTIONS = [
  { label: "工作日加班", value: "weekday" },
  { label: "休息日加班", value: "weekend" },
  { label: "法定节假日加班", value: "holiday" },
];
export const HANDOVER_STATUS_OPTIONS = [
  { label: "进行中", value: "in_progress" },
  { label: "已完成", value: "completed" },
  { label: "已退回", value: "returned" },
];
export const HANDOVER_TYPE_OPTIONS = [
  { label: "离职交接", value: "resignation" },
  { label: "调岗交接", value: "transfer" },
];
function buildFormPayloadFromFields(fields = []) {
  const payload = {};
  for (const f of fields) {
    if (!f?.key) continue;
    const val = f.value ?? f.defaultValue;
    if (val !== undefined && val !== null && val !== "") {
      payload[f.key] = val;
    }
  }
  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);
  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) {
  const keyList = Array.isArray(keys) ? keys : [keys];
  const { formPayload } = resolveInstanceFormPayload(row);
  for (const k of keyList) {
    if (row?.[k] != null && row[k] !== "") return row[k];
    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 ?? "";
}
/** æ‰å¹³åŒ–为 Spring GET å¯ç»‘定的 query(approvalInstanceDto.xxx,勿用方括号) */
export function appendDotNotationQuery(target, prefix, fields) {
  if (!fields || typeof fields !== "object") return;
  for (const [key, value] of Object.entries(fields)) {
    if (value == null || value === "") continue;
    target[`${prefix}.${key}`] = value;
  }
}
function pickApplicantFromSearchForm(searchForm = {}) {
  const out = {};
  const sf = searchForm || {};
  const name = (sf.applicantName || "").trim();
  const kw = (sf.applicantKeyword || "").trim();
  const id = sf.applicantId;
  if (name) out.applicantName = name;
  if (kw) {
    out.applicantName = kw;
    if (/^\d+$/.test(kw)) out.applicantId = Number(kw);
  }
  if (id != null && id !== "") {
    out.applicantId = typeof id === "number" ? id : Number(id) || id;
  }
  return out;
}
function pickInstanceNoFromSearchForm(searchForm = {}) {
  const no = (searchForm?.instanceNo || "").trim();
  return no ? { instanceNo: no } : {};
}
/** æ”¯æŒå®¡æ‰¹å•号查询的审批申请模块 */
export const INSTANCE_NO_SEARCH_MODULE_KEYS = new Set([
  APPROVAL_MODULE_KEYS.REGULAR,
  APPROVAL_MODULE_KEYS.TRANSFER,
  APPROVAL_MODULE_KEYS.WORK_HANDOVER,
  APPROVAL_MODULE_KEYS.LEAVE,
  APPROVAL_MODULE_KEYS.OVERTIME,
]);
const INSTANCE_NO_SEARCH_FIELD = {
  key: "instanceNo",
  type: "input",
  label: "审批单号",
  placeholder: "请输入审批单号",
};
/** ç»„装 approvalInstanceDto æŸ¥è¯¢ç‰‡æ®µï¼ˆç”³è¯·äºº + å®¡æ‰¹å•号) */
export function buildApprovalInstanceSearchDto(searchForm = {}, extraParams = {}) {
  const dto = {
    ...(extraParams && typeof extraParams === "object" ? extraParams : {}),
  };
  Object.assign(dto, pickApplicantFromSearchForm(searchForm));
  Object.assign(dto, pickInstanceNoFromSearchForm(searchForm));
  delete dto.createTime;
  delete dto.createTimeStart;
  delete dto.createTimeEnd;
  return dto;
}
function pickDateRange(searchForm) {
  return buildApprovalInstanceSearchDto(searchForm);
}
/** å„模块默认查询表单(与 Web searchForm å­—段一致) */
const APPLICANT_ONLY_MODULE_KEYS = new Set([
  APPROVAL_MODULE_KEYS.REGULAR,
  APPROVAL_MODULE_KEYS.TRANSFER,
  APPROVAL_MODULE_KEYS.WORK_HANDOVER,
  APPROVAL_MODULE_KEYS.LEAVE,
  APPROVAL_MODULE_KEYS.OVERTIME,
  APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE,
  APPROVAL_MODULE_KEYS.COST_REIMBURSE,
]);
function withInstanceNoSearch(moduleKey, base) {
  if (INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey)) {
    return { instanceNo: "", ...base };
  }
  return base;
}
function applicantOnlySearchForm(moduleKey) {
  if (moduleKey === APPROVAL_MODULE_KEYS.REGULAR) {
    return withInstanceNoSearch(moduleKey, { applicantName: "" });
  }
  if (
    moduleKey === APPROVAL_MODULE_KEYS.TRANSFER ||
    moduleKey === APPROVAL_MODULE_KEYS.WORK_HANDOVER
  ) {
    return withInstanceNoSearch(moduleKey, { applicantId: "" });
  }
  if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) {
    return withInstanceNoSearch(moduleKey, { applicantKeyword: "" });
  }
  return {};
}
export function createModuleSearchForm(moduleKey) {
  if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) {
    return applicantOnlySearchForm(moduleKey);
  }
  if (moduleKey === APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS) {
    return { applicantKeyword: "" };
  }
  return {};
}
/** æœåŠ¡ç«¯ listPage DTO ç‰‡æ®µï¼ˆä¸Ž Web buildApprovalInstanceListParams ä¸€è‡´ï¼‰ */
export function buildModuleListDto(moduleKey, searchForm = {}) {
  return buildApprovalInstanceSearchDto(searchForm);
}
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));
}
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 !== "";
}
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));
}
function matchSelectValue(row, keys, expected) {
  if (!expected) return true;
  const raw = getRowPayloadValue(row, keys);
  return String(raw) === String(expected);
}
function matchApplicantId(row, applicantId) {
  if (!applicantId) return true;
  const id = String(applicantId);
  if (row?.applicantId != null && String(row.applicantId) === id) return true;
  const payloadApplicant = getRowPayloadValue(row, [
    "applicant",
    "applicantId",
    "applicantUserId",
  ]);
  return String(payloadApplicant) === id;
}
/** æŒ‰æ¨¡å— businessType / æ ‡é¢˜å½’属过滤(服务端未生效时的兜底) */
export function filterRowsByModuleBusinessType(moduleKey, rows, typeOptions = []) {
  const cfg = getApprovalModuleConfig(moduleKey);
  if (!cfg) return rows;
  const types = getModuleMatchingBusinessTypes(moduleKey, typeOptions);
  const myLabels = [cfg.label, ...(cfg.typeLabels || [])].filter(Boolean);
  return (rows || []).filter(row => {
    if (types.length && row?.businessType != null && row.businessType !== "") {
      if (types.some(t => matchBusinessTypeValue(row.businessType, t))) {
        return true;
      }
    }
    const title = String(row?.title || row?.templateName || "").trim();
    if (title) {
      if (myLabels.some(l => title === l || title.includes(l))) return true;
      for (const [key, other] of Object.entries(APPROVAL_MODULE_REGISTRY)) {
        if (key === moduleKey) continue;
        const otherLabels = [other.label, ...(other.typeLabels || [])].filter(Boolean);
        if (otherLabels.some(l => title === l || (l.length > 2 && title.includes(l)))) {
          return false;
        }
      }
    }
    return types.length === 0;
  });
}
/** æŒ‰ç”³è¯·äººã€å®¡æ‰¹å•号做前端兜底筛选 */
export function filterRowsByModuleSearch(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)
  );
}
function prependInstanceNoField(fields, moduleKey) {
  if (!INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey)) return fields;
  return [INSTANCE_NO_SEARCH_FIELD, ...fields];
}
/** æ¨¡å—筛选 UI é…ç½® */
export function getModuleSearchMeta(moduleKey) {
  if (moduleKey === APPROVAL_MODULE_KEYS.REGULAR) {
    return {
      fields: prependInstanceNoField(
        [
          { key: "applicantName", type: "input", label: "申请人", placeholder: "请输入申请人" },
        ],
        moduleKey
      ),
    };
  }
  if (
    moduleKey === APPROVAL_MODULE_KEYS.TRANSFER ||
    moduleKey === APPROVAL_MODULE_KEYS.WORK_HANDOVER
  ) {
    return {
      fields: prependInstanceNoField(
        [
          { key: "applicantId", type: "user", label: "申请人", placeholder: "请选择申请人" },
        ],
        moduleKey
      ),
    };
  }
  if (APPLICANT_ONLY_MODULE_KEYS.has(moduleKey)) {
    return {
      fields: prependInstanceNoField(
        [
          {
            key: "applicantKeyword",
            type: "input",
            label: "申请人",
            placeholder: "姓名或编号",
          },
        ],
        moduleKey
      ),
    };
  }
  return { fields: [] };
}
export function resetModuleSearchForm(moduleKey, target) {
  const defaults = createModuleSearchForm(moduleKey);
  Object.keys(target).forEach(k => {
    if (!(k in defaults)) delete target[k];
  });
  Object.assign(target, defaults);
}
export function formatDateRangeLabel(range) {
  if (!Array.isArray(range) || !range[0]) return "";
  if (range[1]) return `${range[0]} è‡³ ${range[1]}`;
  return range[0];
}
export function userSelectLabel(u) {
  const nick = u?.nickName || "";
  const name = u?.userName || "";
  if (nick && name && nick !== name) return `${nick}(${name})`;
  return nick || name || `用户${u?.userId ?? u?.id ?? ""}`;
}