前端配置化表单组件设计方法 | 京东云技术团队
- 2023-05-06 北京
本文字数:3992 字
阅读完需:约 13 分钟
一、背景
前端开发中涉及表单的页面非常多,看似功能简单,开发快速,实则占去了很大一部分时间。当某个表单包含元素过多时还会导致 html 代码过多,vue 文件过大。从而不容易查找、修改和维护。为了提高开发效率及降低维护成本,下面介绍表单配置化组件的封装原理与封装方法。
二、技术方案
如上图所示,封装表单配置化组件的关键点有三个一是如何解决表单元素排布的行列问题,二是表单数据的绑定问题,三是表单元素的参数配置校验等问题。下面分别介绍这三个问题的解决方法。
•配置化表单组件的入参及说明
•计算配置化表单的行数,本表单通过基础的 24 分栏计算表单最终的行数和列数,通过下面方法最终得到一个关于行列的二维数组
newColumnList() {
const newColumnList= []
const row = Math.floor(24 / this.columnSpan)
let newColumnItem = []
for(let i=0; i< this.columnList.length; i++) {
newColumnItem.push(this.columnList[i])
if(newColumnItem.length === row || i === this.columnList.length-1) {
newColumnList.push(newColumnItem)
newColumnItem = []
}
}
return newColumnList
}
•通过上面得到的二维数组进行循环渲染,首先循环渲染行,其次循环渲染列。本方案采用 element 中的表单,当然也可以用其他组件库或者原生表单进行渲染,其原理通用。最终将会根据参数 column.type 决定加载哪一个具体的表单元素。
<el-form ref="form" :model="formData" :label-width="labelWidth" :size="size">
<el-row :gutter="20" v-for="(element,index) in newColumnList" :key="index+'formRow'">
<template v-for="(item, index) in element" >
<column
:key="index + 'formView'"
:columnSpan="columnSpan"
:column="item"
:formData="formData"
/>
</template>
</el-row>
</el-form>
•column 组件最终根据 type 加载具体的表单元素。下面展示 column 组件的入参及其说明,通过 component 加载不同的表单元素
<el-col :span="columnSpan">
<component
:is="column.type + 'View'"
:column="column"
:formData="formData"
v-model="formData[column.name]"
:columnSpan="columnSpan"/>
</el-col>
•这里主要以 select 表单元素为例进行说明,表单元素的双向绑定、校验以及值更新等问题
•column 参数
<el-form-item :label="column.title + ':'" :prop="column.name" :rules="rules">
<el-select
v-model="val"
clearable
:multiple="column.multiple"
:filterable="column.filterable"
:placeholder="'请选择' + column.title"
:disabled="column.disabled"
style="width: 100%"
@change="onChange"
@clear="onClear">
<el-option v-for="item in column.dictionary" :key="item.code" :label="item.name" :value="item.code">
</el-option>
</el-select>
</el-form-item>
rules: [
{
required: this.column.required,
message: this.column.placeholder placeholder ? this.column.placeholder : `请输入${this.column.title}`,
trigger: 'change'
},
...this.column.rules
]
onChange(){
this.$emit('input',this.val)
if(this.column && this.column.changeFunction){
this.column.changeFunction(this.val)
}
},
onClear(){
this.onChange()
}
三、项目实践
•配置化表单为 bs-form,在页面中引入 bs-form 表单组件
<bs-form ref="formDemo"
:columnList="columnList"
:formData="formData"
:columnSpan="columnSpan"
labelWidth="120px">
</bs-form>
<el-row style="text-align: center;">
<el-button type="primary"
@click="onSave">保存</el-button>
<el-button @click="onCancel">取消</el-button>
</el-row>
•formData 参数
formData: {
name: '',
yearIncome: '', // 业务类型
goodsCategoryId: '', // 托寄物品类id
projectManagerErp: '', // 项目经理erp
projectName: '', // 项目名称
projectStage: '', // 项目阶段编码
projectStandardName: '', // 标准名称
projectYear: 2023, // 年份
startRegionId: '', // 始发区域id
startBattleId: '', // 始发战区id
address: [], // 省市
category: null, //图文类型
range: [] //发布范围
}
•分栏参数
columnSpan: 6
•表单配置参数
columnList(){
const self = this
return [
{
type: 'text',
name: 'name',
title: '项目名称',
required: true,
maxlength: 20,
showwordlimit: true,
placeholder: '请输入'
},
{
name: 'category',
type: 'radio',
dictionary: [
{
code: 1,
name: '类型一'
},
{
code: 2,
name: '类型二'
}
],
title: '图文类型',
required: true
},
{
name: 'range',
type: 'checkbox',
title: '发布范围',
dictionary: [
{
code: 1,
name: '范围一'
},
{
code: 2,
name: '范围二'
}
],
required: true
},
{
type: 'text', // 字段类型文本框
name: 'yearIncome', //与后台对接字段
title: '年均收入', // 前端展示字段
required: true, // 必填项设置
maxlength: 50, // 字符串长度限制
showwordlimit: true, // 是否显示字符串长度
placeholder: '请输入', // 占位文本提示
rules: [
{ pattern: /(^[1-9]([0-9]+)?(.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9].[0-9]([0-9])?$)/, message: '请输入数字最多两位小数' }
],
},
{
type: 'select',
name: 'goodsCategoryId',
title: '托寄物品类',
required: true,
filterable: true,
placeholder: '请选择',
dictionary: [{
name: '苹果',
code: '1'
},{
name: '手机',
code: '2'
},{
name: '测试',
code: '3'
},{
name: '樱桃',
code: '7'
},{
name: '荸荠',
code: '9'
}]
},
{
type: 'select',
name: 'startRegionId',
title: '区域',
required: true,
placeholder: '请选择',
dictionary: [{
name: '销售-华北区域',
code: '1'
},{
name: '销售-华东区域',
code: '2'
},{
name: '销售-华南区域',
code: '3'
},{
name: '销售-西南区域',
code: '4'
},{
name: '销售-华中区域',
code: '5'
},{
name: '销售-东北区域',
code: '6'
}],
// 点击下来触发切换联动的事件,为一个函数
changeFunction: function (val) {
}
}, {
type: 'select',
name: 'startBattleId',
title: '战区',
required: true,
placeholder: '请选择',
dictionary: this.battleByRegionList
}, {
type: 'select',
name: 'projectStage',
title: '项目阶段',
required: true,
placeholder: '请选择',
dictionary: [{
name: '项目发起阶段',
code: '10'
},{
name: '项目调研阶段',
code: '20'
},{
name: '可行性分析阶段',
code: '30'
},{
name: '立项阶段',
code: '40'
}]
}, {
type: 'text',
name: 'projectStandardName',
title: '标准名称',
required: true,
placeholder: '请输入',
append: '.com', // 文本框后置内容
}, {
type: 'text',
name: 'projectManagerErp',
title: '项目经理',
required: true,
placeholder: '请输入'
},{
type: 'cascader', // 字段类型下拉框
name: 'address', //与后台对接字段
title: '省市区', // 前端展示字段
required: true, // 必填项设置
placeholder:'请选择', // 占位文本提示
dictionary: [{
value: 'shanxi',
label: '陕西省',
children: [{
value: 'xian',
label: '西安市',
children: [{
value: 'yanta',
label: '雁塔区'
}, {
value: 'beilin',
label: '碑林区'
}, {
value: 'xincheng',
label: '新城区'
}, {
value: 'weiyang',
label: '未央区'
}]
}]
}],
// 点击下来触发切换联动的事件,为一个函数
changeFunction: function(){}
},{
type: 'static',
name: 'projectYear',
title: '年份'
}
]
}
•表单保存
// 保存
async onSave() {
const valid = await this.$refs.formDemo.onValidate()
if(valid) {
this.$message.success('校验通过')
}else {
this.$message.error('校验失败')
}
}
四、成果展示
作者:京东物流 田雷雷
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/c922b8c25a3d5cff969f646eb】。文章转载请联系作者。
京东科技开发者
拥抱技术,与开发者携手创造未来! 2018-11-20 加入
我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩
评论