一直想做分类下拉,然后选择后搜索的页面,正好做项目有了明确的需求,查找后发现el-tree的构件可满足需求,数据要求为:{ id:1, label:name, childer:[……] }形式的,于是乎,开搞!
点击后
在设计数据库的分类表时,需要设置为层级结构的样式,指明父分类id和祖分类id,如此才可满足分类需求。
具体如下,比如我的书签分类表:markClass(以下只写出主要三列)
属性 | 数据类型 | 字段说明 | 举例 |
markClassId | int | 书签分类的id | 1001 |
parentid | int | 该分类的上级分类id(父id) | 1000 |
ancestors | string | 该分类所有上属分类id(祖id) | 0,10,100,1000 |
依据表格完成对后端基础增删改的设置,可以用Ruoyi一键生成。以下主要说明插入时,ancestors的设置:
插入时需要依据父分类id来完成对ancestors的设置。(注意设置ancestors的getter、setter方法)
- 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);
-
- }
el-tree的构件数据要求为:{ id:1, label:name, childer:[……] }形式,于是可定义一个“TreeSelect”类(类名称随意)
- 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
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
getChildren() - {
- return children;
- }
-
- public void setChildren(List
children) - {
- this.children = children;
- }
- }
-
-
- private List
markChildren=new ArrayList(); - //必须实例化,不然会报错java.lang.NullPointerException: null
- //同时设置getter、setter方法
- public List
getMarkChildren() { - return markChildren;
- }
-
- public void setMarkChildren(List
markChildren) { - this.markChildren = markChildren;
- }
在你的service类中创建方法:(如果没有service层,则无视,直接看实现层操作即可)
注意:部分返回值类型为刚刚定义的TreeSelect的列表。
- public List
selectMarkClassTreeList(Markclass markclass); - //接收用户传入的分类,即为当前操作的分类
-
- public List
buildMarkClassTree(List markclassList) ; - //构建树的核心方法
-
- public List
buildMarkClassTreeSelect(List classes) ; -
- //注意返回值类型为刚刚定义的TreeSelect的列表。
- @Override
- public List
selectMarkClassTreeList(Markclass markclass) { - //传入用户操作的分类,获取筛选后的书签分类列表
- List
markClasses = this.selectMarkclassList(markclass); - //selectMarkclassList()为查找分类列表用的方法,返回值为List
类型 - return buildMarkClassTreeSelect(markClasses);
- //构建树
- }
-
- @Override
- public List
buildMarkClassTreeSelect(List classes) { - List
markClasses = buildMarkClassTree(classes); -
- return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
- }
- /**
- * 构建前端所需要树结构
- */
- @Override
- public List
buildMarkClassTree(List markclassList) { - List
returnList=new ArrayList(); - 存储当前分类出现的节点
- List
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
list, Markclass t) { - // 递归。得到子节点列表
- List
childList = getChildList(list, t); - t.setMarkChildren(childList);
- for (Markclass tChild : childList) {
- if (hasChild(list, tChild)) {
- recursionFn(list, tChild);
- }
- }
- }
- /**
- * 得到子节点列表
- */
- private List
getChildList(List list, Markclass t) { - List
tlist = new ArrayList(); - for (Markclass n : list) {
- if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
- tlist.add(n);
- }
- }
- return tlist;
- }
-
- private boolean hasChild(List
list, Markclass t) { - return getChildList(list, t).size() > 0;
- }
- 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
- * @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
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
selectMarkClassTreeList(Markclass markclass) { - //传入用户操作的分类,获取筛选后的书签分类列表
- List
markClasses = this.selectMarkclassList(markclass); - //selectMarkclassList()为查找分类列表用的方法,返回值为List
类型 - return buildMarkClassTreeSelect(markClasses);
- //构建树
- }
-
- @Override
- public List
buildMarkClassTreeSelect(List classes) { - List
markClasses = buildMarkClassTree(classes); -
- return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
- }
- /**
- * 构建前端所需要树结构
- */
- @Override
- public List
buildMarkClassTree(List markclassList) { - List
returnList=new ArrayList(); - 存储当前分类出现的节点
- List
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
list, Markclass t) { - // 递归。得到子节点列表
- List
childList = getChildList(list, t); - t.setMarkChildren(childList);
- for (Markclass tChild : childList) {
- if (hasChild(list, tChild)) {
- recursionFn(list, tChild);
- }
- }
- }
- /**
- * 得到子节点列表
- */
- private List
getChildList(List list, Markclass t) { - List
tlist = new ArrayList(); - for (Markclass n : list) {
- if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
- tlist.add(n);
- }
- }
- return tlist;
- }
-
- private boolean hasChild(List
list, Markclass t) { - return getChildList(list, t).size() > 0;
- }
-
- }
这时候,我们直接添加即可:推荐把它放到对应需要分类显示的文件中,比如我的是在书签的controller中。
- @GetMapping("/deptTree")
- public AjaxResult deptTree(Markclass markclass)
- {
- List
list = markclassService.selectMarkClassTreeList(markclass); - return success(list);
- }
修改查找逻辑的语句:将分类的查询改为,从markclas表的ancestors查:
- <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 >= #{createTime}</if>
- <if test="editTime != null "> and b.editTime <= #{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>
整体思路就是引用它,把它调过来使用就行。下面按我的思路来演示:
- // 查询书签分类列表--tree
- export function markClassTreeSelect(query) {
- return request({
- url: '/markclass/markclass/deptTree',
- method: 'get',
- params: query
- })
- }
注意将v-model中的值,改为你自己的。
- <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"
- />
-
- el-col>
-
- <el-col :span="20" :xs="24">
- …………
- el-col>
- el-row>
-
- …………//你的其它页面布局代码,上文gutter表示左侧分类栏占比大小。
-
-
- template>
内需要添加的:
- 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();
- },
-
- },
点击游戏类后:
(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类,重构一下,改个名就行。
更详细说明请看下文:
如有问题,欢迎留言讨论哦~