表单列表项数据编辑功能

| 2.2k字 | 11分钟

日常开发过程中,会遇到对 React Antd Table 列表的数据进行一键编辑修改的功能,最近刚好做到这个功能,记录一下,方便后续使用参考。

主页面组件

import { useEffect, useState } from "react";
import { Form, Button, Input, message } from "antd";
import styles from "./index.less";
import ResizeTable from "@/components/ResizeTable";
import api from "../../api";

const ProcessTable = () => {
  const [edit, setEdit] = useState(false);
  const [basicData, setBasicData] = useState([]);

  const [craftOrderData, setCraftOrderData] = useState<any>({});

  const [form] = Form.useForm();

  // 查询按钮 进行提交搜索
  const handleSearchCraftOrderSumbit = (craftOrderId: string) => {
    api.searchCraftOrder(craftOrderId).then((res) => {
      setCraftOrderData(res.data);
      setBasicData(res?.data?.detail);
    });
  };

  const onFinish = () => {
    const newdata = form.getFieldsValue().packageProduct;
    setBasicData(newdata);
    // 根据需要获取并处理对应的接口入参
    const { detail, ...extraValue } = craftOrderData;
    const params = { ...extraValue, detail: newdata };

    // 编辑列表项内容后,触发【保存】按钮调用接口更新列表数据
    api.updateCraftOrder(params).then((res) => {
      if (res.code !== 200) {
        message.error(res.message);
        return;
      }
      message.success("更新成功");
      setEdit(false);
    });
  };

  // 需要的列表栏
  const columns = [
    {
      title: "控温方式",
      dataIndex: "temperatureControllerMethod",
      name: "temperatureControllerMethod",
      key: "temperatureControllerMethod",
      align: "center",
      renderType: edit ? "input" : "text",
    },
    {
      title: "设定温度",
      dataIndex: "settingFurnaceTemperature",
      name: "settingFurnaceTemperature",
      key: "settingFurnaceTemperature",
      align: "center",
      renderType: edit ? "input" : "text",
    },
    {
      title: "设定时间",
      dataIndex: "settingDuration",
      name: "settingDuration",
      key: "settingDuration",
      align: "center",
      renderType: edit ? "input" : "text",
    },
    {
      title: "运行时间",
      dataIndex: "runningDuration",
      name: "runningDuration",
      key: "runningDuration",
      align: "center",
      renderType: edit ? "input" : "text",
    },
    {
      title: "重量",
      dataIndex: "weight",
      name: "weight",
      key: "weight",
      align: "center",
      renderType: edit ? "input" : "text",
    },
  ];

  const ListTableForm = (props: any) => {
    const { edit, basicData, form } = props;
    const rules = [{ required: true, message: "请填充内容!" }];

    const [cls, setCls] = useState<any[]>([]);

    function renderType(_: any, record: any, index: any, other: any) {
      const { renderType } = other; //renderType是让编辑态的表格,继承查看态的表格
      switch (renderType) {
        case "input":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <Input
                defaultValue={record.Temperature}
                size={"middle"}
                allowClear
                disabled={other.key === "num"}
              />
            </Form.Item>
          );
        case "input-value":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <Input defaultValue={record.value} />
            </Form.Item>
          );
        case "input-description":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <Input defaultValue={record.description} />
            </Form.Item>
          );
        case "text":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <span>{record.name}</span>
            </Form.Item>
          );
        case "text-value":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <span>{record.value}</span>
            </Form.Item>
          );
        case "text-description":
          return (
            <Form.Item
              style={{ marginBottom: 0 }}
              rules={rules}
              name={[record.name, other.name]}
              fieldKey={[record.fieldKey, other.name]}
            >
              <span>{record.description}</span>
            </Form.Item>
          );
        default:
          return (
            <Form.Item shouldUpdate={true} style={{ marginBottom: 0 }}>
              {({ getFieldValue }) => {
                return (getFieldValue(props.formName) || [])?.[index]?.[
                  other?.name
                ];
              }}
            </Form.Item>
          );
      }
    }

    useEffect(() => {
      const _newProps = props.cls.map(
        (item: { [x: string]: any; render: any }) => {
          const { render, ...resetProps } = item;
          return {
            ...resetProps,
            render: (text: any, record: any, index: any) =>
              renderType(text, record, index, item),
          };
        }
      );
      setCls(_newProps);
    }, [props.cls]);

    return (
      <>
        {edit ? (
          // 编辑态模式下,搭配 Form.List 内嵌Form表单就可以通过form拿到所有列表项修改的值
          <Form.List name={props.formName || "tableForm"}>
            {(fields) => {
              return (
                <>
                  <Form.Item style={{ marginBottom: 0 }}>
                    <ResizeTable
                      rowClassName={() => styles.childtable}
                      tableHeadheight={90}
                      idName="tables" //必传,用来获取表格高度做数据滚动检测
                      title={() => "表单设置"}
                      footer={() => {
                        return (
                          <div className={styles.footers}>
                            <Button
                              onClick={() => {
                                setEdit(false);
                              }}
                            >
                              取消
                            </Button>
                            <Button
                              type="primary"
                              className={styles.primary}
                              onClick={onFinish}
                            >
                              保存
                            </Button>
                          </div>
                        );
                      }}
                      pagination={false}
                      rowKey={"name"}
                      dataSource={fields}
                      columns={cls}
                    />
                  </Form.Item>
                </>
              );
            }}
          </Form.List>
        ) : (
          <Form.Item style={{ marginBottom: 0 }}>
            <ResizeTable
              rowClassName={() => styles.childtable}
              tableHeadheight={90}
              idName="table"
              title={() => "表单设置"}
              footer={() => {
                return (
                  <Button
                    onClick={() => {
                      setEdit(true);
                      form.setFieldsValue({ packageProduct: basicData });
                    }}
                  >
                    编辑
                  </Button>
                );
              }}
              pagination={false}
              rowKey={"name"}
              dataSource={basicData}
              columns={columns}
            />
          </Form.Item>
        )}
      </>
    );
  };

  useEffect(() => {
    // 根据参数,查询table列表数据
    handleSearchCraftOrderSumbit("123");
  }, []);

  return (
    <div className={styles.root}>
      <Form form={form} name="basic" className={styles.main}>
        <ListTableForm
          hideAction={!edit}
          edit={edit}
          formName="packageProduct"
          fields={basicData}
          basicData={basicData}
          cls={columns}
          form={form}
        />
      </Form>
    </div>
  );
};

export default ProcessTable;

无非就是编辑态模式下,通过Form.List内嵌Form.Item表单项的方式在form中拿到所有列表修改项的值。这里页面用到了额外封装的ResizeTable组件,其实就是对Antd的Table做了层封装 使其支持resize动态平铺以及高度自控制的滚动。


ResizeTable组件

import React from "react";
import Table from "@/components/Table";
import { useGetResizeHeight } from "./hook/useGetResizeTable";
import styles from "./index.less";

/*** 自适应高度表格*
 * @功能*
 * 1、自动占满页面剩余高度*
 * 2、改变浏览器窗口时自动适应剩余高度不出现外部滚动条*
 * @前提*
 *  页面自身设置的有高度,而非根据子元素撑开高度*
 * @使用方法*
 * 1、安装lodash--yarn add lodash,仅使用里面的防抖方法*
 * 2、在需要使用的地方导入--import ResizeTable from '@/components/ResizeTable/ResizeTable'*
 * 3、使用--<ResizeTable api和antd的Table一样/>* @demo* import ResizeTable from '@/components/ResizeTable/ResizeTable';*
 * 4、tableHeadheight(表头高度)paginationHeight(分页组件高度可选) middleHeight(底部预留高度可选)
 **/
const ResizeTable: React.FC<any> = (props) => {
  //表头高度<tableHeadheight> 分页组件高度(paginationHeight) 底部预留高度(middleHeight)
  const { tableHeadheight, paginationHeight, middleHeight, idName } = props;
  const [tableHeight] = useGetResizeHeight(
    `${idName}`,
    tableHeadheight,
    paginationHeight ? paginationHeight : 0,
    middleHeight ? middleHeight : 0
  );
  let tableProps = { ...props, scroll: { y: tableHeight } };
  if (props && props.scroll && props.scroll.x) {
    tableProps.scroll.x = props.scroll.x;
  }
  return (
    <div className={styles.tableWrap}>
      <div id={idName} className={styles.table}>
        <Table {...tableProps} />
      </div>
    </div>
  );
};
export default ResizeTable;

useGetResizeHeight动态缩放组件

import { useEffect, useState } from "react";
import debounce from "lodash/debounce";
import { useSize } from "ahooks";
export function useGetResizeHeight(
  name: string,
  tableHeadheight: number,
  paginationHeight: number,
  middleHeight: number
) {
  //设置撑高表格外部包裹元素的高度
  const [tableHeight, setTableHeight] = useState(500);
  const size = useSize(document.getElementById(name));
  const handleGetTableHeight = () => {
    setTimeout(() => {
      let height = document.getElementById(name)!.clientHeight;
      //高度-表头高度<tableHeadheight>-分页组件高度(paginationHeight)-底部预留高度(middleHeight)
      height = height - tableHeadheight - paginationHeight - middleHeight - 6;
      setTableHeight(height);
    });
  };
  useEffect(() => {
    handleGetTableHeight();
    const debounced = debounce(handleGetTableHeight, 200);
    window.addEventListener("resize", debounced);
    return () => window.removeEventListener("resize", debounced);
  }, [size]);

  return [tableHeight];
}

Tips: 这里有个小点要注意下,因为这里的table表头只有一层,使用这种方式是完全没问题的。但如果是合并表头的header,columns中内嵌了children后,使用这个ListTableForm编辑的时候就会导致columns只能显示第一层的部分,导致表头header丢失了。
这个时候可以使用geek的方式进行处理

多表头合并态下,如何编辑的时候固定表格头不变,以下是部分实例代码 参考

  {
    edit ? (
      // 编辑态
      <Form.List name={props.formName || "tableForm"}>
        {(fields) => {
          return (
            <>
              <Form.Item style={{ marginBottom: 0 }}>
                {/* 只展示固定表头做占位用 不展示数据 同时根据style清除原有的table-body样式 */}
                <Table
                  className={`${styles.emptyTableBody}`}
                  pagination={false}
                  dataSource={[]}
                  columns={colums as any}
                />

                {/* 固定上面的表头后,把现在的表头通过样式藏掉,因为表单的数据需要通过column里的name做绑定 */}
                <Table
                  className={`${styles.emptyTableColumn}`}
                  pagination={false}
                  rowKey={"name"}
                  footer={() => {
                    return (
                      <div className={styles.footers}>
                        <Button
                          onClick={() => {
                            setEdit(false);
                          }}
                          style={{ marginRight: 15 }}
                        >
                          取消
                        </Button>
                        <Button
                          type="primary"
                          className={styles.primary}
                          onClick={onFinish}
                        >
                          保存
                        </Button>
                      </div>
                    );
                  }}
                  dataSource={fields}
                  columns={cls}
                />
              </Form.Item>
            </>
          );
        }}
      </Form.List>
    ) : (
      // 仅展示
      <Form.Item style={{ marginBottom: 0 }}>
        <Table />
      </Form.Item>
    )
  }

  // 上面对应的Table隐藏表头head和表格body列表内容的样式
  .emptyTableBody {
    :global(.ant-table-tbody) {
      display: none;
    }
  }
  .emptyTableColumn {
    :global(.ant-table-thead) {
      display: none;
    }
  }