资料
资料地址
后台管理系统目录 前台展示系统目录
1 - 构建工程篇 7 - 渲染前台篇
2 - 前后交互篇 8 - 前台登录篇
3 - 文件上传篇 9 - 前台课程篇
4 - 课程管理篇 10 - 前台支付篇
5 - 章节管理篇 11 - 统计分析篇
6 - 微服务治理 12 - 项目完结篇

目录

  • 一、EasyExcel 基本使用
  • 二、课程分类添加
    • 2.1、后端接口
    • 2.2、前端展示
  • 三、课程分类列表
    • 3.1、后端接口
    • 3.2、前端展示
  • 四、添加课程信息
    • 4.1、数据库表
    • 4.2、后端接口
    • 4.3、前端展示 初始化步骤条
    • 4.4、填写课程基本信息
    • 4.5、讲师下拉列表显示
    • 4.6、课程分类多级联动
    • 4.7、课程封面
    • 4.8、富文本编辑器 Tinymce
  • 五、课程大纲列表
    • 5.1、后端接口
    • 5.2、前端实现
  • 六、修改课程信息
    • 6.1、回显课程信息
    • 6.2、修改课程信息
    • 6.3、前端实现
    • 6.4、优化回显
    • 6.5、优化表单
    • 6.6、修改课程信息

一、EasyExcel 基本使用

1.1、Excel导入导出的应用场景

  • 数据导入:减轻录入工作量

  • 数据导出:统计信息归档

  • 数据传输:异构系统之间数据传输


1.2、EasyExcel简介

  • Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。

  • EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

  • EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)


1.3、EasyExcel写

1、service_edu模块 引入依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version>
</dependency>

2、创建实体类

//设置表头和添加的数据字段
@Data
@ToString
public class UserDemo {//设置excel表头名称@ExcelProperty("序号")private Integer no;//设置excel表头名称@ExcelProperty("姓名")private String name;}

3、实现写操作 (第一种)

创建方法循环设置要添加到Excel的数据(写法一)推荐

public class TestEasyExcel {public static void main(String[] args) {String filename = "C:\\Users\\lapto\\Desktop\\UserDemo.xlsx";EasyExcel.write(filename, UserDemo.class).sheet("用户列表").doWrite(getLists());}private static List<UserDemo> getLists() {ArrayList<UserDemo> list = new ArrayList<>();for (int i = 1; i < 4; i++) {UserDemo demoData = new UserDemo();demoData.setNo(i);demoData.setName("Laptoy:" + i);list.add(demoData);}return list;}
}


4、实现写操作 (第二种)

public class TestEasyExcel {public static void main(String[] args) {String filename = "C:\\Users\\lapto\\Desktop\\UserDemo.xlsx";ExcelWriter excelWriter = EasyExcel.write(filename, UserDemo.class).build();WriteSheet build = EasyExcel.writerSheet("用户列表").build();// 这里 需要指定写用哪个class去写excelWriter.write(getLists(), build);// 千万别忘记finish 会帮忙关闭流excelWriter.finish();}private static List<UserDemo> getLists() {ArrayList<UserDemo> list = new ArrayList<>();for (int i = 1; i < 4; i++) {UserDemo demoData = new UserDemo();demoData.setNo(i);demoData.setName("Laptoy:" + i);list.add(demoData);}return list;}
}

1.4、EasyExcel读

1、实体类

//设置表头和添加的数据字段
@Data
@ToString
public class UserDemo {//设置excel表头名称@ExcelProperty(value = "序号",index = 0)private Integer no;//设置excel表头名称@ExcelProperty(value = "姓名",index = 1)private String name;}

2、创建读取操作的监听器

public class ExcelListener extends AnalysisEventListener<UserDemo> {//创建list集合封装最终的数据List<UserDemo> list = new ArrayList<>();//一行一行去读取excel内容@Overridepublic void invoke(UserDemo demoData, AnalysisContext analysisContext) {System.out.println(demoData);list.add(demoData);}//读取excel表头信息@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头信息" + headMap);}//读取完成后执行@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

3、调用实现最终的读取

public static void main(String[] args) {String filename = "C:\\Users\\lapto\\Desktop\\UserDemo.xlsx";// 指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(filename, UserDemo.class, new ExcelListener()).sheet().doRead();
}


二、课程分类添加

2.1、后端接口

通过传入excel文件进行添加

添加时一级分类只需要添加一次,所以需要判空


1、使用代码生成器生成课程分类代码

strategy.setInclude("edu_subject");

2、控制层

@Api(tags = "课程分类管理")
@CrossOrigin //解决跨域问题
@RestController
@RequestMapping("/eduservice/subject")
public class EduSubjectController {@Autowiredprivate EduSubjectService eduSubjectService;//添加课程分类//获取上传过来的文件,把文件内容读取出来@PostMapping("/addSubject")public R addSubject(MultipartFile file) {//获取上传的excel文件 MultipartFileeduSubjectService.saveSubject(file, eduSubjectService);//判断返回集合是否为空return R.ok();}}

2、业务层

@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {//添加课程分类@Overridepublic void saveSubject(MultipartFile file, EduSubjectService eduSubjectService) {InputStream inputStream = null;try {inputStream = file.getInputStream();//调用方法进行读取EasyExcel.read(inputStream, SubjectData.class, new SubjectExcelListener(eduSubjectService)).sheet().doRead();} catch (Exception e) {e.printStackTrace();throw new LaptoyException(20002, "请选择文件或文件格式不支持");}}
}

3、Listener

public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {//因为SubjectExcelListener態交给spring进行ioc管理,需要自己手动new,不能注入其他对象public EduSubjectService eduSubjectService;//有参,通过构造方法手动注入service进而操作数据库public SubjectExcelListener(EduSubjectService eduSubjectService) {this.eduSubjectService = eduSubjectService;}public SubjectExcelListener() {}//读取excel内容,一行一行读取@Overridepublic void invoke(SubjectData subjectData, AnalysisContext analysisContext) {//表示excel中没有数据,就不需要读取了if (subjectData == null) {throw new LaptoyException(20001, "添加失败");}//一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类//判断是否有一级分类是否重复EduSubject existOneSubject = this.existOneSubject(eduSubjectService, subjectData.getOneSubjectName());if (existOneSubject == null) { //没有相同的一级分类,进行添加existOneSubject = new EduSubject();existOneSubject.setParentId("0"); //设置一级分类id值,0代表为一级分类existOneSubject.setTitle(subjectData.getOneSubjectName());//设置一级分类名eduSubjectService.save(existOneSubject);//给数据库添加一级分类}//判断是否有二级分类是否重复EduSubject existTwoSubject = this.existTwoSubject(eduSubjectService, subjectData.getTwoSubjectName(), existOneSubject.getId());if (existTwoSubject == null) {existTwoSubject = new EduSubject();existTwoSubject.setParentId(existOneSubject.getId());existTwoSubject.setTitle(subjectData.getTwoSubjectName());eduSubjectService.save(existTwoSubject);}}// 判断一级分类不能重复添加private EduSubject existOneSubject(EduSubjectService eduSubjectService, String name) {QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();wrapper.eq("title", name).eq("parent_id", "0");EduSubject oneSubject = eduSubjectService.getOne(wrapper);return oneSubject;}// 判断二级分类不能重复添加private EduSubject existTwoSubject(EduSubjectService eduSubjectService, String name, String parentId) {QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();wrapper.eq("title", name).eq("parent_id", parentId);EduSubject twoSubject = eduSubjectService.getOne(wrapper);return twoSubject;}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

4、测试之前给实体类 edu_subject 配置填充

@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;@TableField(fill = FieldFill.INSERT_UPDATE)
@ApiModelProperty(value = "更新时间")
private Date gmtModified;

5、测试

TestDemo.xlsx


数据库


2.2、前端展示

1、路由

// 课程分类模块路由
{path: 'subject',component: Layout,redirect: '/edu/subject/list',name: '课程分类管理',meta: { title: '课程分类管理', icon: 'nested' },children: [{path: 'list',name: '课程分类列表',component: () => import('@/views/edu/subject/list.vue'),meta: { title: '课程分类列表', icon: 'table' }},{path: 'import',name: '课程分类导入',component: () => import('@/views/edu/subject/import.vue'),meta: { title: '导入课程分类', icon: 'nested' }}]
},

2、组件 import.vue

<template><div class="app-container"><el-form label-width="120px"><el-form-item label="Excel模板"><el-tag><i class="el-icon-download" /><a :href="'/static/test.xlsx'">点击下载模版</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/subject/addSubject'" 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">上传到服务器</el-button></el-upload></el-form-item></el-form></div>
</template><script>export default {data() {return {BASE_API: process.env.BASE_API, // 接口API地址importBtnDisabled: false, // 按钮是否禁用,loading: false,};},methods: {//点击按钮上传文件到接口submitUpload() {this.importBtnDisabled = true;this.loading = true;this.$refs.upload.submit();},//上传成功后fileUploadSuccess(resp) {if (resp.success === true) {this.loading = false;this.$message({type: "success",message: resp.message,});}},//上传失败后fileUploadError(resp) {this.loading = false;this.$message({type: "error",message: "导入失败",});},},
}
</script>



三、课程分类列表

3.1、后端接口

1、创建VO用于封装树形结构数据

//一级分类
@Data
public class OneSubject {private String id;private String title;// 二级分类private List<TwoSubject> children = new ArrayList<>();
}//二级分类
@Data
public class TwoSubject {private String id;private String title;
}

2、控制层

// 课程分类列表(树形)
@ApiOperation(value = "嵌套数据列表")
@GetMapping("/getAllSubject")
public R getAllSubject(){//ist集合泛型是一级分类,一级分类中本身含有二级分类List<OneSubject> list = eduSubjectService.getAllOneTwoSubject();return R.ok().data("data",list);
}

3、业务层

// 树形显示课程分类
@Override
public List<OneSubject> getAllOneTwoSubject() {// 查询所有一级分类   parent_id = 0QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();wrapperOne.eq("parent_id", "0");List<EduSubject> oneSubjectsList = baseMapper.selectList(wrapperOne);// 查询所有二级分类  parent_id != 0QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();wrapperTwo.ne("parent_id", "0");List<EduSubject> twoSubjectsList = baseMapper.selectList(wrapperTwo);// 创建list集合,用于封装最终数据List<OneSubject> finalSubjectList = new ArrayList<>();//封装一级分类for (int i = 0; i < oneSubjectsList.size(); i++) {EduSubject oSubject = oneSubjectsList.get(i);OneSubject oneSubject = new OneSubject();BeanUtils.copyProperties(oSubject, oneSubject);// 添加到listfinalSubjectList.add(oneSubject);// 在一级分类循环遍历查询所有的二级分类// 创建list集合封装每个一级分类的二级分类ArrayList<TwoSubject> finalTwoSubjects = new ArrayList<>();// 遍历二级list集合for (int j = 0; j < twoSubjectsList.size(); j++) {EduSubject tSubject = twoSubjectsList.get(j);// 判断二级分类parentId和一级分类id是否一样if (tSubject.getParentId().equals(oSubject.getId())) {TwoSubject twoSubject = new TwoSubject();BeanUtils.copyProperties(tSubject, twoSubject);finalTwoSubjects.add(twoSubject);}}//把一级下面所有二级分类放到oneSubject里面oneSubject.setChildren(finalTwoSubjects);}return finalSubjectList;
}

4、测试


3.2、前端展示

1、创建API - src/api/teacher/subject.js

import request from '@/utils/request' export default {//课程分类列表getSubjectList() {return request({url: "/eduservice/subject/getAllSubject",method: 'get'})}
}

2、list.vue

<template><div class="app-container"><el-input v-model="filterText" placeholder="输入关键字进行查询" style="margin-bottom: 30px" /><el-tree ref="tree" :data="data" :props="defaultProps" :filter-node-method="filterNode" class="filter-tree" /></div>
</template><script>
import subject from '@/api/teacher/subject.js'export default {data() {return {filterText: "",data: [], //返回所有分类的数据defaultProps: {children: "children",label: "title",},};},watch: {filterText(val) {this.$refs.tree.filter(val);},},methods: {getAllSubjectList() {subject.getSubjectList().then(resp => {this.data = resp.data.data})},// 不区分大小写检索filterNode(value, data) {if (!value) return true;return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1},},created() {this.getAllSubjectList()},
};
</script>

3、优化导入课程分类后路由到课程分类列表 import.vue


四、添加课程信息

4.1、数据库表

  • edu_course:课程表:存储课程基本信息

    • edu_course_description:课程简介表:存储课程简介信息
    • edu_chapter:课程章节表:存储课程章节信息
    • edu_video:课程小节表:存储章节里面小节信息
  • edu_teacher:讲师表
  • edu_subject:分类表


edu_course 课程表

edu_course_description 课程简介表

edu_chapter 课程章节表

edu_video 课程小节表


4.2、后端接口

1、代码生成器生成代码

strategy.setInclude("edu_course", "edu_chapter", "edu_course_description", "edu_video");//根据数据库哪张表生成,有多张表就加逗号继续填写

2、创建vo用于封装表单提交数据

@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoFormVo 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 = "课程专业父级ID")private String 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;}

2、控制层

@Api(tags = "课程模块")
@CrossOrigin //解决跨域问题
@RestController
@RequestMapping("/eduservice/course")
public class EduCourseController {@Autowiredprivate EduCourseService eduCourseService;//添加课程基本信息方法@PostMapping("/addCourseInfo")public R addCourseInfo(@RequestBody CourseInfoFormVo vo){String id = eduCourseService.saveCourseInfo(vo);// 返回课程id给下一步操作return R.ok().data("courseId",id);}}

3、课程描述的id根据数据表分析,此id与课程id为同一个,所以我们修改实体类主键生成策略为手动输入

@TableId(value = "id", type = IdType.INPUT)
private String id;

4、业务层

@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {@Autowiredprivate EduCourseDescriptionService descriptionService;@Overridepublic String saveCourseInfo(CourseInfoFormVo vo) {EduCourse course = new EduCourse();BeanUtils.copyProperties(vo, course);boolean flag = this.save(course);if (!flag) {// 保存失败throw new LaptoyException(20001, "添加课程失败");}// 保存成功// 保存课程描述信息,需要将保存成功的课程id传入课程描述表EduCourseDescription description = new EduCourseDescription();// 保存成功自动生成了课程id,将该id赋值给课程描述表description.setId(course.getId());description.setDescription(vo.getDescription());descriptionService.save(description);return course.getId();}
}

5、测试

{"cover": "封面url","description": "描述信息","id": "","lessonNum": 0,"price": 0,"subjectId": "","subjectParentId": "","teacherId": "","title": "课程名称"
}

4.3、前端展示 初始化步骤条

element-ui step步骤条

1、添加路由

// 课程信息路由
{path: '/course',component: Layout,redirect: '/course/list',name: '课程管理',meta: { title: '课程管理', icon: 'nested' },children: [{path: 'list',name: '课程列表',component: () => import('@/views/edu/course/list.vue'),meta: { title: '课程列表', icon: 'table' }},{path: 'info',name: '添加课程',component: () => import('@/views/edu/course/info.vue'),meta: { title: '添加课程', icon: 'nested' }},{path: 'info/:id',name: 'EduCourseInfoEdit',component: () => import('@/views/edu/course/info.vue'),meta: { title: '编辑课程基本信息', noCache: true },hidden: true},{path: 'chapter/:id',name: 'EduCourseChapterEdit',component: () => import('@/views/edu/course/chapter.vue'),meta: { title: '编辑课程大纲', noCache: true },hidden: true},{path: 'publish/:id',name: 'EduCoursePublishEdit',component: () => import('@/views/edu/course/publish.vue'),meta: { title: '发布课程', noCache: true },hidden: true}]
},

初始化步骤组件

2、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,};},methods: {next() {//跳转到第二步this.$router.push('/course/chapter/1')},},created() {}
};
</script>

3、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,};},methods: {previous() {//跳转到上一步this.$router.push("/course/info/1");},next() {//跳转到第三步this.$router.push("/course/publish/1");},},created() { },
};
</script>

4、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,};},methods: {// 跳转到上一步previous() {this.$router.push("/course/chapter/1");},publish() {this.$router.push("/course/list");}},
};
</script>




4.4、填写课程基本信息

1、定义API - src/api/course.js

import request from '@/utils/request'export default {// 添加课程信息功能addCourseInfo(courseInfo) {return request({url: "/eduservice/course/addCourseInfo",method: 'post',data: courseInfo,})}
}

2、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 --><!-- 课程讲师 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></div>
</template><script>
import course from "@/api/teacher/course.js";export default {data() {return {saveBtnDisabled: false,courseInfo:{title: "",subjectId: "",teacherId: "",subjectParentId: "",lessonNum: 0,description: "",cover: "",price: 0,},};},methods: {saveOrUpdate() {course.addCourseInfo(this.courseInfo).then(resp => {this.$message({message: "添加课程信息成功",type: "success",})//跳转到第二步,并带着这个课程生成的idthis.$router.push({ path: "/course/chapter/" + resp.data.courseId });});},},created() { },
};
</script>

4.5、讲师下拉列表显示

1、定义API - course.js

// 查询所有讲师
getAllTeacher() {return request({url: "/eduservice/teacher/findAll",method: 'get',})
}

2、页面

<el-form-item label="课程讲师"><el-select v-model="courseInfo.teacherId" placeholder="请选择"><el-option v-for="teacher in teacherLists" :key="teacher.id" :label="teacher.name" :value="teacher.id"></el-option></el-select>
</el-form-item>

3、逻辑

import course from "@/api/teacher/course.js";export default {data() {return {teacherLists: [], //封装所有讲师数据};},methods: {//查询所有讲师getListTeacher() {course.getAllTeacher().then((resp) => {this.teacherLists = resp.data.data;});},},created() {this.getListTeacher();},
};

4.6、课程分类多级联动

ElmentUI - 选择器

1、API

// 查询课程分类
getSubjectList() {return request({url: "/eduservice/subject/getAllSubject",method: 'get'})
}

2、页面

<el-form-item label="所属分类"><el-select v-model="courseInfo.subjectParentId" placeholder="一级分类" @change="subjectLevelOneChanged"><el-option v-for="subject in subjectOneLists" :key="subject.id" :label="subject.title" :value="subject.id"></el-option></el-select><el-select v-model="courseInfo.subjectId" placeholder="二级分类"><el-option v-for="subject in subjectTwoLists" :key="subject.id" :label="subject.title" :value="subject.id"></el-option></el-select>
</el-form-item>

3、逻辑

export default {data() {return {subjectOneLists: [], //封装一级分类数据subjectTwoLists: [], //封装二级分类数据   };},methods: {// 查出所有分类信息getSubjectList() {course.getSubjectList().then((resp) => {this.subjectOneLists = resp.data.data})},// 点击一级分类查出二级分类subjectLevelOneChanged(value) {// value就是一级分类的id值for (let i = 0; i < this.subjectOneLists.length; i++) {if (this.subjectOneLists[i].id === value) {this.subjectTwoLists = this.subjectOneLists[i].children;}}}},created() {this.getSubjectList();},
};

4.7、课程封面

1、组件模板

<el-form-item label="课程封面"><el-upload :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" :action="BASE_API + '/eduoss/fileoss/upload'" class="avatar-uploader"><img :src="courseInfo.cover" /></el-upload>
</el-form-item>

2、js

import course from "@/api/teacher/course.js";export default {data() {return {BASE_API: process.env.BASE_API, // 接口API地址courseInfo: {cover: "https://www.baidu.com/img/flexible/logo/pc/result.png",},};},methods: {// 图片上传// 上传成功展示上传的图片到 <img />handleAvatarSuccess(resp, file) {this.courseInfo.cover = resp.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;}},
};

4.8、富文本编辑器 Tinymce

简介通过富文本编辑器进行编辑

Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤


1、复制脚本库

将资源里的富文本编辑器脚本库 components statis 复制到项目的对应目录下

2、配置html变量

在 /build/webpack.dev.conf.js 中添加配置

new HtmlWebpackPlugin({...templateParameters: {BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory}
})

3、引入 js脚本 - index.html

<body><script src="<%=BASE_URL%>/tinymce4.7.5/tinymce.min.js"></script><script src="<%=BASE_URL%>/tinymce4.7.5/langs/zh_CN.js"></script><div id="app"></div><!-- built files will be auto injected -->
</body>

4、使用组件 - info.vue

<el-form-item label="课程简介"><tinymce :height="150" v-model="courseInfo.description" />
</el-form-item>
<script>
import Tinymce from '@/components/Tinymce';export default {components: { Tinymce },
}
</script><style scoped>
.tinymce-container {line-height: 29px;
}
</style>

最终效果

测试完毕:

课程表与课程简介表、讲师表、课程分类表 关联并保存数据成功


五、课程大纲列表

5.1、后端接口

1、创建实体类 章节和小节

章节

@Data
public class ChapterVo implements Serializable {private static final long serialVersionUID = 1L;private String id;private String title;//表示小节private List<VideoVo> children = new ArrayList<VideoVo>();
}

小节 即每个视频

@Data
public class VideoVo implements Serializable {private static final long serialVersionUID = 1L;private String id;private String title;private Boolean free;
}

2、控制层

@Api(tags = "章节模块")
@CrossOrigin
@RestController
@RequestMapping("/eduservice/chapter")
public class EduChapterController {@Autowiredprivate EduChapterService eduChapterService;// 获取课程大纲列表,根据课程id进行查询@GetMapping("/getChapterVideo/{courseId}")public R getChapterVideo(@PathVariable String courseId){List<ChapterVo> list = eduChapterService.getChapterVideoByCourseId(courseId);return R.ok().data("data",list);}
}

3、业务层

@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {@Autowiredprivate EduVideoService eduVideoService;@Overridepublic List<ChapterVo> getChapterVideoByCourseId(String courseId) {// 查询章节信息QueryWrapper<EduChapter> wrapper = new QueryWrapper<>();wrapper.eq("course_id",courseId);List<EduChapter> eduChapters = baseMapper.selectList(wrapper);// 查询小节信息QueryWrapper<EduVideo> wrapper1 = new QueryWrapper<>();wrapper1.eq("course_id",courseId);List<EduVideo> eduVideos = eduVideoService.list(wrapper1);// 创建章节vo列表ArrayList<ChapterVo> finalChapterVos = new ArrayList<>();// 填充章节vo数据 for (int i = 0; i < eduChapters.size(); i++) {EduChapter chapter = eduChapters.get(i);// 创建章节vo对象ChapterVo chapterVo = new ChapterVo();BeanUtils.copyProperties(chapter,chapterVo);finalChapterVos.add(chapterVo);//填充课时vo对象ArrayList<VideoVo> finalVideoVos = new ArrayList<>();for (int j = 0; j < eduVideos.size(); j++) {EduVideo video = eduVideos.get(j);if (chapter.getId().equals(video.getChapterId())){VideoVo videoVo = new VideoVo();BeanUtils.copyProperties(video,videoVo);finalVideoVos.add(videoVo);}}chapterVo.setChildren(finalVideoVos);}return finalChapterVos;}
}

5.2、前端实现

1、定义API - chapter.js

import request from '@/utils/request' //引入已经封装好的axios 和 拦截器export default {//根据课程id获取章节和小节数据列表getChapterVideoByCourseId(courseId) {return request({url: `/eduservice/chapter/getChapterVideo/${courseId}`,method: 'get',})},
}

2、chapter.vue 页面

<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"><ul><li v-for="chapter in chapterVideoList" :key="chapter.id"><p>{{ chapter.title }}<span><el-button type="text">添加课时</el-button><el-button style="" type="text">编辑</el-button><el-button type="text">删除</el-button></span></p><ul><li v-for="video in chapter.children" :key="video.id"><p>{{ video.title }}<span class="acts"><el-button type="text">编辑</el-button><el-button type="text">删除</el-button></span></p></li></ul></li></ul><el-form-item><el-button @click="dialogChapterFormVisible=true">添加章节</el-button><el-button @click="previous">上一步</el-button><el-button :disabled="saveBtnDisabled" type="primary" @click="next">下 一步</el-button></el-form-item></el-form>
</div><script>
import chapter from '@/api/teacher/chapter.js';export default {data() {return {saveBtnDisabled: false,courseId: '',chapterVideoList: [],};},methods: {//根据课程id查询对应的课程章节和小结getChapterVideoByCourseId() {chapter.getChapterVideoByCourseId(this.courseId).then(resp => {this.chapterVideoList = resp.data.dataconsole.log(resp.data.data)})},},created() {//获取路由里的id值if (this.$route.params && this.$route.params.id) {this.courseId = this.$route.params.id}//根据课程id查询对应的课程章节和小结this.getChapterVideoByCourseId();},
};
</script>

3、测试 - http://localhost:9528/#/course/chapter/18


六、修改课程信息

点击上一步按钮,回显当前课程id的信息,并通过 下一步按钮 进行更新课程信息

6.1、回显课程信息

1、控制层

// 根据课程id查询课程基本信息
@GetMapping("/getCourseInfoById/{courseId}")
public R getCourseInfoById(@PathVariable String courseId) {CourseInfoFormVo courseInfoForm = eduCourseService.getCourseInfo(courseId);return R.ok().data("data", courseInfoForm);
}

2、业务层

@Autowired
private EduCourseDescriptionService descriptionService;@Override
public CourseInfoFormVo getCourseInfo(String courseId) {EduCourse eduCourse = this.getById(courseId);CourseInfoFormVo vo = new CourseInfoFormVo();BeanUtils.copyProperties(eduCourse, vo);EduCourseDescription description = descriptionService.getById(courseId);vo.setDescription(description.getDescription());return vo;
}

6.2、修改课程信息

1、控制层

// 修改课程信息
@PostMapping("/updateCourseInfo")
public R updateCourseInfo(@RequestBody CourseInfoFormVo vo) {eduCourseService.updateCourseInfo(vo);return R.ok();
}

2、业务层

@Override
public void updateCourseInfo(CourseInfoFormVo vo) {// 修改课程基本信息EduCourse eduCourse = new EduCourse();BeanUtils.copyProperties(vo, eduCourse);boolean flag = this.updateById(eduCourse);// 失败if(!flag){throw new LaptoyException(20001,"修改课程失败");}// 成功// 修改课程描述信息EduCourseDescription description = new EduCourseDescription();BeanUtils.copyProperties(vo,description);descriptionService.updateById(description);}

6.3、前端实现

1、API - course.js

//根据课程id 查询课程基本信息
getCourseInfoById(courseId) {return request({url: `/eduservice/course/getCourseInfoById/${courseId}`,method: 'get',})
},//修改课程信息
updateCourseInfo(courseInfoForm) {return request({url: "/eduservice/course/updateCourseInfo",method: 'post',data: courseInfoForm,})
}

2、chapter.vue

previous() {//跳转到上一步this.$router.push("/course/info/" + this.courseId);
},
next() {//跳转到第三步this.$router.push("/course/publish/" + this.courseId);
},

3、info.vue

data() {return {courseId: ""}
}methods: {// 获取课程信息getCourseInfo() {course.getCourseInfoById(this.courseId).then((resp) => {this.courseInfo = resp.data.data})},
},
created() {//判断路径中是否有课程idif (this.$route.params && this.$route.params.id) {this.courseId = this.$route.params.id;//根据课程id 查询课程基本信息this.getCourseInfo()};...
},

4、测试

当返回上一步时二级分类回显为id


6.4、优化回显

默认二级分类的标题需要通过点击一级分类后的该方法进行展示

// 点击一级分类查出二级分类
subjectLevelOneChanged(value) {// value就是一级分类的id值for (let i = 0; i < this.subjectOneLists.length; i++) {if (this.subjectOneLists[i].id === value) {this.subjectTwoLists = this.subjectOneLists[i].children;}}
}

现在需要通过获取分类信息,并且将分类信息的每一个的id与当前课程id进行比较,如果相同就将二级分类进行回显标题

1、修改钩子函数

created() {//判断路径中是否有课程id,回显操作(修改)if (this.$route.params && this.$route.params.id) {this.courseId = this.$route.params.id;//根据课程id 查询课程基本信息this.getCourseInfo()} else {// 路径中无课程id,新增操作this.getSubjectList();}// 回显所有讲师this.getListTeacher();
},

2、获取课程信息

// 获取课程信息
getCourseInfo() {course.getCourseInfoById(this.courseId).then((resp) => {this.courseInfo = resp.data.data;//查询所有分类,包含一级和二级所有course.getSubjectList().then((resp) => {//获取所有一级分类this.subjectOneLists = resp.data.data;//把所有一级分类数组进行遍历for (var i = 0; i < this.subjectOneLists.length; i++) {//获取每个一级分类var oneSubject = this.subjectOneLists[i];//比较当前courseInfo里面的一级分类id和所有的一级分类id是否一样if (this.courseInfo.subjectParentId == oneSubject.id) {//获取一级分类中所有的二级分类this.subjectTwoLists = oneSubject.children;}}});});
}


6.5、优化表单

回显数据后,再单击添加课程模块,数据还在,按理应该是清空数据

watch: {$route(to, from) {//路由变化方式,当路由发送变化,方法就执行this.courseInfo = {}},
}

6.6、修改课程信息

saveOrUpdate() {if (this.courseInfo.id) {this.updateCourse();} else {this.saveCourse();}
},saveCourse() {course.addCourseInfo(this.courseInfo).then(resp => {this.$message({message: "添加课程信息成功",type: "success",})//跳转到第二步,并带着这个课程生成的idthis.$router.push({ path: "/course/chapter/" + resp.data.courseId });});
},updateCourse() {course.updateCourseInfo(this.courseInfo).then(resp => {this.$message({message: "修改课程信息成功",type: "success",})//跳转到第二步,并带着这个课程生成的idthis.$router.push({ path: "/course/chapter/" + this.courseId });});
},

2022年最新《谷粒学院开发教程》:4 - 课程管理相关推荐

  1. 2022年最新《谷粒学院开发教程》:9 - 前台课程模块

    资料 资料地址 后台管理系统目录 前台展示系统目录 1 - 构建工程篇 7 - 渲染前台篇 2 - 前后交互篇 8 - 前台登录篇 3 - 文件上传篇 9 - 前台课程篇 4 - 课程管理篇 10 - ...

  2. 谷粒学院(十)课程管理模块 | 课程大纲列表 | 二级联动 | 富文本编辑器

    一.课程添加分析 发布流程图 数据表对应 课程相关表的关系 二.课程管理–添加课程后端 1.使用代码生成器生成相关课程相关的代码 CodeGenerator类中生成 2.细节问题 (1)创建vo实体类 ...

  3. 2022年最新《谷粒学院开发教程》:5 - 章节管理

    资料 资料地址 后台管理系统目录 前台展示系统目录 1 - 构建工程篇 7 - 渲染前台篇 2 - 前后交互篇 8 - 前台登录篇 3 - 文件上传篇 9 - 前台课程篇 4 - 课程管理篇 10 - ...

  4. 2022年最新Java后端开发技术架构总结

    做了十年的Java开发和项目管理,和大家介绍一下我总结的一两年用得比较多的Java框架(包括软件.中间件),以及相关开发.测试.运维.项目管理.技术支持等. 2022年最新Java后端开发技术架构总结 ...

  5. Django 3.2.5博客开发教程:用Admin管理后台管理数据

    上节我们我们把数据库迁移到数据库里去了,那么现在我们数据库里是个什么样的情况呢?我们点击Pycharm右上角的Database,然后在网站项目里选中我们的数据库文件db.sqlite3,把它拖到Dat ...

  6. 微信公众号开发教程[012]-素材管理

    一.关于图文消息,我的理解         我理解中,普通<img><iframe>等标签的src属性,可以跨域,哪里的图片都行.但是公众号的聊天会话界面,放的图片,语音,图文 ...

  7. 2022年最新《谷粒商城开发教程》:1 - 构建工程篇

    Java工程师的进阶之路 代码地址 2 - vue 简要笔记 3 - 商品服务-API-三级分类 4 - 商品服务-API-品牌管理 5 - 商品服务-API-属性分组 目录 前言 一.项目架构 二. ...

  8. 2022年最新《谷粒商城开发教程》:2 - vue 简要笔记

    Java工程师的进阶之路 代码地址 1 - 构建工程篇 2 - vue 简要笔记 3 - 商品服务-API-三级分类 4 - 商品服务-API-品牌管理 5 - 商品服务-API-属性分组 目录 一. ...

  9. 【作废】Inventor 二次开发学习指南入门到精通(含Inventor最新二次开发教程下载)

    (由于AU中国已关闭,很多链接失效,而且有些内容需要更新.特作废此文,另外撰写一篇新的) 年初我曾撰写了一篇文章,登载到我同事的博客,以及AU中国.我想这篇作为本博客的第一篇正式技术文章,应该是最合适 ...

最新文章

  1. linux记录用户命令的日志是,用日志记录Linux用户执行的每一条命令
  2. 邓文迪撑杆跳,甩开老公要独立?_富杂志_新浪博客
  3. ThinkCMF是一款支持Swoole的开源内容管理框架,基于ThinkPHP开发,同时支持PHP-FPM和Swoole双模式,让WEB开发更快!
  4. 【Java SE:抽象类】抽象类的引出与深入理解
  5. 简单神经网络_mnist
  6. matlab 中使用 GPU 加速运算
  7. 【狂神MySQL笔记】初识Mysql
  8. bci测试如何整改_电源动态响应测试,什么样的波形算合格?
  9. mysql截取字符串最后两位_MySQL截取字段中最后两位不想要的字符串 以及截取函数...
  10. 我为何从开发转测试,并坚持了16年?
  11. BIOS锁定纯UEFI启动的解锁办法
  12. 2020计算机行业就业职位及分析
  13. FRP分享 Padavan
  14. MySql:学生表、教师表、课程表、分数表 练习
  15. 玩转python的正则表达式|提取字符串中的所有数字
  16. 你对MySQL中的索引了解多少?
  17. [译] 3.泛型和子类型
  18. java改成字体_更改JRE字体配置
  19. 自治智能的数据库云管平台zCloud v3.3正式发布
  20. 【KV260】K26 SOM烧写脚本

热门文章

  1. PowerMill开发系列功能-批量处理过切检查功能
  2. html表单 asp验证,ASP中JavaScript处理复杂表单的生成与验证
  3. 一网打尽、详解面试最常被问到的几种 Python 设计模式
  4. 计算器算贝塞尔公式_二手车评估计算器是如何估价的?
  5. WeakReference 学习和使用
  6. 用DCGAN生成卡通人脸
  7. eclipse 操作的小实用技巧
  8. 如何学习自动化测试?
  9. python airflow_Airflow使用经验分享
  10. 工作流平台airflow简介