31.添加课程基本信息功能(前后端)
一、功能预实现

步骤1包含:课程标题、课程分类、总课时、课程讲师、总课时、课程简介、课程封面、课程价格等属性
步骤2包含:
步骤3包含:
需要注意的细节问题:
(1)创建vo实体类用于表单数据封装
(2)把表单提交过来的数据添加到数据库
需要向两张表添加数据:课程表和课程描述表
(3)把讲师和分类使用下列列表显示
课程分类 做成二级联动效果
二、课程相关表的关系
我们需要用到的数据库表有:
课程分类表(edu_subject):用于存储课程分类信息
课程表(edu_course):用于存储课程基本信息
课程章节表(edu_chapter):用于存储课程章节信息
讲师表(edu_teacher):用于存储讲师信息
课程描述表(edu_course_description):用于存储课程简介信息
课程小节表(edu_video):用于存储章节里的小节信息
其关系如下:

数据库表代码:
CREATE TABLE `edu_subject` ( `id` char(19) NOT NULL COMMENT '课程类别ID', `title` varchar(10) NOT NULL COMMENT '类别名称', `parent_id` char(19) NOT NULL DEFAULT '0' COMMENT '父ID', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程科目'; CREATE TABLE `edu_course` ( `id` char(19) NOT NULL COMMENT '课程ID', `teacher_id` char(19) NOT NULL COMMENT '课程讲师ID', `subject_id` char(19) NOT NULL COMMENT '课程专业ID', `subject_parent_id` char(19) NOT NULL COMMENT '课程专业父级ID', `title` varchar(50) NOT NULL COMMENT '课程标题', `price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '课程销售价格,设置为0则可免费观看', `lesson_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '总课时', `cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '课程封面图片路径', `buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '销售数量', `view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '浏览数量', `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁', `status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT '课程状态 Draft未发布 Normal已发布', `is_deleted` tinyint(3) DEFAULT NULL COMMENT '逻辑删除 1(true)已删除, 0(false)未删除', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_title` (`title`), KEY `idx_subject_id` (`subject_id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程'; CREATE TABLE `edu_chapter` ( `id` char(19) NOT NULL COMMENT '章节ID', `course_id` char(19) NOT NULL COMMENT '课程ID', `title` varchar(50) NOT NULL COMMENT '章节名称', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '显示排序', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程'; CREATE TABLE `edu_teacher` ( `id` char(19) NOT NULL COMMENT '讲师ID', `name` varchar(20) NOT NULL COMMENT '讲师姓名', `intro` varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介', `career` varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师', `level` int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师', `avatar` varchar(255) DEFAULT NULL COMMENT '讲师头像', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序', `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='讲师'; CREATE TABLE `edu_course_description` ( `id` char(19) NOT NULL COMMENT '课程ID', `description` text COMMENT '课程简介', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程简介'; CREATE TABLE `edu_video` ( `id` char(19) NOT NULL COMMENT '视频ID', `course_id` char(19) NOT NULL COMMENT '课程ID', `chapter_id` char(19) NOT NULL COMMENT '章节ID', `title` varchar(50) NOT NULL COMMENT '节点名称', `video_source_id` varchar(100) DEFAULT NULL COMMENT '云端视频资源', `video_original_name` varchar(100) DEFAULT NULL COMMENT '原始文件名称', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段', `play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '播放次数', `is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否可以试听:0收费 1免费', `duration` float NOT NULL DEFAULT '0' COMMENT '视频时长(秒)', `status` varchar(20) NOT NULL DEFAULT 'Empty' COMMENT 'Empty未上传 Transcoding转码中 Normal正常', `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '视频源文件大小(字节)', `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`), KEY `idx_chapter_id` (`chapter_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程视频';
三、后端接口实现
1、使用代码生成器生成新添加表的相关代码
strategy.setInclude("edu_course","edu_course_description","edu_chapter","edu_video");
2、创建vo类封装表单提交的数据
在entity/vo下创建CourseInfoVo实体类
@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "课程ID")
private String id;
@ApiModelProperty(value = "课程讲师ID")
private String teacherId;
@ApiModelProperty(value = "课程专业ID")
private String subjectId;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
private BigDecimal price;//精确到0.01
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "课程简介")
private String description;
}
3、编写controller和service部分
在EduCourseController类中添加 添加课程基本信息方法代码:
@RestController
@RequestMapping("/eduservice/edu-course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService courseService;
//添加课程基本信息的方法
@PostMapping("addCourseInfo")
public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
//返回添加之后的课程id,为了后面添加大纲使用
String id = courseService.saveCourseInfo(courseInfoVo);
return R.ok().data("courseId",id);
}
}
在EduCourseServiceImpl中实现saveCourseInfo方法:
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
private EduCourseDescriptionService courseDescriptionService;
//添加课程基本信息
@Override
public String saveCourseInfo(CourseInfoVo courseInfoVo) {
//1 向课程表添加课程基本信息
//CourseInfoVo转换城eduCourse对象
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
int insert = baseMapper.insert(eduCourse);
if(insert==0) {
//添加失败
throw new GuliException(20001,"添加课程信息失败");
}
//获取添加之后课程id
String cid = eduCourse.getId();
//2 向课程简介表添加课程简介
EduCourseDescription eduCourseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,eduCourseDescription);
//手动设置id就是课程cid
eduCourseDescription.setId(cid);
courseDescriptionService.save(eduCourseDescription);
return cid;
}
}
注意:课程和描述是一对一关系,添加之后,id值是一样的
因此,课程描述表的实体类的id为手动输入,需修改对应实体类:
@ApiModelProperty(value = "课程ID")
@TableId(value = "id", type = IdType.INPUT)//表手动设置
private String id;
四、前端页面实现
1、添加课程功能路由
{
path: '/course',
component: Layout,
redirect: '/course/table',
name: '课程管理',
meta: { title: '课程管理', icon: 'example' },
children: [
{
path: 'table',
name: '课程列表',
component: () => import('@/views/edu/course/list'),
meta: { title: '课程列表', icon: 'table' }
},
{
path: 'info',
name: '添加课程',
component: () => import('@/views/edu/course/info'),
meta: { title: '添加课程', icon: 'tree' }
},
{
path: 'info/:id',
name: 'EduCourseInfoEdit',
component: () => import('@/views/edu/course/info'),
meta: { title: '编辑课程基本信息', noCache: true },
hidden: true
},
{
path: 'chapter/:id',
name: 'EduCourseChapterEdit',
component: () => import('@/views/edu/course/chapter'),
meta: { title: '编辑课程大纲', noCache: true },
hidden: true
},
{
path: 'publish/:id',
name: 'EduCoursePublishEdit',
component: () => import('@/views/edu/course/publish'),
meta: { title: '发布课程', noCache: true },
hidden: true
}
]
}
注意:第3、4、5为隐藏路由,做页面跳转
2、在api/edu/course.js中添加 添加课程信息接口方法
export default {
// 1 添加课程信息
addCourseInfo(courseInfo){
return request({
url: `/eduservice/edu-course/addCourseInfo`,
method: 'post',
data:courseInfo
})
},
//2 查询所有讲师
getListTeacher(courseInfo){
return request({
url: `/eduservice/edu-teacher/findAll`,
method: 'get',
})
}
}
3、添加课程页面实现
在info.vue中添加代码:
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 所属分类 TODO -->
<!-- 课程分类 -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类" @change="subjectLevelOneChanged">
<el-option
v-for="subject in subjectOneList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="二级分类">
<el-option
v-for="subject in subjectTwoList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
</el-form-item>
<el-form-item label="总课时">
<el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
</el-form-item>
<!-- 课程讲师 TODO -->
<!-- 课程讲师 -->
<el-form-item label="课程讲师">
<el-select
v-model="courseInfo.teacherId"
placeholder="请选择">
<el-option
v-for="teacher in teacherList"
:key="teacher.id"
:label="teacher.name"
:value="teacher.id"/>
</el-select>
</el-form-item>
<el-form-item label="总课时">
<el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
</el-form-item>
<!-- 课程简介 TODO -->
<el-form-item label="课程简介">
<el-input v-model="courseInfo.description" placeholder=""/>
</el-form-item>
<!-- 课程封面 TODO -->
<!-- 课程封面-->
<el-form-item label="课程封面">
<el-upload
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:action="BASE_API+'/eduoss/fileoss'"
class="avatar-uploader">
<img :src="courseInfo.cover">
</el-upload>
</el-form-item>
<el-form-item label="课程价格">
<el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存并下一步</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
export default {
data() {
return {
saveBtnDisabled: false, // 保存按钮是否禁用
courseInfo:{
title: '',
subjectId: '',//二级分类id
subjectParentId: '',//一级分类id
teacherId: '',
lessonNum: 0,
description: '',
cover: '/static/2.jpg',
price: 0
},
BASE_API: process.env.BASE_API, // 接口API地址
teacherList:[],//封装所有的讲师
subjectOneList:[],//一级分类
subjectTwoList:[]
}
},
created() {
//初始化所有讲师
this.getListTeacher()
//初始化一级分类
this.getOneSubject()
},
methods: {
//上传封面成功调用的方法
handleAvatarSuccess(res, file){
this.courseInfo.cover = res.data.url
},
//上传之前调用的方法
beforeAvatarUpload(file){
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
},
//点击某个一级分类,出发change,显示对应二级分类
subjectLevelOneChanged(value){
//value就是一级分类id值
//遍历所有的分类,包含一级和二级分类
for (var i = 0; i < this.subjectOneList.length; i++) {
//每个一级分类
var onesubject = this.subjectOneList[i]
//判断所有一级分类id和点击一级分类id是否一样
if (onesubject.id === value) {
// 从一级分类获取里面所有的二级分类
this.subjectTwoList = onesubject.children
//把二级分类id值清空
this.courseInfo.subjectId = ''
}
}
},
//查询所有的一级分类
getOneSubject(){
subject.getSubjectList()
.then(response=>{
this.subjectOneList = response.data.list
})
},
//查询所有讲师
getListTeacher(){
course.getListTeacher()
.then(response=>{
this.teacherList = response.data.items
})
},
saveOrUpdate() {
course.addCourseInfo(this.courseInfo)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '添加课程信息成功!'
});
//跳转到第二步
this.$router.push({ path: '/course/chapter/'+response.data.courseId })
})
}
}
}
</script>
其中课程讲师通过获取所有讲师,再进行遍历实现;课程分类有两类,一级分类先得到所有分类,再遍历得到,二级分类通过得到的一级分类id得到;课程封面则保存到oss中即可,上传之前需要判断图片格式和大小。
五、课程简介(文本编辑器)
我们使用富文本编辑器来实现课程简介,如下图所示:

1、下载文本编辑器组件
下载地址:https://wws.lanzous.com/iODqlnbwtje 密码:1o42
将下载后的组件放到项目中对应的位置
2、在build/webpack.dev.conf.js中添加配置
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
favicon: resolve('favicon.ico'),
title: 'vue-admin-template',
templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}
})
3、在根目录的index.html中引入脚本文件
<body>
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
4、在info.vue页面中引入并声明组件
import Tinymce from '@/components/Tinymce'
export default {
//声明组件
components: { Tinymce },
5、在页面中使用标签实现
<!-- 课程简介-->
<el-form-item label="课程简介">
<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>
浙公网安备 33010602011771号