From 0333d66e4b397c161c6a44ce1e2a121c2cc41082 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 28 五月 2026 09:20:20 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' into dev_天津_中兴实强

---
 src/pages/oa/_utils/approvalFormField.js |  372 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 372 insertions(+), 0 deletions(-)

diff --git a/src/pages/oa/_utils/approvalFormField.js b/src/pages/oa/_utils/approvalFormField.js
new file mode 100644
index 0000000..813d05e
--- /dev/null
+++ b/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",
+};
+
+/** 瑙f瀽 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();
+}
+
+/** 鏃ユ湡鏃堕棿瀛楁绉嶇被锛歞ate | 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 }[]
+ * 鏀寔锛歰ptions / 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 || "鎵嬪姩閰嶇疆";
+}
+
+/** 瑙f瀽閫夐」鏉ユ簮锛歮anual | 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";
+}
+
+/** 瑙f瀽鏃ユ湡鏃堕棿鑼冨洿榛樿鍊硷細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;
+}
+
+/**
+ * 瑙f瀽涓嬫媺閫夐」锛堝惈浜哄憳/閮ㄩ棬鍔ㄦ�佹潵婧愶級
+ * @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;
+}

--
Gitblit v1.9.3