抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Hello world!

课程管理模块

数据库表关系

course相关表、chapter、video、teacher、subject……这些表都需要

image-20210924111116027

代码生成器

熟悉的操作,run就完事了

贴一下项目结构

image-20210924112051971

controller记得加下跨域注释,entity记得加下自动填充注释……

添加课程信息(后端)

vo表单数据封装类

需要用到表单提交数据,所以写一个vo类封装

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
package com.atguigu.eduservice.entity.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

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 subjectId;

@ApiModelProperty(value = "课程标题")
private String title;

@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
// 0.01
private BigDecimal price;

@ApiModelProperty(value = "总课时")
private Integer lessonNum;

@ApiModelProperty(value = "课程封面图片路径")
private String cover;

@ApiModelProperty(value = "课程简介")
private String description;
}

编写controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin // 跨域
public class EduCourseController {

@Autowired
private EduCourseService courseService;

// 添加课程基本信息
@PostMapping("addCourseInfo")
public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
courseService.saveCourseInfo(courseInfoVo);
return R.ok();
}

}

编写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
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

@Autowired
private EduCourseDescriptionService courseDescriptionService;

// 添加课程基本信息
@Override
public void saveCourseInfo(CourseInfoVo courseInfoVo) {
// 向课程表中添加课程基本信息
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo, eduCourse);
int affectRow = baseMapper.insert(eduCourse);
if (affectRow <= 0) {
throw new GuliException(20001, "添加课程失败");
}
// 向课程简介表中添加课程简介信息
// 注意baseMapper添加的是当前service对应的表,使用对应的service即可
EduCourseDescription courseDescription = new EduCourseDescription();
courseDescription.setId(eduCourse.getId()); // 这里要保证两张表id相同(1-1关系)
courseDescription.setDescription(courseInfoVo.getDescription());
courseDescriptionService.save(courseDescription);
}
}

同时需要实体类中的id属性为手动输入,而不是自动生成

1
2
3
4
5
6
7
8
9
public class EduCourseDescription implements Serializable {

private static final long serialVersionUID = 1L;

@ApiModelProperty(value = "课程ID")
@TableId(value = "id", type = IdType.INPUT)
private String id;

}

测试

course和description表中id一样

添加课程信息(前端)

添加router

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
// 课程信息管理模块路由
{
path: '/course',
component: Layout,
redirect: '/course/list',
name: '课程管理',
meta: { title: '课程管理', icon: 'example' },
children: [
{
path: 'list',
name: '课程列表',
component: () => import('@/views/edu/course/list'),
meta: { title: '课程分类列表', icon: 'tree' }
},
{
path: 'info',
name: '添加课程',
component: () => import('@/views/edu/course/info'),
meta: { title: '添加课程', icon: 'table' }
},
{
path: 'info/:id',
name: 'EduCourseInfoEdit',
component: () => import('@/views/edu/course/info'),
meta: { title: '编辑课程基本信息', icon: 'table' },
// 不显示该路由(隐藏路由)
hidden: true
},
{
path: 'chapter/:id',
name: 'EduCourseChapterEdit',
component: () => import('@/views/edu/course/chapter'),
meta: { title: '编辑课程大纲', icon: 'table' },
// 不显示该路由(隐藏路由)
hidden: true
},

{
path: 'publish/:id',
name: 'EduCoursePublishEdit',
component: () => import('@/views/edu/course/publish'),
meta: { title: '发布课程', icon: 'table' },
// 不显示该路由(隐藏路由)
hidden: true
}
]
},

创建vue视图模板

image-20210924121256036

使用element-ui的步骤条和表单即可

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
<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>

<!-- 课程讲师 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/01.jpg',
price: 0
},
BASE_API: process.env.BASE_API, // 接口API地址
teacherList: [], // 封装所有的讲师
subjectOneList: [], // 一级分类
subjectTwoList: [] // 二级分类
}
},
created() {},
methods: {}
}
</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
<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:'/course/info/1'})
},
next() {
//跳转到第二步
this.$router.push({path:'/course/publish/1'})
}
}
}
</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
54
<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>

创建课程相关接口

api -> edu -> course.js

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

export default {
// 新增课程信息
addCourseInfo(courseInfo) {
return request({
url: `/eduservice/course/addCourseInfo`,
method: 'post',
data: courseInfo
})
}

}

注意这里在后端把之前的代码修改一下,返回新增课程的id,然后重启

编写按钮方法(简单编写未实现功能)

由于表单中显示课程分类和讲师还没有写,所以先简单写一下方法

1
2
3
4
5
6
7
8
9
10
11
12
next() {
// 调用后端接口传入数据
courseApi.addCourseInfo(this.courseInfo).then(result => {
// 显示提示信息
this.$message({
type: 'success',
message: '添加成功!'
})
// 跳转到第二步,同时传递id值
this.$router.push({ path: '/course/chapter/' + result.data.courseId })
})
}

添加课程信息(更多完善)

显示讲师下拉列表(后端)

讲师模块写过了,不带分页查询所有讲师

显示讲师下拉列表(前端)

注意这里要现在api中创建所得所有讲师的接口(如果之前讲师模块没写的话)

1
2
3
4
5
6
7
// 查询所有讲师(不带分页)
getTeacherList() {
return request({
url: `/eduservice/teacher/findAll`,
method: 'get'
})
},

下拉列表模板,使用循环的方法

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 课程讲师 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>

然后编写vue中对应方法即可

1
2
3
4
5
getListTeacher() {
teacherApi.getTeacherList().then(result => {
this.teacherList = result.data.teacherList
})
},

显示二级联动下拉列表(前端)

后端课程分类模块写好了,使用之前的即可

先获得并保存并初始化显示所有一级分类

1
2
3
4
5
getOneSubject() {
subjectApi.getSubjectList().then(result => {
this.subjectOneList = result.data.list
})
},

实现二级联动,为一级分类下拉列表添加@change对应方法

注意此处el-option绑定了value值赋值为id,所以直接获取即可

1
2
3
4
5
6
7
8
9
10
11
12
13
// 二级联动,value为绑定的数据(:value="subject.id")
subjectLevelOneChanged(value) {
// 根据id便利subjectOneList获得其children赋值给二级分类
for (var i = 0; i < this.subjectOneList.length; i++) {
// 判断:所有一级分类id 和 点击一级分类id是否一样
if (value === this.subjectOneList[i].id) {
// 从一级分类获取里面所有的二级分类
this.subjectTwoList = this.subjectOneList[i].children
// 把二级分类id值清空
this.courseInfo.subjectId = ''
}
}
},

测试是有一个错误,course表上添加的数据没有父分类id,看了下是我在vo对象把这个属性注释了(因为之前有null错误),记得重启

富文本编辑器

直接整合,指cv

复制组件和静态资源

字面意思

配置变量

再配置html变量,build -> webpack.dev.conf.js 中如下添加(注意所在位置,其他不要变)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
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
}
})
]
})

引入脚本文件

在index.html引入脚本文件,报错是找不到BASE_URL,之前改了配置重启就行

1
2
<script src=<%=BASE_URL %>/tinymce4.7.5/tinymce.min.js ></script>
<script src=<%=BASE_URL %>/tinymce4.7.5/langs/zh_CN.js ></script>

引入声明组件

1
2
3
4
5
import Tinymce from '@/components/Tinymce' //引入组件
export default {
// 声明组件
components: { Tinymce },
}

使用组件

1
<tinymce :height="300" v-model="courseInfo.description" />

测试了一下,注意数据库中的字段属性改为longtext,要不然图片传上去后端报错Data truncation: Data too long for column 'description'

显示课程章节(后端)

创建章节实体类

类似于一级二级分类即可

编写controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin // 跨域
public class EduChapterController {

@Autowired
private EduChapterService chapterService;

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

}

编写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
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

@Autowired
private EduVideoService videoService;

@Override
public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
// 获得对应id课程的所有章节
QueryWrapper<EduChapter> chapterWrapper = new QueryWrapper<>();
chapterWrapper.eq("course_id", courseId);
List<EduChapter> chapterList = baseMapper.selectList(chapterWrapper);
// 获得对应id课程的所有小节
QueryWrapper<EduVideo> videoWrapper = new QueryWrapper<>();
videoWrapper.eq("course_id", courseId);
List<EduVideo> videoList = videoService.list(videoWrapper);

// 封装
List<ChapterVo> finalList = new ArrayList<>();
for (int i = 0; i < chapterList.size(); i++) {
EduChapter eduChapter = chapterList.get(i);
ChapterVo chapterVo = new ChapterVo();
BeanUtils.copyProperties(eduChapter, chapterVo);

List<VideoVo> children = new ArrayList<>();
for (int j = 0; j < videoList.size(); j++) {
EduVideo eduVideo = videoList.get(j);
if (eduVideo.getChapterId().equals(chapterVo.getId())) {
VideoVo videoVo = new VideoVo();
BeanUtils.copyProperties(eduVideo, videoVo);
children.add(videoVo);
}
}
chapterVo.setChildren(children);
finalList.add(chapterVo);
}

return finalList;
}
}

显示课程章节(前端)

api

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

export default {
// 获得所有以及分类
getChapterVideo(courseId) {
return request({
url: `/eduservice/chapter/getChapterVideo/${courseId}`,
method: 'get',
})
}
}

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
<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="chapterList">
<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="chapterList videoList">
<li v-for="video in chapter.children" :key="video.id">
<p>
{{ video.title }}

<span class="acts">
<el-button style="" type="text">编辑</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>
</div>
</template>
<script>
import chapterApi from '@/api/edu/chapter'
export default {
data() {
return {
saveBtnDisabled: false,
courseId: '', // 课程id
chapterVideoList: []
}
},
created() {
// 获取到路由中的id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
this.getChapterVideo()
}
},
methods: {
// 根据id查询章节和小节
getChapterVideo() {
chapterApi.getChapterVideo(this.courseId).then(result => {
this.chapterVideoList = result.data.chapterVideoList
})
},
previous() {
this.$router.push({ path: '/course/info/' + this.courseId })
},
next() {
// 跳转到第二步
this.$router.push({ path: '/course/publish/' + this.courseId })
}
}
}
</script>
<style scoped>
.chapterList {
position: relative;
list-style: none;
margin: 0;
padding: 0;
}
.chapterList li {
position: relative;
}
.chapterList p {
float: left;
font-size: 20px;
margin: 10px 0;
padding: 10px;
height: 70px;
line-height: 50px;
width: 100%;
border: 1px solid #ddd;
}
.chapterList .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>

修改课程信息(后端)

  1. “上一步”回显方法:根据id查询课程基本信息
  2. 修改课程信息

回显课程信息

编写controller

1
2
3
4
5
6
// 根据id查询基本信息
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId) {
CourseInfoVo courseInfoVo = courseService.getCourseInfoById(courseId);
return R.ok().data("courseInfo", courseInfoVo);
}

编写service

1
2
3
4
5
6
7
8
9
10
11
12
13
// 根据id获得课程Vo
@Override
public CourseInfoVo getCourseInfoById(String courseId) {
// 先查课程表
EduCourse eduCourse = baseMapper.selectById(courseId);
// 再查课程描述表
EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
// 封装
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(eduCourse, courseInfoVo);
courseInfoVo.setDescription(courseDescription.getDescription());
return courseInfoVo;
}

修改课程信息

编写controller

1
2
3
4
5
6
// 修改课程信息
@PostMapping("updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
courseService.updateCourseInfo(courseInfoVo);
return R.ok();
}

编写service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 修改课程信息
@Override
public void updateCourseInfo(CourseInfoVo courseInfoVo) {
// 修改课程表
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo, eduCourse);
int affectRow = baseMapper.updateById(eduCourse);
if (affectRow <= 0) {
throw new GuliException(20001, "修改课程失败");
}
// 修改课程描述表
EduCourseDescription courseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo, courseDescription);
courseDescriptionService.updateById(courseDescription);
}

修改课程信息(前端)

api

src -> api -> edu -> course.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据id获得课程信息
getCourseInfoById(id) {
return request({
url: `/eduservice/course/getCourseInfo/` + id,
method: 'get',
})
},
// 修改课程信息
updateCourseInfo(courseInfo) {
return request({
url: `/eduservice/course/updateCourseInfo`,
method: 'post',
data: courseInfo
})
},

回显vue视图和方法

表单回显数据

记得在跳转到info页面的按钮上添加id的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
import courseApi from '@/api/edu/course'
export default {
data() {
},
created() {
// 判断是否需要数据回显
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
this.getCourseInfo()
}
},
methods: {
// 根据id获得课程信息
getCourseInfo() {
courseApi.getCourseInfoById(this.courseId).then(result => {
this.courseInfo = result.data.courseInfo
})
},
}
}
</script>

这里需要注意,返回该页面回显数据中,courseInfo的二级分类列表subjectTwoList只有在一级分类change的时候才会重新显示,否则该列表会显示绑定的courseInfo.subjectId,所以需要完善

下拉列表数据回显

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
<script>
export default {
watch: {
// 监听路由是否发生了变化,如果是则清除当前页面的数据
$route(to, from) {
if (!this.$route.params || !this.$route.params.id) {
this.courseInfo = {}
}
}
},
created() {
// 获取路由id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
// 调用根据id查询课程的方法
this.getCourseInfo()
} else {
// 初始化所有讲师
this.getListTeacher()
// 初始化一级分类
this.getOneSubject()
}
},
methods: {
// 根据id获得课程信息
getCourseInfo() {
courseApi.getCourseInfoById(this.courseId).then(response => {
// 在courseInfo课程基本信息,包含 一级分类id 和 二级分类id
this.courseInfo = response.data.courseInfo
// 1 查询所有的分类,包含一级和二级
subjectApi.getSubjectList().then(response => {
// 2 获取所有一级分类
this.subjectOneList = response.data.list
// 3 把所有的一级分类数组进行遍历,
for (var i = 0; i < this.subjectOneList.length; i++) {
// 获取每个一级分类
var oneSubject = this.subjectOneList[i]
// 比较当前courseInfo里面一级分类id和所有的一级分类id
if (this.courseInfo.subjectParentId == oneSubject.id) {
// 获取一级分类所有的二级分类
this.subjectTwoList = oneSubject.children
}
}
})
// 初始化所有讲师
this.getListTeacher()
})
},
}
}
</script>

测试,这里很奇怪,我把获得二级列表方法封装了出来,结构顺序都一样的但是测试的时候一开始能显示,后面又不行了,我不理解

修改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
// 点击下一步
next() {
// 判断是添加还是修改
if (this.courseInfo.id) {
this.updateCourse()
} else {
this.addCourse()
}
},
// 添加course
addCourse() {
// 调用后端接口传入数据
courseApi.addCourseInfo(this.courseInfo).then(result => {
// 显示提示信息
this.$message({
type: 'success',
message: '添加成功!'
})
// 跳转到第二步,同时传递id值
this.$router.push({ path: '/course/chapter/' + result.data.courseId })
})
},
// 修改course
updateCourse() {
courseApi.updateCourseInfo(this.courseInfo).then(result => {
// 显示提示信息
this.$message({
type: 'success',s
message: '修改成功!'
})
// 跳转到第二步,同时传递id值
this.$router.push({ path: '/course/chapter/' + this.courseId })
})
}

增、删、改课程章节(后端)

编写controller

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
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin // 跨域
public class EduChapterController {
@Autowired
private EduChapterService chapterService;
// 根据章节id查询章节
@GetMapping("getChapterInfo/{chapterId}")
public R getChapterInfo(@PathVariable("chapterId") String chapterId) {
EduChapter chapter = chapterService.getById(chapterId);
return R.ok().data("chapterInfo", chapter);
}
// 添加章节
@PostMapping("addChapter")
public R addChapter(@RequestBody EduChapter eduChapter) {
chapterService.save(eduChapter);
return R.ok();
}
// 修改章节
@PostMapping("updateChapter")
public R updateChapter(@RequestBody EduChapter eduChapter) {
chapterService.updateById(eduChapter);
return R.ok();
}
// 删除章节
@DeleteMapping("{chapterId}")
public R deleteChapter(@PathVariable("chapterId") String chapterId) {
// 自定义方法,如果有小节则不让删
Boolean b = chapterService.deleteChapter(chapterId);
return b ? R.ok() : R.error();
}
}

编写service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 删除章节
@Override
public Boolean deleteChapter(String chapterId) {
// 根据id判断是否有小节
QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("chapter_id", chapterId);
// 获得小节数
int count = videoService.count(queryWrapper);
if (count > 0) {
throw new GuliException(20001, "含有小节无法删除");
} else {
int row = baseMapper.deleteById(chapterId);
return row > 0;
}
}

增、删、改课程章节(前端)

api

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
import request from '@/utils/request'
export default {
// 添加章节
addChapter(chapter) {
return request({
url: `/eduservice/chapter/addChapter`,
method: 'post',
data: chapter
})
},
// 根据id查询章节
getChapterInfo(chapterId) {
return request({
url: `/eduservice/chapter/getChapterInfo/` + chapterId,
method: 'get',
})
},
// 修改章节
updateChapter(chapter) {
return request({
url: `/eduservice/chapter/updateChapter`,
method: 'post',
data: chapter
})
},
// 删除章节
deleteChapter(chapterId) {
return request({
url: `/eduservice/chapter/` + chapterId,
method: 'delete',
})
},
}

vue视图模板

element-ui dialog cv就完事了

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
<template>
<div class="app-container">
<!-- 添加章节按钮 -->
<el-button type="text" @click="dialogChapterFormVisible = true"
>添加章节</el-button
>
<!-- 添加和修改章节表单 -->
<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>
</div>
</template>
<script>
import chapterApi from '@/api/edu/chapter'
export default {
data() {
return {
saveBtnDisabled: false,
courseId: '', // 课程id
chapterVideoList: [],
chapter: {
// 封装章节数据
title: '',
sort: 0
},
video: {
title: '',
sort: 0,
free: 0,
videoSourceId: ''
},
dialogChapterFormVisible: false, // 章节弹框
dialogVideoFormVisible: false // 小节弹框
}
},
}
</script>

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
saveOrUpdate() {
if (this.chapter.id) {
this.updateChapter()
} else {
this.addChapter()
}
},
// 添加章节
addChapter() {
// 记得添加id
this.chapter.courseId = this.courseId
// 调用api
chapterApi.addChapter(this.chapter).then(result => {
// 关闭弹框
this.dialogChapterFormVisible = false
// 显示提示信息
this.$message({
type: 'success',
message: '添加章节成功!'
})
// 刷新页面
this.getChapterVideo()
// 清空弹框表单内容
this.chapter = { title: '', sort: 0 }
})
},
// 修改章节,显示弹框,数据回显
openEditChatper(chapterId) {
// 弹框
this.openChapterDialog()
// api
chapterApi.getChapterInfo(chapterId).then(result => {
this.chapter = result.data.chapterInfo
})
},
// 修改章节
updateChapter() {
// 调用api
chapterApi.updateChapter(this.chapter).then(result => {
// 关闭弹框
this.dialogChapterFormVisible = false
// 显示提示信息
this.$message({
type: 'success',
message: '修改章节成功!'
})
// 刷新页面
this.getChapterVideo()
})
},
// 删除章节
removeChapter(chapterId) {
this.$confirm('此操作将永久删除该章节记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(result => {
chapterApi
.deleteChapter(chapterId)
.then(result => {
// 显示提示信息
this.$message({
type: 'success',
message: '删除成功!'
})
// 刷新页面
this.getChapterVideo()
})
.catch(err => {
// 显示提示信息
this.$message({
type: 'error',
message: '删除失败!'
})
})
})
},
// 弹出章节弹框
openChapterDialog() {
// 弹框
this.dialogChapterFormVisible = true
// 清空弹框表单内容
this.chapter = { title: '', sort: 0 }
},
// 根据id查询章节和小节
getChapterVideo() {
chapterApi.getChapterVideo(this.courseId).then(result => {
this.chapterVideoList = result.data.chapterVideoList
})
},

增、删、改小节(后端)(待完善)

已完善,详见项目笔记4

controller

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
@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin // 跨域
public class EduVideoController {

@Autowired
private EduVideoService videoService;

// 添加小节
@PostMapping("addVideo")
public R addVideo(@RequestBody EduVideo eduVideo) {
videoService.save(eduVideo);
return R.ok();
}

// 删除小节
@DeleteMapping("{videoId}")
public R deleteVideo(@PathVariable("videoId") String videoId) {
videoService.removeById(videoId);
return R.ok();
}

// 修改小节
@PostMapping("updateVideo")
public R updateVideo(@RequestBody EduVideo eduVideo) {
videoService.updateById(eduVideo);
return R.ok();
}

// 根据小节id查询小节
@GetMapping("getVideoInfo/{videoId}")
public R getVideoInfo(@PathVariable("videoId") String videoId) {
EduVideo video = videoService.getById(videoId);
return R.ok().data("videoInfo", video);
}

}

增、删、改小节(前端)(待完善)

api

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
import request from '@/utils/request'
export default {
// 添加小节
addVideo(video) {
return request({
url: `/eduservice/video/addVideo`,
method: 'post',
data: video
})
},
// 删除小节
deleteVideo(videoId) {
return request({
url: `/eduservice/video/` + videoId,
method: 'delete',
})
},
// 修改小节
updateVideo(video) {
return request({
url: `/eduservice/video/updateVideo`,
method: 'post',
data: video
})
},
// 根据id获得小节信息
getVideoInfo(videoId) {
return request({
url: `/eduservice/video/getVideoInfo/` + videoId,
method: 'get',
})
}
}

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
<!-- 添加和修改小节表单 -->
<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>

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
methods: {
// ++++++++++++++++++小节操作+++++++++++++++++++++
// 打开小节编辑弹框
openEditVideo(id) {
// 弹框
this.dialogVideoFormVisible = true
// 设置章节id
this.video.chapterId = id
},
// 添加小节
addVideo() {
// 设置课程id
this.video.courseId = this.courseId
videoApi.addVideo(this.video).then(response => {
// 关闭弹框
this.dialogVideoFormVisible = false
// 提示
this.$message({
type: 'success',
message: '添加小节成功!'
})
// 刷新页面
this.getChapterVideo()
})
},
// 修改小节
updateVideo() {},
// 删除小节
removeVideo(id) {
this.$confirm('此操作将删除小节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定,执行then方法
// 调用删除的方法
videoApi.deleteVideo(id).then(response => {
// 删除成功
// 提示信息
this.$message({
type: 'success',
message: '删除小节成功!'
})
// 刷新页面
this.getChapterVideo()
})
}) // 点击取消,执行catch方法
},
saveOrUpdateVideo() {
// 先写add,学了视频上传在修改
this.addVideo()
},
// ++++++++++++++++++小节操作+++++++++++++++++++++
}

评论




🧡💛💚💙💜🖤🤍