课程分类

添加课程分类

现在的感觉就是,写一两集后端代码,剩下的两三天都是前端,真的好麻烦呀前端,可能就是没有系统学过Vue吧。
慢慢来吧~
步骤和5-6天的差不多,基本上也是调接口和写vue代码再配合element组件就可以基本实现

添加课程接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import request from '@/utils/request'

export default {

//1 课程分类列表
//current当前页 limit每页显示数 teacherQuery条件对象
getSubjectList(){
return request({
//url: 'eduservice/'+current+"/"+limit,
url: `/eduservice/subject/getAllSubject`,
method: 'get',
})
}
}

以excel的形式直接传文件添加课程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="信息描述">
<el-tag type="info">excel模版说明</el-tag>
<el-tag>
<i class="el-icon-download"/>
<a :href="'/static/01.xlsx'">点击下载模版</a>
</el-tag>
</el-form-item>
<el-form-item label="选择Excel">
<el-upload
ref="upload"
:auto-upload="false"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:disabled="importBtnDisabled"
:limit="1"
:action="BASE_API+'/eduservice/subject/addSubject'"
name="file"
accept=".xls,.xlsx">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button

:loading="loading"
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload">{{ fileUploadBtnText }}</el-button>
</el-upload>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data(){
return {
BASE_API: process.env.BASE_API, // 接口API地址
fileUploadBtnText: '上传到服务器', // 按钮文字
importBtnDisabled: false, // 按钮是否禁用,
loading: false
}
},

created(){

},

methods: {
//点击按钮上传到服务器到接口里面
submitUpload(){
this.importBtnDisabled = true
this.loading = true
// js:document.getElementById("upload").submit()
this.$refs.upload.submit()
},
//上传成功
fileUploadSuccess(response){
//提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
//跳转课程分类列表
//路由跳转
this.$touter.push({path:'/subject/list'})
},
//上传失败
fileUploadError(){
this.loading = false
this.$message({
type: 'error',
message: '添加课程分类失败'
})
}

}
}

</script>

课程分类显示(前后端)

前端直接贴上来了,树形结构的样式也是模板,重点在后端如何实现树形数据的传输

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div class="app-container">
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />

<el-tree
ref="tree2"
:data="data2"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>

</div>
</template>

<script>
import subject from '@/api/edu/subject.js'
export default {

data() {
return {
filterText: '',
data2: [{}], //返回所有分类的数据
defaultProps: {
children: 'children',
label: 'title'
}
}
},
created() {
this.getAllSubjectList()
},
watch: {
filterText(val) {
this.$refs.tree2.filter(val)
}
},

methods: {
getAllSubjectList() {
subject.getSubjectList()
.then(response => {
this.data2 = response.data.list
})
},
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
}
}
</script>

后端

后端要想实现这个树形结构的数据返回给前端使用,需要以下步骤
1、针对返回数据创建对应的实体类,称为Vo(前端和后端的过渡实体类,专门用来对接数据)
2、在两个实体类中表示关系,因为二级分类课程是包在一级分类里面的。

如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.atguigu.eduservice.entity.subject;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

//一级分类实体类
@Data
public class OneSubject {
private String id;
private String title;

//一个一级分类有多个二级分类
private List<TwoSubject> children = new ArrayList<>();
}


1
2
3
4
5
6
7
8
9
10
11
package com.atguigu.eduservice.entity.subject;

import lombok.Data;

@Data
public class TwoSubject {

private String id;
private String title;
}


接着就可以在Service层里面写方法了,注意这里用到了四个实体类和两张表,容易弄混,注意区别!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Override
public List<OneSubject> getAllOneTwoSubject() {
//查询所有一级分类 parent_id = 0
QueryWrapper<EduSubject> queryWrapper1 =new QueryWrapper<>();
queryWrapper1.eq("parent_id","0");
//因为ServiceImpl中内部已经自己注入了baseMapper,所以可以直接使用
List<EduSubject> oneSubjectList = baseMapper.selectList(queryWrapper1);

//查询所有二级分类 parent_id != 0
QueryWrapper<EduSubject> queryWrapper2 =new QueryWrapper<>();
queryWrapper1.ne("parent_id","0");//ne表示不等于
//因为ServiceImpl中内部已经自己注入了baseMapper,所以可以直接使用
List<EduSubject> twoSubjectList = baseMapper.selectList(queryWrapper2);

//创建一个list集合,用于我们存储最终数据
List<OneSubject> finalSubjectList = new ArrayList<>();

//一级分类封装
//查询出来所有的一级分类list集合遍历,得到每一个一级分类对象,获取每一个一级分类的对象值
//封装到要求的list集合里面 List<OneSubject> finalSubjectList
for (EduSubject eduSubject : oneSubjectList) {
//把eduSubject里面的值获取出来,放到OneSubject对象里面
OneSubject oneSubject =new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
//优化方法 eduSubject值复制到对应onSubject对象里面(属性名称一致才会做封装)
BeanUtils.copyProperties(eduSubject,oneSubject);

//二级分类封装,在一级分类下再做循环匹配到对应的一级分类
//创建list集合封装对应
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
//遍历twoSubjectList做封装
for (EduSubject twosSubject : twoSubjectList) {
if(twosSubject.getParentId().equals(oneSubject.getId())){
//把twosSubject值复制到Subject中
TwoSubject subject =new TwoSubject();
BeanUtils.copyProperties(twosSubject,subject);
twoFinalSubjectList.add(subject);
}
}

//把一级下面的二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
//然后再把OneSubject对象存到finalSubjectList集合中
finalSubjectList.add(oneSubject);
}


//
return finalSubjectList;
}
}

课程发布流程

整体的一个课程发布是使用了步骤条来实现一步步完成课程发布流程。内容比较多。

创建新表

下面具体讲一下这些表的作用,方便后面对于业务和代码的理解。
edu_course 课程表:存储课程基本信息

edu_course_description 课程简介表,用富文本框存储课程简介信息

edu_chapter 存储课程章节信息

edu_video 存储章节里面的小节信息

edu_teacher 存储讲师信息

edu_subject 存储课程分类信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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_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_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_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='课程视频';

添加课程信息(后端)

后端就还是熟悉的使用代码生成器生成对应的实体类以及三层架构

还是像上面一样,要创建一个vo实体类来接受前端的json数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.math.BigDecimal;

@Data
public class CourseInfoVo {
@ApiModelProperty(value = "课程ID")
private String id;

@ApiModelProperty(value = "课程讲师ID")
private String teacherId;

@ApiModelProperty(value = "一级分类ID")
private String subjectParentId;

@ApiModelProperty(value = "二级分类ID")
private String subjectId;

@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;
}

之后就可以在EduCourseController里面写接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("/eduservice/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);
}
}

直接把核心Service贴上来了(操作了两张表,edu_course edu_course_description)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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 id = eduCourse.getId();

//2、向课程简介表添加课程数据
EduCourseDescription courseDescription =new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,courseDescription);
//设置描述id就是课程id,从而实现一对一的表关系
courseDescription.setId(id);
courseDescriptionService.save(courseDescription);
return id;
}
}

添加课程信息(前端)

直接贴,不解释了哈哈哈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import request from '@/utils/request'

export default {

//1 添加课程
addCourseInfo(courseInfo){
return request({
url: `/eduservice/course/addCourseInfo`,
method: 'post',
data:courseInfo
})
},
//2 查询所有讲师
getListTeacher(){
return request({
url: `/eduservice/teacher/findAll`,
method: 'get'
})
}
}

这个info.vue只是步骤条中的步骤一,后面还有两个后面在完善
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<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 @change表示一改变就会触发的方法-->
<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-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>
<!-- 课程讲师 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="课程简介">
<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>
<!-- 课程封面-->
<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" width="200px" height="200px">
</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.js'
import subject from '@/api/edu/subject.js'
import Tinymce from '@/components/Tinymce'//引入组件
export default{
//声明组件
components: { Tinymce },
data() {
return {
saveBtnDisabled:false,
courseInfo:{
title: '',
subjectId: '',//二级分类课程id
subjectParentId: '',//一级分类课程id
teacherId: '',
lessonNum: 0,
description: '',
cover: '/static/01.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(value === onesubject.id) {
//从一级分类获取里面所有的二级分类
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>
<style scoped>
.tinymce-container {
line-height: 29px;
}
</style>

章节后端(步骤条第二步)

这个后端代码基本一致,就是删除有点特色,只有章节下面没有小节的时候才可以删除章节(跟瑞吉里面的套餐下菜品在售不能删除套餐一样)
小节的后端和章节的一模一样就不贴了,就是删除那里可以直接用courseId在removeById方法直接删除,不需要判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin
public class EduChapterController {

@Autowired
private EduChapterService chapterService;

//课程大纲列表,根据课程id进行查询
@GetMapping("getChapterVideo/{courseId}")
public R getChapterVideo(@PathVariable String courseId) {
List<ChapterVo> list = chapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("allChapterVideo",list);
}

//添加章节
@PostMapping("addChapter")
public R addChapter(@RequestBody EduChapter eduChapter) {
chapterService.save(eduChapter);
return R.ok();
}

//根据章节id查询
@GetMapping("getChapterInfoById/{chapterId}")
public R getChapterInfo(@PathVariable String chapterId) {
EduChapter eduChapter = chapterService.getById(chapterId);
return R.ok().data("chapter",eduChapter);
}

//修改章节
@PostMapping("updateChapter")
public R updateChapter(@RequestBody EduChapter eduChapter) {
chapterService.updateById(eduChapter);
return R.ok();
}

//删除的方法
@DeleteMapping("{chapterId}")
public R deleteChapter(@PathVariable String chapterId) {
boolean flag = chapterService.deleteChapter(chapterId);
if(flag) {
return R.ok();
} else {
return R.error();
}

}
}

下面就只挂删除的核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
////删除章节的方法
@Override
public boolean deleteChapter(String chapterId) {
//根据chapterid章节id 查询小节表,如果查询数据,不进行删除
QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
wrapper.eq("chapter_id",chapterId);
int count = videoService.count(wrapper);
//判断
if(count >0) {//查询出小节,不进行删除
throw new GuliException(20001,"不能删除");
} else { //不能查询数据,进行删除
//删除章节
int result = baseMapper.deleteById(chapterId);
//成功 1>0 0>0
return result>0;
}
}

章节前端(步骤条第二步)

又到了可恶的前端(步骤条第二步的页面代码)
抽取出来的调用接口就懒得贴上来了,在页面上的代码都可以体现出来(懒~)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
<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-button type="text" @click="openChapterDialog()">添加章节</el-button>

<!-- 章节 -->
<ul class="chanpterList">
<li
v-for="chapter in chapterVideoList"
:key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button type="text" @click="openVideo(chapter.id)">添加小节</el-button>
<el-button style="" type="text" @click="openEditChapter(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 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>
<!-- 添加和修改章节表单 -->
<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>

<!-- 添加和修改课时表单 -->
<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>
</div>
</template>

<script>
import chapter from '@/api/edu/chapter'
import video from '@/api/edu/video'
export default{
data() {
return {
saveBtnDisabled:false,
courseId: '',
chapterVideoList:[],
dialogChapterFormVisible: false,//是否弹框的值
dialogVideoFormVisible: false, //小节弹框
video: {//封装小节数据
title: '',
sort: 0,
free: 0,
videoSourseId: ''
},
chapter: {//封装章节数据
title: '',
sort: 0
}
}
},
created() {
//获取上一步url的id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
//根据课程id查询章节和小节
this.getChapterVideo()
}

},
methods: {
//========================================小节操作方法===========================================

//修改小节
updateVideo(){
video.updatedVideo(this.video)
.then(response => {
//关闭弹框
this.dialogVideoFormVisible = false//是否弹框的值
//提示
this.$message({
type: 'success',
message: '修改小节信息成功!'
});
//刷新界面
this.getChapterVideo()
})
},

//回显小节信息
openEditVideo(videoId){
this.dialogVideoFormVisible = true
//调用接口
video.getVideo(videoId)
.then(response => {
this.video = response.data.video
})

},

//删除小节
removeVideo(id){
this.$confirm('此操作将删除小节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { //点击确定,执行then方法
//调用删除的方法
video.deleteVideoById(id)
.then(response => { //删除成功
// 提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//回到列表页面
this.getChapterVideo()
})
})//点击取消方法省略
},

//添加小节弹框
openVideo(chapterId){
this.dialogVideoFormVisible = true
//清空表单
this.video = {}
//设置章节id
this.video.chapterId = chapterId
},

//添加小节
addVideo() {
//设置课程id
this.video.courseId = this.courseId
video.addVideo(this.video)
.then(response => {
//关闭弹框
this.dialogVideoFormVisible = false//是否弹框的值
//提示
this.$message({
type: 'success',
message: '添加小节信息成功!'
});
//刷新界面
this.getChapterVideo()
})
},

saveOrUpdateVideo(){

if(!this.video.id){
//调用添加小节
this.addVideo()
} else {
this.updateVideo()
}
},

//========================================章节操作方法===========================================
//删除章节
removeChapter(chapterId){
this.$confirm('此操作将删除章节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { //点击确定,执行then方法
//调用删除的方法
chapter.deleteChapterById(chapterId)
.then(response => { //删除成功
// 提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//回到列表页面
this.getChapterVideo()
})
})//点击取消方法省略
},

//修改章节弹框数据回显
openEditChapter(chapterId){
//弹框
this.dialogChapterFormVisible = true
//调用接口
chapter.getChapter(chapterId)
.then(response => {
this.chapter = response.data.chapter
})
},

//弹出添加章节页面
openChapterDialog(){
//弹框
this.dialogChapterFormVisible = true
//清空表单
this.chapter = {}
},

//添加章节
addChapter(){
//设置课程id到chapter对象里面
this.chapter.courseId = this.courseId
chapter.addChapter(this.chapter)
.then(response => {
//关闭弹框
this.dialogChapterFormVisible = false//是否弹框的值
//提示
this.$message({
type: 'success',
message: '添加章节信息成功!'
});
//刷新界面
this.getChapterVideo()
})
},

//修改章节方法
updateChapter(){
chapter.updatedChapter(this.chapter)
.then(response => {
//关闭弹框
this.dialogChapterFormVisible = false//是否弹框的值
//提示
this.$message({
type: 'success',
message: '修改章节信息成功!'
});
//刷新界面
this.getChapterVideo()
})
},

saveOrUpdate() {
if(!this.chapter.id){
this.addChapter()
} else {
this.updateChapter()
}

},

//根据课程id查询章节和小节
getChapterVideo(){
chapter.addAllChapterVideo(this.courseId)
.then(response => {
this.chapterVideoList=response.data.allChapterVideo
})
},
previous() {
this.$router.push({path:"/course/info/"+ this.courseId})
},

next() {
//跳转到第二步
this.$router.push({path:"/course/publish/"+this.courseId})
}
},
}
</script>
<!-- css渲染(不得不吐槽一下,这个美化还变丑了笑死我了) -->
<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>