import type {
|
ControlType,
|
DropdownOption,
|
RawTypeConfig,
|
TypeConfigValidationField,
|
ValidatedDropdownConfig,
|
ValidatedTypeConfig,
|
ValidationResult,
|
} from './types';
|
|
const SUPPORTED_CONTROL_TYPES = ['text', 'dropdown', 'checkbox', 'date', 'multiline'] as const satisfies readonly ControlType[];
|
|
export const CONTROL_TYPES: readonly ControlType[] = Object.freeze([...SUPPORTED_CONTROL_TYPES]);
|
|
function isControlType(value: unknown): value is ControlType {
|
return SUPPORTED_CONTROL_TYPES.some((controlType) => controlType === value);
|
}
|
|
export function normalizeControlType(value: unknown): ControlType {
|
return isControlType(value) ? value : 'text';
|
}
|
|
function isStrictDate(value: string): boolean {
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
if (!match) {
|
return false;
|
}
|
|
const year = Number(match[1]);
|
const month = Number(match[2]);
|
const day = Number(match[3]);
|
const date = new Date(0);
|
|
if (year < 1) {
|
return false;
|
}
|
|
date.setUTCHours(0, 0, 0, 0);
|
date.setUTCFullYear(year, month - 1, day);
|
|
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
}
|
|
function validateDropdown(raw: RawTypeConfig, placeholder: string): ValidationResult<ValidatedDropdownConfig, TypeConfigValidationField> {
|
const options: DropdownOption[] = (raw.options ?? []).map((item) => ({
|
display: String(item.display || '').trim(),
|
value: String(item.value || '').trim(),
|
}));
|
|
if (options.length < 2 || options.some((item) => !item.display || !item.value)) {
|
return {
|
ok: false,
|
field: 'dropdown-options',
|
message: '下拉框至少需要两个完整选项',
|
};
|
}
|
|
const values = options.map((item) => item.value);
|
const hasDuplicate = values.some((value, index) => values.indexOf(value) !== index);
|
|
if (hasDuplicate) {
|
return {
|
ok: false,
|
field: 'dropdown-options',
|
message: '下拉选项业务值不能重复',
|
};
|
}
|
|
const defaultValue = String(raw.defaultOptionValue || '').trim();
|
const selectedIndex = defaultValue ? values.indexOf(defaultValue) : -1;
|
|
if (defaultValue && selectedIndex === -1) {
|
return {
|
ok: false,
|
field: 'dropdown-default',
|
message: '默认项必须来自下拉选项',
|
};
|
}
|
|
return {
|
ok: true,
|
value: {
|
controlType: 'dropdown',
|
placeholder,
|
options,
|
selectedIndex,
|
},
|
};
|
}
|
|
export function validateTypeConfig(raw: RawTypeConfig | null | undefined): ValidationResult<ValidatedTypeConfig, TypeConfigValidationField> {
|
if (!raw) {
|
return {
|
ok: false,
|
field: 'control-type',
|
message: '请选择有效的字段类型',
|
};
|
}
|
|
const type = raw.controlType;
|
|
if (!isControlType(type)) {
|
return {
|
ok: false,
|
field: 'control-type',
|
message: '请选择有效的字段类型',
|
};
|
}
|
|
const placeholder = String(raw.placeholder || '').trim();
|
const defaultValue = String(raw.defaultValue || '');
|
|
if (type === 'dropdown') {
|
return validateDropdown(raw, placeholder);
|
}
|
|
if (type === 'text' && /[\r\n]/.test(defaultValue)) {
|
return {
|
ok: false,
|
field: 'text-default',
|
message: '单行文本默认值不能换行',
|
};
|
}
|
|
if (type === 'date' && defaultValue && !isStrictDate(defaultValue)) {
|
return {
|
ok: false,
|
field: 'date-default',
|
message: '请输入有效的日期',
|
};
|
}
|
|
return {
|
ok: true,
|
value: {
|
controlType: type,
|
placeholder,
|
defaultValue,
|
defaultChecked: Boolean(raw.defaultChecked),
|
},
|
};
|
}
|
|
function hasExactTag(control: unknown, tag: string): boolean {
|
return typeof control === 'object' && control !== null && 'Tag' in control && control.Tag === tag;
|
}
|
|
export function countTag(controls: unknown, tag: string): number {
|
if (!Array.isArray(controls)) {
|
return 0;
|
}
|
|
return (controls as readonly unknown[]).filter((control) => hasExactTag(control, tag)).length;
|
}
|