尚硅谷谷粒学院项目第八天内容之添加课程基本信息、整合文本编辑器、课程大纲管理(课程大纲列表显示、章节信息增删改、小节信息增删改)、课程信息确认(将课程信息添加到数据库)
添加富文本编辑器组件
把资料中的components里的文件夹复制到项目的components里,static里的复制到static里
在build/webpack.dev.conf.js中添加
templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}
在index.js中加入
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js>0</script>
报错,重启;爆红,不用管
在info.vue中引入组件、声明组件
import Tinymce from '@/components/Tinymce';
components: { Tinymce }, //声明组件
富文本编辑器组件的使用
<!-- 课程简介-->
<el-form-item label="课程简介">
<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>
在页面的最后添加一个样式
<style scoped>
.tinymce-container {
line-height: 29px;
}
</style>
效果
在后端中的courseInfo类中添加subjectParentId属性
添加一个课程进行测试
注意前端传过去的courseInfo和后端接收的courseInfo的属性名要一样
在数据库里查看课程是否添加成功(课程表和课程描述表都应该添加进一条数据)
创建课程大纲后端实现
创建实体类Chapter和Video,用于向前端返回数据
entity/chapter/Chapter
@Data
public class Chapter {
String id;
String title;
List<Video> children=new ArrayList<>();
}
entity/chapter/Video
@Data
public class Video {
String id;
String title;
}
controller
@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin
public class EduChapterController {
@Autowired
private EduChapterService chapterService;
//根据courseId获取对应的章节和小节
@GetMapping("getChapterVideo/{courseId}")
public R getChapterVideo(@PathVariable String courseId){
List<Chapter> list=chapterService.getChapterVideo(courseId);
return R.ok().data("list",list);
}
}
service
public interface EduChapterService extends IService<EduChapter> {
List<Chapter> getChapterVideo(String courseId);
}
serviceImpl
@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {
@Autowired
private EduVideoService videoService;
//根据courseId得到对应的章节和小节
@Override
public List<Chapter> getChapterVideo(String courseId) {
ArrayList<Chapter> list = new ArrayList<>();
QueryWrapper<EduChapter> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.eq("course_id",courseId);
List<EduChapter> eduChapterList = baseMapper.selectList(queryWrapper1);
QueryWrapper<EduVideo> queryWrapper2 = new QueryWrapper<>();
queryWrapper1.eq("course_id",courseId);
List<EduVideo> eduVideoList = videoService.list(queryWrapper2);
for(EduChapter eduChapter:eduChapterList){
Chapter chapter = new Chapter();
BeanUtils.copyProperties(eduChapter,chapter);
for (EduVideo eduVideo:eduVideoList){
if(eduChapter.getId().equals(eduVideo.getChapterId())){
Video video = new Video();
BeanUtils.copyProperties(eduVideo,video);
chapter.getChildren().add(video);
}
}
list.add(chapter);
}
return list;
}
}
swagger测试
创建课程大纲前端实现
chapter.js
import request from '@/utils/request'
export default{
//根据courseId查询章节和小节
getChapterVideo(courseId){
return request({
url:`eduservice/chapter/getChapterVideo/${courseId}`,
method:'get'
})
}
}
router/index.js
{
path: 'chapter/:id',
name: '编辑课程大纲',
component: () => import('@/views/edu/course/chapter.vue'),
meta: { title: '编辑课程大纲', noCache: true },
hidden: true
},
chapter.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
<!-- 章节 -->
<ul class="chanpterList">
<li
v-for="chapter in chapterVideoList"
:key="chapter.id">
<p>
{{ chapter.title }}
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>{{ video.title }}</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 chapter 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 //小节弹框
}
},
created() {
//获取路由的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//根据课程id查询章节和小节
this.getChapterVideo()
}
},
methods:{
//根据课程id查询章节和小节
getChapterVideo() {
chapter.getChapterVideo(this.courseId)
.then(response => {
this.chapterVideoList = response.data.list
})
},
previous() {
this.$router.push({path:'/course/info/'+this.courseId})
},
next() {
//跳转到第二步
this.$router.push({path:'/course/publish/'+this.courseId})
}
}
}
</script>
<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>
测试
修改课程基本信息后端实现
controller
//添加课程信息
@PostMapping("addCourseInfo")
public R addCourseInfo(@RequestBody CourseInfo courseInfo){
String id = courseService.addCourseInfo(courseInfo);
return R.ok().data("courseId",id);
}
//根据courseId查询课程信息
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@PathVariable String courseId){
CourseInfo courseInfo=courseService.getCourseInfo(courseId);
return R.ok().data("courseInfo",courseInfo);
}
//修改课程基本信息
@PostMapping("updateCourseInfo")
private R updateCourseInfo(@RequestBody CourseInfo courseInfo){
courseService.updateCourseInfo(courseInfo);
return R.ok();
}
service
CourseInfo getCourseInfo(String courseId);
void updateCourseInfo(CourseInfo courseInfo);
serviceImpl
//根据courseId查询课程信息
@Override
public CourseInfo getCourseInfo(String courseId) {
CourseInfo courseInfo = new CourseInfo();
EduCourse eduCourse = baseMapper.selectById(courseId);
BeanUtils.copyProperties(eduCourse,courseInfo);
EduCourseDescription description = courseDescriptionService.getById(courseId);
courseInfo.setDescription(description.getDescription());
return courseInfo;
}
//修改课程基本信息
@Override
public void updateCourseInfo(CourseInfo courseInfo) {
EduCourse course = new EduCourse();
BeanUtils.copyProperties(courseInfo,course);
baseMapper.updateById(course);
EduCourseDescription description = new EduCourseDescription();
BeanUtils.copyProperties(courseInfo,description);
courseDescriptionService.updateById(description);
}
swagger测试
修改课程基本信息前端实现
info.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 所属分类 TODO -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类" @change="getTwoSort">
<el-option
v-for="sort in oneSort"
:key="sort.id"
:label="sort.title"
:value="sort.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="二级分类">
<el-option
v-for="sort in twoSort"
:key="sort.id"
:label="sort.title"
:value="sort.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>
<!-- 课程简介-->
<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+'/ossservice/uploadAvatar'"
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 Tinymce from '@/components/Tinymce';
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
export default {
components: { Tinymce }, //声明组件
data() {
return {
saveBtnDisabled:false,
courseInfo:{
title: '',
subjectId: '',//二级分类id
subjectParentId:'',//一级分类id
teacherId: '',
lessonNum: 0,
description: '',
cover: '/static/01.jpg',
price: 0
},
courseId:'',
BASE_API: process.env.BASE_API, // 接口API地址
teacherList:[],//封装所有的讲师
oneSort:[],//一级分类
twoSort:[]//二级分类
}
},
created() {
//获取路由的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
this.getCourseInfo()
}
//初始化所有讲师
this.queryAllTeacher()
//初始化一级分类
this.getOneSort()
},
methods:{
//根据courseId获取课程信息,数据回显
getCourseInfo(){
course.getCourseInfo(this.courseId)
.then(response=>{
this.courseInfo=response.data.courseInfo
})
},
//上传封面成功调用的方法
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
},
//根据一级分类查询对应的二级分类
getTwoSort(value){ //value是一级分类id
this.courseInfo.subjectId=''
for(var i=0;i<this.oneSort.length;i++){
if(this.oneSort[i].id===value){
this.twoSort=this.oneSort[i].children
}
}
},
//查询所有的一级分类
getOneSort(){
this.courseInfo.subjectParentId=''
subject.getAllSubjectSort()
.then(response=>{
this.oneSort=response.data.list
})
},
//查询所有的讲师
queryAllTeacher() {
course.queryAllTeacher()
.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>
chapter.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
<!-- 章节 -->
<ul class="chanpterList">
<li
v-for="chapter in chapterVideoList"
:key="chapter.id">
<p>
{{ chapter.title }}
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>{{ video.title }}</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 chapter 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 //小节弹框
}
},
created() {
//获取路由的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//根据课程id查询章节和小节
this.getChapterVideo()
}
},
methods:{
//根据课程id查询章节和小节
getChapterVideo() {
chapter.getChapterVideo(this.courseId)
.then(response => {
this.chapterVideoList = response.data.list
})
},
previous() {
this.$router.push({path:'/course/info/'+this.courseId})
},
next() {
//跳转到第二步
this.$router.push({path:'/course/publish/'+this.courseId})
}
}
}
</script>
<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>
此时,在创建课程大纲页面点击上一步,数据回显时,二级分类显示的是id值
此时,twoSort=[],没法比较,所以直接把id值回显了
修改getCourseInfo()方法
subject.getAllSubjectSort()
.then(response=>{
this.oneSort=response.data.list
for(var i=0;i<this.oneSort.length;i++){
if(this.oneSort[i].id==this.courseInfo.subjectParentId){
this.twoSort=this.oneSort[i].children
}
}
})
二级分类就能成功回显了
此时,在创建课程大纲页面点击上一步到 课程基本信息页面,再点击添加课程,发现数据依然回显
不能在created()里写清空数据的逻辑,因为相同页面的跳转,created()只会执行一次
添加路由监视器
created() {
//获取路由的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
this.getCourseInfo()
}else{
this.init()
}
},
watch:{
$route(to,from){
this.init()
}
},
methods:{
init(){
this.courseInfo={
cover: '/static/01.jpg'
}
//初始化所有讲师
this.queryAllTeacher()
//初始化一级分类
this.getOneSort()
},
}
重启前端,测试
修改课程信息
info.vue
//添加课程
addCourseInfo(){
course.addCourseInfo(this.courseInfo)
.then(response => {
//提示
this.$message({
type: 'success',
message: '添加课程信息成功!'
});
//路由跳转
this.$router.push({path:'/course/chapter/'+response.data.courseId})
})
},
//修改课程
updateCourseInfo(){
course.updateCourseInfo(this.courseInfo)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '修改课程信息成功!'
});
//路由跳转
this.$router.push({path:'/course/chapter/'+this.courseId})
})
},
saveOrUpdate() {
if(this.courseId){ //有id值,修改课程信息
this.updateCourseInfo()
}else{
this.addCourseInfo()
}
}
}
章节增删改查
后端
chapter.vue
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
controller
//添加章节
@PostMapping("addChapter")
public R addChapter(@RequestBody EduChapter eduChapter){
chapterService.save(eduChapter);
return R.ok();
}
//根据chapter_id查询章节信息
@GetMapping("queryChapter/{chapterId}")
public R queryChapter(@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("deleteChapter/{chapterId}")
public R deleteChapter(@PathVariable String chapterId){
boolean b=chapterService.deleteChapter(chapterId);
return R.ok();
}
service
boolean deleteChapter(String chapterId);
serviceImpl
@Override
public boolean deleteChapter(String chapterId) {
QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("chapter_id",chapterId);
int i = videoService.count(queryWrapper);
if (i==0){
baseMapper.deleteById(chapterId);
return true;
}else {
throw new GuliException(20000,"章节下面存在小节,章节删除失败");
}
}
前端
chapter.js
//添加章节
addChapter(chapter){
return request({
url:`eduservice/chapter/addChapter`,
method:'post',
data:chapter
})
},
//根据chapterId查询章节信息
queryChapter(chapterId){
return request({
url:`eduservice/chapter/queryChapter/${chapterId}`,
method:'get'
})
},
//修改章节
updateChapter(chapter){
return request({
url:`eduservice/chapter/updateChapter`,
method:'post',
data:chapter
})
},
//删除章节
deleteChapter(chapterId){
return request({
url:`eduservice/chapter/deleteChapter/${chapterId}`,
method:'delete'
})
}
chapter.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
</el-steps>
<el-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 style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
<el-button style="" type="text" @click="openEditChatper(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
</span>
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>{{ video.title }}
<span class="acts">
<el-button style="" type="text">编辑</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>
</div>
</template>
<script>
import chapter from '@/api/edu/chapter'
export default {
data() {
return {
saveBtnDisabled:false,
courseId:'',//课程id
chapterVideoList:[],
chapter:{ //封装章节数据
courseId:'',
title: '',
sort: 0
},
video: {
title: '',
sort: 0,
free: 0,
videoSourceId: ''
},
dialogChapterFormVisible:false,//章节弹框
dialogVideoFormVisible:false //小节弹框
}
},
created() {
//获取路由的id值
if(this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id
//根据课程id查询章节和小节
this.getChapterVideo()
}
},
methods:{
//根据课程id查询章节和小节
getChapterVideo() {
chapter.getChapterVideo(this.courseId)
.then(response => {
this.chapterVideoList = response.data.list
})
},
//添加章节
addChapter(){
this.chapter.courseId=this.courseId;
chapter.addChapter(this.chapter)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '章节信息添加成功!'
});
this.chapter.title=''
this.chapter.sort=0
this.getChapterVideo() //刷新页面
})
},
saveOrUpdate(){
if(this.chapter.id){
this.updateChapter()
}else{
this.addChapter()
}
this.dialogChapterFormVisible=false //关闭弹窗
},
//弹出添加章节的表单
openChapterDialog(){
this.dialogChapterFormVisible=true
},
//弹出章节的表单并数据回显
openEditChatper(chapterId){
this.dialogChapterFormVisible=true
chapter.queryChapter(chapterId)
.then(response=>{
this.chapter=response.data.chapter
})
},
//修改章节
updateChapter(){
chapter.updateChapter(this.chapter)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '章节信息修改成功!'
});
this.chapter.title=''
this.chapter.sort=0
this.getChapterVideo() //刷新页面
})
},
//删除章节
removeChapter(chapterId){
this.$confirm('此操作将删除章节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { //点击确定,执行then方法
chapter.deleteChapter(chapterId)
.then(response =>{
//提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//刷新页面
this.getChapterVideo()
})
}) //点击取消,执行catch方法
},
previous() {
this.$router.push({path:'/course/info/'+this.courseId})
},
next() {
//跳转到第二步
this.$router.push({path:'/course/publish/'+this.courseId})
}
}
}
</script>
<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>
小节增删改查
后端
controller
@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("deleteVideo/{videoId}")
public R deleteVideo(@PathVariable String videoId){
videoService.removeById(videoId);
return R.ok();
}
//查询小节
@GetMapping("queryVideo/{videoId}")
public R queryVideo(@PathVariable String videoId){
EduVideo eduVideo = videoService.getById(videoId);
return R.ok().data("video",eduVideo);
}
//修改小节
@PostMapping("updateVideo")
public R updateVideo(@RequestBody EduVideo eduVideo){
videoService.updateById(eduVideo);
return R.ok();
}
}
前端
chapter.vue
添加小节弹框组件
<!-- 添加和修改课时表单 -->
<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>
//弹出添加小节的表单
openVideo(chapterId){
this.dialogVideoFormVisible=true
this.video.chapterId=chapterId
this.video.courseId=this.courseId
},
//添加小节
addVideo(){
video.addVideo(this.video)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '小节信息添加成功!'
});
this.dialogVideoFormVisible=false
this.video.title=''
this.video.sort=0
this.video.free=0
this.getChapterVideo() //刷新页面
})
},
//弹出小节的表单并数据回显
openEditVideo(videoId){
this.dialogVideoFormVisible=true
video.queryVideo(videoId)
.then(response=>{
this.video=response.data.video
})
},
//修改小节
updateVideo(){
video.updateVideo(this.video)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '小节信息修改成功!'
});
this.dialogVideoFormVisible=false
this.video.title=''
this.video.sort=0
this.video.free=0
this.video.id=''
this.getChapterVideo() //刷新页面
})
},
saveOrUpdateVideo(){
if(this.video.id){
this.updateVideo()
}else{
this.addVideo()
}
},
//删除小节
removeVideo(videoId){
this.$confirm('此操作将删除小节, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { //点击确定,执行then方法
video.deleteVideo(videoId)
.then(response =>{
//提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//刷新页面
this.getChapterVideo()
})
}) //点击取消,执行catch方法
},
课程发布
后端
EduCourseController
//课程最终发布
@GetMapping("getCoursePublish/{courseId}")
private R getCoursePublish(@PathVariable String courseId){
CoursePublishVo coursePublishVo=courseService.getCoursePublish(courseId);
return R.ok().data("coursePublish",coursePublishVo);
}
service
CoursePublishVo getCoursePublish(String courseId);
serviceImpl
//获取CoursePublish(最终发布的课程信息)
@Override
public CoursePublishVo getCoursePublish(String courseId) {
return baseMapper.getCoursePublish(courseId);
}
eduCourseMapper
CoursePublishVo getCoursePublish(String courseId);
EduCourseMapper.xml
<select id="getCoursePublish" resultType="com.jiabei.eduservice.vo.CoursePublishVo">
SELECT
ec.id,
ec.title,
ec.cover,
ec.lesson_num AS lessonNum,
ec.price,
s1.title AS subjectLevelOne,
s2.title AS subjectLevelTwo,
t.name AS teacherName
FROM
edu_course ec
LEFT JOIN edu_teacher t ON ec.id = t.id
LEFT JOIN edu_subject s1 ON ec.subject_parent_id = s1.id
LEFT JOIN edu_subject s2 ON ec.id = s2.id
WHERE
ec.id =#{id}
</select>
swagger测试,报错:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.jiabei.eduservice.mapper.EduCourseMapper.getCoursePublish
这个异常说明mapper找不到,要么mapper和mapper.xml名称不一致,要么编译时只加载了java文件,没有加载xml文件
解决
service下pom.xml中添加
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
service_edu下的application.properties中添加
mybatis-plus.mapper-locations=classpath:com/jiabei/eduservice/mapper/xml/*.xml
swagger测试,成功