课程相关表关系
01-课程发布表单-步骤导航
一、需求
二、配置路由
1、添加路由
{
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: 'table' }
},
{
path: 'info',
name: 'EduCourseInfo',
component: () => import('@/views/edu/course/info'),
meta: { title: '发布课程' ,icon: 'tree'}
},
{
path: 'info/:id',
name: 'EduCourseInfoEdit',
component: () => import('@/views/edu/course/info'),
meta: { title: '编辑课程基本信息', noCache: true },
hidden: true
},
{
path: 'chapter/:id',
name: '编辑课程大纲',
component: () => import('@/views/edu/course/chapter'),
meta: { title: '编辑课程大纲', icon: 'tree' },
hidden: true
},
{
path: 'publish/:id',
name: 'EduTeacherEdit',
component: () => import('@/views/edu/course/publish'),
meta: { title: '发布课程', noCache:true },
hidden:true
}
]
},
2、添加vue组件
三、整合步骤条组件
参考 http://element-cn.eleme.io/#/zh-CN/component/steps
1、课程信息页面
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>
<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:{
next(){
//跳转到第二步
this.$router.push({path:'/course/chapter/1'})
}
}
}
</script>
2、课程大纲页面
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-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>
3、课程发布页面
publish.vue
<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(){
},
methods:{
previous(){
this.$router.push({path:'/course/chapter/1'})
},
publish(){
//跳转到第二步
this.$router.push({path:'/course/list'})
}
}
}
</script>
02-编辑课程基本信息
一、后台api
1、定义form表单对象
CourseInfoVo.java
@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo implements Serializable {
private static final long serialVersionUID = 1L;
@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则可免费观看")
//0.01
private BigDecimal price;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "课程简介")
private String description;
}
2、修改CourseDescription主键生成策略
由于课程表和课程简介表是一对一的关系,所以简介表的id应该与课程表的id一致。
3、定义控制层接口
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService courseService;
//添加课程基本信息的方法
@ApiOperation(value = "新增课程")
@PostMapping("addCourseInfo")
public R addCourseInfo( @ApiParam(name = "CourseInfoForm", value = "课程基本信息", required = true)
@RequestBody CourseInfoVo courseInfoVo){
courseService.saveCourseInfo(courseInfoVo);
return R.ok();
}
}
4、定义业务层方法
接口:EduCourseService.java
public interface EduCourseService extends IService<EduCourse> {
//添加课程基本信息的方法
void saveCourseInfo(CourseInfoVo courseInfoVo);
}
实现:EduCourseServiceImpl.java
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
private EduCourseDescriptionService courseDescriptionService;
@Override
public void saveCourseInfo(CourseInfoVo courseInfoVo) {
//1 向课程表添加课程基本信息
//将CourseInfoVo对象转换为eduCourse对象
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
int insert = baseMapper.insert(eduCourse);
if (insert == 0){
//添加失败
throw new GuliException(20001,"添加课程信息失败");
}
//获取添加之后的课程id
String cid = eduCourse.getId();
//2 向课程简介表添加课程简介
//EduCourseDescription
EduCourseDescription courseDescription = new EduCourseDescription();
courseDescription.setDescription(courseInfoVo.getDescription());
//设置描述id就是课程id
courseDescription.setId(cid);
courseDescriptionService.save(courseDescription);
}
}
6、Swagger测试
二、前端实现
1、定义api
import request from '@/utils/request'
export default{
//1 添加课程信息
addCourseInfo(courseInfo){
return request({
url:`/eduservice/course/addCourseInfo`,
method:'post',
data:courseInfo
})
}
}
2、组件模板
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
</el-form-item>
<!-- 所属分类 TODO -->
<!-- 课程讲师 TODO -->
<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-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>
3、组件js
<script>
import course from '@/api/edu/course'
export default {
data(){
return {
courseInfo:{
title: '',
subjectId: '',
teacherId: '',
lessonNum: 0,
description: '',
cover: '',
price: 0,
},
saveBtnDisabled: false // 保存按钮是否禁用
}
},
created(){
},
methods:{
saveOrUpdate(){
course.addCourseInfo(this.courseInfo)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '添加课程信息成功!'
});
//跳转到第二步
this.$router.push({path:'/course/chapter/'+response.data.courseId})
})
},
}
}
</script>
03-课程分类多级联动的实现
一、需求
二、获取一级分类
1、组件数据定义
定义在data中
subjectOneList:[],//一级分类
subjectTwoList:[],//二级分类
2、组件模板
<!-- 所属分类 TODO -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类">
<el-option
v-for="subject in subjectOneList"
:key="subject.id"
:label="subject.title"
:value="subject.id"/>
</el-select>
</el-form-item>
3、组件脚本
表单初始化时获取一级分类嵌套列表,引入subject api
import subject from '@/api/edu/subject'
定义方法
//获取一级分类
getOneSubjectList(){
subject.getSubjectList()
.then(response=>{
this.subjectOneList = response.data.list
})
},
三、级联显示二级分类
1、组件模板
<!-- 二级分类 -->
<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>
2、注册change事件
在一级分类的<el-select>组件中注册change事件
<!-- 所属分类 TODO -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类" @change="subjectLevelOneChanged">
.......
</el-select>
3、定义change事件方法
//点击某一个一级分类,触发change事件,显示对应二级分类
subjectLevelOneChanged(value){
//value就是一级分类id值
//遍历所有的分类,包含一级和二级
for(var i =0; i<this.subjectOneList.length;i++){
//每一个一级分类
var oneSubject = this.subjectOneList[i]
//判断:所有一级分类id和点击一级分类id是否一样
if(oneSubject.id === value){
//从一级分类获取里面所有的二级分类
this.subjectTwoList = oneSubject.children
//把二级分类id值清空
this.courseInfo.subjectId = ''
}
}
},
04-讲师下拉列表
一、前端实现
1、组件模板
<!-- 课程讲师 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>
2、定义api
api/edu/course.js
//2 查询所有讲师
getListTeacher(){
return request({
url:`/eduservice/teacher/findAll`,
method:'get'
})
}
组件中引入course api
import course from '@/api/edu/course'
3、组件脚本
定义data
teacherList:[],//讲师列表
获取讲师列表:
//查询所有讲师
getListTeacher(){
course.getListTeacher()
.then(response => {
this.teacherList = response.data.items
})
}
created(){
//初始化所有讲师
this.getListTeacher()
},
05-富文本编辑器Tinymce
一、Tinymce可视化编辑器
参考
https://panjiachen.gitee.io/vue-element-admin/#/components/tinymce
https://panjiachen.gitee.io/vue-element-admin/#/example/create
二、组件初始化
Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤
1、复制脚本库
将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)
2、配置html变量
在 guli-admin/build/webpack.dev.conf.js 中添加配置
使在html页面中可是使用这里定义的BASE_URL变量
3、引入js脚本
在guli-admin/index.html 中引入js脚本
<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
三、组件引入
为了让Tinymce能用于Vue.js项目,vue-element-admin-master对Tinymce进行了封装,下面我们将它引入到我们的课程信息页面
1、复制组件
src/components/Tinymce
2、引入组件
课程信息组件中引入 Tinymce
import Tinymce from '@/components/Tinymce' //引用
export default {
components: { Tinymce }, //声明
.......
}
3、组件模板
<!-- 课程简介-->
<el-form-item label="课程简介">
<tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>
4、组件样式
在info.vue文件的最后添加如下代码,调整上传图片按钮的高度
<style scoped>/* scoped表示只在当前页面有效 */
.tinymce-container {
line-height: 29px;
}
</style>
5、图片的base64编码
Tinymce中的图片上传功能直接存储的是图片的base64编码,因此无需图片服务器
测试:
添加一级分类id:
06-课程封面
一、整合上传组件
参考 http://element-cn.eleme.io/#/zh-CN/component/upload用户头像上传
1、定义data数据
BASE_API: process.env.BASE_API, // 接口API地址
2、组件模板
在info.vue中添加上传组件模板
<!-- 课程封面-->
<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>
5、结果回调
//上传封面成功
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
},
07-课程信息回显
一、后端实现
1、业务层
接口:CourseService.java
//根据ID查询课程
CourseInfoVo getCourseInfoFormById(String courseId);
实现:CourseServiceImpl.java
@Override
public CourseInfoVo getCourseInfoFormById(String courseId) {
//1 查询课程表
EduCourse eduCourse = baseMapper.selectById(courseId);
// EduCourse eduCourse = this.getById(courseId);
if (eduCourse == null){
throw new GuliException(20001, "数据不存在");
}
//把eduCourse对象中的值复制到courseInfoVo中
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(eduCourse,courseInfoVo);
//2 查询描述表
EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
if (eduCourse !=null){
//讲描述信息放入courseInfoVo
courseInfoVo.setDescription(courseDescription.getDescription());
}
return courseInfoVo;
}
2、web层
@ApiOperation(value = "根据ID查询课程")
@GetMapping("getCourseInfo/{courseId}")
public R getCourseInfo(@ApiParam(name = "id", value = "课程ID", required = true)
@PathVariable String courseId){
CourseInfoVo courseInfoVo = courseService.getCourseInfoFormById(courseId);
return R.ok().data("courseInfoVo",courseInfoVo);
}
3、Swagger中测试
二、前端实现
1、定义api
api/edu/course.js
//3 根据id获取课程基本信息
getCourseInfoById(id){
return request({
url:`/eduservice/course/getCourseInfo/${id}`,
method:'get'
})
},
2、组件js
data中声明:
courseId:'',
created()
//获取路由中的路由id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
//调用
this.getInfo()
}
方法:
//根据id获取课程信息
getInfo(){
course.getCourseInfoById(this.courseId)
.then(response => {
this.courseInfo = response.data.courseInfoVo
})
},
三、解决级联下拉菜单回显问题
1、数据库中增加冗余列
subject_parent_id课程专业父级ID
2、pojo中增加属性
entity.Course.java
form.CourseInfo.java
@ApiModelProperty(value = "一级分类ID")
private String subjectParentId;
3、vue组件中绑定数据
edu/course/info.vue
4、修改init方法
将 this.initSubjectList() 和 this.initTeacherList()移至else
//init方法
init(){
//获取路由中的路由id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
//调用
this.getInfo()
}else{
//清空表单
this.courseInfo = {...defaultForm}
//初始化所有讲师
this.getListTeacher()
//初始化一级分类
this.getOneSubjectList()
}
},
5、修改fetchCourseInfoById方法
解决在返回上一步的二级表单的问题。
//根据id获取课程信息
getInfo(){
course.getCourseInfoById(this.courseId)
.then(response => {
this.courseInfo = response.data.courseInfoVo
//1 查询所有的分类,包含一级和二级
subject.getSubjectList()
.then(response=>{
//2 获取所有一级分类
this.subjectOneList = response.data.list
//3 把所有的一级分类数组进行遍历
for(var i=0;i<this.subjectOneList.length; i++){
//获取每一个一级分类
var subjectOne = this.subjectOneList[i]
//比较当前courseInfo里面的一级分类id和所有的一级分类id
if(this.courseInfo.subjectParentId == subjectOne.id){
//获取一级分类里面的所有二级分类
this.subjectTwoList = subjectOne.children
}
}
})
//初始化所有讲师
this.getListTeacher()
})
},
刷新发布课程页面:
const defaultForm = {
title: '',
subjectId: '',//二级分类id
subjectParentId:'',//一级分类id
teacherId: '',
lessonNum: 0,
description: '',
cover: '/static/11.jpg',
price: 0
}
created(){
this.init()
},
//init方法
init(){
//获取路由中的路由id值
if(this.$route.params && this.$route.params.id){
this.courseId = this.$route.params.id
//调用
this.getInfo()
}else{
//清空表单
this.courseInfo = {...defaultForm}
//初始化所有讲师
this.getListTeacher()
//初始化一级分类
this.getOneSubjectList()
}
},
//监听
watch:{
$route(to,from){ //路由变化方式,路由发生变化,方法就会执行
this.init()
}
},
08-更新课程信息
一、后端实现
1、业务层
接口:EduCourseService.java
//更新课程信息
void updateCourseInfoById(CourseInfoVo courseInfoVo);
实现:EduCourseServiceImpl.java
//更新课程信息
@Override
public void updateCourseInfoById(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 courseDescription = new EduCourseDescription();
courseDescription.setId(courseInfoVo.getId());
courseDescription.setDescription(courseInfoVo.getDescription());
courseDescriptionService.updateById(courseDescription);
}
2、web层
//更新课程信息
@ApiOperation(value = "更新课程")
@PostMapping("updateCourseInfo")
public R updateCourseInfo( @ApiParam(name = "CourseInfoForm", value = "课程基本信息", required = true)
@RequestBody CourseInfoVo courseInfoVo){
courseService.updateCourseInfoById(courseInfoVo);
return R.ok();
}
二、前端实现
1、定义api
course.js
//4 修改课程基本信息
updateCourseInfoById(courseInfo){
return request({
url:`/eduservice/course/updateCourseInfo`,
method:'post',
data:courseInfo
})
}
2、组件js
info.vue
updateCourse(){
course.updateCourseInfoById(this.courseInfo)
.then(response=>{
//提示
this.$message({
type: 'success',
message: '修改课程信息成功!'
});
//跳转到第二步
this.$router.push({path:'/course/chapter/'+this.courseId})
})
},
saveOrUpdate(){
if(!this.courseInfo.id){
//添加
this.addCourse()
}else{
//修改
this.updateCourse()
}
},