Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

一直想做分类下拉,然后选择后搜索的页面,正好做项目有了明确的需求,查找后发现el-tree的构件可满足需求,数据要求为:{ id:1, label:name, childer:[……] }形式的,于是乎,开搞!

一、效果预览

img点击并拖拽以移动编辑

点击后img点击并拖拽以移动编辑

二、实现步骤

1、数据库设计层面:

在设计数据库的分类表时,需要设置为层级结构的样式,指明父分类id和祖分类id,如此才可满足分类需求。

具体如下,比如我的书签分类表:markClass(以下只写出主要三列)

属性 数据类型 字段说明 举例
markClassId int 书签分类的id 1001
parentid int 该分类的上级分类id(父id) 1000
ancestors string 该分类所有上属分类id(祖id) 0,10,100,1000

2、后端基础的构建

依据表格完成对后端基础增删改的设置,可以用Ruoyi一键生成。以下主要说明插入时,ancestors的设置:

插入时需要依据父分类id来完成对ancestors的设置。(注意设置ancestors的getter、setter方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private MarkclassMapper markclassMapper;
//这个是在你写的Impl层中的那个类里,你的selectById方法在哪里就引用哪个

public int insertMarkclass(Markclass markclass)
{
Markclass mar=
markclassMapper.selectMarkclassByMarkClassId(markclass.getParentid());
//依据传入的markclass对象的父分类id查找具体的分类

markclass.setAncestors(mar.getAncestors()+","+markclass.getParentid());
//调用set方法。按照字符串拼接的方法,获取父分类所包含的祖分类id,并加上父分类id。

return markclassMapper.insertMarkclass(markclass);

}

点击并拖拽以移动

3、主代码——构建树相关

(1)创建适配el-tree数据类型的类

el-tree的构件数据要求为:{ id:1, label:name, childer:[……] }形式,于是可定义一个“TreeSelect”类(类名称随意)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.blhq.wjs.domain;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;

import com.blhq.common.core.domain.entity.SysMenu;
import com.fasterxml.jackson.annotation.JsonInclude;
//引入需要的你定义到的实体类

/**
* Treeselect树结构实体类
* 数据类型依据el-tree数据需求进行定义,即为:id、label、children,
* @author blhq
*/
public class TreeSelect implements Serializable
{
private static final long serialVersionUID = 1L;

/** 节点ID */
private Long id;

/** 节点名称 */
private String label;

/** 子节点 */
// @JsonInclude(JsonInclude.Include.NON_EMPTY)

private List<TreeSelect> children;

public TreeSelect(Class aclass) {
this.id = aclass.getClassid();
this.label = aclass.getClassname();
// 检查此处是否正确设置了 class_children 属性
this.children = aclass.getClass_children().
stream().map(TreeSelect::new).collect(Collectors.toList());
}
//写了多个TreeSelect构造类,以满足后续其它类型对象的调用,
public TreeSelect(Markclass markclass){
this.id=markclass.getMarkClassId();
this.label=markclass.getClassname();
this.children=markclass.getMarkChildren().
stream().map(TreeSelect::new).collect(Collectors.toList());
}

public TreeSelect(SysMenu menu)
{
this.id = menu.getMenuId();
this.label = menu.getMenuName();
this.children = menu.getChildren().
stream().map(TreeSelect::new).collect(Collectors.toList());
}


public Long getId()
{
return id;
}

public void setId(Long id)
{
this.id = id;
}

public String getLabel()
{
return label;
}

public void setLabel(String label)
{
this.label = label;
}

public List<TreeSelect> getChildren()
{
return children;
}

public void setChildren(List<TreeSelect> children)
{
this.children = children;
}
}

点击并拖拽以移动

补充:Markclass类

1
2
3
4
5
6
7
8
9
10
private List<Markclass> markChildren=new ArrayList<Markclass>();
//必须实例化,不然会报错java.lang.NullPointerException: null
//同时设置getter、setter方法
public List<Markclass> getMarkChildren() {
return markChildren;
}

public void setMarkChildren(List<Markclass> markChildren) {
this.markChildren = markChildren;
}

点击并拖拽以移动

(2)service层:

在你的service类中创建方法:(如果没有service层,则无视,直接看实现层操作即可)

注意:部分返回值类型为刚刚定义的TreeSelect的列表。

1
2
3
4
5
6
7
8
9
    public List<TreeSelect> selectMarkClassTreeList(Markclass markclass);
//接收用户传入的分类,即为当前操作的分类

public List<Markclass> buildMarkClassTree(List<Markclass> markclassList);
//构建树的核心方法

public List<TreeSelect> buildMarkClassTreeSelect(List<Markclass> classes);

//注意返回值类型为刚刚定义的TreeSelect的列表。

点击并拖拽以移动

(3)Impl实现层:

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Override
public List<TreeSelect> selectMarkClassTreeList(Markclass markclass) {
//传入用户操作的分类,获取筛选后的书签分类列表
List<Markclass> markClasses = this.selectMarkclassList(markclass);
//selectMarkclassList()为查找分类列表用的方法,返回值为List<Markclass>类型
return buildMarkClassTreeSelect(markClasses);
//构建树
}

@Override
public List<TreeSelect> buildMarkClassTreeSelect(List<Markclass> classes) {
List<Markclass> markClasses = buildMarkClassTree(classes);

return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
}
/**
* 构建前端所需要树结构
*/
@Override
public List<Markclass> buildMarkClassTree(List<Markclass> markclassList) {
List<Markclass> returnList=new ArrayList<Markclass>();
////存储当前分类出现的节点
List<Long> tempList = markclassList.stream().map(Markclass::getMarkClassId).collect(Collectors.toList());
for (Markclass markclass : markclassList) {
if(!tempList.contains(markclass.getParentid())){
// 如果是顶级节点, 遍历该父节点的所有子节点
recursionFn(markclassList, markclass);
returnList.add(markclass);
//不存在的加入进去,从而完成一整个树的遍历
}
}
if (returnList.isEmpty()) {
returnList = markclassList;
}
return returnList;

}

private void recursionFn(List<Markclass> list, Markclass t) {
// 递归。得到子节点列表
List<Markclass> childList = getChildList(list, t);
t.setMarkChildren(childList);
for (Markclass tChild : childList) {
if (hasChild(list, tChild)) {
recursionFn(list, tChild);
}
}
}
/**
* 得到子节点列表
*/
private List<Markclass> getChildList(List<Markclass> list, Markclass t) {
List<Markclass> tlist = new ArrayList<Markclass>();
for (Markclass n : list) {
if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
tlist.add(n);
}
}
return tlist;
}

private boolean hasChild(List<Markclass> list, Markclass t) {
return getChildList(list, t).size() > 0;
}

点击并拖拽以移动

完整Impl层代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package com.blhq.wjs.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import com.blhq.common.utils.StringUtils;
import com.blhq.wjs.domain.TreeSelect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.blhq.wjs.mapper.MarkclassMapper;
import com.blhq.wjs.domain.Markclass;
import com.blhq.wjs.service.IMarkclassService;

/**
* 书签分类Service业务层处理
*
* @author blhq
* &#064;date 2024-06-19
*/
@Service
public class MarkclassServiceImpl implements IMarkclassService
{

@Autowired
private MarkclassMapper markclassMapper;

/**
* 查询书签分类
*
* @param markClassId 书签分类主键
* @return 书签分类
*/
@Override
public Markclass selectMarkclassByMarkClassId(Long markClassId)
{
return markclassMapper.selectMarkclassByMarkClassId(markClassId);
}

/**
* 查询书签分类列表
*
* @param markclass 书签分类
* @return 书签分类
*/
@Override
public List<Markclass> selectMarkclassList(Markclass markclass)
{
return markclassMapper.selectMarkclassList(markclass);
}

/**
* 新增书签分类
*
* @param markclass 书签分类
* @return 结果
*/
@Override
public int insertMarkclass(Markclass markclass)
{
if (markclass.getMarkClassId() == null) {
return markclassMapper.insertMarkclass(markclass);
}else {
Markclass mar=markclassMapper.selectMarkclassByMarkClassId(markclass.getParentid());
markclass.setAncestors(mar.getAncestors()+","+markclass.getParentid());
return markclassMapper.insertMarkclass(markclass);
}
}
/**
* 修改书签分类
*
* @param markclass 书签分类
* @return 结果
*/
@Override
public int updateMarkclass(Markclass markclass)
{
return markclassMapper.updateMarkclass(markclass);
}

/**
* 批量删除书签分类
*
* @param markClassIds 需要删除的书签分类主键
* @return 结果
*/
@Override
public int deleteMarkclassByMarkClassIds(Long[] markClassIds)
{
return markclassMapper.deleteMarkclassByMarkClassIds(markClassIds);
}

/**
* 删除书签分类信息
*
* @param markClassId 书签分类主键
* @return 结果
*/
@Override
public int deleteMarkclassByMarkClassId(Long markClassId)
{
return markclassMapper.deleteMarkclassByMarkClassId(markClassId);
}


@Override
public List<TreeSelect> selectMarkClassTreeList(Markclass markclass) {
//传入用户操作的分类,获取筛选后的书签分类列表
List<Markclass> markClasses = this.selectMarkclassList(markclass);
//selectMarkclassList()为查找分类列表用的方法,返回值为List<Markclass>类型
return buildMarkClassTreeSelect(markClasses);
//构建树
}

@Override
public List<TreeSelect> buildMarkClassTreeSelect(List<Markclass> classes) {
List<Markclass> markClasses = buildMarkClassTree(classes);

return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
}
/**
* 构建前端所需要树结构
*/
@Override
public List<Markclass> buildMarkClassTree(List<Markclass> markclassList) {
List<Markclass> returnList=new ArrayList<Markclass>();
////存储当前分类出现的节点
List<Long> tempList = markclassList.stream().map(Markclass::getMarkClassId).collect(Collectors.toList());
for (Markclass markclass : markclassList) {
if(!tempList.contains(markclass.getParentid())){
// 如果是顶级节点, 遍历该父节点的所有子节点
recursionFn(markclassList, markclass);
returnList.add(markclass);
//不存在的加入进去,从而完成一整个树的遍历
}
}
if (returnList.isEmpty()) {
returnList = markclassList;
}
return returnList;

}

private void recursionFn(List<Markclass> list, Markclass t) {
// 递归。得到子节点列表
List<Markclass> childList = getChildList(list, t);
t.setMarkChildren(childList);
for (Markclass tChild : childList) {
if (hasChild(list, tChild)) {
recursionFn(list, tChild);
}
}
}
/**
* 得到子节点列表
*/
private List<Markclass> getChildList(List<Markclass> list, Markclass t) {
List<Markclass> tlist = new ArrayList<Markclass>();
for (Markclass n : list) {
if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
tlist.add(n);
}
}
return tlist;
}

private boolean hasChild(List<Markclass> list, Markclass t) {
return getChildList(list, t).size() > 0;
}

}

点击并拖拽以移动

(4)controller层:

这时候,我们直接添加即可:推荐把它放到对应需要分类显示的文件中,比如我的是在书签的controller中。

1
2
3
4
5
6
@GetMapping("/deptTree")
public AjaxResult deptTree(Markclass markclass)
{
List<TreeSelect> list = markclassService.selectMarkClassTreeList(markclass);
return success(list);
}

点击并拖拽以移动

(5)xml文件:

修改查找逻辑的语句:将分类的查询改为,从markclas表的ancestors查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<select id="selectBookmarkList" parameterType="Bookmark" resultMap="BookmarkResult">
select b.markId, b.markName, b.markClassId, b.website, b.`desc`, b.createTime, b.editTime, b.icon, b.statue, b.commonGrade, b.allGrade, b.markPlot, b.likes, b.markExtend, c.classname, c.ancestors from bookmark b
left join markclass c on b.markClassId = c.markClassId
where 1=1
<if test="markId != null "> and markId = #{markId}</if>
<if test="markName != null and markName != ''"> and markName like concat('%', #{markName}, '%')</if>
<if test="markClassId != null ">
AND (b.markClassId = #{markClassId} OR b.markClassId IN ( SELECT c.markClassId FROM class c WHERE find_in_set(#{markClassId}, ancestors) ))
</if>
<if test="website != null and website != ''"> and website like concat('%', #{website}, '%')</if>
<if test="desc != null and desc != ''"> and b.`desc` like concat('%', #{desc}, '%')</if>
<if test="createTime != null "> and b.createTime &gt;= #{createTime}</if>
<if test="editTime != null "> and b.editTime &lt;= #{editTime}</if>
<if test="icon != null and icon != ''"> and icon like concat('%', #{icon}, '%')</if>
<if test="statue != null "> and statue = #{statue}</if>
<if test="commonGrade != null "> and commonGrade = #{commonGrade}</if>
<if test="allGrade != null "> and allGrade like concat('%', #{allGrade}, '%')</if>
<if test="markPlot != null and markPlot != ''"> and markPlot = #{markPlot}</if>
<if test="likes != null "> and likes like concat('%', #{likes}, '%')</if>
<if test="markExtend != null "> and markExtend like concat('%', #{markExtend}, '%')</if>
</select>

点击并拖拽以移动

4、前端应用:

整体思路就是引用它,把它调过来使用就行。下面按我的思路来演示:

(1)引入api:

1
2
3
4
5
6
7
8
// 查询书签分类列表--tree
export function markClassTreeSelect(query) {
return request({
url: '/markclass/markclass/deptTree',
method: 'get',
params: query
})
}

点击并拖拽以移动

(2)页面导入

注意将v-model中的值,改为你自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<el-row :gutter="20">
<el-col :span="4" :xs="24">
<div class="head-container">
<el-input
v-model="markName"
placeholder="请输入书签分类名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<el-tree
:data="markOptions"
:props="defaultProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
<!-- </div>-->
</el-col>

<el-col :span="20" :xs="24">
…………
</el-col>
</el-row>

…………//你的其它页面布局代码,上文gutter表示左侧分类栏占比大小。


</template>

点击并拖拽以移动

内需要添加的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
   data() {
return {
//data内需要添加的数据:

// 书签分类名称
markName:undefined,
// 书签分类选项
markOptions: undefined,
//设置分类数据样式:
defaultProps: {
children: "children",
//children:就是告诉el-tree,
//它需要的children,在你这里的数据,叫啥名,比如我的为children
//就是TreeSelect类中定义的children
label: "label"
},
},
},

watch: {
//在watch内,添加如下内容,没watch就自己加,在data(){},后面。
// 根据名称筛选树
markName(val) {
this.$refs.tree.filter(val);
}
},

created() {
//调用方法
this.getMarkTree();
},

methods: {

// 获取书签分类树
getMarkTree() {
markClassTreeSelect().then(response => {
this.markOptions = response.data;
console.log(response.data);
// console.log(response);
});
},
// 筛选节点
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
// 节点单击事件
handleNodeClick(data) {
this.queryParams.markClassId = data.id;
this.handleQuery();
},

},

点击并拖拽以移动

5、成果展示:

img点击并拖拽以移动编辑

点击游戏类后:

img点击并拖拽以移动编辑

三、我遇到的问题

(1):无法自动装配。找不到’MarkclassMapper’类型的 Bean。

方案:在MarkclassMapper中,最前面加入@Mapper注解即可

(2)Failed to instantiate: Factory method ‘sqlSessionFactory’ threw exception; TypeException: The alias ‘TreeSelect’ is already mapped to the value ‘com.blhq.wjs.domain.TreeSelect’.

方案:如果提示这个,请把我们刚才定义的TreeSelect类,重构一下,改个名就行。

更详细说明请看下文:

评论