树形结构-CRUD接口

先看一下效果:整体的效果
   

新增效果 --默认值是 default

 修改效果 -

大致效果如上

---------------------------------------------------------------------------------------------------------------------------------
下面讲解代码如何实现的
 根据你使用的UI的框架中的树结构---形成相应的数据结构(递归形式)-如果后端给你分好了(感谢他)
这个是给我的是一个  数组包裹的对象形式 进行递归
这个我写在--一个通用的 js 导出形式  这边我需要是的是两种类型 所以分了一下(效果图上的是第一个)  
解释一下:data:后端给你的数据
                  parentId是 从0 或者 -1 开始 看你的需求 我这边是 -1

                   show:我这是区分我要的是那种类型         

递归形式走的

export function buildTree(data, parentId, show) {
  const result = [];
  data?.filter(item => item.parentId === parentId)
    .forEach(items => {
      const children = buildTree(data, items.id, show);
      let node = ''
      if (!show) {
        node = {
          value: `${items.categoryName}`,
          key: `${items.id}`,
          defaultValue: `${items.categoryName}`,
          isEditable: false,
          parentId: `${items.parentId}`,
          children: children.length ? children : undefined,
        };
      } else {
        node = {
          value: `${items.id}`,
          title: `${items.categoryName}`,
          children: children.length ? children : undefined,
        };
      }

      result.push(node);
    });
  return result;
}

 这边都是实现的代码了-------可以详细看下  ---谢谢  -如果不懂-请评论


import { DownOutlined, EditOutlined, PlusOutlined, MinusOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { Input, Tree, message } from 'antd';
import React, { useEffect, useState } from 'react';
import moment from 'moment';
import { GetclubGoodsCategoryList, PostclubGoodsCategoryRemove, PostclubGoodsCategorySave, PostclubGoodsCategoryUpdate } from '@/services/commodity';
import { buildTree } from '@/utils/utils';

const { TreeNode } = Tree;
// 三张图
import reduce from '@/assets/icon/reduce.png';
import addIng from '@/assets/icon/addIng.png';
import editIng from '@/assets/icon/editIng.png';
function TreeData({ onNodeClick }) {

    // 佛祖保佑,不会报错

    const [selectedNode, setSelectedNode] = useState(null);

    // const treeData = buildTree(data1, 0);
    const expandedKeyArr = ["0"];

    const [data, setData] = useState();
    const [listObj, setListObj] = useState({});
    let isMounted = true;
    const getDate = async () => {
        try {
//获取最初的数据  ------------接口
            let res = await GetclubGoodsCategoryList();
            if (isMounted && res && res.code == "200") {
                const treeData = buildTree(res.data, 0);
                setData(treeData);
            }
        } catch (error) {
            console.error("Error fetching data: ", error);
        }
    };
    useEffect(() => {

        getDate();
        return () => {
            isMounted = false;
        };
    }, []);


    const [expandedKeys, setExpandedKeys] = useState(expandedKeyArr);

    // 修改---树结构 有值---接口
    const onChangeUpdate = async () => {
        if (Object.keys(listObj).length > 0) {
            let res = await PostclubGoodsCategoryUpdate({
                categoryName: listObj.value,
                // parentId: listObj.key,
                id: listObj.key,

            });
            if (res && res.code == "200") {
                message.success("修改成功");
//重新获取树形结构
                getDate();
            }
        }

    }

    const onExpand = (expandedKeys) => {
        setExpandedKeys(expandedKeys);
    };
    const handleNodeClick = (node) => {
        setSelectedNode(node);

        // 进行暴露---作为一个组件调用你所点击的值对象
        onNodeClick(node);
    };

    const renderTreeNodes = (data, depth = 0) => {
        return data?.map((item) => {
            const title = item.isEditable ? (
                <div style={{
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center',
                }}>

                        {/*  输入框显示 和   一个对号  一个 叉号

*/}

                    <Input placeholder="输入名称" defaultValue={item.defaultValue}
                        onChange={(e) => onChange(e, item)} />

                    <CheckOutlined style={{
                        margin: '0 4px',
                    }} onClick={() => onSave(item)} />
                    <CloseOutlined onClick={() => onClose(item.parentKey, item.defaultValue)} />
                </div>
            ) : (
                <div onClick={() => handleNodeClick(item)}
                    style={{
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center',
                    }}
                >
                    <div
                        style={{
                            whiteSpace: 'nowrap'
                        }}
                    >{item.value}</div>
                    <div
                        style={{
                            display: 'flex',
                            flexWrap: 'nowrap',
                            alignItems: 'center',
                        }}
                    >
                        {/* <EditOutlined onClick={() => onEdit(item.key)} />
                        <PlusOutlined onClick={() => onAdd(item.key)} />
                        {item.parentKey !== "0" && (
                            <MinusOutlined onClick={() => onDelete(item.key)} />
                        )} */}

                        {/* 两层之上就没有图标l 
                        效果上的左边字 右边图片 

*/}
                        {
                            item.value &&
                            <>
                                <img style={{ margin: '0 4px' }} width='15px' onClick={() => onEdit(item.key)} height='15px' src={editIng} alt="" />
                                {
                                    depth < 2 && <img width='15px' onClick={() => onAdd(item.key)} height='15px' src={addIng} alt="" />
                                }

                                <img style={{ margin: '0 4px' }} onClick={() => onDelete(item.key)} width='15px' height='15px' src={reduce} alt="" />
                            </>

                        }
                    </div>
                </div>
            );

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

            return <TreeNode title={title} key={item.key} />;
        });
    };

    const onAdd = (key) => {
        if (expandedKeys.indexOf(key) === -1) {
            expandedKeys.push(key);
        }
        setExpandedKeys(expandedKeys.slice());
        addNode(key, data);
        setData([...data]);
    };

    const onEdit = (key) => {
        editNode(key, data);
        setData([...data]);
    };

    const editNode = (key, data) =>
        data.forEach((item) => {
            if (item.key === key) {
                item.isEditable = true;
            } else {
                item.isEditable = false;
            }
            item.value = item.defaultValue;
            if (item.children) {
                editNode(key, item.children);
            }
        });

    const getRandomNumber = (min = 1, max = 10000) => {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    };

    const addNode = (key, data) =>
        data.forEach((item) => {
            if (item.key === key) {
                if (item.children) {
                    item.children.push({
                        value: "default",
                        key: getRandomNumber(),
                        parentKey: key,
                        isEditable: false,
                        showAdd: true
                    });
                } else {
                    item.children = [
                        {
                            value: "default",
                            key: getRandomNumber(),
                            parentKey: key,
                            isEditable: false,
                            showAdd: true
                        },
                    ];
                }
                return;
            }
            if (item.children) {
                addNode(key, item.children);
            }
        });

    const onChange = (e, key) => {
        changeNode(key.parentKey ? key : key, e.target.value, data);
        setData([...data]);

    };

    const changeNode = (key, value, data) =>

        data.forEach((item) => {

            if (item.parentKey || item.key == key.key) {

                item.value = value;
            }
            if (item.children) {
                changeNode(key, value, item.children);
            }
        });

    // 保存---数据新增---接口
    const onSaveObj = async () => {

        let res = await PostclubGoodsCategorySave({
            categoryName: listObj.value,
            parentId: listObj.parentKey,
        })
        if (res && res.code === 200) {
            message.success('新增成功')
            getDate();
        } else {
            message.error('新增失败')
        }
    };

    useEffect(() => {

        if (listObj.showAdd) {

            onSaveObj();
        } else {
            onChangeUpdate()
        }


    }, [listObj])
    const onSave = (key) => {

        if (key.value == undefined) {
            message.error('请输入分类名称')
            return
        }
        setListObj(key)
        saveNode(key, data);
        setData([...data]);

    };


    const saveNode = (key, data) =>
        data.forEach((item) => {
            if (item.parentKey === key) {
                item.defaultValue = item.value;
            }
            if (item.children) {
                saveNode(key, item.children);
            }
            item.isEditable = false;
        });

    const onClose = (key, defaultValue) => {
        closeNode(key, defaultValue, data);
        setData([...data]);
    };

    const closeNode = (key, defaultValue, data) =>
        data.forEach((item, index) => {
            item.isEditable = false;
            if (item.parentKey === key) {
                data.splice(index, 1);
                item.value = defaultValue;
            }
            if (item.children) {
                closeNode(key, defaultValue, item.children);
            }
        });

    const onDelete = (key) => {
        deleteNode(key, data);
        setData([...data]);
    };
    //删除节点---接口
    const deleteIds = async (key) => {
        let data = await PostclubGoodsCategoryRemove({
            ids: key,
        })
        if (data && data.code === 200) {
            message.success('删除成功')
            getDate();
        }
    }
    const deleteNode = (key, data) =>
        data.forEach((item, index) => {
            if (item.key === key) {
                if (!item.showAdd) {
                    deleteIds(key);
                }


                data.splice(index, 1);

                return;
            } else {
                if (item.children) {
                    deleteNode(key, item.children);
                }
            }
        });

    return (
        <div style={{
            position: 'relative',
            left: '-14px'
        }}>
            <Tree onExpand={onExpand} expandedKeys={expandedKeys}>
                {renderTreeNodes(data)}
            </Tree>

        </div>
    );
}

export default TreeData;


第二个版本:更新  进行右击出菜单进行新增-修改-删除

直接上代码---效果


import { DownOutlined, EditOutlined, PlusOutlined, MinusOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons';
import { Dropdown, Input, Tooltip, Tree, message, } from 'antd';
import React, { useEffect, useState } from 'react';
import moment from 'moment';
import { GetclubGoodsCategoryList, PostclubGoodsCategoryRemove, PostclubGoodsCategorySave, PostclubGoodsCategoryUpdate } from '@/services/commodity';
import { buildTree } from '@/utils/utils';
import styles from './index.less';
const { TreeNode, DirectoryTree } = Tree;
import { InfoCircleOutlined, UserOutlined } from '@ant-design/icons';
//部分引入根据需要引入  
// import reduce from '@/assets/icon/reduce.png';
// import addIng from '@/assets/icon/addIng.png';
// import editIng from '@/assets/icon/editIng.png';
function TreeData({ onNodeClick, moren }) {

    // 佛祖保佑,不会报错

    const [selectedNode, setSelectedNode] = useState(null);

    // const treeData = buildTree(data1, 0);
    const expandedKeyArr = ["0"];

    const [data, setData] = useState();
    const [listArr, setListArr] = useState({});
    const [listObj, setListObj] = useState({});
    let isMounted = true;
    const getDate = async () => {
        try {

            setData(null);
            let res = await GetclubGoodsCategoryList();
            if (isMounted && res && res.code == "200") {
                const treeData = buildTree(res.data, 0);
                setData(treeData);
                setSelectedNode(null); // Reset selected node
                setExpandedKeys(["0"]); // Reset expanded keys
            }
        } catch (error) {
            console.error("Error fetching data: ", error);
        }
    };
    const [expandedKeys, setExpandedKeys] = useState(expandedKeyArr);
    useEffect(() => {
        console.log('moren', moren)
        getDate();

        return () => {
            isMounted = false;
        };
    }, [moren]);




    // 修改---树结构 有值
    const onChangeUpdate = async () => {
        if (Object.keys(listObj).length > 0) {
            let res = await PostclubGoodsCategoryUpdate({
                categoryName: listObj.value,
                // parentId: listObj.key,
                id: listObj.key,

            });
            if (res && res.code == "200") {
                message.success("修改成功");
                getDate();
            }
        }

    }

    const onExpand = (expandedKeys) => {
        console.log('setExpandedKeys', expandedKeys);
        setExpandedKeys(expandedKeys);
    };
    const handleNodeClick = (node) => {
        setSelectedNode(node);

        // 进行暴露
        onNodeClick(node);
    };
    // const esLing = (node) => {
    //     // 判断字符串长度并添加省略号  
    //     return node.length > 6 ? node.slice(0, 4) + '...' : node;
    // }
    const esLing = (str, maxLength = 8) => {
        let charCount = 0;
        let truncatedStr = '';
        if (str) {
            for (let char of str) {
                charCount += char.match(/[^\x00-\xff]/) ? 2 : 1;
                if (charCount > maxLength * 2) {
                    truncatedStr += '...';
                    break;
                }
                truncatedStr += char;
            }


        }
        return truncatedStr;

    }

    const renderTreeNodes = (data, depth = 0) => {
        return data?.map((item) => {
            const title = item.isEditable ? (
                <div style={{
                    width: '130px',
                    display: 'flex',
                    // justifyContent: 'space-between',
                    alignItems: 'center',
                }}>
                    {/* <Input placeholder="输入名称" defaultValue={item.defaultValue}
                        onChange={(e) => onChange(e, item)} />

                    <CheckOutlined style={{
                        margin: '0 4px',
                    }} onClick={() => onSave(item)} />
                    <CloseOutlined onClick={() => onClose(item.parentKey, item.defaultValue)} /> */}


                    <Input
                        placeholder="输入名称" defaultValue={item.defaultValue}
                        onChange={(e) => onChange(e, item)}

                        suffix={
                            <>
                                <CheckOutlined style={{
                                    margin: '0 4px',
                                }} onClick={() => onSave(item)} />

                                <CloseOutlined onClick={() => onClose(item.parentKey != undefined ? item.parentKey : item.key, item.defaultValue || item.value, item)} />
                            </>
                        }
                    />
                </div>
            ) : (
                <div onClick={() => handleNodeClick(item)}
                    style={{
                        display: 'flex',
                        // justifyContent: 'space-between',
                        alignItems: 'center',
                        textIndent: '-11px'
                    }}
                >
                    <div
                        title={item.value}
                        style={{
                            whiteSpace: 'nowrap',
                            textIndent: '-3px'
                        }}
                    >{esLing(item.value)}</div>
                    <div
                        style={{
                            display: 'flex',
                            flexWrap: 'nowrap',
                            alignItems: 'center',
                        }}
                    >
                        {/* <EditOutlined onClick={() => onEdit(item.key)} />
                        <PlusOutlined onClick={() => onAdd(item.key)} />
                        {item.parentKey !== "0" && (
                            <MinusOutlined onClick={() => onDelete(item.key)} />
                        )} */}

                        {/* 两层之上就没有图标l */}
                        {
                            item.value &&
                            <>
                                {/* <img style={{ margin: '0 4px' }} width='15px' onClick={() => onEdit(item.key)} height='15px' src={editIng} alt="" /> */}
                                {/* {
                                    depth < 2 && <img width='15px' onClick={() => onAdd(item.key)} height='15px' src={addIng} alt="" />
                                }

                                <img style={{ margin: '0 4px' }} onClick={() => onDelete(item.key)} width='15px' height='15px' src={reduce} alt="" /> */}
                            </>

                        }
                    </div>
                </div>
            );

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

            return <TreeNode blockNode title={title} key={item.key} />;
        });
    };
    const di = (data, newValue) => {
        for (let i = 0; i < data.length; i++) {
            if (data[i].value == newValue) {
                return message.error('不能重复添加');
            }
            if (data[i].children) {
                const result = di(data[i].children, newValue);
                if (result) {
                    return result;
                }
            }
        }
        return null;
    };

    const onAdd = (key) => {
        // if (data) {
        //     console.log('http://localhost:8889/', data)
        //     for (var i = 0; i < data.length; i++) {
        //         if (data[i].value == '新值') {

        //             return message.error('不能重复添加');
        //         }
        //     };
        // }
        let d = di(data, '新值')
        console.log('d', d)
        if (d) return

        if (expandedKeys.indexOf(key) == -1) {
            expandedKeys.push(key);
        }
        setExpandedKeys(expandedKeys.slice());
        addNode(key, data);
        setData([...data]);
    };

    const onEdit = (key) => {
        editNode(key, data);
        setData([...data]);
    };

    const editNode = (key, data) =>
        data.forEach((item) => {

            if (item.key == key) {
                item.isEditable = true;
            } else {
                item.isEditable = false;
            }
            item.value = item.defaultValue;
            if (item.children) {
                editNode(key, item.children);
            }
        });

    const getRandomNumber = (min = 1, max = 10000) => {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    };
    const onAddRoot = () => {
        if (data) {
            for (var i = 0; i < data.length; i++) {
                if (data[i].value == '新值') {
                    return message.error('不能重复添加');
                }
            };
        }
        const newNode = {
            value: '新值',
            key: getRandomNumber(),
            parentKey: null,
            isEditable: true,
            showAdd: true,
        };

        setData([...data, newNode]);
        setExpandedKeys([...expandedKeys, newNode.key.toString()]);
    };
    const addNode = (key, data) =>

        data.forEach((item) => {
            if (item.key == key) {
                if (item.children) {
                    item.children.push({
                        value: "新值",
                        key: getRandomNumber(),
                        parentKey: key,
                        isEditable: true,
                        showAdd: true
                    });
                } else {
                    item.children = [
                        {
                            value: "新值",
                            key: getRandomNumber(),
                            parentKey: key,
                            isEditable: true,
                            showAdd: true
                        },
                    ];
                }
                return;
            }
            if (item.children) {
                addNode(key, item.children);
            }

        });

    const onChange = (e, key) => {
        changeNode(key.parentKey ? key : key, e.target.value, data);
        setData([...data]);

    };

    const changeNode = (key, value, data) =>

        data.forEach((item) => {

            if (item.parentKey || item.key == key.key) {

                item.value = value;
            }
            if (item.children) {
                changeNode(key, value, item.children);
            }
        });

    // 保存---数据新增
    const onSaveObj = async () => {

        let res = await PostclubGoodsCategorySave({
            categoryName: listObj.value,
            parentId: listObj.parentKey ? listObj.parentKey : 0,
        })
        if (res && res.code === 200) {
            message.success('新增成功')
            getDate();
        } else {
            message.error('新增失败')
        }

        setListArr({})
    };

    useEffect(() => {

        if (listObj.showAdd) {

            onSaveObj();
        } else {
            onChangeUpdate()
        }


    }, [listObj])
    const onSave = (key) => {

        if (key.value == undefined) {
            message.error('请输入分类名称')
            return
        }
        setListObj(key)
        saveNode(key, data);
        setData([...data]);

    };


    const saveNode = (key, data) =>
        data.forEach((item) => {
            if (item.parentKey == key) {
                item.defaultValue = item.value;
            }
            if (item.children) {
                saveNode(key, item.children);
            }
            item.isEditable = false;
        });

    // 删除最后一个
    const removeLastObject = (arr) => {
        if (Array.isArray(arr) && arr.length > 0) {
            arr.pop();
        }
        return arr;
    }

    const onClose = (key, defaultValue, item) => {
        console.log('item', item)
        console.log('defaultValue12', defaultValue)
        console.log('key', key)
        if (data.length > 0 && key == undefined) {
            let res = JSON.parse(JSON.stringify(data));
            let a = removeLastObject(res)
            console.log('data', a)
            setData([...a]);
            return
        }
        closeNode(key, defaultValue, data);
        setData([...data]);
    };

    const closeNode = (key, defaultValue, data) => {
        console.log('defaultValuecloseNode', defaultValue)
        if (defaultValue == undefined || defaultValue == '') {
            return message.error('请输入分类名称')
        }
        data.forEach((item, index) => {

            if (item.parentKey != undefined ? item.parentKey : item.key == key) {
                item.isEditable = false;
                // if (defaultValue == undefined) {
                //     data.splice(index, 1);
                //     return
                // }

                // item.value = defaultValue;
                console.log('item12', item)
                console.log('keykey', key)
            }
            if (item.children) {
                closeNode(key, defaultValue, item.children);
            }
            setListArr({})
        });
    }


    const onDelete = (key) => {

        deleteNode(key, data);
        setData([...data]);
    };
    //删除节点
    const deleteIds = async (key) => {
        let data = await PostclubGoodsCategoryRemove({
            ids: key,
        })
        if (data && data.code == 200) {
            message.success('删除成功')
            getDate();
        }
        setListArr({})
    }
    const deleteNode = (key, data) => {
        data.forEach((item, index) => {
            if (item.key == key) {
                if (!item.showAdd) {
                    deleteIds(key);
                }


                data.splice(index, 1);

                return;
            } else {
                if (item.children) {
                    deleteNode(key, item.children);
                }
            }
        });

    }



    //    右键点击事件
    const onRightClickAll = ({ node }) => {
        console.log('node', node)
        setListArr(node)
    }
    const delShow = (d) => {
        console.log('ddd', d)
        if (Object.keys(d).length > 0) {
            onAdd(d.key)
        } else {
            onAddRoot()
        }
    }
    const items = [
        {
            label: (
                <div style={{
                    textAlign: 'center',
                }} onClick={() => delShow(listArr)}
                >
                    新增
                </div>
            ),
            disabled: listArr?.pos?.split('-').length == 4,
            key: '0',
        },
        {
            label: (
                <div style={{
                    textAlign: 'center',
                }} onClick={() => onEdit(listArr.key)}
                >
                    修改
                </div>
            ),
            key: '1',
            disabled: Object.keys(listArr).length == 0,
        },

        {
            label: (
                <div style={{
                    textAlign: 'center',
                }} onClick={() => onDelete(listArr.key)}>
                    删除
                </div>
            ),
            key: '2',
            disabled: Object.keys(listArr).length == 0,

        },
    ];
    return (



        <Dropdown
            overlayStyle={{

                background: '#f7f7f7',
                width: '83px',
                borderRadius: '2px 2px 2px 2px',
                border: '1px solid #E1E1E1',
                boxShadow: 'rgba(0, 0, 0, 0.05) 0px 2px 20px 0px'
            }}
            style={{
                width: '83px',
                borderRadius: '2px 2px 2px 2px',
                border: '1px solid #E1E1E1',
                background: '#f7f7f7',
                boxShadow: 'rgba(0, 0, 0, 0.05) 0px 2px 20px 0px'
            }}
            menu={{
                items,
            }}
            trigger={['contextMenu']}
        >
            <div
                className={styles.consty}
                style={{
                    position: 'relative',
                    // left: '-14px',
                    background: '#ffffff',
                    overflow: 'auto'
                }}>
                {
                    data &&
                    <Tree
                        // defaultExpandedKeys={data?.map(node => node.key)}
                        // autoExpandParent={true}
                        // defaultExpandAll={true}
                        // defaultExpandParent={true}
                        style={{
                            height: '82vh',
                            width: '130px'
                        }} onRightClick={onRightClickAll} blockNode onExpand={onExpand} expandedKeys={expandedKeys}>
                        {renderTreeNodes(data)}
                    </Tree>
                }


            </div>

            {/* <div

                style={{
                    textAlign: 'center',
                    height: 200,
                    lineHeight: '200px',
                    color: '#777',
                    background: '#f7f7f7'
                }}
            >
                Right Click on here
            </div> */}
        </Dropdown>




    );
}

export default TreeData;

 

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_2524963996

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值