UNPKG

@lljj/vue3-form-core

Version:

基于 Vue3 、JsonSchema快速构建一个带完整校验的form表单,vue3版本基础框架

304 lines (271 loc) 12.6 kB
/** * 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) ]); }; } };