form表单页面在业务复杂之后,存在很多的数据操作函数,并且大部分的数据操作也是基于form表格数据的。
因此设想使用class去封装这些数据以及所有的数据操作函数,也是貌似可行的。
以下是根据现有业务的一些设想
如下销售订单编辑的表单页面为例
//一般我们习惯在data中定义form数据对象,由于vue的观察机制,所以我们需要在form对象中定义出form
//中含有的变量,否在vue会检查不到该属性值的变化,可能会导致一些渲染问题
data(){
return {
form:{
name:'',
id:'',
...
}
}
}
由于表单字段比较多,一次在data中定义的字段属性也就比较多,会导致vue中的代码冗长,但是并没有实际的逻辑代码。
所以我们或许会将这些参数的定义单独拿出来写,例如
//data.js
const form ={
name:'',
id:'',
...
}
export default data
然后在vue中引入,并赋值给data中的对应值,然后将form的一些数据操作,交互逻辑放在vue的methods中,
这些都是顺其自然的做法,但是由于vue的单文件形势,form的大量html模版语言已经比较冗长,再加上form的相关操作逻辑,代码动则上千,后期的功能复杂之后,methods也越来越多。导致后期维护困难,代码也比较不利于阅读。所以是否可以做一些代码封装,把相互关联的操作逻辑放在一起,即便与理解,也便于使用。
所以就想到了使用class来封装form数据。看上去form的使用也比较贴合class的特性。form的参数都可以放在class的实例属性中,而相关的数据操作方法则可以放在class的实例方法上,由于form的数据操作大部分也是基于form,因此也正好可以this直接指向form本身,和vue的写法并没有太大的出入。封装完之后,vue中只需要调用class中暴露的实例方法来完成数据的操作, 让vue文件代码更加纯净,易读,维护也会方便一些
先大致看一下以上业务的form表单大致需要完成哪些数据操作:
- 参数的初始化,接口获取到数据之后,需要赋值给form
- 关联数据的变化,例如获取到省市区县三者的id,需要拼接成数组形势的地区数据
- form的重置
目前看大致也就以上三点。而逻辑冗长的主要就是第二点的数据联动。
以下是设想的各个需求的实现:
//首先我们先创form的class
class Form {
constructor(obj={}){
this.name='';
this.id='';
...
this.reloadForm(obj);
}
}
使用assign初始化form参数,如果只需要一个空数据的form,则只需要new Form() 而不需要传参。
考虑到接口的返回数据,一般会比本地定义的数据要多。所以在获取到接口数据之后,直接重新 new Form(data),
以此来初始化所有的参数,确保vue可以检测到所有的属性值的变化来渲染页面。
因此class不应提供reLoad等方法来重置数据,会存在一点的风险,直接重新创建实例。
而reSet之类的重置数据方法是可以提供的,在reSet之前,所有的属性已经被实例注册,直接置为空值即可。
//首先我们先创form的class
class Form {
constructor(obj={}){
this.name='';
this.id='';
...
Object.assign(this,obj);
}
reSet(){
Object.keys(this).forEach(key=>{
this[key]=null
})
}
}
接下来处理数据的联动
由于element组件的设计,联级组件的值是一个数组,层级制依次往后,但是接口存储和获取的增是分开的id值,
比如省市区的值,接口是返回的loadProvinceId,loadCityId,loadDistrictId三个值,传入组件时我们必须将他们拼接,
unloadArea,loadArea只有在重载数据和用户通过组件修改时才会变化,所以拼接只需要考虑在重载数据时就可以,所以我们可以直接在构造器中数据初始化完调用拼接
class Form {
constructor(obj={}){
this.name='';
this.id='';
...
Object.assign(this,obj);
this.reunloadArea()
this.reloadArea()
}
reSet(){
Object.keys(this).forEach(key=>{
this[key]=null
})
}
reunloadArea(){
this.unloadArea=this.unloadProvinceId&&this.unloadCityId
&&this.unloadDistrictId?
[this.unloadProvinceId,this.unloadCityId,this.unloadDistrictId]:[]
}
reloadArea(){
this.loadArea=this.rovinceId&&this.loadCityId&&this.loadDistrictId?
[this.rovinceId,this.loadCityId,this.loadDistrictId]:[]
}
}
其他参数的联动,比如选取供应商之后,赋值对应的name、code等值,一般都是根据一个支撑数据来做匹配,
设计一个函数,接受三个值forCreate对应支撑数据数组,attrs需要修改的数据及对应键数组,匹配规则
参数名 | 格式 | 备注 |
---|---|---|
forCreate | [{ id:1,name:’a’,code:’b’ }] |
|
attrs | [{ prop:’loadSiteName’,attr:’extra1’ }] |
prop对应form中的对应属性名, attr对应支撑数据中的属性名 |
rule | item=>item.id==value | 匹配的函数 |
setAttrs(forCreate=[],attrs=[],rulr=()=>false){
const data = forCreate.find(rule) || {};
let setValueProps = [];
let setNullProps = [];
const tempForm = {};
attrs.forEach(({ prop, label }) => {
if (data[label]) {
tempForm[prop] = data[label];
setValueProps.push(prop);
} else {
tempForm[prop] = null;
setNullProps.push(prop);
}
});
this.reloadForm(tempForm);
return { setValueProps, setNullProps }; //抛出修改过的属性,以便做更多的扩展处理
}
//重置表单数据,内部使用,外部使用如果构造器中无此属性会导致vue监听无效
reloadForm(obj) {
Object.assign(this, obj);
if (obj.hasOwnProperty('unloadDistrictId')) {
this.reSetUnloadArea();
}
if (obj.hasOwnProperty('loadDistrictId')) {
this.reSetLoadArea();
}
}
扩展处理比如
在组件的chang事件中调用该方法
<el-select v-model="value" placeholder="请选择" @change="(value)=>{
this.reSet(value,this.forCreate.loads)
}">
<el-option
v-for="item in forCreate.loads"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.disabled">
</el-option>
</el-select>
//
methods:{
reset(value,forCreate){
this.form.setAttrs(forCreate,[{prop:'name',attr:'extra1'},
{prop:'code',attr:'extra2'}],
item=>item.id==value)
}
}
最后提交form,需要获取form中的所有值,同样的,loadArea之类拼接的数值,也是需要拆分提交的
loadAreaIds(){
const [loadProvinceId,loadCityId,loadDistrictId] = this.loadArea
return {loadProvinceId,loadCityId,loadDistrictId}
}
unloadAreaIds(){
const [unloadProvinceId,unloadCityId,unloadDistrictId] = this.unloadArea
return {unloadProvinceId,unloadCityId,unloadDistrictId}
}
getFormData(){
return Object.assign({},this,this.loadAreaIds(),this.unloadAreaIds())
}
//vue中使用
this.form.getFormData()
数据过滤也可以加入其中
getFormData(acceptableHeaderColumnKeys=[]){
const form = Object.assign({},this,this.loadAreaIds(),this.unloadAreaIds())
return ObjectRemoveNull(filterAttrsData(form,acceptableHeaderColumnKeys))
}
以上的设想是为了简化vue主函数中的代码量,让form的操作单独维护,相关的数据操作封装在一起,也可以防止一些类似的数据操作,多处使用时遗漏相关的数据联动等
最后完成form定义代码:
class Form {
constructor(obj = {}) {
this.orderNo = '';
this.loadArea = [];
this.loadSiteCode = '';
this.unloadSiteCode = '';
this.loadProvinceId = '';
this.loadCityId = '';
this.loadDistrictId = '';
this.unloadProvinceId = '';
this.unloadCityId = '';
this.unloadDistrictId = '';
this.unloadArea = [];
this.planNo = '';
this.bizType = '';
this.salesOrgId = '';
this.itemId = '';
this.deliveryMode = '';
this.requestedQty = '';
this.uom = '';
this.currency = '';
this.salesOfficeId = '';
this.customerId = '';
this.customerName = '';
this.unloadSiteId = '';
this.unloadSiteName = '';
this.unloadAddress = '';
this.salesUnitPrice = '';
this.salesAmount = '';
this.requestedUnloadDate = '';
this.customerOrderNo = '';
this.supplierId = '';
this.loadSiteName = '';
this.loadAddress = '';
this.purchaseUnitPrice = '';
this.purchaseAmount = '';
this.requestedLoadDate = '';
this.lotNo = '';
this.dueTo = '';
this.customerContact = '';
this.customerNumber = '';
this.unloadRemarks = '';
this.itemCode = '';
this.itemDescription = '';
this.specialRequirement = '';
this.orderRemarks = '';
this.settlement = '';
this.settlementName = '';
this.reloadForm(obj);
}
//拼接装货地区
reSetLoadArea() {
this.loadArea =
this.loadProvinceId && this.loadCityId && this.loadDistrictId
? [this.loadProvinceId, this.loadCityId, this.loadDistrictId]
: [];
}
//拼接卸货地区
reSetUnloadArea() {
this.unloadArea =
this.unloadProvinceId && this.unloadCityId && this.unloadDistrictId
? [this.unloadProvinceId, this.unloadCityId, this.unloadDistrictId]
: [];
}
//处理地区数据
loadAreaIds() {
const [loadProvinceId, loadCityId, loadDistrictId] = this.loadArea;
return { loadProvinceId, loadCityId, loadDistrictId };
}
//处理地区数据
unloadAreaIds() {
const [unloadProvinceId, unloadCityId, unloadDistrictId] = this.unloadArea;
return { unloadProvinceId, unloadCityId, unloadDistrictId };
}
//表单置空
resetForm() {
Object.keys(this).forEach(key => {
this[key] = null;
});
}
//设置关联字段
setAttrs(forCreate = [], attrs = [], rule = () => false) {
const data = forCreate.find(rule) || {};
let setValueProps = [];
let setNullProps = [];
const tempForm = {};
attrs.forEach(({ prop, label }) => {
if (data[label]) {
tempForm[prop] = data[label];
setValueProps.push(prop);
} else {
tempForm[prop] = null;
setNullProps.push(prop);
}
});
this.reloadForm(tempForm);
return { setValueProps, setNullProps }; //抛出修改过的属性,以便做更多的扩展处理
}
//重置表单数据,内部使用,外部使用如果构造器中无此属性会导致vue监听无效
reloadForm(obj) {
Object.assign(this, obj);
if (obj.hasOwnProperty('unloadDistrictId')) {
this.reSetUnloadArea();
}
if (obj.hasOwnProperty('loadDistrictId')) {
this.reSetLoadArea();
}
}
//获取表单数据
getFormData(acceptableHeaderColumnKeys = []) {
const form = Object.assign(
{},
this,
this.loadAreaIds(),
this.unloadAreaIds()
);
return ObjectRemoveNull(filterAttrsData(form, acceptableHeaderColumnKeys));
}
}
目前在销售订单编辑页面有实践,需求暂时满足