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()
        }
      }

  

posted @ 2021-03-26 21:14  记录人生  阅读(236)  评论(0)    收藏  举报