| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { mapAttachmentsFromApi } from "./approveTemplateConstants.js"; |
| | | import { |
| | | isDynamicOptionSource, |
| | | SELECT_OPTION_SOURCE, |
| | | selectOptionSourceLabel, |
| | | } from "./selectOptionSource.js"; |
| | | |
| | | export { selectOptionSourceLabel }; |
| | | |
| | | /** å¡«æ¥é¡¹ç±»åï¼ä¸å®¡æ¹æäº¤é¡µ field.type ä¸è´ï¼ */ |
| | | export const FORM_FIELD_TYPE_OPTIONS = [ |
| | | { value: "text", label: "åè¡ææ¬" }, |
| | | { value: "textarea", label: "å¤è¡ææ¬" }, |
| | | { value: "number", label: "æ°å" }, |
| | | { value: "date", label: "æ¥æ" }, |
| | | { value: "datetimerange", label: "æ¥ææ¶é´èå´" }, |
| | | { value: "select", label: "䏿鿩" }, |
| | | ]; |
| | | |
| | | /** 常ç¨é¢è®¾ï¼å¦è´¹ç¨æ¥éï¼ */ |
| | | export const FORM_CONFIG_PRESETS = [ |
| | | { |
| | | key: "cost_reimburse", |
| | | label: "è´¹ç¨æ¥é", |
| | | summaryPlaceholder: "è¯·å¡«åæ¥éäºç±ãéé¢ç", |
| | | fields: [ |
| | | { key: "summary", label: "æ¥é说æ", type: "textarea", required: true, rows: 3 }, |
| | | { key: "amount", label: "æ¥ééé¢(å
)", type: "number", required: true, min: 0, precision: 2 }, |
| | | ], |
| | | }, |
| | | { |
| | | key: "travel_reimburse", |
| | | label: "å·®æ
æ¥é", |
| | | summaryPlaceholder: "åºå·®è¡ç¨ä¸è´¹ç¨è¯´æ", |
| | | fields: [ |
| | | { key: "summary", label: "å·®æ
说æ", type: "textarea", required: true, rows: 3 }, |
| | | { key: "amount", label: "æ¥ééé¢(å
)", type: "number", required: true, min: 0, precision: 2 }, |
| | | { key: "tripDays", label: "åºå·®å¤©æ°", type: "number", required: false, min: 0, precision: 0 }, |
| | | ], |
| | | }, |
| | | { |
| | | key: "leave", |
| | | label: "请åç³è¯·", |
| | | summaryPlaceholder: "请填å请åç±»å䏿¶é´", |
| | | fields: [ |
| | | { |
| | | key: "leaveType", |
| | | label: "请åç±»å", |
| | | type: "select", |
| | | required: true, |
| | | options: [ |
| | | { label: "å¹´å", value: "annual" }, |
| | | { label: "ç
å", value: "sick" }, |
| | | { label: "äºå", value: "personal" }, |
| | | { label: "è°ä¼", value: "compensatory" }, |
| | | ], |
| | | }, |
| | | { key: "summary", label: "请åäºç±", type: "textarea", required: true, rows: 2 }, |
| | | { key: "dateRange", label: "è¯·åæ¶é´", type: "datetimerange", required: true }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | function newFieldUid() { |
| | | return `f_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; |
| | | } |
| | | |
| | | export function createEmptyFormField() { |
| | | return { |
| | | _uid: newFieldUid(), |
| | | key: "", |
| | | label: "", |
| | | type: "text", |
| | | required: true, |
| | | rows: 3, |
| | | min: 0, |
| | | precision: 0, |
| | | defaultValue: "", |
| | | optionSource: SELECT_OPTION_SOURCE.STATIC, |
| | | options: [{ label: "", value: "" }], |
| | | }; |
| | | } |
| | | |
| | | /** è§£æå项é»è®¤å¼ï¼ä¾æäº¤é¡µ formPayload åå§åï¼ */ |
| | | export function resolveFieldDefaultValue(field) { |
| | | const type = field?.type || "text"; |
| | | const dv = field?.defaultValue; |
| | | if (dv === undefined || dv === null || dv === "") { |
| | | if (type === "number") return undefined; |
| | | if (type === "datetimerange") return []; |
| | | return ""; |
| | | } |
| | | if (type === "number") { |
| | | const n = Number(dv); |
| | | return Number.isNaN(n) ? undefined : n; |
| | | } |
| | | if (type === "datetimerange") { |
| | | return Array.isArray(dv) ? [...dv] : []; |
| | | } |
| | | return dv; |
| | | } |
| | | |
| | | function hasDefaultValue(field) { |
| | | const type = field?.type || "text"; |
| | | const dv = field?.defaultValue; |
| | | if (dv === undefined || dv === null) return false; |
| | | if (type === "number") return dv !== "" && !Number.isNaN(Number(dv)); |
| | | if (type === "datetimerange") return Array.isArray(dv) && dv.length === 2; |
| | | if (type === "select") return dv !== ""; |
| | | return String(dv).trim() !== ""; |
| | | } |
| | | |
| | | /** æ ¹æ®å段å®ä¹çæ formPayload åå§å¼ï¼å«é»è®¤å¼ï¼ */ |
| | | export function buildFormPayloadFromFields(fields) { |
| | | const payload = {}; |
| | | (fields || []).forEach((f) => { |
| | | const key = (f.key || "").trim(); |
| | | if (!key) return; |
| | | payload[key] = resolveFieldDefaultValue(f); |
| | | }); |
| | | return payload; |
| | | } |
| | | |
| | | export function createEmptyFormConfigData() { |
| | | return { |
| | | summaryPlaceholder: "", |
| | | fields: [], |
| | | }; |
| | | } |
| | | |
| | | function parseFormConfigRaw(formConfig) { |
| | | if (!formConfig) return {}; |
| | | if (typeof formConfig === "object") return formConfig; |
| | | try { |
| | | return JSON.parse(formConfig); |
| | | } catch { |
| | | return {}; |
| | | } |
| | | } |
| | | |
| | | function normalizeDefaultValueFromApi(f) { |
| | | const type = f.type || "text"; |
| | | if (f.defaultValue === undefined || f.defaultValue === null) { |
| | | if (type === "number") return undefined; |
| | | if (type === "datetimerange") return []; |
| | | return ""; |
| | | } |
| | | if (type === "datetimerange" && Array.isArray(f.defaultValue)) { |
| | | return [...f.defaultValue]; |
| | | } |
| | | return f.defaultValue; |
| | | } |
| | | |
| | | /** æ¥å£ formConfig â ç¼è¾å¨æ°æ® */ |
| | | export function parseFormConfigToData(formConfig) { |
| | | const raw = parseFormConfigRaw(formConfig); |
| | | const fields = (raw.fields || raw.formFields || []).map((f) => ({ |
| | | _uid: newFieldUid(), |
| | | key: f.key || "", |
| | | label: f.label || "", |
| | | type: f.type || "text", |
| | | required: f.required !== false, |
| | | rows: f.rows ?? 3, |
| | | min: f.min ?? 0, |
| | | precision: f.precision ?? 0, |
| | | defaultValue: normalizeDefaultValueFromApi(f), |
| | | optionSource: f.optionSource || SELECT_OPTION_SOURCE.STATIC, |
| | | options: (f.options || []).length |
| | | ? f.options.map((o) => ({ label: o.label || "", value: o.value ?? "" })) |
| | | : [{ label: "", value: "" }], |
| | | })); |
| | | return { |
| | | summaryPlaceholder: raw.summaryPlaceholder || "", |
| | | fields, |
| | | }; |
| | | } |
| | | |
| | | /** ç¼è¾å¨æ°æ® â æäº¤ç¨ JSON å符串 */ |
| | | export function buildFormConfigJson(formConfigData) { |
| | | const data = formConfigData || createEmptyFormConfigData(); |
| | | const fields = (data.fields || []).map((f) => { |
| | | const item = { |
| | | key: (f.key || "").trim(), |
| | | label: (f.label || "").trim(), |
| | | type: f.type || "text", |
| | | required: f.required !== false, |
| | | }; |
| | | if (item.type === "textarea") item.rows = Number(f.rows) || 3; |
| | | if (item.type === "number") { |
| | | item.min = f.min ?? 0; |
| | | item.precision = f.precision ?? 0; |
| | | } |
| | | if (item.type === "select") { |
| | | const source = f.optionSource || SELECT_OPTION_SOURCE.STATIC; |
| | | item.optionSource = source; |
| | | if (!isDynamicOptionSource(source)) { |
| | | item.options = (f.options || []) |
| | | .filter((o) => (o.label || "").trim() || (o.value !== "" && o.value != null)) |
| | | .map((o) => ({ label: (o.label || "").trim(), value: o.value })); |
| | | } |
| | | } |
| | | if (hasDefaultValue(f)) { |
| | | item.defaultValue = |
| | | f.type === "datetimerange" && Array.isArray(f.defaultValue) |
| | | ? f.defaultValue |
| | | : f.defaultValue; |
| | | } |
| | | return item; |
| | | }); |
| | | const payload = { |
| | | summaryPlaceholder: (data.summaryPlaceholder || "").trim(), |
| | | fields, |
| | | }; |
| | | return JSON.stringify(payload); |
| | | } |
| | | |
| | | export function applyFormConfigPreset(presetKey) { |
| | | const preset = FORM_CONFIG_PRESETS.find((p) => p.key === presetKey); |
| | | if (!preset) return createEmptyFormConfigData(); |
| | | return parseFormConfigToData({ |
| | | summaryPlaceholder: preset.summaryPlaceholder, |
| | | fields: preset.fields, |
| | | }); |
| | | } |
| | | |
| | | export function validateFormConfigData(formConfigData) { |
| | | const fields = formConfigData?.fields || []; |
| | | if (!fields.length) { |
| | | return { ok: true }; |
| | | } |
| | | const keys = new Set(); |
| | | for (let i = 0; i < fields.length; i++) { |
| | | const f = fields[i]; |
| | | const key = (f.key || "").trim(); |
| | | const label = (f.label || "").trim(); |
| | | if (!key) return { ok: false, message: `请填å第 ${i + 1} 个填æ¥é¡¹çåæ®µæ è¯` }; |
| | | if (!label) return { ok: false, message: `请填å第 ${i + 1} 个填æ¥é¡¹çæ¾ç¤ºåç§°` }; |
| | | if (keys.has(key)) return { ok: false, message: `åæ®µæ è¯ã${key}ãéå¤ï¼è¯·ä¿®æ¹` }; |
| | | keys.add(key); |
| | | if (f.type === "select") { |
| | | const source = f.optionSource || SELECT_OPTION_SOURCE.STATIC; |
| | | if (isDynamicOptionSource(source)) continue; |
| | | const opts = (f.options || []).filter((o) => (o.label || "").trim() && o.value !== "" && o.value != null); |
| | | if (!opts.length) return { ok: false, message: `请为ã${label}ãé
ç½®è³å°ä¸ä¸ªä¸æé项` }; |
| | | } |
| | | } |
| | | return { ok: true }; |
| | | } |
| | | |
| | | export function formFieldTypeLabel(type) { |
| | | return FORM_FIELD_TYPE_OPTIONS.find((x) => x.value === type)?.label || type || "â"; |
| | | } |
| | | |
| | | export function formatDefaultValueDisplay(field) { |
| | | const dv = field?.defaultValue; |
| | | if (dv === undefined || dv === null || dv === "") return "â"; |
| | | if (field?.type === "datetimerange" && Array.isArray(dv)) { |
| | | return dv.length === 2 ? `${dv[0]} ~ ${dv[1]}` : "â"; |
| | | } |
| | | if (field?.type === "select") { |
| | | if (isDynamicOptionSource(field.optionSource)) { |
| | | return `${selectOptionSourceLabel(field.optionSource)}ï¼${String(dv)}`; |
| | | } |
| | | const opt = (field.options || []).find((o) => String(o.value) === String(dv)); |
| | | return opt?.label || String(dv); |
| | | } |
| | | return String(dv); |
| | | } |
| | | |
| | | /** å°å端模æ¿è¡è½¬ä¸ºæäº¤é¡µæ¨¡æ¿ç»æï¼å« fields é»è®¤å¼ãéä»¶ï¼ */ |
| | | export function buildSubmitTemplateFromRow(row) { |
| | | const cfg = parseFormConfigToData(row?.formConfig); |
| | | const fields = (cfg.fields || []).map(({ _uid, ...rest }) => ({ |
| | | ...rest, |
| | | key: rest.key, |
| | | label: rest.label, |
| | | type: rest.type, |
| | | required: rest.required, |
| | | rows: rest.rows, |
| | | min: rest.min, |
| | | precision: rest.precision, |
| | | defaultValue: rest.defaultValue, |
| | | optionSource: rest.optionSource, |
| | | options: rest.options, |
| | | })); |
| | | return { |
| | | label: row?.templateName || "审æ¹", |
| | | businessType: row?.businessType ?? cfg.approvalType ?? "", |
| | | approvalType: cfg.approvalType || "", |
| | | summaryPlaceholder: cfg.summaryPlaceholder || "", |
| | | approvalMode: cfg.approvalMode || "parallel", |
| | | fields, |
| | | storageBlobDTOs: mapAttachmentsFromApi(row), |
| | | }; |
| | | } |
| | | |
| | | export function formConfigFieldsSummary(formConfigData) { |
| | | const fields = formConfigData?.fields || []; |
| | | if (!fields.length) return "â"; |
| | | return fields.map((f) => f.label || f.key || "æªå½å").join("ã"); |
| | | } |