@lljj/vue3-form-core
Version:
基于 Vue3 、JsonSchema快速构建一个带完整校验的form表单,vue3版本基础框架
217 lines (187 loc) • 7.94 kB
JavaScript
/**
* Created by Liu.Jun on 2020/4/16 17:32.
*/
import {
getCurrentInstance, watch, ref, computed, h, provide, toRef
} from 'vue';
import { resolveComponent } from '@lljj/vjsf-utils/vue3Utils';
// 生成form表单默认数据
import getDefaultFormState from '@lljj/vjsf-utils/schema/getDefaultFormState';
import { deepEquals } from '@lljj/vjsf-utils/utils';
// 基础公共样式
import '@lljj/vjsf-utils/style/baseForm.css';
import vueProps from './props';
// 默认表单底部
import FormFooter from './components/FormFooter.js';
import SchemaField from './fields/SchemaField';
import fieldProps from './fields/props';
export {
fieldProps,
SchemaField
};
export default function createForm(globalOptions = {}) {
const Form = {
name: 'VueForm',
props: vueProps,
emits: ['update:modelValue', 'change', 'cancel', 'submit', 'validation-failed', 'form-mounted'],
setup(props, { slots, emit }) {
// global components
const internalInstance = getCurrentInstance();
if (!Form.installed && globalOptions.WIDGET_MAP.widgetComponents) {
Object.entries(globalOptions.WIDGET_MAP.widgetComponents).forEach(
([componentName, component]) => internalInstance.appContext.app.component(componentName, component)
);
// 只注册一次
Form.installed = true;
}
// 使用provide 传递跨组件数据
const fallbackLabel = toRef(props, 'fallbackLabel');
provide('genFormProvide', {
fallbackLabel,
});
// rootFormData
const rootFormData = ref(getDefaultFormState(props.schema, props.modelValue, props.schema, props.strictMode));
const footerParams = computed(() => ({
show: true,
okBtn: '保存',
okBtnProps: {},
cancelBtn: '取消',
...props.formFooter
}));
// form组件实例,不需要响应式
let formRef = null;
// 更新formData
const emitFormDataChange = (newValue, oldValue) => {
// 支持v-model ,引用类型
emit('update:modelValue', newValue);
// change 事件,引用类型修改属性 newValue
emit('change', {
newValue,
oldValue
});
};
// 更新props
const willReceiveProps = (newVal, oldVal) => {
if (!deepEquals(newVal, oldVal)) {
const tempVal = getDefaultFormState(props.schema, props.modelValue, props.schema, props.strictMode);
if (!deepEquals(rootFormData.value, tempVal)) {
rootFormData.value = tempVal;
}
}
};
// emit v-model,同步值
watch(rootFormData, (newValue, oldValue) => {
emitFormDataChange(newValue, oldValue);
}, {
deep: true
});
// schema 被重新赋值
watch(() => props.schema, (newVal, oldVal) => {
willReceiveProps(newVal, oldVal);
});
// model value 变更
watch(() => props.modelValue, (newVal, oldVal) => {
willReceiveProps(newVal, oldVal);
});
// 保持v-model双向数据及时性
emitFormDataChange(rootFormData.value, props.modelValue);
const getDefaultSlot = () => {
if (slots.default) {
return slots.default({
formData: rootFormData,
formRefFn: () => formRef
});
}
if (footerParams.value.show) {
return h(FormFooter, {
globalOptions,
okBtn: footerParams.value.okBtn,
okBtnProps: footerParams.value.okBtnProps,
cancelBtn: footerParams.value.cancelBtn,
formItemAttrs: footerParams.value.formItemAttrs,
onCancel() {
emit('cancel');
},
onSubmit() {
// 优先获取组件 $$validate 方法,方便对 validate方法转换
(formRef.$$validate || formRef.validate)((isValid, resData) => {
if (isValid) {
return emit('submit', rootFormData);
}
console.warn(resData);
return emit('validation-failed', resData);
});
}
});
}
return [];
};
return () => {
const {
// eslint-disable-next-line no-unused-vars
layoutColumn = 1, inlineFooter, labelSuffix, isMiniDes, defaultSelectFirstOption, popover, ...uiFormProps
} = props.formProps;
const { inline = false, labelPosition = 'top' } = uiFormProps;
const schemaProps = {
schema: props.schema,
uiSchema: props.uiSchema,
errorSchema: props.errorSchema,
customFormats: props.customFormats,
customRule: props.customRule,
rootSchema: props.schema,
rootFormData: rootFormData.value, // 根节点的数据
curNodePath: '', // 当前节点路径
globalOptions, // 全局配置,差异化ui框架
formProps: {
labelPosition,
labelSuffix: ':',
defaultSelectFirstOption: true,
inline,
...props.formProps
}
};
return h(
resolveComponent(globalOptions.COMPONENT_MAP.form),
{
class: {
genFromComponent: true,
formInlineFooter: inlineFooter,
formInline: inline,
[`genFromComponent_${props.schema.id}Form`]: !!props.schema.id,
layoutColumn: !inline,
[`layoutColumn-${layoutColumn}`]: !inline
},
setFormRef: (form) => {
formRef = form;
internalInstance.ctx.$$uiFormRef = formRef;
emit('form-mounted', form, {
formData: rootFormData.value
});
},
// 阻止form默认submit
onSubmit(e) {
e.preventDefault();
},
model: rootFormData,
labelPosition,
inline,
...uiFormProps
},
{
default: () => [
h(
SchemaField,
schemaProps
),
getDefaultSlot(),
]
}
);
};
},
};
Form.install = (vueApp, options = {}) => {
vueApp.component(options.name || Form.name, Form);
};
return Form;
}