/** * 树操作通用方法,将一些常用方法提炼出来,方便使用。 * @module 树操作工具 */ import {cloneDeep} from 'lodash'; import {uniqueArray, arrayRemoveAll, arrayRemove} from './index'; /** * 将数据转换成tree所需格式 * @param {object} data 要进行转换的object * @param {String} [keyField='id'] 指定data中某个字段转换为树所需的key * @param {String} [titleField='name'] 指定data中某个字段转换为树所需的name * @returns {{name: *, key: *}} */ export function generateTreeNode(data, keyField = 'id', titleField = 'name') { return {...data, title: data[titleField], key: data[keyField]}; } /** * 将数据转换成tree所需格式 * @param {Array} data 要进行转换的一些数据 * @param {String} [keyField='id'] 指定data中某个字段转换为树所需的key * @param {String} [titleField='name'] 指定data中某个字段转换为树所需的name * @returns {Array} */ export function generateTreeNodes(data, keyField = 'id', titleField = 'name') { let arr = []; if (data && data.length) { arr = data.map(d => generateTreeNode(d, keyField, titleField)); } return arr; } /** * 根据key 将node设置成叶子节点 * @param {Array} treeData 树的树状结构数据 * @param {String} key 节点的key值 */ export function setLeaf(treeData, key) { const loopLeaf = (data) => { for (let item of data) { if (item.key === key) { item.isLeaf = true; break; } if (item.children && item.children.length) { loopLeaf(item.children); } } }; loopLeaf(treeData); } /** * 给指定key的节点添加子节点 * @param {Array} treeData 树的树状结构数据 * @param {String} key 节点的key值 * @param {Array} child 要添加的子节点 */ export function appendChildrenByKey(treeData, key, child) { const loop = (data) => { for (let item of data) { if (key === item.key) { if (item.children) { item.children = item.children.concat(child); } else { item.children = child; } if (!item.children || !item.children.length) { setLeaf(treeData, key); } break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); } /** * 检测某个节点是否有parent节点 * @param {Array} rows 所有节点,扁平数据,非树状结构 * @param {object} row 需要判断得节点 * @returns {boolean} */ export function hasParent(rows, row) { let parentKey = row.parentKey; return rows.find(r => r.key === parentKey); } /** * 根据key,查询其所有后代节点,一般会用于删除 * @param {Array} rows 具有key,parentKey关系的扁平数据结构 * @param {object} key 要查询的节点 key * @returns {Array} */ export function getGenerationsByKey(rows, key) { // 这个函数会被多次调用,对rows做深拷贝,否则会产生副作用。 rows = cloneDeep(rows); const parentNode = rows.find(item => item.key === key); if (!parentNode) return []; let nodes = [parentNode]; let generationNodes = [cloneDeep(parentNode)]; // 存放要处理的节点 let toDo = nodes.map((v) => v); while (toDo.length) { // 处理一个,头部弹出一个。 let node = toDo.shift(); // 获取子节点。 rows.forEach(row => { if (row.parentKey === node.key) { let child = cloneDeep(row); generationNodes.push(child); // child加入toDo,继续处理 toDo.push(child); } }); } return generationNodes; } /** * js构造树方法。会给节点添加parentKeys,parentNodes,parentTexts属性,方便后期数据提取 * @param {Array} rows 具有key,parentKey关系的扁平数据结构,标题字段为text * @param {object} [parentNode=null] 开始节点 * @returns {Array} */ export function convertToTree(rows, parentNode = null) { // 这个函数会被多次调用,对rows做深拷贝,否则会产生副作用。 rows = cloneDeep(rows); parentNode = cloneDeep(parentNode); let nodes = []; if (parentNode) { nodes.push(parentNode); } else { // 获取所有的顶级节点 nodes = rows.filter(r => !hasParent(rows, r)); } // 存放要处理的节点 let toDo = nodes.map((v) => v); while (toDo.length) { // 处理一个,头部弹出一个。 let node = toDo.shift(); // 获取子节点。 rows.forEach(row => { if (row.parentKey === node.key) { let child = cloneDeep(row); let parentKeys = [node.key]; if (node.parentKeys) { parentKeys = node.parentKeys.concat(node.key); } child.parentKeys = parentKeys; let parentTexts = [node.text]; if (node.parentTexts) { parentTexts = node.parentTexts.concat(node.text); } child.parentTexts = parentTexts; const tempNode = cloneDeep(node); delete tempNode.children; delete tempNode.parentKeys; delete tempNode.parentNodes; delete tempNode.parentTexts; let parentNodes = [tempNode]; if (node.parentNodes) { parentNodes = node.parentNodes.concat(parentNodes); } child.parentNodes = parentNodes; if (node.children) { node.children.push(child); } else { node.children = [child]; } // child加入toDo,继续处理 toDo.push(child); } }); } if (parentNode) { return nodes[0].children; } return nodes; } /** * 根据指定数据的键值对,查找node,比如根据path查找: getNodeByPropertyAndValue(treeData, 'path', '/user/list') * @param {Array} treeData 树状结构数据 * @param {String} key key值,比如'path','text'等节点数据属性 * @param {*} value 节点属性所对应的数据 * @param {Function} [compare] 节点属性所对应的数据比较方式, 默认 === 比对 * @returns {object} 返回根据 key value查找到的节点 */ export function getNodeByPropertyAndValue(treeData, key, value, compare) { if (!treeData || !treeData.length) return null; if (!compare) compare = (a, b, item) => a === b; let node = null; const loop = (data) => { for (let item of data) { if (compare(item[key], value, item)) { node = {...item}; break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); return node; } /** * 根据key查找节点 * @param {Array} treeData 树状结构数据 * @param {String} key * @returns {object} 根据key查找到的节点 */ export function getNodeByKey(treeData, key) { return getNodeByPropertyAndValue(treeData, 'key', key); } /** * 根据key查找后代元素的key * @param {Array} treeData 树状结构数据 * @param {String} key * @returns {*[]} 根据key查找到的所有后代节点key */ export function getGenerationKeys(treeData, key) { const node = getNodeByKey(treeData, key); const keys = []; const loop = (node) => { const {key, children} = node; if (children?.length) { children.forEach(loop); } else { keys.push(key); } }; loop(node); return keys.filter(item => item !== key); } /** * 根据key查找所有后代元素 * @param {Array} treeData 树状结构数据 * @param {String} key * @returns {Array} 根据key查找到的所有后代节点 */ export function getGenerationalNodesByKey(treeData, key) { const node = getNodeByKey(treeData, key); if (!node.children || !node.children.length) { return []; } const allNodes = []; const loop = (data) => { data.forEach(d => { allNodes.push(d); if (d.children && d.children) { loop(d.children); } }); }; loop(node.children); return allNodes; } /** * 获取选中节点的keys,点击父节点时,其下所有后代元素将被全被选中,或者全不选中,选中子节点时,其所有祖先节点将被选中 * @param treeData 树状结构数据 * @param {Array} checkedKeys 点击过之后,树选中的keys * @param {boolean} checked 当前点击时 checked (true)还是 unchecked(false) * @param {String} checkNodeKey 当前点击节点的key * @returns {Array} 选中的keys */ export function getCheckedKeys(treeData, checkedKeys, checked, checkNodeKey) { // TODO 区分半选和全选 let allKeys = [...checkedKeys]; const generationalNodes = getGenerationalNodesByKey(treeData, checkNodeKey); const generationalKeys = generationalNodes.map(n => n.key); if (checked) { // 选中所有后代节点 allKeys = allKeys.concat(generationalKeys); // 选中有祖先节点 const node = getNodeByKey(treeData, checkNodeKey); if (node.parentKeys) { allKeys = allKeys.concat(node.parentKeys); } } else { // 取消选中所有后代节点 allKeys = arrayRemoveAll(allKeys, generationalKeys.concat(checkNodeKey)); // 判断其父节点是否还有子节点选中了,如果没有,父节点也不选中 const node = getNodeByKey(treeData, checkNodeKey); if (node.parentKeys) { const pks = [...node.parentKeys]; pks.reverse(); pks.forEach(key => { const pNode = getNodeByKey(treeData, key); if (pNode.children && pNode.children.length) { let hasCheckedChild = false; for (let pCNode of pNode.children) { if (allKeys.indexOf(pCNode.key) > -1) { hasCheckedChild = true; break; } } if (!hasCheckedChild) { allKeys = arrayRemove(allKeys, key); } } }); } } return uniqueArray(allKeys); } /** * 根据key删除节点 * @param {Array} treeData 树的树状结构数据 * @param {String} key 要删除节点的key值 */ export function removeNodeByKey(treeData, key) { if (!treeData || !treeData.length) return null; const loop = (data) => { for (let i = 0; i < data.length; i++) { const item = data[i]; if (item.key === key) { data.splice(i, 1); break; } else if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); } /** * 给指定key的node节点增加一个新的子节点 * @param {Array} treeData 树的树状结构数据 * @param {String} key 要操作的节点的key值 * @param {object} newNode 需要加入的子节点 */ export function addNodeChildByKey(treeData, key, newNode) { if (!treeData || !treeData.length) return null; newNode.isLeaf = true; const loop = (data) => { for (let item of data) { if (item.key === key) { if (item.children) { item.children.push({...newNode}); } else { item.children = [{...newNode}]; } break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); } /** * 更新某个节点 * @param {Array} treeData 树的树状结构数据 * @param {object} newNode 需要跟新的节点新数据,会根据key对原数据进行比对 */ export function updateNode(treeData, newNode) { if (!treeData || !treeData.length) return null; const loop = (data) => { for (let item of data) { if (item.key === newNode.key) { Object.keys(item).forEach(key => { item[key] = newNode[key]; }); break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); } /** * 根据某个节点,获取其最顶级节点 * @param {Array} treeData 树状结构数据 * @param {object} node 节点数据 * @returns {object} 最顶层节点 */ export function getTopNodeByNode(treeData, node) { if (!treeData || !treeData.length || !node) return null; if (node && !node.parentKey) return node; let parentNode = null; const loop = (data) => { // 查找node的父节点 for (let item of data) { if (item.key === node.parentKey) { parentNode = {...item}; break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); return getTopNodeByNode(treeData, parentNode); // 继续查找parentNode的父节点 } /** * 渲染树,cb(node[, children nodes]) * @param {Array} treeData 树的树状结构数据 * @param {function} cb 回调函数:cb(node[, children nodes]) */ export function renderNode(treeData, cb) { const loop = data => data.map((item) => { if (item.children) { return cb(item, loop(item.children)); // item children Item } return cb(item); // 叶子节点 }); return loop(treeData); } /** * 查找给定节点,及其后代节点property属性,第一个不为空的值 * @param {Array} treeData 树的树状结构数据 * @param {object} node 节点数据 * @param {String} property 属性,比如 key, path等 * @returns {*} */ export function getFirstValue(treeData, node, property) { if (node[property]) return node[property]; let firstValue = null; const loop = data => { for (let item of data) { if (item[property]) { firstValue = item[property]; break; } if (item.children && item.children.length) { loop(item.children); } } }; if (node.children && node.children.length) { loop(node.children); } return firstValue; }
/** * 树操作通用方法,将一些常用方法提炼出来,方便使用。 * @module树操作工具 */
import {cloneDeep} from 'lodash'; import {uniqueArray, arrayRemoveAll, arrayRemove} from './index';
/** * 将数据转换成tree所需格式 * @param{object}data 要进行转换的object * @param{String}[keyField='id'] 指定data中某个字段转换为树所需的key * @param{String}[titleField='name'] 指定data中某个字段转换为树所需的name * @returns{{name: *, key: *}} */ export function generateTreeNode(data, keyField = 'id', titleField = 'name') { return {...data, title: data[titleField], key: data[keyField]}; }
/** * 将数据转换成tree所需格式 * @param{Array}data 要进行转换的一些数据 * @param{String}[keyField='id'] 指定data中某个字段转换为树所需的key * @param{String}[titleField='name'] 指定data中某个字段转换为树所需的name * @returns{Array} */ export function generateTreeNodes(data, keyField = 'id', titleField = 'name') { let arr = []; if (data && data.length) { arr = data.map(d => generateTreeNode(d, keyField, titleField)); } return arr; }
/** * 根据key 将node设置成叶子节点 * @param{Array}treeData 树的树状结构数据 * @param{String}key 节点的key值 */ export function setLeaf(treeData, key) { const loopLeaf = (data) => { for (let item of data) { if (item.key === key) { item.isLeaf = true; break; } if (item.children && item.children.length) { loopLeaf(item.children); } } }; loopLeaf(treeData); }
/** * 给指定key的节点添加子节点 * @param{Array}treeData 树的树状结构数据 * @param{String}key 节点的key值 * @param{Array}child 要添加的子节点 */ export function appendChildrenByKey(treeData, key, child) { const loop = (data) => { for (let item of data) { if (key === item.key) { if (item.children) { item.children = item.children.concat(child); } else { item.children = child; }
if (!item.children || !item.children.length) { setLeaf(treeData, key); } break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); }
/** * 检测某个节点是否有parent节点 * @param{Array}rows 所有节点,扁平数据,非树状结构 * @param{object}row 需要判断得节点 * @returns{boolean} */ export function hasParent(rows, row) { let parentKey = row.parentKey; return rows.find(r => r.key === parentKey); }
/** * 根据key,查询其所有后代节点,一般会用于删除 * @param{Array}rows 具有key,parentKey关系的扁平数据结构 * @param{object}key 要查询的节点 key * @returns{Array} */ export function getGenerationsByKey(rows, key) { // 这个函数会被多次调用,对rows做深拷贝,否则会产生副作用。 rows = cloneDeep(rows); const parentNode = rows.find(item => item.key === key); if (!parentNode) return [];
let nodes = [parentNode]; let generationNodes = [cloneDeep(parentNode)];
// 存放要处理的节点 let toDo = nodes.map((v) => v);
while (toDo.length) { // 处理一个,头部弹出一个。 let node = toDo.shift(); // 获取子节点。 rows.forEach(row => { if (row.parentKey === node.key) { let child = cloneDeep(row); generationNodes.push(child); // child加入toDo,继续处理 toDo.push(child); } }); } return generationNodes; }
/** * js构造树方法。会给节点添加parentKeys,parentNodes,parentTexts属性,方便后期数据提取 * @param{Array}rows 具有key,parentKey关系的扁平数据结构,标题字段为text * @param{object}[parentNode=null] 开始节点 * @returns{Array} */ export function convertToTree(rows, parentNode = null) { // 这个函数会被多次调用,对rows做深拷贝,否则会产生副作用。 rows = cloneDeep(rows); parentNode = cloneDeep(parentNode);
let nodes = []; if (parentNode) { nodes.push(parentNode); } else { // 获取所有的顶级节点 nodes = rows.filter(r => !hasParent(rows, r)); }
// 存放要处理的节点 let toDo = nodes.map((v) => v);
while (toDo.length) { // 处理一个,头部弹出一个。 let node = toDo.shift(); // 获取子节点。 rows.forEach(row => { if (row.parentKey === node.key) { let child = cloneDeep(row); let parentKeys = [node.key]; if (node.parentKeys) { parentKeys = node.parentKeys.concat(node.key); } child.parentKeys = parentKeys;
let parentTexts = [node.text]; if (node.parentTexts) { parentTexts = node.parentTexts.concat(node.text); } child.parentTexts = parentTexts;
const tempNode = cloneDeep(node); delete tempNode.children; delete tempNode.parentKeys; delete tempNode.parentNodes; delete tempNode.parentTexts; let parentNodes = [tempNode]; if (node.parentNodes) { parentNodes = node.parentNodes.concat(parentNodes); } child.parentNodes = parentNodes;
if (node.children) { node.children.push(child); } else { node.children = [child]; } // child加入toDo,继续处理 toDo.push(child); } }); }
if (parentNode) { return nodes[0].children; } return nodes; }
/** * 根据指定数据的键值对,查找node,比如根据path查找: getNodeByPropertyAndValue(treeData, 'path', '/user/list') * @param{Array}treeData 树状结构数据 * @param{String}key key值,比如'path','text'等节点数据属性 * @param{*}value 节点属性所对应的数据 * @param{Function}[compare] 节点属性所对应的数据比较方式, 默认 === 比对 * @returns{object} 返回根据 key value查找到的节点 */ export function getNodeByPropertyAndValue(treeData, key, value, compare) { if (!treeData || !treeData.length) return null; if (!compare) compare = (a, b, item) => a === b; let node = null; const loop = (data) => { for (let item of data) { if (compare(item[key], value, item)) { node = {...item}; break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); return node; }
/** * 根据key查找节点 * @param{Array}treeData 树状结构数据 * @param{String}key * @returns{object} 根据key查找到的节点 */ export function getNodeByKey(treeData, key) { return getNodeByPropertyAndValue(treeData, 'key', key); }
/** * 根据key查找后代元素的key * @param{Array}treeData 树状结构数据 * @param{String}key * @returns{*[]} 根据key查找到的所有后代节点key */ export function getGenerationKeys(treeData, key) { const node = getNodeByKey(treeData, key); const keys = []; const loop = (node) => { const {key, children} = node; if (children?.length) { children.forEach(loop); } else { keys.push(key); } }; loop(node);
return keys.filter(item => item !== key); }
/** * 根据key查找所有后代元素 * @param{Array}treeData 树状结构数据 * @param{String}key * @returns{Array} 根据key查找到的所有后代节点 */ export function getGenerationalNodesByKey(treeData, key) { const node = getNodeByKey(treeData, key); if (!node.children || !node.children.length) { return []; } const allNodes = []; const loop = (data) => { data.forEach(d => { allNodes.push(d); if (d.children && d.children) { loop(d.children); } }); }; loop(node.children); return allNodes; }
/** * 获取选中节点的keys,点击父节点时,其下所有后代元素将被全被选中,或者全不选中,选中子节点时,其所有祖先节点将被选中 * @paramtreeData 树状结构数据 * @param{Array}checkedKeys 点击过之后,树选中的keys * @param{boolean}checked 当前点击时 checked (true)还是 unchecked(false) * @param{String}checkNodeKey 当前点击节点的key * @returns{Array} 选中的keys */ export function getCheckedKeys(treeData, checkedKeys, checked, checkNodeKey) { // TODO 区分半选和全选 let allKeys = [...checkedKeys]; const generationalNodes = getGenerationalNodesByKey(treeData, checkNodeKey); const generationalKeys = generationalNodes.map(n => n.key);
if (checked) { // 选中所有后代节点 allKeys = allKeys.concat(generationalKeys);
// 选中有祖先节点 const node = getNodeByKey(treeData, checkNodeKey); if (node.parentKeys) { allKeys = allKeys.concat(node.parentKeys); } } else { // 取消选中所有后代节点 allKeys = arrayRemoveAll(allKeys, generationalKeys.concat(checkNodeKey));
// 判断其父节点是否还有子节点选中了,如果没有,父节点也不选中 const node = getNodeByKey(treeData, checkNodeKey); if (node.parentKeys) { const pks = [...node.parentKeys]; pks.reverse(); pks.forEach(key => { const pNode = getNodeByKey(treeData, key); if (pNode.children && pNode.children.length) { let hasCheckedChild = false; for (let pCNode of pNode.children) { if (allKeys.indexOf(pCNode.key) > -1) { hasCheckedChild = true; break; } } if (!hasCheckedChild) { allKeys = arrayRemove(allKeys, key); } } }); } } return uniqueArray(allKeys); }
/** * 根据key删除节点 * @param{Array}treeData 树的树状结构数据 * @param{String}key 要删除节点的key值 */ export function removeNodeByKey(treeData, key) { if (!treeData || !treeData.length) return null; const loop = (data) => { for (let i = 0; i < data.length; i++) { const item = data[i]; if (item.key === key) { data.splice(i, 1); break; } else if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); }
/** * 给指定key的node节点增加一个新的子节点 * @param{Array}treeData 树的树状结构数据 * @param{String}key 要操作的节点的key值 * @param{object}newNode 需要加入的子节点 */ export function addNodeChildByKey(treeData, key, newNode) { if (!treeData || !treeData.length) return null; newNode.isLeaf = true; const loop = (data) => { for (let item of data) { if (item.key === key) { if (item.children) { item.children.push({...newNode}); } else { item.children = [{...newNode}]; } break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); }
/** * 更新某个节点 * @param{Array}treeData 树的树状结构数据 * @param{object}newNode 需要跟新的节点新数据,会根据key对原数据进行比对 */ export function updateNode(treeData, newNode) { if (!treeData || !treeData.length) return null; const loop = (data) => { for (let item of data) { if (item.key === newNode.key) { Object.keys(item).forEach(key => { item[key] = newNode[key]; }); break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); }
/** * 根据某个节点,获取其最顶级节点 * @param{Array}treeData 树状结构数据 * @param{object}node 节点数据 * @returns{object} 最顶层节点 */ export function getTopNodeByNode(treeData, node) { if (!treeData || !treeData.length || !node) return null;
if (node && !node.parentKey) return node; let parentNode = null; const loop = (data) => { // 查找node的父节点 for (let item of data) { if (item.key === node.parentKey) { parentNode = {...item}; break; } if (item.children && item.children.length) { loop(item.children); } } }; loop(treeData); return getTopNodeByNode(treeData, parentNode); // 继续查找parentNode的父节点 }
/** * 渲染树,cb(node[, children nodes]) * @param{Array}treeData 树的树状结构数据 * @param{function}cb 回调函数:cb(node[, children nodes]) */ export function renderNode(treeData, cb) { const loop = data => data.map((item) => { if (item.children) { return cb(item, loop(item.children)); // item children Item }
return cb(item); // 叶子节点 }); return loop(treeData); }
/** * 查找给定节点,及其后代节点property属性,第一个不为空的值 * @param{Array}treeData 树的树状结构数据 * @param{object}node 节点数据 * @param{String}property 属性,比如 key, path等 * @returns{*} */ export function getFirstValue(treeData, node, property) { if (node[property]) return node[property]; let firstValue = null; const loop = data => { for (let item of data) { if (item[property]) { firstValue = item[property]; break; } if (item.children && item.children.length) { loop(item.children); } } }; if (node.children && node.children.length) { loop(node.children); } return firstValue; } 标签:node,通用,树结构,treeData,param,item,key,操作,children From: https://www.cnblogs.com/hellofangfang/p/17750752.html