前言
哈喽!我是话里人,本篇Blog将会手把手带大家《如何从0到1进行课程发布》,让大家理清各个环节流程,快速上手,话不多说,那咱们书接上回~
在线教育 -- 如何从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有任何错误和建议,欢迎能人们留言!