在线教育-谷粒学院学习笔记(六)-爱代码爱编程
1 内容介绍
- 课程大纲管理
- 课程大纲列表显示
- 章节、小节的添加、修改、删除
- 课程信息确认
- 编写sql语句实现
- 课程最终发布
2 课程大纲列表后端实现
参考课程分类列表
1 创建实体类
创建章节、小节两个实体类,在章节实体类中使用list表示小节
@Data
public class VideoVo {
private String id;
private String title;
}
@Data
public class ChapterVo {
private String id;
private String title;
// 章节包含很多小节
private List<VideoVo> children = new ArrayList<>();
}
2 controller和service
controller
@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);
}
}
service
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
@Autowired
private EduVideoService videoService; // 注入小节的service
// 课程大纲列表,根据课程id查章节、小节
@Override
public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
// 1.根据课程id查询课程里面所有的章节
QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
wrapperChapter.eq("course_id", courseId);
List<EduChapter> eduChapterList = baseMapper.selectList(wrapperChapter);
// 2.根据课程id查询课程里面所有的小节
QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
wrapperVideo.eq("course_id", courseId);
List<EduVideo> eduVideoList = videoService.list(wrapperVideo);
// 创建list集合,用于最终的数据封装 ChapterVo
List<ChapterVo> finalList = new ArrayList<>();
// 3.遍历查询章节list集合,进行封装
for (EduChapter eduChapter: eduChapterList) {
ChapterVo chapterVo = new ChapterVo();
BeanUtils.copyProperties(eduChapter, chapterVo);
finalList.add(chapterVo);
// 创建list集合,用于封装章节中的小节VideoVo
List<VideoVo> videoList = new ArrayList<>();
// 4.遍历查询小节list集合,进行封装
for (EduVideo eduVideo : eduVideoList) {
// 判断小节的chapter_id与章节的id是否一样
if (eduVideo.getChapterId().equals(eduChapter.getId())) {
VideoVo videoVo = new VideoVo();
BeanUtils.copyProperties(eduVideo, videoVo);
videoList.add(videoVo);
}
}
// 小节list放到章节中
chapterVo.setChildren(videoList);
}
return finalList;
}
}
3 课程大纲列表前端实现
1 api/edu/chapter.js
import request from '@/utils/request'
export default {
// 根据课程id获取章节、小节列表
getAllChapterVideo(courseId) {
return request({
url: `/eduservice/chapter/getChapterVideo/${courseId}`,
method: 'get'
})
}
}
2 chapter.vue
<!-- 章节 -->
<ul>
<li v-for="chapter in chapterVideoList" :key="chapter.id">
{{ chapter.title }}
<ul>
<li v-for="video in chapter.children" :key="video.id">
{{ video.title }}
</li>
</ul>
</li>
</ul>
created() {
// 获取路由的id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
}
// 根据课程id查询章节和小节
this.getChapterVideo()
},
methods: {
// 根据课程id查询章节、小节列表
getChapterVideo() {
chapter.getAllChapterVideo(this.courseId)
.then(response => {
this.chapterVideoList = response.data.allChapterVideo
})
},
4 修改课程基本信息
-
点击上一步,回到上一步,并做课程基本信息数据回显
-
数据回显页面,修改数据库内容
1 后端接口
- 根据课程id,查询课程基本信息接口
- 修改课程信息接口
controller
// 根据课程id查询课程基本信息
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId) {
CourseInfoVo courseInfoVo = courseService.getCourseInfo(courseId);
return R.ok().data("courseInfoVo", courseInfoVo);
}
// 修改课程信息
@PostMapping("updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
courseService.updateCourseInfo(courseInfoVo);
return R.ok();
}
service
// 根据课程id查询课程基本信息
@Override
public CourseInfoVo getCourseInfo(String courseId) {
// 1 查询课程表
EduCourse eduCourse = baseMapper.selectById(courseId);
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(eduCourse, courseInfoVo);
// 2 查询描述表
EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
courseInfoVo.setDescription(courseDescription.getDescription());
return courseInfoVo;
}
// 修改课程信息
@Override
public void updateCourseInfo(CourseInfoVo courseInfoVo) {
// 1 修改课程表
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo, eduCourse);
int update = baseMapper.updateById(eduCourse);
if (update == 0) {
throw new GuliException(20001, "修改课程信息失败");
}
// 2 修改描述表
EduCourseDescription description = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo, description);
courseDescriptionService.updateById(description);
}
2 前端实现
-
定义接口的两个方法:api/edu/course.js
// 3 根据课程id查询课程基本信息 getCourseInfo(id) { return request({ url: `/eduservice/course/getCourseInfo/${id}`, method: 'get' }) }, // 4 修改课程信息 updateCourseInfo(courseInfoVo) { return request({ url: '/eduservice/course/updateCourseInfo', method: 'post', data: courseInfoVo }) }
-
修改chapter.vue,跳转页面
// 上一步 previous() { this.$router.push({ path: '/course/info/' + this.courseId }) }, // 下一步 next() { this.$router.push({ path: '/course/publish/' + this.courseId }) }
-
在info页面实现数据回显
- 获取路由的课程id,调用根据id查询接口,数据显示
// 获取路由id值 if (this.$route.params && this.$route.params.id) { this.courseId = this.$route.params.id // 调用根据课程id查询 this.getInfo() }
// 7 根据课程id查询 getInfo() { course.getCourseInfo(this.courseId) .then(response => { this.courseInfo = response.data.courseInfoVo }) }
-
下拉列表数据回显
-
根据存储id和分类所有id进行比较,如果比较相同,让相同值进行数据回显
<!--selected选中--> <select> <option selected>前端开发</option> <select>
created() { // 修改 if (this.$route.params && this.$route.params.id) { // 获取路由id值 this.courseId = this.$route.params.id // 调用根据课程id查询 this.getInfo() } else { // 添加 // 初始化所有讲师 this.getListTeacher() // 初始化一级分类 this.getOneSubject() } },
// 7 根据课程id查询 getInfo() { course.getCourseInfo(this.courseId) .then(response => { // courseInfo包含一级分类id和二级分类id this.courseInfo = response.data.courseInfoVo // 1)查询所有的分类,包含一级和二级 subject.getSubjectList() .then(response => { // 2)获取所有一级分类 this.subjectOneList = response.data.list // 3)遍历一级分类,获取二级分类 for (const oneSubject of this.subjectOneList) { if (this.courseInfo.subjectParentId === oneSubject.id) { // 当前一级分类id和所有一级分类id比较 this.subjectTwoList = oneSubject.children } } }) // 初始化所有讲师 this.getListTeacher() }) }
-
-
添加、修改并保存
// 添加课程 addCourse() { course.addCourseInfo(this.courseInfo) .then(response => { // 提示信息 this.$message({ type: 'success', message: '添加课程信息成功' }) // 跳转到下一步 this.$router.push({ path: '/course/chapter/' + response.data.courseId }) }) }, // 修改课程 updateCourse() { course.updateCourseInfo(this.courseInfo) .then(response => { // 提示信息 this.$message({ type: 'success', message: '修改课程信息成功' }) // 跳转到下一步 this.$router.push({ path: '/course/chapter/' + this.courseId }) }) }, // 6 保存 saveOrUpdate() { // 判断添加还是修改 // 没有id值,添加 if (!this.courseInfo.id) { this.addCourse() } else { // 有id值,修改 this.updateCourse() } },
5 课程章节操作
添加、修改、删除
1 添加按钮:添加章节
<el-button type="text" @click="dialogChapterFormVisible=true">添加章节</el-button>
2 点击 添加章节 按钮,弹出添加框,输入章节信息,点击保存添加
<!-- 添加和修改章节表单 -->
<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>
3 开发章节后端接口
-
修改章节
-
添加章节
-
删除章节
- 如果章节里面没有小节,直接删除
- 如果章节里面有小节
- 第一种:删除章节的时候,把里面的所有小节都删除
- 第二种:不让其删除(√)
controller
// 添加章节
@PostMapping("addChapter")
public R addChapter(@RequestBody EduChapter eduChapter) {
chapterService.save(eduChapter);
return R.ok();
}
// 根据章节id查询
@GetMapping("getChapterInfo/{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);
return flag ? R.ok() : R.error();
}
service
// 删除章节
@Override
public boolean deleteChapter(String 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);
return result > 0;
}
}
4 前端调用接口
chapter.js
// 添加章节
addChapter(chapter) {
return request({
url: `/eduservice/chapter/addChapter`,
method: 'post',
data: chapter
})
},
// 根据id查询章节
getChapter(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'
})
}
chapter.vue
<ul>
<li v-for="chapter in chapterVideoList" :key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button type="text" @click="openEditChapter(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
</span>
</p>
<ul>
// 添加章节
addChapter() {
// 设置课程id到chapter对象中
this.chapter.courseId = this.courseId
chapter.addChapter(this.chapter)
.then(response => {
// 1) 关闭弹窗
this.dialogChapterFormVisible = false
// 2) 提示信息
this.$message({
type: 'success',
message: '添加章节成功'
})
// 3) 刷新页面
this.getChapterVideo()
})
},
// 修改章节
updateChapter() {
chapter.updateChapter(this.chapter)
.then(response => {
// 1) 关闭弹窗
this.dialogChapterFormVisible = false
// 2) 提示信息
this.$message({
type: 'success',
message: '修改章节成功'
})
// 3) 刷新页面
this.getChapterVideo()
})
},
// 保存
saveOrUpdate() {
if (!this.chapter.id) {
this.addChapter()
} else {
this.updateChapter()
}
},
// 删除章节
removeChapter(chapterId) {
// 1)弹出确认框
this.$confirm('此操作将删除章节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // 点击确认,执行then方法
chapter.deleteChapter(chapterId)
.then((response) => {
// 提示信息
this.$message({
type: 'success',
message: '删除成功!'
})
// 2) 刷新页面
this.getChapterVideo()
})
})
},
// 修改章节弹框数据回显
openEditChapter(chapterId) {
// 弹框
this.dialogChapterFormVisible = true
// 调用接口
chapter.getChapter(chapterId)
.then(response => {
this.chapter = response.data.chapter
})
}
6 课程小节操作
7 课程信息确认
1 概述
封面、课程名称、课程价格、课程分类、课程简介、课程讲师…
这些数据需要查询多张表,一般通过手写sql实现
多表连接查询:内连接、左外连接、右外连接
select ec.id,ec.title,ec.price,ec.lesson_num,
ecd.description,
et.`name`,
es1.title as oneSubject,
es2.title AS twoSubject
from edu_course ec LEFT OUTER JOIN edu_course_description ecd ON ec.id=ecd.id
LEFT OUTER JOIN edu_teacher et ON ec.teacher_id=et.id
LEFT OUTER JOIN edu_subject es1 ON ec.subject_parent_id=es1.id
LEFT OUTER JOIN edu_subject es2 ON ec.subject_id=es2.id
WHERE ec.id='1555079243711434754'
2 controller、service、mapper
controller
// 根据课程id查询课程确认信息
@GetMapping("getPublishCourseInfo/{id}")
public R getPublishCourseInfo(@PathVariable String id) {
CoursePublishVo coursePublishVo = courseService.publishCourseInfo(id);
return R.ok().data("publishCourse", coursePublishVo);
}
service
@Override
public CoursePublishVo publishCourseInfo(String id) {
CoursePublishVo publishCourseInfo = baseMapper.getPublishCourseInfo(id);
return publishCourseInfo;
}
mapper
public interface EduCourseMapper extends BaseMapper<EduCourse> {
public CoursePublishVo getPublishCourseInfo(String courseId);
}
<!--sql语句:根据课程id查询课程确认信息-->
<select id="getPublishCourseInfo" resultType="com.mys.eduservice.entity.vo.CoursePublishVo">
select ec.id,ec.title,ec.price,ec.lesson_num AS lessonNum,ec.cover,
ecd.description,
et.`name` as teacherName,
es1.title as subjectLevelOne,
es2.title AS subjectLevelTwo
from edu_course ec LEFT OUTER JOIN edu_course_description ecd ON ec.id=ecd.id
LEFT OUTER JOIN edu_teacher et ON ec.teacher_id=et.id
LEFT OUTER JOIN edu_subject es1 ON ec.subject_parent_id=es1.id
LEFT OUTER JOIN edu_subject es2 ON ec.subject_id=es2.id
WHERE ec.id=#{courseId}
</select>
3 测试报错与解决
问题:项目中创建mapper接口,编写xml文件写sql语句,执行出错:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.mys.eduservice.mapper.EduCourseMapper.getPublishCourseInfo
错误原因:由maven默认加载机制造成的,maven加载时,把java文件夹里面.java文件进行编译,其他类型文件不会加载
解决办法:
-
复制xml到target目录
-
把xml文件放到resources目录
-
推荐:通过配置实现
-
pom.xml
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
application.properties
# 配置mapper.xml文件的路径 mybatis-plus.mapper-locations=classpath:com/mys/eduservice/mapper/xml/*.xml
-
4 前端实现:显示查询的课程确认信息
api/edu/course.js
// 5 课程确认信息的显示
getPublishCourseInfo(id) {
return request({
url: `/eduservice/course/getPublishCourseInfo/${id}`,
method: 'get'
})
}
publish.vue
created() {
// 1 获取路由课程id
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
// 2 调用接口方法,根据课程id查询
this.getPublishCourseId()
}
},
// 根据课程id查询确认发布信息
getPublishCourseId() {
course.getPublishCourseInfo(this.courseId)
.then(response => {
this.publishCourse = response.data.publishCourse
// 提示信息
this.$message({
type: 'success',
message: '查询课程发布确认信息成功'
})
})
}
8 课程最终发布
修改课程的status状态是Normal-已发布
controller
// 课程最终发布:修改课程状态
@PostMapping("publishCourse/{id}")
public R publishCourse(@PathVariable String id) {
EduCourse eduCourse = new EduCourse();
eduCourse.setId(id);
eduCourse.setStatus("Normal");
courseService.updateById(eduCourse);
return R.ok();
}
publish.vue
// 发布
publish() {
// 调用发布方法
course.publishCourse(this.courseId)
.then(response => {
// 提示信息
this.$message({
type: 'success',
message: '发布成功'
})
// 跳转到课程列表
this.$router.push({ path: '/course/list' })
})
},