react antd可编辑树

该文章展示了一个使用React、AntDesign库和typescript编写的可编辑树形组件。组件支持添加、删除、编辑节点,以及拖放操作,用于管理具有层级结构的数据。

效果:
在这里插入图片描述

代码:

// index.tsx
import {
  ChangeEvent,
  forwardRef,
  useEffect,
  useImperativeHandle,
  useState,
} from "react";
import { Dropdown, Menu, Popconfirm, Tree, TreeProps, Input } from "antd";
import styles from "./index.module.scss";
import {
  CheckOutlined,
  CloseOutlined,
  EditOutlined,
  MinusOutlined,
  PlusOutlined,
} from "@ant-design/icons";
const { TreeNode } = Tree;

export interface TreeNodeType {
  value: string;
  defaultValue: string;
  key: string;
  isEditable: boolean;
  children?: any;
  title?: any;
  parentKey: string;
  type: number; // 0 根结点, 1 产品线, 2 子团队, 3 小组
}

const ADD_NODE_TYPE = {
  1: "产品线",
  2: "子团队",
  3: "小组",
};

const ADD_SUB_NODE_TYPE = {
  1: ["产品线"],
  2: ["子团队"],
  3: ["小组"],
};

interface Props {
  data: TreeNodeType[];
}

function EditableTree(props: Props, ref: any) {
  const { data: initData } = props;

  useImperativeHandle(ref, () => ({
    getTreeData: () => treeData,
  }));

  const [treeData, setTreeData] = useState<TreeNodeType[]>([]);
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);

  useEffect(() => {
    if (initData) {
      setTreeData(initData);
    }
  }, [initData]);

  const onExpand = (expandedKeys: React.Key[]) => {
    console.log("onExpand", expandedKeys);
    setExpandedKeys(expandedKeys);
  };

  const renderAddMenu = (key: string, type: number) => {
    const items = ADD_SUB_NODE_TYPE[type];
    return (
      <Menu>
        {items.map((item: string) => (
          <Menu.Item key={item} onClick={() => onAdd(key, type)}>
            {item}
          </Menu.Item>
        ))}
      </Menu>
    );
  };

  const renderTreeNodes = (data: TreeNodeType[]) =>
    data.map((item: TreeNodeType) => {
      if (item.isEditable) {
        item.title = (
          <div className={styles.titleContainer}>
            <Input
              className={styles.inputField}
              value={item.value}
              onChange={(e) => onChangeInput(e, item.key)}
            />
            <span className={styles.operationField}>
              <CloseOutlined
                onClick={() => onClose(item.key, item.defaultValue)}
                style={{ marginLeft: 10 }}
              />
              <CheckOutlined
                onClick={() => onSave(item.key)}
                style={{ marginLeft: 10 }}
              />
            </span>
          </div>
        );
      } else {
        item.title = (
          <div className={styles.titleContainer}>
            <span style={{ wordBreak: "break-all" }}>{item.value}</span>
            <span className={styles.operationField}>
              <EditOutlined
                onClick={() => onEdit(item.key)}
                style={{ marginLeft: 10 }}
              />
              {item.type < 3 && (
                <Dropdown overlay={renderAddMenu(item.key, item.type + 1)}>
                  <PlusOutlined style={{ marginLeft: 10 }} />
                </Dropdown>
              )}
              {item.parentKey === "0" ? null : (
                <Popconfirm
                  title={`该组织【${item.value}】下的附属组织都会删除,是否确定删除?`}
                  onConfirm={() => onDelete(item.key)}
                  onCancel={() => {}}
                  okText="是"
                  cancelText="否"
                >
                  <MinusOutlined style={{ marginLeft: 10 }} />
                </Popconfirm>
              )}
            </span>
          </div>
        );
      }

      if (item.children) {
        return (
          <TreeNode title={item.title} key={item.key}>
            {renderTreeNodes(item.children)}
          </TreeNode>
        );
      }

      return <TreeNode {...item} />;
    });

  const onAdd = (key: string, type: number) => {
    console.log("add");
    if (expandedKeys.indexOf(key) === -1) {
      setExpandedKeys([...expandedKeys, key]);
    }
    setTreeData(addNode(key, treeData, type));
  };

  const addNode = (key: string, data: TreeNodeType[], type: number) =>
    data.map((item: TreeNodeType) => {
      if (item.key === key) {
        const defaultValue = {
          value: `${ADD_NODE_TYPE[type]}XXX`,
          defaultValue: `${ADD_NODE_TYPE[type]}XXX`,
          key: Math.random(), // 唯一 key
          parentKey: key,
          isEditable: false,
          type,
        };
        item.children
          ? item.children.push(defaultValue)
          : (item.children = [defaultValue]);
      } else {
        if (item.children) {
          addNode(key, item.children, type);
        }
      }
      return item;
    });

  const onDelete = (key: string) => {
    console.log("delete");
    // 为什么要加这一行,不然删除后数据不能回显到页面上
    setExpandedKeys(expandedKeys.filter((i) => i !== key));
    setTreeData(deleteNode(key, treeData));
  };

  const deleteNode = (key: string, data: TreeNodeType[]) => {
    data.forEach((item, index) => {
      if (item.key === key) {
        data.splice(index, 1);
      }
      if (item.children) {
        deleteNode(key, item.children);
      }
    });
    return data;
  };

  const onEdit = (key: string) => {
    console.log("edit");
    setTreeData(editNode(key, treeData));
  };

  const editNode = (key: string, data: TreeNodeType[]) =>
    data.map((item: TreeNodeType) => {
      if (item.key === key) {
        item.isEditable = true;
      } else {
        item.isEditable = false;
      }
      // 当某节点处于编辑状态,并改变数据,点击编辑其他节点时,此节点变成不可编辑状态,value 需要回退到 defaultvalue
      item.value = item.defaultValue;
      if (item.children) {
        editNode(key, item.children);
      }
      return item;
    });

  const onClose = (key: string, defaultValue: string) => {
    console.log("close");
    setTreeData(closeNode(key, defaultValue, treeData));
  };

  const closeNode = (key: string, defaultValue: string, data: TreeNodeType[]) =>
    data.map((item: TreeNodeType) => {
      item.isEditable = false;
      if (item.key === key) {
        item.value = defaultValue;
      }
      if (item.children) {
        closeNode(key, defaultValue, item.children);
      }
      return item;
    });

  const onSave = (key: string) => {
    console.log("save");
    setTreeData(saveNode(key, treeData));
  };

  const saveNode = (key: string, data: TreeNodeType[]) =>
    data.map((item: TreeNodeType) => {
      if (item.key === key) {
        item.defaultValue = item.value;
      }
      if (item.children) {
        saveNode(key, item.children);
      }
      item.isEditable = false;
      return item;
    });

  const onChangeInput = (event: ChangeEvent<HTMLInputElement>, key: string) => {
    console.log("onchange");
    setTreeData(changeNode(key, event.target.value, treeData));
  };

  const changeNode = (key: string, value: string, data: TreeNodeType[]) =>
    data.map((item: TreeNodeType) => {
      if (item.key === key) {
        item.value = value;
      }
      if (item.children) {
        changeNode(key, value, item.children);
      }
      return item;
    });

  const onDrop: TreeProps["onDrop"] = (info) => {
    console.log(info);
    const dropKey = info.node.key;
    const dragKey = info.dragNode.key;
    const dropPos = info.node.pos.split("-");
    const dropPosition =
      info.dropPosition - Number(dropPos[dropPos.length - 1]);

    const loop = (
      data: TreeNodeType[],
      key: React.Key,
      callback: (node: TreeNodeType, i: number, data: TreeNodeType[]) => void
    ) => {
      for (let i = 0; i < data.length; i++) {
        if (data[i].key === key) {
          return callback(data[i], i, data);
        }
        if (data[i].children) {
          loop(data[i].children!, key, callback);
        }
      }
    };
    const data = [...treeData];

    // Find dragObject
    let dragObj: TreeNodeType;
    loop(data, dragKey, (item, index, arr) => {
      arr.splice(index, 1);
      dragObj = item;
    });

    if (!info.dropToGap) {
      // Drop on the content
      loop(data, dropKey, (item) => {
        item.children = item.children || [];
        item.children.unshift(dragObj);
      });
    } else if (
      ((info.node as any).props.children || []).length > 0 && // Has children
      (info.node as any).props.expanded && // Is expanded
      dropPosition === 1 // On the bottom gap
    ) {
      loop(data, dropKey, (item) => {
        item.children = item.children || [];
        item.children.unshift(dragObj);
      });
    } else {
      let ar: TreeNodeType[] = [];
      let i: number;
      loop(data, dropKey, (_item, index, arr) => {
        ar = arr;
        i = index;
      });
      if (dropPosition === -1) {
        ar.splice(i!, 0, dragObj!);
      } else {
        ar.splice(i! + 1, 0, dragObj!);
      }
    }
    setTreeData(data);
  };

  return (
    <div style={{ width: 400 }}>
      {treeData.length > 0 && (
        <Tree
          //   expandedKeys={expandedKeys}
          //   onExpand={onExpand}
          defaultExpandAll={true}
          //   draggable
          //   onDrop={onDrop}
          blockNode
          selectable={false}
        >
          {renderTreeNodes(treeData)}
        </Tree>
      )}
    </div>
  );
}

export default forwardRef(EditableTree);
// index.module.scss
.inputField {
    border: none;
    border-bottom: 1px solid;
    background: none;
    line-height: normal;
}

.titleContainer{
    display: flex;
    justify-content: flex-start;
    .operationField{
        margin-left: auto;
        min-width: 100px;
    }
}
:global {
    .ant-tree li .ant-tree-node-content-wrapper:hover {
        background-color: unset;
    }
}

参考:https://blog.csdn.net/sujinchang939024/article/details/124719638,https://github.com/JerryMissTom/react-editable-tree

<think>好的,我现在需要帮助用户解决如何在React中使用Ant Design(antd)实现可编辑表格的问题。用户提到了他们看到的几个引用,其中涉及可编辑单元格和整个表格的编辑。首先,我应该整理这些引用中的信息,看看有什么可以借鉴的部分。 首先,引用[1]展示了一个可编辑单元格的组件EditableCell,它通过状态管理编辑模式,并在保存时调用父组件的方法。这个组件使用了Input和Icon,可能适合单独单元格的编辑。引用[2]提到了使用useState来管理整个表格的数据,可能涉及到表格级别的状态控制。引用[3]提到在React中处理表单数据绑定的挑战,特别是与antd Pro的可编辑表格相关,可能需要使用form实例或者自定义的渲染方法。引用[4]则展示了一个EditTable组件的使用方式,可能封装了可编辑表格的逻辑。 接下来,我需要考虑用户的需求是实现可编辑表格,可能有两种方式:逐行编辑和单元格编辑。根据引用中的例子,用户可能需要一个分步的指导,结合这些引用的代码片段。 首先,我应该介绍如何实现可编辑单元格,参考引用[1]的EditableCell组件。这个组件通过toggleEdit切换编辑状态,保存时调用handleSave方法。然后,在表格的columns配置中使用这个自定义组件,将每个需要编辑的列的render方法替换为EditableCell实例,并传递相应的props,如record和handleSave。 然后,整个表格的数据管理可能需要使用useState,如引用[2]所示,将数据保存在父组件的状态中。当单元格保存时,更新父组件的状态,触发重新渲染。 另外,引用[3]提到了在React中处理表单数据的困难,特别是双向绑定的问题。可能需要使用antd的Form组件或者自定义的onChange处理函数来同步数据的变化。例如,在EditableCell中,当用户输入时,更新本地状态,并在保存时调用父组件的handleSave来更新整个数据源。 最后,引用[4]中的EditTable组件可能是一个封装好的可编辑表格,用户可以直接传入columns和dataSource,并指定保存函数。这可能是一个更高级的封装,但用户可能需要了解如何构建这样的组件。 在生成相关问题的时候,需要考虑用户可能遇到的后续问题,比如性能优化、表单验证、复杂交互等,这些都可以作为相关问题提出。 现在需要将这些思考整理成一个结构化的回答,分步骤说明如何实现可编辑表格,结合代码示例,并确保引用的正确使用。同时,生成的相关问题要覆盖用户可能感兴趣的扩展内容。</think>以下是使用React和Ant Design实现可编辑表格的步骤详解: ### 一、实现可编辑单元格(基础方案) 参考引用[1]的独立单元格编辑方案,创建可编辑单元格组件: ```jsx import React from 'react'; import { Input, Icon } from 'antd'; class EditableCell extends React.Component { state = { editing: false, value: this.props.value }; toggleEdit = () => { this.setState({ editing: !this.state.editing }, () => { if (this.state.editing) this.input.focus() }) }; save = () => { this.props.onChange(this.state.value); this.toggleEdit(); }; render() { return this.state.editing ? ( <Input ref={node => this.input = node} value={this.state.value} onChange={e => this.setState({ value: e.target.value })} onBlur={this.save} onPressEnter={this.save} /> ) : ( <div onClick={this.toggleEdit}> {this.state.value || ' '} <Icon type="edit" style={{ marginLeft: 8 }} /> </div> ) } } ``` ### 二、构建可编辑表格 参考引用[2][4]实现表格数据管理: ```jsx import { Table } from 'antd'; const EditableTable = ({ dataSource, columns }) => { const [data, setData] = useState(dataSource); const handleSave = (record, newValue, dataIndex) => { const newData = data.map(item => item.key === record.key ? { ...item, [dataIndex]: newValue } : item ); setData(newData); }; const mergedColumns = columns.map(col => { if (!col.editable) return col; return { ...col, render: (text, record) => ( <EditableCell value={text} onChange={value => handleSave(record, value, col.dataIndex)} /> ) } }); return <Table dataSource={data} columns={mergedColumns} pagination={false} />; } ``` ### 三、使用示例 ```jsx const columns = [ { title: '姓名', dataIndex: 'name', editable: true }, { title: '年龄', dataIndex: 'age', editable: true } ]; const data = [ { key: '1', name: '张三', age: 28 }, { key: '2', name: '李四', age: 32 } ]; <EditableTable columns={columns} dataSource={data} /> ``` ### 四、扩展功能实现 1. **行级编辑**(参考引用[3]): ```jsx const expandedRowRender = (record) => ( <Form> <Form.Item label="姓名"> <Input value={record.name} onChange={e => handleFieldChange(record.key, 'name', e.target.value)} /> </Form.Item> </Form> ) ``` 2. **数据验证**: ```jsx // 在EditableCell中添加验证逻辑 validate = value => { if (this.props.validator) { return this.props.validator(value) } return true } save = () => { if (this.validate(this.state.value)) { this.props.onChange(this.state.value) } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值