谷粒学院项目笔记8——课程管理模块

  尚硅谷谷粒学院项目第八天内容之添加课程基本信息、整合文本编辑器、课程大纲管理(课程大纲列表显示、章节信息增删改、小节信息增删改)、课程信息确认(将课程信息添加到数据库)

添加富文本编辑器组件

把资料中的components里的文件夹复制到项目的components里,static里的复制到static里

在build/webpack.dev.conf.js中添加

      templateParameters: {

        BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory

      }

 在index.js中加入

    <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>

    <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js>0</script>

 报错,重启;爆红,不用管

 在info.vue中引入组件、声明组件

import Tinymce from '@/components/Tinymce';

components: { Tinymce }, //声明组件

 富文本编辑器组件的使用

        <!-- 课程简介-->

        <el-form-item label="课程简介">

            <tinymce :height="300" v-model="courseInfo.description"/>

        </el-form-item>

在页面的最后添加一个样式

<style scoped>

.tinymce-container {

  line-height: 29px;

}

</style>

效果

在后端中的courseInfo类中添加subjectParentId属性

  添加一个课程进行测试

注意前端传过去的courseInfo和后端接收的courseInfo的属性名要一样

在数据库里查看课程是否添加成功(课程表和课程描述表都应该添加进一条数据)

创建课程大纲后端实现

创建实体类Chapter和Video,用于向前端返回数据

entity/chapter/Chapter

@Data
public class Chapter {
    String id;
    String title;
    List<Video> children=new ArrayList<>();
}

entity/chapter/Video

@Data
public class Video {
    String id;
    String title;
}

controller

@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin
public class EduChapterController {

    @Autowired
    private EduChapterService chapterService;

    //根据courseId获取对应的章节和小节
    @GetMapping("getChapterVideo/{courseId}")
    public R getChapterVideo(@PathVariable String courseId){
        List<Chapter> list=chapterService.getChapterVideo(courseId);
        return R.ok().data("list",list);
    }
}

service

public interface EduChapterService extends IService<EduChapter> {
    
    List<Chapter> getChapterVideo(String courseId);
}

serviceImpl

@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    @Autowired
    private EduVideoService videoService;
    
    //根据courseId得到对应的章节和小节
    @Override
    public List<Chapter> getChapterVideo(String courseId) {
        ArrayList<Chapter> list = new ArrayList<>();

        QueryWrapper<EduChapter> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("course_id",courseId);
        List<EduChapter> eduChapterList = baseMapper.selectList(queryWrapper1);

        QueryWrapper<EduVideo> queryWrapper2 = new QueryWrapper<>();
        queryWrapper1.eq("course_id",courseId);
        List<EduVideo> eduVideoList = videoService.list(queryWrapper2);

        for(EduChapter eduChapter:eduChapterList){
            Chapter chapter = new Chapter();
            BeanUtils.copyProperties(eduChapter,chapter);

            for (EduVideo eduVideo:eduVideoList){
                if(eduChapter.getId().equals(eduVideo.getChapterId())){
                    Video video = new Video();
                    BeanUtils.copyProperties(eduVideo,video);
                    chapter.getChildren().add(video);
                }
            }
            list.add(chapter);
        }
        return list;
    }
}

swagger测试

创建课程大纲前端实现

chapter.js

import request from '@/utils/request'
export default{
    //根据courseId查询章节和小节
    getChapterVideo(courseId){
        return request({
            url:`eduservice/chapter/getChapterVideo/${courseId}`,
            method:'get'
        })
    }
}

router/index.js

      {
        path: 'chapter/:id',
        name: '编辑课程大纲',
        component: () => import('@/views/edu/course/chapter.vue'),
        meta: { title: '编辑课程大纲', noCache: true },
        hidden: true
      },

chapter.vue

<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 }}</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:[],
            chapter:{ //封装章节数据
                title: '',
                sort: 0
            },
            video: {
                title: '',
                sort: 0,
                free: 0,
                videoSourceId: ''
            },
            dialogChapterFormVisible:false,//章节弹框
            dialogVideoFormVisible:false //小节弹框
            
        }
    },
    created() {
        //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            //根据课程id查询章节和小节
            this.getChapterVideo()
        }
    },
    methods:{
        //根据课程id查询章节和小节
        getChapterVideo() {
            chapter.getChapterVideo(this.courseId)
                .then(response => {
                    this.chapterVideoList = response.data.list
                })
        },
        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>

测试

修改课程基本信息后端实现

controller

    //添加课程信息
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfo courseInfo){
        String id = courseService.addCourseInfo(courseInfo);
        return R.ok().data("courseId",id);
    }

    //根据courseId查询课程信息
    @GetMapping("getCourseInfo/{courseId}")
    public R getCourseInfo(@PathVariable String courseId){
        CourseInfo courseInfo=courseService.getCourseInfo(courseId);
        return R.ok().data("courseInfo",courseInfo);
    }

    //修改课程基本信息
    @PostMapping("updateCourseInfo")
    private R updateCourseInfo(@RequestBody CourseInfo courseInfo){
        courseService.updateCourseInfo(courseInfo);
        return R.ok();
    }

service

    CourseInfo getCourseInfo(String courseId);

    void updateCourseInfo(CourseInfo courseInfo);

serviceImpl

    //根据courseId查询课程信息
    @Override
    public CourseInfo getCourseInfo(String courseId) {
        CourseInfo courseInfo = new CourseInfo();
        EduCourse eduCourse = baseMapper.selectById(courseId);
        BeanUtils.copyProperties(eduCourse,courseInfo);

        EduCourseDescription description = courseDescriptionService.getById(courseId);
        courseInfo.setDescription(description.getDescription());
        return courseInfo;
    }

    //修改课程基本信息
    @Override
    public void updateCourseInfo(CourseInfo courseInfo) {
        EduCourse course = new EduCourse();
        BeanUtils.copyProperties(courseInfo,course);
        baseMapper.updateById(course);

        EduCourseDescription description = new EduCourseDescription();
        BeanUtils.copyProperties(courseInfo,description);
        courseDescriptionService.updateById(description);
    }

swagger测试

修改课程基本信息前端实现

info.vue

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="最终发布"/>
    </el-steps>

    <el-form label-width="120px">

        <el-form-item label="课程标题">
            <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
        </el-form-item>

        <!-- 所属分类 TODO -->
        <el-form-item label="课程分类">
            <el-select
                v-model="courseInfo.subjectParentId"
                placeholder="一级分类" @change="getTwoSort">

                <el-option
                    v-for="sort in oneSort"
                    :key="sort.id"
                    :label="sort.title"
                    :value="sort.id"/>

            </el-select>

            <!-- 二级分类 -->
            <el-select v-model="courseInfo.subjectId" placeholder="二级分类">
                <el-option 
                    v-for="sort in twoSort"
                    :key="sort.id"
                    :label="sort.title"
                    :value="sort.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>

        <!-- 课程封面-->
        <el-form-item label="课程封面">

            <el-upload
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeAvatarUpload"
                :action="BASE_API+'/ossservice/uploadAvatar'"
                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="免费课程请设置为0元"/> 元
        </el-form-item>

        <el-form-item>
            <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存并下一步</el-button>
        </el-form-item>
    </el-form>
  </div>
</template>
<script>
import Tinymce from '@/components/Tinymce';
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
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:[],//封装所有的讲师
            oneSort:[],//一级分类
            twoSort:[]//二级分类
        }   
    },
    created() {
         //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            this.getCourseInfo()
        }
            //初始化所有讲师
            this.queryAllTeacher()
            //初始化一级分类
            this.getOneSort()
        
        
    },
    methods:{
        //根据courseId获取课程信息,数据回显
        getCourseInfo(){
            course.getCourseInfo(this.courseId)
                .then(response=>{
                    this.courseInfo=response.data.courseInfo
                })
           
        },
        //上传封面成功调用的方法
        handleAvatarSuccess(res, file) {
            this.courseInfo.cover = res.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
        },

        //根据一级分类查询对应的二级分类
        getTwoSort(value){ //value是一级分类id
            this.courseInfo.subjectId=''
            for(var i=0;i<this.oneSort.length;i++){
                if(this.oneSort[i].id===value){
                    this.twoSort=this.oneSort[i].children
                }
            }
        },
        //查询所有的一级分类
        getOneSort(){
            this.courseInfo.subjectParentId=''
            subject.getAllSubjectSort()
                .then(response=>{
                    this.oneSort=response.data.list
                })
        },
        //查询所有的讲师
        queryAllTeacher() {
            course.queryAllTeacher()
                .then(response => {
                    this.teacherList = response.data.items
                })
        },
        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>

chapter.vue

<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 }}</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:[],
            chapter:{ //封装章节数据
                title: '',
                sort: 0
            },
            video: {
                title: '',
                sort: 0,
                free: 0,
                videoSourceId: ''
            },
            dialogChapterFormVisible:false,//章节弹框
            dialogVideoFormVisible:false //小节弹框
            
        }
    },
    created() {
        //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            //根据课程id查询章节和小节
            this.getChapterVideo()
        }
    },
    methods:{
        //根据课程id查询章节和小节
        getChapterVideo() {
            chapter.getChapterVideo(this.courseId)
                .then(response => {
                    this.chapterVideoList = response.data.list
                })
        },
        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>

此时,在创建课程大纲页面点击上一步,数据回显时,二级分类显示的是id值

 此时,twoSort=[],没法比较,所以直接把id值回显了

修改getCourseInfo()方法

 subject.getAllSubjectSort()

     .then(response=>{

         this.oneSort=response.data.list

         for(var i=0;i<this.oneSort.length;i++){

               if(this.oneSort[i].id==this.courseInfo.subjectParentId){

                     this.twoSort=this.oneSort[i].children

               }

          }

     })

 二级分类就能成功回显了

此时,在创建课程大纲页面点击上一步到 课程基本信息页面,再点击添加课程,发现数据依然回显

不能在created()里写清空数据的逻辑,因为相同页面的跳转,created()只会执行一次

添加路由监视器

    created() {
         //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            this.getCourseInfo()
        }else{
            this.init()
        }
    },

    watch:{
        $route(to,from){
           this.init()
        }
    },

    methods:{
        init(){
            this.courseInfo={
                cover: '/static/01.jpg'
            }
            //初始化所有讲师
            this.queryAllTeacher()
            //初始化一级分类
            this.getOneSort()
        },
    }

重启前端,测试

修改课程信息

info.vue

        //添加课程
        addCourseInfo(){
            course.addCourseInfo(this.courseInfo)
                .then(response => {
                    //提示
                    this.$message({
                        type: 'success',
                        message: '添加课程信息成功!'
                    });
                    //路由跳转
                    this.$router.push({path:'/course/chapter/'+response.data.courseId})
                })
        },
        //修改课程
        updateCourseInfo(){
            course.updateCourseInfo(this.courseInfo)
                .then(response=>{
                     //提示
                    this.$message({
                        type: 'success',
                        message: '修改课程信息成功!'
                    });
                    //路由跳转
                    this.$router.push({path:'/course/chapter/'+this.courseId})
                })
        },
        saveOrUpdate() {
           if(this.courseId){ //有id值,修改课程信息
             this.updateCourseInfo()
           }else{
            this.addCourseInfo()
           }
        }
    }

章节增删改查

后端

chapter.vue

<el-button type="text" @click="openChapterDialog()">添加章节</el-button>

controller

    //添加章节
    @PostMapping("addChapter")
    public R addChapter(@RequestBody EduChapter eduChapter){
        chapterService.save(eduChapter);
        return R.ok();
    }

    //根据chapter_id查询章节信息
    @GetMapping("queryChapter/{chapterId}")
    public R queryChapter(@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("deleteChapter/{chapterId}")
    public R deleteChapter(@PathVariable String chapterId){
        boolean b=chapterService.deleteChapter(chapterId);
        return R.ok();
    }

service

boolean deleteChapter(String chapterId);

serviceImpl

    @Override
    public boolean deleteChapter(String chapterId) {
        QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("chapter_id",chapterId);
        int i = videoService.count(queryWrapper);
        if (i==0){
            baseMapper.deleteById(chapterId);
            return true;
        }else {
            throw new GuliException(20000,"章节下面存在小节,章节删除失败");
        }
    }

前端

 chapter.js

    //添加章节
    addChapter(chapter){
        return request({
            url:`eduservice/chapter/addChapter`,
            method:'post',
            data:chapter
        })
    },

    //根据chapterId查询章节信息
    queryChapter(chapterId){
        return request({
            url:`eduservice/chapter/queryChapter/${chapterId}`,
            method:'get'
        })
    },

    //修改章节
    updateChapter(chapter){
        return request({
            url:`eduservice/chapter/updateChapter`,
            method:'post',
            data:chapter
        })
    },

    //删除章节
    deleteChapter(chapterId){
        return request({
            url:`eduservice/chapter/deleteChapter/${chapterId}`,
            method:'delete'
        })
    }

chapter.vue

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

                <span class="acts">
                    <el-button style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
                    <el-button style="" type="text" @click="openEditChatper(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">编辑</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'


export default {
    data() {
        return {
            saveBtnDisabled:false,
            courseId:'',//课程id
            chapterVideoList:[],
            chapter:{ //封装章节数据
                courseId:'',
                title: '',
                sort: 0
            },
            video: {
                title: '',
                sort: 0,
                free: 0,
                videoSourceId: ''
            },
            dialogChapterFormVisible:false,//章节弹框
            dialogVideoFormVisible:false //小节弹框
            
        }
    },
    created() {
        //获取路由的id值
        if(this.$route.params && this.$route.params.id) {
            this.courseId = this.$route.params.id
            //根据课程id查询章节和小节
            this.getChapterVideo()
        }
    },
    methods:{
        //根据课程id查询章节和小节
        getChapterVideo() {
            chapter.getChapterVideo(this.courseId)
                .then(response => {
                    this.chapterVideoList = response.data.list
                })
        },
        //添加章节
        addChapter(){
            this.chapter.courseId=this.courseId;
            chapter.addChapter(this.chapter)
             .then(response=>{
                    //提示
                    this.$message({
                        type: 'success',
                        message: '章节信息添加成功!'
                    });
                    this.chapter.title=''
                    this.chapter.sort=0
                    this.getChapterVideo() //刷新页面
             })
        },
        saveOrUpdate(){
            if(this.chapter.id){
                this.updateChapter()
            }else{
                this.addChapter()
            }
             this.dialogChapterFormVisible=false //关闭弹窗
        },
        //弹出添加章节的表单
        openChapterDialog(){
            this.dialogChapterFormVisible=true
        },
        //弹出章节的表单并数据回显
        openEditChatper(chapterId){
            this.dialogChapterFormVisible=true
            chapter.queryChapter(chapterId)
              .then(response=>{
                 this.chapter=response.data.chapter
              })
        },
        //修改章节
        updateChapter(){
            chapter.updateChapter(this.chapter)
                .then(response=>{
                    //提示
                    this.$message({
                        type: 'success',
                        message: '章节信息修改成功!'
                    });
                    this.chapter.title=''
                    this.chapter.sort=0
                    this.getChapterVideo() //刷新页面
                })
        },
        //删除章节
        removeChapter(chapterId){
            this.$confirm('此操作将删除章节, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击确定,执行then方法
                chapter.deleteChapter(chapterId)
                    .then(response =>{
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
            }) //点击取消,执行catch方法
        },
        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>

小节增删改查

后端

controller

@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin
public class EduVideoController {

    @Autowired
    private EduVideoService videoService;

    //添加小节
    @PostMapping("addVideo")
    public R addVideo(@RequestBody EduVideo eduVideo){
        videoService.save(eduVideo);
        return R.ok();
    }
    //删除小节
    @DeleteMapping("deleteVideo/{videoId}")
    public R deleteVideo(@PathVariable String videoId){
        videoService.removeById(videoId);
        return R.ok();
    }
    //查询小节
    @GetMapping("queryVideo/{videoId}")
    public R queryVideo(@PathVariable String videoId){
        EduVideo eduVideo = videoService.getById(videoId);
        return R.ok().data("video",eduVideo);
    }
    //修改小节
    @PostMapping("updateVideo")
    public R updateVideo(@RequestBody EduVideo eduVideo){
        videoService.updateById(eduVideo);
        return R.ok();
    }
}

前端

chapter.vue

添加小节弹框组件

 <!-- 添加和修改课时表单 -->
<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>
        //弹出添加小节的表单
        openVideo(chapterId){
            this.dialogVideoFormVisible=true
            this.video.chapterId=chapterId
            this.video.courseId=this.courseId
        },
        //添加小节
        addVideo(){
            video.addVideo(this.video)
             .then(response=>{
                    //提示
                    this.$message({
                        type: 'success',
                        message: '小节信息添加成功!'
                    });
                    this.dialogVideoFormVisible=false
                     this.video.title=''
                    this.video.sort=0
                    this.video.free=0
                    this.getChapterVideo() //刷新页面
             })
        },
         //弹出小节的表单并数据回显
        openEditVideo(videoId){
            this.dialogVideoFormVisible=true
            video.queryVideo(videoId)
              .then(response=>{
                 this.video=response.data.video
              })
        },
        //修改小节
        updateVideo(){
            video.updateVideo(this.video)
             .then(response=>{
                  //提示
                    this.$message({
                        type: 'success',
                        message: '小节信息修改成功!'
                    });
                    this.dialogVideoFormVisible=false
                     this.video.title=''
                    this.video.sort=0
                    this.video.free=0
                    this.video.id=''
                    this.getChapterVideo() //刷新页面
             })
        },
        saveOrUpdateVideo(){
            if(this.video.id){
                this.updateVideo()
            }else{
                this.addVideo()
            }
           
        },
         //删除小节
        removeVideo(videoId){
            this.$confirm('此操作将删除小节, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击确定,执行then方法
                video.deleteVideo(videoId)
                    .then(response =>{
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
            }) //点击取消,执行catch方法
        },

 

课程发布

后端

EduCourseController

   //课程最终发布
    @GetMapping("getCoursePublish/{courseId}")
    private R getCoursePublish(@PathVariable String courseId){
        CoursePublishVo coursePublishVo=courseService.getCoursePublish(courseId);
        return R.ok().data("coursePublish",coursePublishVo);
    }

service

 CoursePublishVo getCoursePublish(String courseId);

serviceImpl

    //获取CoursePublish(最终发布的课程信息)
    @Override
    public CoursePublishVo getCoursePublish(String courseId) {
        return baseMapper.getCoursePublish(courseId);
    }

 eduCourseMapper

CoursePublishVo getCoursePublish(String courseId);

EduCourseMapper.xml

    <select id="getCoursePublish"   resultType="com.jiabei.eduservice.vo.CoursePublishVo">
        SELECT
            ec.id,
            ec.title,
            ec.cover,
            ec.lesson_num AS lessonNum,
            ec.price,
            s1.title AS subjectLevelOne,
            s2.title AS subjectLevelTwo,
            t.name AS teacherName
        FROM
            edu_course ec
                LEFT JOIN edu_teacher t ON ec.id = t.id
                LEFT JOIN edu_subject s1 ON ec.subject_parent_id = s1.id
                LEFT JOIN edu_subject s2 ON ec.id = s2.id
        WHERE
            ec.id =#{id}
    </select>

swagger测试,报错: 

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.jiabei.eduservice.mapper.EduCourseMapper.getCoursePublish

这个异常说明mapper找不到,要么mapper和mapper.xml名称不一致,要么编译时只加载了java文件,没有加载xml文件

解决

service下pom.xml中添加

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

service_edu下的application.properties中添加

mybatis-plus.mapper-locations=classpath:com/jiabei/eduservice/mapper/xml/*.xml

 swagger测试,成功

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值