谷粒学院16万字笔记+1600张配图(七)——课程分类管理

项目源码与所需资料
链接: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.可以看到测试成功:

①显示添加成功的信息

在这里插入图片描述

②然后自动跳转到"课程分类列表"路由

在这里插入图片描述

③去数据库中可以看到确实插入了数据

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/maxiangyu_/article/details/127027187