学以致用,基于elementui尝试封装一个form组件
写在前面
2020年的最后一个月,掘金开始了年度征文。
这几周,陆陆续续看了10篇左右的征文,其中有技术大神也有和我一样初出校园的新人。看的不是太多,属实是不敢再看。看见大家的年度总结,才发现原来自己距离优秀还有那么远的距离.....
所以这几周下班后,开始思考这一年自己学习到了什么。作为一名大四实习生,年初的疫情在家办公期间,多数时间都游离在正式项目组之外,公司安排给我的开发任务并不多。说来惭愧,在那接近两个月的时间里,除了处理掉交给我的任务,另外拿出一些时间来解决我的毕设以外。剩下的空余时间甚至被我当作了加长版寒假......
3月底,公司复工,我开始接触微信小程序,公众号h5页面的开发。熟悉移动端开发,可能就是我这一年为数不多的收获了吧。
本月,暂时告别了前面已上线的移动端项目,回到了实习时接触的后台管理的开发。有相似却又不同,项目不是很大,前端开发只有我一人,不再是跟着师傅后面写页面了,但也因为前端就我一个人写,所以开发的自由度很高。如果不是看到这些年度总结,我估计还是会在公司以前的后台管理的主体框架内添加页面。但这一次,也许有风险,我想说一句"教练,我想打球。"。
也许会有坑,也许会有bug,但那有如何,虽千万人吾往矣,气势不能丢,大不了加班花更多的时间去解决问题呗。
上面那句写的豪迈,但其实呀这次的后台管理项目并不复杂,哈,这才是我想浪的关键。
正文开始
通过vue动态组件封装form组件
记得之前在掘金上看到过一篇讲解动态组件的文章,当时看见就有了用来封装组件的想法,但因为一些原因最终没有付诸实践,而且当时没有养成点赞关注不迷路的好习惯,所以现在尴尬的是找不到了文章地址了...如果你恰好也看到过那篇文章请在评论区告诉我或者留一道传送门,文章应该是刨析我下面这个例子的。
动态组件其实就是通过<component>元素的is属性来实现动态切换的组件。需要注意的是is属性传入的要是注册的组件名,官网介绍。
先来一个简单的例子。
这是input组件页面
<template>
<div class="item-input">
<i-input v-bind="$attrs"/>
</div>
</template>
<script>
export default {
name: 'Input',
data() {
return {}
}
}
</script>
这是中间层的form组件,元素中通过is将不同的form组件传入
<template>
<div class="hello">
<div v-for="item in formIpt" :key="item.label">
<component :is="item.type" v-bind="item" ></component>
</div>
</div>
</template>
<script>
import Inputs from "./formItem/input";
const CompFormItem = {
components: {
Input,
},
name: "FormItem",
props: {
formItem: {
required: true
}
},
render(h) {
return h(`${this.formItem.type}`);
}
};
export default {
name: "Form",
components: {
Input,
CompFormItem
},
props: {
formIpt: {
type: Array,
required: true,
default: () => []
}
}
};
</script>
最顶层的主页面
<template>
<div id="app">
<comForm :formIpt="formIpt" />
</div>
</template>
<script>
import comForm from './components/form/form.vue'
export default {
name: 'App',
components: {
comForm
},
data() {
return {
formIpt: [
{
type: "Input",
models: "",
label: '文本框',
placeholder: "我是默认值",
clearable: true,
}
],
}
},
}
</script>
实现的效果是
看到这个页面是不是感觉我有点呆,直接引用一个<el-input>不就好了,干嘛整这么麻烦。别急,再往后看看。
这里有几个点需要注意一下,在中间层form页面通过v-bind="item"将主页面中传入的对象全部传下级的input组件,然后在input组件中再通过v-bind="$attrs",将父作用域中不作为(且获取) prop 被识别的特性 (class 和 style 除外)传给更深的内部组件,也就是elementui的<el-input>组件,所以我们最外层的placeholder和clearable被传给了<el-input>。
这个时候,其实只要我们继续将诸如<el-select> ,<el-checkbox>组件如同<el-input>一样封装进来,那么再主页面中我们只要往formIpt这个数组中的对象传入不同的type就可以相对应的渲染不同的form组件了。
现在子组件的种类先不管,既然是表单组件,那么表单校验就必不可少,我们先将校验加入。
为form组件添加校验
首先对form组件页面动刀,将校验加入。
<template>
<div class="form">
<el-form
:validate-on-rule-change='false'
:model="formRule.model?formRule.model:''"
:rules="formRule.rule"
:inline="true"
:ref="formRule.refName">
<div :class="[fromClass.isColumn ? 'column-form' : 'search-form']">
<el-form-item
v-for="item in formIpt"
:label="item.label"
:key="item.label"
:prop="item.ruleProp">
<component :is="item.type" v-bind="item" :value.sync="item.models"></component>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script>
import Input from "./formItem/input";
const CompFormItem = {
components: {
Input,
},
name: "FormItem",
props: {
formItem: {
required: true
}
},
render(h) {
return h(`${this.formItem.type}`);
}
};
export default {
name: "Form",
components: {
Input,
CompFormItem
},
props: {
formIpt: {
type: Array,
required: true,
default: () => []
},
formRule: {
type: Object,
required: false,
default: () => {}
},
fromClass: {
type: Object,
required: false,
default: () => {}
},
},
watch: {
'formIpt': {
handler(newValue) {
//当表单值发生改变将改变后的值赋给校验所绑定的model
this.initFormRule(newValue)
},
deep: true
}
},
created () {
this.initFormRule(this.formIpt)
},
methods: {
initFormRule (newValue) {
for (let k = 0; k < newValue.length; k++) {
this.$props.formRule.model[newValue[k].ruleProp] = newValue[k].models
}
},
}
};
</script>
然后是input组件
<template>
<div class="item-input">
<el-input
v-bind="$attrs"
v-model="Val"
@blur="blur?blur():none()"
@focus="focus?focus():none()"
@change="change?change():none()"
></el-input>
</div>
</template>
<script>
export default {
name: 'Input',
data() {
return {
Val: ''
}
},
props: {
value: {
type: String,
default: () => ''
},
blur: {
type: Function,
default: () => () => {}
},
focus: {
type: Function,
default: () => () => {}
},
change: {
type: Function,
default: () => () => {}
}
},
watch: {
Val: function (newVal) {
this.$emit('update:value',newVal)
},
},
created() {
this.Val = this.value
},
methods: {
none () {
return false
},
}
}
</script>
<style scoped lang="scss">
</style>
这里也有几点需要注意一下。
-
首先
v-bind="$attrs"会将父作用域中不作为prop传给下级,所以我们在input组件的prop中将我们需要的属性接收,比如change事件等。 -
监听formIpt值的变化,一旦form组件的值改变,如输入框发生改变,我们要将表单双向绑定的值赋值给表单校验绑定的对应mode,所以在后面的主页面中formIpt中ruleProp值要与formRule中model内的值对应。
-
上面说的双向绑定,通过
.sync修饰符来实现,当输入框内值改变的时候,将formIpt内的models值改变,以达到双向绑定的效果。
然后在主页面中需要做如下修改
<template>
<div id="app">
<comForm ref="testForm" :formIpt="formIpt" :formRule="formRule" :fromClass="fromClass" />
</div>
</template>
<script>
import comForm from './components/form/form.vue'
export default {
name: 'App',
components: {
comForm
},
data() {
return {
Val: '',
formIpt: [
{
type: "Input",
models: "",
label: '文本框',
ruleProp: 'label1',
placeholder: "我是默认值",
clearable: true,
focus: this.focus,
},
],
formRule: {
model: {
label1: '',
},
refName: 'testForm',
rule: {
label1: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
}
},
fromClass: {
isColumn: false
}
}
},
methods: {
focus() {
console.log('---------------------------','获得焦点')
},
}
}
</script>
这里我们可以往我们的comForm组件中传入formIpt,formRule,fromClass三个值,分别对应表单项,校验规则,以及表单样式,样式先不谈。
效果如下:
这样子一个基本的form组件就封装完成了,接下来我们开始丰富form元素的种类,select,datePicker之类和input差不多就不再赘述,下面我们试试将upload封装进form组件。
封装upload组件
先来一个单文件上传,上代码。
<template>
<div class="item-upload">
<el-upload
class="avatar-uploader"
v-bind="$attrs"
:accept="accept"
:action="action"
:headers="headers"
:data="data"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:on-progress="handleProgress"
:on-success="successUpload"
:on-error="handleError"
:on-remove="handleRemove"
>
<img v-if="value&&value!==''" :src="value" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
export default {
name: 'Input',
data() {
return {
Val: ''
}
},
props: {
value: {
type: String,
default: () => ''
},
ruleProp: {
type: String,
default: () => ''
},
action: {
type: String,
default: () => ''
},
accept: {
type: String,
default: () => ''
},
headers: {
type: Object,
default: () => {}
},
data: {
type: Object,
default: () => {}
},
handleSuccess: {
type: Function,
default: () => () => {}
},
handleBeforeUpload: {
type: Function,
default: () => () => {}
},
handleError: {
type: Function,
default: () => () => {}
},
handleRemove: {
type: Function,
default: () => () => {}
},
handleProgress: {
type: Function,
default: () => () => {}
}
},
watch: {
Val: function (newVal) {
this.$emit('update:value',newVal)
},
},
created() {
this.Val = this.value
},
methods: {
none () {
return false
},
async successUpload(response, file, fileList) {
//这里我是接口返回图片地址,如果不是或者有其他操作,可以放在主页面的handleSuccess中处理
this.Val = response.data
await this.handleSuccess(response, file, fileList)
//上传成功后重置校验
this.$emit('resetItemValidate',this.ruleProp)
}
}
}
</script>
<style scoped lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
然后在中间层接受resetItemValidate函数重置校验
<template>
<div class="form">
<el-form
:validate-on-rule-change='false'
:model="formRule.model?formRule.model:''"
:rules="formRule.rule"
:inline="true"
:ref="formRule.refName">
<div :class="[fromClass.isColumn ? 'column-form' : 'search-form']">
<el-form-item
v-for="(item, index) in formIpt"
:label="item.label"
:key="index"
:prop="item.ruleProp">
<component :is="item.type" v-bind="item" :value.sync="item.models" @resetItemValidate="resetItemValidate"></component>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script>
export default {
name: "Form",
methods: {
resetItemValidate(name) {
this.$refs[this.formRule.refName].validateField(name)
}
}
};
</script>
最后,主页面,组件需要的属性,方法与之前组件一样都放在formIpt中传入
<template>
<div id="app">
<comForm ref="testForm" :formIpt="formIpt" :formRule="formRule" :fromClass="fromClass" />
<el-button @click="btnClick" type="primary">主要按钮</el-button>
</div>
</template>
<script>
import comForm from './components/form/form.vue'
export default {
name: 'App',
components: {
comForm
},
data() {
return {
Val: '',
formIpt: [
{
type: "Input",
models: "",
label: '文本框',
ruleProp: 'label1',
placeholder: "我是默认值",
clearable: true,
focus: this.focus,
},
{
type: "Select",
models: "",
label: '下拉框',
ruleProp: 'label2',
placeholder: "我是下拉框",
clearable: true,
option: [
{
label: '选项1',
value: '1'
}
],
blur: this.blur,
},
{
type: "DatePicker",
mold: "datetime",
models: "",
label: '日期选择器',
ruleProp: 'label3',
placeholder: "我是日期选择器",
clearable: true,
blur: this.blur,
},
{
type: "Upload",
models: "",
label: '文件上传',
ruleProp: 'label4',
action: "你的上传文件接口地址",
handleBeforeUpload: this.handleBeforeUpload,
handleSuccess: this.handleSuccess,
handleError: this.handleError,
},
],
formRule: {
model: {
label1: '',
label2: '',
label3: '',
label4: '',
},
refName: 'testForm',
rule: {
label1: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
label2: [
{ required: true, message: '请选择内容', trigger: 'change' }
],
label3: [
{ required: true, message: '请选择日期', trigger: 'change' }
],
label4: [
{ required: true, message: '请上传文件', trigger: 'change' }
],
}
},
fromClass: {
isColumn: false
}
}
},
methods: {
focus() {
console.log('---------------------------','获得焦点')
},
handleBeforeUpload(file) {},
handleSuccess(response, file, fileList) {},
handleError(err, file, fileList) {},
//校验表单
btnClick() {
this.$refs.testForm.$refs.testForm.validate()
}
}
}
</script>
效果如下:
封装富文本编辑器
这次项目的业务需求中用到了富文本编辑器,所以我也将其封装到form组件中来,产品给的要求是尽可能简洁,所以我最后选择了wangEditor,一个轻量级的富文本编辑器,官网地址
<template>
<div class="item-editor">
<div id="editorElem" style="text-align:left"></div>
</div>
</template>
<script>
import E from 'wangeditor'
export default {
name: 'Editor',
data() {
return {
Val: '',
editor: '',
}
},
props: {
value: {
type: String,
default: () => ''
},
action: {
type: String,
default: () => ''
},
ruleProp: {
type: String,
default: () => ''
},
menus: {
type: Array,
}
},
watch: {
Val: function (newVal) {
this.$emit('update:value',newVal)
}
},
created() {
this.Val = this.value
this.editor = new E('#editorElem')
//编辑器菜单通过主页面传入,不传为默认值 具体参数详见文档
if(this.menus) {
this.editor.config.menus = this.menus
}
//上传图片接口地址
this.editor.config.uploadImgServer = this.action
//自定义 fileName
this.editor.config.uploadFileName = 'file'
//自定义上传参数
this.editor.config.uploadImgParams = {
type: 'image',
}
console.log(this.editor.config)
},
mounted () {
this.editor.config.uploadImgHooks = {
// 上传图片之前
before: function(xhr) {
console.log(xhr)
},
// 图片上传并返回了结果,图片插入已成功
success: function(xhr) {
console.log('success', xhr)
},
// 图片上传并返回了结果,但图片插入时出错了
fail: function(xhr, editor, resData) {
console.log('fail', resData)
},
// 上传图片出错,一般为 http 请求的错误
error: function(xhr, editor, resData) {
console.log('error', xhr, resData)
},
// 上传图片超时
timeout: function(xhr) {
console.log('timeout', xhr)
},
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// result 即服务端返回的接口
console.log('customInsert', result)
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可,
insertImgFn(result.data)
}
}
this.editor.config.onchange = html => {
this.Val = html
//当编辑器内容改变时重置校验
this.$emit('resetItemValidate',this.ruleProp)
}
// 取消自动 focus
this.editor.config.focus = false
this.editor.create()
this.initEditor()
},
methods: {
initEditor () {
//如果有初始值 则赋值
if (this.value !== '') {
this.editor.txt.html(this.value)
}
},
clearEditor () {
this.editor.txt.html('')
},
}
}
</script>
效果如下:
到这一步大致的form组件基本完成了,剩下的就是将其他常见的form组件再集成进来,这里就不再介绍了,附上一份目前的项目结构。
接下来,要完善form的样式将它用到我们的后台管理当中,通过上面的fromClass我们将后台管理常见的表单格式展现出来,如查询表单,操作表单。
丰富form表单样式
我的构想中这个表单组件会用在两个场景分别是搜索表单,以及新增修改查看详情时的编辑表单。
查询表单:
编辑表单:
要满足上述要求,针对form组件页面进行改动
<template>
<el-form
class="form"
:validate-on-rule-change='false'
:model="formRule.model?formRule.model:''"
:rules="formRule.rule"
:inline="true"
:ref="formRule.refName">
<div :class="[fromClass.isColumn ? 'column-form' : 'search-form']">
<el-form-item
v-for="(item, index) in formIpt"
:style="{'width': getWidth(item.span, fromClass.isColumn), 'margin-right': '0'}"
:label-width="item.labelWidth ? item.labelWidth : '120px'"
:label="item.label"
:key="index"
:prop="item.ruleProp">
<component :is="item.type" v-bind="item" :value.sync="item.models" @resetItemValidate="resetItemValidate"></component>
</el-form-item>
</div>
<el-form-item :class="fromClass.searchButtom" v-if="searchFunc">
<el-button type="primary" :size="fromClass.formSize || 'small'" @click="query(searchFunc.query)">
{{searchFunc.queryText}}
</el-button>
<el-button :size="fromClass.formSize || 'small'" @click="reset(searchFunc.reset)">
{{searchFunc.resetText}}
</el-button>
<el-button type="success" v-if="searchFunc.add" :size="fromClass.formSize || 'small'" @click="func(searchFunc.add)">
{{searchFunc.addText}}
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import Input from "./formItem/input";
import Select from "./formItem/select";
import DatePicker from "./formItem/datePicker";
import Upload from "./formItem/upload";
import Editor from "./formItem/editor";
const CompFormItem = {
components: {
Input,
Select,
DatePicker,
Upload,
Editor,
},
name: "FormItem",
props: {
formItem: {
required: true
}
},
render(h) {
return h(`${this.formItem.type}`);
}
};
export default {
name: "Form",
components: {
Input,
Select,
DatePicker,
Upload,
Editor,
CompFormItem
},
props: {
formIpt: {
type: Array,
required: true,
default: () => []
},
formRule: {
type: Object,
required: false,
default: () => {}
},
searchFunc: {
type: Object,
required: false
},
fromClass: {
type: Object,
required: false,
default: () => {
return {
isColumn: false,
searchButtom: 'search-form-btn',
}
}
},
},
watch: {
'formIpt': {
handler(newValue) {
this.initFormRule(newValue)
},
deep: true
}
},
created () {
this.initFormRule(this.formIpt)
},
methods: {
getWidth(span, isColumn = false) {
if(!isColumn && !span) { //搜索表单 未设置span 默认一行三个搜索条件
span = 8
} else if(isColumn && !span) { //编辑表单 未设置span 默认一行展示两个
span = 12
}
span = span > 24 ? 24 : span
let width = `${Number((span / 24)*100).toFixed(1)}%`
return width
},
initFormRule (newValue) {
for (let k = 0; k < newValue.length; k++) {
this.$props.formRule.model[newValue[k].ruleProp] = newValue[k].models
}
},
formResetFields () {
this.$refs[this.$props.formRule.refName].resetFields()
},
resetItemValidate(name) {
this.$nextTick(() => {
this.$refs[this.formRule.refName].validateField(name)
})
},
query (func) {
if (this.$props.formRule.rule !== '{}') {
this.$refs[this.$props.formRule.refName].validate((valid) => {
if (valid) {
func()
}
})
} else {
func()
}
},
reset (fn) {
this.$props.formIpt.forEach(item => {
item.models = ''
})
console.log( this.$props.formIpt)
if (fn) {
fn()
}
},
func (fn) {
fn()
}
}
};
</script>
<style scoped lang="scss">
.form{
display: flex;
.search-form{
width: calc(100% - 240px);
display: flex;
flex-wrap: wrap;
}
.el-form-item{
/deep/.el-form-item__content{
width: calc(100% - 120px);
&>div{
&>div{
width: 100%;
}
}
}
}
.search-form-btn{
width: 240px;
margin-right: 0;
display: flex;
align-items: flex-end;
/deep/.el-form-item__content{
width: 100%;
}
}
.column-form {
display: flex;
width: 100%;
align-items: flex-start;
flex-wrap: wrap;
}
}
</style>
改动完的form组件,添加了如下几点
-
首先通过传入的fromClass中的isColumn判断是否位编辑表单,true为编辑表单, false为查询表单
-
新增传入属性searchFunc,传入则展示查询表单右侧按钮,默认展示查询和重置按钮,并且根据是否传入子属性add判断新增按钮是否存在
searchFunc: { query,//查询执行方法 reset,//重置执行方法 add, //新增执行方法 queryText,//查询按钮名称 resetText,//重置按钮名称 addText,//新增按钮名称 }, -
formIpt中每个对象新增span属性,来控制该表单项占一行大小,最大24为独占一行,默认查询表单一行三项,编辑表单一行两项
最后在主页面中调用组件
<template>
<div id="app">
<comForm ref="searcForm" :formIpt="searchIpt" :formRule="searchRule" :searchFunc="searchFunc" />
<el-dialog
width="70%"
:title="dialogInfo.title"
:visible.sync="dialogInfo.dialogFlag"
:destroy-on-close="true"
@close="closeDialog"
>
<comForm v-if="dialogInfo.dialogFlag" ref="testForm" :formIpt="formIpt" :formRule="formRule" :fromClass="fromClass" />
<div slot="footer" class="dialog-footer">
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="dialogConfirm">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import comForm from './components/form/form.vue'
export default {
name: 'App',
components: {
comForm
},
data() {
return {
searchIpt: [
{
type: "Input",
models: "",
label: '查询条件1',
ruleProp: '',
placeholder: "我是查询条件1",
clearable: true,
},
{
type: "Input",
models: "",
label: '查询条件2',
ruleProp: '',
placeholder: "我是查询条件1",
clearable: true,
},
{
type: "Input",
models: "",
label: '查询条件3',
ruleProp: '',
placeholder: "我是查询条件1",
clearable: true,
},
{
type: "DatePicker",
mold: "datetime",
models: "",
label: '查询条件4',
ruleProp: '',
placeholder: "我是查询条件4",
clearable: true,
},
],
searchRule: {
model: {},
refName: 'searcForm',
rule: {}
},
formIpt: [
{
type: "Input",
models: "",
label: '文本框',
ruleProp: 'label1',
placeholder: "我是默认值",
clearable: true,
focus: this.focus,
},
{
type: "Select",
models: "",
label: '下拉框',
ruleProp: 'label2',
placeholder: "我是下拉框",
clearable: true,
option: [
{
label: '选项1',
value: '1'
}
],
blur: this.blur,
change: this.change,
},
{
type: "DatePicker",
mold: "datetime",
models: "",
label: '日期选择器',
ruleProp: 'label3',
placeholder: "我是日期选择器",
clearable: true,
blur: this.blur,
},
{
type: "Upload",
models: "",
label: '文件上传',
ruleProp: 'label4',
action: "你的上传文件接口地址",
handleBeforeUpload: this.handleBeforeUpload,
handleSuccess: this.handleSuccess,
handleError: this.handleError,
},
{
type: "Editor",
models: "",
label: '富文本编辑器',
ruleProp: 'label5',
action: "你的上传文件接口地址",
span: 24
},
],
formRule: {
model: {
label1: '',
label2: '',
label3: '',
label4: '',
label5: '',
},
refName: 'testForm',
rule: {
label1: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
label2: [
{ required: true, message: '请选择内容', trigger: 'blur' }
],
label3: [
{ required: true, message: '请选择内容', trigger: 'blur' }
],
label4: [
{ required: true, message: '请上传文件', trigger: 'change' }
],
label5: [
{ required: true, message: '请输入富文本编辑器内容', trigger: 'change' },
]
}
},
fromClass: {
isColumn: true
},
searchFunc: {
query: this.query,
reset: this.reset,
add: this.add,
queryText: '搜索',
resetText: '重置',
addText: '新增',
},
dialogInfo: {
dialogFlag: false,
title: '',
type: ''
},
}
},
methods: {
focus() {
console.log('---------------------------','获得焦点')
},
change() {
console.log('---------------------------','下拉框')
},
handleBeforeUpload(file) {
console.log(file)
},
handleSuccess(response, file, fileList) {
console.log(response)
console.log(file)
console.log(fileList)
},
handleError(err, file, fileList) {
console.log(err)
console.log(file)
console.log(fileList)
},
dialogConfirm() {
console.log(this.$refs.testForm.$refs.testForm)
// this.$refs.testForm.$refs.testForm.validate()
this.$refs.testForm.$refs.testForm.validate((valid) => {
if (valid) {
this.$message({
message: '新增成功',
type: 'success'
})
this.dialogInfo.dialogFlag = false
}
})
},
query() {
console.log('查询')
},
reset() {
console.log('重置')
},
add() {
console.log('新增')
this.dialogInfo.title = '新增'
this.dialogInfo.dialogFlag = true
},
closeDialog() {
this.dialogInfo.dialogFlag = false
}
}
}
</script>
可以看到封装完的form组件我们需要往里传入几个不同的属性来满足对应需求,它们分别是:
-
formIpt接收数组,内部为传入的表单项。 -
formRule接收对象,为表单校验想对应属性。formRule: { model: {},//表单校验里验证的值与formIpt中对象的ruleProp要保持一致 refName,//表单组件绑定的ref名,可在外部通过$refs根据需求对表单进入校验/重置 rule,//校验规则 } -
searchFunc接收对象,上文有提,用来控制查询表单对应按钮以及事件 -
fromClass接收对象,用于控制表单展现类型(查询/编辑)
最终效果:
做到这里,这个简易的form组件已经可以满足简单的后台管理,接下来我们只需要根据不同业务再往form组件里继续封装子组件即可。
最后总结来看,这个组件并不复杂,但在整个编写过程中收获还是挺多的。 另附上代码地址
感谢
感谢掘友们的支持,如果本文有帮助到你的地方,记得点赞哦。
最后,祝大家新年快乐,赶走不开心。