React基于antd Tree 的可编辑及同级拖拽排序树

Snipaste_2022-11-03_17-37-00.png

贴合业务只有两级 可以放开末尾的按钮控制

import React, {Key, useState} from "react";
import {Input, Modal, Space, Tree} from "antd";
import {FieldDataNode} from "rc-tree/lib/interface";
import type {TreeProps} from 'antd/es/tree';
import {
  EditTwoTone,
  PlusCircleTwoTone,
  MinusCircleTwoTone,
  CloseCircleTwoTone,
  CheckCircleTwoTone,
} from "@ant-design/icons";

import {nanoid} from "nanoid";

type DataNode = FieldDataNode<{
  key: string;
  title: string;
  level: number;
  editValue?: string;
  isEditable?: boolean;
}>

const treeData: DataNode[] = [
  {
    title: "0",
    key: nanoid(),
    level: 1,
    children: [
      {
        title: "0-1",
        level: 2,
        key: nanoid(),
      },
      {
        title: "0-2",
        level: 2,
        key: nanoid(),
      },
      {
        title: "0-3",
        level: 2,
        key: nanoid(),
      },
      {
        title: "0-4",
        level: 2,
        key: nanoid(),
      },
    ],
  },
];

export const CategoryModal = (props) => {

  const [gData, setGData] = useState<DataNode[]>([{
      title: "root",
      key: 'root',
      level: 0,
      children: treeData
    }]
  );
  const [expandedKeys, setExpandedKeys] = useState<Key[]>(['root'].concat(gData.map(item => item.key)));

  const onAdd = (key) => {
    const data = [...gData];
    loop(data, key, (item) => {
      const children = Array.isArray(item.children) ? [...item.children] : []
      children.push({
        key: nanoid(), // 这个 key 应该是唯一的
        title: "default",
        level: item.level + 1
      })
      item.children = children
    });
    setExpandedKeys([...expandedKeys, key])
    setGData(data);
  };

  const onEdit = (key) => {
    const data = [...gData];
    closeNode(data);
    loop(data, key, (item) => {
      item.editValue = item.title
      item.isEditable = true
    });
    setGData(data);
  };

  // input 改变
  const onChange = (e, key) => {
    const data = [...gData];
    loop(data, key, (item) => {
      item.editValue = e.target.value
    });
    setGData(data)
  };

  // 保存
  const onSave = (key) => {
    const data = [...gData];
    loop(data, key, (item) => {
      item.isEditable = false
      item.title = item.editValue || ''
    });
    console.log(data)
    setGData(data);
  };

  // 关闭输入
  const onClose = (key) => {
    const data = [...gData];
    loop(data, key, (item) => {
      item.editValue = item.title
      item.isEditable = false
    });
    setGData(data);
  };

  const closeNode = (data) =>
    data.forEach((item) => {
      item.isEditable = false;
      if (item.children) {
        closeNode(item.children);
      }
    });

  const onDelete = (key) => {
    const data = [...gData];
    loop(data, key, (_, index, arr) => {
      arr.splice(index, 1);
    });
    setGData(data);
  };

  // 找到节点数据并回调处理节点方法 callback回调: node-当前节点 i-当前节点序号 data-包含当前节点的节点组 parentNode 当前节点父节点
  const loop = (
    data: DataNode[],
    key: React.Key,
    callback: (node: DataNode, i: number, data: DataNode[], parent?: DataNode) => void,
    parentNode?: DataNode,
  ) => {
    for (let i = 0; i < data.length; i++) {
      if (data[i].key === key) {
        return callback(data[i], i, data, parentNode);
      }
      if (data[i].children) {
        loop(data[i].children!, key, callback, data[i]);
      }
    }
  };

  const onDrop: TreeProps['onDrop'] = info => {
    const dropKey = info.node.key; // 终止键
    const dragKey = info.dragNode.key;// 拖动键
    const dropPos = info.node.pos.split('-');  // 放置位置

    // -1是移动到和他平级在他上面 1是移动到和他平级在他下面 0是移动到他下面作为他子集
    const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);

    const data = [...gData];

    // 被拖动的节点-dragKey
    let dragObj: DataNode;
    let dragIndex: number;
    let dragArr: DataNode[];
    let dragParent: DataNode | undefined;
    loop(data, dragKey, (item, index, arr, parent) => {
      dragObj = item;
      dragIndex = index
      dragArr = arr
      dragParent = parent
    });

    // 放置的节点-dropKey
    loop(data, dropKey, (item, index, arr, parent) => {
      console.log(dragParent?.key, parent?.key, item.key, dropPosition)
      // 同级
      if (dragParent?.key == parent?.key) {
        dragArr.splice(dragIndex, 1) // 卸载
        if (dropPosition === -1) {
          arr.splice(index!, 0, dragObj!); // 移动到和他平级在他上面
        } else {
          arr.splice(index! + 1, 0, dragObj!); // 移动到和他平级在他下面
        }
      }
      // 同父级
      if (dragParent?.key == item.key) {
        dragArr.splice(dragIndex, 1) // 卸载
        item.children?.splice(0, 0, dragObj!) // 放到子节点第一个
      }
    });
    setGData(data);
  };

  const handleOk = () => {}

  const handleCancel = () => {
    props.closeModel()
  }

  return (
    <Modal
      open={props.open}
      onOk={handleOk}
      title='Hello World'
      onCancel={handleCancel}
    >
      <Tree
        draggable
        blockNode
        onDrop={onDrop}
        expandedKeys={expandedKeys}
        onExpand={setExpandedKeys}
        treeData={gData}
        titleRender={(nodeData: DataNode) => {
          if (nodeData.isEditable) {
            return (
              <Space>
                <Input maxLength={10} value={nodeData.editValue || ''} onChange={(e) => onChange(e, nodeData.key)}/>
                <CloseCircleTwoTone twoToneColor="#f81d22" onClick={() => onClose(nodeData.key)}/>
                <CheckCircleTwoTone onClick={() => onSave(nodeData.key)}/>
              </Space>
            );
          } else {
            return (
              <Space>
                <span>{nodeData.title}</span>
                {nodeData.level > 0 && (
                  <EditTwoTone onClick={() => onEdit(nodeData.key)}/>
                )}
                {nodeData.level < 2 && (
                  <PlusCircleTwoTone twoToneColor="#95de64" onClick={() => onAdd(nodeData.key)}/>
                )}
                {nodeData.level > 0 && (
                  <MinusCircleTwoTone twoToneColor="#f81d22" onClick={() => onDelete(nodeData.key)}/>
                )}
              </Space>
            );
          }
        }}
      >
      </Tree>
    </Modal>
  );
}

参考
https://juejin.cn/post/6844903815586512910
https://blog.csdn.net/weixin_44147791/article/details/124084064
https://ant.design/components/tree-cn/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • I 前言 Antd是基于Ant Design设计体系的React UI组件库,主要用于研发企业级中后台产品,在前端...
    nojsja阅读 4,902评论 0 7
  • 目的 如果你使用 Vue 开发项目,那么你一定用过或听过大名鼎鼎的 Element-UI[https://elem...
    CondorHero阅读 5,480评论 0 0
  • 前端需要了解的色彩知识 概述 在前端领域,我们常常需要跟色彩打交道,除了一部分从设计转过来的前端外(ps:历史原因...
    维李设论阅读 689评论 0 5
  • 一) 定义对象: https://www.runoob.com/js/js-obj-intro.html[http...
    wg689阅读 448评论 0 1
  • Bookmarks 书签栏 入职 华为新员工小百科(刷新时间202003023) - 人才供应知多少 - 3MS知...
    Btrace阅读 1,336评论 0 0