项目源码与所需资料
链接:https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
提取码:8z59
文章目录
demo07-课程分类管理
1.数据库设计
1.在资料中找到"guli_edu.sql"这个脚本文件并双击打开
2.找到下图所示的这段语句
CREATE TABLE `edu_subject` (
`id` char(19) NOT NULL COMMENT '课程类别ID',
`title` varchar(10) NOT NULL COMMENT '类别名称',
`parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID',
`sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modified` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程科目';
3.将上一步说的那段语句复制到数据库中并执行该sql语句就可以创建出edu_subject数据表了
4.将下面的sql语句复制到数据库中并执行,向数据表edu_subject中插入数据
INSERT INTO `edu_subject` VALUES ('1233388958893465602','前端','0',0,'2020-02-28 21:50:17','2020-02-28 21:50:17'),('1233388959656828930','js','1233388958893465602',0,'2020-02-28 21:50:17','2020-02-28 21:50:17'),('1233388960105619457','vue','1233388958893465602',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388960550215681','jq','1233388958893465602',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388960822845441','后端','0',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388961263247362','java','1233388960822845441',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388961670094849','c++','1233388960822845441',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388962072748034','数据库','0',0,'2020-02-28 21:50:18','2020-02-28 21:50:18'),('1233388962391515137','mysql','1233388962072748034',0,'2020-02-28 21:50:18','2020-02-28 21:50:18');
5.执行后数据表中的数据如下
我们可以看出来,这里用了两级分类,字段parent_id的值是该分类的父分类id,当parent_id字段值为0时表示这是一个一级分类,它没有父分类
2.EasyExcel实现写操作
2.1引入依赖
1.在service_edu模块的pom.xml中添加如下代码
<dependencies>
<!-- easyexcel依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
2.EasyExcel是对Apache poi这个框架的封装,所以service_edu模块中也需要引入poi的依赖,但是呢,这个依赖在service模块就已经引入过了,所以这里不再需要重复引入
并且我们在总项目的pom.xml中规定了使用的是3.17版本的poi,老师说了:2.1.1版本的easyexcel必须对应3.17版本的poi,否则会有问题的!
2.2创建实体类
创建包excel,然后在该包下创建实体类DemoData并在类中编写代码
@Data
public class DemoData {
//设置excel表头名称
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String sname;
}
2.3实现写操作
1.在excel包下创建类TestEasyExcel并在类中编写代码
public class TestEasyExcel {
public static void main(String[] args) {
//实现excel的写操作
//1.设置写入的excel文件的路径和名称
String filename = "C:\\Users\\mxy\\Desktop\\mxy01.xlsx";
//2.调用easyexcel的方法实现写操作
EasyExcel
.write(filename, DemoData.class)
.sheet("学生列表")
.doWrite(getData());
}
//创建静态方法返回list集合
private static List<DemoData> getData() {
List<DemoData> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setSno(i);
data.setSname("lucy" + i);
list.add(data);
}
return list;
}
}
write(String pathName, Class head)
方法第一个参数是文件路径名称;第二个参数是实体类class.doWrite(List data)
方法参数是将要写入的数据,是一个列表- 文件流会自动关闭
2.在"main"上右键选择"Run ‘TestEasyExcel.main()’"执行main方法
3.可以看到桌面上生成了mxy01.xlsx文件,点开看看里面的数据
注意:这里红框圈起来的"学生列表"的来源是:第1步的截图中的第18行.sheet("学生列表")
3.EasyExcel实现读操作
3.1创建实体类
在excel包下创建实体类DemoData2:
@Data
public class DemoData2 {
//设置excel第一列对应的属性
@ExcelProperty(index = 0)
private Integer sno;
//设置excel第二列对应的属性
@ExcelProperty(index = 1)
private String sname;
}
3.2创建监听器
每次读取都是执行监听器中的方法,读操作实际上是在监听器中完成的,所以我们在excel包下创建读取操作的监听器ExcelListener,并使该类继承AnalysisEventListener,然后我们在类中编写代码:
public class ExcelListener extends AnalysisEventListener<DemoData2> {
//一行一行读取excel内容(从第二行开始读取,第一行是表头,该方法不会读取第一行)
@Override
public void invoke(DemoData2 data, AnalysisContext analysisContext) {
System.out.println("****" + data);
}
//读取表头内容
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:" + headMap);
}
//读取完成之后执行这个方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}
3.3最终的方法调用
1.在excel包下创建类TestEasyExcel2并在类中编写代码
public class TestEasyExcel2 {
public static void main(String[] args) {
//实现excel的写操作
//1.设置读取的excel文件的路径和名称
String filename = "C:\\Users\\mxy\\Desktop\\mxy01.xlsx";
//2.调用easyexcel的方法实现读操作
EasyExcel
.read(
filename,
DemoData2.class,
new ExcelListener())
.sheet()
.doRead();
}
}
- read方法的第一个参数是文件的路径名称
2.在"main"上右键选择"Run ‘TestEasyExcel2.main()’"执行main方法
3.去控制台看输出打印
4.添加课程分类(后端)
4.1引入easyexcel依赖
我们在"2.1引入依赖"已经给service_edu模块引入了该依赖
4.2生成代码
1.修改代码生成器中的部分代码:将表改为edu_subject
2.在"run"上右键,选择"Run ‘run()’"来执行代码生成器
3.可以看到我们成功生成了代码
4.我就是有强迫症,我要和老师的一样,所以我也将控制器EduSubjectController中的@RequestMapping(“/eduservice/edu-subject”)改为@RequestMapping(“/eduservice/subject”)
5.解决跨域问题,给EduSubjectController类添加注解@CrossOrigin
4.3在控制层编写代码
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//1.添加课程分类
//获取上传过来的文件,然后把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
subjectService.saveSubject(file);
return R.ok();
}
}
- 我们不能像"3.3最终的方法调用"那样直接将文件的路径名称在方法中定义,因为这个路径名称是我电脑路径中的,别人想要使用这个程序读取excel时,如果excel和我电脑中的路径名称不同,就没办法成功读取excel。正确做法是:获取上传过来的文件,然后把文件内容读取出来
- addSubject方法的参数:
MultipartFile file
:file就是上传过来的excel文件
4.4在业务层编写代码
1.先在EduSubjectService中编写接口
public interface EduSubjectService extends IService<EduSubject> {
//添加课程分类
void saveSubject(MultipartFile file);
}
2.在实现类EduSubjectServiceImpl中实现方法saveSubject
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file) {
}
}
4.5创建实体类
在entity包下创建包excel,然后在excel包下创建实体类SubjectData,注意:实体类中的属性需要和excel中的列对应上
@Data
public class SubjectData {
//设置excel第一列对应的属性
@ExcelProperty(index = 0)
private String oneSubjectName;
//设置excel第二列对应的属性
@ExcelProperty(index = 1)
private String twoSubjectName;
}
4.6回到业务层继续编写代码
实现类EduSubjectServiceImpl的完整代码
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file) {
try {
//1.得到文件输入流
InputStream in = file.getInputStream();
//2.调用方法进行读取
EasyExcel
.read(
in,
SubjectData.class,
new SubjectExcelListener())
.sheet()
.doRead();
} catch(Exception e) {
}
}
}
"3.3最终的方法调用"的第1步中我们说过,我们在"3.3最终的方法调用"中的read方法的第一个参数是文件路径名称。但我们在"4.3在控制层编写代码"说过:此时文件是通过上传得到的,所以这里的read方法的第一个参数是文件输入流而不再是文件路径名称
4.7创建监听器
4.7.1监听器部分代码
在eduservice包下创建包listener,然后在在listener包下创建监听器SubjectExcelListener,并使其继承AnalysisEventListener
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
public EduSubjectService subjectService;
public SubjectExcelListener() {}
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
//一行一行读取excel内容(从第二行开始读取,第一行是表头,该方法不会读取第一行)
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
}
//读取完成之后执行这个方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
4.7.2遇到的问题
1.我们以前创建的业务层实现类、控制层的控制器等等都是在类上添加一个注解使得可以将类交给Spring管理。但是在"4.6回到业务层继续编写代码"截图的第38行的new SubjectExcelListener()
,可以知道我们需要自己手动new监听器对象,所以不能将监听器交给Spring管理。可是不交给Spring管理会有一个问题:不能在监听器类SubjectExcelListener中使用注解@Autowired注入EduSubjectService对象(这个对象以后会在监听器用到)
解决办法:手动将EduSubjectService实例对象传过来("4.7.1监听器部分代码"的截图中的第10~14行)
2.对应的我们就需要修改EduSubjectController、EduSubjectService、EduSubjectServiceImpl中的部分代码,如下图所示(红框圈起来的都是添加上去的代码,其它地方不需要修改)
①EduSubjectController
②EduSubjectService
③EduSubjectServiceImpl
3.这样的话,实现类EduSubjectServiceImpl中new监听器对象时将EduSubjectService实例对象作为参数传给有参构造方法(上一张截图的第38行:new SubjectExcelListener(subjectService)
),这样的话就实现了手动将EduSubjectService实例对象注入到监听器(SubjectExcelListener)中
4.7.3继续编写监听器代码
完整的监听器代码如下:
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
public EduSubjectService subjectService;
public SubjectExcelListener() {}
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
//一行一行读取excel内容(从第二行开始读取,第一行是表头,该方法不会读取第一行)
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
//1.判断文件是否为空
if (subjectData == null) {
throw new GuliException(20001, "文件数据为空");
}
//2.一行一行读取,每次读取有两个值,第一个值是一级分类,第二个值是二级分类
//①添加一级分类
EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
if (existOneSubject == null) { //一级分类没有重复,进行添加
existOneSubject = new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());
subjectService.save(existOneSubject);
}
//②添加二级分类
//先获得一级分类的id
String pid = existOneSubject.getId();
//再添加二级分类
EduSubject existTwoSubject = this.existTwoSubject(subjectService,subjectData.getTwoSubjectName(), pid);
if (existTwoSubject == null) { //二级分类没有重复,进行添加
existTwoSubject = new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());
subjectService.save(existTwoSubject);
}
}
//判断一级分类是否重复
private EduSubject existOneSubject(EduSubjectService subjectService, String name) {
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title", name);
wrapper.eq("parent_id", "0");
EduSubject oneSubject = subjectService.getOne(wrapper);
return oneSubject;
}
//判断二级分类是否重复
private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title", name);
wrapper.eq("parent_id", pid);
EduSubject twoSubject = subjectService.getOne(wrapper);
return twoSubject;
}
//读取完成之后执行这个方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
- 为什么要判断一级、二级分类是否重复:因为肯定不能在数据表中有多条数据的title是"前端"吧;也不能在数据表中的"前端"子分类下有多条数据的title是"vue"吧
- 为什么取得一级分类id的语句是
existOneSubject.getId()
:判断一级分类是否重复时如果重复,那么就是已经有这个一级分类,所以可以用existOneSubject.getId()
取得一级分类的id;如果不重复,那么就是还没有这个一级分类,需要添加这个一级分类,添加成功后existOneSubject中就会回显id值(在"demo02-MybatisPlus"的"2.9添加操作"说过),所以可以用existOneSubject.getId()
取得一级分类的id。也就是说,无论一级分类是否重复,都可以用这行语句取得一级分类的id,所以说,取得一级分类id的语句是existOneSubject.getId()
- 记着给实体类EduSubject的属性gmtCreate、gmtModified添加注解@TableField以实现自动填充
4.8测试
1.在数据库中使用语句DELETE FROM edu_subject
删除表中的数据
2.我在桌面创建了名为mxy.xlsx的excel文件,并在里面填写了数据,如图所示
3.启动service_edu项目,在地址栏输入http://localhost:8001/swagger-ui.html进行访问
4.选中了桌面的mxy.xlsx文件后点击"Try it out!"进行测试
5.可以看到测试成功
5.添加课程分类(前端)
5.1添加课程分类路由
在src–>router–>index.js中添加路由
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'example' },
children: [
{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/edu/teacher/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'save',
name: '添加课程分类',
component: () => import('@/views/edu/teacher/save'),
meta: { title: '添加课程分类', icon: 'tree' }
}
]
},
5.2创建vue页面
既然刚刚已经添加了路由,那么接下来就需要创建和路由中路径对应的页面:
在src–>views–>edu目录下创建目录subject,然后在subject目录下创建list.vue、save.vue
5.3编写save.vue
1.我们控制层的EduSubjectController类中的addSubject方法的参数是MultipartFile file
,所以前端页面save.vue中就需要有一个上传组件
2.去element-ui中找一个合适的复制过来根据需求修改
3.老师给过我们代码了,不需要我们自己去找了
save.vue完整代码:
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="信息描述">
<el-tag type="info">excel模版说明</el-tag>
<el-tag>
<i class="el-icon-download"/>
<a :href="'/static/mxy.xlsx'">点击下载模版</a>
</el-tag>
</el-form-item>
<el-form-item label="选择Excel">
<el-upload
ref="upload"
:auto-upload="false"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:disabled="importBtnDisabled"
:limit="1"
:action="BASE_API+'/eduservice/subject/addSubject'"
name="file"
accept=".xls, .xlsx">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button
:loading="loading"
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload">上传到服务器</el-button>
</el-upload>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
importBtnDisabled: false, // 按钮是否禁用
loading: false
}
},
created() {
},
methods: {
//点击"上传到服务器"时,该方法内部上传文件到接口中
submitUpload() {
this.importBtnDisabled = true
this.loading = true
this.$refs.upload.submit()
},
//上传成功执行该方法
fileUploadSuccess() {
//1.提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
//2.跳转到课程分类列表(使用路由跳转)
},
//上传失败执行该方法
fileUploadError() {
//提示信息
this.loading = false
this.$message({
type: 'error',
message: '添加课程分类失败'
})
}
}
}
</script>
①第13~30行的<el-upload>标签是一个上传组件,其中:
ref="upload"
:组件的唯一标识,类似于html标签的id属性:auto-upload="false"
:关闭自动上传,这样的话必须点击"上传到服务器"才会上传。如果为true,那么选择好文件后就会自动上传到服务器:on-success="fileUploadSuccess"
:上传成功执行方法fileUploadSuccess:on-error="fileUploadError"
:上传失败执行方法fileUploadError:disabled="importBtnDisabled"
:按钮是否禁用:limit="1"
:限制每次只能传一个文件:action="BASE_API+'/eduservice/subject/addSubject'"
:后端接口的地址name="file"
:我们在前端学过,上传按钮的代码是:<input type="file" name="file"/>
。name="file"
就相当于以前上传按钮中的name="file"
accept=".xls, .xlsx"
:只能上传excel类型的文件,无法上传其他类型的文件@click="submitUpload"
:点击"上传到服务器"会执行submitUpload方法
②我们在"demo06-上传讲师头像"的"6.2在save.html添加上传组件"中说过:“我们以前发送请求都是使用ajax,但是ajax不能直接上传文件,所以我们需要用提交表单的方式来上传文件”。vue中使用表单提交文件的固定写法在第50~52行。其中this.$refs.upload.submit()
里面的upload对应着第14行的ref="upload"
,这行代码的底层实现是:document.getElementById("upload").submit()
③<a :href="'/static/mxy.xlsx'">点击下载模版</a>
:点击"点击下载模板"就会从/static/mxy.xlsx中下载模板,所以我们将模板mxy.xlsx放到本地的vue-admin-1010–>static中
5.4测试
1.在数据库中使用语句DELETE FROM edu_subject
删除表中的数据
2.在确保service_edu、nginx、前端项目都启动的情况下访问http://localhost:9528
3.在"添加课程分类"路由对应的save.vue页面选择好文件后点击"上传到服务器"
4.可以看到测试成功
6.显示课程分类(参考tree路由把前端整合出来)
1.我们是想让课程分类以树形的形式显示,刚好人家给的示例中Tree路由就是这样显示的
2.先看一下Tree路由
3.顺着/views/tree/index
找到views–>tree下的index.vue页面,将页面中的所有代码复制下来,粘贴到src–>views>edu–>subject–>list.vue
①第3行的<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
是一个输入框,是用来检索的
第5~12行的<el-tree>标签就是最终显示的树形结构,其中:
-
ref="tree2"
:是唯一标识,类似于html中标签的id属性 -
:data="data2"
:要显示的数据,数据是json格式,不需要我们自己遍历,框架已经封装好了,我们只需要将后端返回的json数据赋值给data2即可 -
:props="defaultProps"
:取到节点名称和子节点这个数据模型结合
:props="defaultProps"
表示:数据中为children的是子节点,为label的是节点名称 -
:filter-node-method="filterNode"
和methods: {...}
中定义的filterNode方法共同作用,目的是为了实现输入框中的检索功能 -
class="filter-tree"
和default-expand-all
:布局、样式
7.显示课程分类(后端)
7.1知道要给data2什么格式的数据
首先我们要知道,人家前端给我们做了封装,所以呢,显示的数据data2的格式必须要和人家的一模一样,否则人家框架肯定认不出来你的格式,进而框架就无法给你的数据进行遍历,所以说,数据模型中的data2的数据格式必须要和人家的一模一样,比如下面这样的格式,人家框架才可以认出来
[
{
id: 1,
label: '一级分类名称',
children: [
{
id: 4,
label: '二级分类名称'
},
{
id: 5,
label: '二级分类名称'
}
]
},
{
id: 2,
label: '一级分类名称',
children: [
{
id: 6,
label: '二级分类名称'
}
]
},
{
id: 3,
label: '一级分类名称',
children: [
{
id: 7,
label: '二级分类名称'
}
]
}
]
7.2创建实体类
1.我们已经知道了要给data2什么格式的数据了,可是这种格式的数据我们怎么才能构造出来呢:针对返回的数据格式创建对应的实体类
我们这个课程分类一共有两级,所以要创建两个实体类,分别对应一级分类和二级分类。那么我们在entity包下创建包subject,然后在subject包下创建实体类OneSubject、TwoSubject,并给这两个实体类都添加两个属性:id和title
//一级分类
@Data
public class OneSubject {
private String id;
private String title;
}
//二级分类
@Data
public class TwoSubject {
private String id;
private String title;
}
2.想办法能显示两个实体类之间的关系(一个一级分类有n个二级分类,n=1,2,3…)
在OneSubject实体类中添加下面这行代码就可以实现了:
//一个一级分类有n个二级分类,n=1,2,3...
private List<TwoSubject> children = new ArrayList<>();
7.3控制层
在控制器EduSubjectController中创建方法getAllSubject并在方法内编写代码
//2.课程分类列表(树形)
@GetMapping("getAllSubject")
public R getAllSubject() {
List<OneSubject> list = subjectService.getAllOneTwoSubject();
return R.ok().data("list", list);
}
为什么方法getAllOneTwoSubject返回值类型是List<OneSubject>:因为该方法返回的数据是多个OneSubject对象
7.4业务层
7.4.1业务层接口
在业务层的EduSubjectService接口中定义抽象方法getAllOneTwoSubject
//2.课程分类列表(树形)
List<OneSubject> getAllOneTwoSubject();
7.4.2业务层实现类
在实现类EduSubjectServiceImpl中重写刚刚在业务层接口中定义的方法getAllOneTwoSubject
//课程分类列表(树形)
@Override
public List<OneSubject> getAllOneTwoSubject() {
//1.查询所有一级分类(parentid = 0)
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id", "0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
//List<EduSubject> oneSubjectList = this.list(wrapperOne);
//2.查询所有二级分类(parentid != 0)
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id", "0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
//3.创建一个list集合,用于存储最终封装的数据
List<OneSubject> finalSubjectList = new ArrayList<>();
//4.封装一级分类
/**
* 在第1步查询出来的所有一级分类是List<EduSubject>类型的,我们需
* 要想办法将List<EduSubject>类型转换为List<OneSubject>类型
*/
//遍历oneSubjectList集合
for (int i = 0; i < oneSubjectList.size(); i++) {
//①得到oneSubjectList中的每个EduSubject对象
EduSubject eduSubject = oneSubjectList.get(i);
//②把eduSubject中我们需要的值获取出来,放到oneSubject中
OneSubject oneSubject = new OneSubject();
//oneSubject.setId(eduSubject.getId());
//oneSubject.setTitle(eduSubject.getTitle());
/**
* 上面这两个代码我们可以用工具类来实现
* 注意:工具类BeanUtils是Spring包里面的,别导错包了
*/
BeanUtils.copyProperties(eduSubject, oneSubject);
//③将每个oneSubject放到finalSubjectList中
finalSubjectList.add(oneSubject);
//④封装二级分类
//创建list集合封装每个一级分类下的所有二级分类
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
//遍历twoSubjectList集合
for (int m = 0; m < twoSubjectList.size(); m++) {
//④.1:得到twoSubjectList中的每个EduSubject对象
EduSubject tSubject = twoSubjectList.get(m);
//若二级分类的parent_id和一级分类的id相等,那么就进行封装
if (tSubject.getParentId().equals(eduSubject.getId())) {
//④.2:把tSubject中我们需要的值获取出来,放到twoSubject中
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(tSubject, twoSubject);
//④.3:将每个twoSubject放到twoFinalSubjectList中
twoFinalSubjectList.add(twoSubject);
}
}
//⑤把一级下面所有二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}
return finalSubjectList;
}
1.截图中的第58行:我们以前都是在控制层调用业务层的方法从而操作数据库,而这里需要在业务层就操作数据库,有两种方法:
- 在业务层调用持久层的方法:
baseMapper.selectList(wrapperOne)
- BaseMapper实例对象从哪来的:在"demo03-后台讲师管理模块"的"3.1准备工作"的第3步我们说过:**我们在业务层创建的实现类都会继承ServiceImpl实现类,而ServiceImpl实现类①实现了IService接口并且②在该实现类内部注入了BaseMapper实例对象用来调用BaseMapper中的方法以达到操作数据库的目的。**简单说就是:只要是业务层的实现类,就会继承ServiceImpl实现类,那么业务层的这些实现类内部就都有BaseMapper实例对象,从而可以操作数据库
- 在业务层调用业务层本身的方法:
this.list(wrapperOne)
2.截图中第87行:使用工具类BeanUtils时只能复制这两个实体类中属性名对应的,比如:EduSubject实体类中有属性title,OneSubject实体类中也有属性title,此时执行代码BeanUtils.copyProperties(eduSubject, oneSubject);
就可以从eduSubject对象中复制title的属性值给oneSubject对象的title属性。这也是为什么在"7.2创建实体类"中创建两个实体类时让属性名为id和title
7.5测试
1.启动service_edu项目,访问http://localhost:8001/swagger-ui.html
2.点击"Try it out!"进行测试
3.可以看到测试成功,成功返回了数据
8.显示课程分类(前端)
8.1定义api方法
在src–>api–>edu目录下创建subject.js并编写代码
import request from '@/utils/request'
export default {
//1.课程分类列表
getSubjectList() {
return request({
url: `/eduservice/subject/getAllSubject`,
//因为这里的url中没有参数,所以可以用飘号(`)也可以用引号
method: 'get',
})
}
}
8.2修改数据模型
1.将list.vue页面的数据模型中的data2改为空数组
2.我们在"7.2创建实体类"中创建一级分类和二级分类的实体类时都是用属性title来表示分类的标题,所以要将label: 'label'
中的'label'
改为'title'
顺带着,将下图中方框圈起来的label改为title
8.3引入subject.js文件
我们在"8.1定义api方法"中,在subject.js定义了getSubjectList方法,我们以后需要用这个方法,所以需要在list.vue页面引入subject.js文件
import subject from '@/api/edu/subject'
8.4methods: {…}中定义方法
//课程分类列表
getAllSubjectList() {
subject.getSubjectList()
.then(response => {
this.data2 = response.data.list
})
},
8.5created方法中进行调用
在list.vue页面中添加created方法,并在方法内部调用"8.4methods: {…}中定义方法"中定义的getAllSubjectList方法
created() {
this.getAllSubjectList()
},
8.6测试&完善
1.在启动service_edu、nginx、前端项目的情况下访问http://localhost:9528,点击"课程分类列表"列表,可以看到成功显示了课程分类
2.测试过程中会发现一个问题:我在输入框输入Java可以检索到,但是如果输入java就无法检索到
解决办法是:将methods: {...}
的filterNode方法中的return data.title.indexOf(value) !== -1
改为return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
,表示将检索框中的内容和分类的标题都转换为小写再做比较
8.7路由跳转
在"5.3编写save.vue"给出的代码中的第63行,如果添加课程分类成功会跳转到课程分类列表,我们这里来完善一下
this.$router.push({path:'/subject/list'})
8.8再次测试
1.在数据库中使用语句DELETE FROM edu_subject
删除表中的数据
2.在确保service_edu、nginx、前端项目都启动的情况下访问http://localhost:9528
3.在"添加课程分类"路由对应的save.vue页面选择好文件后点击"上传到服务器"
4.可以看到测试成功:
①显示添加成功的信息
②然后自动跳转到"课程分类列表"路由
③去数据库中可以看到确实插入了数据