@alifd/next react+form+table组合式写法

前言

最近前端大佬用@alifd/next来写后台管理系统,给我分配了一些页面类似下面的页面
在这里插入图片描述
其中分公司、存储地址、存储区三级联动。table中每一项都可以进行删除。添加按钮的弹框的页面类似下面这个样子。
在这里插入图片描述
点击弹框的行,多选,全选都可以进行更新到第一张图的页面

遇到的问题

  • 怎么控制这个table变化呢,怎么进行,输入框单独可以操作,下拉框单独。如果对一个数据元进行来回更改,那么页面的消耗就很大。等等问题,下面就来例举几个吧

实现

之前看了一下官方的field关于类似方案的例子,发现写的比较简洁。也不好操作。所以我就尝试form+table组合式写法魔改了一下

1,利用form对table进行包裹,因为form内嵌了field,这样应该就比较方便一些。

   const fieldTable = Field.useField(); //初始化fieldTable
    <Form field={
    
    fieldTable}>
         <Table.StickyLock
           size="medium"
           primaryKey="id" // 识别的唯一值
           fixedHeader
           maxBodyHeight={
    
    400} //和fixedHeader结合,设置table盒子的高度
           dataSource={
    
    dataSource} //数据源
           columns={
    
    columns} // 列表
           rowSelection={
    
    rowSelection} // 选择每一行的操作,默认多选
         />
       </Form>

选择rowSelection设置

 const [selectedRowKeys, setSelectedRowKeys] = useState([]);
 const [selectedRows, setSelectedRows] = useState([]);
 const rowSelection = {
    
    
    selectedRowKeys,
    onChange: (_selectedRowKeys, _selectedRows) => {
    
     // 单选时的操作
      setSelectedRowKeys(_selectedRowKeys);
      setSelectedRows(_selectedRows);
    },
    onSelectAll: (selected, _selectedRows) => {
    
     //全选时的操作
      setSelectedRowKeys(_selectedRows.map((item) => item.cid));
    },
    getProps: (record) => {
    
     // 禁用的☑️的操作
      return {
    
    
        disabled: mode === 'view',
      };
    },
  };

2,对columns进行封装,(删除了几个重复类型的,大致能懂应该)

const columns = [
      // 新增/编辑用到的id,itemId,不显示但是可以区分(用是否有itemId来区分是点击编辑时拿到的数据,还是编辑时新增还是新增时新增的数据)
      {
    
    
        width: '0px', // 不会显示在页面
        dataIndex: 'id',
        align: 'center',
        lock: 'left',
        cell: (value, index, record) => {
    
    
          return <div style={
    
    {
    
     display: 'none' }}>{
    
    FormChildItem(value, index, record, 'id')}</div>;
        },
      },
      {
    
    
        width: '0px',// 不会显示在页面
        dataIndex: 'itemId',
        align: 'center',
        lock: 'left',
        cell: (value, index, record) => {
    
    
          return <div style={
    
    {
    
     display: 'none' }}>{
    
    FormChildItem(value, index, record, 'itemId')}</div>;
        },
      },
      {
    
    
        title: '类型',
        dataIndex: 'wirType',
        align: 'center',
        lock: 'left',
        cell: (value, index, record) => {
    
    
          return value ? (
            <Tag size="small" type="normal" color={
    
    wirTypeColor[value]}>
              {
    
    wirTypeName[value] || 'BTS备货'}
            </Tag>
          ) : (
            <Tag size="small" type="normal" color="turquoise">
              BTS备货
            </Tag>
          );
        },
      },
      // 下面是formItem进行嵌套的
      mode === 'add' ? isNull() : isRequireNo(), // 根据是编辑还是新增,动态显示table的列
      {
    
    
        title: '物料名称',
        dataIndex: 'itemName',
        align: 'center',
        lock: 'left',
        cell: (value, index, record) => {
    
    
          return FormChildItem(value, index, record, 'itemName');
        },
      },
      {
    
    
        title: '键号',
        dataIndex: 'itemCode',
        align: 'center',
        cell: (value, index, record) => {
    
    
          return FormChildItem(value, index, record, 'itemCode');
        },
      },
      {
    
    
        title: '申请数量',
        dataIndex: 'applyQuantity',
        align: 'center',
        cell: (value, index, record) => {
    
    
          return FormChildItemRequire(value, index, record, 'applyQuantity');
        },
      },
      {
    
    
        title: '存储区',
        dataIndex: 'areaId',
        align: 'center',
        cell: (value, index, record) => {
    
    
          return FormChildItemSelect(value, index, record, 'areaId');
        },
      },
    ];

isNull用来对三元表达式进行操作,不然写table就会报错

 const isNull = () => {
    
    
    return {
    
    
      align: 'center',
      width: '0px',
    };
  };
  const isRequireNo = () => {
    
    
    return {
    
    
      title: '订单编号',
      dataIndex: 'requireNo',
      align: 'center',
      lock: 'left',
      cell: (value, index, record) => {
    
    
        return <div>{
    
    value ? `${
      
      value}⚠️` : ''}</div>;
      },
    };
  };

3 封装的FormChildItem普通的表单显示,FormChildItemRequire可校验的InputFormChildItemSelect可校验的select。为什么要给普通FormChildItem也用表单包裹呢,主要是后端同学要用到里面的数据。要做处理,下面因该就会明白点。

  • 下面是用每一行的id来进行区分的,⚠️不要用索引index来代替,因为删除的时候会出问题
 const FormChildItem = (value, index, record, _name) => {
    
    
    return (
      <FormItem
        isPreview // 查看的时候不能修改的一个属性
        name={
    
    `${
      
      _name}-${
      
      record.id}`}
        labelAlign="left"
      >
        <Input defaultValue={
    
    value} />
      </FormItem>
    );
  };
  const FormChildItemSelect = (value, index, record, _name) => {
    
    
    return (
      <FormItem
        isPreview={
    
    mode === 'view'}
        asterisk
        name={
    
    `${
      
      _name}-${
      
      record.id}`}
        required
        requiredMessage="请选择存储区"
        labelAlign="left"
      >
        <Select style={
    
    {
    
     width: '110px' }} defaultValue={
    
    value} dataSource={
    
    storageType} />
      </FormItem>
    );
  };
  const FormChildItemRequire = (value, index, record, _name) => {
    
    
    return (
      <FormItem
        isPreview={
    
    mode === 'view'}. 
        asterisk
        name={
    
    `${
      
      _name}-${
      
      record.id}`}
        required
        requiredMessage="请输入申请数量"
        labelAlign="left"
      >
        <NumberPicker defaultValue={
    
    value} min={
    
    0} />
      </FormItem>
    );
  };

4,既然讲到了删除,那就写一下删除的方法(新增就不写了,比较简单,弹框子组件用setDatasource来设置传值)

  // 点击删除按钮删除
  const clickDeleteDataObj = () => {
    
    
    if (!selectedRowKeys?.length) {
    
    
      Message.error('请选择物料明细进行删除');
    } else {
    
    
      const newArr = removeDuplicates(dataSource, selectedRows); //dataSource:新增时通过从弹框选中拿到的数据数组;selectedRows:第一个页面删除时拿到的行数数组
      setDataSource(newArr); // 重新设置table的源数据
      // 重置key
      setSelectedRowKeys([]); //清空被第一个页面被选中的框
    }
  };
  // removeDuplicates删除的方法, 删除table数据,通过id来删除,简单有效。
  const removeDuplicates = (arr1, arr2) => {
    
    
    const ids = new Set(arr2.map((item) => item.id));
    return arr1.filter((item) => !ids.has(item.id));
  };

5,保存的时候校验该怎么做呢?

这里做了两个表单校验,通过页面1可以观察到,顶部也是个form要进行校验,它的field名字就叫field

 // 保存草稿| 提交申请
  const handleOk = (type: number) => {
    
    
    // type 1为保存草稿,2为提交申请
    field.validate(field.getNames(), async (error) => {
    
    
      if (error === null) {
    
    
        const paramsValue: Object = field.getValues(); // 获取顶部的form数据
        if (!dataSource?.length) {
    
    
          return Message.error('请添加物料信息');
        }
        // 对table进行校验
        fieldTable.validate(fieldTable.getNames(), async (errorIn) => {
    
    
          const paramsValueIn: Object = fieldTable.getValues(); // 获取table的数据,这里获取的数据就是被FormItem包裹的数据,
          //但是拿到的是对象,而且是所有`name-id`的对象,所以下面的方法将对象变为数组
          const detailVoList = objChangeArr(paramsValueIn); // 
          if (errorIn === null) {
    
    
            let TCode = 0;
            let TMsg = '';
            if (mode === 'add') {
    
    
              // 新增,submitImmediately:保存草稿(false)和提交申请(true)
              const {
    
     code = 0, msg = '' } = await services.fetchAddReplenishmentDraftAndApply({
    
    
                ...paramsValue,
                detailVoList,
                submitImmediately: type === 2,
              });
              TCode = code;
              TMsg = msg;
            } else {
    
    
              // 编辑
              // 只需要editDataSource被标记删除的数据
              const _editDetailVoList = editDataSource.filter((v: any) => v.delete);
              console.log(_editDetailVoList, '_editDetaVoList');
              const {
    
     code = 0, msg = '' } = await services.fetchEditReplenishmentDraftAndApply({
    
    
                ...paramsValue,
                detailVoList: [...detailVoList, ...(_editDetailVoList || [])],
                submitImmediately: type === 2,
              });
              TCode = code;
              TMsg = msg;
            }
            if (TCode === 101) {
    
    
              Message.success(TMsg);
              onOk?.(); // 父组件传入的值,不用管
              field.resetToDefault();
            }
          } else {
    
    
            // console.log(error);
          }
        });
      } else {
    
    
        // console.log(error);
      }
    });
  };
  • 对象转数组
    类似这样的对象在这里插入图片描述
    转为数组在这里插入图片描述
 // 将对象转换成数组
  const objChangeArr = (obj) => {
    
    
    const _detailVoList = Object.keys(obj)
      .reduce((acc: any, key: string) => {
    
    
        const [prefix, id] = key.split('-');
        if (prefix === 'id') {
    
    
          acc.push({
    
    
            // 编辑时,在有名为itemId的前提下,拿到id,否则(编辑时新增的,新增时新增的)就不传id,
            id: obj[`itemId-${
      
      id}`] ? obj[`id-${
      
      id}`] : undefined, // 这个属于业务逻辑,不注重
            itemId: obj[`itemId-${
      
      id}`] || obj[`id-${
      
      id}`],
            itemCode: obj[`itemCode-${
      
      id}`],
            applyQuantity: obj[`applyQuantity-${
      
      id}`],
            areaId: obj[`areaId-${
      
      id}`],
          });
        }
        return acc;
      }, [])
      .filter(Boolean);
    return _detailVoList;
  };

还有其他问题

  • 1,保存,编辑时回显的被删除的数据,并打上标记delete
    可以页面初始化的时候设置另外一个datasource数据源进行保存,然后合并进去
  • 2,三级联动问题,参考第一个页面,重新选择分公司或者存储地址,table里面的数据要清空,所以我是这样写的
 // 清空所有存储区 // 这里利用dataSource来进行遍历。通过field的setValue,重新设置为空字符串来清空
  const clearAreaId = () => {
    
    
    dataSource?.map((itemClear: any) => {
    
    
      const areaIdNum = `areaId-${
      
      itemClear.id}`;
      fieldTable.setValue(areaIdNum, '');
      return itemClear;
    });
  };

后记

这么写完后,我这边功能没什么问题了,但是大佬却说我写的过于复杂,不好维护,要改成field写法,但是对于field写法,我是感觉不想写。不知道其他大佬有什么建议没有。

多多分享,快快成长。

猜你喜欢

转载自blog.csdn.net/weixin_45701199/article/details/129885442