硅谷课堂第七天-点播管理模块(一)
一、后台管理系统-点播管理模块
1、点播管理模块需求
*添加点播课程,包含课程基本信息,课程章节,课程小结和最终发布
1.1、创建课程相关表
课程基本信息表
课程描述表
表对应关系
2、环境搭建
2.1、生成相关代码
3、功能实现-课程列表
实现分页条件查询点播课程功能
3.1、开发课程列表接口
编写CourseQueryVo
@Data
public class CourseQueryVo {
@ApiModelProperty(value = "课程讲师ID")
private Long teacherId;
@ApiModelProperty(value = "课程专业ID")
private Long subjectId;
@ApiModelProperty(value = "课程专业父级ID")
private Long subjectParentId;
@ApiModelProperty(value = "课程标题")
private String title;
}
VO(view object) 值对象
视图对象,用于展示层,当前端页面需要接收到后端传递的信息进行展示时,采用VO对象对向前端页面传输的值进行封装。主要应用场景:前端页面接收的值比POJO对象中的值多,或者需要接收的值来自各个实体类的参数,此时传递给前端页面或从前端页面接收值很难采用数据库对应的实体类进行封装,采用VO单独封装会大大提高效率。
编写CourseController
@RestController
@RequestMapping("/admin/vod/course")
@CrossOrigin
public class CourseController {
@Autowired
private CourseService courseService;
//点播课程列表条件查询带分页
// page当前页,limit每页记录数 courseQueryVo是建立了一个条件查询的vo实体类,里面是封装好的条件查询的条件
@ApiOperation("点播课程列表")
@GetMapping("{page}/{limit}")
public Result courseList(@PathVariable Long page,
@PathVariable Long limit,
CourseQueryVo courseQueryVo){
//pageParam是组装分页
Page<Course> pageParam=new Page<>(page,limit);
//不好确定要返回什么样的数据时,选择返回map类型
//所以给courseService中findPageCourse的方法的返回类型则为map类型
Map<String,Object> map=courseService.findPageCourse(pageParam,courseQueryVo);
return Result.ok(map);
}
@PathVariable知识补充:
@PathVariable 映射 URL 绑定的占位符 通过 @PathVariable 可以将 URL
中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过 @PathVariable(“xxx”)
绑定到操作方法的入参中。 一般与@RequestMapping一起使用
1、若方法参数名称和需要绑定的url中变量名称一致时,可以简写:
@RequestMapping("/getUser/{name}")
public User getUser(@PathVariable String name){
return userService.selectUser(name);
}
2、若方法参数名称和需要绑定的url中变量名称不一致时,写成:
@RequestMapping("/getUserById/{name}")
public User getUser(@PathVariable("name") String userName){
return userService.selectUser(userName);
}
编写CourseService
public interface CourseService extends IService<Course> {
//点播课程列表
Map<String, Object> findPageCourse(Page<Course> pageParam, CourseQueryVo courseQueryVo);
}
编写CourseServiceImpl
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
@Autowired
private TeacherService teacherService;
@Autowired
private SubjectService subjectService;
//点播课程列表 条件查询带分页
//因为courseController中定义返回类型为map所以这里条件查询带分页返回map类型
@Override
public Map<String, Object> findPageCourse(Page<Course> pageParam, CourseQueryVo courseQueryVo) {
//获取条件值
String title = courseQueryVo.getTitle();
Long subjectParentId = courseQueryVo.getSubjectParentId();//一层课程分类
Long subjectId = courseQueryVo.getSubjectId();//二层课程分类
Long teacherId = courseQueryVo.getTeacherId();
//判断条件为空,封装条件
QueryWrapper<Course> wrapper=new QueryWrapper<>();
if (!StringUtils.isEmpty(title)){
wrapper.like("title",title);
}
if(!StringUtils.isEmpty(subjectId)) {
wrapper.eq("subject_id",subjectId);
}
if(!StringUtils.isEmpty(subjectParentId)) {
wrapper.eq("subject_parent_id",subjectParentId);
}
if(!StringUtils.isEmpty(teacherId)) {
wrapper.eq("teacher_id",teacherId);
}
//调用方法实现条件查询分页 pageParam是分页数据(当前页,每页记录数)
//wrapper是条件,baseMapper是mybatisplus封装好的,集成了基础的增删改查
//baseMapper.selectPage分页查询方法,能得到所要查询的数据集合
// coursePage的值都有啥记得查看一下,写到文章里面(应该是Page.class里面的东西)
Page<Course> coursePage = baseMapper.selectPage(pageParam, wrapper);
//总记录数
long totalCount = coursePage.getTotal();
//总页数
long totalPage = coursePage.getPages();
//获取每页数据集合getRecords(),它是每页数据集合
List<Course> list=coursePage.getRecords();
//查询数据里面有几个id
//讲师id 课程分类id(一层和二层) 因为数据库是id值,前台要显示名称
//获取这些id对应的名称,进行封装,返回给前台显示
//stream流的运用
list.stream().forEach(item -> {
this.getNameById(item);//getNameById在本页下面定义了该方法
// ,获取这些id对应的名称,进行封装,最终显示,文章要写在一起方便一块阅读
});//我要是自己写就是在这里直接根据id获取值,然后再封装到对象里,这里是自己定义了一个方法
//将总记录数,总页数,每页数据集合封装到map中返回,注意map的键名是给前端要接受的
Map<String,Object> map=new HashMap<>();
map.put("totalCount",totalCount);
map.put("totalPage",totalPage);
map.put("records",list);
return map;
}
//获取这些id对应的名称,进行封装,最终显示
private Course getNameById(Course course) {
//根据讲师id获取讲师名称
Teacher teacher = teacherService.getById(course.getTeacherId());
if (teacher!=null){
String name=teacher.getName();
//getParam是course所继承的BaseEntity里面的属性值,下面有BaseEntity的属性
// BaseEntity.class里面有private Map<String,Object> param = new HashMap<>();
//一个map集合所以要用course.getParam()得到map集合然后再put添加进去键值对
course.getParam().put("teacherName",name);
}
//根据课程分类id获取课程分类名称,跟上面一样,课程有两级分类所以要获取两级的课程分类名称
Subject subjectOne = subjectService.getById(course.getSubjectParentId());
if(subjectOne != null) {
course.getParam().put("subjectParentTitle",subjectOne.getTitle());
}
Subject subjectTwo = subjectService.getById(course.getSubjectId());
if(subjectTwo != null) {
course.getParam().put("subjectTitle",subjectTwo.getTitle());
}
return course;
}
}
BaseEntity(map中用到了param属性)
@Data
public class BaseEntity implements Serializable {
@ApiModelProperty(value = "id")
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@JsonIgnore
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map<String,Object> param = new HashMap<>();
}
coursePage的参数有
3.2、开发课程列表前端
(1)src目录下router/index.js文件添加路由
// 课程管理
{
path: '/vodcourse',
component: Layout,
redirect: '/vod/course/list',
name: 'Vodcourse',
meta: {
title: '点播管理',
icon: 'el-icon-bank-card'
},
alwaysShow: true,
children: [
{
path: 'course/list',
name: 'CourseList',
component: () => import('@/views/vod/course/list'),
meta: { title: '课程列表',icon:'table' }
},
{
path: 'course/info',
name: 'CourseInfo',
component: () => import('@/views/vod/course/form'),
meta: { title: '发布课程' },
hidden: true
},
{
path: 'course/info/:id',
name: 'CourseInfoEdit',
component: () => import('@/views/vod/course/form'),
meta: { title: '编辑课程' },
hidden: true
},
{
path: 'course/chapter/:id',
name: 'CourseChapterEdit',
component: () => import('@/views/vod/course/form'),
meta: { title: '编辑大纲' },
hidden: true,
},
{
path: 'course/chart/:id',
name: 'CourseChart',
component: () => import('@/views/vod/course/chart'),
meta: { title: '课程统计' },
hidden: true
}
]
},
(2)创建vue页面
(3)在api目录创建course.js文件
import request from '@/utils/request'
const api_name = '/admin/vod/course'
export default {
//课程列表
getPageList(page, limit, searchObj) {
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
//使用json格式传递后台用requestbody所以是json,写法data:searchobj
//使用普通格式传递 写法 params:searchObj
params: searchObj
})
},
}
(4)在api目录teacher.js文件定义接口
import request from '@/utils/request'
const api_name='/admin/vod/teacher'//抽出来公共的用const定义然后参数获取
export default{
//查询所有讲师
list(){
return request({
url: `${api_name}/findAll`,
method: 'get'
})
},
}
(5)编写list.vue页面
<template>
<div class="app-container">
<!--查询表单-->
<el-card class="operate-container" shadow="never">
<el-form :inline="true" class="demo-form-inline">
<!-- 所属分类:级联下拉列表 -->
<!-- 一级分类 -->
<el-form-item label="课程类别">
<el-select
v-model="searchObj.subjectParentId"
placeholder="请选择"
@change="subjectLevelOneChanged"><!--点击该事件调用subjectLevelOneChanged该方法-->
<el-option
v-for="subject in subjectList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/><!--subjectLevelOneChanged(value)方法的value值从这里来-->
</el-select>
<!-- 二级分类 -->
<el-select v-model="searchObj.subjectId" placeholder="请选择">
<el-option
v-for="subject in subjectLevelTwoList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
</el-form-item>
<!-- 标题 -->
<el-form-item label="标题">
<el-input v-model="searchObj.title" placeholder="课程标题"/>
</el-form-item>
<!-- 讲师 -->
<el-form-item label="讲师">
<el-select
v-model="searchObj.teacherId"
placeholder="请选择讲师">
<el-option
v-for="teacher in teacherList"
:key="teacher.id"
:label="teacher.name"
:value="teacher.id"/>
</el-select>
</el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
</el-card>
<!-- 工具按钮 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" @click="add()">添加</el-button>
</el-card>
<!-- 表格 -->
<el-table :data="list" border stripe>
<el-table-column label="#" width="50">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="封面" width="200" align="center">
<template slot-scope="scope">
<img :src="scope.row.cover" alt="scope.row.title" width="100%">
</template>
</el-table-column>
<el-table-column label="课程信息">
<template slot-scope="scope">
<a href="">{{ scope.row.title }}</a>
<p>
分类:{{ scope.row.param.subjectParentTitle }} > {{ scope.row.param.subjectTitle }}
</p>
<p>
课时:{{ scope.row.lessonNum }} /
浏览:{{ scope.row.viewCount }} /
付费学员:{{ scope.row.buyCount }}
</p>
</template>
</el-table-column>
<el-table-column label="讲师" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.param.teacherName }}
</template>
</el-table-column>
<el-table-column label="价格(元)" width="100" align="center" >
<template slot-scope="scope">
<!-- {{ typeof '0' }} {{ typeof 0 }} {{ '0' == 0 }} -->
<!-- {{ typeof scope.row.price }}
{{ typeof Number(scope.row.price) }}
{{ typeof Number(scope.row.price).toFixed(2) }} -->
<el-tag v-if="Number(scope.row.price) === 0" type="success">免费</el-tag>
<!-- 前端解决保留两位小数的问题 -->
<!-- <el-tag v-else>{{ Number(scope.row.price).toFixed(2) }}</el-tag> -->
<!-- 后端解决保留两位小数的问题,前端不用处理 -->
<el-tag v-else>{{ scope.row.price }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="课程状态" width="100" align="center" >
<template slot-scope="scope">
<el-tag :type="scope.row.status === 0 ? 'warning' : 'success'">{{ scope.row.status === 0 ? '未发布' : '已发布' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="发布时间" width="140" align="center">
<template slot-scope="scope">
{{ scope.row.createTime ? scope.row.createTime.substr(0, 16) : '' }}
</template>
</el-table-column>
<el-table-column label="操作" width="210" align="center">
<template slot-scope="scope">
<router-link :to="'/vodcourse/course/info/'+scope.row.id">
<el-button type="text" icon="el-icon-edit" >修改</el-button>
</router-link>
<router-link :to="'/vodcourse/course/chapter/'+scope.row.id">
<el-button type="text" icon="el-icon-edit" >编辑大纲</el-button>
</router-link>
<router-link :to="'/vodcourse/course/chart/'+scope.row.id">
<el-button type="text" icon="el-icon-edit">课程统计</el-button>
</router-link>
<el-button type="text" icon="el-icon-delete" @click="removeById(scope.row.id)" >删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="total, sizes, prev, pager, next, jumper"
@size-change="changePageSize"
@current-change="changeCurrentPage"
/>
</div>
</template>
<script>
import courseApi from '@/api/vod/course'
import teacherApi from '@/api/vod/teacher'
import subjectApi from '@/api/vod/subject'
export default {
data() {
return {
list: [], // 课程列表
total: 0, // 总记录数
page: 1, // 页码
limit: 10, // 每页记录数
searchObj: {//条件查询的条件
subjectId: ''// 解决查询表单无法选中二级类别
}, // 查询条件
teacherList: [], // 讲师列表
subjectList: [], // 一级分类列表
subjectLevelTwoList: [] // 二级分类列表,
}
},
created() {
this.fetchData()//得到课程列表信息,页面渲染前(一般用于调用后台接口)
// 初始化课程分类列表
this.initSubjectList()
// 获取讲师列表
this.initTeacherList()
},
methods: {
fetchData() {
//courseApi是引用的js文件,调用里面的getPageList方法得到课程列表信息
courseApi.getPageList(this.page, this.limit, this.searchObj).then(response => {
this.list = response.data.records//records是后端提供的键名 response.data.名字为所要的数据
console.log(this.list)
this.total = response.data.totalCount
})
},
//得到所有讲师调用的是tearch.js里面的方法
initTeacherList() {
teacherApi.list().then(response => {
this.teacherList = response.data
})
},
initSubjectList() {
getChildList(0)是获取一级课程分类,用到了subject.js的方法
subjectApi.getChildList(0).then(response => {
this.subjectList = response.data
})
},
//根据值得到下一级课程分类(点击一级分类得到一级分类下的二级分类都有哪些)
//捆绑了下拉框的@change="subjectLevelOneChanged"
subjectLevelOneChanged(value) {
subjectApi.getChildList(value).then(response => {
this.subjectLevelTwoList = response.data
this.searchObj.subjectId = ''
})
},
add() {
this.$router.push({ path: '/vodcourse/course/info' })//路由地址
},
// 每页记录数改变,size:回调参数,表示当前选中的“每页条数”
changePageSize(size) {
this.limit = size
this.fetchData()
},
// 改变页码,page:回调参数,表示当前选中的“页码”
changeCurrentPage(page) {
this.page = page
this.fetchData()
},
// 重置表单
resetData() {
this.searchObj = {}
this.subjectLevelTwoList = [] // 二级分类列表
this.fetchData()
}
}
}
</script>
teacher.js
//查询所有讲师
list(){
return request({
url: `${api_name}/findAll`,
method: 'get'
})
},
tearcher的后台直接使用mybatisplus封装好的方法就能实现这里就不贴代码了
subject.js
//根据课程第一级查询第二级
getChildList(id) {
return request({
url: `${api_name}/getChildSubject/${id}`,
method: 'get'
})
}
subjectController
//查询下一层课程分类
//根据parent_id,懒加载,每次查询一层数据(懒加载是前端控制的)
@ApiOperation("查询下一层的课程分类")
@GetMapping("getChildSubject/{id}")
public Result getChildSubject(@PathVariable Long id) {
List<Subject> list = subjectService.selectSubjectList(id);
return Result.ok(list);
}
subjectSerive
//课程分类列表
List<Subject> selectSubjectList(Long id);
subjectSeriveImpl
//课程分类列表
//懒加载,每次查询一层数据
@Override
public List<Subject> selectSubjectList(Long id) {
QueryWrapper<Subject> wrapper=new QueryWrapper<>();
wrapper.eq("parent_id",id);
List<Subject> subjectList=baseMapper.selectList(wrapper);
//subjectList遍历,得到每个subject对象,判断是否有下一层数据,有设置hasChildren=true,hasChildren为实体类里面的数据库不存在的字段,前台根据该字段判断是否显示下一层
for (Subject subject:subjectList) {
Long subjectId = subject.getId();
//查询是否由下一层数据
boolean isChild = this.isChildren(subjectId);//this调用当前类对象
//封装到对象里面将hasChildren=true/false
subject.setHasChildren(isChild);
}
return subjectList;
}
二、发布课程-填写课程基本信息
1、界面效果
2、添加课程基本信息接口
2.1、创建课程描述的service和mapper
无非就是把前端传来的CourseVo拆分成Course和CourseDescription这两个对象,分别传入对应的表,还是比较好做的
2.2、创建添加课程基本信息接口
(1)controller
/**
* 添加课程基本信息 CourseForm是自己写的包装类,专门用于添加所需的实体属性
*/
@ApiOperation("添加课程基本信息")
@PostMapping("save")
public Result save(@RequestBody CourseFormVo courseFormVo){
Long courseId=courseService.saveCourseInfo(courseFormVo);
return Result.ok(null);
}
(2)CourseFormVo实体类
@ApiModel("课程基本信息")
@Data
public class CourseFormVo {
@ApiModelProperty(value = "课程ID")
private Long id;
@ApiModelProperty(value = "课程讲师ID")
private Long teacherId;
@ApiModelProperty(value = "课程专业ID")
private Long subjectId;
@ApiModelProperty(value = "课程专业父级ID")
private Long subjectParentId;
@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;
}
(3)service
public interface CourseService extends IService<Course> {
//点播课程列表查询
Map<String, Object> findPageCourse(Page<Course> pageParam, CourseQueryVo courseQueryVo);
//添加课程基本信息
Long saveCourseInfo(CourseFormVo courseFormVo);
}
(4)serviceImpl
//添加课程基本信息
@Override
public Long saveCourseInfo(CourseFormVo courseFormVo) {
//添加课程基本信息,操作course表
Course course=new Course();
//BeanUtils工具类
//BeanUtils.copyProperties(courseFormVo,course);将courseFormVo赋给course值
// 或者使用get,set赋值也可以
//前端传来的是CourseFormVo对象所要要赋给course
BeanUtils.copyProperties(courseFormVo,course);
//添加时还是用的course实体类
baseMapper.insert(course);//或者注入courseService用save方法
//添加课程描述信息,操作course_description表
CourseDescription courseDescription=new CourseDescription();
//设置课程id通过course得到id赋给课程描述信息表里的主键
courseDescription.setId(course.getId());//或者用表里的course_id做一对一关系也行,这里用的是主键
courseDescriptionService.save(courseDescription);
return null;
}
3、添加课程基本信息前端
3.1、课程列表list.vue添加方法
<!-- 工具按钮 -->
<el-card class="operate-container" shadow="never">
<i class="el-icon-tickets" style="margin-top: 5px"></i>
<span style="margin-top: 5px">数据列表</span>
<el-button class="btn-add" @click="add()">添加</el-button>
</el-card>
add() {
this.$router.push({ path: '/vodcourse/course/info' })//路由地址
},
3.2、course.js定义接口
//添加课程基本信息
saveCourseInfo(courseInfo) {
return request({
url: `${api_name}/save`,
method: 'post',
data: courseInfo//json格式因为后台save方法中参数用@requestBody接受的
})
},
3.3、创建课程基本信息添加页面
(1)form.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程</h2>
<el-steps :active="active" finish-status="success" simple style="margin-bottom: 40px">
<el-step title="填写课程基本信息" />
<el-step title="创建课程大纲" />
<el-step title="发布课程" />
</el-steps>
<!-- 填写课程基本信息v-if进行判断0为课程基本信息 -->
<info v-if="active === 0" />
<!-- 创建课程大纲 -->
<chapter v-if="active === 1" />
<!-- 发布课程 -->
<Publish v-if="active === 2 || active === 3" />
</div>
</template>
<script>
// 引入子组件
import Info from '@/views/vod/course/components/Info'
import Chapter from '@/views/vod/course/components/Chapter'
import Publish from '@/views/vod/course/components/Publish'
export default {
components: { Info, Chapter, Publish }, // 注册子组件
data() {
return {
active: 0,
courseId: null
}
},
created() {
// 获取路由id来判断添加的是课程还是章节
if (this.$route.params.id) {
this.courseId = this.$route.params.id
}
if (this.$route.name === 'CourseInfoEdit') {
this.active = 0
}
if (this.$route.name === 'CourseChapterEdit') {
this.active = 1
}
}
}
</script>
知识补充:
== 用于比较两者是否相等,忽略数据类型。
=== 用于更严谨的比较,值和值的数据类型都需要同时比较
(2)Info.vue
<template>
<div class="app-container">
<!-- 课程信息表单 -->
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 课程讲师 -->
<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-select
v-model="courseInfo.subjectParentId"
placeholder="请选择"
@change="subjectChanged">
<el-option
v-for="subject in subjectList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="请选择">
<el-option
v-for="subject in subjectLevelTwoList"
:key="subject.id"
:label="subject.title"
:value="subject.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="课程简介">
<el-input v-model="courseInfo.description" type="textarea" rows="5"/>
</el-form-item>
<!-- 课程封面 -->
<el-form-item label="课程封面">
<el-upload
:show-file-list="false"
:on-success="handleCoverSuccess"
:before-upload="beforeCoverUpload"
:on-error="handleCoverError"
:action="BASE_API+'/admin/vod/file/upload?module=cover'"
class="cover-uploader">
<img v-if="courseInfo.cover" :src="courseInfo.cover">
<i v-else class="el-icon-plus avatar-uploader-icon"/>
</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>
<div style="text-align:center">
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveAndNext()">保存并下一步</el-button>
</div>
</div>
</template>
<script>
import courseApi from '@/api/vod/course'
import teacherApi from '@/api/vod/teacher'
import subjectApi from '@/api/vod/subject'
export default {
data() {
return {
BASE_API: 'http://localhost:8301',
saveBtnDisabled: false, // 按钮是否禁用
courseInfo: {// 表单数据
price: 0,
lessonNum: 0,
// 以下解决表单数据不全时insert语句非空校验
teacherId: '',
subjectId: '',
subjectParentId: '',
cover: '',
description: ''
},
teacherList: [], // 讲师列表
subjectList: [], // 一级分类列表
subjectLevelTwoList: []// 二级分类列表
}
},
created() {
if (this.$parent.courseId) { // 数据回显(应该是上一步的实现)
this.fetchCourseInfoById(this.$parent.courseId)
} else { // 新增
// 初始化分类列表
this.initSubjectList()
}
// 初始化分类列表
this.initSubjectList()
// 获取讲师列表
this.initTeacherList()
},
methods: {
// 获取讲师列表
initTeacherList() {
teacherApi.list().then(response => {
this.teacherList = response.data
})
},
// 初始化分类列表
initSubjectList() {
subjectApi.getChildList(0).then(response => {
this.subjectList = response.data
})
},
// 选择一级分类,切换二级分类
subjectChanged(value) {
subjectApi.getChildList(value).then(response => {
this.courseInfo.subjectId = ''
this.subjectLevelTwoList = response.data
})
},
// 上传成功回调
handleCoverSuccess(res, file) {
this.courseInfo.cover = res.data
},
// 上传校验
beforeCoverUpload(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
},
// 错误处理
handleCoverError() {
console.log('error')
this.$message.error('上传失败2')
},
// 获取课程信息
fetchCourseInfoById(id) {
courseApi.getCourseInfoById(id).then(response => {
this.courseInfo = response.data
// 初始化分类列表getChildList(0)在subject获取课程父分类
subjectApi.getChildList(0).then(response => {
this.subjectList = response.data
// 填充二级菜单:遍历一级菜单列表,
this.subjectList.forEach(subject => {
// 找到和courseInfo.subjectParentId一致的父类别记录
if (subject.id === this.courseInfo.subjectParentId) {
// 拿到当前类别下的子类别列表,将子类别列表填入二级下拉菜单列表
subjectApi.getChildList(subject.id).then(response => {
this.subjectLevelTwoList = response.data
})
}
})
})
})
},
// 保存并下一步
saveAndNext() {
this.saveBtnDisabled = true
if (!this.$parent.courseId) {
this.saveData()
} else {
this.updateData()
}
},
// 保存
saveData() {
courseApi.saveCourseInfo(this.courseInfo).then(response => {
this.$message.success(response.message)
this.$parent.courseId = response.data // 获取courseId
this.$parent.active = 1 // 下一步,改进行章节的添加
})
},
// 修改
updateData() {
courseApi.updateCourseInfoById(this.courseInfo).then(response => {
this.$message.success(response.message)
this.$parent.courseId = response.data // 获取courseId
this.$parent.active = 1 // 下一步
})
},
}
}
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.cover-uploader .avatar-uploader-icon {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
font-size: 28px;
color: #8c939d;
width: 640px;
height: 357px;
line-height: 357px;
text-align: center;
}
.cover-uploader .avatar-uploader-icon:hover {
border-color: #409EFF;
}
.cover-uploader img {
width: 640px;
height: 357px;
display: block;
}
</style>
三、发布课程-修改课程基本信息
1、修改课程基本信息接口
一看就是个更新信息,按ID查询回显+update操作
按ID查询的URL
注意,这个getById返回的是Vo,因为要带着课程描述一起更改,所以返回的是CourseFormVo
1.1、CourseService定义方法
//根据id获取课程信息
CourseFormVo getCourseInfoById(Long id);
//修改课程信息
void updateCourseId(CourseFormVo courseFormVo);
1.2、CourseServiceImpl实现方法
//根据id查询课程信息
@Override
public CourseFormVo getCourseInfoById(Long id) {
//课程基本信息
Course course=baseMapper.selectById(id);
//一定要判空操作啊
if (course==null){
return null;
}
//课程描述信息
CourseDescription courseDescription=courseDescriptionService.getById(id);
//封装
CourseFormVo courseFormVo=new CourseFormVo();
BeanUtils.copyProperties(course,courseFormVo);
//封装描述
if(courseDescription!=null){
courseFormVo.setDescription(courseDescription.getDescription());
}
return courseFormVo;
}
//修改课程信息(修改俩表一个基本信息表一个详情信息)
@Override
public void updateCourseId(CourseFormVo courseFormVo) {
//修改课程基本信息
Course course = new Course();
BeanUtils.copyProperties(courseFormVo, course);
baseMapper.updateById(course);
//修改课程详情信息
CourseDescription courseDescription = courseDescriptionService.getById(course.getId());
courseDescription.setDescription(courseFormVo.getDescription());
courseDescription.setId(course.getId());
courseDescriptionService.updateById(courseDescription);
}
1.3、CourseController实现方法
/**
* 根据id获取课程信息
*/
@GetMapping("get/{id}")
public Result get(@PathVariable Long id){
//因为涉及要查课程基本信息也要查课程详情信息所以自己定义一个方法
//返回CourseFormVo对象因为有所有需要的对象属性
CourseFormVo courseFormVo=courseService.getCourseInfoById(id);
return Result.ok(courseFormVo);
}
/**
* 修改课程信息
*/
@PostMapping("update")
public Result update(@RequestBody CourseFormVo courseFormVo){
courseService.updateCourseId(courseFormVo);
return Result.ok(null);
}
2、修改课程基本信息前端
2.1、course.js定义方法
//id获取课程信息
getCourseInfoById(id) {
return request({
url: `${api_name}/get/${id}`,
method: 'get'
})
},
//修改课程信息
updateCourseInfoById(courseInfo) {
return request({
url: `${api_name}/update`,
method: 'post',
data: courseInfo
})
},
2.2、修改Info.vue页面
<script>
....
created() {
if (this.$parent.courseId) { // 数据回显(应该是上一步的实现)
this.fetchCourseInfoById(this.$parent.courseId)
} else { // 新增
// 初始化分类列表
this.initSubjectList()
}
// 初始化分类列表
this.initSubjectList()
// 获取讲师列表
this.initTeacherList()
},
methods: {
......
// 获取课程信息
fetchCourseInfoById(id) {
courseApi.getCourseInfoById(id).then(response => {
this.courseInfo = response.data
// 初始化分类列表getChildList(0)在subject获取课程父分类
subjectApi.getChildList(0).then(response => {
this.subjectList = response.data
// 填充二级菜单:遍历一级菜单列表,
this.subjectList.forEach(subject => {
// 找到和courseInfo.subjectParentId一致的父类别记录
if (subject.id === this.courseInfo.subjectParentId) {
// 拿到当前类别下的子类别列表,将子类别列表填入二级下拉菜单列表
subjectApi.getChildList(subject.id).then(response => {
this.subjectLevelTwoList = response.data
})
}
})
})
})
},
// 保存并下一步
saveAndNext() {
this.saveBtnDisabled = true
if (!this.$parent.courseId) {
this.saveData()
} else {
this.updateData()
}
},
// 修改
updateData() {
courseApi.updateCourseInfoById(this.courseInfo).then(response => {
this.$message.success(response.message)
this.$parent.courseId = response.data // 获取courseId
this.$parent.active = 1 // 下一步
})
},
......
}
}
</script>
2.3、创建Chapter-index.vue页面
<template>
<div class="app-container">
<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>
export default {
data() {
return {
}
},
created() {
},
methods: {
// 上一步
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>
三、java8新特性学习
链接:java8