文章目录
一、课程分类模块
1、修改路由(前端)
/* 课程分类模块*/
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'example' },
children: [
{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/subject/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'save',
name: '添加课程分类',
component: () => import('@/views/subject/save'),
meta: { title: '添加课程分类', icon: 'tree' }
},
]
},
2、设计使用Excel表格添加分类课程页面(前端)
1、添加上传文件组件
2、编写页面
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="信息描述">
<el-tag type="info">excel模版说明</el-tag>
<el-tag>
<i class="el-icon-download"/>
<a :href="OSS_PATH + '/excel/%E8%AF%BE%E7%A8%8B%E5%88%86%E7%B1%BB%E5%88%97%E8%A1%A8%E6%A8%A1%E6%9D%BF.xls'">点击下载模版</a>
</el-tag>
</el-form-item>
<el-form-item label="选择Excel">
<el-upload
ref="upload"
:auto-upload="false"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:disabled="importBtnDisabled"
:limit="1"
:action="BASE_API+'/eduservice/edu-subject/insertExcel'"
name="file"
accept="application/vnd.ms-excel">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button
:loading="loading"
style="margin-left: 10px;"
size="small"
type="success"
@click="submitUpload">{{ fileUploadBtnText }}</el-button>
</el-upload>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default{
data(){
return{
BASE_API: process.env.VUE_APP_BASE_API, // 接口API地址
// OSS_PATH: process.env.OSS_PATH, // 阿里云OSS地址
fileUploadBtnText: '上传到服务器', // 按钮文字
importBtnDisabled: false, // 按钮是否禁用,
loading: false,
}
},
created() {
},
methods: {
submitUpload() {
this.fileUploadBtnText = '正在上传'
this.importBtnDisabled = true
this.loading = true
this.$refs.upload.submit()
},
/* 上传成功*/
fileUploadSuccess(response) {
/* 提示*/
this.fileUploadBtnText = '导入成功'
this.loading = false
this.$message({
type: 'success',
message: response.message
})
/* 跳转到课程分类列表*/
this.$router.push({name:'课程分类列表'})
},
/* 上传失败*/
fileUploadError(response) {
this.fileUploadBtnText = '导入失败'
this.loading = false
this.$message({
type: 'error',
message: '导入失败'
})
}
},
}
</script>
<style>
</style>
3、设计课程分类展示(后端)
先确定前端需要的数据格式
我们的课程分两级,所以为了方便和效率
我们建立两个实体类,一级分类一个,二级分类一个
然后根据两个实体类查询
1、编写两个实体类,分别代表两个级别
package com.yzpnb.eduservice.entity.subject;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 一级分类
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OneSubject {
@ApiModelProperty("一级分类id")
private String id;
@ApiModelProperty("一级课程名")
private String title;
@ApiModelProperty("多个二级课程包含再一个一级分类")
private List<TwoSubject> children=new ArrayList<>();
}
package com.yzpnb.eduservice.entity.subject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 二级分类
*/
@Data
public class TwoSubject {
@ApiModelProperty("二级分类id")
private String id;
@ApiModelProperty("二级课程名")
private String title;
}
2、编写Controller接口
/**
* 查询
*/
//1、查询课程,根据id和parend_id决定树形结构
@GetMapping("selectTree")
@ApiOperation("查询树形结构课程目录")
public Result selectTree(){
List<OneSubject> list=eduSubjectService.selectAllOneSubject();
return Result.ok().data("allSubject",list);
}
3、service接口实体类
/**查询所有课程,返回树形结构*/
@Override
public List<OneSubject> selectAllOneSubject() {
//1、查询所有一级分类 parent_id 等于0
QueryWrapper<EduSubject> queryOneWrapper=new QueryWrapper<>();
queryOneWrapper.eq("parent_id","0");
List<EduSubject> oneSubjects = baseMapper.selectList(queryOneWrapper);//baseMapper 是我们的Mapper继承的接口封装好的对象
//2、查询所有二级分类 parent_id 不等于0
QueryWrapper<EduSubject> queryTwoWrapper=new QueryWrapper<>();
queryTwoWrapper.ne("parent_id","0");
List<EduSubject> twoSubjects = baseMapper.selectList(queryTwoWrapper);//baseMapper 是我们的Mapper继承的接口封装好的对象
//3、封装一级分类
List<OneSubject> list=new ArrayList<>();//list用来保存最终返回结果集
for (EduSubject eduOneSubject:oneSubjects) {//遍历所有一级分类
/**1、创建一级分类对象,将所有一级分类集合中的每个对象的数据,赋值给一级分类对象*/
OneSubject oneSubject=new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
BeanUtils.copyProperties(eduOneSubject,oneSubject);//这个是spring boot提供的工具类中的方法,封装了我上面注释的两条代码的实现逻辑
/**2、遍历二级分类*/
for (EduSubject eduTwoSubject:twoSubjects) {
TwoSubject twoSubject=new TwoSubject();
BeanUtils.copyProperties(eduTwoSubject,twoSubject);//将当前遍历对象的相应属性封装到二级分类对象中
/**如果二级分类的parent_id等于当前一级分类的id就添加到此一级分类*/
if(eduTwoSubject.getParentId().equals(eduOneSubject.getId())){//使用equals比较,因为==号比较的是内存地址,而我们要比的仅仅是字面值
oneSubject.getChildren().add(twoSubject);//将这个二级分类添加到当前一级目录的集合中
}
}
/**3、将每个一级分类对象添加到集合中*/
list.add(oneSubject);
}
return list;//将集合返回
}
4、测试接口
4、课程分类展示(前端)
1、编写API接口
2、修改list页面
二、课程管理模块(添加课程)
需要用到的表
1、(后端)编写添加课程信息接口(涉及两张表,一次性添加课程基本信息和课程简介信息)
1、使用代码生成器直接生成java对应数据库中各个表的MVC结构
2、实体类
实体类:需要单独创建一个实体类用于保存用户提交的表单,因为,它有两张表的数据,其它两张表对应的实体类需要完善
package com.yzpnb.eduservice.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 课程基本信息实体类
* */
@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo {
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 = "课程专业父ID")
private String subjectParentId;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
private BigDecimal price;//这里使用BigDecimal是math类提供的比float和double精度更准确的一个类型,因为我们的价格是.00的形式
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "课程简介")
private String description;
}
3、controller接口
package com.yzpnb.eduservice.controller;
import com.yzpnb.common_utils.Result;
import com.yzpnb.eduservice.entity.vo.CourseInfoVo;
import com.yzpnb.eduservice.service.EduCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 课程 前端控制器
* </p>
*
* @author testjava
* @since 2020-05-24
* 课程基本信息
*/
@RestController
@RequestMapping("/eduservice/edu-course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService eduCourseService;
/**
* 添加
*/
//1、添加课程基本信息
@PostMapping("insertCourseInfo")
public Result insertCourseInfo(@RequestBody CourseInfoVo courseInfoVo)//将用户提交的表单保存到此对象中
{
//调用接口
String id=eduCourseService.insertCourseInfo(courseInfoVo);
return Result.ok().data("courseId",id);//将添加完成后的id值返回
}
}
4、service接口实现类
package com.yzpnb.eduservice.service.impl;
import com.yzpnb.eduservice.entity.EduCourse;
import com.yzpnb.eduservice.entity.EduCourseDescription;
import com.yzpnb.eduservice.entity.vo.CourseInfoVo;
import com.yzpnb.eduservice.mapper.EduCourseMapper;
import com.yzpnb.eduservice.service.EduCourseDescriptionService;
import com.yzpnb.eduservice.service.EduCourseService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* 课程 服务实现类
* </p>
*
* @author testjava
* @since 2020-05-24
*/
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
EduCourseDescriptionService eduCourseDescriptionService;//注入课程简介信息的添加接口,我们添加课程简介的时候需要使用
/**
*添加课程基本信息
* @return
*/
@Override
public String insertCourseInfo(CourseInfoVo courseInfoVo) {
/**1、课程表中添加课程基本信息*/
//1.1将courseInfoVo中保存的所有信息中挑出课程基本信息添加到基本信息表
EduCourse eduCourse=new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
//1.2调用baseMapper中添加方法将课程添加
int index=baseMapper.insert(eduCourse);
//1.3判断是否添加成功
if(index<=0){
throw new CustomExceptionHandler(20001,"添加课程信息失败");//抛出我们的自定义异常
}
/**2、课程简介表中添加课程简介*/
//2.1将courseInfoVo中保存的信息中挑出简介信息添加到简介表中
EduCourseDescription eduCourseDescription=new EduCourseDescription();
//BeanUtils.copyProperties(eduCourseDescription,courseInfoVo);与下面的这行代码效果相同,但会自动生成id所以依然需要为其人为设置id
eduCourseDescription.setDescription(courseInfoVo.getDescription());
//**这两个表因为是1对1的关系,id值应该相同,将自动为Course表生成的id直接赋值给简介表**
eduCourseDescription.setId(eduCourse.getId());
//2.2调用接口添加信息
eduCourseDescriptionService.save(eduCourseDescription);
return eduCourse.getId();//返回id值
}
}
5、测试
2、(前端)课程管理模块
1、设计路由
/* 课程管理模块*/
{
path: '/course',
component: Layout,
redirect: '/course/list',
name: '课程管理模块',
meta: { title: '课程管理模块', icon: 'nested' },
children: [
{
path: 'list',
name: '课程展示',
component: () => import('@/views/course/list'),
meta: { title: '课程展示', icon: 'table' }
},
{
path: 'save1',
name: '课程添加',
component: () => import('@/views/course/save1'),
meta: { title: '课程添加', icon: 'tree' },
},
{
path: 'save1/:id',
name: '课程添加1',
component: () => import('@/views/course/save1'),
meta: { title: '编辑课程基本信息', icon: 'tree' },
hidden:true
},
{
path: 'save2/:id',
name: '课程添加2',
component: () => import('@/views/course/save2'),
meta: { title: '编辑课程大纲', icon: 'tree' },
hidden:true
},
{
path: 'save3/:id',
name: '课程添加3',
component: () => import('@/views/course/save3'),
meta: { title: '发布课程', icon: 'tree' },
hidden:true
},
2、编写对应api接口
import request from '@/utils/request'//引入request.js,它封装了axios请求处理
export default{
/*保存课程信息 */
saveCourseInfo(courseInfo) {
return request({
url: `/eduservice/edu-course/insertCourseInfo`,
method: 'post',
data:courseInfo
})
}
}
3、编写save1页面
<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>
<!-- 内容-->
<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="next">保存并下一步</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import eduCourse from '@/api/course/eduCourse.js'//引入api接口
const defaultForm = {//创建默认表单内容对象
title: '', //课程标题
teacherId: '', //讲师id
subjectId: '', //课程分类id
subjectParentId:'',//课程分类父id
lessonNum: 0, //总课时
description: '', //课程简介
cover: '',
price: 0 //课程价格
}
export default {
data() {
return {
courseInfo: defaultForm,//为数据赋初始值
saveBtnDisabled: false // 保存按钮是否禁用
}
},
watch: {
$route(to, from) {//监听路由是否发生变化
console.log('watch $route')
this.init()//如果路由改变,调用初始化函数
}
},
created() {//页面加载时,调用初始化函数
console.log('info created')
this.init()
},
methods: {
/* 初始化函数*/
init() {
if (this.$route.params && this.$route.params.id) {//如果当前路由的params有值并且传过来的值是id就执行
const id = this.$route.params.id//将当前的id值拿出来
console.log(id)
} else {//若没有值,就直接赋值为默认的初始值
this.courseInfo = { ...defaultForm }
}
},
/* 下一步函数*/
next() {//当点击下一步时
console.log('next')
this.saveBtnDisabled = true//让保存按钮可用
if (!this.courseInfo.id) {//如果现在数据中没有id值
this.saveData()//执行保存函数
} else {
this.updateData()
}
},
// 保存
saveData() {
//调用api接口
eduCourse.saveCourseInfo(this.courseInfo).then(response => {
this.$message({//提示
type: 'success',
message: '保存成功!'
})
return response// 将响应结果传递给then
}).then(response => {
this.$router.push({name:"课程添加2",params:{id:response.data.courseId}})
}).catch((response) => {
this.$message({
type: 'error',
message: response.message
})
})
},
updateData() {//跳转路由,id为1
this.$router.push({name:"课程添加2",params:{id:1}})
}
}
}
</script>
4、save2页面(用于测试,代码都是复制save1的,之后会改)
<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() {
console.log('chapter created')
},
methods: {
previous() {
console.log('previous')
this.$router.push({name:"课程添加",params:{id:1}})
},
next() {
console.log('next')
this.$router.push({name:"课程添加3",params:{id:1}})
}
}
}
</script>
5、测试
3、完善讲师选择
1、添加页面组件
<!-- 课程讲师 -->
<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接口
3、编写逻辑代码
4、完善课程分类选择(二级联动分类)
1、添加组件
<!-- 所属分类:级联下拉列表 -->
<el-form-item label="课程类别">
<!-- 一级分类 -->
<el-select
v-model="courseInfo.subjectParentId"
placeholder="请选择"
@change="oneSubjectChange(courseInfo.subjectParentId)">
<el-option
v-for="subject in oneSubject"
:key="subject.id"
:label="subject.title"
:value="subject.id"
/>
</el-select>
<!-- 二级分类 -->
<el-select v-model="courseInfo.subjectId" placeholder="请选择">
<el-option
v-for="subject in twoSubject"
:key="subject.value"
:label="subject.title"
:value="subject.id"/>
</el-select>
</el-form-item>
2、api接口
3、编写逻辑代码
4、测试
5、复用阿里云OSS头像上传的微服务,实现课程封面上传(分模快微服务,一个服务到处使用)
1、添加组件
<el-form-item label="课程封面">
<el-upload
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:action="BASE_API+'/ossservice/uploadFile'"
class="avatar-uploader">
<img :src="courseInfo.cover">
</el-upload>
</el-form-item>
2、代码实现
/* 上传成功调用*/
handleAvatarSuccess(res, file) {
console.log(res)// 上传响应
console.log(URL.createObjectURL(file.raw))// base64编码
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
},