一、课程发布分析
1、发布流程图
2、数据表对应
3、课程相关表的关系
二、课程管理–添加课程后端
1、使用代码生成器生成相关课程的代码
2、细节问题
(1)创建vo实体类用户表单数据封装 vo:ViewObject:用于后台向前台显示所创建的实体
(2)把表单提交过来的数据添加到数据库 ==>向两张表添加数据:课程表 和 课程描述表
(3)把讲师和分类使用下拉列表显示 课程分类 做成二级联动
3、创建vo实体类
@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 = "课程专业父级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 String description;
}
4、编写Controller类
@Api(description = "课程管理")
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService courseService;
@ApiOperation("添加课程基本信息")
@PostMapping("saveCourseInfo")
public R saveCourseInfo(@ApiParam(name = "CourseInfoForm", value = "课程基本信息", required = true)
@RequestBody CourseInfoVo courseInfoVo){
//返回课程id,为了跳转到课程大纲使用
String courseId = courseService.saveCourseInfo(courseInfoVo);
return R.ok().data("courseId", courseId);
}
}
5、编写Service类
@Service
@Transactional //开启事务管理
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
private EduCourseDescriptionService courseDescriptionService;
//添加课程基本信息
@Override
public String saveCourseInfo(CourseInfoVo courseInfoVo) {
//1.添加课程基本信息信息到课程表
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
int insert = this.baseMapper.insert(eduCourse);
if(insert==0){
throw new GuLiException(20001, "添加课程失败!");
}
//2.添加课程简介信息到简介表
String id = eduCourse.getId();//获得添加之后的课程id
EduCourseDescription eduCourseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,eduCourseDescription);
eduCourseDescription.setId(id);//因为两张表是一对一关系,所以id也应该相同
boolean save = courseDescriptionService.save(eduCourseDescription);
if(save==false){
throw new GuLiException(20001, "添加课程失败!");
}
return id;
}
}
注意问题:
(1)课程和描述是一对一关系,添加之后,id应该是相同的。所以描述的id要填入课程的id.
EduCourseDescription类中id 属性@TableId(value = "id", type = IdType.INPUT)
(2)EduCourse类和EduCourseDescription类中的时间需要自动填充@TableField(fill = FieldFill.INSERT)
6、启动Swagger测试
…
三、课程管理–添加课程前端(填写课程基本信息)
1、添加课程管理路由
api/router/index.js
{
path: '/course',
component: Layout, //布局
redirect: '/course/table',
name: 'CourseAdd',
meta: { title: '课程管理', icon: 'form' },
children: [{
path: 'list',
name: 'EduCourseList',
component: () =>
import ('@/views/edu/course/list'),
meta: { title: '课程列表', icon: 'table' }
},
{
path: 'info',
name: 'EduCourseInfo',
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
}
]
},
**注意:**这里有三个模块添加隐藏域是为了形成 下一步—>下一步—>确认发布 上一步—>上一步—>取消发布 的效果
2、添加Vue组件
3、整合步骤条组件实现页面跳转
参考:http://element-cn.eleme.io/#/zh-CN/component/steps
1、课程页面信息— 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>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存并下一步</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
saveBtnDisabled:false // 保存按钮是否禁用
}
},
created() {
},
methods:{
saveOrUpdate() {
//跳转到第二步
this.$router.push({path:'/course/chapter/1'})
}
}
}
</script>
2、课程大纲页面—chapter.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" 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>
<el-button @click="previous">上一步</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
saveBtnDisabled: false // 保存按钮是否禁用
}
},
created() {
},
methods: {
previous() {
this.$router.push({ path: '/edu/course/info/1' })
},
next() {
//跳转到第二步
this.$router.push({ path: '/edu/course/publish/1' })
}
}
}
</script>
3、课程发布页面—publish.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-form label-width="120px">
<el-form-item>
<el-button @click="previous">返回修改</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
saveBtnDisabled: false // 保存按钮是否禁用
}
},
created() {
console.log('publish created')
},
methods: {
previous() {
console.log('previous')
this.$router.push({ path: '/course/chapter/1' })
},
publish() {
console.log('publish')
this.$router.push({ path: '/course/list' })
}
}
}
</script>
4、定义api
api/edu/course.js
import request from '@/utils/request'
export default {
//1.添加课程信息
saveCourseInfo(courseInfo) {
return request({
url: `/eduservice/course/saveCourseInfo`,
method: 'post',
data: courseInfo
})
}
}
5、添加课程表单
src/views/edu/info.vue
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 所属分类 TODO -->
<!-- 课程讲师 TODO -->
<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-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>
<script>
import course from '@/api/edu/course'
export default {
data() {
return {
saveBtnDisabled:false,
courseInfo:{
title: '',
subjectId: '',//二级分类id
subjectParentId:'',//一级分类id
teacherId: '',
lessonNum: 0,
description: '',
cover: '',
price: 0
},
}
},
created() {
},
methods:{
//提交课程信息
saveOrUpdate(){
course.saveCourseInfo(this.courseInfo)
.then(response=>{
this.$message({
type:'success',
message:'保存成功!'
})
//跳转到课程大纲页面
this.$router.push({path:'/course/chapter/'+response.data.courseId})
}).catch(response=>{
this.$message({
type:'error',
message:'保存失败!'
})
})
}
}
}
</script>
**主意:**TODO是为了后续进行完善,一个项目都是从简单到复杂,功能一步步变繁多的.
7、讲师下拉列表
(1)组件模板
<!-- 课程讲师 -->
<el-form-item label="课程讲师">
<el-select
filterable
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>
(2)定义api
api/edu/teacher.js
//查询所有讲师
getTeacherList() {
return request({
url: `/eduservice/teacher/findAll`,
method: 'get'
})
}
组件中引入teacher的api
import teacher from '@/api/edu/teacher'
(3)组件js代码
定义data
teacherList: [] // 讲师列表
表单初始化时获取讲师列表
created() {
this.getTeacherList()
},
methods: {
//....
//查询所有讲师
getTeacherList(){
course.getTeacherList().then(response=>{
this.teacherList = response.data.items
})
}
}
(4)页面效果展示
8、课程分类多级联动
1、两个下拉列表实现
思路分析:使用多个下拉列表来达到多级联动效果,前面的被触发,后面的下拉列表被赋值填充.
(1)组件数据定义—定义data中
subjectOneList:[],//一级分类
subjectTwoList:[]//二级分类
(2)组件模板
<el-form-item label="课程分类">
<el-select
filterable
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 filterable 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>
(3)组件脚本
created() {
//初始化一级分类
this.getOneSubject()
},
method:{
//....
//查询所有的一级分类
getOneSubject() {
subject.getSubjectList()
.then(response => {
this.subjectOneList = response.data.list
})
},
点击某个一级分类,触发change,显示对应二级分类
subjectLevelOneChanged(value){
//value就是一级分类id值
//遍历所有的分类,包含一级和二级
for(var i=0;i<this.subjectOneList.length;i++) {
//每个一级分类
var oneSubject = this.subjectOneList[i]
//判断:所有一级分类id 和 点击一级分类id是否一样
if(value === oneSubject.id) {
//从一级分类获取里面所有的二级分类
this.subjectTwoList = oneSubject.children
}
}
//把二级分类id值清空
this.courseInfo.subjectId = ''
}
}
(1)页面效果
2、使用级联组件
(1)组件模板
<el-form-item label="课程分类">
<el-cascader
filterable
v-model="subject"
:options="subjectList"
:props="subProps"
@change="handleChange">
</el-cascader>
</el-form-item>
(2)模板数据
courseInfo:{
//...
subjectId: '',//一级分类id
subjectParentId:'',//二级分类id
//...
},
subjectList:[],//课程分类列表
subProps:{//模板数据
value:'id',
label:'title',
children:'children'
},
subject:[],//存放级联列表的id数组
(3)组件js脚本
handleChange(){//参数为value数据,如果不需要可以不写.
console.log(this.subject);
this.courseInfo.subjectId = this.subject[1] //将对应的一二级分类id放入到courseInfo中
this.courseInfo.subjectParentId = this.subject[0]
}
(4)页面效果
9、课程封面
参考 http://element-cn.eleme.io/#/zh-CN/component/upload 用户头像上传
1、定义data数据模板
courseInfo:{
//....
cover: 'https://guli-photos.oss-cn-hangzhou.aliyuncs.com/course.jpg',//默认头像
//....
},
BASE_API: process.env.BASE_API // 接口API地址
2、组件模板
<el-form-item label="课程封面">
<el-upload
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:action="BASE_API+'/ossService/file/upload'"
class="avatar-uploader">
<img :src="courseInfo.cover">
</el-upload>
</el-form-item>
3、结果回调
handleAvatarSuccess(res,file){//头像上传成功后
this.courseInfo.cover = res.data.url
},
beforeAvatarUpload(file){//上传头像之前进行照片校验
const isPic = (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isPic) {
this.$message.error('上传头像图片只能是 JPG/JPEG/PNG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isPic && isLt2M
}
10、富文本编辑器
1、Tinymce可视化编辑器
参考:
https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce
https://panjiachen.gitee.io/vue-element-admin/#/example/create
2、组件初始化
(1)复制脚本库
将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)
(2)配置html变量
在 guli-admin/build/webpack.dev.conf.js 中添加配置,使在html页面中可是使用这里定义的BASE_URL变量
new HtmlWebpackPlugin({
//.....
templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}
})
(3)引入js脚本
在guli-admin/index.html 中引入js脚本
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
3、组件引入
(1)引入组件
info.vue中引入 Tinymce
import Tinymce from '@/components/Tinymce'
export default {
components: { Tinymce },
......
}
(2)组件模板
<!-- 课程简介-->
<el-form-item label="课程简介">
<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>
(3)组件样式
<style scoped>
.tinymce-container {
line-height: 29px;
}
</style>
(4)组件效果图
11、课程基本信息页面效果展示
四、课程大纲列表 - 后端开发
1、创建两个实体类,章节和小节
ChapterVo
@ApiModel(value = "章节封装类",description = "章节封装类")
@Data
public class ChapterVo {
private String id;
private String title;
//表示小节
private List<VideoVo> children = new ArrayList<>();
}
VideoVo
@ApiModel(value = "小节封装类",description = "小节封装类")
@Data
public class VideoVo {
private String id;
private String title;
}
2、编写Controller
@Api(description = "课程大纲")
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin
public class EduChapterController {
@Autowired
private EduChapterService chapterService;
//课程大纲列表,根据课程id进行查询
@ApiOperation("根据课程id查询章节和小节")
@GetMapping("getChapterVideo/{courseId}")
public R getChapterVideo(@PathVariable String courseId){
List<ChapterVo> chapterVoList = chapterService.getChapterVideo(courseId);
return R.ok().data("list",chapterVoList);
}
}
3、编写Service
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
@Autowired
private EduVideoService videoService;
//根据课程id查询课程大纲
@Override
public List <ChapterVo> getChapterVideo(String courseId) {
//1.查询所有章节
QueryWrapper <EduChapter> chapterWrapper = new QueryWrapper <>();
chapterWrapper.eq("course_id",courseId);
List <EduChapter> chapterList = this.baseMapper.selectList(chapterWrapper);
//2.查询所有的小节
QueryWrapper <EduVideo> videoWrapper = new QueryWrapper <>();
videoWrapper.eq("course_id",courseId);
List <EduVideo> videoList = videoService.list(videoWrapper);
//定义最终的数据类型
List <ChapterVo> finalChapterList = new ArrayList <ChapterVo>();
//3.遍历章节集合,将其封装到List <ChapterVo>
for (EduChapter chapter : chapterList) {
ChapterVo chapterVo = new ChapterVo();
BeanUtils.copyProperties(chapter,chapterVo);
finalChapterList.add(chapterVo);//将其放入结果集
List <VideoVo> children = new ArrayList <>();
//4.遍历小节集合,将其封装到对应的章节中
for (EduVideo video : videoList) {
if(video.getChapterId().equals(chapter.getId())){//如果当前小节输入当前章节,则加入该章节
VideoVo videoVo = new VideoVo();
BeanUtils.copyProperties(video,videoVo);
children.add(videoVo);
}
}
chapterVo.setChildren(children);//将章节的对应小节设置进去
}
return finalChapterList;
}
}
4、使用Swagger进行测试
…
五、课程大纲列表 – 前端开发
1、页面模板
<!-- 章节 -->
<ul class="chanpterList">
<li
v-for="chapter in chapterVideoList"
:key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
<el-button style="" type="text" @click="openEditChatper(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
</span>
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>{{ video.title }}
<span class="acts">
<el-button style="" type="text" @click="openEditVideo(video.id)">编辑</el-button>
<el-button type="text" @click="removeVideo(video.id)">删除</el-button>
</span>
</p>
</li>
</ul>
</li>
</ul>
<div>
<el-button @click="previous">上一步</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
</div>
2、页面样式
<style scoped>
.chanpterList{
position: relative;
list-style: none;
margin: 0;
padding: 0;
}
.chanpterList li{
position: relative;
}
.chanpterList p{
float: left;
font-size: 20px;
margin: 10px 0;
padding: 10px;
height: 70px;
line-height: 50px;
width: 100%;
border: 1px solid #DDD;
}
.chanpterList .acts {
float: right;
font-size: 14px;
}
.videoList{
padding-left: 50px;
}
.videoList p{
float: left;
font-size: 14px;
margin: 10px 0;
padding: 10px;
height: 50px;
line-height: 30px;
width: 100%;
border: 1px dotted #DDD;
}
</style>
3、定义api
import request from '@/utils/request'
export default {
//根据课程id查询章节和小节
getChapterVideo(courseId) {
return request({
url: `/eduservice/chapter/getChapterVideo/${courseId}`,
method: 'get'
})
}
}
4、页面调用
在views/edu/course/chapter.vue 中
created() {
//获取路由中的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//调用 根据课程id查询章节和小节
this.getChapterVideo()
}
},
methods:{
//根据课程id查询章节和小节
getChapterVideo() {
chapter.getChapterVideo(this.courseId)
.then(response =>{
this.chapterVideoList = response.data.allChapterVideo
})
},
//......
}
5、效果展示
六、修改课程基本信息(从大纲返回到课程基本信息页面)
流程:点击上一步的时候,回到第一步页面,把课程基本信息数据回显。修改数据后点击保存实现更新数据效果。
1、后端Controller类
//根据课程id查询课程信息
@ApiOperation("查询课程信息")
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId){//@PathVariable可以省略嘛???
CourseInfoVo courseInfoVo = courseService.getCourseInfo(courseId);
return R.ok().data("courseInfoVo",courseInfoVo);
}
//根据课程id修改课程信息
@ApiOperation("修改课程信息")
@PutMapping("updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
courseService.updateCourseInfo(courseInfoVo);
return R.ok();
}
2、后端Service类
//通过id查询课程信息
@Override
public CourseInfoVo getCourseInfo(String courseId) {
//1.查询课程信息
EduCourse course = this.baseMapper.selectById(courseId);
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(course,courseInfoVo);
//2.查询课程描述信息
EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
courseInfoVo.setDescription(courseDescription.getDescription());
return courseInfoVo;
}
//修改课程信息
@Override
public void updateCourseInfo(CourseInfoVo courseInfoVo) {
//1.修改课程表
EduCourse course = new EduCourse();
BeanUtils.copyProperties(courseInfoVo,course);
int update1 = this.baseMapper.updateById(course);
if(update1==0){
throw new GuLiException(20001, "修改失败!");
}
//2.修改课程描述表
EduCourseDescription courseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,courseDescription);
boolean flag = courseDescriptionService.updateById(courseDescription);
if(!flag){
throw new GuLiException(20001, "修改失败!");
}
}
3、定义前端两个接口
api/edu/course.js
//根据id查询课程信息
getCourseInfo(courseId) {
return request({
url: `/eduservice/course/getCourseInfo/${courseId}`,
method: 'get'
})
},
//根据id修改课程信息
updateCourseInfo(courseInfoVo) {
return request({
url: `/eduservice/course/updateCourseInfo`,
method: 'put',
data: courseInfoVo
})
}
4、修改/edu/course/chapter.vue中路径
5、在info.vue页面实现数据回显
created() {
//获取路由id值,回显数据
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//调用根据id查询课程的方法
this.getCourseInfo()
}else{
//初始化courseInfo
this.courseInfo = {cover: 'https://guli-photos.oss-cn-hangzhou.aliyuncs.com/course.jpg'}//先清空...
//初始化一级分类
this.getOneSubject()
}
//初始化讲师列表
this.getListTeacher()
},
methods:{
//根据课程id查询,回显数据
getCourseInfo(){
course.getCourseInfo(this.courseId)
.then(response => {
//在courseInfo课程基本信息,包含一级分类id和二级分类id
this.courseInfo = response.data.courseInfoVo
//需要补充代码的地方...
})
}
//....
}
注意
:这里我们在运行的时候发现二级分类下拉框没有数据,因为subjectTwoList数组为空,所以只显示查询出来的id。
解决办法就是
:因为在courseInfo课程基本信息,包含一级分类id和二级分类id,所以先查询所有一级分类,然后遍历所有一级分类,比较当前courseInfo里面一级分类id 和 所有的一级分类id,如果相同则遍历一级分类下面的二级分类。
subject.getAllSubject().then(response=>{
this.subjectOneList = response.data.list
for(var i = 0;i < this.subjectOneList.length;i++){
if(this.subjectOneList[i].id==this.courseInfo.subjectParentId){{
this.subjectTwoList = this.subjectOneList[i].children
}}
}
});
此时启动服务测试,页面所以数据都可正常回显。
6、更新数据 js
这里我们对之前写的加以修改,把添加和修改方法单独提出来写
addCourseInfo(){//添加课程基本信息
course.saveCourseInfo(this.courseInfo)
.then(response=>{
this.$message({
type:'success',
message:'课程添加成功!'
})
//跳转到课程大纲页面
this.$router.push({path:'/course/chapter/'+response.data.courseId})
}).catch(response=>{
this.$message({
type:'error',
message:'课程添加失败!'
})
})
},
updateCourseInfo(){//修改课程基本信息
course.updateCourseInfo(this.courseInfo).then(response=>{
this.$message({
type:'success',
message:'课程修改成功!'
})
//跳转到课程大纲页面
this.$router.push({path:'/course/chapter/'+this.courseId})
}).catch(response=>{
this.$message({
type:'error',
message:'课程修改失败!'
})
})
},
//提交课程信息
saveOrUpdate(){
if(!this.courseInfo.id){
// console.log("添加课程");
this.addCourseInfo()
}else{
// console.log("修改课程");
this.updateCourseInfo()
}
},
7、解决从修改页面跳转到添加页面,页面数据保留的问题
添加一个路径监听,路径发生变化就执行一次初始化方法
created() {
this.init()
},
watch:{//监听
$route(to,from){//当路由路径发生变化时,方法就会被执行
this.init()
}
},
methods: {
init(){//初始化方法封装
if(this.$route.params && this.$route.params.id){//如果是数据回显
this.courseId = this.$route.params.id
//查询课程信息
this.getCourseInfo()
}else{//如果是课程添加
this.courseInfo = {cover: 'https://guli-photos.oss-cn-hangzhou.aliyuncs.com/course.jpg'}//先清空...
this.getOneSubject()//查询所有一级分类
}
// this.getAllSubject()//方法二:级联下拉菜单
this.getTeacherList()
}
}
8、启动服务测试即可
七、课程章节添加、修改、删除 --后端开发
1、编写Controller类
//查询章节
@GetMapping("getChapter/{id}")
@ApiOperation("查询章节通过id")
public R getChapter(@PathVariable String id){
EduChapter chapter = chapterService.getById(id);
return R.ok().data("chapter",chapter);
}
//添加章节
@PostMapping("addChapter")
@ApiOperation("添加章节")
public R addChapter(@RequestBody EduChapter chapter){
boolean save = chapterService.save(chapter);
if(save){
return R.ok();
}else{
return R.error();
}
}
//修改章节
@ApiOperation("修改章节")
@PutMapping("updateChapter")
public R updateChapter(@RequestBody EduChapter chapter){
boolean update = chapterService.updateById(chapter);
if(update){
return R.ok();
}else{
return R.error();
}
}
//删除章节
@DeleteMapping("delChapter/{id}")
public R delChapter(@PathVariable String id){
Boolean flag = chapterService.delChapter(id);
if (flag){
return R.ok();
}else{
return R.error();
}
}
注意:在实体类中的时间加上@TableField(fill = FieldFill.INSERT)
等注解
2、编写Service类
//删除章节
@Override
public Boolean delChapter(String chapterId) {
//1.先查询该章节下是否有小节
QueryWrapper <EduVideo> wrapper = new QueryWrapper <>();
wrapper.eq("chapter_id", chapterId);
int count = videoService.count(wrapper);
//2.如果有则 无法删除
if(count>0){
throw new GuLiException(20001,"清先删除小节,再删除章节!");
}else{
//如果没有则进行删除..
int delete = this.baseMapper.deleteById(chapterId);
return delete > 0;
}
}
注意:这里我们要对删除进行判断
:
- 如果章节里面没有小节,直接删除。
- 如果章节里面有小节,如何删除?
第一种: 删除章节的时候,把章节里面所有小节都删除。
第二种: 如果删除章节下面有小节,不让进行删除。
我们现在对于删除章节使用的是第一种,后面对于删除课程采用的是第二种(删除课程->删除课程描述->删除对应的章节->删除对应的小节)
八、课程章节添加、修改、删除 – 前端开发
1、定义api接口
//根据章节id查询章节.
getChapter(chapterId) {
return request({
url: `/eduservice/chapter/getChapter/${chapterId}`,
method: 'get'
})
},
//根据id更新章节
updateChapter(chapter) {
return request({
url: `/eduservice/chapter/updateChapter`,
method: 'put',
data: chapter
})
},
//根据id删除章节
delChapter(id) {
return request({
url: `/eduservice/chapter/delChapter/${id}`,
method: 'delete'
})
},
//增加章节
addChapter(chapter) {
return request({
url: `/eduservice/chapter/addChapter`,
method: 'post',
data: chapter
})
}
2、添加章节按钮
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
3、添加弹出框表单
<!-- 添加和修改章节表单 -->
<el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
<el-form :model="chapter" label-width="120px">
<el-form-item label="章节标题">
<el-input v-model="chapter.title"/>
</el-form-item>
<el-form-item label="章节排序">
<el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogChapterFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate">确 定</el-button>
</div>
</el-dialog>
4.编写js
data() {
return {
//....
courseId:'',//课程id
chapterVideoList:[],
chapter:{//封装章节数据
title: '',
sort: 0
},
dialogChapterFormVisible:false//章节弹框
}
},
methods:{
//删除章节
removeChapter(chapterId){
this.$confirm('此操作将永久删除章节记录,是否继续?','提示',{
confirmButtonText:'确定',
cancelButtonText:'取消',
type:'warning'
}).then(()=>{//点击确定
chapter.delChapter(chapterId).then((response)=>{
this.$message({
type:'success',
message:'删除成功!'
})
//回到列表页面
this.getAllChapterVideo()
}).catch((response)=>{
this.$message({
type: 'error',
message: response.data.message
})
})
})
.catch(() => { // 失败
this.$message({
type: 'info',
message: '取消删除!'
})
this.getAllChapterVideo()
}
)
},
//弹出添加章节表单
openChapterDialog(){
this.dialogChapterFormVisible = true
this.chapter = {title: '',sort: 0}
},
//数据回显
editChapter(id){
this.dialogChapterFormVisible = true
chapter.getChapter(id).then(response=>{
this.chapter = response.data.chapter
})
},
//编辑章节
updateChapter(){
chapter.updateChapter(this.chapter).then(response=>{
//给与提示
this.$message({
type:'success',
message:'章节编辑成功!'
})
//关闭弹框
this.dialogChapterFormVisible = false
this.getAllChapterVideo()//查询所有章节/小节,刷新页面
}).catch(response=>{
//给与提示
this.$message({
type:'error',
message:'章节编辑失败!'
})
//关闭弹框
this.dialogChapterFormVisible = false
this.getAllChapterVideo()
})
},
//增加章节
addChapter(){
this.chapter.courseId = this.courseId
chapter.addChapter(this.chapter).then(response=>{
this.dialogChapterFormVisible = false
//给与提示
this.$message({
type:'success',
message:'章节添加成功!'
})
//刷新页面
this.getAllChapterVideo()//这些公共语句即使类似也必须放在then里面,不然逻辑就会混乱...
}).catch(response=>{
this.dialogChapterFormVisible = false
//给与提示
this.$message({
type:'error',
message:'章节添加失败!'
})
//刷新页面
this.getAllChapterVideo()
})
},
//增加和修改
saveOrUpdate(){
if(!this.chapter.id || this.chapter.id==""){
this.addChapter()
}else{
this.updateChapter()
}
},
//......
}
注意
:启动服务运行出现403错误。
-
第一种是Controller类中跨域注解没有写
@CrossOrigin
。 -
第二种是访问路径写的不对。
九、课程小节添加、修改、删除 --后端开发
1、编写Controller类
@Api(description = "小节管理")
@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin
public class EduVideoController {
@Autowired
private EduVideoService videoService;
//增加小节
@ApiOperation("添加小节")
@PostMapping("addVideo")
public R addVideo(@RequestBody EduVideo video){
boolean flag = videoService.save(video);
if(flag){
return R.ok();
}else{
return R.error();
}
}
//修改小节
@ApiOperation("修改小节")
@PutMapping("updateVideo")
public R updateVideo(@RequestBody EduVideo video){
boolean flag = videoService.updateById(video);
if(flag){
return R.ok();
}else{
return R.error();
}
}
//删除小节TODD 后面这个方法需要完善:删除小节时候,同时要把里面的视频删除
@ApiOperation("删除小节")
@DeleteMapping("delVideo/{id}")
public R delVideo(@PathVariable String id){
boolean flag = videoService.removeById(id);
if(flag){
return R.ok();
}else{
return R.error();
}
}
//查询小节
@ApiOperation("查询小节")
@GetMapping("getVideo/{id}")
public R getVideo(@PathVariable String id){
EduVideo video = videoService.getById(id);
return R.ok().data("video",video);
}
}
注意:在实体类中的时间加上@TableField(fill = FieldFill.INSERT)
等注解
十、课程小节添加、修改、删除 – 前端开发
1、定义api接口
import request from '@/utils/request'
export default {
//添加小节
addVideo(video) {
return request({
url: `/eduservice/video/addVideo`,
method: 'post',
data: video
})
},
//修改小节
updateVideo(video) {
return request({
url: `/eduservice/video/updateVideo`,
method: 'put',
data: video
})
},
//删除小节
delVideo(id) {
return request({
url: `/eduservice/video/delVideo/${id}`,
method: 'delete'
})
},
//查询小节通过id
getVideo(id) {
return request({
url: `/eduservice/video/getVideo/${id}`,
method: 'get'
})
},
}
2、添加修改页面
<!-- 添加和修改课时表单 -->
<el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
<el-form :model="video" label-width="120px">
<el-form-item label="课时标题">
<el-input v-model="video.title"/>
</el-form-item>
<el-form-item label="课时排序">
<el-input-number v-model="video.sort" :min="0" controls-position="right"/>
</el-form-item>
<el-form-item label="是否免费">
<el-radio-group v-model="video.free">
<el-radio :label="true">免费</el-radio>
<el-radio :label="false">默认</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传视频">
<!-- TODO -->
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVideoFormVisible = false">取 消</el-button>
<el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button>
</div>
</el-dialog>
3、引入包
import video from '@/api/edu/video'
4、初始数据定义
data() {
return {
//....
video: {//封装小节数据
title: '',
sort: 0,
isFree: 0,
videoSourceId: '',//视频id(后面用到)
videoOriginalName:''//视频名称(后面用到)
},
dialogVideoFormVisible:false//小节弹框
}
},
5、编写增删改查的js
//当点击更新/添加中的确认按钮时
saveOrUpdateVideo(){
if(!this.video.id){
this.addVideo()
}else{
this.updateVideo()
}
},
//当点击添加小节
openVideoDialog(chapterId){
this.dialogVideoFormVisible = true
this.chapterId = chapterId
this.fileList = []
this.video ={// 课时对象 //打开之前先清空
title: '',
sort: 0,
isFree: 0,
videoSourceId: '',
videoOriginalName:''
}
},
//添加小节
addVideo(){
this.video.courseId = this.courseId
this.video.chapterId = this.chapterId
video.addVideo(this.video).then(response=>{
this.dialogVideoFormVisible = false
//给与提示
this.$message({
type:'success',
message:'小节添加成功!'
})
//刷新页面
this.getAllChapterVideo()//这些公共语句即使类似也必须放在then里面,不然逻辑就会混乱...
}).catch(response=>{
this.dialogVideoFormVisible = false
//给与提示
this.$message({
type:'error',
message:'小节添加失败!'
})
//刷新页面
this.getAllChapterVideo()//这些公共语句即使类似也必须放在then里面,不然逻辑就会混乱...
})
},
//编辑小节前的数据回显
editVideo(id){
this.openVideoDialog(id)//进行初始化
this.dialogVideoFormVisible = true
video.getVideo(id).then(response=>{
//普通书籍回显
this.video = response.data.video
//进行视频回显
if(this.video.videoOriginalName!=""){
this.fileList = [{name:this.video.videoOriginalName}]
}
})
},
//编辑小节
updateVideo(){
video.updateVideo(this.video).then(response=>{
//给与提示
this.$message({
type:'success',
message:'小节编辑成功!'
})
//关闭弹框
this.dialogVideoFormVisible = false
this.getAllChapterVideo()
}).catch(response=>{
//给与提示
this.$message({
type:'error',
message:'小节编辑成功!'
})
//关闭弹框
this.dialogVideoFormVisible = false
this.getAllChapterVideo()
})
},
//删除小节
delVideo(id){
this.$confirm('此操作将永久删除小节记录,是否继续?','提示',{
confirmButtonText:'确定',
cancelButtonText:'取消',
type:'warning'
}).then(()=>{//点击确定
video.delVideo(id).then((response)=>{
this.$message({
type:'success',
message:'删除成功!'
})
//回到列表页面
this.getAllChapterVideo()
}).catch(()=>{
this.$message({
type:'error',
message:'删除失败!'
})
})
}).catch(()=>{
this.$message({
type:'info',
message:'取消删除!'
})
})
},
//...
6、启动服务测试即可
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客