课程管理模块
数据库表关系
course相关表、chapter、video、teacher、subject……这些表都需要
代码生成器
熟悉的操作,run就完事了
贴一下项目结构
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则可免费观看") 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, "添加课程失败"); } EduCourseDescription courseDescription = new EduCourseDescription(); courseDescription.setId(eduCourse.getId()); 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视图模板
使用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: '添加成功!' }) 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
|
<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
| subjectLevelOneChanged(value) { for (var i = 0; i < this.subjectOneList.length; i++) { if (value === this.subjectOneList[i].id) { this.subjectTwoList = this.subjectOneList[i].children 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(), 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;
@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) { QueryWrapper<EduChapter> chapterWrapper = new QueryWrapper<>(); chapterWrapper.eq("course_id", courseId); List<EduChapter> chapterList = baseMapper.selectList(chapterWrapper); 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>
|
修改课程信息(后端)
- “上一步”回显方法:根据id查询课程基本信息
- 修改课程信息
回显课程信息
编写controller
1 2 3 4 5 6
| @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
| @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
| 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() } },
addCourse() { courseApi.addCourseInfo(this.courseInfo).then(result => { this.$message({ type: 'success', message: '添加成功!' }) this.$router.push({ path: '/course/chapter/' + result.data.courseId }) }) },
updateCourse() { courseApi.updateCourseInfo(this.courseInfo).then(result => { this.$message({ type: 'success',s message: '修改成功!' }) 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; @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) { 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 }) }, 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() { this.chapter.courseId = this.courseId 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() chapterApi.getChapterInfo(chapterId).then(result => { this.chapter = result.data.chapterInfo }) },
updateChapter() { 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 } },
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(); } @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 }) }, 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="上传视频"> </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 this.video.chapterId = id }, addVideo() { 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(() => { videoApi.deleteVideo(id).then(response => { this.$message({ type: 'success', message: '删除小节成功!' }) this.getChapterVideo() }) }) }, saveOrUpdateVideo() { this.addVideo() }, }
|