vue表单中批量导入功能_Vue 编辑 & 新建表单复用的一些思考

写在前面最近的工作,都是一些后台管理类项目,涉及到表单的使用上,有着大量的相同内容的表单,根据使用场景分为新建表单与编辑表单。在日常的搬砖过程中,对此类出现较多的场景做了一些思考。

以下将通过一个小案例结合几种常见场景一步一步进行分析并实现。

举个栗子实现一个员工管理功能,其中包含员工列表,新增员工,修改员工信息功能。新增员工表单新增员工表单编辑员工表单

上面图中可以看出,员工的编辑表单和删除表单基本一致。

实现

以下涉及到的代码使用element组件,其实无论使用iview、element或其他vue组件库,思路上没有太多差别。

最差实现没有封装,实现一个功能后,另一个功能代码直接复制。开发过程中见过不少人采用这种方式进行开发,这可能是完成功能最快的方式,同时也是带来问题最多的。不容易维护,复制的代码中如果存在bug,相当于将bug也复制了一份。后续解决bug,或者添加新功能需要花经历修改两处代码。比如:表单需要增加一项出生年月的表单项,需要在添加和编辑的代码中都进行修改。

代码冗余重复率高,常见的代码质量管理的工具中都会包含有重复率一项。

… 总之,复制粘贴代码违反了DRY原则:系统的每一个功能都应该有唯一的实现。如果多次遇到同样的问题,就应该抽象出一个共同的解决方法,不要重复开发同样的功能。

封装组件,提取整个弹窗将整个弹出层和表单封装成组件,新建和编辑功能直接调用。 平时看同事代码时,发现这也是同事们用的比较多的实现方式。

:title="title"

:visible="visible"

@update:visible="handleVisibleChange"

>

取消

保存

export default {

name: "EditForm",

props: {

// 是否显示表单 visible: {

type: Boolean,

default: false

},

// 弹窗的title title: String,

// 回显数据 model: {

type: Object,

default: null

}

},

data() {

return {

form: {

cid: '',

name: '',

address: ''

},

rules: {

name: {required: true, message: '请输入姓名', trigger: 'blur'},

cid: {required: true, message: '请输入身份证号码', trigger: 'blur'},

address: {required: true, message: '请输入联系地址', trigger: 'blur'}

},

}

},

watch: {

// 监听 编辑时回显表单 model(employeeInfo) {

this.form = {...employeeInfo} // 简单的浅克隆 }

},

methods: {

handleSave() {

// 表单验证 返回数据 this.$refs.EditForm.validate((valid) => {

if (valid) {

this.$emit('save', this.form)

}

})

},

handleVisibleChange(value) {

this.$emit('update:visible', value)

}

}

}

在父组件中,只需要通过props向表单组件传递一些必要的数据,即实现了新增和编辑功能。

编辑时可以通过传入名为model的props,表单组件中通过watch的model的变化实现表单的回显。

title="新增员工"

:visible.sync="showAddForm"

@save="handleAddEmployee"

/>

:model="editFormData"

title="编辑员工"

:visible.sync="showEditForm"

@save="handleEditEmployee"

/>

import EditForm from "./EditForm";

export default {

components: {

EditForm

},

data() {

return {

showEditForm: false, // 是否显示编辑表单 showAddForm: false, // 是否显示新增表单 editFormData: {}

}

},

methods: {

// 添加一个员工 handleAddEmployee(employeeInfo) {

// do something },

// 编辑一个员工 handleEditEmployee(employeeInfo) {

// do something }

}

}

现在,编辑和新建复用了同一个组件。如果需求变更:增加一个出生年月的表单项,之后只需要修改这一个文件,就完成两个功能的修改。

这种实现方式已经可以满足我们目前的需求,但是还是会存在一些问题:不符合单一职责原则:现在表单组件中既封装了表单中的数据和功能,也有操作按钮,Dialog组件的一些数据及操作(如:visible状态),表单组件中通过定义title、visible等props对Dialog所需的props进行了一次中转,产生了一些额外的代码。

不仅如此,假设现在需要对保存按钮也进行区分:编辑员工时,保存按钮文案改为编辑,或者在新建员工时增加暂存按钮及功能。我们就需要通过增加表单组件的props,或在组件内部进行判断来实现,这里通过增加按钮或修改文案举例,实际上通过slot插槽或其他方式也可以解决这个问题,真实的业务场景可能更为复杂。目的是想说明这些没有涉及到表单功能的修改,但是我们依然需要修改同一个组件。

组件覆盖的场景比较少,在案例中,我们的添加修改都是弹窗的形式,但是在更复杂的真实业务中,可能需要通过一个页面进行添加员工,编辑通过弹窗实现。或者需要实现批量添加员工这样的功能,因为我们的组件内部也集成了Dialog,就没办法很好的完成。

更进一步,剥离Dialog和按钮基于对第二种方式出现问题的思考,我们可以对表单组件与Dialog,操作按钮进行剥离,进一步抽象表单组件。

export default {

name: "EditForm",

props: {

// 回显数据 model: {

type: Object,

default: null

}

},

data() {

return {

form: {

cid: '',

name: '',

address: ''

},

rules: {

name: {required: true, message: '请输入姓名', trigger: 'blur'},

cid: {required: true, message: '请输入身份证号码', trigger: 'blur'},

address: {required: true, message: '请输入联系地址', trigger: 'blur'}

},

}

},

watch: {

// 监听 编辑时回显表单 model(employeeInfo) {

this.form = {...employeeInfo} // 简单的浅克隆 }

},

methods: {

// 对外暴露获取数据的方法,内部进行表单的校验,父组件中通过refs调用 getValue() {

return new Promise((resolve, reject) => {

this.$refs.EditForm.validate((valid) => {

if (valid) {

resolve({...this.form})

} else {

reject('表单校验没通过,可以抛出一个异常')

}

})

})

}

}

}

在剥离了Dialog和操作按钮后,相关的中转props的逻辑也被剥离出了组件。

由于保存按钮现在不在表单组件中了,没办法通过emit的方式对父组件暴露表单数据,所以改为在methods中注册了一个getValue方法,方法中进行了表单验证,通过返回一个Promise的方式,返回表单数据或异常。父组件在使用这个新的表单组件时,通过ref注册一个表单组件的引用,调用表单组件中的getValue方法获取组件内部的数据。

具体操作如下:

取消

保存

取消

保存

import NewEditForm from "./NewEditForm";

export default {

components: {

NewEditForm

},

data() {

return {

showEditForm: false, // 是否显示编辑表单 showAddForm: false // 是否显示新建表单 },

methods: {

...

// 编辑一个员工 handleEditEmployee() {

this.$refs.NewEditForm.getValue()

.then((employeeInfo) => {

// 调用编辑接口 })

.catch((error) => {

// 处理表单验证失败 })

},

// 添加一个员工 handleAddEmployee() {

this.$refs.AddForm.getValue()

.then((employeeInfo) => {

// 调用添加接口 })

.catch((error) => {

// 处理表单验证失败 })

}

}

}

通过这样封装,当我们需要修改Dialog或者操作按钮,直接在父组件中修改即可。表单组件粒度更小也更灵活,可以覆盖到更多的使用场景,想要实现一个添加员工页面或 批量添加员工,就方便很多。

实现批量添加员工通过v-for循环表单组件实现批量添加员工表单功能。

代码如下:

序号:{{index + 1}}

删除


type="primary"

style="display: block"

@click="handleAddForm"

>

添加员工

取消

保存

import NewEditForm from "./NewEditForm";

export default {

components: {

NewEditForm

},

data() {

return {

showForm: true,

employeeList: [Symbol()] // 删除功能需要提供唯一key值才能保证严谨 }

},

methods: {

// 新增一个表单 handleAddForm() {

this.employeeList.push(Symbol())

},

// 移除表单 handleRemoveForm(symble) {

this.employeeList.splice(this.employeeList.indexOf(symble), 1)

},

// 通过refs.getValue方法配合Promise.all方法 handleSave() {

Promise.all(

this.$refs.EmployeeForm.map(formRef => {

return formRef.getValue()

})

)

.then(formData => {

// do something })

.catch(error => {

// 处理验证失败的异常 })

}

}

}

因为考虑到如果有删除单个员工表单功能,v-for循环的是一个装有Symbol的数组,做为每个表单唯一的key值。

点击保存时则通过循环调用每个表单组件中的getValue方法,将返回的Promise对象放入数组,最后通过Promise.all方法获取表单数据或处理异常。

(完。

最后

这是我第一篇文章。

平时工作也遇到过不少问题,也学习到了不少的优秀的代码,由于没有记录的习惯,总是会忘掉,希望自己能养成定期总结、思考的习惯 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值