任务:
-
添加课程前端实现
-
课程分类列表显示(树形)
-
课程管理模块需求
-
添加课程基本信息功能
1. 课程分类管理实现
-
修改路由;
-
添加路由对应的vue页面;
save.vue页面进行文件上传:将excel文件上传到数据库中。 用到element-ui上传组件。
-
测试
2. 课程列表功能(树形显示)
2.2 前端
- 添加路由
- subject.js调用后端接口
- list.vue
<template>
<div class="app-container">
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
<el-tree
ref="tree2"
:data="data2"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>
</div>
</template>
<script>
import subject from '@/api/edu/subject'
export default {
data() {
return {
filterText: '',
data2: [],
defaultProps: {
children: 'children',
label: 'title'
}
}
},
watch: {
filterText(val) {
this.$refs.tree2.filter(val)
}
},
created(){
this.getAllSubjectList()
},
methods: {
getAllSubjectList() {
subject.getSubjectList()
.then(response => {
this.data2 = response.data.list
})
.error(error => {
})
},
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
}
}
</script>
重要代码:
这里复制的时候,把watch方法复制到methods里面去了,导致界面出现问题,排查浪费了不少时间
2.1 后端
要点在于返回特定格式数据。
- 建立2个实体类:分别表示一级分类和二级分类
- 建立2个实体类的关系
3. controller
4. ServiceImpl(重点)
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file, EduSubjectService eduSubjectService) {
try{
//得到文件的输入流
InputStream in = file.getInputStream();
//调用方法进行读取
EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(eduSubjectService)).sheet().doRead();
}catch (Exception e){
e.printStackTrace();
}
}
//课程分类列表(树形)
@Override
public List<OneSubject> getAllOneTwoSubject() {
//1. 从edu_subject表中查询出所有的一级分类:parent_id = '0'
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id", '0');
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
System.out.println("一级目录:"+oneSubjectList.size());
//2.edu_subject表中查询出所有的二级分类: parent_id != '0'
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id", '0');
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
//3.创建集合,用来返回最终的数据
List<OneSubject> finalSubjectList = new ArrayList<>();
/******************************** 4.封装一级分类 ***************************/
//将 oneSubjectList(即List<EduSubject>) -----> 转换成List<OneSubject>
for(int i = 0; i < oneSubjectList.size(); i++){
//得到每个oneSubjectList对象
EduSubject eduSubject = oneSubjectList.get(i);
//将eduSubject的值获取出来,放到OneSubject对象里面
OneSubject oneSubject = new OneSubject();
//oneSubject.setId(eduSubject.getId());
//oneSubject.setTitle(eduSubject.getId());
/** 用spring中的工具类进行复制**/
BeanUtils.copyProperties(eduSubject, oneSubject);
/********************************** 5.封装二级分类 **********************/
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
for (int j = 0; j < twoSubjectList.size(); j++) {
//获取每个二级分类
EduSubject subject = twoSubjectList.get(j);
if(subject.getParentId().equals(oneSubject.getId())){
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(subject, twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
//把所有的二级分类放到一级分类中
oneSubject.setChildren(twoFinalSubjectList);
//添加到List<OneSubject>中
finalSubjectList.add(oneSubject);
}
return finalSubjectList;
}
}
重要步骤:
注意方法返回值为List< OneSubject >
测试:
测试:
3. 课程管理模块概述
3.1 课程管理需求
3.2 课程管理涉及到的表
3.3 注意事项
4 课程添加模块
4.1 后端
- 自动生成Service,Controller,Entity。
- 创建Vo类,用于封装表单提交的数据—因为表单中包含几种对应的实体类。
- Controller中添加课程。
- Service进行实际的课程信息第实际添加::1. 课程表添加信息;2. 课程描述表添加信息。
Bug: 添加信息后,课程表和课程描述表信息相互独立,没有联系。
解决: 将课程描述表的id设置为课程表的id
具体做法:
1. 修改主键生成策略:
2. 修改Service
4.2 前端
1. 添加路由
- 添加课程的页面: info.vue,chapter.vue,publish.vue
- js调用后台接口
- publish.vue中调用接口
bug: 添加课程后,紧接着跳转到添加章节页面,根据课程id添加章节,所以要把课程id传过去,所以在添加课程的后台接口中要返回课程id。
下拉列表显示所有讲师
前端调用后端接口,前端用Element-ui实现,后端查询所有讲师接口前面已经写好。
二级联动添加二级课程分类
用下拉列表实现。
-
第一次进入页面,显示所有一级列表,二级列表为空。
-
当选中某一个一级列表时,显示一级列表对应的二级列表。
实现:后台在做一级分类和二级分类时已经提供了1个接口,可以返回所有的一级分类,而返回的一级分类的实体类中包含一个成员:二级分类,所以我们这里可以不用写接口,直接调用之前写好的接口就可以。
已经写好的接口:
info.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 所属分类 TODO -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类" @change="subjectLevelOneChanged">
<el-option
v-for="subject in subjectOneList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="二级分类">
<el-option
v-for="subject in subjectTwoList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
</el-form-item>
<!-- 课程讲师 TODO -->
<!-- 课程讲师 -->
<el-form-item label="课程讲师">
<el-select
v-model="courseInfo.teacherId"
placeholder="请选择">
<el-option
v-for="teacher in teacherList"
:key="teacher.id"
:label="teacher.name"
:value="teacher.id"/>
</el-select>
</el-form-item>
<el-form-item label="总课时">
<el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
</el-form-item>
<!-- 课程简介 TODO -->
<el-form-item label="课程简介">
<el-input v-model="courseInfo.description" placeholder=""/>
</el-form-item>
<!-- 课程封面 TODO -->
<el-form-item label="课程价格">
<el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存并下一步</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
export default {
data() {
return {
saveBtnDisabled: false,
courseInfo:{
title: '',
subjectId:'',//二级分类id
subjectParentId:'',//一级分类id
teacherId: '',
lessonNum: 0,
description: '',
cover: '',
price: 0
},
//封装所有讲师数据
teacherList:[],
//一级分类列表
subjectOneList:[],
//二级分类列表
subjectTwoList:[]
}
},
created() {
//初始化所有讲师
this.getListTeacher()
//初始化一级分类
this.getOneSubject()
},
methods: {
//点击某个一级分类,会触发change时间,会显示对应的二级分类
subjectLevelOneChanged(value){
//value为一级分类的id值,事件发生时,会自动传过来id值,不同自己在上面中写个参数传递id值
//alert(value)
//遍历所有的一级分类,如果选中的id=一级id,直接获取到当前一级分类的子分类(二级分类列表)
for(var i = 0; i < this.subjectOneList.length; i++){
var oneSubject = this.subjectOneList[i]
if(oneSubject.id === value){
//从一级分类中获取所有的二级分类
this.subjectTwoList = oneSubject.children
}
}
},
//查询所有的一级分类
getOneSubject(){
console.log("dfsfds")
subject.getSubjectList()
.then(response => {
this.subjectOneList = response.data.list
})
},
saveOrUpdate() {
course.addCourseInfo(this.courseInfo)
.then(response =>{
//提示添加成功
this.$message({
type: 'success',
message: '添加课程信息成功!'
})
//跳转到添加章节页面
this.$router.push({path:'/course/chapter/' + response.data.courseId})
})
this.$router.push({ path: '/course/chapter/1' })
},
//查询所有讲师
getListTeacher(){
course.getListTeacher()
.then(response =>{
this.teacherList = response.data.item
})
}
}
}
</script>
- 在info.vue中加入下拉列表:
- 重要代码
测试:
bug:比如选择了前端开发,然后选Vue;再选择后端开发,Vue还在,应该清除。
原因:Vue双向绑定设置了之后,以后不会清空;
解决方法:每次选择一级列表触发事件方法时,在事件方法里面清空二级事件的id值。
课程封面上传
上传到阿里云OSS,前面上传图像时接口已经写好,这里直接用。这里主要是修改前端。
(完)