前言
以往我再开发表格的多个批量处理的操作的时候,都是挨个实现,提取部分公共函数。最近在做需求设计的时候,我灵感一现,后知后觉的发现,批量处理如果后续的操作是一致的,是不是可以做成一套完整的流程,不同之处可以根据 type 进行区分。
批量操作,在进行实际操作之前,其实做了一些特殊校验,像一个步骤条。第一步,先校验是否选择了表格数据,通常没有选择数据的时候是不允许进行批量操作的,我们会提示:“请至少选择一项”。第二步,二次确认提示,这一步在大部分开发需求中是不存在的,但是此次的开发中需要进行这一步。第三步,数据状态正确性校验,比如批量取消操作,在订单平台中,已完成的订单是不允许取消的,所以要进行数据的状态校验,选择的全部数据是正确的情况下才能继续批量操作。
之前我都是通过条件判断进行下一步操作,这样开发很快,但是如果产品想在某一步之后再加一个步骤,那么我需要改两个地方,当前步骤的下一步改完新增步骤,原来的下一步放到新增步骤中,相当于把步骤条断开之后再重组。那么我何不直接做成链式的调用,每次新增步骤,只需要重组一次步骤条即可。
功能实现基于 React 框架,组件开发使用的 hooks 函数式组件。
需求分析
UI
需求分析
我把需求总结之后,以表格的形式展示所有的需求点,这样看的更加直观且易懂:
其中支付状态和订单状态的全部状态值如下:
通过需求列表不难看出,所有的批量操作三个步骤很相似,对于两个操作的不同之处可以设置开关变量来控制。
开发设计
从上面的需求分析不难看出,批量操作和数据结构中的单链表很像,每个节点的指针指向后继节点,所以这次的功能开发采用链式编程的方式实现。将每层的操作维护到数组中,每层操作成功之后调取下一个操作。
具体实现
设置批量操作分类对象
/** @name 批量操作分类对象 */
const batchObj = {
cancel: {
confirmTips: '是否取消所有选中的订单?', // 二次确认提示, 为空时跳过二次确认
failedTips: '不可选择', // 校验不通过提示
inspectionFlag: false, // 校验是否通过标识
payStatusList: [1], // 需要校验的支付状态列表
},
push: {
confirmTips: '',
failedTips: '不可选择', // 校验不通过提示
inspectionFlag: false, // 校验是否通过标识
payStatusList: [1], // 需要校验的支付状态列表
orderStatusList: [2], // 需要校验的订单状态列表
},
};
复制代码
按钮组
为每个按钮都添加点击事件。
<div onClick={() => batchOperate('cancel')}>
批量取消
</div>
<div onClick={() => batchOperate('push')}>
批量推送
</div>
复制代码
按钮点击事件
/**
* 批量操作
* @param {string} type 操作按钮的key值
* @return {void} 无
*/
const batchOperate = type => {
return applyChainCall(type, [batchCheckboxOptions, batchDoubleConfirm, batchInspection]);
};
复制代码
触发链式调用事件
默认从第一个开始,可调整触发位置。比如某个批处理操作可能从第二个方法开始,可以根据 type 的值作为条件判断重置 index 的值为 2。
/**
* 链式调用 当调用到该函数的时候,该函数才执行
* @param {Array} chainCalls 所有的链式函数列表
* @param {string} type 操作的按钮类型
* @param {number} index 链式函数调用的位置 默认第一个
* @return {void} 无
*/
const applyChainCall = (type, chainCalls, index = 0) => {
return chainCalls[index](type, chainCalls)();
};
复制代码
获取下一步函数的处理事件
链式函数列表变量中存放了所有步骤的函数方法,再调用下一步方法的时候,可以将当前函数名传入到该方法中,可以获取传入函数在链式函数列表中的索引值,那么下一步方法是该索引值增加 1 所得到的方法,直接执行通过计算获取到的下一步方法就能进入下一个操作。
/**
* 获取下一步函数的处理事件 直接执行下一步操作
* @param {string} type 操作的按钮类型
* @param {Array} chainCalls 所有的链式函数列表
* @param {Function} funcType 当前步骤的方法名
* @return {Function} 下一步方法执行
*/
const goToNext = (type, chainCalls, funcType) => {
let funcIndex = 0;
chainCalls.forEach((chainItem, chainIndex) => {
if (chainItem === funcType) {
funcIndex = chainIndex;
}
});
return chainCalls[funcIndex + 1](type, chainCalls)();
};
复制代码
表格选择数据事件
// 1)先定义一个变量,存储表格选中的数据。
/** @name 表格选中的数据 */
const [batchSelectList, setBatchSelectList] = useState([]);
// 2)因为我的项目使用的antd框架,所以获取选中项数据直接调用antd提供API接口。
// 通过rowSelection的onChange事件获取选中项,并将数据更新到batchSelectList变量中。
/**
* 表格多选操作-设置选择项数据
* @param {void} 无
* @return {void} 无
*/
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
setBatchSelectList(selectedRows);
},
};
复制代码
是否选择表格数据事件
判断是否选择了表格数据,根据表格选中的数据变量 batchSelectList 的数组长度,如果为 0 表示一项都没有选中,给出提示,反之标识已选中数据,进入下一步操作。
/**
* 批量操作-是否选择列表数据
* @param {string} type 操作的按钮类型
* @param {Array} chainCalls 所有的链式函数列表
* @return {void} 无
*/
const batchCheckboxOptions = (type, chainCalls) => {
return () => {
// => true: 如果列表数据一项都没有选中则给出提示弹窗,反之直接到下一步
if (batchSelectList.length === 0) {
Modal.confirm({
keyboard: true,
maskClosable: true,
title: '提示',
content: '请至少选择一项',
});
} else {
return goToNext(type, chainCalls, batchCheckboxOptions);
}
};
};
复制代码
二次确认事件
二次确认事件是否执行可以根据批量操作分类对象中 confirmTips 变量是否为空进行判断,如果不为空则展示二次确认弹窗,反之直接进入下一步操作。
/**
* 批量操作-二次确认
* @param {string} type 操作的按钮类型
* @param {Array} chainCalls 所有的链式函数列表
* @return {void} 无
*/
const batchDoubleConfirm = (type, chainCalls) => {
return () => {
const batchItem = batchObj[type];
// => true: 有二次提示的才弹出二次提示弹窗,反之直接到下一步
if (batchItem.confirmTips) {
Modal.confirm({
keyboard: true,
maskClosable: true,
title: '提示',
content: batchItem.confirmTips,
onOk: () => {
return goToNext(type, chainCalls, batchDoubleConfirm);
},
});
} else {
return goToNext(type, chainCalls, batchDoubleConfirm);
}
};
};
复制代码
校验操作事件
根据校验失败列表的长度,区分调用不同的方法。如果校验失败列表的数组长度不为 0,表示有校验失败的表格数据,则调用校验失败方法,反之调用校验成功方法。
/**
* 批量操作-校验操作
* @param {string} type 操作的按钮类型
* @return {void} 无
*/
const batchInspection = type => {
return () => {
const batchItem = batchObj[type];
/** @name 校验失败列表 */
const failedList = batchSelectList.filter(item => !getIncludesFlag(batchItem, item));
/** @name 校验成功列表 */
const passedList = batchSelectList.filter(item => getIncludesFlag(batchItem, item));
// =>true: 如果校验失败列表的数组长度不为0,表示有校验失败的表格数据,则调用校验失败方法,反之调用校验成功方法
if (failedList.length !== 0) {
batchInspectionFailed(type, failedList);
} else {
batchInspectionPassed(type, passedList);
}
};
};
复制代码
根据需求,不同批量操作校验的状态类型不一样,所以我将状态的校验提炼出了单独的方法进行维护。
/**
* 批量操作-筛选状态
* @param {Object} batchItem 批处理对象
* @param {Object} item 筛选的对象
* @return {boolean} 筛选状态的布尔值
*/
const getIncludesFlag = (batchItem, item) => {
const payStatusList = batchItem.payStatusList;
const orderStatusList = batchItem.orderStatusList;
const hasPayStatus = !!payStatusList;
const hasOrderStatus = !!orderStatusList;
let flag = false;
//=>true: 如果校验状态存在,则判断校验状态列表中是否包含当前需要校验的状态值
if ((!hasPayStatus || (hasPayStatus && payStatusList.includes(item.payStatus))) && (!hasOrderStatus || (hasOrderStatus && orderStatusList.includes(item.orderStatus)))) {
flag = true;
}
return flag;
};
复制代码
校验成功方法
/**
* 批量操作-校验通过
* @param {string} type 操作的按钮类型
* @param {Array} list 通过校验的数据列表
* @return {void} 无
*/
const batchInspectionPassed = (type, list) => {
// 调用接口,一般会将选择的表格数据处理成后端要求的入参的格式,传入接口中。
};
复制代码
校验失败方法
/**
* 批量操作-校验不通过
* @param {string} type 操作的按钮类型
* @param {Array} list 没有通过校验的数据列表
* @return {void} 无
*/
const batchInspectionFailed = (type, list) => {
const batchItem = batchObj[type];
let contentList = [];
list.forEach(item => {
contentList.push(item.adjustNo);
});
contentList = contentList.map(item => '订单号' + item);
const content = contentList.join(',') + batchItem.failedTips;
Modal.confirm({
keyboard: true,
maskClosable: true,
title: '提示',
content: content,
});
};
复制代码
以上,批量处理的操作就全部完成了。
总结
总结一下上面的链式方法的主要功能:
当调用到该函数的时候,该函数才执行;
可以控制从第几个函数开始;
支持跳过某个函数;
对于新增操作步骤,改动代价小,便于后期维护。
除此以外,通过设置分类对象统一管理不同分类但是功能相似的模块或者操作,使得代码简洁、易读、易维护。
如果大家有什么好的建议,欢迎留言。━(`∀´)ノ!
评论