Day207&208.富文本编辑器、课程大纲列表、课程修改、课程章节添加修改、课程小节CRUD、课程最终发布 -谷粒学院

谷粒学院

富文本编辑器Tinymce

一、Tinymce可视化编辑器

参考:https://panjiachen.gitee.io/vue-element-admin/#/example/create

二、组件初始化

Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤

1、复制脚本库

将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)

2、配置html变量

在 guli-admin/build/webpack.dev.conf.js 中添加配置

使在html页面中可是使用这里定义的BASE_URL变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZD6URoQQ-1614611373296)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301115058890.png)]

new HtmlWebpackPlugin({
    
    
    ......,
    templateParameters: {
    
    
    	BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
    }
})

3、引入js脚本

在guli-admin/index.html 中引入js脚本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLiP9QVt-1614611373302)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301115540528.png)]


三、组件引入

为了让Tinymce能用于Vue.js项目,vue-element-admin-master对Tinymce进行了封装,下面我们将它引入到我们的课程信息页面

1、复制组件

src/components/Tinymce

2、引入组件

课程信息组件中引入 Tinymce

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKsmmxSr-1614611373308)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301115937921.png)]

//引入Tinymce富文本编辑器组件
import Tinymce from '@/components/Tinymce';

export default {
    
    
    ....
  components: {
    
     Tinymce },
}

3、组件模板

<!-- 课程简介-->
<el-form-item label="课程简介">
	<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>

4、组件样式

在info.vue文件的最后添加如下代码,调整上传图片按钮的高度

<style scoped>
  .tinymce-container {
    
    
  line-height: 29px;
  }
</style>

5、显示效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ww9iCrar-1614611373314)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301125720949.png)]

四、测试

  • 图片的base64编码

    Tinymce中的图片上传功能直接存储的是图片的base64编码,因此无需图片服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-muCkUiWd-1614611373321)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301130036443.png)]

  • 后台报错

在这里插入图片描述

  • 修改数据库description字段类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajB8FDia-1614611373330)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301122147362.png)]

  • 发现一级分类没有值

在这里插入图片描述

  • 在后端8001,com.achang.eduservice.entity.vo.CourseInfoForm中添加一级分类属性

要求跟前端对应的数据名字一致,不然无法获取

    @ApiModelProperty(value = "一级分类ID")
    private String subjectParentId;

在这里插入图片描述

  • 再次测试,成功有值,且对应id

在这里插入图片描述


课程大纲列表功能

一、后端实现

1、创建两个实体类

章节和小节,在章节实体类使用list表示小节

在这里插入图片描述

  • VideoVo
@Data
public class VideoVo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    private String id;

    private String title;

    private Boolean free;

}
  • ChapterVo
@Data
public class ChapterVo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    private String id;

    private String title;

    //表示小节
    private List<VideoVo> children = new ArrayList<VideoVo>();

}

2、Controller

  • EduChapterController
@RestController
@RequestMapping("/eduservice/edu-chapter")
public class EduChapterController {
    
    

    @Autowired
    private EduChapterService eduChapterService;

    //获取课程大纲列表,根据课程id进行查询
    @GetMapping("/getChapterVideo/{courseId}")
    public R getChapterVideo(@PathVariable String courseId){
    
    
        List<ChapterVo> list = eduChapterService.getChapterVideoByCourseId(courseId);

        return R.ok().data("allChapterVideo",list);
    }

}

3、Service

  • service接口
public interface EduChapterService extends IService<EduChapter> {
    
    

    List<ChapterVo> getChapterVideoByCourseId(String courseId);

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

    @Autowired
    private EduVideoService eduVideoService;

    @Override
    public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
    
    
        //最终要的数据列表
        ArrayList<ChapterVo> finalChapterVos = new ArrayList<>();

        //查询章节信息
        QueryWrapper<EduChapter> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id",courseId);
        List<EduChapter> eduChapters = baseMapper.selectList(wrapper);

        //查询小节信息
        QueryWrapper<EduVideo> wrapper1 = new QueryWrapper<>();
        wrapper1.eq("course_id",courseId);
        List<EduVideo> eduVideos = eduVideoService.list(wrapper1);

        //填充章节vo数据
        for (int i = 0; i < eduChapters.size(); i++) {
    
    
            EduChapter chapter = eduChapters.get(i);

            //创建章节vo对象
            ChapterVo chapterVo = new ChapterVo();
            BeanUtils.copyProperties(chapter,chapterVo);
            finalChapterVos.add(chapterVo);

            //填充课时vo对象
            ArrayList<VideoVo> finalVideoVos = new ArrayList<>();
            for (int j = 0; j < eduVideos.size(); j++) {
    
    
                EduVideo video = eduVideos.get(j);

                if (chapter.getId().equals(video.getChapterId())){
    
    
                    VideoVo videoVo = new VideoVo();
                    BeanUtils.copyProperties(video,videoVo);
                    finalVideoVos.add(videoVo);
                }
            }

            chapterVo.setChildren(finalVideoVos);

        }
        return finalChapterVos;

    }
}

4、测试


二、前端实现

1、定义api

chapter.js

import request from '@/utils/request' //引入已经封装好的axios 和 拦截器

export default{
    
    
    //根据课程id获取章节和小节数据列表
    getChapterVideoByCourseId(courseId){
    
    
        return request({
    
    
            url:`/eduservice/edu-chapter/getChapterVideo/${
      
      courseId}`,
            method: 'get',
        })
    },
}

2、引入api脚本方法

import chapter from '@/api/teacher/chapter.js';

3、定义方法、变量、created()调用

export default {
    
    
  data() {
    
    
    return {
    
    
      courseId:'',
      chapterVideoList:[],
      ....
    };
  },
  methods: {
    
    
    //根据课程id查询对应的课程章节和小结
    getChapterVideoByCourseId(){
    
    
        chapter.getChapterVideoByCourseId(this.courseId)
          .then(resp=>{
    
    
            this.chapterVideoList =  resp.data.allChapterVideo
          })
    },
	...
  },
  created() {
    
    
    //获取路由里的id值
    if(this.$route.params && this.$route.params.id){
    
    
      this.courseId = this.¥route.params.id
    }
    //根据课程id查询对应的课程章节和小结
    this.getChapterVideoByCourseId();
  },
};
</script>

4、组件模版

    <ul>
      <li v-for="chapter in chapterVideoList" :key="chapter.id">
        <p>
          {
   
   { chapter.title }}
          <span>
            <el-button type="text">添加课时</el-button>
            <el-button style="" type="text">编辑</el-button>
            <el-button type="text">删除</el-button>
          </span>
        </p>

        <ul>
          <li v-for="video in chapter.children" :key="video.id">
            {
   
   { video.title }}
          </li>
        </ul>
      </li>
      <li>
        <!-- 视频 -->
        <ul class="chanpterList videoList">
          <li v-for="video in chapter.children" :key="video.id">
            <p>
              {
   
   { video.title }}
              <span class="acts">
                <el-button type="text">编辑</el-button>
                <el-button type="text">删除</el-button>
              </span>
            </p>
          </li>
        </ul>
      </li>
    </ul>

课程修改功能

一、后端接口实现

1、根据课程id查询课程基本信息

  • EduCourseController
    //根据课程id查询课程基本信息
    @GetMapping("/getCourseInfoById/{courseId}")
    public R getCourseInfoById(@PathVariable String courseId){
    
    
        CourseInfoForm courseInfoForm = eduCourseService.getCourseInfo(courseId);
        return R.ok().data("courseInfoForm",courseInfoForm);
    }
  • EduCourseService
    //根据课程id查询课程基本信息
    CourseInfoForm getCourseInfo(String courseId);

  • EduCourseServiceImpl
//课程描述注入
@Autowired
private EduCourseDescriptionService eduCourseDescriptionService;

@Override
public CourseInfoForm getCourseInfo(String courseId) {
    
    
    //查询课程表
    EduCourse eduCourse = baseMapper.selectById(courseId);
    CourseInfoForm courseInfoForm = new CourseInfoForm();
    BeanUtils.copyProperties(eduCourse,courseInfoForm);

    //查询简介表
    EduCourseDescription courseDescription = eduCourseDescriptionService.getById(courseId);
    courseInfoForm.setDescription(courseDescription.getDescription());

    return courseInfoForm;
}

2、修改课程信息

  • EduCourseController
//修改课程信息
@PostMapping("/updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoForm courseInfoForm){
    
    
    eduCourseService.updateCourseInfo(courseInfoForm);
    return R.ok();
}
  • EduCourseService
//修改课程信息
void updateCourseInfo(CourseInfoForm courseInfoForm);
  • EduCourseServiceImpl
@Override
public void updateCourseInfo(CourseInfoForm courseInfoForm) {
    
    
    //1、修改课程表
    EduCourse eduCourse = new EduCourse();
    BeanUtils.copyProperties(courseInfoForm,eduCourse);
    int update = baseMapper.updateById(eduCourse);
    if (update <= 0){
    
    
        throw new AchangException(20001,"修改课程信息失败");
    }

    //2、修改描述信息
    EduCourseDescription eduCourseDescription = new EduCourseDescription();
    eduCourseDescription.setDescription(courseInfoForm.getDescription());
    eduCourseDescription.setId(courseInfoForm.getId());
    eduCourseDescriptionService.updateById(eduCourseDescription);
}

二、前端实现

  • 定义api方法

guli-admin\src\api\teacher\course.js

    //根据课程id 查询课程基本信息
    getCourseInfoById(courseId){
    
    
        return request({
    
    
            url:`/eduservice/edu-course/getCourseInfoById/${
      
      courseId}`,
            method: 'get',
        })
    },
    //修改课程信息
    updateCourseInfo(courseInfoForm){
    
    
        return request({
    
    
            url:"/eduservice/edu-course/updateCourseInfo",
            method: 'post',
            data: courseInfoForm,
        })
    }
  • 修改chapter页面跳转路径

guli-admin\src\views\edu\course\chapter.vue

    //跳转到上一步
    previous() {
    
    
      this.$router.push({
    
     path: "/course/info/"+this.courseId});
    },
    next() {
    
    
      //跳转到第三步
      this.$router.push({
    
     path: "/course/publish/"+this.courseId});
    }
  • info页面 created()
created() {
    
    
    //判断路径中是否有课程id
    if (this.$route.params && this.$route.params.id) {
    
    
        this.courseId = this.$route.params.id;
        //根据课程id 查询课程基本信息
        this.getCourseInfo()
    },
        ....
}
  • info页面 methods
methods: {
    
    
    //获取课程信息
    getCourseInfo() {
    
    
        course.getCourseInfoById(this.courseId).then((resp) => {
    
    
            this.courseInfo = resp.data.courseInfoForm
        })
    },
        ....
}
  • info页面 data
  data() {
    
    
    return {
    
    
		...
      courseId: "",
    };
  }
  • 测试

在这里插入图片描述

  • 上面问题,二级分类中的数据显示出现了问题
  created() {
    
    
    //判断路径中是否有课程id
    if (this.$route.params && this.$route.params.id) {
    
    
      this.courseId = this.$route.params.id;
      //根据课程id 查询课程基本信息
      this.getCourseInfo();
    } else {
    
    
      //初始化所有讲师
      this.getListTeacher();
      //初始化一级分类
      this.getOneSubject();
    }
  }
    //获取课程信息
    getCourseInfo() {
    
    
      course.getCourseInfoById(this.courseId).then((resp) => {
    
    
        this.courseInfo = resp.data.courseInfoForm;
        //查询所有分类,包含一级和二级所有
        subject.getSubjectList().then((resp) => {
    
    
          //获取所有一级分类
          this.subjectOneLists = resp.data.list;

          //把所有一级分类数组进行遍历
          for (var i = 0; i < this.subjectOneLists.length; i++) {
    
    
            //获取每个一级分类
            var oneSubject = this.subjectOneLists[i];
            //比较当前courseInfo里面的一级分类id和所有的一级分类id是否一样
            if (this.courseInfo.subjectParentId == oneSubject.id) {
    
    
              //获取一级分类中所有的二级分类
              this.subjectTwoLists = oneSubject.children;
            }
          }
        });
        //初始化所有讲师
        this.getListTeacher();
      });
    }
  • 出现一个bug,就是回显数据后,再单击添加课程,数据还在,按理应该是清空数据

添加监听器,监听路由,如果路由变化,就将courseInfo的数据清空

  watch: {
    
    
    $route(to, from) {
    
    
      //路由变化方式,当路由发送变化,方法就执行
      console.log("watch $route");
      this.courseInfo={
    
    }
    },
  }
  • 实现修改功能

guli-admin\src\views\edu\course\info.vue

    //添加课程
    saveCourse() {
    
    
      course.addCourseInfo(this.courseInfo).then((resp) => {
    
    
        this.$message({
    
    
          message: "添加课程信息成功",
          type: "success",
        });
        //跳转到第二步,并带着这个课程生成的id
        this.$router.push({
    
     path: "/course/chapter/" + resp.data.courseId });
      });
    },

    //修改课程
    updateCourse() {
    
    
      course.updateCourseInfo(this.courseInfo).then((resp) => {
    
    
        this.$message({
    
    
          message: "修改课程信息成功",
          type: "success",
        });
        //跳转到第二步,并带着这个课程生成的id
        this.$router.push({
    
     path: "/course/chapter/" + this.courseId });
      });
    },
	
    //判断是修改还是新增
    saveOrUpdate() {
    
    
      //判断courseInfo中是否有id值
      if (this.courseInfo.id) {
    
    
        //有id值,为修改
        this.updateCourse();
      } else {
    
    
        //没id值,为添加
        this.saveCourse();
      }
    }

课程章节添加修改删除功能

一、后端接口实现

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

    //根据章节id查询
    @GetMapping("getChapter/{chapterId}")
    public R getChapter(@PathVariable String chapterId){
    
    
        EduChapter eduChapter = eduChapterService.getById(chapterId);
        return R.ok().data("chapter",eduChapter);
    }

    //修改章节
    @PostMapping("updateChapter")
    public R updateChapter(@RequestBody EduChapter eduChapter){
    
    
        eduChapterService.updateById(eduChapter);
        return R.ok();
    }

    //删除章节
    @DeleteMapping("deleteById/{chapterId}")
    public R deleteById(@PathVariable String chapterId){
    
    
        boolean flag = eduChapterService.deleteChapter(chapterId);
        if (flag){
    
    
            return R.ok();
        }else {
    
    
            return R.error();
        }

    }
  • service
boolean deleteChapter(String chapterId);
  • serviceImpl
    //删除章节的方法
    @Override
    public boolean deleteChapter(String chapterId) {
    
    
        //根据chapter章节id 查询查询小节表,如果查询有数据,则不删除
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("chapter_id",chapterId);
        int count = eduVideoService.count(wrapper);
        //判断
        if (count>0){
    
    
            //能查询出来小节,不进行删除
            throw new AchangException(20001,"还有小节数据,不能删除");
        }else {
    
    
            //不能查询出小节,进行删除
            int delete = baseMapper.deleteById(chapterId);
            return delete>0;
        }
    }

二、前端页面实现

  • 定义api
    //添加章节
    addChapter(chapter) {
    
    
        return request({
    
    
            url: `/eduservice/edu-chapter/addChapter`,
            method: `post`,
            data: chapter
        })
    },
    //根据id查询章节
    updateChapterById(chapterID) {
    
    
        return request({
    
    
            url: `/eduservice/edu-chapter/getChapter/${
      
      chapterID}`,
            method: `get`,
        })
    },
    //修改章节
    updateChapter(chapter) {
    
    
        return request({
    
    
            url: `/eduservice/edu-chapter/updateChapter`,
            method: `post`,
            data: chapter
        })
    },
    //删除章节
    deleteById(chapterID) {
    
    
        return request({
    
    
            url: `/eduservice/edu-chapter/deleteById/${
      
      chapterID}`,
            method: `delete`,
        })
    }
  • 引入api
import chapter from "@/api/teacher/chapter.js";
  • 【添加课程】前端实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnDlKFNU-1614611373370)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301192930998.png)]

methods: {
    
    
	//添加章节
    saveChapter() {
    
    
      //设置课程id到chapter对象中
      this.chapter.courseId = this.courseId
      chapter.addChapter(this.chapter).then((resp) => {
    
    
        //关闭弹框
        this.dialogChapterFormVisible = false;
        //提示信息
        this.$message({
    
    
          message: "添加章节成功",
          type: "success",
        });
        //刷新页面
        this.getChapterVideoByCourseId()
      });
    },
    saveOrUpdate() {
    
    
          this.saveChapter()
        }
}
  • 设置com.achang.eduservice.entity.EduChapter的创建时间和更新时间自动增添

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSQ1jeQ8-1614611373373)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301193058647.png)]

  • 测试新增章节功能是否优先

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haSaK6wy-1614611373374)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301193140747.png)]

  • 设置弹出表单时,清空数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEe86p2C-1614611373375)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301193809829.png)]

    //弹出添加章节表单
    openChapterDialog(){
    
    
       //清空之前的数据
        this.chapter={
    
    }
        //显示弹框
        this.dialogChapterFormVisible = true
    }
  • 【修改功能】实现
//修改章节
    updateChapter() {
    
    
      //设置课程id到chapter对象中
      this.chapter.courseId = this.courseId;
      chapter.updateChapter(this.chapter).then((resp) => {
    
    
        //关闭弹框
        this.dialogChapterFormVisible = false;
        //提示信息
        this.$message({
    
    
          message: "修改章节成功",
          type: "success",
        });
        //刷新页面
        this.getChapterVideoByCourseId();
      });
    }
    saveOrUpdate() {
    
    
      if (this.chapter.id) {
    
    
        //修改章节
        this.updateChapter();
      } else {
    
    
        //新增章节
        this.saveChapter();
      }
    }

  • 【删除功能】实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TS94m8kw-1614611373377)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301201936160.png)]

    //删除章节
    removeById(chapterId) {
    
    
      this.$confirm("此操作将永久删除章节信息, 是否继续?", "提示", {
    
    
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
    
    
        //点击确定,执行then方法
        chapter.deleteById(chapterId).then((resp) => {
    
    
          //删除成功
          //提示信息
          this.$message({
    
    
            type: "success",
            message: "删除成功!",
          });
          //刷新页面
          this.getChapterVideoByCourseId();
        });
      });
    }

课程章节小节功能

一、后端接口实现

  • com.achang.eduservice.controller.EduVideoController
@RestController
@RequestMapping("/eduservice/edu-video")
@CrossOrigin //解决跨域问题
public class EduVideoController {
    
    

    @Autowired
    private EduVideoService eduVideoService;

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


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

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

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


}

二、前端实现

  • 页面搭建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tE45zGri-1614611373379)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301210837755.png)]

<!--添加小节表单-->
    <!-- 添加和修改课时表单 -->
    <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(video.id)"
          >确 定</el-button
        >
      </div>
    </el-dialog>
  • api

guli-admin\src\api\teacher\video.js

import request from '@/utils/request' //引入已经封装好的axios 和 拦截器

export default {
    
    
    //添加小节
    addVideo(video) {
    
    
        return request({
    
    
            url: `/eduservice/edu-video/addVideo`,
            method: `post`,
            data: video
        })
    },
    //根据id查询小节
    getVideoById(videoId) {
    
    
        return request({
    
    
            url: `/eduservice/edu-video/getVideoById/${
      
      videoId}`,
            method: `get`,
        })
    },
    //修改小节
    updateVideo(video) {
    
    
        return request({
    
    
            url: `/eduservice/edu-video/updateVideo`,
            method: `post`,
            data: video
        })
    },
    //删除小节
    deleteById(videoId) {
    
    
        return request({
    
    
            url: `/eduservice/edu-video/deleteVideo/${
      
      videoId}`,
            method: `delete`,
        })
    },
}
  • 引入api
import video from "@/api/teacher/video.js";
  • 方法定义
    //添加小节弹框的方法
    openEditVideo(chapterId) {
    
    
      //清空之前的数据
      this.video = {
    
    };
      //显示弹框
      this.dialogVideoFormVisible = true;
      //设置章节id
      this.video.chapterId = chapterId;
    },
  • 添加方法
    //添加小节
    addVideo() {
    
    
      //设置课程id
      this.video.courseId = this.courseId;
      video.addVideo(this.video).then((resp) => {
    
    
        //关闭弹框
        this.dialogVideoFormVisible = false;
        //提示信息
        this.$message({
    
    
          message: "添加小节成功",
          type: "success",
        });
        //刷新页面
        this.getChapterVideoByCourseId();
      });
    },
  • 给EduVideo,entity对象添加自动插入时间和修改时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PydDrQmx-1614611373381)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301211614154.png)]

  • 测试通过

  • 【删除小节】功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MClVIf1z-1614611373382)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301213128067.png)]

    //删除小节
    removeVideo(videoId) {
    
    
      this.$confirm("此操作将永久删除小节信息, 是否继续?", "提示", {
    
    
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
    
    
        //点击确定,执行then方法
        video.deleteById(videoId).then((resp) => {
    
    
          //删除成功
          //提示信息
          this.$message({
    
    
            type: "success",
            message: "删除成功!",
          });
          //刷新页面
          this.getChapterVideoByCourseId();
        });
      });
    },

  • 【小节修改】功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypmselu5-1614611373384)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301214211583.png)]

    //修改小节表单回显
    getVideoById(videoId) {
    
    
      //弹出小节弹窗
      this.dialogVideoFormVisible = true;
      video.getVideoById(videoId).then((resp) => {
    
    
        this.video = resp.data.video;
      });
    },
  • 【小节修改】功能
    //小节修改
    updateVideorById(videoId) {
    
    
      //设置小节id到video对象中
      this.video.id = videoId;
      video.updateVideo(this.video).then((resp) => {
    
    
        //关闭弹框
        this.dialogVideoFormVisible = false;
        //提示信息
        this.$message({
    
    
          message: "修改小节成功",
          type: "success",
        });
        //刷新页面
        this.getChapterVideoByCourseId();
      });
    },
  • 测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2L2Oos8t-1614611373386)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301220516228.png)]


课程最终发布

一、后端实现

  • com.achang.eduservice.entity.vo.CoursePublishVo
@Data
public class CoursePublishVo implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    private String id;//课程id
    
    private String title; //课程名称

    private String cover; //封面

    private Integer lessonNum;//课时数

    private String subjectLevelOne;//一级分类

    private String subjectLevelTwo;//二级分类

    private String teacherName;//讲师名称

    private String price;//价格 ,只用于显示

}
  • 数据访问层

接口:EduCourseMapper

public interface EduCourseMapper extends BaseMapper<EduCourse> {
    
    
    public CoursePublishVo getPublishCourseInfo(String courseId);
}

实现:EduCourseMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.achang.eduservice.mapper.EduCourseMapper">

    <select id="getPublishCourseInfo"    resultType="com.achang.eduservice.entity.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>
</mapper>
  • EduCourseMapper接口
@Component
public interface EduCourseMapper extends BaseMapper<EduCourse> {
    
      
    public CoursePublishVo getPublishCourseInfo(String courseId);
}
  • EduCourseService接口
    根据课程id查询课程确认信息
    public CoursePublishVo getPublishCourseInfo(String courseId);
  • EduCourseServiceImpl
    //根据课程id查询课程确认信息
    @Override
    public CoursePublishVo getPublishCourseInfo(String courseId) {
    
    
        return eduCourseMapper.getPublishCourseInfo(courseId);
    }
  • EduCourseController
    //根据课程id查询课程确认信息
    @GetMapping("/getpublishCourseInfo/{id}")
    public R getpublishCourseInfo(@PathVariable String id){
    
    
        CoursePublishVo publishCourseInfo = eduCourseService.getPublishCourseInfo(id);
        return R.ok().data("publishCourse",publishCourseInfo);
    }
  • 测试,访问:http://localhost:8801/swagger-ui.html

  • 报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yjqfo99p-1614611373388)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301225614464.png)]

  • 原因、解决方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-07skounZ-1614611373390)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301230028092.png)]

  • application.properties下指定xml文件夹
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/achang/eduservice/mapper/xml/*.xml
  • 再次测试,成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iExDrqrh-1614611373391)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210301230137703.png)]


二、前端实现

  • api方法定义

guli-admin\src\api\teacher\course.js

    //课程确认信息显示
    getPublishCourseInfo(courseId){
    
    
        return request({
    
    
            url:"/eduservice/edu-course/getpublishCourseInfo/"+courseId,
            method: 'get',
        })
    }
  • 导入api方法
import course from '@/api/teacher/course.js'
  • 定义变量方法
export default {
    
    
  data() {
    
    
    return {
    
    
		...
      courseId:'',
      publishCourseinfo:{
    
    },
    };
  },
  methods: {
    
    
    //根据课程id查询
    getPublishCourseInfo(){
    
    
        course.getPublishCourseInfo(this.courseId)
          .then(resp=>{
    
    
              this.publishCourseinfo = resp.data.publishCourse
          })
    },
      ....
  },
  created() {
    
    
    //获取路由中的id值
    if(this.$route.params && this.$route.params.id){
    
    
      this.courseId = this.$route.params.id
      //调用接口方法根据课程id查询课程信息
      this.getPublishCourseInfo()
    }   
  },
};
  • 组件模板
<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="publishCourseinfo.cover" />
      <div class="main">
        <h2>{
   
   { publishCourseinfo.title }}</h2>
        <p class="gray">
          <span>共{
   
   { publishCourseinfo.lessonNum }}课时</span>
        </p>
        <p>
          <span
            >所属分类:{
   
   { publishCourseinfo.subjectLevelOne }} —
            {
   
   { publishCourseinfo.subjectLevelTwo }}</span
          >
        </p>
        <p>课程讲师:{
   
   { publishCourseinfo.teacherName }}</p>
        <h3 class="red">¥{
   
   { publishCourseinfo.price }}</h3>
      </div>
    </div>

    <el-form label-width="120px">
      <el-form-item>
        <el-button @click="previous">返回修改</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="publish"
          >发布课程</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>
  • CSS样式
<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>
  • 测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sqytJH8m-1614661399288)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210302123504880.png)]


课程发布改变状态

一、后端

  • EduCourseController
//课程最终发布
//修改课程状态
@PostMapping("publishCourse/{id}")
public R publishCourse(@PathVariable String id){
    
    
    EduCourse eduCourse = new EduCourse();
    eduCourse.setStatus("Normal"); //设置课程发布状态
    eduCourse.setId(id);
    boolean flag = eduCourseService.updateById(eduCourse);
    if (flag){
    
    
        return R.ok();
    }else {
    
    
        return R.error();
    }
}

二、前端

  • 定义api方法

guli-admin\src\api\teacher\course.js

    //课程最终发布
    publishCourse(courseId) {
    
    
        return request({
    
    
            url: "/eduservice/edu-course/publishCourse/" + courseId,
            method: 'post',
        })
    }
  • 方法定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQYWRT6O-1614661399295)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210302124722822.png)]

    //发布课程
    publish() {
    
    
      this.$confirm("你确定要发布此课程, 是否继续?", "提示", {
    
    
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
    
    
        course.publishCourse(this.courseId).then((resp) => {
    
    
          //提示信息
          this.$message({
    
    
            message: "课程发布成功",
            type: "success",
          });
          //跳转课程列表页面
          this.$router.push({
    
     path: "/course/list" });
        });
      });
    }
  • 测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-teMb32rB-1614661399297)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210302130134561.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59Y9Z2xT-1614661399301)(../../../../../AppData/Roaming/Typora/typora-user-images/image-20210302130128949.png)]

猜你喜欢

转载自blog.csdn.net/qq_43284469/article/details/114274115