| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |