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>