@lljj/vue3-form-core
Version:
基于 Vue3 、JsonSchema快速构建一个带完整校验的form表单,vue3版本基础框架
304 lines (271 loc) • 12.6 kB
JavaScript
/**
* Created by Liu.Jun on 2020/5/19 10:15 下午.
*/
import { ref, watch, h } from 'vue';
import {
getPathVal, setPathVal, deletePathVal, nodePath2ClassName
} from '@lljj/vjsf-utils/vue3Utils';
import {
isEmptyObject, filterObject, isObject, getSchemaType
} from '@lljj/vjsf-utils/utils';
import {
getWidgetConfig, getUiOptions, getUserErrOptions
} from '@lljj/vjsf-utils/formUtils';
import retrieveSchema from '@lljj/vjsf-utils/schema/retriev';
import getDefaultFormState from '@lljj/vjsf-utils/schema/getDefaultFormState';
import { getMatchingOption, isValid } from '@lljj/vjsf-utils/schema/validate';
import vueProps from '../../props';
import Widget from '../../../components/Widget';
import SchemaField from '../../SchemaField';
export default {
name: 'SelectLinkageField',
props: {
...vueProps,
combiningType: {
type: String,
default: 'anyOf' // anyOf oneOf
},
selectList: {
type: Array,
require: true
}
},
setup(props) {
const computedCurSelectIndexByFormData = (formData) => {
const index = getMatchingOption(formData, props.selectList, props.rootSchema, true);
return index || 0;
};
// 当前选中 option 项
const curSelectIndex = ref(computedCurSelectIndexByFormData(getPathVal(props.rootFormData, props.curNodePath)));
// 下拉选项 VNode
const getSelectBoxVNode = () => {
// 下拉选项参数
const selectWidgetConfig = getWidgetConfig({
schema: props.schema[`${props.combiningType}Select`] || {}, // 扩展 oneOfSelect,anyOfSelect字段
uiSchema: props.uiSchema[`${props.combiningType}Select`] || {}, // 通过 uiSchema['oneOf'] 配置ui信息
curNodePath: props.curNodePath,
rootFormData: props.rootFormData,
}, () => ({
// 枚举参数
widget: 'SelectWidget'
}));
// title description 回退到 schema 配置,但这里不使用 uiSchema配置
// select ui配置需要使用 (oneOf|anyOf)Select
selectWidgetConfig.label = selectWidgetConfig.label || props.schema.title;
selectWidgetConfig.description = selectWidgetConfig.description || props.schema.description;
// 下拉列表枚举值
if (!selectWidgetConfig.uiProps.enumOptions) {
const uiSchemaSelectList = props.uiSchema[props.combiningType] || [];
selectWidgetConfig.uiProps.enumOptions = props.selectList.map((option, index) => {
const curUiOptions = getUiOptions({
schema: option,
uiSchema: uiSchemaSelectList[index],
containsSpec: false,
// curNodePath: props.curNodePath,
// rootFormData: props.rootFormData,
});
return {
label: curUiOptions.title || `选项 ${index + 1}`,
value: index,
};
});
}
// oneOf option 渲染
// 选择框 VNode
return h(
Widget,
{
key: `fieldSelect_${props.combiningType}`,
class: {
[`fieldSelect_${props.combiningType}`]: true
},
isFormData: false,
curValue: curSelectIndex.value,
curNodePath: props.curNodePath,
rootFormData: props.rootFormData,
globalOptions: props.globalOptions,
...selectWidgetConfig,
onOtherDataChange: (event) => {
curSelectIndex.value = event;
}
}
);
};
// 对象 切换了select
// 如果object 类型 option有添加属性 这里做移除
// 对新option计算默认值
watch(curSelectIndex, (newVal, oldVal) => {
const curFormData = getPathVal(props.rootFormData, props.curNodePath);
// 计算出 新选项默认值
const newOptionData = getDefaultFormState(props.selectList[newVal], undefined, props.rootSchema);
const hasOwn = Object.prototype.hasOwnProperty;
// 移除旧key
if (isObject(curFormData)) {
const oldSelectSchema = retrieveSchema(
props.selectList[oldVal],
props.rootSchema
);
if (getSchemaType(oldSelectSchema) === 'object') {
// 移除旧schema添加的属性
// Object.keys(oldSelectSchema.properties)
for (const key in oldSelectSchema.properties) {
if (
hasOwn.call(oldSelectSchema.properties, key)
&& !hasOwn.call(newOptionData, key)
) {
deletePathVal(curFormData, key);
}
}
}
}
// 设置新值
if (isObject(newOptionData)) {
Object.entries(newOptionData).forEach(([key, value]) => {
if (
value !== undefined
&& (
curFormData[key] === undefined
|| isObject(value)
|| ((() => {
const newSelectSchema = retrieveSchema(
props.selectList[newVal],
props.rootSchema
);
return newSelectSchema.properties[key]?.const !== undefined;
})())
)
) {
// 这里没找到一个比较合理的新旧值合并方式
//
// 1. 如果anyOf里面同名属性中的schema包含了 const 配置,产生了新的值这里做覆盖处理
// 2. 其它场景保留同名key的旧的值
setPathVal(curFormData, key, value);
}
});
} else {
setPathVal(
props.rootFormData,
props.curNodePath,
(newOptionData === undefined && isValid(retrieveSchema(
props.selectList[newVal],
props.rootSchema
), curFormData)) ? curFormData : newOptionData
);
}
});
return () => {
const { curNodePath } = props;
const pathClassName = nodePath2ClassName(curNodePath);
// is object
const isTypeObject = (props.schema.type === 'object' || props.schema.properties);
// 选择附加的节点
const childrenVNodeList = [getSelectBoxVNode()];
// 当前option内容
let curSelectSchema = props.selectList[curSelectIndex.value];
// 当前选中节点合并schema
if (curSelectSchema) {
// 覆盖父级的属性
const {
// eslint-disable-next-line no-unused-vars
properties,
// eslint-disable-next-line no-unused-vars
[props.combiningType]: combiningType,
// eslint-disable-next-line no-unused-vars
[`${props.combiningType}Select`]: combiningTypeSelect,
...parentSchema
} = props.schema;
curSelectSchema = Object.assign({}, parentSchema, curSelectSchema);
}
// object类型但没有附加属性
const isObjectEmptyAttachProperties = isTypeObject && isEmptyObject(curSelectSchema && curSelectSchema.properties);
if (curSelectSchema && !isObjectEmptyAttachProperties) {
// 当前节点的ui err配置,用来支持所有选项的统一配置
// 取出 oneOf anyOf 同级配置,然后再合并到 当前选中的schema中
const userUiOptions = filterObject(getUiOptions({
schema: props.schema,
uiSchema: props.uiSchema,
containsSpec: false,
curNodePath,
rootFormData: props.rootFormData,
}), key => (key === props.combiningType ? undefined : `ui:${key}`));
const userErrOptions = filterObject(getUserErrOptions({
schema: props.schema,
uiSchema: props.uiSchema,
errorSchema: props.errorSchema
}), key => (key === props.combiningType ? undefined : `err:${key}`));
childrenVNodeList.push(
h(
SchemaField,
{
key: `appendSchema_${props.combiningType}`,
...props,
schema: {
'ui:showTitle': false, // 默认不显示title
'ui:showDescription': false, // 默认不显示描述
...curSelectSchema,
},
required: props.required,
uiSchema: {
...userUiOptions, // 合并oneOf 级的配置
...((props.uiSchema[props.combiningType] || [])[curSelectIndex.value])
},
errorSchema: {
...userErrOptions, // 合并oneOf 级的配置
...((props.errorSchema[props.combiningType] || [])[curSelectIndex.value])
},
// needValidFieldGroup: false // 单独校验,这里无需处理
}
)
);
}
// object 需要保持原有属性,如果存在原有属性这里单独渲染
let originVNode = null;
if (isTypeObject && !isEmptyObject(props.schema.properties)) {
const {
// eslint-disable-next-line no-unused-vars
title, description, properties, ...optionSchema
} = curSelectSchema;
// object 原始项渲染也需要合并anyOf的内容
const origSchema = Object.assign({}, props.schema, optionSchema);
delete origSchema[props.combiningType];
originVNode = h(SchemaField, {
key: `origin_${props.combiningType}`,
class: {
[`${props.combiningType}_originBox`]: true,
[`${pathClassName}-originBox`]: true
},
...props,
schema: origSchema,
// needValidFieldGroup: false // 单独校验,这里无需处理
});
}
// oneOf 校验 VNode
childrenVNodeList.push(
h(Widget, {
key: `validateWidget-${props.combiningType}`,
class: {
validateWidget: true,
[`validateWidget-${props.combiningType}`]: true
},
schema: props.schema,
uiSchema: props.uiSchema,
errorSchema: props.errorSchema,
curNodePath: props.curNodePath,
rootFormData: props.rootFormData,
globalOptions: props.globalOptions
})
);
return h('div', [
originVNode,
h('div', {
key: `appendBox_${props.combiningType}`,
class: {
appendCombining_box: true,
[`${props.combiningType}_appendBox`]: true,
[`${pathClassName}-appendBox`]: true
}
}, childrenVNodeList)
]);
};
}
};