先看一下效果:整体的效果
新增效果 --默认值是 default
修改效果 -
大致效果如上
---------------------------------------------------------------------------------------------------------------------------------
下面讲解代码如何实现的
根据你使用的UI的框架中的树结构---形成相应的数据结构(递归形式)-如果后端给你分好了(感谢他)
这个是给我的是一个 数组包裹的对象形式 进行递归
这个我写在--一个通用的 js 导出形式 这边我需要是的是两种类型 所以分了一下(效果图上的是第一个)
解释一下:data:后端给你的数据
parentId是 从0 或者 -1 开始 看你的需求 我这边是 -1show:我这是区分我要的是那种类型
递归形式走的
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;