介绍
本篇文章主要介绍基于json schema 实现 vue antd 的动态表单中的第三部分:实现。
介绍如何解析 json schema 和 ui schema 代码实现!
源代码实现
DyForm
动态表单的容器,主要负责解析 json shcema 和 uischema,以及提供表单项加载容器
<template>
<a-form @submit="handleSubmit"
style="margin-top: 8px"
:autoFormCreate="(form)=>{this.form = form}"
v-bind="formOption">
<dy-item :properties="rootProperties"></dy-item>
<a-form-item style="margin-top: 32px">
<a-button type="primary" htmlType="submit" :loading="submiting">
提交
</a-button>
<a-button style="margin-left: 8px">保存</a-button>
</a-form-item>
</a-form>
</template>
<style lang="less">
</style>
<script lang="tsx">
import {
Component,
Prop,
Vue,
Emit,
} from 'vue-property-decorator';
import {
State,
Mutation,
namespace,
} from 'vuex-class';
import DyFormitem from './DyFormitem.vue';
import { DFSchema } from './schema/DfSchema';
import { DFUISchema } from './schema/UiSchema';
import FormProperty from './domain/FormProperty';
@Component({
components: {
'dy-item': DyFormitem,
},
})
export default class DyForm extends Vue {
/**
* json schema 描述的表单结构
*/
@Prop({
type: Object,
default() {
return {};
},
})
private formSchema!: DFSchema;
/**
* UI schema 描述表单渲染
*/
@Prop({
type: Object,
default() {
return {};
},
})
private uiSchema!: DFUISchema;
/**
* 表单选项,未使用
*/
@Prop({
type: Object,
default() {
return {};
},
})
private formOption!: any;
/**
* 表单是否提交中
*/
@Prop({
type: Boolean,
default: false,
})
private submiting!: boolean;
private form: any = null;
private rootProperties: FormProperty[] = [];
private mounted() {
this.rootProperties = this.createRootProperties();
}
/**
* 创建属性
*/
private createRootProperties() {
const properties: any = this.formSchema.properties;
const rootProperties = Object.keys(properties)
.map((key: any) => {
const item = properties[key];
const uiItem = this.uiSchema[key];
return new FormProperty(key, item, uiItem, this.formSchema.required);
});
return rootProperties;
}
/**
* 表单提交
*/
private handleSubmit(e: any) {
e.preventDefault();
this.form.validateFieldsAndScroll((err: any, values: any) => {
if (err) {
return;
}
this.success(values);
});
}
@Emit('onSuccess')
private success(values: any) {
}
}
</script>
DyFormitem
主要是动态表单项form-item
的展示容器,根据dy-form
组件中传递解析后的form属性进行表单渲染
<template>
<div>
<component v-for="(formitem,index) in properties"
:key="index"
:is="createWidgets(formitem)"
:formitem="formitem"></component>
</div>
</template>
<style lang="less">
</style>
<script lang="tsx">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { State, Mutation, namespace } from 'vuex-class';
import registry from './WidgetRegistry';
@Component({
components: {},
})
export default class DyFormitem extends Vue {
@Prop({type: Array, default() {
return [];
}})
private properties!: any[];
/**
* 从 组件工厂中获取组件并显示
*/
private createWidgets(foritem: any): any {
const key = `df-${foritem.type}`;
const comp = registry.getType(key);
return comp;
}
}
</script>
dyformitemMixin
动态表单项和表单组件的混入,提炼每个组件需要的公共方法和属性
import { Observable, Subscription, BehaviorSubject } from 'rxjs';
import {
Component,
Prop,
Vue,
Emit,
Model,
Watch,
} from 'vue-property-decorator';
import { DFSchema } from './schema/DfSchema';
import FormProperty from './domain/FormProperty';
export interface IDyFormMixin {
formitem?: FormProperty;
}
/**
* 动态表单项组件的混入
*/
@Component({})
export default class DyFormMixin extends Vue implements IDyFormMixin {
/**
* 表单属性
*/
@Prop({type: Object, default: () => {}})
public formitem!: FormProperty;
/**
* 组件属性
*/
get widgetAttrs() {
return this.formitem.widgetAttrs;
}
/**
* form-item 属性
*/
get itemAttrs() {
return this.formitem.formitemAttrs;
}
/**
* 标签
*/
get label(): string {
return this.formitem.label;
}
}
StringWidget
文本框组件
<template>
<df-item :formitem="formitem">
<a-input v-bind="widgetAttrs"/>
</df-item>
</template>
<style lang="less">
</style>
<script lang="ts">
import { Component, Prop, Vue, Mixins } from 'vue-property-decorator';
import { State, Mutation, namespace } from 'vuex-class';
import { DFSchema } from './../schema/DfSchema';
import DyFormitemWapper from './../DyFormitemWapper.vue';
import DyFormMixin, { IDyFormMixin } from '@/components/dynamicform/dyformitemMixin';
@Component({
components: {
'df-item': DyFormitemWapper,
},
})
export default class StringWidget extends Mixins<IDyFormMixin>(DyFormMixin) {
}
</script>
其他动态表单的组件编写和
StringWidget
保持同样的代码,只不过是将a-input
转换为其他组件
WidgetRegistry
动态表单组件注册类, 提供注册和获取方法
import { Component, Prop, Vue } from 'vue-property-decorator';
/**
* 动态表单组件注册类
* 提供注册和获取方法
*/
class WidgetRegistry {
private widgets: { [type: string]: any } = {};
private defaultWidget: any;
/**
* 设置默认组件,以便找不到type对应的逐渐时候显示
* @param widget
*/
public setDefault(widget: any) {
this.defaultWidget = widget;
}
/**
* 注册动态表单组件
* @param type
* @param widget
*/
public register(type: string, widget: any) {
this.widgets[type] = widget;
}
/**
* 判断指定的组件名称是否存在
* @param type
*/
public has(type: string) {
return this.widgets.hasOwnProperty(type);
}
/**
* 根据指定类型获取动态组件
* @param type
*/
public getType(type: string): any {
if (this.has(type)) {
return this.widgets[type];
}
return this.defaultWidget;
}
}
const widgetRegistry = new WidgetRegistry();
export default widgetRegistry;
注册组件见代码
// 日期范围
registry.register('df-daterange', DateRangeWidget);
// 数字输入框
registry.register('df-number', NumberWidget);
// 文本框
registry.register('df-string', StringWidget);
registry.register('df-text', TextWidget);
// 区域文本框
registry.register('df-textarea', TextareaWidget);
// 开关
registry.register('df-boolean', SwitchWidget);
// 拖动条
registry.register('df-slider', SliderWidget);
// 星打分
registry.register('df-rate', RateWidget);
// 下拉框
registry.register('df-select', SelectWidget);
// 单选框
registry.register('df-radio', RadioWidget);
// 上传文件
registry.register('df-upload', UploadWidget);
registry.register('df-uploaddragger', UploadDraggerWidget);
FormProperty
form属性,主要是提供一些属性,提供给动态表单项属性,
一般是由json shcme 和ui shcmea组成
import { DFSchema } from './../schema/DfSchema';
import { DFUISchemaItem } from './../schema/UiSchema';
// tslint:disable:variable-name
/**
* form属性,主要是提供一些属性,提供给动态表单项属性
* 一般是有 json schem 和 ui shcem 和 formdata组合提供
*/
export default class FormProperty {
public formData: any = {};
private _formSchem: DFSchema = {};
private _uiSchema: DFUISchemaItem = {};
private _propertyId: string = '' ;
private _required: string[] = [];
constructor(
propertyId: string ,
formSchema: DFSchema,
uiSchema: DFUISchemaItem,
required: string[] = []) {
this._propertyId = propertyId;
this._formSchem = formSchema;
this._uiSchema = uiSchema;
this._required = required;
}
get key(): string {
return this._propertyId;
}
get id(): string {
return `$$${this._propertyId}`;
}
get formSchema(): DFSchema {
if (this._formSchem == null) {
return {};
}
return this._formSchem;
}
get uiSchema(): DFUISchemaItem {
const itemui: any = this.formSchema.ui;
const ui: any = this._uiSchema;
return {
...itemui,
...ui,
};
}
get type() {
if (this.uiSchema.widget) {
return this.uiSchema.widget;
}
return this.formSchema.type;
}
get formitemAttrs() {
const attrs = this.ui.itemattrs;
attrs.fieldDecoratorId = this.key;
attrs.fieldDecoratorOptions = this.fieldDecoratorOptions;
return attrs;
}
get widgetAttrs() {
return this.ui.widgetattrs;
}
get ui(): any {
const propertyUi: any = this.formSchema.ui;
const ui = {
...propertyUi,
...this.uiSchema,
};
return ui;
}
get label(): string {
if (this.formSchema.title) {
return this.formSchema.title;
}
if (this.uiSchema.widgetattrs && this.uiSchema.widgetattrs.label) {
return this.uiSchema.widgetattrs.label;
}
return this.key;
}
get listSource(): any[] {
if (!this.formSchema.enum) {
return [];
}
return this.formSchema.enum;
}
get rules(): any[] {
const rules: any[] = [];
const isRequired = this._required.includes(this.key);
if (isRequired) {
let msg = '必填项';
if (this.ui.errors) {
msg = this.ui.errors.required;
}
rules.push({ required: true, message: msg });
}
if (this.formSchema.maxLength) {
let msg = '超过最大长度';
if (this.ui.errors) {
msg = this.ui.errors.maxLength;
}
rules.push({ max: this.formSchema.maxLength, message: msg });
}
if (this.formSchema.minLength) {
let msg = '最小长度';
if (this.ui.errors) {
msg = this.ui.errors.minLength;
}
rules.push({ min: this.formSchema.minLength, message: msg });
}
if (this.formSchema.pattern) {
let msg = '正则表达式不正确';
if (this.ui.errors) {
msg = this.ui.errors.pattern;
}
rules.push({ pattern: this.formSchema.pattern, message: msg });
}
if (this.formSchema.maximum) {
let msg = '最大数';
if (this.ui.errors) {
msg = this.ui.errors.maximum;
}
rules.push({ type: 'number', max: this.formSchema.maximum, message: msg });
}
if (this.formSchema.minimum) {
let msg = '最小数';
if (this.ui.errors) {
msg = this.ui.errors.minimum;
}
rules.push({ type: 'number', min: this.formSchema.minimum, message: msg });
}
// { required: true, message: '请输入姓名' }
return rules;
}
get fieldDecoratorOptions(): any {
return {
initialValue: this.formData[this.key],
rules: this.rules,
};
}
}
参考资料
ng-alain-form
json shcema
antd-vue
网友评论