• java实现分类下拉树,点击时对应搜索---后端逻辑


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

    一、效果预览

    点击后

    二、实现步骤

    1、数据库设计层面:

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

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

    属性数据类型字段说明举例
    markClassIdint书签分类的id1001
    parentidint该分类的上级分类id(父id)1000
    ancestorsstring该分类所有上属分类id(祖id)0,10,100,1000

    2、后端基础的构建

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

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

    1. private MarkclassMapper markclassMapper;
    2. //这个是在你写的Impl层中的那个类里,你的selectById方法在哪里就引用哪个
    3. public int insertMarkclass(Markclass markclass)
    4. {
    5. Markclass mar=
    6. markclassMapper.selectMarkclassByMarkClassId(markclass.getParentid());
    7. //依据传入的markclass对象的父分类id查找具体的分类
    8. markclass.setAncestors(mar.getAncestors()+","+markclass.getParentid());
    9. //调用set方法。按照字符串拼接的方法,获取父分类所包含的祖分类id,并加上父分类id。
    10. return markclassMapper.insertMarkclass(markclass);
    11. }

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

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

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

    1. package com.blhq.wjs.domain;
    2. import java.io.Serializable;
    3. import java.util.List;
    4. import java.util.stream.Collectors;
    5. import com.blhq.common.core.domain.entity.SysMenu;
    6. import com.fasterxml.jackson.annotation.JsonInclude;
    7. //引入需要的你定义到的实体类
    8. /**
    9. * Treeselect树结构实体类
    10. * 数据类型依据el-tree数据需求进行定义,即为:id、label、children,
    11. * @author blhq
    12. */
    13. public class TreeSelect implements Serializable
    14. {
    15. private static final long serialVersionUID = 1L;
    16. /** 节点ID */
    17. private Long id;
    18. /** 节点名称 */
    19. private String label;
    20. /** 子节点 */
    21. // @JsonInclude(JsonInclude.Include.NON_EMPTY)
    22. private List children;
    23. public TreeSelect(Class aclass) {
    24. this.id = aclass.getClassid();
    25. this.label = aclass.getClassname();
    26. // 检查此处是否正确设置了 class_children 属性
    27. this.children = aclass.getClass_children().
    28. stream().map(TreeSelect::new).collect(Collectors.toList());
    29. }
    30. //写了多个TreeSelect构造类,以满足后续其它类型对象的调用,
    31. public TreeSelect(Markclass markclass){
    32. this.id=markclass.getMarkClassId();
    33. this.label=markclass.getClassname();
    34. this.children=markclass.getMarkChildren().
    35. stream().map(TreeSelect::new).collect(Collectors.toList());
    36. }
    37. public TreeSelect(SysMenu menu)
    38. {
    39. this.id = menu.getMenuId();
    40. this.label = menu.getMenuName();
    41. this.children = menu.getChildren().
    42. stream().map(TreeSelect::new).collect(Collectors.toList());
    43. }
    44. public Long getId()
    45. {
    46. return id;
    47. }
    48. public void setId(Long id)
    49. {
    50. this.id = id;
    51. }
    52. public String getLabel()
    53. {
    54. return label;
    55. }
    56. public void setLabel(String label)
    57. {
    58. this.label = label;
    59. }
    60. public List getChildren()
    61. {
    62. return children;
    63. }
    64. public void setChildren(List children)
    65. {
    66. this.children = children;
    67. }
    68. }

    补充:Markclass类

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

    (2)service层:

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

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

    1. public List selectMarkClassTreeList(Markclass markclass);
    2. //接收用户传入的分类,即为当前操作的分类
    3. public List buildMarkClassTree(List markclassList);
    4. //构建树的核心方法
    5. public List buildMarkClassTreeSelect(List classes);
    6. //注意返回值类型为刚刚定义的TreeSelect的列表。

    (3)Impl实现层:

    核心代码:
    1. @Override
    2. public List selectMarkClassTreeList(Markclass markclass) {
    3. //传入用户操作的分类,获取筛选后的书签分类列表
    4. List markClasses = this.selectMarkclassList(markclass);
    5. //selectMarkclassList()为查找分类列表用的方法,返回值为List类型
    6. return buildMarkClassTreeSelect(markClasses);
    7. //构建树
    8. }
    9. @Override
    10. public List buildMarkClassTreeSelect(List classes) {
    11. List markClasses = buildMarkClassTree(classes);
    12. return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
    13. }
    14. /**
    15. * 构建前端所需要树结构
    16. */
    17. @Override
    18. public List buildMarkClassTree(List markclassList) {
    19. List returnList=new ArrayList();
    20. 存储当前分类出现的节点
    21. List tempList = markclassList.stream().map(Markclass::getMarkClassId).collect(Collectors.toList());
    22. for (Markclass markclass : markclassList) {
    23. if(!tempList.contains(markclass.getParentid())){
    24. // 如果是顶级节点, 遍历该父节点的所有子节点
    25. recursionFn(markclassList, markclass);
    26. returnList.add(markclass);
    27. //不存在的加入进去,从而完成一整个树的遍历
    28. }
    29. }
    30. if (returnList.isEmpty()) {
    31. returnList = markclassList;
    32. }
    33. return returnList;
    34. }
    35. private void recursionFn(List list, Markclass t) {
    36. // 递归。得到子节点列表
    37. List childList = getChildList(list, t);
    38. t.setMarkChildren(childList);
    39. for (Markclass tChild : childList) {
    40. if (hasChild(list, tChild)) {
    41. recursionFn(list, tChild);
    42. }
    43. }
    44. }
    45. /**
    46. * 得到子节点列表
    47. */
    48. private List getChildList(List list, Markclass t) {
    49. List tlist = new ArrayList();
    50. for (Markclass n : list) {
    51. if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
    52. tlist.add(n);
    53. }
    54. }
    55. return tlist;
    56. }
    57. private boolean hasChild(List list, Markclass t) {
    58. return getChildList(list, t).size() > 0;
    59. }
    完整Impl层代码:
    1. package com.blhq.wjs.service.impl;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. import java.util.stream.Collectors;
    5. import com.blhq.common.utils.StringUtils;
    6. import com.blhq.wjs.domain.TreeSelect;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.stereotype.Service;
    9. import com.blhq.wjs.mapper.MarkclassMapper;
    10. import com.blhq.wjs.domain.Markclass;
    11. import com.blhq.wjs.service.IMarkclassService;
    12. /**
    13. * 书签分类Service业务层处理
    14. *
    15. * @author blhq
    16. * @date 2024-06-19
    17. */
    18. @Service
    19. public class MarkclassServiceImpl implements IMarkclassService
    20. {
    21. @Autowired
    22. private MarkclassMapper markclassMapper;
    23. /**
    24. * 查询书签分类
    25. *
    26. * @param markClassId 书签分类主键
    27. * @return 书签分类
    28. */
    29. @Override
    30. public Markclass selectMarkclassByMarkClassId(Long markClassId)
    31. {
    32. return markclassMapper.selectMarkclassByMarkClassId(markClassId);
    33. }
    34. /**
    35. * 查询书签分类列表
    36. *
    37. * @param markclass 书签分类
    38. * @return 书签分类
    39. */
    40. @Override
    41. public List selectMarkclassList(Markclass markclass)
    42. {
    43. return markclassMapper.selectMarkclassList(markclass);
    44. }
    45. /**
    46. * 新增书签分类
    47. *
    48. * @param markclass 书签分类
    49. * @return 结果
    50. */
    51. @Override
    52. public int insertMarkclass(Markclass markclass)
    53. {
    54. if (markclass.getMarkClassId() == null) {
    55. return markclassMapper.insertMarkclass(markclass);
    56. }else {
    57. Markclass mar=markclassMapper.selectMarkclassByMarkClassId(markclass.getParentid());
    58. markclass.setAncestors(mar.getAncestors()+","+markclass.getParentid());
    59. return markclassMapper.insertMarkclass(markclass);
    60. }
    61. }
    62. /**
    63. * 修改书签分类
    64. *
    65. * @param markclass 书签分类
    66. * @return 结果
    67. */
    68. @Override
    69. public int updateMarkclass(Markclass markclass)
    70. {
    71. return markclassMapper.updateMarkclass(markclass);
    72. }
    73. /**
    74. * 批量删除书签分类
    75. *
    76. * @param markClassIds 需要删除的书签分类主键
    77. * @return 结果
    78. */
    79. @Override
    80. public int deleteMarkclassByMarkClassIds(Long[] markClassIds)
    81. {
    82. return markclassMapper.deleteMarkclassByMarkClassIds(markClassIds);
    83. }
    84. /**
    85. * 删除书签分类信息
    86. *
    87. * @param markClassId 书签分类主键
    88. * @return 结果
    89. */
    90. @Override
    91. public int deleteMarkclassByMarkClassId(Long markClassId)
    92. {
    93. return markclassMapper.deleteMarkclassByMarkClassId(markClassId);
    94. }
    95. @Override
    96. public List selectMarkClassTreeList(Markclass markclass) {
    97. //传入用户操作的分类,获取筛选后的书签分类列表
    98. List markClasses = this.selectMarkclassList(markclass);
    99. //selectMarkclassList()为查找分类列表用的方法,返回值为List类型
    100. return buildMarkClassTreeSelect(markClasses);
    101. //构建树
    102. }
    103. @Override
    104. public List buildMarkClassTreeSelect(List classes) {
    105. List markClasses = buildMarkClassTree(classes);
    106. return markClasses.stream().map(TreeSelect::new).collect(Collectors.toList());
    107. }
    108. /**
    109. * 构建前端所需要树结构
    110. */
    111. @Override
    112. public List buildMarkClassTree(List markclassList) {
    113. List returnList=new ArrayList();
    114. 存储当前分类出现的节点
    115. List tempList = markclassList.stream().map(Markclass::getMarkClassId).collect(Collectors.toList());
    116. for (Markclass markclass : markclassList) {
    117. if(!tempList.contains(markclass.getParentid())){
    118. // 如果是顶级节点, 遍历该父节点的所有子节点
    119. recursionFn(markclassList, markclass);
    120. returnList.add(markclass);
    121. //不存在的加入进去,从而完成一整个树的遍历
    122. }
    123. }
    124. if (returnList.isEmpty()) {
    125. returnList = markclassList;
    126. }
    127. return returnList;
    128. }
    129. private void recursionFn(List list, Markclass t) {
    130. // 递归。得到子节点列表
    131. List childList = getChildList(list, t);
    132. t.setMarkChildren(childList);
    133. for (Markclass tChild : childList) {
    134. if (hasChild(list, tChild)) {
    135. recursionFn(list, tChild);
    136. }
    137. }
    138. }
    139. /**
    140. * 得到子节点列表
    141. */
    142. private List getChildList(List list, Markclass t) {
    143. List tlist = new ArrayList();
    144. for (Markclass n : list) {
    145. if (StringUtils.isNotNull(n.getParentid()) && n.getParentid().longValue() == t.getMarkClassId().longValue()) {
    146. tlist.add(n);
    147. }
    148. }
    149. return tlist;
    150. }
    151. private boolean hasChild(List list, Markclass t) {
    152. return getChildList(list, t).size() > 0;
    153. }
    154. }

    (4)controller层:

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

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

    (5)xml文件:

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

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

    4、前端应用:

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

    (1)引入api:

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

    (2)页面导入

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

    1. <template>
    2. <el-row :gutter="20">
    3. <el-col :span="4" :xs="24">
    4. <div class="head-container">
    5. <el-input
    6. v-model="markName"
    7. placeholder="请输入书签分类名称"
    8. clearable
    9. size="small"
    10. prefix-icon="el-icon-search"
    11. style="margin-bottom: 20px"
    12. />
    13. div>
    14. <el-tree
    15. :data="markOptions"
    16. :props="defaultProps"
    17. :expand-on-click-node="false"
    18. :filter-node-method="filterNode"
    19. ref="tree"
    20. node-key="id"
    21. default-expand-all
    22. highlight-current
    23. @node-click="handleNodeClick"
    24. />
    25. el-col>
    26. <el-col :span="20" :xs="24">
    27. …………
    28. el-col>
    29. el-row>
    30. …………//你的其它页面布局代码,上文gutter表示左侧分类栏占比大小。
    31. template>

    内需要添加的:

    1. data() {
    2. return {
    3. //data内需要添加的数据:
    4. // 书签分类名称
    5. markName:undefined,
    6. // 书签分类选项
    7. markOptions: undefined,
    8. //设置分类数据样式:
    9. defaultProps: {
    10. children: "children",
    11. //children:就是告诉el-tree,
    12. //它需要的children,在你这里的数据,叫啥名,比如我的为children
    13. //就是TreeSelect类中定义的children
    14. label: "label"
    15. },
    16. },
    17. },
    18. watch: {
    19. //在watch内,添加如下内容,没watch就自己加,在data(){},后面。
    20. // 根据名称筛选树
    21. markName(val) {
    22. this.$refs.tree.filter(val);
    23. }
    24. },
    25. created() {
    26. //调用方法
    27. this.getMarkTree();
    28. },
    29. methods: {
    30. // 获取书签分类树
    31. getMarkTree() {
    32. markClassTreeSelect().then(response => {
    33. this.markOptions = response.data;
    34. console.log(response.data);
    35. // console.log(response);
    36. });
    37. },
    38. // 筛选节点
    39. filterNode(value, data) {
    40. if (!value) return true;
    41. return data.label.indexOf(value) !== -1;
    42. },
    43. // 节点单击事件
    44. handleNodeClick(data) {
    45. this.queryParams.markClassId = data.id;
    46. this.handleQuery();
    47. },
    48. },

    5、成果展示:

    点击游戏类后:

    三、我遇到的问题

    (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类,重构一下,改个名就行。

    更详细说明请看下文:

    分类树实现时遇见的bug:如XXXController

    c8ca0a15 cannot be cast to XXX……-CSDN博客文章浏览阅读2次。实现分类树实现时遇见的bughttps://blog.csdn.net/qq_64595427/article/details/139843770?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22139843770%22%2C%22source%22%3A%22qq_64595427%22%7D

    如有问题,欢迎留言讨论哦~

  • 相关阅读:
    SqlServer中的集合运算符
    matlab之数组排序的方法和函数
    避免代价高昂的 ECM 集成错误的 3 个技巧
    世界环境日 | 周大福用心服务推动减碳环保
    《WEB安全渗透测试》(29)记一次HOST头投毒漏洞
    Elasticsearch:在 Elastic 中访问机器学习模型
    文字悬停效果
    算法通关村第六村-白银挑战树的层序遍历
    Flink1.15源码阅读——执行图executiongraph
    80C51单片机指令寻址方式
  • 原文地址:https://blog.csdn.net/qq_64595427/article/details/139810563