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