基于Ant Design Vue封装一个表单控件
开源代码
https://github.com/naturefwvue/nf-vue3-ant
有缺点本来是写在最后的,但是博文写的似乎有点太长了,估计大家没时间往下看,于是就把有缺点写在前面了,不喜欢可以先跳过。
缺点
灵活性肯定是没有了,封装的还是有些过度,灵活度大大降低,没有使用slot,想加点啥目前是不可能的,等以后需要了再说,毕竟这个项目才刚刚开始。
(为啥缺点只有一条?那不那啥吗,基于ant design vue封装的,他们都那么强大了,还能有啥缺点?封装后除了失去灵活性还能差啥?)
优点
- 简洁,代码很少,做好meta就可以了,另外meta也不需要手写,有个小工具可以辅助创建。
- 风格统一,代码就是这样,当需要写新的表单的时候,也不需要复制粘贴,只需要弄个meta就行了,想变风格都变不了。
- 可以统一修改升级。UI版本升级了,VUE版本升级了,咋办?改一下组件内部代码即可,调用组件的代码并不需要修改。这样还怕升级了吗?
- 可以跨UI,甚至跨框架。之前看了一下element,本来想用的,但是不支持vue3.0只好作罢。element的使用方式也是大同小异,那么我基于element也封装一套组件,保证外部使用方式一致,那么是不是可以做到UI随便切换了呢?
- 便于项目升级。项目打包发布后,如果需求有变更,一般修改完后需要重新打包发布。而我们的项目是通过 meta 来控制表单的,也就是说如果有变动,那么改json文件即可,而json可以通过ajax来加载,不用打包到项目里面。
为啥还要封装
antdv都已经提供那么的组件了,还不够用吗?为啥还要折腾
首先antd 是一个非常强大UI库,提供了很强大的功能和漂亮的UI,使用方面也是非常的灵活,不仅有Form表单,还有各种Data Entry组件,非常灵活。只是鱼和熊掌不能兼得,antdv为了灵活而牺牲了一些简洁性。
select
比如a-select,官网代码如下:(有删减)
<template>
<div>
<a-select
v-model:value="value1"
style="width: 120px"
@focus="focus"
ref="select"
@change="handleChange"
>
<a-select-option value="jack">
Jack
</a-select-option>
<a-select-option value="lucy">
Lucy
</a-select-option>
<a-select-option value="disabled" disabled>
Disabled
</a-select-option>
<a-select-option value="Yiminghe">
yiminghe
</a-select-option>
</a-select>
</div>
</template>
<script>
export default {
data() {
window.test = this; // 话说这个是干嘛用的?
return {
value1: 'lucy'
};
},
methods: {
focus() {
console.log('focus');
},
handleChange(value) {
console.log(`selected ${value}`);
},
},
};
</script>
首先要设置一些属性,然后还要逐行设置 a-select-option,是不是有点麻烦?
form
再来看一下form的官网示例:(七个字段的简单表单)
<template>
<a-form :model="form" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item label="Activity name">
<a-input v-model:value="form.name" />
</a-form-item>
<a-form-item label="Activity zone">
<a-select v-model:value="form.region" placeholder="please select your zone">
<a-select-option value="shanghai">
Zone one
</a-select-option>
<a-select-option value="beijing">
Zone two
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Activity time">
<a-date-picker
v-model:value="form.date1"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%;"
/>
</a-form-item>
<a-form-item label="Instant delivery">
<a-switch v-model:checked="form.delivery" />
</a-form-item>
<a-form-item label="Activity type">
<a-checkbox-group v-model:value="form.type">
<a-checkbox value="1" name="type">
Online
</a-checkbox>
<a-checkbox value="2" name="type">
Promotion
</a-checkbox>
<a-checkbox value="3" name="type">
Offline
</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Resources">
<a-radio-group v-model:value="form.resource">
<a-radio value="1">
Sponsor
</a-radio>
<a-radio value="2">
Venue
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="Activity form">
<a-input v-model:value="form.desc" type="textarea" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="onSubmit">
Create
</a-button>
<a-button style="margin-left: 10px;">
Cancel
</a-button>
</a-form-item>
</a-form>
</template>
<script>
export default {
data() {
return {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
form: {
name: '',
region: undefined,
date1: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
},
};
},
methods: {
onSubmit() {
console.log('submit!', this.form);
},
},
};
</script>
在Form表单里面也是这样的设置方式,而表单里面有很多各种各样的控件,一个一个写起来实在是太累。看这样的代码有点眼晕,似乎也不太便于维护,不知道大家是怎么编写和维护的。
大家都知道我很懒,我想用v-for来做表单,这样即使一百个字段也是一个for搞定,这样代码就简单多了。
那么如何实现呢?
如何封装?
vue的思路就是——数据驱动,那么我就把这个思路做的更彻底一点,——让数据驱动dom的属性
统一标签名称
想要for循环,标签必须统一,a-input、a-select等等都不一样,这还怎么循环?所以要先做一个统一的组件,以便于for循环。然后内部再分为多种不同的组件,这样便于维护,要不然代码都写到一起就太乱了。
于是结构就是这样:
结构图
统一属性
除了标签之外,属性也要一致,否则还是不能for。那么怎么办呢?不同的控件需要的属性都不一样呀,这个好办,我们整合成两个就行
v-model value
这个必须单独拿出来。
meta
其他的属性都统一放在这里,把这个东东传递进去就好,然后内部识别领取自己的属性。这样就搞定了。
代码
我们来看meta的结构。
meta
以input为例,其他都大同小异
props: {
modelValue: String,
meta: {
type: Object,
default: () => {
return {
controlId: Number, // 编号,区别同一个表单里的其他控件
colName: String, // 字段名称
controlType: Number, // 用类型编号表示type
isClear: { // 连续添加时是否恢复默认值
type: Boolean,
default: false
},
defaultValue: String, // 默认值
autofocus: { // 是否自动获得焦点
type: Boolean,
default: false
},
required: { // 必填
type: Boolean,
default: true
},
disabled: {
// 是否禁用
type: Boolean,
default: false
},
readonly: { // 只读
type: Boolean,
default: false
},
pattern: String, // 用正则做验证。
placeholder: String,
title: String, // 提示信息
maxlength: Number, // 最大字符数
autocomplete: { // off
type: String,
default: 'on'
}
}
}
}
},
不同类型的组件,会有所调整。
input
模板部分
<template>
<div class="components-input-demo-presuffix">
<a-input
:id="'id' + meta.controlId"
:name="'c' + meta.controlId"
:value="modelValue"
:autofocus="meta.autofocus"
:disabled="meta.disabled"
:readonly="meta.readonly"
:placeholder="meta.placeholder"
:title="meta.title"
:maxlength="meta.maxlength"
:autocomplete="meta.autocomplete"
:key="'ckey_'+meta.controlId"
size="small"
@input="myInput"
>
</a-input>
</div>
</template>
先把需要的属性,通过meta都给绑定上
js
<script>
export default {
name: 'nf-form-input',
model: {
prop: 'modelValue',
event: 'input'
},
props: {
modelValue: String,
meta: {
type: Object,
default: () => {
return {
controlId: Number, // 编号,区别同一个表单里的其他控件
colName: String, // 字段名称
controlType: Number, // 用类型编号表示type
isClear: { // 连续添加时是否恢复默认值
type: Boolean,
default: false
},
defaultValue: String, // 默认值
autofocus: { // 是否自动获得焦点
type: Boolean,
default: false
},
required: { // 必填
type: Boolean,
default: true
},
disabled: {
// 是否禁用
type: Boolean,
default: false
},
readonly: { // 只读
type: Boolean,
default: false
},
pattern: String, // 用正则做验证。
placeholder: String,
title: String, // 提示信息
maxlength: Number, // 最大字符数
autocomplete: { // off
type: String,
default: 'on'
}
}
}
}
},
methods: {
myInput: function (e) {
var returnValue = e.target.value
var colName = this.meta.colName // event.target.getAttribute('colname')
this.$emit('update:modelValue', returnValue) // 返回给调用者
this.$emit('getvalue', returnValue, colName) // 返回给中间组件
}
}
}
</script>
这样我们只要做好meta,就可以完全控制控件了。其他控件也是类似的思路,就不一一贴代码了。
form-Item
组件分的太零碎,使用的时候很麻烦,那么怎么办呢?再做个组件整合一下。
<template>
<span class="hello">
<nfArea v-if="meta.controlType === 100" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfUrl v-else-if="meta.controlType === 105" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfInput v-else-if="meta.controlType <= 119" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfNumber v-else-if="meta.controlType === 131" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfSlider v-else-if="meta.controlType === 132" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfDatetime v-else-if="meta.controlType <= 149" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfUpload v-else-if="meta.controlType <= 159" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfColor v-else-if="meta.controlType === 160" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfCheck v-else-if="meta.controlType === 180" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfChecks v-else-if="meta.controlType === 182" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfRadios v-else-if="meta.controlType === 183" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
<nfSelect v-else-if="meta.controlType <= 191" :modelValue="modelValue" @change="myChange" @getvalue="sendValue" :meta="meta"/>
<nfInputMore v-else-if="meta.controlType === 200" :modelValue="modelValue" @getvalue="sendValue" :meta="meta"/>
</span>
</template>
很笨的方法,挨个类型判断。这里使用了魔数,大概会被喷,不过早就习惯了。
<script>
import nfArea from './nf-form-textarea.vue' // 100
import nfInput from './nf-form-input.vue' // 100-107
import nfUrl from './nf-form-input-url.vue' // 105
import nfNumber from './nf-form-number.vue' // 131
import nfSlider from './nf-form-numslider.vue' // 132
import nfDatetime from './nf-form-datetime.vue' // 140-144
import nfUpload from './nf-form-upload.vue' // 150-151
import nfColor from './nf-form-color.vue' // 160
import nfCheck from './nf-form-check.vue' // 180
import nfChecks from './nf-form-checks.vue' // 182
import nfRadios from './nf-form-radios.vue' // 183
import nfSelect from './nf-form-select.vue' // 190
import nfInputMore from './nf-form-inputmore.vue' // 200
export default {
name: 'nf-form-item',
components: {
nfInput,
nfUrl,
nfArea,
nfNumber,
nfSlider,
nfDatetime,
nfUpload,
nfColor,
nfCheck,
nfChecks,
nfRadios,
nfSelect,
nfInputMore
},
props: {
modelValue: Object,
meta: Object
},
methods: {
myChange: function (value) {
this.$emit('change', value)
this.$emit('update:modelValue', value)
},
sendValue: function (value, colName) {
this.$emit('update:modelValue', value)
this.$emit('getvalue', value, colName) // 返回给中间组件
}
}
}
</script>
在这里统一注册各种零散的组件,使用的时候就不用想,到底要用哪种组件了。
表单
好了,准备工作都做好了,我们可以开始for循环了。
找了半天,antdv没有提供单纯的table,只好手动找class了,于是代码变成了这样。
<div class="ant-table ant-table-body ant-table-default ant-table-bordered" >
<table>
<colgroup><col style="width: 30%; min-width: 30%;"><col>
</colgroup>
<tbody class="ant-table-tbody">
<tr v-for="(item,index) in metaInfo" :key="index">
<td align="right" style="padding:10px 10px;height:20px">
{{item.title}}:
</td>
<td align="left" style="padding:10px 10px;height:20px">
<nfInput v-model="modelValue[item.colName]" :meta="item" />
</td>
</tr>
</tbody>
</table>
</div>
代码行数和控件(字段)数量无关。代码数量也和有多少表单无关。
是不是看起来一点都不像一个表单?代码是不是少的有点可怜?
nfInput 控件有两个属性v-model 和 meta,他会根据meta自动创建需要的dom,并且绑定属性。当然实际干活的是vue和antdv,我只是做了一种尝试。
<script>
import { ref } from 'vue'
import nfInput from '@/components/nf-form/nf-form-item.vue'
export default {
name: 'FormDemo',
components: {
nfInput
},
setup () {
const json = require('./FormDemo.json') // 加载meta信息,json格式
const modelValue = ref({}) // 放数据的model
const metaInfo = ref(json.companyForm) // 表单需要的meta信息
const myClick = (key) => {
// 更换表单的meta
metaInfo.value = json[key]
// 动态创建model
modelValue.value = {}
for (var k in metaInfo.value) {
var item = metaInfo.value[k]
modelValue.value[item.colName] = ''
}
}
myClick('companyForm')
return {
modelValue,
metaInfo,
myClick
}
}
}
</script>
meta,单独的json文件
meta并不需要写在代码里,因为实在是太长了。可以写在单独的json文件里面,这样便于加载。另外也可以做成ajax加载的方式,这样项目发布后,如何需求有变动,需要调整表单的话,那么只需要单独修改json文件即可,不用重新打包发布。
{
"companyForm":{
"1000":{
"controlId": 1000,
"colName": "companyName",
"controlType": 101,
"isClear": true,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "请输入公司名称",
"title": "公司名称",
"autocomplete": "on",
"size": 30,
"maxlength": 100,
"optionList": []
},
"1001":{
"controlId": 1001,
"colName": "companyCode",
"controlType": 131,
"isClear": true,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "公司邮编",
"title": "公司邮编",
"autocomplete": "on",
"min": 100000,
"max": 999999,
"step": 1,
"maxlength": 6,
"optionList": []
},
"1002":{
"controlId": 1002,
"colName": "legalPerson",
"controlType": 101,
"isClear": true,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "请输入法人姓名",
"title": "法人",
"autocomplete": "on",
"size": 20,
"maxlength": 50,
"optionList": []
},
"1003":{
"controlId": 1003,
"colName": "liaisonMan",
"controlType": 101,
"isClear": true,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "请输入联系人姓名",
"title": "联系人",
"autocomplete": "on",
"size": 20,
"maxlength": 50,
"optionList": []
},
"1004": {
"controlId": "1004",
"colName": "address",
"controlType": 101,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "请输入公司地址",
"title": "公司地址",
"autocomplete": "on",
"size": 30,
"maxlength": 50,
"optionKey": "",
"optionList": [
]
},
"1005": {
"controlId": "1005",
"colName": "telphone",
"controlType": 103,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "请输入公司电话",
"title": "公司电话",
"autocomplete": "on",
"size": 30,
"maxlength": 50,
"optionKey": "",
"optionList": [
]
},
"1006": {
"controlId": "1006",
"colName": "URL",
"controlType": 105,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "https://www.",
"title": "公司网址",
"autocomplete": "on",
"size": 30,
"maxlength": 50,
"optionKey": "",
"optionList": [
]
},
"1007": {
"controlId": "1007",
"colName": "Email",
"controlType": 104,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"placeholder": "@",
"title": "公司邮件",
"autocomplete": "on",
"size": 30,
"maxlength": 50,
"optionKey": "",
"optionList": [
]
},
"1008": {
"controlId": 1008,
"colName": "type",
"title": "公司类型",
"controlType": 190,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"pattern": "",
"class": "",
"optionList": [
{ "value": 1, "title": "有限责任公司" },
{ "value": 2, "title": "股份有限责任公司" },
{ "value": 3, "title": "个人独资企业" },
{ "value": 4, "title": "合伙企业" },
{ "value": 5, "title": "个体工商户" }
]
},
"1009": {
"controlId": 1009,
"colName": "createDate",
"controlType": 140,
"isClear": true,
"defaultValue": "",
"autofocus": false,
"disabled": false,
"required": true,
"readonly": false,
"pattern": "",
"class": "",
"title": "成立日期",
"min": "1910-01-01",
"max": "2999-12-31",
"step": 1
}
}
}
数据和代码分离,是不是很完美。
为啥不直接用antdv提供的 Form 表单?
这个嘛,思路不太一样。好吧,其实是官网的代码,在本地还没有调试成功,等研究明白了还是会用的。
基于Ant Design Vue封装一个表单控件的更多相关文章
- Vue 2.x折腾记 - (17) 基于Ant Design Vue 封装一个配置式的表单组件
前言 写了个类似上篇搜索的封装,但是要考虑的东西更多. 具体业务比展示的代码要复杂,篇幅太长就不引入了. 效果图 2019-04-25 添加了下拉多选的渲染,并搜索默认过滤文本而非值 简化了渲染的子组 ...
- Vue进阶之表单控件绑定
1.单行input <html> <head> <meta charset="UTF-8"> <meta name="viewp ...
- 基于 el-form 封装一个依赖 json 动态渲染的表单控件
nf-form 表单控件的功能 基于 el-form 封装了一个表单控件,包括表单的子控件. 既然要封装,那么就要完善一些,把能想到的功能都要实现出来,不想留遗憾. 毕竟UI库提供的功能都很强大了,不 ...
- react 项目实战(四)组件化表单/表单控件 高阶组件
高阶组件:formProvider 高阶组件就是返回组件的组件(函数) 为什么要通过一个组件去返回另一个组件? 使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能. 我们现在已经有 ...
- 我教女朋友学编程Html系列(6)—Html常用表单控件
做过网页的人都知道,html表单控件十分重要.基本上我们注册会员.登录用户,都需要填写用户名.密码,那些框框都是表单控件. 本来今天就想写一些常用的html表单控件,于是开始搜资料,找到了一个网页,作 ...
- 如何给动态添加的form表单控件添加表单验证
最近使用jQuery Validate做表单验证很方便,api地址为http://www.runoob.com/jquery/jquery-plugin-validate.html 但是在使用的时候也 ...
- Angular19 自定义表单控件
1 需求 当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件:自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互 2 官方文档 -> 点击前 ...
- Angular 从入坑到挖坑 - 表单控件概览
一.Overview angular 入坑记录的笔记第三篇,介绍 angular 中表单控件的相关概念,了解如何在 angular 中创建一个表单,以及如何针对表单控件进行数据校验. 对应官方文档地址 ...
- Flutter Form表单控件超全总结
注意:无特殊说明,Flutter版本及Dart版本如下: Flutter版本: 1.12.13+hotfix.5 Dart版本: 2.7.0 Form.FormField.TextFormField是 ...
随机推荐
- 【CF1110E】 Magic Stones - 差分
题面 Grigory has n n magic stones, conveniently numbered from \(1\) to \(n\). The charge of the \(i\)- ...
- CSS动画实例:Loading加载动画效果(一)
一些网站或者APP在加载新东西的时候,往往会给出一个好看有趣的Loading图,大部分的Loading样式都可以使用CSS3制作出来,它不仅比直接使用gif图简单方便,还能节省加载时间和空间.下面介绍 ...
- 每日一道 LeetCode (19):合并两个有序数组
每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...
- Android CC框架中,新建组件无法显示布局问题
出错: 当在创建新的组件时,跳转到新组件成功,但是无法正确显示布局,即获取到布局文件的控件等. 原因: 当在创建新的组件时,默认生成MainActivity以及其布局activity_main.每个组 ...
- css3 属性 text-overflow 实现截取多余文字内容 以省略号来代替多余内容
css3 属性 text-overflow: ellipsis 前言 正文 结束语 前言 我们在设计网站的时候有时会遇到这样一个需求:因为页面空间大小的问题,需要将多余的文字隐藏起来,并以省略号代替, ...
- Cobalt Strike简单使用
---恢复内容开始--- 一.介绍: 后渗透测试工具,基于Java开发,适用于团队间协同作战,简称“CS”. CS分为客户端和服务端,一般情况下我们称服务端为团队服务器,该工具具有社工功能(社会工程学 ...
- Jmeter 常用函数(26)- 详解 __chooseRandom
如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 从指定的范围里面取值 语法格式 ${_ ...
- 推断(inference)和预测(prediction)
上二年级的大儿子一直在喝无乳糖牛奶,最近让他尝试喝正常牛奶,看看反应如何.三天过后,儿子说,好像没反应,我可不可以说我不对乳糖敏感了. 我说,呃,这个问题不简单啊.你知道吗,这在统计学上叫推断. 儿子 ...
- Spring MVC 的运行流程
1.用户发送请求到DispatcherServlet 2.DispatcherServlet调用处理器映射器(HanderMapping)找到处理器 3.处理器映射器(HanderMapping)返回 ...
- 高并发&性能优化(一)------总体介绍
[开篇词] 本文主要通过一些经典的高并发场景,以及一些基本的运维工具来讲述一些关于高并发以及性能优化相关的内容,主要包括性能瓶颈的定位,性能调优的思路和技巧等. [性能的衡量指标] ?什么是性能 性能 ...