写点什么

「工作小记」多个批量操作的链式实现

作者:叶一一
  • 2022 年 9 月 05 日
    北京
  • 本文字数:4195 字

    阅读完需:约 14 分钟

「工作小记」多个批量操作的链式实现

前言

以往我再开发表格的多个批量处理的操作的时候,都是挨个实现,提取部分公共函数。最近在做需求设计的时候,我灵感一现,后知后觉的发现,批量处理如果后续的操作是一致的,是不是可以做成一套完整的流程,不同之处可以根据 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,  });};
复制代码


以上,批量处理的操作就全部完成了。

总结

总结一下上面的链式方法的主要功能:

  • 当调用到该函数的时候,该函数才执行;

  • 可以控制从第几个函数开始;

  • 支持跳过某个函数;

  • 对于新增操作步骤,改动代价小,便于后期维护。


除此以外,通过设置分类对象统一管理不同分类但是功能相似的模块或者操作,使得代码简洁、易读、易维护。


如果大家有什么好的建议,欢迎留言。━(`∀´)ノ!

发布于: 2 小时前阅读数: 11
用户头像

叶一一

关注

苍生涂涂,天下缭燎,诸子百家,唯我纵横。 2022.09.01 加入

非职业传道受业解惑前端程序媛,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。

评论

发布
暂无评论
「工作小记」多个批量操作的链式实现_前端_叶一一_InfoQ写作社区