gaoluyang
2026-05-28 0333d66e4b397c161c6a44ce1e2a121c2cc41082
src/pages/oa/_utils/approvalFormField.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
import { parseTime } from "@/utils/ruoyi";
/** å¡«æŠ¥å­—段类型:下拉 */
export const SELECT_FIELD_TYPES = new Set(["select", "dropdown", "picker"]);
/** æ—¥æœŸæ—¶é—´ç±» type */
const DATE_KIND_BY_TYPE = {
  date: "date",
  time: "time",
  datetime: "datetime",
  datetimerange: "datetime",
};
const DEFAULT_FORMAT = {
  date: "YYYY-MM-DD",
  time: "HH:mm",
  datetime: "YYYY-MM-DD HH:mm:ss",
};
/** è§£æž formConfig JSON(含嵌套 formPayload,与 Web parseInstanceFormConfig ä¸€è‡´ï¼‰ */
export function parseApprovalFormConfig(raw) {
  if (!raw) return { prompt: "", fields: [], formPayload: {} };
  try {
    const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
    const payload = obj?.formPayload;
    return {
      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: [], formPayload: {} };
  }
}
/**
 * ä¿®æ”¹å®¡æ‰¹ï¼šæ¨¡æ¿å­—段定义 + å®žä¾‹å·²å¡«å€¼åˆå¹¶
 */
export function mergeFormConfigForEdit(templateRaw, instanceRaw) {
  const template = parseApprovalFormConfig(templateRaw);
  const instance = parseApprovalFormConfig(instanceRaw);
  const valueMap = {};
  instance.fields.forEach(field => {
    if (!field?.key) return;
    const val = field.value ?? field.defaultValue;
    if (val !== undefined && val !== null && val !== "") {
      valueMap[field.key] = val;
    }
  });
  Object.assign(valueMap, instance.formPayload || {});
  const baseFields = template.fields.length ? template.fields : instance.fields;
  return {
    prompt: instance.prompt || template.prompt,
    fields: baseFields.map(field => ({
      ...field,
      value: valueMap[field.key] ?? field.value ?? field.defaultValue ?? "",
    })),
  };
}
/** æ˜¯å¦ä¸ºä¸‹æ‹‰ç±»å­—段 */
export function isSelectField(field) {
  const type = String(field?.type ?? "").toLowerCase();
  return SELECT_FIELD_TYPES.has(type);
}
/** æ˜¯å¦ä¸ºå¤šè¡Œæ–‡æœ¬ */
export function isTextareaField(field) {
  return String(field?.type ?? "").toLowerCase() === "textarea";
}
/** è¯»å–字段配置的日期格式(兼容 format / dateFormat / timeFormat) */
export function getFieldFormatStr(field) {
  return (
    field?.format ?? field?.dateFormat ?? field?.timeFormat ?? ""
  ).trim();
}
/** æ—¥æœŸæ—¶é—´å­—段种类:date | time | datetime | null */
export function getDateFieldKind(field) {
  const type = String(field?.type ?? "").toLowerCase();
  if (DATE_KIND_BY_TYPE[type]) return DATE_KIND_BY_TYPE[type];
  const fmt = getFieldFormatStr(field);
  if (!fmt) return null;
  const hasDate = /Y{2,4}|D{1,2}/i.test(fmt);
  const hasTime = /H{1,2}|h{1,2}|m{1,2}|s{1,2}/i.test(fmt);
  if (hasDate && hasTime) return "datetime";
  if (hasTime && !hasDate) return "time";
  if (hasDate) return "date";
  return null;
}
/** æ˜¯å¦ä¸ºæ—¥æœŸ/时间类字段(不含日期时间范围) */
export function isDateLikeField(field) {
  if (isDatetimerangeField(field)) return false;
  return !!getDateFieldKind(field);
}
/** @deprecated ä½¿ç”¨ isDateLikeField */
export function isDateField(field) {
  return isDateLikeField(field);
}
/** uView datetime-picker çš„ mode */
export function getDatePickerMode(field) {
  const kind = getDateFieldKind(field);
  if (kind === "time") return "time";
  if (kind === "datetime") return "datetime";
  return "date";
}
/** moment é£Žæ ¼æ ¼å¼ â†’ parseTime æ¨¡æ¿ */
export function momentFormatToParsePattern(fmt) {
  if (!fmt) return null;
  return fmt
    .replace(/YYYY/g, "{y}")
    .replace(/YY/g, "{y}")
    .replace(/DD/g, "{d}")
    .replace(/dd/g, "{d}")
    .replace(/MM/g, "{m}")
    .replace(/HH/g, "{h}")
    .replace(/hh/g, "{h}")
    .replace(/mm/g, "{i}")
    .replace(/ss/g, "{s}");
}
/** å°†æ—¶é—´æˆ³/Date æ ¼å¼åŒ–为字段配置格式 */
export function formatFieldDateValue(field, dateSource) {
  const kind = getDateFieldKind(field);
  if (!kind) return "";
  const fmt = getFieldFormatStr(field) || DEFAULT_FORMAT[kind];
  const pattern = momentFormatToParsePattern(fmt);
  let date;
  if (typeof dateSource === "number") date = new Date(dateSource);
  else if (dateSource instanceof Date) date = dateSource;
  else return String(dateSource ?? "");
  return parseTime(date, pattern) || "";
}
/** å±•示用:将已存值按配置格式回显 */
export function formatFieldDisplayValue(field, storedValue) {
  if (storedValue === undefined || storedValue === null || storedValue === "") {
    return "";
  }
  if (!getDateFieldKind(field)) return String(storedValue);
  const ts = parseFieldDateToTs(storedValue);
  if (ts) return formatFieldDateValue(field, ts);
  return String(storedValue);
}
/** å°†å·²å­˜æ—¥æœŸå­—符串转为时间戳(供选择器初始值) */
export function parseFieldDateToTs(value) {
  if (value === undefined || value === null || value === "") return null;
  if (typeof value === "number") return value;
  const str = String(value).trim();
  const normalized = str.replace(/-/g, "/").replace("T", " ");
  const t = new Date(normalized).getTime();
  return Number.isNaN(t) ? null : t;
}
/** æ˜¯å¦ä¸ºæ•°å­— */
export function isNumberField(field) {
  return String(field?.type ?? "").toLowerCase() === "number";
}
/**
 * å°†å­—段配置中的选项规范为 { label, value }[]
 * æ”¯æŒï¼šoptions / optionList;项为字符串或 { label|name|text, value|key|code }
 */
export function normalizeFieldOptions(field) {
  const raw =
    field?.options ?? field?.optionList ?? field?.dictOptions ?? field?.items;
  if (!Array.isArray(raw) || !raw.length) return [];
  return raw
    .map((item, index) => {
      if (item == null) return null;
      if (typeof item === "string" || typeof item === "number") {
        const text = String(item);
        return { label: text, value: text };
      }
      if (typeof item !== "object") return null;
      const label =
        item.label ??
        item.name ??
        item.text ??
        item.dictLabel ??
        item.title;
      const rawValue =
        item.value ?? item.key ?? item.code ?? item.dictValue ?? item.id;
      if (label == null && rawValue == null) return null;
      const value =
        rawValue !== undefined && rawValue !== null ? rawValue : label ?? index;
      return {
        label: String(label ?? value),
        value,
      };
    })
    .filter(Boolean);
}
/** æŒ‰å­˜å‚¨å€¼åŒ¹é…é€‰é¡¹å±•示文案 */
export function getFieldOptionLabel(field, storedValue) {
  if (storedValue === undefined || storedValue === null || storedValue === "") {
    return "";
  }
  const options = normalizeFieldOptions(field);
  const strVal = String(storedValue);
  const matched = options.find(
    opt =>
      String(opt.value) === strVal ||
      String(opt.label) === strVal
  );
  return matched?.label ?? "";
}
/** åˆå§‹åŒ–填报值:优先已填 value,其次 defaultValue */
export function getFieldInitialValue(field) {
  if (field?.value !== undefined && field?.value !== null && field?.value !== "") {
    return field.value;
  }
  if (
    field?.defaultValue !== undefined &&
    field?.defaultValue !== null &&
    field?.defaultValue !== ""
  ) {
    return field.defaultValue;
  }
  return "";
}
/** æ¨¡æ¿ç¼–辑:控件类型选项(与 Web ç«¯ä¸€è‡´ï¼‰ */
export const FIELD_EDITOR_TYPE_OPTIONS = [
  { name: "单行文本", value: "text" },
  { name: "多行文本", value: "textarea" },
  { name: "数字", value: "number" },
  { name: "日期", value: "date" },
  { name: "日期时间范围", value: "datetimerange" },
  { name: "下拉选择", value: "select" },
];
/** ä¸‹æ‹‰é€‰é¡¹æ¥æº */
export const FIELD_OPTION_SOURCE_OPTIONS = [
  { name: "手动配置", value: "manual" },
  { name: "人员列表", value: "user" },
  { name: "部门列表", value: "dept" },
];
const OPTION_SOURCE_ALIASES = {
  manual: "manual",
  user: "user",
  personnel: "user",
  userlist: "user",
  dept: "dept",
  department: "dept",
  deptlist: "dept",
};
export function getFieldEditorTypeLabel(type) {
  const found = FIELD_EDITOR_TYPE_OPTIONS.find(
    item => String(item.value) === String(type)
  );
  return found?.name || type || "-";
}
export function getFieldOptionSourceLabel(source) {
  const key = getFieldOptionSource(source);
  const found = FIELD_OPTION_SOURCE_OPTIONS.find(item => item.value === key);
  return found?.name || "手动配置";
}
/** è§£æžé€‰é¡¹æ¥æºï¼šmanual | user | dept */
export function getFieldOptionSource(fieldOrSource) {
  const raw =
    typeof fieldOrSource === "object"
      ? fieldOrSource?.optionSource
      : fieldOrSource;
  const key = String(raw ?? "manual")
    .trim()
    .toLowerCase();
  return OPTION_SOURCE_ALIASES[key] || "manual";
}
export function isDatetimerangeField(field) {
  return String(field?.type ?? "").toLowerCase() === "datetimerange";
}
/** è§£æžæ—¥æœŸæ—¶é—´èŒƒå›´é»˜è®¤å€¼ï¼šstart,end */
export function parseDatetimerangeValue(stored) {
  if (stored === undefined || stored === null || stored === "") {
    return { start: "", end: "" };
  }
  const parts = String(stored)
    .split(",")
    .map(s => s.trim());
  return { start: parts[0] || "", end: parts[1] || "" };
}
export function joinDatetimerangeValue(start, end) {
  const s = String(start ?? "").trim();
  const e = String(end ?? "").trim();
  if (!s && !e) return "";
  return `${s},${e}`;
}
export function formatDatetimerangeDisplay(stored) {
  const { start, end } = parseDatetimerangeValue(stored);
  if (!start && !end) return "";
  if (start && end) return `${start} è‡³ ${end}`;
  return start || end;
}
/**
 * è§£æžä¸‹æ‹‰é€‰é¡¹ï¼ˆå«äººå‘˜/部门动态来源)
 * @param {object} field
 * @param {{ users?: array, depts?: array }} context
 */
export function resolveFieldOptions(field, context = {}) {
  const source = getFieldOptionSource(field);
  if (source === "user") {
    return (context.users || []).map(user => ({
      label: user.nickName || user.userName || String(user.userId ?? ""),
      value: user.userId,
    }));
  }
  if (source === "dept") {
    return (context.depts || []).map(dept => ({
      label: dept.deptName || dept.name || String(dept.deptId ?? dept.id ?? ""),
      value: dept.deptId ?? dept.id,
    }));
  }
  return normalizeFieldOptions(field);
}
export function createEmptyFieldOption() {
  return { label: "", value: "" };
}
/** å°†ç¼–辑草稿规范为可提交的字段对象 */
export function buildFieldConfigPayload(draft, existingKey) {
  const payload = {
    key: (draft.key || existingKey || "").trim(),
    label: (draft.label || "").trim(),
    type: draft.type || "text",
    required: !!draft.required,
    defaultValue: String(draft.defaultValue ?? "").trim(),
  };
  if (isSelectField(payload)) {
    payload.optionSource = getFieldOptionSource(draft.optionSource);
    if (payload.optionSource === "manual") {
      payload.options = (draft.options || [])
        .map(opt => ({
          label: String(opt?.label ?? "").trim(),
          value: String(opt?.value ?? "").trim(),
        }))
        .filter(opt => opt.label && opt.value);
    } else {
      delete payload.options;
    }
  }
  return payload;
}