谷粒学院笔记

301 阅读3分钟

谷粒学院项目

项目后台设计---课程管理

本模块涉及6张数据库表:

edu_course:                课程表:存储课程基本信息
edu_course_description:    课程简介表:存储课程简介信息
edu_chapter:               课程章节表:存储课程章节信息
edu_video:                 课程小节表:存储章节里面小节信息
edu_teacher:               讲师表
edu_subject:               分类表

课程相关表的关系:

捕获.PNG

一.添加课程基本信息--后端实现

1.使用代码生成器生成课程相关代码

捕获.PNG

2.细节问题

捕获.PNG

3.创建vo类,用于封装表单提交数据

package com.atguigu.eduservice.entity.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.math.BigDecimal;

@Data
public class CourseInfoVo {

    @ApiModelProperty(value = "课程ID")
    private String id;
    @ApiModelProperty(value = "课程讲师ID")
    private String teacherId;
    @ApiModelProperty(value = "课程专业ID")
    private String subjectId;
    @ApiModelProperty(value = "课程标题")
    private String title;
    @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    // 价格可以精确到一个更准确的值
    private BigDecimal price;
    @ApiModelProperty(value = "总课时")
    private Integer lessonNum;
    @ApiModelProperty(value = "课程封面图片路径")
    private String cover;
    @ApiModelProperty(value = "课程简介")
    private String description;

}

4.编写controller和service部分

controller:

package com.atguigu.eduservice.controller;


import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.vo.CourseInfoVo;
import com.atguigu.eduservice.service.EduCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 课程 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@RestController
@RequestMapping("/eduservice/edu-course")
@CrossOrigin
public class EduCourseController {
    @Autowired
    private EduCourseService courseService;

    // 添加课程基本信息的方法
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
        // 返回添加之后课程id,为了后面添加大纲使用
        String id = courseService.saveCourseInfo(courseInfoVo);
        return R.ok().data("CourseId", id);
    }
}

Service:

package com.atguigu.eduservice.service.impl;

import com.atguigu.eduservice.entity.EduCourse;
import com.atguigu.eduservice.entity.EduCourseDescription;
import com.atguigu.eduservice.entity.vo.CourseInfoVo;
import com.atguigu.eduservice.mapper.EduCourseMapper;
import com.atguigu.eduservice.service.EduCourseDescriptionService;
import com.atguigu.eduservice.service.EduCourseService;
import com.atguigu.servicebase.exceptionhandler.GuliException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 课程 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

    // 课程简介表的service对象
    @Autowired
    private EduCourseDescriptionService eduCourseDescriptionService;

    // 添加课程基本信息的方法
    @Override
    public String saveCourseInfo(CourseInfoVo courseInfoVo) {
        // 1.向课程表添加课程基本信息
        // CourseInfoVo转换成eduCourse对象
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int insert = baseMapper.insert(eduCourse);
        if (insert <= 0) {
            // 添加失败
            throw new GuliException(20001, "添加课程信息失败");
        }
        // 添加成功
        // 得到添加课程成功以后的课程id
        String cid = eduCourse.getId();
        // 2.向课程简介表添加课程简介
        // 创建课程描述对象
        EduCourseDescription courseDescription = new EduCourseDescription();
        // 手动设置描述id就是课程id
        courseDescription.setId(cid);
        // 往对象中赋值
        courseDescription.setDescription(courseInfoVo.getDescription());
        // 往描述表中添加数据
        eduCourseDescriptionService.save(courseDescription);
        return cid;
    }
}

二.添加课程基本信息--前端实现

1.添加课程管理路由

捕获.PNG

2.创建调用后端的api接口

捕获.PNG

3.info.vue编写

(1)把讲师用下拉列表显示

捕获.PNG

捕获.PNG

捕获.PNG

(2)把课程分类用下拉列表显示,分类做成二级联动效果

捕获.PNG

捕获.PNG

捕获.PNG

(3)课程封面上传

捕获.PNG

捕获.PNG

(4)优化简介
1.整合tiny-mce组件
2.配置html变量
3.引入js脚本
4.页面中使用文本编辑器组件
(5)最终代码
<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="1" process-status="wait" align-center style="marginbottom: 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-input>
                </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>
               <!-- 课程简介-->
                <el-form-item label="课程简介">
                    <tinymce :height="300" v-model="courseInfo.description"/>
                </el-form-item>
                <!-- 课程封面 TODO -->
                <!-- 课程封面-->
                <el-form-item label="课程封面">
                     <el-upload 
                        :show-file-list="false" 
                        :on-success="handleAvatarSuccess" 
                        :before-upload="beforeAvatarUpload" 
                        :action="BASE_API+'/eduoss/fileoss'" 
                        class="avatar-uploader">
                        <img :src="courseInfo.cover">
                    </el-upload>
                </el-form-item>

                <el-form-item label="课程价格">
                    <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="请填写课程价格"/>
                </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'
import Tinymce from '@/components/Tinymce' // 引入组件
export default{
    // 声明组件
    components: { Tinymce },
    data(){
        return{
            saveBtnDisabled:false,
            courseInfo:{  //前端向后端提交的表单对象
                title: '',
                subjectId: '', // 二级分类id
                subjectParentId:'', // 一级分类id
                teacherId: '',
                lessonNum: 0,
                description: '',
                cover: '/static/01.jpg',
                price: 0
            },
            BASE_API: process.env.BASE_API, // 接口API地址
            teacherList:[],   // 封装所有讲师的数据
            subjectOneList:[], // 封装所有一级分类
            subjectTwoList:[]  // 封装所有二级分类
        }
    },
    created(){
        // 初始化所有讲师
        this.getListTeacher()
        // 初始化一级分类
        this.getListOnesubject()
    },
    methods:{
        // 上传封面成功调用的方法
        handleAvatarSuccess(response, file){
            this.$message({
                type: 'success',
                message: '上传成功'
            })
            // 给往后端传的对象赋值
            this.courseInfo.cover = response.data.url
        },
        // 上传封面之前调用的方法--设置上传文件的格式
        beforeAvatarUpload(file){
            const isJPG = file.type === 'image/jpeg'
            const isLt2M = file.size / 1024 / 1024 < 2
            if (!isJPG) {
                this.$message.error('上传头像图片只能是 JPG 格式!')
            }
            if (!isLt2M) {
                this.$message.error('上传头像图片大小不能超过 2MB!')
            }
            return isJPG && isLt2M
        },
        // 当点击某个一级分类时,调用这个方法
        subjectLevelOneChanged(value){
            // value就是一级分类的id值,框架帮我们做了封装
            // 遍历所有一级分类
            for(let i = 0; i < this.subjectOneList.length; i++){
                if(this.subjectOneList[i].id === value){
                    // 如果当前遍历到的一级分类的id值与点击的一级分类的id值相同
                    // 添加二级分类
                    this.subjectTwoList = this.subjectOneList[i].children
                    // 把二级分类的id值清空
                    this.courseInfo.subjectId = ''
                }
            }
        },
        // 查询所有讲师
        getListTeacher(){
          course.getListTeacher()
            .then(response => {
                this.teacherList = response.data.items
            })  
        },
         // 查询所有一级分类
        getListOnesubject(){
            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})
            })
        }
    }
}
</script>
<style scoped>
.tinymce-container {
    line-height: 29px;
}
</style>

三:课程大纲列表--后端实现

1.创建两个实体类,章节和小节,在章节实体类使用list表示小节

捕获.PNG

捕获.PNG

2.编写controller和service

package com.atguigu.eduservice.controller;


import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.chapter.ChapterVo;
import com.atguigu.eduservice.service.EduChapterService;
import com.atguigu.eduservice.service.EduCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 * 课程 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@RestController
@RequestMapping("/eduservice/chapter")
public class EduChapterController {
    @Autowired
    private EduChapterService eduChapterService;

    // 课程大纲列表,根据课程id进行查询
    @GetMapping("getChapterVide/{courseId}")
    public R getChapterVide(@PathVariable String courseId) {
        List<ChapterVo> list = eduChapterService.getChapterVideoByCourseId(courseId);
        return R.ok().data("allChapterVideo", list);
    }
}
package com.atguigu.eduservice.service.impl;

import com.atguigu.eduservice.entity.EduChapter;
import com.atguigu.eduservice.entity.EduVideo;
import com.atguigu.eduservice.entity.chapter.ChapterVo;
import com.atguigu.eduservice.entity.chapter.VideoVo;
import com.atguigu.eduservice.mapper.EduChapterMapper;
import com.atguigu.eduservice.service.EduChapterService;
import com.atguigu.eduservice.service.EduVideoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    @Autowired
    private EduVideoService eduVideoService;

    @Override
    public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
        // 1.根据课程id查询出所有章节数据
        // Select * from edu_chapter Where course_id = courseId;
        QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
        wrapperChapter.eq("course_id", courseId);
        // 得到所有章节数据
        List<EduChapter> chapterList = baseMapper.selectList(wrapperChapter);

        // 2.根据课程id查询出所有小节数据
        // Select * from edu_video Where course_id = courseId;
        QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
        wrapperVideo.eq("course_id", courseId);
        // 得到所有小节数据
        List<EduVideo> videoList = eduVideoService.list(wrapperVideo);
        // 3.遍历查询章节list集合进行封装
        List<ChapterVo> list = new ArrayList<>();
        for (EduChapter eduChapter : chapterList) {
            // 将eduChapter转化为ChapterVo
            ChapterVo chapterVo = new ChapterVo();
            BeanUtils.copyProperties(eduChapter, chapterVo);
            list.add(chapterVo);

            // 4.封装小节
            for (EduVideo eduVideo : videoList) {
                if (eduVideo.getCourseId().equals(eduChapter.getCourseId())) {
                    // 可以封装
                    VideoVo videoVo = new VideoVo();
                    BeanUtils.copyProperties(eduVideo, videoVo);
                    chapterVo.getChildren().add(videoVo);
                }
            }
        }
        return list;
    }
}

四:课程大纲列表--前端实现

(1)定义调用后端api的方法

import request from '@/utils/request'


export default{
    // 前端调用后端api的方法 

    // 1.根据课程id过去章节和小节数据列表
    getAllChapterVideo(courseId){
        return request({
            url: `/eduservice/chapter/getChapterVide/${courseId}`,
            method: 'get'
          })
    }
}

(2)前端chapter.vue页面初步实现

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text">添加章节</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 }}
                            </p>
                        </li>
                    </ul>
                </li>
            </ul>

            <div>
                <el-button @click="previous">上一步</el-button>
                <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
            </div>
    </div>
</template>

<script>
import chapter from '@/api/edu/chapter'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[]
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        //根据课程id过去章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

五:修改课程信息--后端实现

(1)定义controller

package com.atguigu.eduservice.controller;


import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.vo.CourseInfoVo;
import com.atguigu.eduservice.service.EduCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 课程 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@RestController
@RequestMapping("/eduservice/edu-course")
@CrossOrigin
public class EduCourseController {
    @Autowired
    private EduCourseService courseService;

    // 添加课程基本信息的方法
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
        // 返回添加之后课程id,为了后面添加大纲使用
        String id = courseService.saveCourseInfo(courseInfoVo);
        return R.ok().data("CourseId", id);
    }

    // 根据课程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();
    }
}

(2)定义service

package com.atguigu.eduservice.service.impl;

import com.atguigu.eduservice.entity.EduCourse;
import com.atguigu.eduservice.entity.EduCourseDescription;
import com.atguigu.eduservice.entity.vo.CourseInfoVo;
import com.atguigu.eduservice.mapper.EduCourseMapper;
import com.atguigu.eduservice.service.EduCourseDescriptionService;
import com.atguigu.eduservice.service.EduCourseService;
import com.atguigu.servicebase.exceptionhandler.GuliException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 课程 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

    // 课程简介表的service对象
    @Autowired
    private EduCourseDescriptionService eduCourseDescriptionService;

    // 添加课程基本信息的方法
    @Override
    public String saveCourseInfo(CourseInfoVo courseInfoVo) {
        // 1.向课程表添加课程基本信息
        // CourseInfoVo转换成eduCourse对象
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int insert = baseMapper.insert(eduCourse);
        if (insert <= 0) {
            // 添加失败
            throw new GuliException(20001, "添加课程信息失败");
        }
        // 添加成功
        // 得到添加课程成功以后的课程id
        String cid = eduCourse.getId();
        // 2.向课程简介表添加课程简介
        // 创建课程描述对象
        EduCourseDescription courseDescription = new EduCourseDescription();
        // 手动设置描述id就是课程id
        courseDescription.setId(cid);
        // 往对象中赋值
        courseDescription.setDescription(courseInfoVo.getDescription());
        // 往描述表中添加数据
        eduCourseDescriptionService.save(courseDescription);
        return cid;
    }

    @Override
    public CourseInfoVo getCourseInfo(String courseId) {
        // 1.先查询课程表
        EduCourse eduCourse = baseMapper.selectById(courseId);
        // 2.查询描述表
        EduCourseDescription eduCourseDescription = eduCourseDescriptionService.getById(courseId);
        // 3.拼接数据
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        BeanUtils.copyProperties(eduCourse, courseInfoVo);
        BeanUtils.copyProperties(eduCourseDescription, courseInfoVo);
        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 eduCourseDescription = new EduCourseDescription();
        BeanUtils.copyProperties(courseInfoVo, eduCourseDescription);
        eduCourseDescriptionService.updateById(eduCourseDescription);
    }
}

六:修改课程信息--前端实现

(1)定义调用后端api的接口

捕获.PNG

(2)修改chapter.vue页面跳转路径

捕获.PNG

(3)在info.vue页面实现数据回显

捕获.PNG

捕获.PNG

捕获.PNG

403: 1.跨域  2.路径写错了

(4)最终实现

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="1" process-status="wait" align-center style="marginbottom: 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-input>
                </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>
               <!-- 课程简介-->
                <el-form-item label="课程简介">
                    <tinymce :height="300" v-model="courseInfo.description"/>
                </el-form-item>
                <!-- 课程封面 TODO -->
                <!-- 课程封面-->
                <el-form-item label="课程封面">
                     <el-upload 
                        :show-file-list="false" 
                        :on-success="handleAvatarSuccess" 
                        :before-upload="beforeAvatarUpload" 
                        :action="BASE_API+'/eduoss/fileoss'" 
                        class="avatar-uploader">
                        <img :src="courseInfo.cover">
                    </el-upload>
                </el-form-item>

                <el-form-item label="课程价格">
                    <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="请填写课程价格"/>
                </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'
import Tinymce from '@/components/Tinymce' // 引入组件
export default{
    // 声明组件
    components: { Tinymce },
    data(){
        return{
            saveBtnDisabled:false,
            courseInfo:{  //前端向后端提交的表单对象
                title: '',
                subjectId: '', // 二级分类id
                subjectParentId:'', // 一级分类id
                teacherId: '',
                lessonNum: 0,
                description: '',
                cover: '/static/01.jpg',
                price: 0
            },
            courseId:'',
            BASE_API: process.env.BASE_API, // 接口API地址
            teacherList:[],   // 封装所有讲师的数据
            subjectOneList:[], // 封装所有一级分类
            subjectTwoList:[]  // 封装所有二级分类
        }
    },
    created(){
        // 获取路由中的课程id值
        if(this.$route.params && this.$route.params.id){
            // 说明路径中有id值,我们做修改操作
            this.courseId = this.$route.params.id
            // 调用根据id查询的方法
            this.getInfo()
        } else {
            // 说明路径中没有id值,我们做添加操作
            // 初始化所有讲师
            this.getListTeacher()
            // 初始化一级分类
            this.getListOnesubject()
        }
    },
    watch: { // 监听
        $route(to, from) { // 路由变化的方式,当路由发生变化,这个方法就会执行
            this.init()
        }
    },
    methods:{
        // 清空相关数据
        init(){
            this.courseInfo = {}
            this.courseInfo.cover = '/static/01.jpg'
        },
        // 根据课程id查询
        getInfo(){
            course.getCourseInfoId(this.courseId)   
                .then(response =>{
                    this.courseInfo = response.data.courseInfoVo
                    // 初始化分类列表
                    // 1.查询出所有分类
                    subject.getSubjectList()
                        .then(response =>{
                            // 2.获取所有一级分类
                            this.subjectOneList = response.data.list
                            for(let i = 0; i < this.subjectOneList.length; i++){
                                // 3.遍历所有一级分类
                                if(this.subjectOneList[i].id === this.courseInfo.subjectParentId){
                                    // 当前遍历到的一级分类等于当前课程所属的一级分类
                                    this.subjectTwoList = this.subjectOneList[i].children
                                }
                            }
                    })
                    // 初始化所有讲师
                    this.getListTeacher()
                })
        },
        // 上传封面成功调用的方法
        handleAvatarSuccess(response, file){
            this.$message({
                type: 'success',
                message: '上传成功'
            })
            // 给往后端传的对象赋值
            this.courseInfo.cover = response.data.url
        },
        // 上传封面之前调用的方法--设置上传文件的格式
        beforeAvatarUpload(file){
            const isJPG = file.type === 'image/jpeg'
            const isLt2M = file.size / 1024 / 1024 < 2
            if (!isJPG) {
                this.$message.error('上传头像图片只能是 JPG 格式!')
            }
            if (!isLt2M) {
                this.$message.error('上传头像图片大小不能超过 2MB!')
            }
            return isJPG && isLt2M
        },
        // 当点击某个一级分类时,调用这个方法
        subjectLevelOneChanged(value){
            // value就是一级分类的id值,框架帮我们做了封装
            // 遍历所有一级分类
            for(let i = 0; i < this.subjectOneList.length; i++){
                if(this.subjectOneList[i].id === value){
                    // 如果当前遍历到的一级分类的id值与点击的一级分类的id值相同
                    // 添加二级分类
                    this.subjectTwoList = this.subjectOneList[i].children
                    // 把二级分类的id值清空
                    this.courseInfo.subjectId = ''
                }
            }
        },
        // 查询所有讲师
        getListTeacher(){
          course.getListTeacher()
            .then(response => {
                this.teacherList = response.data.items
            })  
        },
         // 查询所有一级分类
        getListOnesubject(){
            subject.getSubjectList() 
                .then(response =>{
                    this.subjectOneList = response.data.list
                })
        },
        // 添加课程
        addCourse(){
            // 调用方法,往后端发送数据
            course.addCourseInfo(this.courseInfo)
                .then(response => {
                    // 提示信息
                     this.$message({
                        type: 'success',
                        message: '添加课程信息成功'
                    });
                    // 跳转到第二步
                    this.$router.push({path:'/course/chapter/'+response.data.CourseId})
            })
        },
        // 修改课程
        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()
           }
        }
    }
}
</script>
<style scoped>
.tinymce-container {
    line-height: 29px;
}
</style>

七: 章节/小节--添加,修改,删除

1:增加添加章节按钮

<el-button type="text">添加章节</el-button>

2:点击添加章节按钮,弹出提示框

捕获.PNG

捕获.PNG

3:开发章节后端接口--添加、修改、删除

捕获.PNG

捕获.PNG

4:修改前端页面(chapter.vue)

(1)添加调用后端接口的api

捕获.PNG

(2)实现添加章节功能

捕获.PNG

出现这个,说明后端代码出现问题了

捕获.PNG

捕获.PNG

捕获.PNG

捕获.PNG

(3)实现章节修改功能

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text" @click="dialogChapter">添加章节</el-button>
            <!-- 章节 -->
            <ul class="chanpterList">
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    <p>
                        {{ chapter.title }}

                        <span class="acts">
                            <el-button style="" type="text" @click="openEditChapter(chapter.id)">编辑</el-button>
                            <el-button type="text">删除</el-button>
                         </span>
                    </p>
                    <!-- 视频 -->
                    <ul class="chanpterList videoList">
                        <li v-for="video in chapter.children" :key="video.id">
                            <p>
                                {{ video.title }}
                            </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'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[],
            dialogChapterFormVisible:false,  // 表示章节弹框的值
            chapter:{ // 封装章节数据
                courseId:'',
                title:'',
                sort:0
            },
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        // 点击修改按钮后,调用的方法
        openEditChapter(chapterId){
            // 弹出修改框---和保存框是一个
            this.dialogChapterFormVisible = true
            // 根据chapterid查询出数据,进行回显
            chapter.getChapterById(chapterId)  
                .then(response => {
                    this.chapter = response.data.eduChapter
                })
        },
        // 点击添加章节按钮后,执行的方法
        dialogChapter(){
            this.dialogChapterFormVisible = true
            // 章节数据清空
            this.chapter = {}
        },
        // 添加章节
        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(){ 
            // 根据章节里面是否有id来进行判断是修改还是添加
            if(this.chapter.courseId == ''){
                // 没有课程id
                // 添加
                this.addChapter()
            } else {
                // 修改
                this.updateChapter()
            }
        },
        //根据课程id获取章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

(4)实现章节删除功能

TODO 需要判断是否有小节

(5)实现小节添加功能--后端接口实现

```
package com.atguigu.eduservice.controller;


import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.EduVideo;
import com.atguigu.eduservice.service.EduVideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 课程视频 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2022-10-05
 */
@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin
public class EduVideoController {
    @Autowired
    EduVideoService eduVideoService;

    // 添加小节
    @PostMapping("addVideo")
    public R addVideo(@RequestBody EduVideo eduVideo) {
        eduVideoService.save(eduVideo);
        return R.ok();
    }

    // 根据小节id查询小节
    @GetMapping("getVideoById/{videoId}")
    public R getVideoById(@PathVariable String videoId) {
        EduVideo eduVideo = eduVideoService.getById(videoId);
        return R.ok().data("eduVideo", eduVideo);
    }

    // 根据小节id删除小节
    // TODO 后面这个方法需要完善:在删除小节时,同时要把里面的视频也删除
    @DeleteMapping("deleteVideo/{videoId}")
    public R deleteVideo(@PathVariable String videoId) {
        eduVideoService.removeById(videoId);
        return R.ok();
    }

    // 修改小节
    @PostMapping("updateVideo")
    public R updateVideo(@RequestBody EduVideo eduVideo) {
        eduVideoService.updateById(eduVideo);
        return R.ok();
    }
}
```

(5)实现小节添加功能--前端页面实现

定义api接口

import request from '@/utils/request'


export default{
    // 前端调用后端api的方法 

    // 添加小节的方法
    addVideo(video){
        return request({
            url: `/eduservice/video/addVideo`,
            method: 'post',
            data: video
        })
    },

    // 根据小节id删除小节
    deleteVideo(videoId){
        return request({
            url: `/eduservice/video/deleteVideo/${videoId}`,
            method: 'delete',
        })
    },

    // 修改小节
    updateVideo(video){
        return request({
            url: `/eduservice/video/updateVideo`,
            method: 'post',
            data: video
        })
    },

    // 根据小节id查询小节
    getVideoById(videoId){
        return request({
            url: `/eduservice/video/getVideoById/${videoId}`,
            method: 'get',
        })
    }
}

chapter.vue页面的完善

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text" @click="dialogChapter">添加章节</el-button>
            <!-- 章节 -->
            <ul class="chanpterList">
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    <p>
                        {{ chapter.title }}

                        <span class="acts">
                            <el-button 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>
                    <!-- 视频 -->
                    <ul class="chanpterList videoList">
                        <li v-for="video in chapter.children" :key="video.id">
                            <p>
                                {{ video.title }}
                            </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>

            <!-- 添加和修改课时表单 -->
            <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>
    </div>
</template>

<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[],
            dialogChapterFormVisible:false,  // 表示章节弹框的值
            dialogVideoFormVisible:false,    // 表示小节弹框的值
            chapter:{ // 封装章节数据
                courseId:'',
                title:'',
                sort:0
            },
            video:{  // 封装小节数据
                chapterId:'',
                courseId:'',
                title:'',
                sort:0,
                free:0,
                videoSourceId:''
            }
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        //**************************************针对小节的操作**********************************/
        // 点击添加小节,调用这个方法
        openVideo(chapterId){
            // 弹出框
            this.dialogVideoFormVisible = true
            // 给小节数据添加上章节id
            this.video.chapterId = chapterId
            // 小节数据清空
            this.video.title = ''
            this.video.sort = 0
            this.free = 0
        },
        // 添加小节的方法
        addVideo(){
            // 设置课程id到video对象当中去
            this.video.courseId = this.courseId
            // 调用api接口,往数据库中添加小节
            video.addVideo(this.video)
                .then(response => {
                    // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '操作成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 点击小节弹框的确定以后,要执行的方法
        saveOrUpdateVideo(){
            this.addVideo()
        },
        //**************************************针对章节的操作**********************************/
        // 点击删除按钮以后,调用的方法
        removeChapter(chapterId){
            // 删除提示框
            this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() =>{
                // 点击确定以后要执行的操作
                // 调用后端api接口,进行删除操作
                chapter.deleteChapter(chapterId)
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击修改按钮后,调用的方法
        openEditChapter(chapterId){
            // 弹出修改框---和保存框是一个
            this.dialogChapterFormVisible = true
            // 根据chapterid查询出数据,进行回显
            chapter.getChapterById(chapterId)  
                .then(response => {
                    this.chapter = response.data.eduChapter
                })
        },
        // 点击添加章节按钮后,执行的方法
        dialogChapter(){
            this.dialogChapterFormVisible = true
            // 章节数据清空
            this.chapter = {}
        },
        // 添加章节
        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(){ 
            // 根据章节里面是否有id来进行判断是修改还是添加
            if(this.chapter.courseId){
                // 没有课程id
                // 添加
                this.updateChapter()
            } else {
                // 修改
                this.addChapter()
            }
        },
        //根据课程id获取章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

(6)实现小节修改功能--前端页面实现

chapter.vue

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text" @click="dialogChapter">添加章节</el-button>
            <!-- 章节 -->
            <ul class="chanpterList">
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    <p>
                        {{ chapter.title }}

                        <span class="acts">
                            <el-button 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>
                    <!-- 视频 -->
                    <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="removeChapter(chapter.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>

            <!-- 添加和修改课时表单 -->
            <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>
    </div>
</template>

<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[],
            dialogChapterFormVisible:false,  // 表示章节弹框的值
            dialogVideoFormVisible:false,    // 表示小节弹框的值
            chapter:{ // 封装章节数据
                courseId:'',
                title:'',
                sort:0
            },
            video:{  // 封装小节数据
                chapterId:'',
                courseId:'',
                title:'',
                sort:0,
                free:0,
                videoSourceId:''
            }
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        //**************************************针对小节的操作**********************************/
        // 点击修改按钮后,调用的方法
        openEditVideo(videoId){
            // 弹出修改框 --- 和添加框是一个
            this.dialogVideoFormVisible = true
            // 根据小节id查询出数据,进行回显
            video.getVideoById(videoId)
                .then(response => {
                    this.video = response.data.eduVideo
                })
        },
        // 点击添加小节,调用这个方法
        openVideo(chapterId){
            // 弹出框
            this.dialogVideoFormVisible = true
            // 给小节数据添加上章节id
            this.video.chapterId = chapterId
            // 小节数据清空
            this.video.title = ''
            this.video.sort = 0
            this.free = 0
        },
        // 添加小节的方法
        addVideo(){
            alert("debug")
            // 设置课程id到video对象当中去
            this.video.courseId = this.courseId
            // 调用api接口,往数据库中添加小节
            video.addVideo(this.video)
                .then(response => {
                    // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '操作成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 修改小节的方法
        updateVideo(){
            video.updateVideo(this.video)   
                .then(response => {
                     // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '修改成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 点击小节弹框的确定以后,要执行的方法
        saveOrUpdateVideo(){
            // 根据章节id来判断是添加还是修改小节
            if(this.video.chapterId){
                // 有章节id,说明是修改小节
                this.updateVideo()
            } else {
                // 添加小节
                this.addVideo()
            }
        },
        //**************************************针对章节的操作**********************************/
        // 点击删除按钮以后,调用的方法
        removeChapter(chapterId){
            // 删除提示框
            this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() =>{
                // 点击确定以后要执行的操作
                // 调用后端api接口,进行删除操作
                chapter.deleteChapter(chapterId)
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击修改按钮后,调用的方法
        openEditChapter(chapterId){
            // 弹出修改框---和保存框是一个
            this.dialogChapterFormVisible = true
            // 根据chapterid查询出数据,进行回显
            chapter.getChapterById(chapterId)  
                .then(response => {
                    this.chapter = response.data.eduChapter
                })
        },
        // 点击添加章节按钮后,执行的方法
        dialogChapter(){
            this.dialogChapterFormVisible = true
            // 章节数据清空
            this.chapter = {}
        },
        // 添加章节
        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(){ 
            // 根据章节里面是否有课程id来进行判断是修改还是添加
            if(this.chapter.courseId){
                // 有课程id
                // 修改
                this.updateChapter()
            } else {
                // 添加
                this.addChapter()
            }
        },
        //根据课程id获取章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

(7)实现小节删除功能--前端页面实现

chapter.vue

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text" @click="dialogChapter">添加章节</el-button>
            <!-- 章节 -->
            <ul class="chanpterList">
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    <p>
                        {{ chapter.title }}

                        <span class="acts">
                            <el-button 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>
                    <!-- 视频 -->
                    <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>

            <!-- 添加和修改课时表单 -->
            <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>
    </div>
</template>

<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[],
            dialogChapterFormVisible:false,  // 表示章节弹框的值
            dialogVideoFormVisible:false,    // 表示小节弹框的值
            chapter:{ // 封装章节数据
                courseId:'',
                title:'',
                sort:0
            },
            video:{  // 封装小节数据
                chapterId:'',
                courseId:'',
                title:'',
                sort:0,
                free:0,
                videoSourceId:''
            }
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        //**************************************针对小节的操作**********************************/
        // 点击删除按钮后,调用的方法
        removeVideo(videoId){
            // 删除提示框
            this.$confirm('此操作将永久删除小节记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning' 
            }).then(() => {
                // 点确定以后执行的操作
                video.deleteVideo(videoId) 
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击修改按钮后,调用的方法
        openEditVideo(videoId){
            // 弹出修改框 --- 和添加框是一个
            this.dialogVideoFormVisible = true
            // 根据小节id查询出数据,进行回显
            video.getVideoById(videoId)
                .then(response => {
                    this.video = response.data.eduVideo
                })
        },
        // 点击添加小节,调用这个方法
        openVideo(chapterId){
            // 弹出框
            this.dialogVideoFormVisible = true
            // 给小节数据添加上章节id
            this.video.chapterId = chapterId
            // 小节数据清空
            this.video.title = ''
            this.video.sort = 0
            this.free = 0
        },
        // 添加小节的方法
        addVideo(){
            alert("debug")
            // 设置课程id到video对象当中去
            this.video.courseId = this.courseId
            // 调用api接口,往数据库中添加小节
            video.addVideo(this.video)
                .then(response => {
                    // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '操作成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 修改小节的方法
        updateVideo(){
            video.updateVideo(this.video)   
                .then(response => {
                     // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '修改成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 点击小节弹框的确定以后,要执行的方法
        saveOrUpdateVideo(){
            // 根据章节id来判断是添加还是修改小节
            if(this.video.chapterId){
                // 有章节id,说明是修改小节
                this.updateVideo()
            } else {
                // 添加小节
                this.addVideo()
            }
        },
        //**************************************针对章节的操作**********************************/
        // 点击删除按钮以后,调用的方法
        removeChapter(chapterId){
            // 删除提示框
            this.$confirm('此操作将永久删除章节记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() =>{
                // 点击确定以后要执行的操作
                // 调用后端api接口,进行删除操作
                chapter.deleteChapter(chapterId)
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击修改按钮后,调用的方法
        openEditChapter(chapterId){
            // 弹出修改框---和保存框是一个
            this.dialogChapterFormVisible = true
            // 根据chapterid查询出数据,进行回显
            chapter.getChapterById(chapterId)  
                .then(response => {
                    this.chapter = response.data.eduChapter
                })
        },
        // 点击添加章节按钮后,执行的方法
        dialogChapter(){
            this.dialogChapterFormVisible = true
            // 章节数据清空
            this.chapter = {}
        },
        // 添加章节
        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(){ 
            // 根据章节里面是否有课程id来进行判断是修改还是添加
            if(this.chapter.courseId){
                // 有课程id
                // 修改
                this.updateChapter()
            } else {
                // 添加
                this.addChapter()
            }
        },
        //根据课程id获取章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

(8)chapter.vue页面最终实现

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
            <el-steps :active="2" process-status="wait" align-center style="marginbottom: 40px;">
                <el-step title="填写课程基本信息"/>
                <el-step title="创建课程大纲"/>
                <el-step title="提交审核"/>
            </el-steps>

            <!-- 自己编写的列表显示功能
            <ul>
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    {{chapter.title}}
                    <ul>>
                        <li v-for="video in chapter.children" :key="video.id">
                            {{video.title}}
                        </li>
                    </ul>
                </li>
            </ul> -->
            <el-button type="text" @click="dialogChapter">添加章节</el-button>
            <!-- 章节 -->
            <ul class="chanpterList">
                <li v-for="chapter in chapterVideoList" :key="chapter.id">
                    <p>
                        {{ chapter.title }}

                        <span class="acts">
                            <el-button 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>
                    <!-- 视频 -->
                    <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>

            <!-- 添加和修改课时表单 -->
            <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>
    </div>
</template>

<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
    data(){
        return{
            saveBtnDisabled:false,
            courseId:'', // 课程id
            chapterVideoList:[],
            dialogChapterFormVisible:false,  // 表示章节弹框的值
            dialogVideoFormVisible:false,    // 表示小节弹框的值
            chapter:{ // 封装章节数据
                courseId:'',
                title:'',
                sort:0
            },
            video:{  // 封装小节数据
                chapterId:'',
                courseId:'',
                title:'',
                sort:0,
                free:0,
                videoSourceId:''
            }
        }
    },
    created(){
        // 获取到路由里面的课程id值
        if(this.$route.params && this.$route.params.id){
            this.courseId = this.$route.params.id
            this.getChapterVideo()
        }
    },
    methods:{
        //**************************************针对小节的操作**********************************/
        // 点击删除按钮后,调用的方法
        removeVideo(videoId){
            // 删除提示框
            this.$confirm('此操作将永久删除小节记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning' 
            }).then(() => {
                // 点确定以后执行的操作
                video.deleteVideo(videoId) 
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击编辑按钮后,调用的方法
        openEditVideo(videoId){
            // 弹出修改框 --- 和添加框是一个
            this.dialogVideoFormVisible = true
            // 根据小节id查询出数据,进行回显
            video.getVideoById(videoId)
                .then(response => {
                    this.video = response.data.eduVideo
                })
        },
        
        // 点击添加小节,调用这个方法
        openVideo(chapterId){
            // 弹出框
            this.dialogVideoFormVisible = true
            // 给小节数据添加上章节id
            this.video.chapterId = chapterId
            // 小节数据清空
            this.video.courseId = ''
            this.video.title = ''
            this.video.sort = 0
            this.free = 0
        },

        // 添加小节的方法
        addVideo(){
            // 设置课程id到video对象当中去
            this.video.courseId = this.courseId
            // 调用api接口,往数据库中添加小节
            video.addVideo(this.video)
                .then(response => {
                    // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '添加成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },

        // 修改小节的方法
        updateVideo(){
            video.updateVideo(this.video)   
                .then(response => {
                     // 关闭弹窗
                    this.dialogVideoFormVisible = false
                    // 提示成功信息
                    this.$message({
                        type: 'success',
                        message: '修改成功!'
                    });
                    // 刷新页面
                    this.getChapterVideo()
                })
        },
        // 点击小节弹框的确定以后,要执行的方法
        saveOrUpdateVideo(){
            // 根据小节里面是否有课程id来进行判断是修改还是添加
            if(this.video.courseId){
                // 有课程id 说明是修改小节
                this.updateVideo()
            } else {
                // 添加小节
                this.addVideo()
            }
        },
        //**************************************针对章节的操作**********************************/
        // 点击删除按钮以后,调用的方法
        removeChapter(chapterId){
            // 删除提示框
            this.$confirm('此操作将永久删除章节记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() =>{
                // 点击确定以后要执行的操作
                // 调用后端api接口,进行删除操作
                chapter.deleteChapter(chapterId)
                    .then(response => {
                         // 提示删除成功信息
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 返回页面  
                        // 根据课程id获取章节和小节数据列表
                        // 这里我们已经有了课程id了,所以不需要在重新赋值了
                        this.getChapterVideo() 
                    })
            })
        },
        // 点击修改按钮后,调用的方法
        openEditChapter(chapterId){
            // 弹出修改框---和保存框是一个
            this.dialogChapterFormVisible = true
            // 根据chapterid查询出数据,进行回显
            chapter.getChapterById(chapterId)  
                .then(response => {
                    this.chapter = response.data.eduChapter
                })
        },
        // 点击添加章节按钮后,执行的方法
        dialogChapter(){
            this.dialogChapterFormVisible = true
            // 章节数据清空
            this.chapter = {}
        },
        // 添加章节
        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(){ 
            // 根据章节里面是否有课程id来进行判断是修改还是添加
            if(this.chapter.courseId){
                // 有课程id
                // 修改
                this.updateChapter()
            } else {
                // 添加
                this.addChapter()
            }
        },
        //根据课程id获取章节和小节数据列表
        getChapterVideo(){
            chapter.getAllChapterVideo(this.courseId)
                .then(response =>{
                    this.chapterVideoList = response.data.allChapterVideo
                })
        },
        previous(){
            // 跳转到第一步
            this.$router.push({path:'/course/info/'+this.courseId})
        },
        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>

八: 课程信息确认

1.后端接口实现

我们要在课程信息确认页面显示的数据包括:课程名称、课程价格(edu_course)、课程分类(edu_subject)、课程简介(edu_course_description)、课程讲师(edu_teacher)。总共包含4张表,我们需要自己写sql语句实现

(1)自己编写sql语句

select ec.id, ec.price, ec.lesson_num as lessonNum, ec.title, ec.cover,
       et.name as teacherName, //注意,这里变量别名必须与返回的实体类中属性名一致
       es1.title as subjectLevelOne,
       es2.title as subjectLevelTwo
from edu_course ec left outer join edu_course_description ecd on ec.id = ecd.id
				   left outer join edu_teacher et on ec.teacher_id = et.id
				   left outer join edu_subject es1 on ec.subject_parent_id = es1.id
				   left outer join edu_subject es2 on ec.subject_id = es2.id
where ec.id = '?';

(2)整合后端接口

1.编写返回给前端的实体类

捕获.PNG

2.在mapper接口中定义实现复杂查询的方法

捕获.PNG

捕获.PNG

   3.实现controller、service方法
   

捕获.PNG

捕获.PNG

4.但是项目一启动,报错

捕获.PNG

原因:未编译.xml文件(maven加载的时候,把java文件夹里面.java类型文件进行编译,其他类型文件不会加载)

捕获.PNG

解决方案:
1.复制xml到target中去
2.把xml文件放到resource中去
3.通过配置来实现(推荐)
    (1)pom.xml
    

捕获.PNG

    (2)项目application中进行配置
    

捕获.PNG

(3)编写前端页面

<template>
    <div class="app-container">
        <h2 style="text-align: center;">发布新课程</h2>
        <el-steps :active="3" process-status="wait" align-center style="margin-bottom:40px;">
            <el-step title="填写课程基本信息"/>
            <el-step title="创建课程大纲"/>
            <el-step title="发布课程"/>
        </el-steps>
        <div class="ccInfo">
            <img :src="publishCourse.cover">
            <div class="main">
                <h2>{{ publishCourse.title }}</h2>
                <p class="gray"><span>共{{ publishCourse.lessonNum }}课时</span></p>
                <p><span>所属分类:{{ publishCourse.subjectLevelOne }} — {{publishCourse.subjectLevelTwo }}</span></p>
                <p>课程讲师:{{ publishCourse.teacherName }}</p>
                <h3 class="red">¥{{ publishCourse.price }}</h3>
            </div>
        </div>
        <div>
            <el-button @click="previous">返回修改</el-button>
            <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
        </div>
    </div>
</template>

<script>
import course from "@/api/edu/course"
export default {
data() {
    return {
        saveBtnDisabled: false, // 保存按钮是否禁用
        courseId:'', // 课程id
        publishCourse: {},
    }
},
created() {
    // 获取路由中课程的id值
    if(this.$route.params && this.$route.params.id){
        this.courseId = this.$route.params.id
        // 根据课程id值来查询显示数据
        this.getPublishCourse()
    }
},
methods: {
    // 根据课程id值来查询显示数据
    getPublishCourse(){
        course.getPublishCourseInfo(this.courseId) 
            .then(response => {
                this.publishCourse = response.data.publishCourse
            })
    },
    previous() {
        console.log('previous')
        this.$router.push({ path: '/course/chapter/1' })
    },
    publish() {
        console.log('publish')
        this.$router.push({ path: '/course/list' })
    }
}
}
</script>
<style scoped>
.ccInfo {
background: #f5f5f5;
padding: 20px;
overflow: hidden;
border: 1px dashed #DDD;
margin-bottom: 40px;
position: relative;
}
.ccInfo img {
background: #d6d6d6;
width: 500px;
height: 278px;
display: block;
float: left;
border: none;
}
.ccInfo .main {
    margin-left: 520px;
}
.ccInfo .main h2 {
font-size: 28px;
margin-bottom: 30px;
line-height: 1;
font-weight: normal;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main h3 {
left: 540px;
bottom: 20px;
line-height: 1;
font-size: 28px;
color: #d32f24;
font-weight: normal;
position: absolute;
}
</style>