32.课程大纲列表功能(前后端)
课程大纲列表实现如下图所示:

从上图中可以看出,我们需要实现:
①章节和小节的CRUD,并在前端中以树形结构显示,和课程分类列表类似。
②点击上一步根据课程id进行数据回显,并对课程信息进行修改。
一、章节的CRUD

1、后端实现
①创建两个实体类ChapterVo和VideoVo,分别表示章节和小节,其中章节包含小节集合
@Data
public class ChapterVo {
private String id;
private String title;
//表示小节
private List<VideoVo> children = new ArrayList<>();
}
@Data
public class VideoVo {
private String id;
private String title;
}
②在EduChapterController中编写代码
@RestController
@RequestMapping("/eduservice/edu-chapter")
@CrossOrigin
public class EduChapterController {
@Autowired
private EduChapterService chapterService;
//存储大纲列表
@GetMapping("getChapterVideo/{courseId}")
public R getChapterVideo(@PathVariable String courseId) {
List<ChapterVo> list = chapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("allChapterVideo", list);
}
//添加章节
@PostMapping("addChapter")
public R addChapter(@RequestBody EduChapter eduChapter){
chapterService.save(eduChapter);
return R.ok();
}
//根据章节id查询
@GetMapping("getChapterInfo/{chapterId}")
public R getChapterInfo(@PathVariable String chapterId){
EduChapter eduChapter = chapterService.getById(chapterId);
return R.ok().data("chapter",eduChapter);
}
//修改章节
@PostMapping("updateChapter")
public R updateChapter(@RequestBody EduChapter eduChapter){
chapterService.updateById(eduChapter);
return R.ok();
}
//删除的方法
@DeleteMapping("{chapterId}")
public R deleteChapter(@PathVariable String chapterId) {
boolean flag = chapterService.deleteChapter(chapterId);
if (flag) {
return R.ok();
} else {
return R.error();
}
}
}
注意:getChapterVideo方法返回的是章节集合,包含了小节
③在EduChapterServiceImpl中实现getChapterVideoByCourseId和deleteChapter方法:
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
@Autowired
private EduVideoService videoService;//注入小节的service
//课程大纲列表,根据课程id进行查询
@Override
public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
//1 根据课程id查询课程所有的章节
QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
wrapperChapter.eq("course_id",courseId);
List<EduChapter> eduChaptersList = baseMapper.selectList(wrapperChapter);
//2 根据课程id查询课程所有的小节
QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
wrapperChapter.eq("course_id",courseId);
List<EduVideo> eduVideoList = videoService.list(wrapperVideo);
//创建list集合,用于最终封装数据
List<ChapterVo> finalList = new ArrayList<>();
//3 遍历查询章节list集合进行封装
//遍历查询章节list集合
for (int i = 0; i < eduChaptersList.size(); i++) {
//每个章节
EduChapter eduChapter = eduChaptersList.get(i);
//eduChapter对象值复制到CharterVo里面
ChapterVo chapterVo = new ChapterVo();
BeanUtils.copyProperties(eduChapter,chapterVo);
//把chapterVo放到最终list集合
finalList.add(chapterVo);
//创建集合,用于封装章节的小节
List<VideoVo> videoList = new ArrayList<>();
//4 遍历查询小节list集合,进行封装
for (int m = 0; m < eduVideoList.size(); m++) {
//得到每个小节
EduVideo eduVideo = eduVideoList.get(m);
//判断:小节里面chapter和章节的id是否一样
if(eduVideo.getChapterId().equals(eduChapter.getId())){
//进行封装
VideoVo videoVo = new VideoVo();
BeanUtils.copyProperties(eduVideo,videoVo);
//放到小节封装的集合
videoList.add(videoVo);
}
}
//把封装之后小节的list集合,放到章节对象里面
chapterVo.setChildren((videoList));
}
return finalList;
}
//删除章节的方法
@Override
public boolean deleteChapter(String chapterId) {
//根据章节id查询小节表,如果查询到数据,则不进行删除
QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
wrapper.eq("chapter_id",chapterId);
int count = videoService.count(wrapper);
//判断
if(count>0) {//查询出小节,不删除
throw new GuliException(20001,"不能删除");
} else { //查询不到数据,进行删除
int result = baseMapper.deleteById(chapterId);
return result>0;
}
}
}
注意:章节删除需要判断是否存在小节,不存在小节我们才进行删除
2、前端实现
①在api/edu/chapter.js中添加调用后端接口方法
import request from '@/utils/request'
export default {
// 1 根据课程id获取章节和小节数据列表
getAllChapterVideo(courseId){
return request({
url: '/eduservice/edu-chapter/getChapterVideo/'+courseId,
method: 'get',
})
},
//2 添加章节
addChapter(chapter){
return request({
url: '/eduservice/edu-chapter/addChapter/',
method: 'post',
data:chapter
})
},
// 3 根据id查询章节
getChapter(chapterId){
return request({
url: '/eduservice/edu-chapter/getChapterInfo/'+chapterId,
method: 'get'
})
},
//4 修改章节
updateChapter(chapter){
return request({
url: '/eduservice/edu-chapter/updateChapter/',
method: 'post',
data:chapter
})
},
//5 删除章节
deleteChapter(chapterId){
return request({
url: '/eduservice/edu-chapter/' + chapterId,
method: 'delete'
})
}
}
②在chapter.vue中编写章节的CRUD页面实现
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
<!-- 章节 -->
<ul class="chanpterList">
<li
v-for="chapter in chapterVideoList"
:key="chapter.id">
<p>
{{ chapter.title }}
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>{{ video.title }}
<span class="acts">
<el-button style="" type="text" @click="openEditVideo(video.id)">编辑</el-button>
<el-button type="text" @click="removeVideo(video.id)">删除</el-button>
</span>
</p>
</li>
</ul>
</li>
</ul>
<div>
<el-button @click="previous">上一步</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
</div>
<!-- 添加和修改章节表单弹框 -->
<el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
<el-form :model="chapter" label-width="120px">
<el-form-item label="章节标题">
<el-input v-model="chapter.title"/>
</el-form-item>
<el-form-item label="章节排序">
<el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogChapterFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default {
data() {
return {
saveBtnDisabled: false, // 保存按钮是否禁用
courseId:'',//课程id
chapterVideoList:[],
chapter:{//封装章节数据
title: '',
sort: 0
},
dialogChapterFormVisible:false,//章节弹框
}
},
created() {
//获取路由的id值,判断路由有参数且为id参数
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//根据课程id查询章节和小节
this.getChapterVideo()
}
},
methods: {
//==================================章节操作==========================================
//删除章节
removeChapter(chapterId){
this.$confirm('此操作将删除章节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//调用删除的方法
chapter.deleteChapter(chapterId)
.then(response=>{
//提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//刷新页面
this.getChapterVideo()
})
})
},
//修改章节弹框做数据回显
openEditChapter(chapterId){
//弹框
this.dialogChapterFormVisible = true
//调用接口
chapter.getChapter(chapterId)
.then(response=>{
this.chapter = response.data.chapter
})
},
//弹出添加章节页面
openChapterDialog(){
//弹框
this.dialogChapterFormVisible=true
//表单数据清空
this.chapter.title = ''
this.chapter.sort = 0
},
//添加章节
addChapter(){
//设置课程id到chapter对象里面
this.chapter.courseId = this.courseId
chapter.addChapter(this.chapter)
.then(response=>{
//关闭弹框
this.dialogChapterFormVisible = false
//提示信息
this.$message({
type: 'success',
message: '添加章节成功!'
});
//刷新页面
this.getChapterVideo()
})
},
//修改章节的方法
updateChapter(){
chapter.updateChapter(this.chapter)
.then(response=>{
//关闭弹框
this.dialogChapterFormVisible = false
//提示信息
this.$message({
type: 'success',
message: '修改章节成功!'
});
//刷新页面
this.getChapterVideo()
})
},
saveOrUpdate(){
if(!this.chapter.id) {
this.addChapter()
} else {
this.updateChapter()
}
},
getChapterVideo(){
chapter.getAllChapterVideo(this.courseId)
.then(response=>{
this.chapterVideoList = response.data.allChapterVideo
})
},
previous() {
console.log('previous')
this.$router.push({ path: '/course/info/'+this.courseId })
},
next() {
console.log('next')
this.$router.push({ path: '/course/publish/'+this.courseId })
}
}
}
</script>
<style scoped>
.chanpterList{
position: relative;
list-style: none;
margin: 0;
padding: 0;
}
.chanpterList li{
position: relative;
}
.chanpterList p{
float: left;
font-size: 20px;
margin: 10px 0;
padding: 10px;
height: 70px;
line-height: 50px;
width: 100%;
border: 1px solid #DDD;
}
.chanpterList .acts {
float: right;
font-size: 14px;
}
.videoList{
padding-left: 50px;
}
.videoList p{
float: left;
font-size: 14px;
margin: 10px 0;
padding: 10px;
height: 50px;
line-height: 30px;
width: 100%;
border: 1px dotted #DDD;
}
</style>
首先进入该页面chapter.vue,会判断路由上的参数课程id,根据课程id查询出所有的章节小节;还有添加章节(需要对表单数据进行清空),修改章节(需要先根据章节id做数据回显),删除章节等功能。
二、小节的CRUD

小节的上传视频功能在下一章中实现
1、后端实现
在EduVideoController中编写代码
@RestController
@RequestMapping("/eduservice/edu-video")
@CrossOrigin
public class EduVideoController {
@Autowired
private EduVideoService videoService;
//添加小节
@PostMapping("addVideo")
public R addVideo(@RequestBody EduVideo eduVideo){
videoService.save(eduVideo);
return R.ok();
}
//删除小节
// TODO 后面这个方法需要完善,删除小节时候,同时把里面视频删除
@DeleteMapping("{id}")
public R deleteVideo(@PathVariable String id){
videoService.removeById(id);
return R.ok();
}
//修改章节
@PostMapping("updateVideo")
public R updateVideo(@RequestBody EduVideo eduVideo){
videoService.updateById(eduVideo);
return R.ok();
}
//根据章节id查询
@GetMapping("getVideoInfo/{videoId}")
public R getVideoInfo(@PathVariable String videoId){
EduVideo eduVideo = videoService.getById(videoId);
return R.ok().data("video",eduVideo);
}
}
2、前端实现
①在api/edu/video.js中添加调用后端接口方法
import request from '@/utils/request'
export default {
//添加小节
addVideo(video){
return request({
url: '/eduservice/edu-video/addVideo/',
method: 'post',
data:video
})
},
//删除小节
deleteVideo(id){
return request({
url: '/eduservice/edu-video/' + id,
method: 'delete'
})
},
//修改小节
updateVideo(video){
return request({
url: '/eduservice/edu-video/updateVideo/',
method: 'post',
data:video
})
},
//根据id查询小节
getVideo(id){
return request({
url: '/eduservice/edu-video/getVideoInfo/'+id,
method: 'get'
})
}
}
②在chapter.vue中编写小节的CRUD页面实现
在小节后面添加小节的添加,修改,删除按钮
<p>
{{ chapter.title }}
<span class="acts">
<el-button style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
<el-button style="" type="text" @click="openEditChapter(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
</span>
</p>
添加小节弹框
<!-- 添加和修改小节表单 -->
<el-dialog :visible.sync="dialogVideoFormVisible" title="添加小节">
<el-form :model="video" label-width="120px">
<el-form-item label="小节标题">
<el-input v-model="video.title"/>
</el-form-item>
<el-form-item label="小节排序">
<el-input-number v-model="video.sort" :min="0" controls-position="right"/>
</el-form-item>
<el-form-item label="是否免费">
<el-radio-group v-model="video.free">
<el-radio :label="true">免费</el-radio>
<el-radio :label="false">默认</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传视频">
<!-- TODO -->
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVideoFormVisible = false">取 消</el-button>
<el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
</div>
</el-dialog>
添加小节相关的默认参数
data() {
return {
。。。
video:{
title: '',
sort: 0,
free: 0,
videoSourceId: ''
},
saveVideoBtnDisabled:false,// 保存按钮是否禁用
dialogVideoFormVisible:false
}
}
添加小节的CRUD操作
//==================================小节操作==========================================
//修改小节弹框做数据回显
openEditVideo(id){
//弹框
this.dialogVideoFormVisible = true
//调用接口
video.getVideo(id)
.then(response=>{
this.video = response.data.video
})
},
//修改小节
updateVideo(){
video.updateVideo(this.video)
.then(response=>{
//关闭弹框
this.dialogVideoFormVisible = false
//提示信息
this.$message({
type: 'success',
message: '修改小节成功!'
});
//刷新页面
this.getChapterVideo()
})
},
//删除小节
removeVideo(id){
this.$confirm('此操作将删除小节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//调用删除的方法
video.deleteVideo(id)
.then(response=>{
//提示信息
this.$message({
type: 'success',
message: '删除小节成功!'
});
//刷新页面
this.getChapterVideo()
})
})
},
//添加小节弹框
openVideo(chapterId){
//弹框
this.dialogVideoFormVisible = true
//设置章节id
this.video.chapterId = chapterId
//表单数据清空
this.video.title = ''
this.video.sort = 0
this.video.videoSourceId = ''
this.video.free = 0
},
//添加小节
addVideo(){
this.video.courseId = this.courseId
video.addVideo(this.video)
.then(response=>{
//关闭弹框
this.dialogVideoFormVisible = false
//提示信息
this.$message({
type: 'success',
message: '添加小节成功!'
});
//刷新页面
this.getChapterVideo()
})
},
saveOrUpdateVideo(){
if(!this.video.id) {
this.addVideo()
} else {
this.updateVideo()
}
},
三、修改课程基本信息
在大纲列表页面点击上一步跳转到添加课程基本信息,此时应为课程信息的修改,我们可以通过路由传回课程id进行数据回显并修改课程信息。
1、后端实现
在EduVideoController中添加代码
//根据课程id查询课程基本信息
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId){
CourseInfoVo courseInfoVo = courseService.getCourseInfo(courseId);
return R.ok().data("courseInfoVo",courseInfoVo);
}
//修改课程信息
@PostMapping("updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
courseService.updateCourseInfo(courseInfoVo);
return R.ok();
}
由于添加课程信息页面包含了课程表和课程简介表中的数据,因此我们需要在EduCourseServiceImpl类中自己实现:
@Override
public CourseInfoVo getCourseInfo(String courseId) {
//1 查询课程表
EduCourse eduCourse = baseMapper.selectById(courseId);
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(eduCourse,courseInfoVo);
//2 查询课程描述表
EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
courseInfoVo.setDescription(courseDescription.getDescription());
return courseInfoVo;
}
//修改课程信息
@Override
public void updateCourseInfo(CourseInfoVo courseInfoVo) {
//1 修改课程表
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
int update = baseMapper.updateById(eduCourse);
if(update==0){
throw new GuliException(20001,"修改课程信息失败");
}
//2 修改描述表
EduCourseDescription description = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,description);
courseDescriptionService.updateById(description);
}
2、前端实现
①在api/edu/course.js中添加调用后端接口方法
//根据课程id查询课程基本信息
getCourseInfoById(courseId){
return request({
url: `/eduservice/edu-course/getCourseInfo/${courseId}`,
method: 'get',
})
},
//修改课程信息
updateCourseInfo(courseInfo){
return request({
url: `/eduservice/edu-course/updateCourseInfo`,
method: 'post',
data:courseInfo
})
}
②在info.vue中添加及修改对应代码
created() {
this.init()
},
methods: {
init() {
//获取路由的id值,判断路由有参数且为id参数
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//调用根据id查询课程方法
this.getInfo()
} else {
//清空表单
this.courseInfo = {}
//初始化一级分类
this.getOneSubject()
}
},
//根据课程id获取信息
getInfo(){
course.getCourseInfoById(this.courseId)
.then(response=>{
//在courseInfo课程基本信息中,包含一级分类id和二级分类id
this.courseInfo = response.data.courseInfoVo
//1 查询出所有的分类,包含一级和二级
subject.getSubjectList()
.then(response=>{
//2 获取所有一级分类
this.subjectOneList = response.data.list
//3 把所有的一级分类进行遍历,
for (let i = 0; i < this.subjectOneList.length; i++) {
//获取每个一级分类
var oneSubject = this.subjectOneList[i]
//比较当前courseInfo里面的一级分类id和所有的一级分类id
if(this.courseInfo.subjectParentId == oneSubject.id){
//获取一级分类的所有二级分类
this.subjectTwoList = oneSubject.children
}
}
})
//初始化所有讲师
this.getListTeacher()
})
},
//修改课程
updateCourse(){
course.updateCourseInfo(this.courseInfo)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '修改课程信息成功!'
});
//跳转到第二步
this.$router.push({ path: '/course/chapter/' + this.courseId})
})
},
saveOrUpdate() {
//判断添加还是修改
if(!this.courseInfo.id){
//添加
this.addCourse()
}else {
//修改
this.updateCourse()
}
}
浙公网安备 33010602011771号