硅谷课堂第八天-点播管理模块(二)
一、发布课程-创建课程大纲
重点就放在章节和小节的接口上
看看大纲的数据JSON结构:
数组结构的JSON,明显是返回List集合
1、课程章节接口
实现课程章节的列表、添加、修改和删除功能
1.1、编写章节Controller
@RestController
@RequestMapping("/admin/vod/chapter")
@CrossOrigin
@Api(tags = "章节管理接口")
public class ChapterController {
@Autowired
private ChapterService chapterService;
//1大纲列表(章节和小节列表也叫课时)树形结构显示 截图json数据格式(是个数组。对应java中list集合)children是小节的集合
/**
* 第一章
* 第一节
* 第二节
* 第二章
* 第一节
* 第二节
**/
@ApiOperation("大纲列表")
@GetMapping("getNestedTreeList/{courseId}")
public Result getTreeList(@PathVariable Long courseId){
//看看chapterVo有什么属性,他里面还用到了videoVO这是小节(课时)里面的内容 看看course表跟chapter表字段,还有那张图的映射关系
List<ChapterVo> list=chapterService.getTreeList(courseId);
return Result.ok(list);
}
//2添加章节
@ApiOperation("添加章节")
@PostMapping("save")
public Result save(@RequestBody Chapter chapter){
chapterService.save(chapter);
return Result.ok(null);
}
//3修改-根据id查询
@ApiOperation("根据id查询章节信息")
@GetMapping("get/{id}")
public Result get(@PathVariable Long id){
Chapter chapter=chapterService.getById(id);
return Result.ok(chapter);
}
//4修改实现(@putMapping也行,resultufl风格推荐用putMapping)
@PostMapping("update")
public Result update(@RequestBody Chapter chapter){
chapterService.updateById(chapter);
return Result.ok(null);
}
//5删除章节(这里好像只删除了章节没有删除里面的小节,后边腾讯云点播管理模块三完善了该功能)
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id){
chapterService.removeById(id);
return Result.ok(null);
}
}
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
mybatisplus实现逻辑删除:mybatisplus使用
json格式:
ChapterVo(章节对象)
@ApiModel("课程章节对象")
@Data
public class ChapterVo {
@ApiModelProperty(value = "章节ID")
private Long id;
@ApiModelProperty(value = "章节标题")
private String title;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "章节下的课时列表")
//小节(课时)用VideoVo进行封装
private List<VideoVo> children = new ArrayList<>();
}
VideoVo(小节也是课时对象)
@ApiModel("课时信息")
@Data
public class VideoVo {
@ApiModelProperty(value = "课时ID")
private Long id;
@ApiModelProperty(value = "课时标题")
private String title;
@ApiModelProperty(value = "是否可以试听")
private Integer isFree;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "腾讯云视频ID")
private String videoSourceId;
}
course表:
chapter表:
表关系:
效果图:
1.2、编写章节Service
(1)ChapterService
public interface ChapterService extends IService<Chapter> {
1大纲列表(章节和小节列表也叫课时)树形结构显示
List<ChapterVo> getTreeList(Long courseId);
}
(2)ChapterServiceImpl
@Service
public class ChapterServiceImpl extends ServiceImpl<ChapterMapper, Chapter> implements ChapterService {
@Autowired
private VideoService videoService;
//章节小结列表封装
@Override
public List<ChapterVo> getTreeList(Long courseId) {
//定义最终数据List集合
List<ChapterVo>finalChapterList=new ArrayList<>();
//1根据courseId获取课程所有章节
QueryWrapper<Chapter>wrapperChapter=new QueryWrapper<>();
wrapperChapter.eq("course_id",courseId);
//如果是自己的实现类就用baseMapper去查询,不是自己的就调用
List<Chapter>chapterList=baseMapper.selectList(wrapperChapter);
//2根据courseId获取课程里面所有小节(课时) video是小节内容
// 利用LambdaQueryWrapper写方便复习一下之前的知识
//其实等同于上边获取课程章节的方法,这里使用java8实现 查询一下::的意义
LambdaQueryWrapper<Video> wrapperVideo=new LambdaQueryWrapper<>();
wrapperVideo.eq(Video::getCourseId,courseId);
List<Video> videoList=videoService.list(wrapperVideo);
//封装章节(记住一个原则遇到集合要么遍历要么return)
//遍历所有章节 自己写的话定义一个sql语句也能实现这是一个嵌套循环,多多理解去
for (int i = 0; i <chapterList.size() ; i++) {
//得到课程每个章节
Chapter chapter=chapterList.get(i);
// (该集合的泛型为ChapterVo而我们得到课程每个章节为chapter所以要它转换为chapervo中)
ChapterVo chapterVo=new ChapterVo();
BeanUtils.copyProperties(chapter,chapterVo);
//得到每个章节对象放到finalChaperList集合
finalChapterList.add(chapterVo);
//封装章节里的小节
//创建List集合用户封装章节里的所有小节
List<VideoVo>videoVoList=new ArrayList<>();
//遍历小节List
for (Video video:videoList) {
//判断小节是哪个章节下面的
//章节id 和小节chapter_id相同则为一个章节
if(chapter.getId().equals(video.getChapterId())){
//video--videoVo跟上边同理
VideoVo videoVo=new VideoVo();
BeanUtils.copyProperties(video,videoVo);
//放到videoVoList
videoVoList.add(videoVo);
}
}
//把章节里面所有小节集合放到每个章节里面
chapterVo.setChildren(videoVoList);
}
return finalChapterList;
}
}
知识补充:
java8中可以通过 “::” 关键字来访问类的构造方法,对象方法,静态方法
ChapterVo:
@ApiModel("课程章节对象")
@Data
public class ChapterVo {
@ApiModelProperty(value = "章节ID")
private Long id;
@ApiModelProperty(value = "章节标题")
private String title;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "章节下的课时列表")
//小节(课时)用VideoVo进行封装
private List<VideoVo> children = new ArrayList<>();
}
2、课程小节接口
2.1、编写VideoController
@RestController
@RequestMapping("/admin/vod/video")
@CrossOrigin
@Api(tags = "小节管理接口")
public class VideoController {
@Autowired
private VideoService videoService;
//根据id查询小节
@ApiOperation(value = "获取")
@GetMapping("get/{id}")
public Result get(@PathVariable Long id) {
Video video = videoService.getById(id);
return Result.ok(video);
}
//小节新增
@ApiOperation(value = "新增")
@PostMapping("save")
public Result save(@RequestBody Video video) {
videoService.save(video);
return Result.ok(null);
}
//小节修改
@ApiOperation(value = "修改")
@PutMapping("update")
public Result updateById(@RequestBody Video video) {
videoService.updateById(video);
return Result.ok(null);
}
//小节删除
@ApiOperation(value = "删除")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {
videoService.removeById(id);
return Result.ok(null);
}
}
3、课程大纲前端
3.1、定义接口
(1)chapter.js
import request from '@/utils/request'
const api_name = '/admin/vod/chapter'
export default {
getNestedTreeList(courseId) {
return request({
url: `${api_name}/getNestedTreeList/${courseId}`,
method: 'get'
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
save(chapter) {
return request({
url: `${api_name}/save`,
method: 'post',
data: chapter
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
updateById(chapter) {
return request({
url: `${api_name}/update`,
method: 'post',
data: chapter
})
}
}
(2)创建video.js
import request from '@/utils/request'
const api_name = '/admin/vod/video'
export default {
save(video) {
return request({
url: `${api_name}/save`,
method: 'post',
data: video
})
},
getById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
updateById(video) {
return request({
url: `${api_name}/update`,
method: 'put',
data: video
})
},
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
}
}
3.2、编写章节页面
(1)Chapter -> index.vue
<template>
<div class="app-container">
<!-- 添加章节按钮 -->
<div>
<el-button type="primary" @click="addChapter()">添加章节</el-button>
</div>
<!-- 章节列表 -->
<ul class="chapterList">
<li
v-for="chapter in chapterList"
:key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button type="text" @click="addVideo(chapter.id)">添加课时</el-button>
<el-button type="text" @click="editChapter(chapter.id)">编辑</el-button>
<el-button type="text" @click="removeChapterById(chapter.id)">删除</el-button>
</span>
</p>
<!-- 视频 -->
<ul class="chapterList videoList">
<li
v-for="video in chapter.children"
:key="video.id">
<p>
{{ video.title }}
<el-tag v-if="!video.videoSourceId" size="mini" type="danger">
{{ '尚未上传视频' }}
</el-tag>
<span class="acts">
<el-tag v-if="video.isFree" size="mini" type="success">{{ '免费观看' }}</el-tag>
<el-button type="text" @click="editVideo(chapter.id, video.id)">编辑</el-button>
<el-button type="text" @click="removeVideoById(video.id)">删除</el-button>
</span>
</p>
</li>
</ul>
</li>
</ul>
<!-- 章节表单对话框 -->
<chapter-form ref="chapterForm" />
<!-- 课时表单对话框 -->
<video-form ref="videoForm" />
<div style="text-align:center">
<el-button type="primary" @click="prev()">上一步</el-button>
<el-button type="primary" @click="next()">下一步</el-button>
</div>
</div>
</template>
<script>
import chapterApi from '@/api/vod/chapter'
import videoApi from '@/api/vod/video'
// 引入组件
import ChapterForm from '@/views/vod/course/components/Chapter/Form'
import VideoForm from '@/views/vod/course/components/Video/Form'
export default {
// 注册组件
components: { ChapterForm, VideoForm },
data() {
return {
chapterList: [] // 章节嵌套列表
}
},
created() {
this.fetchNodeList()
},
methods: {
// 获取章节小节数据
fetchNodeList() {
chapterApi.getNestedTreeList(this.$parent.courseId).then(response => {
this.chapterList = response.data
})
},
//删除章节
removeChapterById(chapterId) {
this.$confirm('此操作将永久删除该章节,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return chapterApi.removeById(chapterId)
}).then(response => {
this.fetchNodeList()
this.$message.success(response.message)
}).catch((response) => {
if (response === 'cancel') {
this.$message.info('取消删除')
}
})
},
// 添加章节
addChapter() {
this.$refs.chapterForm.open()
},
// 编辑章节
editChapter(chapterId) {
this.$refs.chapterForm.open(chapterId)
},
// 添加课时
addVideo(chapterId) {
this.$refs.videoForm.open(chapterId)
},
// 编辑课时
editVideo(chapterId, videoId) {
this.$refs.videoForm.open(chapterId, videoId)
},
// 删除课时
removeVideoById(videoId) {
this.$confirm('此操作将永久删除该课时, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return videoApi.removeById(videoId)
}).then(response => {
this.fetchNodeList()
this.$message.success(response.message)
}).catch((response) => {
if (response === 'cancel') {
this.$message.info('取消删除')
}
})
},
// 上一步
prev() {
this.$parent.active = 0
},
// 下一步
next() {
this.$parent.active = 2
}
}
}
</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 dashed #DDD;
}
</style>
(2)Chapter -> Form.vue
<template>
<!-- 添加和修改章节表单 -->
<el-dialog :visible="dialogVisible" title="添加章节" @close="close()">
<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"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="close()">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate()">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
import chapterApi from '@/api/vod/chapter'
export default {
data() {
return {
dialogVisible: false,
chapter: {
sort: 0
}
}
},
methods: {
open(chapterId) {
this.dialogVisible = true
if (chapterId) {
chapterApi.getById(chapterId).then(response => {
this.chapter = response.data
})
}
},
close() {
this.dialogVisible = false
// 重置表单
this.resetForm()
},
resetForm() {
this.chapter = {
sort: 0
}
},
saveOrUpdate() {
if (!this.chapter.id) {
this.save()
} else {
this.update()
}
},
save() {
this.chapter.courseId = this.$parent.$parent.courseId
chapterApi.save(this.chapter).then(response => {
this.$message.success(response.message)
// 关闭组件
this.close()
// 刷新列表
this.$parent.fetchNodeList()
})
},
update() {
chapterApi.updateById(this.chapter).then(response => {
this.$message.success(response.message)
// 关闭组件
this.close()
// 刷新列表
this.$parent.fetchNodeList()
})
}
}
}
</script>
3.3、编写小节(课时)页面
(1)Video -> Form.vue
<template>
<!-- 添加和修改课时表单 -->
<el-dialog :visible="dialogVisible" title="添加课时" @close="close()">
<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" />
</el-form-item>
<el-form-item label="是否免费">
<el-radio-group v-model="video.isFree">
<el-radio :label="0">免费</el-radio>
<el-radio :label="1">默认</el-radio>
</el-radio-group>
</el-form-item>
<!-- 上传视频 -->
<el-form-item label="上传视频">
<el-upload
ref="upload"
:auto-upload="false"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-exceed="handleUploadExceed"
:file-list="fileList"
:limit="1"
:before-remove="handleBeforeRemove"
:on-remove="handleOnRemove"
:action="BASE_API+'/admin/vod/upload'">
<el-button slot="trigger" size="small" type="primary">选择视频</el-button>
<el-button
:disabled="uploadBtnDisabled"
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload()">上传</el-button>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="close()">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate()">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
import videoApi from '@/api/vod/video'
//import vodApi from '@/api/vod/vod'
export default {
data() {
return {
BASE_API: 'http://localhost:8301',
dialogVisible: false,
video: {
sort: 0,
free: false
},
fileList: [], // 上传文件列表
uploadBtnDisabled: false
}
},
methods: {
open(chapterId, videoId) {
this.dialogVisible = true
this.video.chapterId = chapterId
if (videoId) {
videoApi.getById(videoId).then(response => {
this.video = response.data
// 回显
if (this.video.videoOriginalName) {
this.fileList = [{ 'name': this.video.videoOriginalName }]
}
})
}
},
close() {
this.dialogVisible = false
// 重置表单
this.resetForm()
},
resetForm() {
this.video = {
sort: 0,
free: false
}
this.fileList = [] // 重置视频上传列表
},
saveOrUpdate() {
if (!this.video.id) {
this.save()
} else {
this.update()
}
},
save() {
this.video.courseId = this.$parent.$parent.courseId
videoApi.save(this.video).then(response => {
this.$message.success(response.message)
// 关闭组件
this.close()
// 刷新列表
this.$parent.fetchNodeList()
})
},
update() {
videoApi.updateById(this.video).then(response => {
this.$message.success(response.message)
// 关闭组件
this.close()
// 刷新列表
this.$parent.fetchNodeList()
})
},
// 上传多于一个视频
handleUploadExceed(files, fileList) {
this.$message.warning('想要重新上传视频,请先删除已上传的视频')
},
// 上传
submitUpload() {
this.uploadBtnDisabled = true
this.$refs.upload.submit() // 提交上传请求
},
// 视频上传成功的回调
handleUploadSuccess(response, file, fileList) {
this.uploadBtnDisabled = false
this.video.videoSourceId = response.data
this.video.videoOriginalName = file.name
},
// 失败回调
handleUploadError() {
this.uploadBtnDisabled = false
this.$message.error('上传失败2')
},
// 删除视频文件确认
handleBeforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${file.name}?`)
},
// 执行视频文件的删除
handleOnRemove(file, fileList) {
if (!this.video.videoSourceId) {
return
}
}
}
}
</script>
二、发布课程-课程最终发布
1、课程最终发布接口
1.1、编写CourseController
添加方法
/**
* 根据课程id获取课程发布信息
* @param id
* @return
*/
@ApiOperation("根据id获取课程发布信息")
@GetMapping("getCoursePublishVo/{id}")
public Result getCoursePublishVoById(
@ApiParam(value = "课程ID", required = true)
@PathVariable Long id){
CoursePublishVo coursePublishVo = courseService.getCoursePublishVo(id);
return Result.ok(coursePublishVo);
}
/**
* 根据课程id发布课程
* @param id
* @return
*/
@ApiOperation("课程最终发布")
@PutMapping("publishCourseById/{id}")
public Result publishCourseById(
@ApiParam(value = "课程ID", required = true)
@PathVariable Long id){
courseService.publishCourseById(id);
return Result.ok(null);
}
CoursePublishVo:
@ApiModel("课程发布对象")
@Data
public class CoursePublishVo {
@ApiModelProperty(value = "课程ID")
private String id;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "一级分类标题")
private String subjectParentTitle;
@ApiModelProperty(value = "二级分类标题")
private String subjectTitle;
@ApiModelProperty(value = "讲师姓名")
private String teacherName;
@ApiModelProperty(value = "课程销售价格")
private String price;//只用于显示
}
1.2、编写CourseService
//根据id获取课程发布信息
CoursePublishVo getCoursePublishVo(Long id);
//根据id发布课程
void publishCourseById(Long id);
1.3、编写CourseServiceImpl
/***
* 根据课程id获取课程发布信息(涉及三张表课程基本信息表,讲师表,分类表所以要自己写一个sql语句)
* @param id
* @return
*/
@Override
public CoursePublishVo getCoursePublishVo(Long id) {
return baseMapper.selectCoursePublishVoById(id);
}
/**
* 课程最终发布(修改发布状态)
* @param id
* @return
*/
@Override
public void publishCourseById(Long id) {
//根据id获取课程信息
Course course=baseMapper.selectById(id);
course.setStatus(1);//1为已经发布
course.setPublishTime(new Date());
baseMapper.updateById(course);
}
1.4、编写CourseMapper
public interface CourseMapper extends BaseMapper<Course> {
/**
* 根据课程id获取课程发布信息
* @param id
* @return
*/
CoursePublishVo selectCoursePublishVoById(Long id);
}
1.5、编写CourseMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.ggkt.vod.mapper.CourseMapper">
<select id="selectCoursePublishVoById" resultType="com.atguigu.ggkt.vo.vod.CoursePublishVo">
SELECT
c.id,
c.title,
c.cover,
c.lesson_num AS lessonNum,
c.price,
t.name AS teacherName,
s1.title AS subjectParentTitle,
s2.title AS subjectTitle
FROM course c
LEFT OUTER JOIN teacher t ON c.teacher_id=t.id
LEFT OUTER JOIN `subject` s1 ON c.subject_parent_id=s1.id
LEFT OUTER JOIN `subject` s2 ON c.subject_id=s2.id
WHERE c.id=#{id}
</select>
</mapper>
left outer join简写left join
sql中subject是关键字,表中的字段名是MySql的关键字时,我们可以在表名上面加``
容易混淆的关键字有subject,describe等等
1.6、添加配置(为什么)
为什么添加配置:
不添加会报这样的错误,因为扫描不到
(1)application.properties添加
mybatis-plus.mapper-locations=classpath:com/atguigu/ggkt/vod/mapper/xml/*.xml
(2)service模块pom.xml添加
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes> <include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2、课程最终发布前端
2.1、course.js定义接口
//获取发布课程信息
getCoursePublishById(id) {
return request({
url: `${api_name}/getCoursePublishVo/${id}`,
method: 'get'
})
},
//发布课程
publishCourseById(id) {
return request({
url: `${api_name}/publishCourseById/${id}`,
method: 'put'
})
},
2.2、编写Publish.vue
<template>
<div class="app-container">
<!--课程预览-->
<div class="ccInfo">
<img :src="coursePublish.cover">
<div class="main">
<h2>{{ coursePublish.title }}</h2>
<p class="gray"><span>共{{ coursePublish.lessonNum }}课时</span></p>
<p><span>所属分类:{{ coursePublish.subjectParentTitle }} — {{ coursePublish.subjectTitle }}</span></p>
<p>课程讲师:{{ coursePublish.teacherName }}</p>
<h3 class="red">¥{{ coursePublish.price }}</h3>
</div>
</div>
<div style="text-align:center">
<el-button type="primary" @click="prev()">上一步</el-button>
<el-button :disabled="publishBtnDisabled" type="primary" @click="publish()">发布课程</el-button>
</div>
</div>
</template>
<script>
import courseApi from '@/api/vod/course'
export default {
data() {
return {
publishBtnDisabled: false, // 按钮是否禁用
coursePublish: {}
}
},
created() {
if (this.$parent.courseId) {
this.fetchCoursePublishById(this.$parent.courseId)
}
},
methods: {
// 获取课程发布信息
fetchCoursePublishById(id) {
courseApi.getCoursePublishById(id).then(response => {
this.coursePublish = response.data
})
},
// 上一步
prev() {
this.$parent.active = 1
},
// 下一步
publish() {
this.publishBtnDisabled = true
courseApi.publishCourseById(this.$parent.courseId).then(response => {
this.$parent.active = 3
this.$message.success(response.message)
this.$router.push({ path: '/vodcourse/course/list' })//路由跳转
})
}
}
}
</script>
<style scoped>
.ccInfo {
background: #f5f5f5;
padding: 20px;
overflow: hidden;
border: 1px dashed #DDD;
margin-bottom: 40px;
position: relative;
}
.ccInfo img {
background: #d6d6d6;
width: 500px;
height: 278px;
display: block;
float: left;
border: none;
}
.ccInfo .main {
margin-left: 520px;
}
.ccInfo .main h2 {
font-size: 28px;
margin-bottom: 30px;
line-height: 1;
font-weight: normal;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main p {
margin-bottom: 10px;
word-wrap: break-word;
line-height: 24px;
max-height: 48px;
overflow: hidden;
}
.ccInfo .main h3 {
left: 540px;
bottom: 20px;
line-height: 1;
font-size: 28px;
color: #d32f24;
font-weight: normal;
position: absolute;
}
</style>
知识补充:
{{}}作为html模板,用于输出对象属性和函数返回值,其中内容可以是:变量,三元表达式,函数
绑定简单的键值
fetchCoursePublishById(id) 这里通过这个方法获取对象然后得到相应的值
三、功能实现-课程删除
1、课程删除接口
一个课程下包含多个内容
(1)编写课程Controller
/**
* 删除课程
* @param id
* @return
*/
@ApiOperation(value = "删除课程")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {
courseService.removeCourseById(id);
return Result.ok(null);
}
(2)编写课程Service
//根据id删除课程
void removeCourseById(Long id);
(3)编写课程ServiceImpl
/**
* 根据课程id删除课程信息
* @param id
*/
@Override
public void removeCourseById(Long id) {
//根据课程id删除小节
videoService.removeVideoByCourseId(id);
//根据课程id删除章节
chapterService.removeChapterByCourseId(id);
//根据课程id删除描述
courseDescriptionService.removeById(id);
//根据课程id删除课程
baseMapper.deleteById(id);
}
(4)编写VideoService
//根据课程id删除小节
void removeVideoByCourseId(Long id);
(5)编写VideoServiceImpl
//根据课程id删除小节
@Override
public void removeVideoByCourseId(Long id) {
QueryWrapper<Video> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
baseMapper.delete(wrapper);
}
(6)编写ChapterService
//根据课程id删除章节
void removeChapterByCourseId(Long id);
(7)编写ChapterServiceImpl
//根据课程id删除章节
@Override
public void removeChapterByCourseId(Long id) {
QueryWrapper<Chapter> wrapper = new QueryWrapper<>();
wrapper.eq("course_id",id);
baseMapper.delete(wrapper);
}
2、课程删除前端
2.1、course.js定义接口
//删除课程
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: 'delete'
})
},
2.2、course -> list.vue添加方法
methods: {
......
// 根据id删除数据
removeById(id) {
this.$confirm('此操作将永久删除该课程,以及该课程下的章节和视频,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return courseApi.removeById(id)
}).then(response => {
this.fetchData()
this.$message.success(response.message)
}).catch((response) => { // 失败
if (response === 'cancel') {
this.$message.info('取消删除')
}
})
},
}