vue-element-admin-master 在线教育 -【5】如何从0到1进行课程发布

前言

哈喽!我是话里人,本篇Blog将会手把手带大家《如何从0到1进行课程发布》,让大家理清各个环节流程,快速上手,话不多说,那咱们书接上回~

1.课程管理:需求

1.1 课程发布

1.1.1 基本信息

1.1.2 大纲管理

1.1.3 提交审核

1.2 课程列表

2. 课程管理:基本模块

2.1 后端:domain

  • EduVideo

    package com.czxy.zx.domain;
    
    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * 课程视频
     *
     */
    @Data
    @ApiModel(value="EduVideo对象", description="课程视频")
    public class EduVideo {
    
        @ApiModelProperty(value = "视频ID")
        @TableId(value = "id", type = IdType.ASSIGN_UUID)
        private String id;
    
        @ApiModelProperty(value = "课程ID")
        private String courseId;
    
        @ApiModelProperty(value = "章节ID")
        private String chapterId;
    
        @ApiModelProperty(value = "节点名称")
        private String title;
    
        @ApiModelProperty(value = "排序字段")
        private Integer sort;
    
        @ApiModelProperty(value = "播放次数")
        private Long playCount;
    
        @ApiModelProperty(value = "是否可以试听:0免费 1收费")
        private Integer isFree;
    
        @ApiModelProperty(value = "视频资源")
        private String videoSourceId;
    
        @ApiModelProperty(value = "视频原始文件名字")
        private String videoOriginalName;
    
        @ApiModelProperty(value = "视频时长(秒)")
        private Float duration;
    
        @ApiModelProperty(value = "视频状态:见阿里云文档")
        private String status;
    
        @ApiModelProperty(value = "视频源文件大小(字节)")
        private Long size;
    
        @ApiModelProperty(value = "乐观锁")
        private Long version;
    
        @ApiModelProperty(value = "创建时间")
        @TableField(value = "gmt_create",fill = FieldFill.INSERT)
        private Date gmtCreate;
    
        @ApiModelProperty(value = "更新时间")
        @TableField(value = "gmt_modified",fill = FieldFill.INSERT_UPDATE)
        private Date gmtModified;
    
    }
    
    
  • EduChapter

    package com.czxy.zx.domain;
    
    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * 课程
     *
     */
    @Data
    @ApiModel(value="EduChapter对象", description="课程")
    public class EduChapter {
    
        @ApiModelProperty(value = "章节ID")
        @TableId(value = "id", type = IdType.ASSIGN_UUID)
        private String id;
    
        @ApiModelProperty(value = "课程ID")
        private String courseId;
    
        @ApiModelProperty(value = "章节名称")
        private String title;
    
        @ApiModelProperty(value = "显示排序")
        private Integer sort;
    
        @ApiModelProperty(value = "创建时间")
        @TableField(fill = FieldFill.INSERT, value = "gmt_create")
        private Date gmtCreate;
    
        @ApiModelProperty(value = "更新时间")
        @TableField(fill = FieldFill.INSERT_UPDATE, value = "gmt_modified")
        private Date gmtModified;
    
    
    }
    
    
  • EduCourse

    package com.czxy.zx.domain;
    
    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.io.Serializable;
    import java.math.BigDecimal;
    import java.util.Date;
    
    /**
     * 课程
     *
     */
    @Data
    @ApiModel(value="EduCourse对象", description="课程")
    public class EduCourse {
    
        @ApiModelProperty(value = "课程ID")
        @TableId(value = "id", type = IdType.ASSIGN_UUID)
        private String id;
    
        @ApiModelProperty(value = "课程讲师ID")
        private String teacherId;
    
        @ApiModelProperty(value = "课程专业ID二级分类ID")
        private String subjectId;
    
        @ApiModelProperty(value = "一级分类ID")
        private String subjectParentId;
    
        @ApiModelProperty(value = "课程标题")
        private String title;
    
        @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
        private BigDecimal price;
    
        @ApiModelProperty(value = "总课时")
        private Integer lessonNum;
    
        @ApiModelProperty(value = "课程封面图片路径")
        private String cover;
    
        @ApiModelProperty(value = "销售数量")
        private Long buyCount;
    
        @ApiModelProperty(value = "浏览数量")
        private Long viewCount;
    
        @ApiModelProperty(value = "乐观锁")
        private Long version;
    
        @ApiModelProperty(value = "视频状态 Draft未发布  Normal已发布")
        private String status;
    
        @ApiModelProperty(value = "创建时间")
        @TableField(fill = FieldFill.INSERT, value = "gmt_create")
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
        private Date gmtCreate;
    
        @ApiModelProperty(value = "更新时间")
        @TableField(fill = FieldFill.INSERT_UPDATE, value = "gmt_modified")
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
        private Date gmtModified;
    
    
    }
    
    
  • EduCourseDescription

    package com.czxy.zx.domain;
    
    import com.baomidou.mybatisplus.annotation.FieldFill;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * 课程简介
     *
     */
    @Data
    @ApiModel(value="EduCourseDescription对象", description="课程简介")
    public class EduCourseDescription {
    
    
        @ApiModelProperty(value = "课程ID")
        //将CourseDescription和Course的ID保持一致
        @TableId(value = "id", type = IdType.INPUT)
        private String id;
    
        @ApiModelProperty(value = "课程简介")
        private String description;
    
        @ApiModelProperty(value = "创建时间")
        @TableField(fill = FieldFill.INSERT, value = "gmt_create")
        private Date gmtCreate;
    
        @ApiModelProperty(value = "更新时间")
        @TableField(fill = FieldFill.INSERT_UPDATE, value = "gmt_modified")
        private Date gmtModified;
    
    
    }
    
    

2.2 后端:Mapper

  • EduVideoMapper

    package com.czxy.zx.course.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.zx.domain.EduVideo;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * 课程视频 Mapper 接口
     *
     */
    @Mapper
    public interface EduVideoMapper extends BaseMapper<EduVideo> {
    
    
    }
    
    
  • EduChapterMapper

    package com.czxy.zx.course.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.zx.domain.EduChapter;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * 课程章节 Mapper 接口
     */
    @Mapper
    public interface EduChapterMapper extends BaseMapper<EduChapter> {
    
    }
    
  • EduCourseMapper

    package com.czxy.zx.course.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.zx.domain.EduCourse;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * 课程 Mapper 接口
     */
    @Mapper
    public interface EduCourseMapper extends BaseMapper<EduCourse> {
    
    }
    
  • EduCourseDescriptionMapper

    package com.czxy.zx.course.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.zx.domain.EduCourseDescription;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * 课程介绍 Mapper 接口
     */
    @Mapper
    public interface EduCourseDescriptionMapper extends BaseMapper<EduCourseDescription> {
    
    }
    

2.3 后端:Service 接口

  • EduVideoService

    package com.czxy.zx.course.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduVideo;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    public interface EduVideoService extends IService<EduVideo> {
    }
    
    
  • EduChapterService

    package com.czxy.zx.course.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduChapter;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    public interface EduChapterService extends IService<EduChapter> {
    }
    
    
  • EduCourseService

    package com.czxy.zx.course.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduCourse;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    public interface EduCourseService extends IService<EduCourse> {
    }
    
    
  • EduCourseDescriptionService

    package com.czxy.zx.course.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduCourseDescription;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    public interface EduCourseDescriptionService extends IService<EduCourseDescription> {
    }
    
    

2.4 后端:Service 实现类

  • EduVideoServiceImpl

    package com.czxy.zx.course.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.course.mapper.EduVideoMapper;
    import com.czxy.zx.course.service.EduVideoService;
    import com.czxy.zx.domain.EduVideo;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * 课程视频 服务类
     *
     */
    @Service
    @Transactional
    public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {
    
    
    }
    
    
  • EduChapterServiceImpl

    package com.czxy.zx.course.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.course.mapper.EduChapterMapper;
    import com.czxy.zx.course.service.EduChapterService;
    import com.czxy.zx.domain.EduChapter;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @Service
    @Transactional
    public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
    
    
    }
    
  • EduCourseServiceImpl

    package com.czxy.zx.course.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.course.mapper.EduCourseMapper;
    import com.czxy.zx.course.service.EduCourseService;
    import com.czxy.zx.domain.EduCourse;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @Service
    @Transactional
    public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
    
    }
    
  • EduCourseDescriptionServiceImpl

    package com.czxy.zx.course.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.course.mapper.EduCourseDescriptionMapper;
    import com.czxy.zx.course.service.EduCourseDescriptionService;
    import com.czxy.zx.domain.EduCourseDescription;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @Service
    @Transactional
    public class EduCourseDescriptionServiceImpl extends ServiceImpl<EduCourseDescriptionMapper, EduCourseDescription> implements EduCourseDescriptionService {
    
    }
    

2.5 后端:Controller

  • EduVideoController

    package com.czxy.zx.course.controller;
    
    import com.czxy.zx.course.service.EduVideoService;
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @RestController
    @RequestMapping("/video")
    @Api(tags = "课程视频接口", description = "课程视频的CRUD操作")
    public class EduVideoController {
        @Resource
        private EduVideoService eduVideoService;
    
    
    }
    
    
  • EduChapterController

    package com.czxy.zx.course.controller;
    
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @RestController
    @RequestMapping("/chapter")
    @Api(tags = "课程章节", description="课程章节CRUD操作")
    public class EduChapterController {
    }
    
    
  • EduCourseController

    package com.czxy.zx.course.controller;
    
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @RestController
    @RequestMapping("/course")
    @Api(tags = "课程管理", description="课程CRUD操作")
    public class EduCourseController {
    }
    
    
  • EduCourseDescriptionController

    package com.czxy.zx.course.controller;
    
    import com.czxy.zx.course.service.EduCourseDescriptionService;
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @RestController
    @RequestMapping("/desc")
    @Api(tags = "课程介绍管理", description = "课程介绍CRUD操作")
    public class EduCourseDescriptionController {
        @Resource
        private EduCourseDescriptionService eduCourseDescriptionService;
    
    }
    
    

2.6 前端

2.6.1 需求

2.6.2 路由

    {
      path: 'publish',
      name: '发布课程',
      component: () => import('@/views/edu/course/publish'),
      meta: { title: '发布课程', icon: 'edit' }
    }

2.6.3 页面

<template>
  <el-card class="info-box-card">
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="active">
      <el-step title="课程基本信息"></el-step>
      <el-step title="课程大纲管理"></el-step>
      <el-step title="提交审核"></el-step>
    </el-steps>
    <!-- 课程基本信息 -->
    <div v-if="active==1">
      11111
    </div>
    <!-- 课程大纲管理 -->
    <div v-if="active==2">
      2222
    </div>
    <!-- 提交审核 -->
    <div v-if="active==3">
      3333
    </div>

    <el-button v-if="active==1" type="primary" @click="nextChapter">保存,下一步</el-button>
    <el-button v-if="active==2" type="primary" @click="backInfo">返回基本信息</el-button>
    <el-button v-if="active==2" type="primary" @click="nextPublish">下一步</el-button>
    <el-button v-if="active==3" type="primary" @click="backChapter">返回大纲管理</el-button>
    <el-button v-if="active==3" type="primary" @click="publish">发布课程</el-button>
  </el-card>
</template>

<script>
export default {
  data() {
    return {
      active: 1
    }
  },
  methods: {
    nextChapter() {
      this.active = 2
    },
    backInfo() {
      this.active = 1
    },
    nextPublish() {
      this.active = 3
    },
    backChapter() {
      this.active = 2
    },
    publish() {

    }
  },
}
</script>

<style>
  .info-box-card {
    margin: 10px;
  }
</style>

3. 课程管理:发布课程基本信息

3.1 需求

3.2 前端基本页面

<template>
  <el-card class="info-box-card">
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="active">
      <el-step title="课程基本信息"></el-step>
      <el-step title="课程大纲管理"></el-step>
      <el-step title="提交审核"></el-step>
    </el-steps>
    <!-- 课程基本信息 -->
    <div v-if="active==1">
      <el-form ref="form" :model="course" label-width="80px">
        <el-form-item label="课程标题">
          <el-input v-model="course.title"></el-input>
        </el-form-item>
        <el-form-item label="课程科目">
          <el-cascader v-model="course.subjectId" :props="subjectProps"></el-cascader>
        </el-form-item>
        <el-form-item label="课程讲师">
          <el-select v-model="course.teacherId" placeholder="请选择讲师">
            <el-option
              v-for="(teacher,index) in teacherList"
              :key="index"
              :label="teacher.name"
              :value="teacher.id">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="总课时">
          <el-input-number v-model="course.lessonNum" controls-position="right" :min="1"></el-input-number>
        </el-form-item>
        <el-form-item label="课程价格">
          <el-input-number v-model="course.price" controls-position="right" :min="1"></el-input-number></el-form-item>
        <el-form-item label="课程介绍">
          <tinymce v-model="course.description" :height="300" />
        </el-form-item>
      </el-form>
    </div>
    <!-- 课程大纲管理 -->
    <div v-if="active==2">
      2222
    </div>
    <!-- 提交审核 -->
    <div v-if="active==3">
      3333
    </div>

    <el-button v-if="active==1" type="primary" @click="nextChapter">保存,下一步</el-button>
    <el-button v-if="active==2" type="primary" @click="backInfo">返回基本信息</el-button>
    <el-button v-if="active==2" type="primary" @click="nextPublish">下一步</el-button>
    <el-button v-if="active==3" type="primary" @click="backChapter">返回大纲管理</el-button>
    <el-button v-if="active==3" type="primary" @click="publish">发布课程</el-button>
  </el-card>
</template>

<script>
import Tinymce from '@/components/Tinymce'

export default {
  components: {
    Tinymce
  },
  data() {
    return {
      active: 1,
      course: {},
      subjectProps: {
        lazy: true,
          lazyLoad (node, resolve) {
            // 通过调用resolve将子节点数据返回,通知组件数据加载完成
            // resolve(nodes)
          }
      },
      teacherList: [

      ]
    }
  },
  methods: {
    nextChapter() {
      this.active = 2
    },
    backInfo() {
      this.active = 1
    },
    nextPublish() {
      this.active = 3
    },
    backChapter() {
      this.active = 2
    },
    publish() {

    }
  },
}
</script>

<style>
  .info-box-card {
    margin: 10px;
  }
</style>

3.3 课程科目

  • 导入ajax方法

    // 导入 ajax
    import { findAllSub } from '@/api/edu/course'
    
  • 声明变量

      data() {
        return {
          subjectList: [] ,   // 所有科目
          subjectProps: {     // 科目数据与级联选择对应关系
            value: 'id',
            label: 'title'
          },
        }
      }
    
  • 声明方法

    methods: {
        async findAllSubject() {  // 查询所有科目
          let { data } = await findAllSub()
          this.subjectList = data
        }
      },
    
  • 页面加载成功,查询所有科目

      mounted() {
        // 查询所有科目
        this.findAllSubject()
    
      },
    
  • 显示科目

  <el-form-item label="课程科目">
     <el-cascader v-model="course.subjectId" 
          :options="subjectList" 
          :props="subjectProps" 
          style="width:300px">
     </el-cascader>
  </el-form-item>
  • 完整代码
<template>
  <el-card class="info-box-card">
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="active">
      <el-step title="课程基本信息"></el-step>
      <el-step title="课程大纲管理"></el-step>
      <el-step title="提交审核"></el-step>
    </el-steps>
    <!-- 课程基本信息 -->
    <div v-if="active==1">
      <el-form ref="form" :model="course" label-width="80px">
        <el-form-item label="课程标题">
          <el-input v-model="course.title"></el-input>
        </el-form-item>
        <el-form-item label="课程科目">
          <el-cascader v-model="course.subjectId" :options="subjectList" :props="subjectProps" style="width:300px"></el-cascader>
        </el-form-item>
        <el-form-item label="课程讲师">
          <el-select v-model="course.teacherId" placeholder="请选择讲师">
            <el-option
              v-for="(teacher,index) in teacherList"
              :key="index"
              :label="teacher.name"
              :value="teacher.id">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="总课时">
          <el-input-number v-model="course.lessonNum" controls-position="right" :min="1"></el-input-number>
        </el-form-item>
        <el-form-item label="课程价格">
          <el-input-number v-model="course.price" controls-position="right" :min="1"></el-input-number></el-form-item>
        <el-form-item label="课程介绍">
          <tinymce v-model="course.description" :height="300" />
        </el-form-item>
      </el-form>
    </div>
    <!-- 课程大纲管理 -->
    <div v-if="active==2">
      2222
    </div>
    <!-- 提交审核 -->
    <div v-if="active==3">
      3333
    </div>

    <el-button v-if="active==1" type="primary" @click="nextChapter">保存,下一步</el-button>
    <el-button v-if="active==2" type="primary" @click="backInfo">返回基本信息</el-button>
    <el-button v-if="active==2" type="primary" @click="nextPublish">下一步</el-button>
    <el-button v-if="active==3" type="primary" @click="backChapter">返回大纲管理</el-button>
    <el-button v-if="active==3" type="primary" @click="publish">发布课程</el-button>
  </el-card>
</template>

<script>
// 富文本编辑器
import Tinymce from '@/components/Tinymce'

// 导入 ajax
import { findAllSub } from '@/api/edu/course'

export default {
  components: {
    Tinymce
  },
  data() {
    return {
      active: 1,
      course: {},
      teacherList: [

      ],
      subjectList: [] ,   // 所有科目
      subjectProps: {     // 科目数据与级联选择对应关系
        value: 'id',
        label: 'title'
      },
    }
  },
  methods: {
    nextChapter() {
      this.active = 2
    },
    backInfo() {
      this.active = 1
    },
    nextPublish() {
      this.active = 3
    },
    backChapter() {
      this.active = 2
    },
    publish() {

    },
    async findAllSubject() {  // 查询所有科目
      let { data } = await findAllSub()
      this.subjectList = data
    }
  },
  mounted() {
    // 查询所有科目
    this.findAllSubject()

  },
}
</script>

<style>
  .info-box-card {
    margin: 10px;
  }
</style>

3.4 课程讲师

  • 导入ajax

    import { findAll } from '@/api/edu/teacher'
    
  • 声明变量

      data() {
        return {
          teacherList: [], // 所有老师
        }
      },
    
  • 声明方法

    methods: {
        async findAllTeacher() {  // 查询所有科目
          let { data } = await findAll()
          this.teacherList = data
        }
    }
    
  • 调用方法

      mounted() {
    
        // 查询所有老师
        this.findAllTeacher()
    
      },
    
  • 显示内容

    <el-form-item label="课程讲师">
        <el-select v-model="course.teacherId" placeholder="请选择讲师">
            <el-option
                v-for="(teacher,index) in teacherList"
                :key="index"
                :label="teacher.name"
                :value="teacher.id">
            </el-option>
        </el-select>
    </el-form-item>
    

3.5 富文本编辑器:Tinymce

  • 导入 tinymce

    // 富文本编辑器
    import Tinymce from '@/components/Tinymce'
    
  • 声明 tinymce组件

    export default {
      components: {
        Tinymce
      },
    }
    
  • 使用tinymce组件

    <el-form-item label="课程介绍">
       <tinymce v-model="course.description" :height="300" />
    </el-form-item>
    
  • 支持中文

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

3.6 保存基本信息(下一步)

3.5.0 分析

  • 在页面中使用courseVo封装所有的数据,但数据库提供了2张表来保存数据
    • edu_course:课程表,保存的是课程相关的基本信息
    • edu_course_description:课程描述表,保存的是课程的描述内容(富文本编辑器生成的内容)
  • 基本需求:保存
    • 如果有id将进行更新操作
    • 如果没有id将进行插入操作

3.5.1 后端实现

  • 编写保存或更新方法接口

  • 编写service接口

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
public interface EduCourseService extends IService<EduCourse> {
    /**
     * 保存
     * @param courseVo
     * @return
     */
    String saveOrUpdate(CourseVo courseVo);
}
  • 编写service实现

package com.czxy.zx.course.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.zx.course.mapper.EduCourseDescriptionMapper;
import com.czxy.zx.course.mapper.EduCourseMapper;
import com.czxy.zx.course.service.EduCourseDescriptionService;
import com.czxy.zx.course.service.EduCourseService;
import com.czxy.zx.course.vo.CourseVo;
import com.czxy.zx.domain.EduCourse;
import com.czxy.zx.domain.EduCourseDescription;
import com.czxy.zx.exception.EduException;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@Service
@Transactional
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

    @Resource
    private EduCourseDescriptionMapper eduCourseDescriptionMapper;

    @Override
    public String saveOrUpdate(CourseVo courseVo) {
        int result = 1;
        // 保存或更新 course
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseVo,eduCourse);
        // 处理studentId
        eduCourse.setSubjectId(courseVo.getSubjectId()[1]);
        eduCourse.setSubjectParentId(courseVo.getSubjectId()[0]);
        if(StringUtils.isNotBlank(courseVo.getId())) {
            result &= baseMapper.updateById(eduCourse);
        } else {
            result &= baseMapper.insert(eduCourse);
        }

        // 保存 description
        EduCourseDescription eduCourseDescription = new EduCourseDescription();
        eduCourseDescription.setId(eduCourse.getId());
        eduCourseDescription.setDescription(courseVo.getDescription());
        eduCourseDescription.setGmtCreate(new Date());
        if(StringUtils.isNotBlank(courseVo.getId())) {
            result &= eduCourseDescriptionMapper.updateById(eduCourseDescription);
        } else {
            result &= eduCourseDescriptionMapper.insert(eduCourseDescription);
        }

        if(result == 1) {
            return eduCourse.getId();
        }
        // 保存失败,抛异常保证两个操作一个事务
        throw new EduException("保存失败");
    }
}
  • 编写Vo封装类

package com.czxy.zx.course.vo;

import lombok.Data;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@Data
public class CourseVo{
    private String id;
    private String title;
    private String[] subjectId;
    private String teacherId;
    private Integer lessonNum;
    private Double price;
    private String description;
}

  • 编写controller实现

package com.czxy.zx.course.controller;

import com.czxy.zx.course.service.EduCourseService;
import com.czxy.zx.course.vo.CourseVo;
import com.czxy.zx.vo.BaseResult;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@RestController
@RequestMapping("/course")
@Api(tags = "课程管理", description="课程CRUD操作")
public class EduCourseController {

    @Resource
    private EduCourseService eduCourseService;

    @PostMapping
    public BaseResult save(@RequestBody CourseVo courseVo) {
        String id = eduCourseService.saveOrUpdate(courseVo);
        return BaseResult.ok("保存成功", id);
    }

}

3.5.2 前端实现

  • 编写ajax

// 保存课程
export function saveCourse(courseVo) {
  return axios.post('/course-service/course',courseVo);
}
  • 导入ajax函数

    import { findAllSub, saveCourse } from '@/api/edu/course'
    

  • 调用ajax

    async nextChapter() {
      // 保存
      let { data,message } = await saveCourse(this.course)
      debugger
      this.$message.success(message)
      // 保存id
      this.course.id = data
      this.active = 2
    },

4.课程管理:课程大纲管理

4.1 需求

  • 章节操作

  • 添加和修改章节

  • 课时操作

  • 添加和修改课时

4.2 保存章节:添加

4.2.1 需求

  • 点击按钮,显示弹出层

  • 显示弹出层,对课程章节进行操作

  • 接口

4.2.2 后端实现:保存

package com.czxy.zx.course.controller;

import com.czxy.zx.course.service.EduChapterService;
import com.czxy.zx.domain.EduChapter;
import com.czxy.zx.vo.BaseResult;
import io.swagger.annotations.Api;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@RestController
@RequestMapping("/chapter")
@Api(tags = "课程章节", description="课程章节CRUD操作")
public class EduChapterController {

    @Resource
    private EduChapterService eduChapterService;

    @PostMapping
    public BaseResult save(@RequestBody EduChapter eduChapter) {
        // 保存
        eduChapterService.saveOrUpdate(eduChapter);
        return BaseResult.ok("保存成功");
    }

}

4.2.3 前端实现:添加

  • ajax请求

// 保存章节
export function saveCha( chapter ) {
  return axios.post('/course-service/chapter',chapter);
}
  • 显示按钮和弹出层

    <!-- 课程大纲管理 -->
        <div v-if="active==2">
          <!-- 章节添加按钮 -->
          <el-button type="primary" plain icon="el-icon-document-add" @click="showChapterDialog()">添加章节</el-button>
          <!-- 章节弹出层 -->
          <el-dialog
            :title="chapterDialogTitle"
            :visible.sync="chapterDialogVisible"
            width="40%"
            >
            <el-form :model="chapter">
              <el-form-item label="章节标题" label-width="80px">
                <el-input v-model="chapter.title" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="章节排序" label-width="80px">
                <el-input-number v-model="chapter.sort" controls-position="right" :min="0"></el-input-number>
              </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
              <el-button @click="chapterDialogVisible = false">取 消</el-button>
              <el-button type="primary" @click="saveChapter">确 定</el-button>
            </div>
          </el-dialog>
          <!-- 章节展示 -->
        </div>
    
  • 导入ajax

  • 声明变量

    data() {
        return {
          chapterDialogVisible: false,    //章节弹出层
          chapterDialogTitle: '',         //章节弹出层标题
          chapter: {},                    //章节对象
        }
    }
    
  • 编写方法

    methods: {
       showChapterDialog(chapterId) {
          // 显示弹出层
          this.chapterDialogVisible = true
          // 记录课程id
          this.chapter.courseId = this.course.id
           
          if(chapterId) {
            // 修改
            this.chapterDialogTitle = '修改章节'
            // 查询章节
          } else {
            // 添加
            this.chapterDialogTitle = '添加章节'
          }
    
        },
        async saveChapter() {
          // 发送ajax
          let { message } = await saveCha(this.chapter)
          this.$message.success(message)
          // 关闭弹出层
          this.chapterDialogVisible = false
          // 清空数据
          this.chapter = {}
          // 刷新列表
          this.findAllChapter()
    
        },
        findAllChapter() {
    
        }
    }
    

4.3 章节列表

4.3.1 需求

4.3.2 后端实现

  • 接口

  • 完善JavaBean:修改 EduChapter,添加videoList字段

        @TableField(exist = false)
        private List<EduVideo> videoList;
    
  • 编写service接口:查询所有章节,含视频信息(子章节)

    package com.czxy.zx.course.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.zx.domain.EduChapter;
    
    import java.util.List;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    public interface EduChapterService extends IService<EduChapter> {
        /**
         * 查询所有
         * @return
         */
        List<EduChapter> findAll(String courseId);
    }
    
    
  • 编写service实现

    package com.czxy.zx.course.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.zx.course.mapper.EduChapterMapper;
    import com.czxy.zx.course.mapper.EduVideoMapper;
    import com.czxy.zx.course.service.EduChapterService;
    import com.czxy.zx.domain.EduChapter;
    import com.czxy.zx.domain.EduVideo;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @Service
    @Transactional
    public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
    
        @Resource
        private EduVideoMapper eduVideoMapper;
    
        @Override
        public List<EduChapter> findAll(String courseId) {
            // 查询指定course的章节
            QueryWrapper<EduChapter> chapterQueryWrapper = new QueryWrapper<>();
            chapterQueryWrapper.eq("course_id",courseId);
            chapterQueryWrapper.orderByAsc("sort");
            List<EduChapter> list = this.baseMapper.selectList(chapterQueryWrapper);
    
            // 查询直接对应的视频
            for (EduChapter eduChapter : list) {
                QueryWrapper<EduVideo> videoQueryWrapper = new QueryWrapper<>();
                videoQueryWrapper.orderByAsc("sort");
                videoQueryWrapper.eq("chapter_id",eduChapter.getId());
                List<EduVideo> videoList = eduVideoMapper.selectList(videoQueryWrapper);
                eduChapter.setVideoList(videoList);
            }
            return list;
        }
    }
    
  • 编写Controller实现

    package com.czxy.zx.course.controller;
    
    import com.czxy.zx.course.service.EduChapterService;
    import com.czxy.zx.domain.EduChapter;
    import com.czxy.zx.vo.BaseResult;
    import io.swagger.annotations.Api;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.util.Date;
    import java.util.List;
    
    /**
     * @author txt
     * @email tanxintong9968@163.com
     */
    @RestController
    @RequestMapping("/chapter")
    @Api(tags = "课程章节", description="课程章节CRUD操作")
    public class EduChapterController {
    
        @Resource
        private EduChapterService eduChapterService;
    
    
        @GetMapping("/course/{courseId}")
        public BaseResult findAll(@PathVariable("courseId") String courseId) {
            List<EduChapter> list = eduChapterService.findAll(courseId);
            return BaseResult.ok("查询成功", list);
        }
    
    }
    
    

4.3.3 前端实现

  • 编写ajax函数

    // 查询当前课程的相关章节
    export function findAllCha( courseId ) {
      return axios.get(`/course-service/chapter/course/${courseId}`);
    }
    
  • 导入ajax函数

  • 声明变量

    data() {
        return {
            chapterList: [],                //章节列表
        }
    }
    
  • 调用ajax函数

        async findAllChapter() {
          let { data } = await findAllCha(this.course.id)
          this.chapterList = data
        }
    
  • 下一步 / 返回章节 / 保存章节,均需要调用查询所有章节

  • 显示章节列表

    <!-- 章节展示 -->
          <el-collapse>
            <el-collapse-item v-for="(c,ci) in chapterList" :key="ci" >
              <template slot="title">
                <div class="chapter-title-item">
                  <span>{{c.title}}</span>
                  <span>
                    <el-button type="text">添加课时</el-button>
                    <el-button type="text">编辑</el-button>
                    <el-button type="text">删除</el-button>
                  </span>
                </div>
              </template>
              <div class="video-title-item" v-for="(v,vi) in c.videoList" :key="vi">
                <span>{{v.title}}</span>
                <span>
                  <el-button type="text">编辑</el-button>
                  <el-button type="text">删除</el-button>
                </span>
              </div>
            </el-collapse-item>
          </el-collapse>
    
  • 显示列表对应的CSS样式

    .chapter-title-item {
        width: 100%;
        border-left: 1px solid #e6ebf5;
        /* border-right: 1px solid #e6ebf5; */
        padding: 0 10px;
        display: flex;
        justify-content: space-between;			/*两端对齐*/
      }
      .video-title-item{
        border-left: 1px solid #e6ebf5;
        border-top: 1px solid #e6ebf5;
        margin-left: 30px;
        margin-right: 31px;
        line-height: 40px;
        display: flex;
        justify-content: space-between;
      }
    

4.4 保存章节:更新

4.4.1 需求

  • 编辑章节

4.4.2 后端实现:查询详情

    @GetMapping("/{chapterId}")
    public BaseResult findById(@PathVariable("chapterId") String chapterId) {
        EduChapter eduChapter = eduChapterService.getById(chapterId);
        return BaseResult.ok("查询成功", eduChapter);
    }

4.4.3 前端实现:修改

  • 编写ajax函数

    // 查询章节详情
    export function findChaById( chapterId ) {
      return axios.get(`/course-service/chapter/${chapterId}`);
    }
    
  • 导入ajax函数

  • 调用ajax函数

        async showChapterDialog(chapterId) {
          this.chapterDialogVisible = true
          if(chapterId) {
            // 修改
            this.chapterDialogTitle = '修改章节'
            // 查询章节
            let { data } = await findChaById(chapterId)
            this.chapter = data
          } else {
            // 添加
            this.chapterDialogTitle = '添加章节'
          }
    
        },
    

4.5 保存课时

4.5.1 需求

4.5.2 后端实现

  • 编写保存和查询所有视频信息

package com.czxy.zx.course.controller;

import com.czxy.zx.course.service.EduVideoService;
import com.czxy.zx.domain.EduChapter;
import com.czxy.zx.domain.EduVideo;
import com.czxy.zx.vo.BaseResult;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@RestController
@RequestMapping("/video")
@Api(tags = "课程视频接口", description = "课程视频的CRUD操作")
public class EduVideoController {
    @Resource
    private EduVideoService eduVideoService;

    @PostMapping
    public BaseResult save(@RequestBody EduVideo eduVideo) {
        eduVideoService.saveOrUpdate(eduVideo);
        return BaseResult.ok("保存成功");
    }

    @GetMapping("/{videoId}")
    public BaseResult findById(@PathVariable("videoId") String videoId) {
        EduVideo eduVideo = eduVideoService.getById(videoId);
        return BaseResult.ok("查询成功", eduVideo);
    }


}

4.5.3 前端实现:添加

  • 编写ajax函数

    // 保存视频
    export function saveVid( video ) {
      return axios.post('/course-service/video',video);
    }
    
  • 导入ajax函数

  • 点击“添加课时”显示弹出层

<el-button type="text" @click.stop="showVideoDialog(c.id)">添加课时</el-button>
  • 编写弹出层

    <!-- 课时(视频)弹出层 -->
          <el-dialog
            :title="videoDialogTitle"
            :visible.sync="videoDialogVisible"
            width="40%"
            >
            <el-form :model="video">
              <el-form-item label="课时标题" label-width="80px">
                <el-input v-model="video.title" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="课时排序" label-width="80px">
                <el-input-number v-model="video.sort" controls-position="right" :min="0"></el-input-number>
              </el-form-item>
              <el-form-item label="是否免费" label-width="80px">
                <el-radio-group v-model="video.isFree">
                  <el-radio :label="0">免费</el-radio>
                  <el-radio :label="1">默认</el-radio>
                </el-radio-group>
              </el-form-item>
              <el-form-item label="上传视频" label-width="80px">
    
              </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
              <el-button @click="videoDialogVisible = false">取 消</el-button>
              <el-button type="primary" @click="saveVideo">确 定</el-button>
            </div>
          </el-dialog>
    
  • 声明变量

    data() {
        return {
          videoDialogTitle: '',           //视频弹出层标题
          videoDialogVisible: false,      //视频弹出层
          video: {},                      //视频对象
        }
    }
    
  • 编写显示函数

    method: {
       showVideoDialog(chapterId, videoId) {
          // 记录课程id
          this.video.courseId = this.course.id
          // 记录章节id
          this.video.chapterId = chapterId
    
          this.videoDialogVisible = true
          if(videoId) {
            // 修改
            this.videoDialogTitle = '修改课时'
            // 查询视频
    
          } else {
            // 添加
            this.videoDialogTitle = '添加课时'
          }
        },
    }
    
  • 编写保存函数

        async saveVideo(chapterId) {
          // ajax
          let { message } = await saveVid( this.video )
          this.$message.success(message)
          // 关闭弹出层
          this.videoDialogVisible = false
          // 清空数据
          this.video = {}
          // 查询所有
          this.findAllChapter()
        }
    

4.5.4 前端实现:更新

  • 编写ajax函数

// 查询视频详情
export function findVidById( videoId ) {
  return axios.get(`/course-service/video/${videoId}`);
}
  • 导入ajax函数

  • 显示弹出层查询视频详情

async showVideoDialog(chapterId, videoId) {
      // 记录课程id
      this.video.courseId = this.course.id
      // 记录章节id
      this.video.chapterId = chapterId

      this.videoDialogVisible = true
      if(videoId) {
        // 修改
        this.videoDialogTitle = '修改课时'
        // 查询视频
        let { data } = await findVidById(videoId)
        this.video = data

      } else {
        // 添加
        this.videoDialogTitle = '添加课时'
      }
    },

4.6 删除章节

4.6.1 需求

  • 删除章节

  • 当该章节下存在课时,该章节不可删除;

  • 须先删除该章节下的课时,直到该章节下没有课时,该章节才可删除

4.6.2 后端实现

  • 编写Controller实现
package com.czxy.zx.course.controller;

import com.czxy.zx.course.service.EduChapterService;
import com.czxy.zx.domain.EduChapter;
import com.czxy.zx.vo.BaseResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@RestController
@RequestMapping("/chapter")
public class EduChapterController {
    @Resource
    private EduChapterService eduChapterService;

    /**
     * 删除章节
     * @param chapterId
     * @return
     */
    @DeleteMapping("/{chapterId}")
    public BaseResult deleteById(@PathVariable("chapterId") String chapterId){
        boolean result = eduChapterService.removeChapterById(chapterId);
        if (result){
            return BaseResult.ok("删除章节成功");
        }
        return BaseResult.error("删除章节失败");
    }

}

  • 编写service接口
package com.czxy.zx.course.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.zx.domain.EduChapter;

import java.util.List;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
public interface EduChapterService extends IService<EduChapter> {
    /**
     * 删除章节
     * @param chapterId
     * @return
     */
    boolean removeChapterById(String chapterId);
}

  • 编写service实现类
package com.czxy.zx.course.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.zx.course.mapper.EduChapterMapper;
import com.czxy.zx.course.mapper.EduVideoMapper;
import com.czxy.zx.course.service.EduChapterService;
import com.czxy.zx.course.service.EduVideoService;
import com.czxy.zx.domain.EduChapter;
import com.czxy.zx.domain.EduVideo;
import com.czxy.zx.exception.EduException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@Service
@Transactional
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
    @Resource
    private EduVideoMapper eduVideoMapper;
    @Resource
    private EduVideoService eduVideoService;

    @Override
    public boolean removeChapterById(String chapterId) {
        QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("chapter_id", chapterId);
        List<EduVideo> list = eduVideoService.list(queryWrapper);
        if (list.size() != 0){
            throw new EduException("该章节下存在课时,请先删除课时");
        }

        int deleteById = baseMapper.deleteById(chapterId);
        return deleteById > 0;
    }

}

4.6.3 前端实现

  • 编写ajax函数
// 通过章节Id删除章节
export function deleteChapterById(chapterId) {
  return axios.delete(`/course-service/chapter/${chapterId}`);
}
  • 导入ajax函数

  • 编写ajax方法

    // 删除章节
    async deleteChapterByIdFn(chapterId){
      this.$confirm('您确定要删除该章节么?', '删除提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(async () => {
          // 发送ajax
          let baseResult = await deleteChapterById(chapterId)
          // 提示信息
          this.$message.success(baseResult.message)
          // 刷新页面
          this.findAllChapterFn()
        }).catch((response) => {
          // 错误提示
          this.$message.warning('删除失败或用户已取消!')  
        });
    },
  • 调用ajax函数
<el-button type="text" @click.stop="deleteChapterByIdFn(chapter.id)">删除</el-button>

4.7 删除课时

4.7.1 需求

  • 删除课时

  • 当用户点击取消时,提示信息

  • 当用户点击确定时,提示信息

4.7.2 后端实现

  • 编写Controller实现
package com.czxy.zx.course.controller;

import com.czxy.zx.course.service.EduVideoService;
import com.czxy.zx.domain.EduVideo;
import com.czxy.zx.vo.BaseResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author txt
 * @email tanxintong9968@163.com
 */
@RestController
@RequestMapping("/video")
public class EduVideoController {
    @Resource
    private EduVideoService eduVideoService;

    /**
     * 删除课时
     * @param videoId
     * @return
     */
    @DeleteMapping("/{videoId}")
    public BaseResult deleteById(@PathVariable("videoId") String videoId){
        boolean result = eduVideoService.removeById(videoId);
        if (result){
            return BaseResult.ok("删除课时成功");
        } else {
            return BaseResult.error("删除课时失败");
        }
    }

}

4.7.2 前端实现

  • 编写ajax函数
// 通过课时Id删除课时
export function deleteVideoById(videoId) {
  return axios.delete(`/course-service/video/${videoId}`);
}
  • 导入ajax函数

  • 编写ajax方法

    // 删除课时
    async deleteVideoByIdFn(videoId){
      this.$confirm('您确定要删除本节课么?', '删除提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(async() => {
          // 发送ajax
          let baseResult = await deleteVideoById(videoId)
          // 成功提示
          this.$message.success(baseResult.message)
          // 刷新页面
          this.findAllChapterFn()
        }).catch(() => {
          // 失败提示
          this.$message.warning('删除失败或用户已取消!')        
        });
    },
  • 调用ajax方法
<el-button type="text" @click.stop="deleteVideoByIdFn(video.id)">删除</el-button>

5.课程管理:发布课程

5.1 显示基本信息

  • 编写显示区域

    <!-- 提交审核 -->
        <div v-if="active==3">
          <el-row class="publish-info">
            <el-col :span="12" class="cover">
              <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80" alt="封面图片" >
            </el-col>
            <el-col :span="12">
              <h2>{{ course.title }}</h2>
              <p class="gray"><span>{{ course.lessonNum }}课时</span></p>
              <p><span>所属分类:{{ getSubject }}</span></p>
              <p>课程讲师:{{ getTeacher }}</p>
              <h3 class="red">{{ course.price | number }}</h3>
            </el-col>
          </el-row>
        </div>
    
  • 对应的CSS样式

    .publish-info {
        background: #F5F5F5;
      }
    
      .publish-info .cover {
        text-align: center;
      }
    
      .publish-info img{
        width: 500px;
        height:278px;
      }
      .publish-info .red{
        font-size: 28px;
        color: #d32f24;
      }
    
  • 使用“计算属性 computed” 处理 分类数据和讲师数据

    computed: {
        getTeacher() {
          // 过滤出选中的老师
          let teacher = this.teacherList.find(item=>{
            return item.id == this.course.teacherId
          })
          return teacher.name
        },
        getSubject() {
          // 过滤一级
          let oneSubject = this.subjectList.find(item=>{
            return item.id == this.course.subjectId[0]
          })
          // 过滤二级
          let twoSubject = oneSubject.children.find(item=>{
            return item.id == this.course.subjectId[1]
          })
          // 显示两级数据
          return oneSubject.title + " / " + twoSubject.title
        }
      },
    
  • 使用过滤器 filters 处理价格显示2位小数

      filters: {
        number(value) {
          return Number(value).toFixed(2)
        }
      },
    

5.2 接口

5.3 后端实现

  • 编写service接口

        /**
         * 发布
         * @param courseId
         * @return
         */
        boolean publish(String courseId);
    

  • 编写service实现类

        @Override
        public boolean publish(String courseId) {
            // 更新的数据
            EduCourse eduCourse = new EduCourse();
            eduCourse.setId(courseId);
            eduCourse.setStatus("Normal");
            // 更新操作
            int i = baseMapper.updateById(eduCourse);
            return i == 1;
        }
    

  • 编写controller

        @PutMapping("/publish/{courseId}")
        public BaseResult publish(@PathVariable("courseId") String courseId) {
            boolean result = eduCourseService.publish(courseId);
            if (result) {
                return BaseResult.ok("发布成功");
            }
            return BaseResult.error("发布失败");
        }
    

5.4 前端实现

  • 编写ajax函数

    // 发布课程
    export function coursePub(courseId) {
      return axios.put(`/course-service/course/publish/${courseId}`);
    }
    

  • 引入ajax函数

  • 完成发布功能

        async publish() {
          let { message } = await coursePub(this.course.id)
          this.$message.success(message)
          // 跳转到查询所有
          this.$router.push('/course/courseList')
        },
    

6.课程列表

6.1 需求

  • 接口

6.2 后端实现

  • 编写service接口

        /**
         * 查询所有
         * @param courseVo
         * @return
         */
        Page<EduCourse> findAll(Integer size, Integer current, CourseVo courseVo);
    

  • 编写service实现类

    
        @Override
        public Page<EduCourse> findAll(Integer size, Integer current, CourseVo courseVo) {
            // 1 条件查询
            QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
            if (courseVo.getSubjectId() != null && courseVo.getSubjectId().length > 1) {
                queryWrapper.eq(StringUtils.isNotBlank(courseVo.getSubjectId()[1]),"subject_id", courseVo.getSubjectId()[1]);
            }
            queryWrapper.like(StringUtils.isNotBlank(courseVo.getTitle()),"title", courseVo.getTitle());
            queryWrapper.eq(StringUtils.isNotBlank(courseVo.getTeacherId()),"teacher_id", courseVo.getTeacherId());
    
            // 2 分页条件
            Page<EduCourse> page = new Page<>(current,size);
    
            // 3 查询
            this.baseMapper.selectPage(page, queryWrapper);
    
            // 4 关联查询
    
            // 5 返回
            return page;
        }
    

  • 编写controller

        @PostMapping("/condition/{size}/{current}")
        public BaseResult findAll(
                @PathVariable("size") Integer size,
                @PathVariable("current") Integer current,
                @RequestBody CourseVo courseVo) {
            Page<EduCourse> page = eduCourseService.findAll(size,current,courseVo);
            return BaseResult.ok("查询成功", page);
        }
    

6.3 前端实现

6.3.1 显示页面

  • 编写路由

        {
          path: 'courseList',
          name: '课程列表',
          component: () => import('@/views/edu/course/courseList'),
          meta: { title: '课程列表', icon: 'list' }
        }
    

  • 创建vue页面

    <template>
      <div>
        课程列表
      </div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
    
    </style>
    
    

6.3.2 ajax 查询

// 查询课程
export function conditionCou(coursePage,courseVo) {
  return axios.post(`/course-service/course/condition/${coursePage.size}/${coursePage.current}`, courseVo);
}

6.3.3 内容展示

  • 条件
  • 展示
  • 分页
  • js代码
<template>
  <div class="app-container">
    <!-- 查询条件 -->
    <div class="filter-container">
      <el-form ref="conditionForm" :inline="true" size="mini" :model="courseVo" class="demo-form-inline">
        <el-form-item label="课程类别" >
          <el-cascader v-model="courseVo.subjectId" :options="subjectList" clearable :props="subjectProps" style="width:300px"></el-cascader>
        </el-form-item>
        <el-form-item label="课程标题">
          <el-input v-model="courseVo.title" clearable placeholder="请输入课程标题"></el-input>
        </el-form-item>
        <el-form-item label="课程讲师">
          <el-select v-model="courseVo.teacherId" clearable placeholder="请选择讲师">
            <el-option ref="xxx"
              v-for="(teacher,index) in teacherList"
              :key="index"
              :label="teacher.name"
              :value="teacher.id">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item >
          <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="conditionCourse">
            搜索
          </el-button>
          <el-button class="filter-item" style="margin-left: 10px;"  @click="clearCondition">
            清空
          </el-button>
        </el-form-item>
      </el-form>
    </div>

    <!-- 列表展示 -->
    <el-table
      v-loading="listLoading"
      :data="coursePage.records"
      border
      fit
      highlight-current-row
      style="width: 100%;"
    >
      <el-table-column label="ID" prop="id" align="center" width="80">
        <template slot-scope="{row,$index}">
          <span>{{ (coursePage.current-1) * coursePage.size + $index + 1 }}</span>
        </template>
      </el-table-column>
      <el-table-column label="课程信息" align="center" width="350">
        <template slot-scope="{row}">
          <el-row>
            <el-col :span="8">
              <img src="https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80" alt="封面图片" >
            </el-col>
            <el-col :span="16">
              <div>{{row.title}} </div>
              <div>{{row.lessonNum}}课时</div>
            </el-col>
          </el-row>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" width="120px" align="center">
        <template slot-scope="{row}">
          <span>{{ row.gmtCreate | parseTime('{y}-{m}-{d}')}}</span>
        </template>
      </el-table-column>
      <el-table-column label="发布时间" width="120px" align="center">
        <template slot-scope="{row}">
          <span>{{ row.gmtModified | parseTime('{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="价格" prop="id" align="center" width="100px">
        <template slot-scope="{row}">
          <span>{{ Number(row.price).toFixed(2) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="付费学院" width="120px" align="center">
        <template slot-scope="{row}">
          <span>{{ row.buyCount }}</span>
        </template>
      </el-table-column>
      <el-table-column label="状态" prop="status" align="center" width="100px">
        <template slot-scope="{row}">
          <span>{{ row.status == 'Draft' ? '未发布' : '已发布' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
        <template slot-scope="{row,$index}">
          <el-button type="primary" size="mini" @click="updateCoursePre(row)">
            修改
          </el-button>
          <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="deleteCourse(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页条 -->
    <pagination v-show="coursePage.total>0" :total="coursePage.total" :pageSizes="[1,2,3,5]" :page.sync="coursePage.current" :limit.sync="coursePage.size" @pagination="conditionCourse" />

  </div>
</template>

<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive

// 导入 ajax
import { findAllSub, conditionCou } from '@/api/edu/course'
import { findAll } from '@/api/edu/teacher'

import { parseTime } from '@/utils'

export default {
  components: { Pagination },
  directives: { waves },
  data() {
    return {
      subjectList: [] ,   // 所有科目
      subjectProps: {     // 科目数据与级联选择对应关系
        value: 'id',
        label: 'title'
      },
      courseVo: {         // 查询条件

      },
      teacherList: [],    // 讲师列表
      listLoading: true,
      coursePage: {       // 课程列表
        current: 1,
        size: 3,
        total: 0
      }
    }
  },
  methods: {
    async findAllSubject() {  // 查询所有科目
      let { data } = await findAllSub()
      this.subjectList = data
    },
    async findAllTeacher() {  // 查询所有科目
      let { data } = await findAll()
      this.teacherList = data
    },
    async conditionCourse() {
      this.listLoading = true
      // 查询
      let { data } = await conditionCou(this.coursePage, this.courseVo)
      this.coursePage = data
      this.listLoading = false
    },
    clearCondition() {
      this.courseVo = {}
    }
  },
  mounted() {
    // 查询所有科目
    this.findAllSubject()
    // 查询所有老师
    this.findAllTeacher()
    // 条件查询
    this.conditionCourse()
  },

}
</script>

<style>

</style>

7.课程管理:编辑课程

7.1 需求

  • 点击“修改”,跳转到“发布课程”,对该课程进行修改

  • 接口

7.2 页面跳转

    updateCoursePre(row) {
      let id = row.id
      // 跳转到发布页面
      this.$router.push(`/course/publish?id=${id}`)
    }

7.2 后端实现:回显

  • 通过id查询EduCourse,并将查询结果封装成 CourseVo

  • 编写Service接口

        /**
         * 查询详情
         * @param courseId
         * @return
         */
        CourseVo findById(String courseId);
    
  • 编写Service实现类

    @Override
        public CourseVo findById(String courseId) {
            // 1 查询详情
            EduCourse eduCourse = baseMapper.selectById(courseId);
    
            // 2 封装基本数据
            CourseVo courseVo = new CourseVo();
            BeanUtils.copyProperties(eduCourse, courseVo);
    
            // 3 分类数据
            courseVo.setSubjectId(new String[]{eduCourse.getSubjectParentId(), eduCourse.getSubjectId()});
    
            // 4 描述数据
            EduCourseDescription eduCourseDescription = eduCourseDescriptionMapper.selectById(courseId);
            courseVo.setDescription(eduCourseDescription.getDescription());
    
    
            return courseVo;
        }
    
  • 编写Controller

        @GetMapping("/{courseId}")
        public BaseResult findById(@PathVariable("courseId") String courseId) {
            CourseVo courseVo = eduCourseService.findById(courseId);
            return BaseResult.ok("查询成功", courseVo);
        }
    

7.3 前端实现:回显

  • 编写ajax函数

    // 查询课程详情
    export function findCouById(courseId) {
      return axios.get(`/course-service/course/${courseId}`);
    }
    
  • 引入ajax函数

  • 页面加载成功,如果有id参数,将查询课程详情

        // 查询课程详情
        let courseId = this.$route.query.id
        if( courseId ) {
          this.course.id = courseId
          this.findCourseById()
        }
    
  • 编写查询详情函数

        async findCourseById() {
          debugger
          // 查询
          let { data } = await findCouById(this.course.id)
          this.course = data
        }
    

8. 作业:删除课程

相信大家跟着上面的进度已经到了这一步,这个【删除课程】环节就留给各位小伙伴啦,如果遇到什么问题欢迎各位小伙伴下方留言!
届时欢迎大家一起交流学习~

end

好啦,以上就是本期全部内容,能看到这里的人呀,都是能人

十年修得同船渡,大家一起点关注。

我是话里人,感谢各位【能人】的:点赞收藏评论,我们下期见!

各位能人们的支持就是话里人前进的巨大动力~

注:如果本篇Blog有任何错误和建议,欢迎能人们留言!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

♚焕蓝·未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值