Tree结构目录场景
概述
技术栈:
- springBoot
- Mybatis-Plus
- Vue
- stream
内容:
做一个目录场景
步骤
1. 新建项目
2. 整合SSM
- 在pom文件中整合ssm相关的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<version>1.18.20</version>
</dependency>
<!--测试test依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
3. 在application.yaml
中配置端口和数据库连接
logging.level:设置日志级别,后面跟生效的区域,比如root表示整个项目,也可以设置为某个包下,也可以具体到某个类名(日志级别的值不区分大小写)
server:
port: 9100 # 端口号
spring:
freemarker: # freemarker模板引入
suffix: .html # 模板后缀
cache: false # 关闭缓存
jackson: # 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 # 时区
locale: zh_CN
generator: # 数据处理(防止精度丢失)
write-numbers-as-strings: true
write-bigdecimal-as-plain: true
serialization:
write-dates-as-timestamps: false
datasource: #数据源
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/yue-user-cours-db?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf-8&useSSL=false #数据库
username: root
password:
hikari:
connection-timeout: 60000
validation-timeout: 3000
idle-timeout: 60000
login-timeout: 5
max-lifetime: 60000
maximum-pool-size: 30
minimum-idle: 10
read-only: false
# mybatis-plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:/mapper/*.xml
logging:
level:
root: info
# logging.level:设置日志级别,后面跟生效的区域,
#比如root表示整个项目,也可以设置为某个包下,也可以具体到某个类名(日志级别的值不区分大小写)
4. 编写Category
相关bean
、mapper
、service
和controller
@TableField
: 注解用于标识非主键的字段。将数据库列与 JavaBean 中的属性进行映射
数据库表
pojo
@TableName(表名)
:对数据库中对应表进行映射,操作数据@TableId
: 将某个成员变量指定为数据表主键@TableField
:标识非主键的字段。将数据库列与 JavaBean 中的属性进行映射@TableField(exist = false)
:默认为true,而修改为false
的话 ,代表当前这个字段不在表中
package com.yue.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
/**
* @Author: 夜雨
* @Date: 2021-12-21-9:55
* @Description:课程分类
* @Version 1.0
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("kss_course_category")
public class CourseCategory implements java.io.Serializable{
@TableId(type = IdType.AUTO)
private Integer id;
// 上课标题
private String title;
// 分类描述
private String descrciption;
// 是否更新
private Integer mark;
// 发布状态1发布 0未发布
private Integer status;
// 类型 1文件夹 2文件
private Integer type;
// 父id =0 根集 其它全部都是子集(节点pid对映哪个id就说明其是哪个节点的子节点)
private Integer pid;
// 是否展开和收起
private Boolean isexpand;
// 子节点集合:这里一定加exist=false ,代表当前这个字段不在表中,不加就会报错
@TableField(exist = false)
private List<CourseCategory> chilrenList;
// 创建时间
@TableField(fill = FieldFill.INSERT)//在新增的时候填充
private Date createTime;
// 更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)//在新增的时候填充
private Date updateTime;
}
mapper
BaseMapper
接口:Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能,其 全限定名称为com.baomidou.mybatisplus.core.mapper.BaseMapper<T>
,该接口提供了插入、修改、删除和查询功能。我们可以在测试类中直接使用了insert()
、selectById()
、updateById()
和deleteById()
方法
BaseMapper部分源码截图:
package com.yue.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yue.pojo.CourseCategory;
/**
* @Author: 夜雨
* @Date: 2021-12-21-10:18
* @Description:
* @Version 1.0
*/
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
}
service & serviceImpl
IService
接口:Mapper 继承该接口后,即可获得CRUD功能,其 全限定名称为com.baomidou.mybatisplus.extension.service<T>
.使用该接口时,其实现类也必须继承ServiceImpl
类
IService接口部分源码截图:
ServiceImpl类部分源码截图
接口:
package com.yue.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yue.pojo.CourseCategory;
/**
* @Author: 夜雨
* @Date: 2021-12-21-10:27
* @Description:
* @Version 1.0
*/
public interface CourseCategoryService extends IService<CourseCategory> {
}
实现类:
package com.yue.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yue.mapper.CourseCategoryMapper;
import com.yue.pojo.CourseCategory;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
/**
* @Author: 夜雨
* @Date: 2021-12-21-10:34
* @Description:
* @Version 1.0
*/
@Service
@Log4j2
public class CourseCategoryServiceImpl extends ServiceImpl<CourseCategoryMapper, CourseCategory> implements CourseCategoryService {
}
controller
package com.yue.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: 夜雨
* @Date: 2021-12-21-11:15
* @Description:
* @Version 1.0
*/
@RestController
public class CourseCategoryController {
@GetMapping("/api/category/tree")
public String tree(){
return "tree";
}
}
测试整合SSM:@MapperScan("com.yue.mapper")
package com.yue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.yue.mapper")
public class YeyuStreamVueTreeApplication {
public static void main(String[] args) {
SpringApplication.run(YeyuStreamVueTreeApplication.class, args);
}
}
进行查询测试:
package com.yue.controller;
import com.yue.pojo.CourseCategory;
import com.yue.service.CourseCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author: 夜雨
* @Date: 2021-12-21-11:15
* @Description:
* @Version 1.0
*/
@RestController
public class CourseCategoryController {
@Autowired
private CourseCategoryService courseCategoryService;
@GetMapping("/api/category/tree")
public List<CourseCategory> tree(){
return courseCategoryService.list();
}
}
http://localhost:9100/api/category/tree
:
5. 使用stream流完成分类的递归的相关操作
(1)定义接口
public interface CourseCategoryService extends IService<CourseCategory> {
/*
* 返回分类tree
* */
List<CourseCategory> findCategoires();
}
(2)递归实现:
sorted((a,b) -> a.getSorted()-b.getSorted())
:升序sorted((a,b) -> b.getSorted()-a.getSorted())
:降序
@Service
@Log4j2
public class CourseCategoryServiceImpl extends ServiceImpl<CourseCategoryMapper, CourseCategory> implements CourseCategoryService {
@Override
public List<CourseCategory> findCategoires() {
//1.查询表中所有数据
List<CourseCategory> allList = this.list();
//2.查询根节点
List<CourseCategory> rootList = allList.stream().filter(category -> category.getPid().equals(0)).
sorted((a,b) -> a.getSorted()-b.getSorted()).
collect(Collectors.toList());
//3.查询子节点
List<CourseCategory> subList = allList.stream().filter(category -> !category.getPid().equals(0)).collect(Collectors.toList());
//4.循环根节点找到各自对应子节点
rootList.forEach(root -> rootSub(root,subList));
return rootList;
}
/*
* 根节点寻找对应子节点
* */
private void rootSub(CourseCategory root,List<CourseCategory> subList){
//根节点id和子节点pid相等
List<CourseCategory> childList = subList.stream().filter(sub -> sub.getPid().equals(root.getId())).
sorted((a,b) -> a.getSorted()-b.getSorted()).
collect(Collectors.toList());
//如果某个根节点下有子节点,就将子节点放入对应根节点下
if (!CollectionUtils.isEmpty(childList)) {
root.setChildrenList(childList);
//如果子节点不为空,则继续递归,去找对应子节点
childList.stream().forEach(child -> rootSub(child,subList));
}else {
root.setChildrenList(new ArrayList<>());
}
}
}
将控制层修改后进去看看效果
6. 使用axios完成异步接口的调用
概述
两种方式:
-
要么使用现成的tree组件 v-tree/ztree/other tree
-
自己开发
步骤:
- 查询接口返回数据
- 使用Axios查询数据接口返回给页面
- 使用vue来进行tree实现
此处注意一点:
temeleaf
下的html文件只可通过路由访问,不能直接访问
测试引入axios.js
是否成功:
<style> [v-cloak]{display: none} </style>
作用:防止抖动渲染(就是刚进去会有一刹那的{{title}}
显示)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>树形菜单</title>
<style>
/*防止抖动渲染*/
[v-cloak]{display: none}
</style>
</head>
<body>
<div id="app" v-cloak>
<h1>{{title}}</h1>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
var vue = new Vue({
el:"#app", //el 配置项指实例负责管理的区域; #app 指 id="app" 的dom标签里的所有内容(只对其有效)
data:{
title:"基于vue的Tree结构目录场景",
},
created:function(){
// 1: 页面加载初始化执行获取数组
},
methods: {
// 加载tree的数据信息
}
})
</script>
</body>
</html>
注意:此处可能静态资源没编译进去会报js404,所以这个时候可以手动复制进编译文件中:
测试结果:引入成功
调用代码
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>树形菜单</title>
<style>
[v-cloak]{display: none}
</style>
</head>
<body>
<div id="app" v-cloak>
<h1>{{title}}</h1>
<div>{{dataList}}</div>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
var vue = new Vue({
el: "#app",
data: {
title: "基于vue的Tree结构目录场景",
dataList:[]
},
created: function () {
// 1: 页面加载初始化执行获取数组
this.loadTree();
},
methods: {
// 加载tree的数据信息
loadTree: function () {
axios.get("/api/category/tree").then(res => {
this.dataList=res.data;
})
}
}
})
</script>
</body>
</html>
测试调用结果:调用成功
7. 使用vue的组件和递归组件完成tree结构
基础框架实现
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>树形菜单</title>
<style>
[v-cloak]{display: none}
</style>
</head>
<body>
<div id="app" v-cloak>
<h1>{{title}}</h1>
<yue-tree v-bind:date="dateList"></yue-tree>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
/*自定义组件*/
Vue.component("yue-tree",{
/*自定义属性*/
props:{
date:Array
},
/*自定义模板*/
template:"<ul>" +
"<li v-for='(d,index) in date'>{{d.title}} <yue-tree :date='d.childrenList'></yue-tree></li>"+
"</ul>",
})
var vue = new Vue({
el: "#app",
data: {
title: "基于vue的Tree结构目录场景",
dateList:[]
},
created: function () {
// 1: 页面加载初始化执行获取数组
this.loadTree();
},
methods: {
// 加载tree的数据信息a
loadTree: function () {
axios.get("/api/category/tree").then(res => {
this.dateList=res.data;
})
}
}
})
</script>
</body>
</html>
添加图标
/*自定义模板*/
template:"<ul>" +
"<li v-for='(d,index) in date'>"+
"<span>" +
"<span v-if='d.type==1'>目录图标 </span> " +
"<span v-if='d.type==2'>文件图标 </span> " +
"{{d.title}}" +
"</span>"+
"<yue-tree v-show='d.isexpand' :date='d.childrenList'></yue-tree>"+
"</li>"+
"</ul>",
添加单击事件
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>树形菜单</title>
<style>
[v-cloak] {
display: none
}
</style>
</head>
<body>
<div id="app" v-cloak>
<h1>{{title}}</h1>
<yue-tree v-bind:date="dateList"></yue-tree>
</div>
<script src="/js/vue.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
/*自定义组件*/
Vue.component("yue-tree", {
/*自定义属性*/
props: {
date: Array
},
/*自定义模板*/
template: "<ul>" +
"<li v-for='(d,index) in date' @click.stop ='expend(d)'>" +
"<span>" +
"<span v-if='d.type==1'>目录图标 </span> " +
"<span v-if='d.type==2'>文件图标 </span> " +
"{{d.title}}" +
"</span>" +
"<yue-tree v-show='d.isexpand' :date='d.childrenList'></yue-tree>" +
"</li>" +
"</ul>",
/*自定义事件*/
methods: {
expend: function (obj) {
obj.isexpand = !obj.isexpand
}
}
})
var vue = new Vue({
el: "#app",
data: {
title: "基于vue的Tree结构目录场景",
dateList: []
},
created: function () {
// 1: 页面加载初始化执行获取数组
this.loadTree();
},
methods: {
// 加载tree的数据信息a
loadTree: function () {
axios.get("/api/category/tree").then(res => {
this.dateList = res.data;
})
}
}
})
</script>
</body>
</html>