React 审批流程组件封装-前端

975 阅读4分钟

63FE4CEC3366F4835E262AADDD3A3B3F.png 接到一个审批流程图的需求,就是我们日常OA系统当中的某一种流程,例如请假流程,出差审批流程等,都需要由我们发起,领导逐级审批,最后查看审批结果;

我觉得这是其中一种,是固定死审批人以及流程步骤,还有一种,设定多个流程,当我们选择某一个流程,流程步骤已经定好了,但是人员可以让我们自行选择,每一流程的人员由用户自定义。

这个整体效果是怎么样呢??

小二,给客官上图

image.png 需求单子就是这么个需求,下面开始试试水,看这水深不深啊!

需求拆解流程:

  • 根据流程不同,每一步的流程内容也不同;
  • 选择人员以及删除人员
  • 人员信息展示

好像也就三个流程,一个一个来

首先呢,这个流程肯定不是写死,根据数据渲染,其次,从样式上看,其中好像这是一个 步骤条组件,每一步骤下面嵌套着N多个联系人。

组件的选用以及二次封装

  • Antd Steps
  • Antd Avatar
  • Antd Badge
  • Antd Button
  • AvatarConstructor // 自己封装 头像构造器

Antd Steps

  • 步骤条组件可以完成我们每一流程当中的审批节点
<Steps 
    progressDot // 使得节点为 小圆点
    current={stepData.length} // 进度条进行到哪个节点
    direction="vertical"  // 进度条方向
    items={handleTransformData(stepData)} // 每一个节点的数据
/>
  • 步骤条数据定义
 // 步骤条数据
  const stepItems = [
    {
      title: '测试1',
      stepId: 1, // 用来标记每一个节点Id
      avatarData: [], // 用来标记每一个节点人员信息
    },
    {
      title: '测试2',
      stepId: 2,
      avatarData: [],
    },
    {
      title: '结束',
      avatarData: null,
      stepId: 3,
    },
  ]
  • 根据数据去渲染出每一个节点的审批人员信息

  // 数据转换 数据格式 根据Antd Step组件要求进去转化
  const handleTransformData = (data = []) => {
    const res = data?.map((it, idx) => {
      // eslint-disable-next-line no-unsafe-optional-chaining
      // 这一步是 使得最后一个审批节点无展示内容
      if (idx === data?.length - 1) {
        return {
          title: it.title,
          description: null,
        }
      }
    // 其余的节点 根据数据以及我们抽离的 头像构造器组件 完成渲染
      return {
        title: it.title,
        description: creatorAvatar(it.stepId, it.avatarData),
      }
    })

    return res
  }

AvatarConstructor

  • 每一个审批节点,都会存在审批人员的信息,封装成一个头像组件
  • 这里采用到 Antd Avatar && Badge组件
  • 头像构造器
 // 头像构造器
  const creatorAvatar = (stepId: number, peopleData = []) => {
  // 每一个审批人员的卡片
    const avatarBox = peopleData?.map(item => {
      const { uname, userUnit, imgUrl, pid } = item
      return (
        <ReviewerCp
          uname={uname} // 传进去的用户名
          userUnit={userUnit} // 传进去的用户所属单位
          imgUrl={imgUrl || ''} // 传进去的用户头像url
          pid={pid} // 传进去的用户id
          changeOriginData={setStepData} // 传进去的改变整个步骤条数据的 方法 === react setXXX Fn()
          originData={stepData} // 传进去的步骤条的数据
          stepId={stepId} // 当前用户所属的步骤ID
          key={pid} // 唯一标识
        />
      )
    })
    
     return (
      <div className="flex flex-col">
        {!!avatarBox?.length && <div className="flex flex-col">{avatarBox}</div>}
        // 默认的添加人员按钮
        <Button
          type="dashed"
          icon={<PlusOutlined className="add-icon" />}
          shape="circle"
          className="mt-2"
          size="large"
          onClick={() => openSelectPeopleModal(stepId, stepData)}
        />
      </div>
    )
  }

  • <ReviewerCp/>
      <div className="flex items-center">
        <div>
          <Badge
            count={<CloseCircleOutlined onClick={() => handleDelete()} />}
            offset={[-5, 5]}
            className="avatar-badge"
          >
            <Avatar
              shape={imgType || 'circle'}
              size="large"
              src={imgUrl || ''}
              icon={<UserOutlined />}
              className="avatar-img"
            />
          </Badge>
        </div>
        <div className="flex flex-col ml-2">
          <div className="uname-text">{uname || '--'}</div>
          <div className="user-unit-text">{userUnit || '--'}</div>
        </div>
      </div>
  • 效果展示-人员卡片

image.png

  • 效果展示-待添加人员状态

image.png

节点的人员添加

  • 添加人员
  • 逻辑:当用户点击添加按钮的时候,我们需要去拿到一些信息
  • 首先,选中节点的id ==> stepId
  • 其次,获取当前节点人员数据 有则追加,无则添加
// 添加人员
      const handleAddApprover = (stepId: number, peopleData = [], addPeopleData = []) => {
        const tempData = peopleData
        const tempAddData = []
        // 这里只是为了判断 一次添加多个或者单个 是否生效
        if (Math.random() > 0.5) {
          tempAddData.push(
            ...[
              {
                uname: `小面33${Math.random()}`,
                userUnit: `餐厅344${Math.random()}`,
                pid: parseInt(`${Math.random() * 100}`, 10),
              },
              {
                uname: `小面33${Math.random()}`,
                userUnit: `餐厅344${Math.random()}`,
                pid: parseInt(`${Math.random() * 100}`, 10),
              },
            ]
          )
        } else {
          tempAddData.push(
            ...[
              {
                uname: `小面33${Math.random()}`, // 头像组件需要展示的信息 用户名
                userUnit: `餐厅344${Math.random()}`, // 头像组件需要展示的信息 用户所属单位
                pid: parseInt(`${Math.random() * 100}`, 10), // 人员id,删除需要
              },
            ]
          )
        }
        peopleData?.forEach((item, idx) => {
          if (item.stepId === stepId) {
            let addResData = []
            if (item?.avatarData) {
              addResData = item?.avatarData
              addResData.push(...tempAddData)
            } else {
              addResData.push(...tempAddData)
            }
            tempData.splice(idx, 1, Object.assign(item, { avatarData: addResData }))
          }
        })
        setStepData([...tempData])
      }
  • 效果展示-添加人员

image.png

节点的人员删除

  • 删除单个节点的人员
  • 逻辑:当用户点击头像上的 Badge 的时候,我们需要去拿到一些信息
  • 首先,拿到用户点击头像的这一节点id ==> stepId
  • 其次,用户想删除的 pid
  • 数据操作,把我们不想要的去除
const handleDelete = () => {
    const tempData = originData // 备份一份原数据
    
    originData?.forEach((item, index) => {
    // 找到当前的步骤ID
      if (item.stepId === stepId) {
      // 拿出当前步骤ID的人员数据
        const tempAvatarData = item.avatarData
        // 根据用户删除的OD 去过滤出剩余的人员ID
        const resAvatarData = tempAvatarData?.filter(it => it.pid !== pid)
        // 重新set数据进去 数组的 splice 
        tempData.splice(index, 1, Object.assign(item, { avatarData: resAvatarData }))
      }
    })
    // react 当中 setData Fn()
    changeOriginData([...tempData])
  }
  • 效果展示-删除人员

image.png

哈哈哈,都看到这里了,给个点赞吧,别瞅瞅,就是说你啦~~~

小福利

F32FC225709C04E37550A42520AECE08.png

66CE7AA387F56CDA84177D841DA03093.png

DDAB9356EE08AEBD3F7984AD4521BBC0.png

A8ED5B893346122C623A247E8BF8F382.png

下期预告-树形穿梭框