一握爷、背景
最近的開發(fā)工作用到“樹”模型比較多,例如節(jié)點樹模叙、權(quán)限樹等歇拆。
每一個實體都有自己特殊的字段,所以最初的解決方法是給每一個實體寫?yīng)毺氐霓D(zhuǎn)換方法范咨。
然后需要轉(zhuǎn)換為樹的實體變多故觅,代碼就會冗余,因此寫一個工具類渠啊,提供對應(yīng)的方法將常見的List轉(zhuǎn)換為Tree输吏。
二、實現(xiàn)
1.效果
UI框架:layui(官方文檔:https://www.layui.com/doc/)
在使用layui的過程中發(fā)現(xiàn)替蛉,目前l(fā)ayui的組件功能并沒有很強大贯溅,比如樹組件就不能實現(xiàn)“只選單個”拄氯,但是layui簡單易用,源碼也不復(fù)雜它浅,因此可以通過修改源碼來達到目的译柏。
后續(xù)計劃將自己在使用layui過程中遇到的問題和解決方法寫出來分享,歡迎各位碼友關(guān)注學(xué)習(xí)罚缕。
2.編碼實現(xiàn)
2.1 實體TreeDot
前端框架實現(xiàn)樹組件艇纺,一般都有自己獨特的數(shù)據(jù)源格式。從layui文檔可看到layui實現(xiàn)樹組件的數(shù)據(jù)源格式:
因此邮弹,首先建一個實體TreeDot,那么將這個實體返回前端就可以直接渲染成樹蚓聘。使用其他的UI框架類推腌乡。
package pri.xiaowd.layui.pojo;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/***
* 樹點
* 需要實現(xiàn)樹的類可以繼承該類,手寫set方法夜牡,在設(shè)定本身屬性值時同時設(shè)置該類中的相關(guān)屬性
* @ClassName TreeTreeDot
* @Author xiaowd
* @DateTime 2020/2/23 15:58
*/
@Data
public class TreeDot<T> {
/**
* 點ID
*/
private String id;
/**
* 點名稱
*/
private String title;
/**
* 父點ID
*/
private String parentId;
/**
* 該點是否展開与纽,默認不展開
*/
private Boolean spread = false;
/**
* 該點是否選中,默認不選中
*/
private Boolean checked = false;
/**
* 該點的圖標塘装,默認不設(shè)置
*/
private String icon;
/**
* 該點的其他屬性
*/
private Map<String,Object> attributes = new HashMap<>();
/**
* 該點的子樹集合
*/
private List<TreeDot<T>> children = new ArrayList<>();
}
TreeDot類的字段和文檔提供的字段可能不完全一樣急迂,這個根據(jù)自己的實際需求來定就行。最為重要的是 id蹦肴、title僚碎、children 三個字段,layui根據(jù)這三個字段就可以構(gòu)建一顆樹阴幌,其他都可以不要勺阐,也可以新增自己額外需要的,例如 parentId矛双、attributes 渊抽,新增的字段不會影響樹的構(gòu)建。
另外议忽,這里用到了泛型懒闷,因為需要適應(yīng)所有需要轉(zhuǎn)換為樹的實體。
2.2 實體Node
建自己需要轉(zhuǎn)換成樹的原始實體栈幸,一般就是從數(shù)據(jù)源讀取的數(shù)據(jù)愤估。這個實體需要繼承上面建的TreeDot,并重寫set方法侦镇。這里以Node為例:
package pri.xiaowd.layui.pojo;
import lombok.Data;
/***
* 節(jié)點
* @ClassName Node
* @Author xiaowd
* @DateTime 2020/1/31 15:37
*/
@Data
public class Node extends TreeDot {
/**
* 節(jié)點編碼
*/
private Integer nodeId;
/**
* 節(jié)點名稱
*/
private String nodeName;
/**
* 父節(jié)點編碼
*/
private Integer parentNodeId;
/**
* 創(chuàng)建時間
*/
private Long time;
public void setNodeId(Integer nodeId) {
this.nodeId = nodeId;
super.setId(String.valueOf(nodeId));
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
super.setTitle(nodeName);
}
public void setParentNodeId(Integer parentNodeId) {
this.parentNodeId = parentNodeId;
super.setParentId(String.valueOf(parentNodeId));
}
public void setTime(Long time) {
this.time = time;
super.getAttributes().put("time",time);
}
}
繼承TreeDot之后灵疮,也就擁有了TreeDot的字段,因此重寫set方法壳繁,將字段的值同時賦給TreeDot的字段震捣。這里有一個小技巧荔棉,因為必要的字段是id和title,那么多余的字段就可以統(tǒng)一放在一個集合中attributes蒿赢。注意润樱,attributes需要一開始就實例化,不然會報錯哦羡棵。
2.3 工具類TreeDotUtils
接下來就是具體的工具類了壹若,這個工具類是通用的,因此需要使用泛型(同時使用了遞歸皂冰,就這兩個重要的知識點)店展,只要是繼承了TreeDot并重寫set方法的實體都可以轉(zhuǎn)化:
package pri.xiaowd.layui.util;
import pri.xiaowd.layui.pojo.TreeDot;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/***
* 操作“樹”的工具
* @ClassName TreeDotUtils
* @Author xiaowd
* @DateTime 2020/4/22 9:14
*/
public class TreeDotUtils {
/**
* 將List轉(zhuǎn)換為Tree
* @MethosName convertListToTreeDot
* @Author xiaowd
* @Date 2020/4/22 9:17
* @param tList
* @return java.util.List<cn.eshore.common.entity.Tree<T>>
*/
public static <T extends TreeDot> List<TreeDot<T>> convertListToTreeDot(List<T> tList){
List<TreeDot<T>> treeDotList = new ArrayList<>();
if(tList != null && tList.size() > 0){
for(T t:tList){
if(!isTreeDotExist(tList,t.getParentId())){
//不存在以父ID為ID的點,說明是當(dāng)前點是頂級節(jié)點
TreeDot<T> tTreeDot = getTreeDotByT(t, tList);
treeDotList.add(tTreeDot);
}
}
}
return treeDotList;
}
/**
* 根據(jù)ID判斷該點是否存在
* @MethosName isTreeDotExist
* @Author xiaowd
* @Date 2020/4/22 9:50
* @param tList
* @param id 點ID
* @return java.lang.Boolean
*/
private static <T extends TreeDot> Boolean isTreeDotExist(List<T> tList, String id) {
for(T t:tList){
if(t.getId().equals(id)){
return true;
}
}
return false;
}
/**
* 獲取指定父點的子樹
* @MethosName getChildTreeList
* @Author xiaowd
* @Date 2020/4/22 10:02
* @param parentTreeDot 父點
* @param tList
* @return java.util.List<cn.eshore.common.entity.Tree<T>>
*/
private static <T extends TreeDot> List<TreeDot<T>> getChildTreeDotList(TreeDot<T> parentTreeDot, List<T> tList){
List<TreeDot<T>> childTreeDotList = new ArrayList<>();
for(T t:tList){
if(parentTreeDot.getId().equals(t.getParentId())){
//如果父ID是傳遞樹點的ID秃流,那么就是傳遞樹點的子點
TreeDot<T> tTreeDot = getTreeDotByT(t,tList);
childTreeDotList.add(tTreeDot);
}
}
return childTreeDotList;
}
/**
* 根據(jù)實體獲取TreeDot
* @MethosName getTreeDotByT
* @Author xiaowd
* @Date 2020/5/4 22:17
* @param t
* @param tList
* @return pri.xiaowd.layui.pojo.TreeDot<T>
*/
private static <T extends TreeDot> TreeDot<T> getTreeDotByT(T t,List<T> tList){
TreeDot<T> tTreeDot = new TreeDot<>();
tTreeDot.setId(t.getId());
tTreeDot.setParentId(t.getParentId());
tTreeDot.setTitle(t.getTitle());
tTreeDot.setChecked(t.getChecked());
tTreeDot.setIcon(t.getIcon());
tTreeDot.setAttributes(t.getAttributes());
tTreeDot.setChildren(getChildTreeDotList(tTreeDot,tList));
return tTreeDot;
}
/**
* 獲取根據(jù)指定ID所在點為父點的樹
* @MethosName getTreeDotById
* @Author xiaowd
* @Date 2020/4/22 15:00
* @param id
* @param treeDotList
* @return cn.eshore.common.entity.TreeDot<T>
*/
public static <T extends TreeDot> TreeDot<T> getTreeDotById(String id,List<TreeDot<T>> treeDotList){
if(id != null && !"".equals(id) && treeDotList != null && treeDotList.size() > 0){
for(TreeDot<T> treeDot:treeDotList){
if(id.equalsIgnoreCase(treeDot.getId())){
return treeDot;
}
if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
TreeDot<T> td = getTreeDotById(id, treeDot.getChildren());
if(td != null){
return td;
}
}
}
}
return null;
}
/**
* 將TreeList的所有點轉(zhuǎn)換為ID的Set集合
* @MethosName convertTreeDotToIdSet
* @Author xiaowd
* @Date 2020/4/22 16:13
* @param treeDotList
* @param kClass ID的類型
* @return java.util.Set<K>
*/
public static <T extends TreeDot,K> Set<K> convertTreeDotToIdSet(List<TreeDot<T>> treeDotList,Class<K> kClass){
Set<K> idSet = new HashSet<>();
if(treeDotList != null && treeDotList.size() > 0){
for(TreeDot<T> treeDot:treeDotList){
idSet.add((K)treeDot.getId());
if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));
}
}
}
return idSet;
}
/**
* 將Tree(單點)的所有點轉(zhuǎn)換為ID的Set集合
* @MethosName convertTreeDotToIdSet
* @Author xiaowd
* @Date 2020/4/29 9:08
* @param treeDot
* @param kClass
* @return java.util.Set<K>
*/
public static <T extends TreeDot,K> Set<K> convertTreeDotToIdSet(TreeDot<T> treeDot,Class<K> kClass){
Set<K> idSet = new HashSet<>();
if(treeDot != null){
idSet.add((K)treeDot.getId());
if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));
}
}
return idSet;
}
}
在需要轉(zhuǎn)化時赂蕴,只需要調(diào)用 convertListToTreeDot(List<T> tList) 方法即可。參數(shù)List<T>是原始的實體集合舶胀。
- 該方法在拿到原始的實體集合List<T>后概说,進行遍歷,通過將遍歷得到的實體的父ID傳入 isTreeDotExist(List<T> tList, String id) 方法判斷該實體的父實體是否存在嚣伐,如果不存在說明就是根實體(PS.根實體也是一個集合糖赔,根不一定就只有一個);
- 找到根實體之后轩端,通過 getTreeDotByT(T t,List<T> tList) 方法將該實體轉(zhuǎn)換為樹點TreeDot對象放典;該TreeDot對象中的children字段是子樹集合,通過 getChildTreeDotList(TreeDot<T> parentTreeDot, List<T> tList) 獲得船万;
- getChildTreeDotList方法使用了遞歸刻撒。有兩個參數(shù),第一個是樹點TreeDot<T>耿导,要獲取哪個樹點的子樹集合声怔,這個參數(shù)就傳哪個樹點;第二個是原始的實體集合List<T>舱呻。
- 遍歷原始的實體集合List<T>醋火,如果遍歷得到的實體的父ID是傳入的樹點的ID,那么這個實體就是傳入樹點的子樹集合其中一員箱吕,因此將該實體轉(zhuǎn)換為樹點并放到集合中芥驳,返回集合作為傳入樹點的子樹集合。
- 因為這個轉(zhuǎn)換出來的樹點也有自己的子樹集合茬高,那么就需要通過遞歸獲取兆旬,只是第一個參數(shù)已經(jīng)變成了這個轉(zhuǎn)換出來的樹點。
- 工具類中還額外提供了集合方法:
getTreeDotById(String id,List<TreeDot<T>> treeDotList) : 根據(jù)ID獲取樹點怎栽;
convertTreeDotToIdSet(List<TreeDot<T>> treeDotList,Class<K> kClass) : 根據(jù)樹集合(根有一到多個)獲取其ID的Set集合丽猬;
convertTreeDotToIdSet(TreeDot<T> treeDot,Class<K> kClass) : 根據(jù)樹(根只有一個)獲取其ID的Set集合宿饱。
2.4 測試
測試的話就不從數(shù)據(jù)庫拿到List<T>了,手動建即可脚祟。
- controller
@RequestMapping("/tree")
@ResponseBody
public Map<String,Object> tree(){
//國節(jié)點 中國
Node rootNode = new Node();
rootNode.setNodeId(110000);
rootNode.setNodeName("中國");
rootNode.setParentNodeId(0);
rootNode.setTime(System.currentTimeMillis());
//省節(jié)點 廣東
Node pNode1 = new Node();
pNode1.setNodeId(120000);
pNode1.setNodeName("廣東");
pNode1.setParentNodeId(110000);
pNode1.setTime(System.currentTimeMillis());
//市節(jié)點 廣州
Node cNode1 = new Node();
cNode1.setNodeId(120001);
cNode1.setNodeName("廣州");
cNode1.setParentNodeId(120000);
cNode1.setTime(System.currentTimeMillis());
//區(qū)節(jié)點 廣州
Node aNode1 = new Node();
aNode1.setNodeId(1200011);
aNode1.setNodeName("天河區(qū)");
aNode1.setParentNodeId(120001);
aNode1.setTime(System.currentTimeMillis());
//子節(jié)點 湖南
Node pNode2 = new Node();
pNode2.setNodeId(130000);
pNode2.setNodeName("湖南");
pNode2.setParentNodeId(110000);
pNode2.setTime(System.currentTimeMillis());
//市節(jié)點 長沙
Node cNode2 = new Node();
cNode2.setNodeId(130001);
cNode2.setNodeName("長沙");
cNode2.setParentNodeId(130000);
cNode2.setTime(System.currentTimeMillis());
//子節(jié)點 上海
Node pNode3 = new Node();
pNode3.setNodeId(140000);
pNode3.setNodeName("上海");
pNode3.setParentNodeId(110000);
pNode3.setTime(System.currentTimeMillis());
List<Node> nodeList = new ArrayList<>();
nodeList.add(rootNode);
nodeList.add(pNode1);
nodeList.add(cNode1);
nodeList.add(aNode1);
nodeList.add(pNode2);
nodeList.add(cNode2);
nodeList.add(pNode3);
//轉(zhuǎn)換
List<TreeDot<Node>> nodeTreeDotList = TreeDotUtils.convertListToTreeDot(nodeList);
Map<String,Object> result = new HashMap<>();
result.put("nodeTreeDotList",nodeTreeDotList);
return result;
}
- js
<script type="text/javascript" th:inline="javascript">
var ctxPath=[[${#httpServletRequest.getContextPath()}]];
layui.use(['tree'],function(){
var tree = layui.tree;
tree.render({
elem: '#node-tree',
id: 'node-tree',
data: getNodeTreeDot(),
onlyIconControl: true,
edit: ['add'],
customOperate: true,
text: {
none: '無節(jié)點'
}
});
});
function getNodeTreeDot() {
var data = [];
layui.jquery.ajax({
url: ctxPath + "/tree",
type: "post",
async: false,
success: function(result){
data = result.nodeTreeDotList;
}
});
console.log(data)
return data;
}
</script>
- 效果
三谬以、結(jié)語
將List轉(zhuǎn)換為樹有不同的寫法,但是基本的思路是一樣的由桌,將這種轉(zhuǎn)換寫成一個工具類为黎,我認為對減少代碼冗余、優(yōu)化代碼美觀程度行您、提高編碼效率都有較大的幫助铭乾。如果各位碼友有自己的想法,或者覺得我的寫法還可以再優(yōu)化娃循,歡迎給我留言片橡,共同交流。
交流郵箱:weidag_xiao@sina.com