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;
|
}
|