@fx-form/utils
Version:
vue json schema form 使用的基础utils工具类
424 lines (374 loc) • 12.5 kB
JavaScript
import retrieveSchema from './schema/retriev';
import { getPathVal } from './vueUtils';
import { getSchemaType, isObject } from './utils';
// 通用的处理表达式方法
// 这里打破 JSON Schema 规范
const regExpression = /{{(.*)}}/;
function handleExpression(rootFormData, curNodePath, expression, fallBack) {
// 未配置
if (undefined === expression) {
return undefined;
}
// 配置了 mustache 表达式
const matchExpression = regExpression.exec(expression);
regExpression.lastIndex = 0; // 重置索引
if (matchExpression) {
const code = matchExpression[1].trim();
// eslint-disable-next-line no-new-func
const fn = new Function('parentFormData', 'rootFormData', `return ${code}`);
return fn(getPathVal(rootFormData, curNodePath, 1), rootFormData);
}
// 回退
return fallBack();
}
export function replaceArrayIndex({ schema, uiSchema } = {}, index) {
const itemUiOptions = getUiOptions({
schema,
uiSchema,
containsSpec: false
});
return ['title', 'description'].reduce((preVal, curItem) => {
if (itemUiOptions[curItem]) {
preVal[`ui:${curItem}`] = String(itemUiOptions[curItem]).replace(/\$index/g, index + 1);
}
return preVal;
}, {});
}
// 是否为 hidden Widget
export function isHiddenWidget({
schema = {},
uiSchema = {},
curNodePath = '',
rootFormData = {}
}) {
const widget = uiSchema['ui:widget'] || schema['ui:widget'];
const hiddenExpression = uiSchema['ui:hidden'] || schema['ui:hidden'];
// 支持配置 ui:hidden 表达式
return widget === 'HiddenWidget'
|| widget === 'hidden'
|| !!handleExpression(rootFormData, curNodePath, hiddenExpression, () => {
// 配置了函数 function
if (typeof hiddenExpression === 'function') {
return hiddenExpression(getPathVal(rootFormData, curNodePath, 1), rootFormData);
}
// 配置了常量 ??
return hiddenExpression;
});
}
// 解析当前节点 ui field
export function getUiField(FIELDS_MAP, {
schema = {},
uiSchema = {},
}) {
const field = schema['ui:field'] || uiSchema['ui:field'];
// vue 组件,或者已注册的组件名
if (typeof field === 'function' || typeof field === 'object' || typeof field === 'string') {
return {
field,
fieldProps: uiSchema['ui:fieldProps'] || schema['ui:fieldProps'], // 自定义field ,支持传入额外的 props
};
}
// 类型默认 field
const fieldCtor = FIELDS_MAP[getSchemaType(schema)];
if (fieldCtor) {
return {
field: fieldCtor
};
}
// 如果包含 oneOf anyOf 返回空不异常
// SchemaField 会附加onyOf anyOf信息
if (!fieldCtor && (schema.anyOf || schema.oneOf)) {
return {
field: null
};
}
// 不支持的类型
throw new Error(`不支持的field类型 ${schema.type}`);
}
// 解析用户配置的 uiSchema options
export function getUserUiOptions({
schema = {},
uiSchema = {},
curNodePath, // undefined 不处理 表达式
rootFormData = {}
}) {
// 支持 uiSchema配置在 schema文件中
return Object.assign({}, ...[schema, uiSchema].map(itemSchema => Object.keys(itemSchema)
.reduce((options, key) => {
const value = itemSchema[key];
// options 内外合并
if (key === 'ui:options' && isObject(value)) {
return { ...options, ...value };
}
if (key.indexOf('ui:') === 0) {
// 只对 ui:xxx 配置形式支持表达式
return {
...options,
[key.substring(3)]: curNodePath === undefined ? value : handleExpression(rootFormData, curNodePath, value, () => value)
};
}
return options;
}, {})));
}
// 解析当前节点的ui options参数
export function getUiOptions({
schema = {},
uiSchema = {},
containsSpec = true,
curNodePath,
rootFormData,
}) {
const spec = {};
if (containsSpec) {
spec.readonly = !!schema.readOnly;
if (undefined !== schema.multipleOf) {
// 组件计数器步长
spec.step = schema.multipleOf;
}
if (schema.minimum || schema.minimum === 0) {
spec.min = schema.minimum;
}
if (schema.maximum || schema.maximum === 0) {
spec.max = schema.maximum;
}
if (schema.minLength || schema.minLength === 0) {
spec.minlength = schema.minLength;
}
if (schema.maxLength || schema.maxLength === 0) {
spec.maxlength = schema.maxLength;
}
if (schema.format === 'date-time' || schema.format === 'date') {
// 数组类型 时间区间
// 打破了schema的规范,type array 配置了 format
if (schema.type === 'array') {
spec.isRange = true;
spec.isNumberValue = !(schema.items && schema.items.type === 'string');
} else {
// 字符串 ISO 时间
spec.isNumberValue = !(schema.type === 'string');
}
}
}
// 计算ui配置
return {
title: schema.title /* || curNodePath.split('.').pop() */, // 默认使用 schema 的配置
description: schema.description,
// 特殊处理部分
...spec,
// 用户配置最高优先级
...getUserUiOptions({
schema,
uiSchema,
curNodePath,
rootFormData
})
};
}
// 获取当前节点的ui 配置 (options + widget)
// 处理成 Widget 组件需要的格式
export function getWidgetConfig({
schema = {},
uiSchema = {},
curNodePath,
rootFormData,
}, fallback = null) {
const uiOptions = getUiOptions({
schema,
uiSchema,
curNodePath,
rootFormData,
});
// 没有配置 Widget ,各个Field组件根据类型判断
if (!uiOptions.widget && fallback) {
Object.assign(uiOptions, fallback({
schema,
uiSchema
}));
}
const {
widget,
title: label,
labelWidth,
description,
attrs: widgetAttrs,
class: widgetClass,
style: widgetStyle,
fieldAttrs,
fieldStyle,
fieldClass,
emptyValue,
width,
getWidget,
onChange,
...uiProps
} = uiOptions;
return {
widget,
label,
labelWidth,
description,
widgetAttrs,
widgetClass,
widgetStyle,
fieldAttrs,
width,
fieldStyle,
fieldClass,
emptyValue,
getWidget,
onChange,
uiProps
};
}
// 解析用户配置的 errorSchema options
export function getUserErrOptions({
schema = {},
uiSchema = {},
errorSchema = {}
}) {
return Object.assign({}, ...[schema, uiSchema, errorSchema].map(itemSchema => Object.keys(itemSchema)
.reduce((options, key) => {
const value = itemSchema[key];
// options 内外合并
if (key === 'err:options' && isObject(value)) {
return { ...options, ...value };
}
if (key.indexOf('err:') === 0) {
return { ...options, [key.substring(4)]: value };
}
return options;
}, {})));
}
// ui:order object-> properties 排序
export function orderProperties(properties, order) {
if (!Array.isArray(order)) {
return properties;
}
const arrayToHash = arr => arr.reduce((prev, curr) => {
prev[curr] = true;
return prev;
}, {});
const errorPropList = arr => (arr.length > 1
? `properties '${arr.join("', '")}'`
: `property '${arr[0]}'`);
const propertyHash = arrayToHash(properties);
const orderFiltered = order.filter(
prop => prop === '*' || propertyHash[prop]
);
const orderHash = arrayToHash(orderFiltered);
const rest = properties.filter(prop => !orderHash[prop]);
const restIndex = orderFiltered.indexOf('*');
if (restIndex === -1) {
if (rest.length) {
throw new Error(
`uiSchema order list does not contain ${errorPropList(rest)}`
);
}
return orderFiltered;
}
if (restIndex !== orderFiltered.lastIndexOf('*')) {
throw new Error('uiSchema order list contains more than one wildcard item');
}
const complete = [...orderFiltered];
complete.splice(restIndex, 1, ...rest);
return complete;
}
/**
* 单个匹配
* 常量,或者只有一个枚举
*/
export function isConstant(schema) {
return (
(Array.isArray(schema.enum) && schema.enum.length === 1)
|| schema.hasOwnProperty('const')
);
}
export function toConstant(schema) {
if (Array.isArray(schema.enum) && schema.enum.length === 1) {
return schema.enum[0];
} if (schema.hasOwnProperty('const')) {
return schema.const;
}
throw new Error('schema cannot be inferred as a constant');
}
/**
* 是否为选择列表
* 枚举 或者 oneOf anyOf 每项都只有一个固定常量值
* @param _schema
* @param rootSchema
* @returns {boolean|*}
*/
export function isSelect(_schema, rootSchema = {}) {
const schema = retrieveSchema(_schema, rootSchema);
const altSchemas = schema.oneOf || schema.anyOf;
if (Array.isArray(schema.enum)) {
return true;
} if (Array.isArray(altSchemas)) {
return altSchemas.every(altSchemasItem => isConstant(altSchemasItem));
}
return false;
}
// items 都为一个对象
export function isFixedItems(schema) {
return (
Array.isArray(schema.items)
&& schema.items.length > 0
&& schema.items.every(item => isObject(item))
);
}
// 是否为多选
export function isMultiSelect(schema, rootSchema = {}) {
if (!schema.uniqueItems || !schema.items) {
return false;
}
return isSelect(schema.items, rootSchema);
}
// array additionalItems
// https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation
export function allowAdditionalItems(schema) {
if (schema.additionalItems === true) {
console.warn('additionalItems=true is currently not supported');
}
return isObject(schema.additionalItems);
}
// 下拉选项
export function optionsList(schema, uiSchema, curNodePath, rootFormData) {
// enum
if (schema.enum) {
const uiOptions = getUserUiOptions({
schema,
uiSchema,
curNodePath,
rootFormData
});
// ui配置 enumNames 优先
const enumNames = uiOptions.enumNames || schema.enumNames;
return schema.enum.map((value, i) => {
const label = (enumNames && enumNames[i]) || String(value);
return { label, value };
});
}
// oneOf | anyOf
const altSchemas = schema.oneOf || schema.anyOf;
const altUiSchemas = uiSchema.oneOf || uiSchema.anyOf;
return altSchemas.map((curSchema, i) => {
const uiOptions = (altUiSchemas && altUiSchemas[i]) ? getUserUiOptions({
schema: curSchema,
uiSchema: altUiSchemas[i],
curNodePath,
rootFormData
}) : {};
const value = toConstant(curSchema);
const label = uiOptions.title || curSchema.title || String(value);
return { label, value };
});
}
export function fallbackLabel(oriLabel, isFallback, curNodePath) {
if (oriLabel) return oriLabel;
if (isFallback) {
const backLabel = curNodePath.split('.').pop();
// 过滤纯数字字符串
if (backLabel && (backLabel !== `${Number(backLabel)}`)) return backLabel;
}
return '';
}