EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.1.1version>
dependency>
dependencies>
这个又依赖与poi的依赖,而且版本也一定要对应
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>3.17version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.17version>
dependency>
和excel数据对应
@Data
public class ExcelData {
//设置excel表头名称
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String sname;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
public static void main(String[] args) throws Exception {
//实现excel写的操作
// 设置写入文件夹地址和excel文件名
String fileName = "D:\\java.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
//write方法两个参数 ,第一个参数文件路径名称,第二个参数实体类class
EasyExcel.write(fileName, ExcelData.class).sheet("第一个sheet").doWrite(data());
}
//循环设置要添加的数据,最终封装到list集合中
private static List<ExcelData> data() {
List<ExcelData> list = new ArrayList<ExcelData>();
for (int i = 0; i < 10; i++) {
ExcelData data = new ExcelData();
data.setSno(i);
data.setSname("dyk"+i);
list.add(data);
}
return list;
}
@Data
public class ExcelData {
//设置excel表头名称,index设置列对应的属性
@ExcelProperty(value = "学生编号",index = 0)
private Integer sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}
package com.atguigu.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//创建读取excel监听器
public class ExcelListener extends AnalysisEventListener<ExcelData> {
//创建list集合封装最终的数据
List<ExcelData> list = new ArrayList<ExcelData>();
private final ArrayList<ExcelData> ExcelData = new ArrayList<ExcelData>();
//一行一行去读取excle内容
@Override
public void invoke(ExcelData user, AnalysisContext analysisContext) {
System.out.println("***"+user);
list.add(user);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
public static void main(String[] args) {
String fileName = "D:\\java.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ExcelData.class, new ExcelListener()).sheet().doRead();
}
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.1.6version>
dependency>
在entity下新建一个excel包,在excel包下新建一个SubjectData类
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
@RestController
@Api(tags="课程分类管理")
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//添加课程分类
//获取上传过来的文件,把内容读取出来,就不用上传到服务器
@ApiOperation(value = "Excel批量导入")
@PostMapping("addSubject")
public ResultVo addSubject(MultipartFile file){
// 获取上传的excel文件 MultipartFile
subjectService.saveSubject(file,subjectService);
return ResultVo.ok();
}
}
public interface EduSubjectService extends IService<EduSubject> {
void saveSubject(MultipartFile file,EduSubjectService subjectService);
}
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file,EduSubjectService subjectService) {
try {
//文件输入流
InputStream in= file.getInputStream();
//调用方法进行读取
EasyExcel.read(in, SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();
}catch (Exception e){
throw new GuliException(20002,"添加课程分类失败");
}
}
}
在eduservice下新建一个listener包,SubjectExcelListener 类
package com.atguigu.eduservice.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.eduservice.entity.EduSubject;
import com.atguigu.eduservice.entity.excel.SubjectData;
import com.atguigu.eduservice.service.EduSubjectService;
import com.atguigu.servicebase.exceptionhandler.GuliException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
public EduSubjectService subjectService;
public SubjectExcelListener() {
}
//创建有参数构造,传递subjectService用于操作数据库
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if(subjectData==null){
throw new GuliException(20001,"文件数据为空");
}
//一行一行读取,每次读取有两个值,第一个值一级分类,第二个值二级分类
EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
if(existOneSubject==null){
//没有相同的一级分类就进行添加
existOneSubject=new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());//设置一级分类名称
subjectService.save(existOneSubject);
}
//获取一级分类id值
String pid=existOneSubject.getId();
//添加二级分类
//判断二级分类是否重复
EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
if(existTwoSubject==null){
//没有相同的一级分类就进行添加
existTwoSubject=new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());//设置二级分类名称
subjectService.save(existTwoSubject);
}
}
//判断一级分类不能重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id","0");
EduSubject oneSubject = subjectService.getOne(wrapper);
return oneSubject;
}
//判断二级分类不能重复添加
private EduSubject existTwoSubject(EduSubjectService subjectService,String name,String pid){
QueryWrapper<EduSubject> wrapper=new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject twoSubject = subjectService.getOne(wrapper);
return twoSubject;
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: {
title: '课程分类管理',
icon: 'example'
},
children: [{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/edu/subject/list'),
meta: {
title: '课程分类列表',
icon: 'table'
}
},
{
path: 'save',
name: '添加课程分类',
component: () => import('@/views/edu/subject/save'),
meta: {
title: '添加课程分类',
icon: 'tree'
}
},
]
},
js定义数据
<script>
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
importBtnDisabled: false, // 按钮是否禁用,
loading: false,
};
}
}
<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="'/static/01.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>
methods: {
//点击按钮上传文件到接口里面
submitUpload() {
this.importBtnDisabled = true;
this.loading = true;
// js: document.getElementById("upload").submit()
this.$refs.upload.submit();
},
//上传成功
fileUploadSuccess(response) {
//提示信息
this.loading = false;
this.$message({
type: "success",
message: "添加课程分类成功",
});
//跳转课程分类列表
//路由跳转
this.$router.push({ path: "/subject/list" });
},
//上传失败
fileUploadError() {
this.loading = false;
this.$message({
type: "error",
message: "添加课程分类失败",
});
},
},
<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="'/static/01.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,
};
},
created() {},
methods: {
//点击按钮上传文件到接口里面
submitUpload() {
this.importBtnDisabled = true;
this.loading = true;
// js: document.getElementById("upload").submit()
this.$refs.upload.submit();
},
//上传成功
fileUploadSuccess(response) {
//提示信息
this.loading = false;
this.$message({
type: "success",
message: "添加课程分类成功",
});
//跳转课程分类列表
//路由跳转
this.$router.push({ path: "/subject/list" });
},
//上传失败
fileUploadError() {
this.loading = false;
this.$message({
type: "error",
message: "添加课程分类失败",
});
},
},
};
</script>
@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;
}
@ApiOperation(value = "嵌套数据列表")
@GetMapping("/getAllSubject")
//课程分类列表(树形)
public ResultVo getAllSubject(){
//list集合泛型是一级分类,因为一级分类有他本身和二级分类的集合
List<OneSubject> list= subjectService.getAllOneTwoSubject();
return ResultVo.ok().data("list",list);
}
List<OneSubject> getAllOneTwoSubject();
@Override
public List<OneSubject> getAllOneTwoSubject() {
//查询所有一级分类 parentid=0
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id","0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
//查询所有二级分类 parentid!=0
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperOne.ne("parent_id","0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
//创建list集合,用于存放最终封装的数据
List<OneSubject> finalSubjectList=new ArrayList<>();
//封装一级分类
//查询出来所有的一级分类list集合遍历,得到每一级分类对象,获得每个一级分类对象值
//封装到要求的list集合里面
for (int i = 0; i < oneSubjectList.size(); i++) {
EduSubject eduSubject = oneSubjectList.get(i);
OneSubject oneSubject = new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
//把eduSubject值复制到对应的oneSubject对象里面,两个对象里面的属性相同对应的的自动赋值
BeanUtils.copyProperties(eduSubject,oneSubject);
//在一级分类循环遍历查询所有的二级分类
//创建list集合封装每个一级分类的二级分类
List<TwoSubject> twoFinalSubjectList=new ArrayList<>();
//遍历二级分类list集合
for (int j = 0; j < twoSubjectList.size(); j++) {
EduSubject tSubject = twoSubjectList.get(j);
//如果二级分类的parentid和一级分类的id一样,就把它加入到一级分类
if(tSubject.getParentId().equals(eduSubject.getId())){
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(tSubject,twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
//把一级下面所有的二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
finalSubjectList.add(oneSubject);
}
return finalSubjectList;
}
api/edu/subject.js
import request from '@/utils/request'
export default {
//1 课程分类列表
getSubjectList() {
return request({
url: '/eduservice/subject/getAllSubject',
method: 'get'
})
}
}
<template>
<div class="app-container">
<el-input
v-model="filterText"
placeholder="Filter keyword"
style="margin-bottom: 30px"
/>
<el-tree
ref="tree2"
:data="data2"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>
</div>
</template>
<script>
import subject from "@/api/edu/subject";
export default {
data() {
return {
filterText: "",
data2: [], //返回所有分类数据
defaultProps: {
children: "children",
label: "title",
},
};
},
created() {
this.getAllSubjectList();
},
watch: {
filterText(val) {
this.$refs.tree2.filter(val);
},
},
methods: {
getAllSubjectList() {
subject.getSubjectList().then((response) => {
this.data2 = response.data.list;
});
},
filterNode(value, data) {
if (!value) return true;
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1;
},
},
};
</script>
不区分大小写
filterNode(value, data) {
if (!value) return true;
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1;
},
因为课程基本信息和课程简介不在一张表上,
package com.atguigu.eduservice.entity.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class CourseInfoVo {
@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;
}
因为课程简介的id和课程信息的id是对应的,需要自己手动设置成课程信息的id不能自动生成
@ApiModelProperty(value = "课程ID")
@TableId(value = "id", type = IdType.INPUT)
private String id;
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
@Api(tags="课程管理")
public class EduCourseController {
@Autowired
private EduCourseService courseService;
//添加课程基本信息
@ApiOperation(value = "新增课程")
@PostMapping("/addCourseInfo")
public ResultVo addCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
String id = courseService.saveCourseInfo(courseInfoVo);
return ResultVo.ok().data("courseId",id);
}
}
String saveCourseInfo(CourseInfoVo courseInfoVo);
@Autowired
private EduCourseDescriptionService courseDescriptionService;
@Override
public String 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();
//向课程简介表添加课程介绍
EduCourseDescription CourseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,CourseDescription);
//设置描述id就是课程id
CourseDescription.setId(cid);
courseDescriptionService.save(CourseDescription);
return cid;
}
{
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: '添加课程',
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: 'EduCourseChapterEdit',
component: () => import('@/views/edu/course/chapter'),
meta: {
title: '编辑课程大纲',
noCache: true
},
hidden: true
},
{
path: 'publish/:id',
name: 'EduCoursePublishEdit',
component: () => import('@/views/edu/course/publish'),
meta: {
title: '发布课程',
noCache: true
},
hidden: true
}
]
},
import request from '@/utils/request'
export default {
//1 添加课程信息
addCourseInfo(courseInfo) {
return request({
url: '/eduservice/course/addCourseInfo',
method: 'post',
data:courseInfo
})
},
}
<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() {
console.log("info created");
},
methods: {
next() {
console.log("next");
this.$router.push({
path: "/course/chapter/" + response.data.courseId,
});
},
},
};
</script>
<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({ path: "/edu/course/info/1" });
},
next() {
console.log("next");
this.$router.push({ path: "/edu/course/publish/1" });
},
},
};
</script>
<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() {
console.log("publish created");
},
methods: {
previous() {
console.log("previous");
this.$router.push({ path: "/edu/course/chapter/1" });
},
publish() {
console.log("publish");
this.$router.push({ path: "/edu/course/list" });
},
},
};
</script>
<!-- 课程讲师 -->
<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>
api/edu/course.js
import request from '@/utils/request'
export default {
//1 添加课程信息
addCourseInfo(courseInfo) {
return request({
url: '/eduservice/course/addCourseInfo',
method: 'post',
data:courseInfo
})
},
//2 查询所有讲师
getListTeacher() {
return request({
url: '/eduservice/teacher/findAll',
method: 'get'
})
}
}
引入course
import course from "@/api/edu/course";
teacherList: [], //封装所有的讲师
//查询所有的讲师
getListTeacher() {
course.getListTeacher().then((response) => {
this.teacherList = response.data.items;
});
},
created() {
//初始化所有讲师
this.getListTeacher();
//初始化一级分类
this.getOneSubject();
}
<!-- 所属分类 TODO -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类"
@change="subjectLevelOneChanged"
>
<el-option
v-for="subject in subjectOneList"
:key="subject.id"
:label="subject.title"
:value="subject.id"
/>
</el-select>
定义在data中
subjectOneList: [], //一级分类
subjectTwoList: [], //二级分类
表单初始化时获取一级分类嵌套列表,引入subject api
import subject from "@/api/edu/subject";
//查询所有的一级分类
getOneSubject() {
subject.getSubjectList().then((response) => {
this.subjectOneList = response.data.list;
});
<!-- 二级分类 -->
<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>
</el-form-item>
在一级分类的组件中注册change事件
<el-select @change="subjectLevelOneChanged" ......
//点击某个一级分类,触发change,显示对应二级分类
subjectLevelOneChanged(value) {
//value就是一级分类id值
//遍历所有的分类,包含一级和二级
for (var i = 0; i < this.subjectOneList.length; i++) {
//每个一级分类
var oneSubject = this.subjectOneList[i];
//判断:所有一级分类id 和 点击一级分类id是否一样
if (value === oneSubject.id) {
//从一级分类获取里面所有的二级分类
this.subjectTwoList = oneSubject.children;
}
}
//把二级分类id值清空
this.courseInfo.subjectId = "";
},
<!-- 课程封面-->
<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>
BASE_API: process.env.BASE_API, // 接口API地址
//上传封面成功调用的方法
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;
},
Tinymce是一个传统javascript插件,默认不能用于Vue.js因此需要做一些特殊的整合步骤
将脚本库复制到项目的static目录下(在vue-element-admin-master的static路径下)
在 guli-admin/build/webpack.dev.conf.js 中添加配置
使在html页面中可是使用这里定义的BASE_URL变量
templateParameters: {
BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}
在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进行了封装,下面我们将它引入到我们的课程信息页面
课程信息组件中引入 Tinymce
import Tinymce from '@/components/Tinymce'
export default {
components: { Tinymce },
......
}
<!-- 课程简介-->
<el-form-item label="课程简介">
<tinymce :height="300" v-model="courseInfo.description" />
</el-form-item>
在info.vue文件的最后添加如下代码,调整上传图片按钮的高度
<style scoped>
.tinymce-container {
line-height: 29px;
}
</style>
在service_edu下面的entity下新建一个chapter包
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "章节信息")
public class ChapterVo {
private String id;
private String title;
//小结
private List<VideoVo> children = new ArrayList<>();
}
@ApiModel(value = "小结信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VideoVo {
private String id;
private String title;
}
public interface EduChapterService extends IService<EduChapter> {
List<ChapterVo> getChapterVideoByCourseId(String courseId);
}
@Autowired
private EduVideoService videoService;//注入小节service
@Override
public List<ChapterVo> getChapterVideoByCourseId(String courseId) {
//根据课程id查询课程里面所有章节
QueryWrapper<EduChapter> eduChapterQueryWrapper = new QueryWrapper<EduChapter>();
eduChapterQueryWrapper.eq("course_id",courseId);
List<EduChapter> eduChapterList = baseMapper.selectList(eduChapterQueryWrapper);
//根据课程id查询课程里面所有的小节
QueryWrapper<EduVideo> eduVideoQueryWrapper = new QueryWrapper<>();
eduVideoQueryWrapper.eq("course_id",courseId);
List<EduVideo> eduVideoList = videoService.list(eduVideoQueryWrapper);
List<ChapterVo> finalList=new ArrayList<>();
//遍历查询章节list集合进行封装
for (int i = 0; i < eduChapterList.size(); i++) {
//每个章节
ChapterVo chapterVo = new ChapterVo();
EduChapter eduChapter = eduChapterList.get(i);
BeanUtils.copyProperties(eduChapter,chapterVo);
//创建集合,用于封装章节的小节
List<VideoVo> videoList=new ArrayList<>();
for (int j = 0; j < eduVideoList.size(); j++) {
EduVideo eduVideo = eduVideoList.get(j);
//判断 小节里面的chapterid和章节里面的id是否一样
if(eduVideo.getChapterId().equals(eduChapter.getId())){
//进行封装
VideoVo videoVo=new VideoVo();
BeanUtils.copyProperties(eduVideo,videoVo);
//放到封装到小节集合
videoList.add(videoVo);
}
}
chapterVo.setChildren(videoList);
finalList.add(chapterVo);
}
//遍历查询小结list集合进行封装
return finalList;
}
import request from '@/utils/request'
export default {
//1 根据课程id获取章节和小节数据列表
getAllChapterVideo(courseId) {
return request({
url: '/eduservice/chapter/getChapterVideo/'+courseId,
method: 'get'
})
},
}
courseId: '', // 所属课程
chapterNestedList: [] // 章节嵌套课时列表
//根据课程id查询章节和小节
getChapterVideo() {
chapter.getAllChapterVideo(this.courseId).then((response) => {
this.chapterVideoList = response.data.allChapterVideo;
});
},
created() {
//获取路由的id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id;
//根据课程id查询章节和小节
this.getChapterVideo();
}
},
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
<!-- 章节 -->
<ul class="chanpterList">
<li v-for="chapter in chapterVideoList" :key="chapter.id">
<p>
{{ chapter.title }}
<span class="acts">
<el-button style="" type="text" @click="openVideo(chapter.id)"
>添加小节</el-button
>
<el-button style="" type="text" @click="openEditChatper(chapter.id)"
>编辑</el-button
>
<el-button type="text" @click="removeChapter(chapter.id)"
>删除</el-button
>
</span>
</p>
<!-- 视频 -->
<ul class="chanpterList videoList">
<li v-for="video in chapter.children" :key="video.id">
<p>
{{ video.title }}
<span class="acts">
<el-button style="" type="text">编辑</el-button>
<el-button type="text" @click="removeVideo(video.id)"
>删除</el-button
>
</span>
</p>
</li>
</ul>
</li>
</ul>
<div>
<el-button @click="previous">上一步</el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next"
>下一步</el-button
>
</div>
将样式的定义放在页面的最后
scope表示这里定义的样式只在当前页面范围内生效,不会污染到其他的页面
<style scoped>
.chanpterList {
position: relative;
list-style: none;
margin: 0;
padding: 0;
}
.chanpterList li {
position: relative;
}
.chanpterList p {
float: left;
font-size: 20px;
margin: 10px 0;
padding: 10px;
height: 70px;
line-height: 50px;
width: 100%;
border: 1px solid #ddd;
}
.chanpterList .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 dotted #ddd;
}
</style>
//根据课程id查询课程基本信息
@ApiOperation(value = "根据ID查询课程")
@GetMapping("getCourseInfo/{courseId}")
public ResultVo getCourseInfo(@PathVariable String courseId){
CourseInfoVo courseInfoVo=courseService.getCourseInfo(courseId);
return ResultVo.ok().data("courseInfoVo",courseInfoVo);
}
//通过id查询课程信息
@Override
public CourseInfoVo getCourseInfo(String courseId) {
//查询课程表
EduCourse eduCourse=baseMapper.selectById(courseId);
CourseInfoVo courseInfoVo = new CourseInfoVo();
BeanUtils.copyProperties(eduCourse,courseInfoVo);
//查询描述表
EduCourseDescription courseDescription=courseDescriptionService.getById(courseId);
BeanUtils.copyProperties(courseDescription,courseInfoVo);
return courseInfoVo;
}
api/edu/course.js
//根据课程id查询课程基本信息
getCourseInfoId(id) {
return request({
url: '/eduservice/course/getCourseInfo/' + id,
method: 'get'
})
},
created() {
//获取路由id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id;
//调用根据id查询课程的方法
this.getInfo();
} else {
//初始化所有讲师
this.getListTeacher();
//初始化一级分类
this.getOneSubject();
}
},
//根据课程id查询
getInfo() {
course.getCourseInfoId(this.courseId).then((response) => {
//在courseInfo课程基本信息,包含 一级分类id 和 二级分类id
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 oneSubject = this.subjectOneList[i];
//比较当前courseInfo里面一级分类id和所有的一级分类id
if (this.courseInfo.subjectParentId == oneSubject.id) {
//获取一级分类所有的二级分类
this.subjectTwoList = oneSubject.children;
}
}
});
//初始化所有讲师
this.getListTeacher();
});
},
public void updateCourseInfo(CourseInfoVo courseInfoVo) {
//修改课程表
EduCourse eduCourse=new EduCourse();
BeanUtils.copyProperties(courseInfoVo,eduCourse);
int i = baseMapper.updateById(eduCourse);
if(i==0){
throw new GuliException(20001,"修改课程信息失败");
}
EduCourseDescription courseDescription = new EduCourseDescription();
BeanUtils.copyProperties(courseInfoVo,courseDescription);
courseDescriptionService.updateById(courseDescription);
}
@ApiOperation(value = "更新课程")
@PostMapping("/updateCourseInfo")
public ResultVo updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
courseService.updateCourseInfo(courseInfoVo);
return ResultVo.ok();
}
//修改课程信息
updateCourseInfo(courseInfo) {
return request({
url: '/eduservice/course/updateCourseInfo',
method: 'post',
data: courseInfo
})
}
//修改课程
updateCourse() {
course.updateCourseInfo(this.courseInfo).then((response) => {
//提示
this.$message({
type: "success",
message: "修改课程信息成功!",
});
//跳转到第二步
this.$router.push({ path: "/course/chapter/" + this.courseId });
});
},
<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 -->
<el-form-item label="课程分类">
<el-select
v-model="courseInfo.subjectParentId"
placeholder="一级分类"
@change="subjectLevelOneChanged"
>
<el-option
v-for="subject in subjectOneList"
:key="subject.id"
:label="subject.title"
:value="subject.id"
/>
</el-select>
<!-- 二级分类 -->
<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>
</el-form-item>
<!-- 课程讲师 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>
<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="课程简介">
<tinymce :height="300" v-model="courseInfo.description" />
</el-form-item>
<!-- 课程封面 TODO -->
<!-- 课程封面-->
<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>
</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-item>
<el-button
:disabled="saveBtnDisabled"
type="primary"
@click="saveOrUpdate"
>保存并下一步</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import course from "@/api/edu/course";
import subject from "@/api/edu/subject";
import Tinymce from "@/components/Tinymce"; //引入组件
export default {
//声明组件
components: { Tinymce },
data() {
return {
saveBtnDisabled: false,
courseInfo: {
title: "",
subjectId: "", //二级分类id
subjectParentId: "", //一级分类id
teacherId: "",
lessonNum: 0,
description: "",
cover: "/static/01.jpg",
price: 0,
},
courseId: "",
BASE_API: process.env.BASE_API, // 接口API地址
teacherList: [], //封装所有的讲师
subjectOneList: [], //一级分类
subjectTwoList: [], //二级分类
};
},
created() {
//获取路由id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id;
//调用根据id查询课程的方法
this.getInfo();
} else {
//初始化所有讲师
this.getListTeacher();
//初始化一级分类
this.getOneSubject();
}
},
methods: {
//根据课程id查询
getInfo() {
course.getCourseInfoId(this.courseId).then((response) => {
//在courseInfo课程基本信息,包含 一级分类id 和 二级分类id
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 oneSubject = this.subjectOneList[i];
//比较当前courseInfo里面一级分类id和所有的一级分类id
if (this.courseInfo.subjectParentId == oneSubject.id) {
//获取一级分类所有的二级分类
this.subjectTwoList = oneSubject.children;
}
}
});
//初始化所有讲师
this.getListTeacher();
});
},
//上传封面成功调用的方法
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;
},
//点击某个一级分类,触发change,显示对应二级分类
subjectLevelOneChanged(value) {
//value就是一级分类id值
//遍历所有的分类,包含一级和二级
for (var i = 0; i < this.subjectOneList.length; i++) {
//每个一级分类
var oneSubject = this.subjectOneList[i];
//判断:所有一级分类id 和 点击一级分类id是否一样
if (value === oneSubject.id) {
//从一级分类获取里面所有的二级分类
this.subjectTwoList = oneSubject.children;
//把二级分类id值清空
this.courseInfo.subjectId = "";
}
}
},
//查询所有的一级分类
getOneSubject() {
subject.getSubjectList().then((response) => {
this.subjectOneList = response.data.list;
});
},
//查询所有的讲师
getListTeacher() {
course.getListTeacher().then((response) => {
this.teacherList = response.data.items;
});
},
//添加课程
addCourse() {
course.addCourseInfo(this.courseInfo).then((response) => {
//提示
this.$message({
type: "success",
message: "添加课程信息成功!",
});
//跳转到第二步
this.$router.push({
path: "/course/chapter/" + response.data.courseId,
});
});
},
//修改课程
updateCourse() {
course.updateCourseInfo(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();
}
},
},
};
</script>
<style scoped>
.tinymce-container {
line-height: 29px;
}
</style>
//添加章节
@ApiOperation(value = "新增章节")
@PostMapping("addChapter")
@ApiParam(name = "eduChapter", value = "章节对象", required = true)
public ResultVo addChapter(@RequestBody EduChapter eduChapter){
chapterService.save(eduChapter);
return ResultVo.ok();
}
//根据章节id查询
@ApiOperation(value = "根据ID查询章节")
@ApiParam(name = "chapterId", value = "章节ID", required = true)
@GetMapping("getChapterInfo/{chapterId}")
public ResultVo getChapterInfo(@PathVariable String chapterId){
EduChapter eduChapter= chapterService.getById(chapterId);
return ResultVo.ok().data("chapter",eduChapter);
}
//修改章节
@ApiOperation(value = "根据ID修改章节")
@ApiParam(name = "eduChapter", value = "章节对象", required = true)
@GetMapping("updateChapter")
public ResultVo updateChapter(@RequestBody EduChapter eduChapter){
chapterService.updateById(eduChapter);
return ResultVo.ok();
}
//删除章节
@ApiOperation(value = "根据ID删除章节")
@ApiParam(name = "chapterId", value = "章节ID", required = true)
@DeleteMapping("{chapterId}")
public ResultVo deleteChapter(@PathVariable String chapterId){
boolean flag=chapterService.deleteChapter(chapterId);
if(flag){
return ResultVo.ok();
}
else{
return ResultVo.error();
}
}
boolean deleteChapter(String chapterId);
//删除章节
@Override
public boolean deleteChapter(String chapterId) {
//根据chapterId章节id查询小节表,如果查询数据,不进行删除
QueryWrapper<EduVideo> QueryWrapper = new QueryWrapper<>();
QueryWrapper.eq("chapter_id",chapterId);
int count = videoService.count(QueryWrapper);
//判断
if(count>0){//能查询出来小节,不进行删除
throw new GuliException(20001,"不能删除");
}else { //没有查询到数据,进行删除
//删除章节
int result=baseMapper.deleteById(chapterId);
return result>0;
}
}
//添加章节
addChapter(chapter) {
return request({
url: '/eduservice/chapter/addChapter',
method: 'post',
data: chapter
})
},
//根据id查询章节
getChapter(chapterId) {
return request({
url: '/eduservice/chapter/getChapterInfo/'+chapterId,
method: 'get'
})
},
//修改章节
updateChapter(chapter) {
return request({
url: '/eduservice/chapter/updateChapter',
method: 'post',
data: chapter
})
},
//删除章节
deleteChapter(chapterId) {
return request({
url: '/eduservice/chapter/'+chapterId,
method: 'delete'
})
},
chapter: {
//封装章节数据
title: "",
sort: 0,
},
dialogChapterFormVisible: false, //章节弹框
<el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
<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"
controls-position="right"
/>
el-form-item>
el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogChapterFormVisible = false">取 消el-button>
<el-button type="primary" @click="saveOrUpdate">确 定el-button>
div>
el-dialog>
<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
//添加章节
addChapter() {
//设置课程id到chapter对象里面
this.chapter.courseId = this.courseId;
chapter.addChapter(this.chapter).then((response) => {
//关闭弹框
this.dialogChapterFormVisible = false;
//提示
this.$message({
type: "success",
message: "添加章节成功!",
});
//刷新页面
this.getChapterVideo();
});
},
<el-button style="" type="text" @click="openEditChatper(chapter.id)">编辑</el-button>
//修改章节弹框数据回显
openEditChatper(chapterId) {
//弹框
this.dialogChapterFormVisible = true;
//调用接口
chapter.getChapter(chapterId).then((response) => {
this.chapter = response.data.chapter;
});
},
//修改章节的方法
updateChapter() {
chapter.updateChapter(this.chapter).then((response) => {
//关闭弹框
this.dialogChapterFormVisible = false;
//提示
this.$message({
type: "success",
message: "修改章节成功!",
});
//刷新页面
this.getChapterVideo();
});
},
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
//删除章节
removeChapter(chapterId) {
this.$confirm("此操作将删除章节, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
//点击确定,执行then方法
//调用删除的方法
chapter.deleteChapter(chapterId).then((response) => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除成功!",
});
//刷新页面
this.getChapterVideo();
});
}); //点击取消,执行catch方法
},
//弹出添加章节页面
openChapterDialog() {
//弹框
this.dialogChapterFormVisible = true;
//表单数据清空
this.chapter.title = "";
this.chapter.sort = 0;
},
但是我偷懒了没有使用这个对象,直接用的EduVideo
package com.atguigu.eduservice.entity.video;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel(value = "课时基本信息", description = "编辑课时基本信息的表单对象")
@Data
public class VideoInfoVo {
@ApiModelProperty(value = "视频ID")
private String id;
@ApiModelProperty(value = "节点名称")
private String title;
@ApiModelProperty(value = "课程ID")
private String courseId;
@ApiModelProperty(value = "章节ID")
private String chapterId;
@ApiModelProperty(value = "视频资源")
private String videoSourceId;
@ApiModelProperty(value = "显示排序")
private Integer sort;
@ApiModelProperty(value = "是否可以试听:0默认 1免费")
private Boolean free;
}
//添加小节
@ApiOperation(value = "新增课时")
@PostMapping("/addVideo")
public ResultVo addVideo(@RequestBody EduVideo eduVideo){
boolean save = videoService.save(eduVideo);
if(save){
return ResultVo.ok();
}
else{
return ResultVo.error();
}
}
//根据ID查询课时
@ApiParam(name = "id", value = "课时ID", required = true)
@ApiOperation(value = "根据ID查询课时")
@GetMapping("getVideoInfo/{id}")
public ResultVo getVideoInfo(@PathVariable String id){
EduVideo video = videoService.getById(id);
return ResultVo.ok().data("video",video);
}
//修改小节
@ApiOperation(value = "更新课时")
@PostMapping("updateVideo")
public ResultVo updateCourseInfo(@RequestBody EduVideo eduVideo){
boolean update = videoService.updateById(eduVideo);
if(update){
return ResultVo.ok();
}
else{
return ResultVo.error();
}
}
//删除小节
@PostMapping("{id}")
@ApiOperation(value = "根据ID删除课时")
@ApiParam(name = "id", value = "课时ID", required = true)
public ResultVo deleteVideo(@PathVariable String id){
boolean b = videoService.removeById(id);
if(b){
return ResultVo.ok();
}
else{
return ResultVo.error();
}
}
import request from '@/utils/request'
export default {
//添加小节
addVideo(video) {
return request({
url: '/eduservice/video/addVideo',
method: 'post',
data: video
})
},
//获取小节信息
getVideoInfo(id) {
return request({
url: '/eduservice/video/getVideoInfo/' + id,
method: 'get',
})
},
updateVideoInfo(video) {
return request({
url: '/eduservice/video/updateVideo',
method: 'post',
data: video
})
},
//删除小节
deleteVideo(id) {
return request({
url: '/eduservice/video/' + id,
method: 'post'
})
},
}
video: {
title: "",
sort: 0,
free: 0,
videoSourceId: "",
},
dialogChapterFormVisible: false, //章节弹框
dialogVideoFormVisible: false, //小节弹框
<el-button style="" type="text" @click="openVideo(chapter.id)" >添加小节</el-button>
<el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
<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"
controls-position="right"
/>
el-form-item>
<el-form-item label="是否免费">
<el-radio-group v-model="video.free">
<el-radio :label="true">免费el-radio>
<el-radio :label="false">默认el-radio>
el-radio-group>
el-form-item>
<el-form-item label="上传视频">
el-form-item>
el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVideoFormVisible = false">取 消el-button>
<el-button type="primary" @click="saveOrUpdateVideo">确 定el-button>
div>
el-dialog>
import video1 from "@/api/edu/video";
//添加小节弹框的方法
openVideo(chapterId) {
//弹框
this.dialogVideoFormVisible = true;
//设置章节id
this.video.chapterId = chapterId;
},
//添加小节
addVideo() {
//设置课程id
this.video.courseId = this.courseId;
video1.addVideo(this.video).then((response) => {
//关闭弹框
this.dialogVideoFormVisible = false;
//提示
this.$message({
type: "success",
message: "添加小节成功!",
});
//刷新页面
this.getChapterVideo();
});
},
openEditVideo(videoId) {
//弹框
this.dialogVideoFormVisible = true;
//调用接口
video1.getVideoInfo(videoId).then((response) => {
this.video = response.data.video;
});
},
updateDataVideo() {
video1.updateVideoInfo(this.video).then((response) => {
//关闭弹框
this.dialogChapterFormVisible = false;
//提示
this.$message({
type: "success",
message: "修改小节成功!",
});
//刷新页面
this.getChapterVideo();
});
},
saveOrUpdateVideo() {
// this.saveVideoBtnDisabled = true;
if (!this.video.id) {
this.addVideo();
} else {
this.updateDataVideo();
}
},
<el-button style="" type="text" @click="openEditVideo(video.id)">编辑</el-button>
openEditVideo(videoId) {
//弹框
this.dialogVideoFormVisible = true;
//调用接口
video1.getVideoInfo(videoId).then((response) => {
this.video = response.data.video;
});
},
updateDataVideo() {
video1.updateVideoInfo(this.video).then((response) => {
//关闭弹框
this.dialogChapterFormVisible = false;
//提示
this.$message({
type: "success",
message: "修改小节成功!",
});
//刷新页面
this.getChapterVideo();
});
},
<el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
//删除小节
removeVideo(id) {
this.$confirm("此操作将删除小节, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
//点击确定,执行then方法
//调用删除的方法
video1.deleteVideo(id).then((response) => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除小节成功!",
});
//刷新页面
this.getChapterVideo();
});
}); //点击取消,执行catch方法
},
加了nginx后,put和delete请求都会出现跨域问题,需要修改nginx配置,由于我没有解决,所以我把put和delete改成了post请求,也有可能是我用的最新版的nginx的原因
@ApiModel(value = "课程发布信息")
@Data
public class CoursePublishVo {
private String id;
private String title;
private String cover;
private Integer lessonNum;
private String subjectLevelOne;
private String subjectLevelTwo;
private String teacherName;
private String price;//只用于显示
}
public interface EduCourseMapper extends BaseMapper<EduCourse> {
public CoursePublishVo getPublishCourseInfo(String courseId);
}
<select id="getPublishCourseInfo" resultType="com.atguigu.eduservice.entity.vo.CoursePublishVo">
SELECT ec.id,ec.`title`,ec.`price`,ec.`lesson_num` AS lessonNum,ec.`cover`,
et.`name` AS teacherName,
es1.`title` AS subjectLevelOne,
es2.title AS subjectLevelTwo
FROM edu_course ec LEFT JOIN edu_course_description ecd ON ec.id=ecd.id
LEFT JOIN edu_teacher et ON ec.`teacher_id`=et.`id`
LEFT JOIN edu_subject es1 ON ec.`subject_parent_id`=es1.id
LEFT JOIN edu_subject es2 ON ec.subject_id=es2.`id`
WHERE ec.`id`=#{courseId}
select>
@Override
public CoursePublishVo publishCourseInfo(String id) {
CoursePublishVo publishCourseInfo = baseMapper.getPublishCourseInfo(id);
return publishCourseInfo;
}
//根据课程id查询课程确认信息
@GetMapping("/getPublishCourseInfo/{id}")
public ResultVo getPublishCourseInfo(@PathVariable String id){
CoursePublishVo coursePublishVo= courseService.publishCourseInfo(id);
return ResultVo.ok().data("publishCourse",coursePublishVo);
}
AbstractHandlerExceptionResolver.java:194 |org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver |Resolved exception caused by handler execution: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.guli.edu.mapper.CourseMapper.getCoursePublishVoById
dao层编译后只有class文件,没有mapper.xml,因为maven工程在默认情况下src/main/java目录下的所有资源文件是不发布到target目录下的,
在service的pom.xml中
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
重新打包项目会发现target目录下出现了xml文件夹
如果target目录里没有生成需要把target目录删除掉重新编译
在eduservice的配置文件中添加
mybatis-plus:
mapper-locations: classpath:com/atguigu/eduservice/mapper/xml/*.xml
//课程最终发布
//修改课程状态
@PostMapping("publishCourse/{id}")
public ResultVo publishCourse(@PathVariable String id){
EduCourse eduCourse = new EduCourse();
eduCourse.setId(id);
eduCourse.setStatus("Normal");//设置课程发布状态
boolean b = courseService.updateById(eduCourse);
if (b){
return ResultVo.ok();
}
else {
return ResultVo.error();
}
}
分析这个页面一共有两个远程方法:一个是根基课程id获取课程基本预览信息,第二个是发布课程
//课程确认信息显示
getPublihCourseInfo(id) {
return request({
url: '/eduservice/course/getPublishCourseInfo/' + id,
method: 'get'
})
},
//课程最终发布
publihCourse(id) {
return request({
url: '/eduservice/course/publishCourse/' + id,
method: 'post'
})
},
data() {
return {
saveBtnDisabled: false, // 保存按钮是否禁用
courseId: "",
coursePublish: {},
};
},
previous() {
//console.log("previous");
this.$router.push({ path: "/course/chapter/" + this.courseId });
},
publish() {
course.publihCourse(this.courseId).then((response) => {
//提示
this.$message({
type: "success",
message: "课程发布成功!",
});
//跳转课程列表页面
this.$router.push({ path: "/course/list" });
});
},
created() {
//获取路由课程id值
if (this.$route.params && this.$route.params.id) {
this.courseId = this.$route.params.id;
//调用接口方法根据课程id查询
this.getCoursePublishId();
}
},
//根据课程id查询
getCoursePublishId() {
course.getPublihCourseInfo(this.courseId).then((response) => {
this.coursePublish = response.data.publishCourse;
});
},
<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>
<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.subjectLevelOne }} —
{{ coursePublish.subjectLevelTwo }}span
>
p>
<p>课程讲师:{{ coursePublish.teacherName }}p>
<h3 class="red">¥{{ coursePublish.price }}h3>
div>
div>
<div>
<el-button @click="previous">返回修改el-button>
<el-button :disabled="saveBtnDisabled" type="primary" @click="publish"
>发布课程el-button
>
div>
div>
template>
CourseQuery
@ApiModel(value = "Course查询对象", description = "课程查询对象封装")
@Data
public class CourseQueryVo {
@ApiModelProperty(value = "课程名称")
private String title;
@ApiModelProperty(value = "课程状态 Draft未发布 Normal已发布")
private String status;
@ApiModelProperty(value = "讲师id")
private String teacherId;
@ApiModelProperty(value = "一级类别id")
private String subjectParentId;
@ApiModelProperty(value = "二级类别id")
private String subjectId;
}
void pageQuery(Page<EduCourse> eduCoursePage, CourseQueryVo courseQuery);
@Override
public void pageQuery(Page<EduCourse> eduCoursePage, CourseQueryVo courseQuery) {
QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("gmt_create");
if(courseQuery==null){
this.page(eduCoursePage,queryWrapper);
return;
}
String title = courseQuery.getTitle();
String teacherId = courseQuery.getTeacherId();
String subjectId = courseQuery.getSubjectId();
String subjectParentId = courseQuery.getSubjectParentId();
String status = courseQuery.getStatus();
if(!StringUtils.isEmpty(title)){
queryWrapper.like("title",title);
}
if(!StringUtils.isEmpty(teacherId)){
queryWrapper.eq("teacher_id",teacherId);
}
if(!StringUtils.isEmpty(status)){
queryWrapper.eq("status",status);
}
if(!StringUtils.isEmpty(subjectId)){
queryWrapper.eq("subject_id", subjectId);
}
if(!StringUtils.isEmpty(subjectParentId)){
queryWrapper.eq("subject_parent_id", subjectParentId);
}
this.page(eduCoursePage,queryWrapper);
}
public ResultVo getCourseList(@PathVariable long page,@PathVariable long limit,@RequestBody(required = false) CourseQueryVo courseQuery){
Page<EduCourse> eduCoursePage = new Page<>(page, limit);
courseService.pageQuery(eduCoursePage,courseQuery);
List<EduCourse> list = eduCoursePage.getRecords();
long total = eduCoursePage.getTotal();
return ResultVo.ok().data("list",list).data("rows",total);
}
<template>
<div class="app-container">
课程列表
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-input v-model="courseQuery.title" placeholder="课程名称" />
el-form-item>
<el-form-item>
<el-select
v-model="courseQuery.status"
clearable
placeholder="课程状态"
>
<el-option value="Normal" label="已发布" />
<el-option value="Draft" label="未发布" />
el-select>
el-form-item>
<el-form-item>
<el-input v-model="courseQuery.teacherId" placeholder="教师id" />
el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getList()"
>查询el-button
>
<el-button type="default" @click="resetData()">清空el-button>
el-form>
<el-table :data="list" border fit highlight-current-row>
<el-table-column label="序号" width="70" align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
template>
el-table-column>
<el-table-column prop="title" label="课程名称" align="center" />
<el-table-column label="课程状态" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.status === "Normal" ? "已发布" : "未发布" }}
template>
el-table-column>
<el-table-column
prop="lessonNum"
label="课时数"
width="60"
align="center"
/>
<el-table-column prop="gmtCreate" label="添加时间" width="160" />
<el-table-column
prop="viewCount"
label="浏览数量"
width="60"
align="center"
/>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<router-link :to="'/course/info/' + scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit"
>编辑课程基本信息el-button
>
router-link>
<router-link :to="'/course/chapter/' + scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit"
>编辑课程大纲息el-button
>
router-link>
<el-button
type="danger"
size="mini"
icon="el-icon-delete"
@click="removeDataById(scope.row.id)"
>删除课程信息el-button
>
template>
el-table-column>
el-table>
<el-pagination
:current-page="page"
:page-size="limit"
:total="total"
style="padding: 30px 0; text-align: center"
layout="total, prev, pager, next, jumper"
@current-change="getList"
/>
div>
template>
<script>
//引入调用teacher.js文件
import course from "@/api/edu/course";
export default {
//写核心代码位置
// data:{
// },
data() {
//定义变量和初始值
return {
list: null, //查询之后接口返回集合
page: 1, //当前页
limit: 10, //每页记录数
total: 0, //总记录数
courseQuery: {}, //条件封装对象
};
},
created() {
//页面渲染之前执行,一般调用methods定义的方法
//调用
this.getList();
},
methods: {
//创建具体的方法,调用teacher.js定义的方法
//讲师列表的方法
getList(page = 1) {
this.page = page;
course
.getListCourse(this.page, this.limit, this.courseQuery)
.then((response) => {
//请求成功
//response接口返回的数据
this.list = response.data.list;
this.total = response.data.rows;
});
},
resetData() {
//清空的方法
//表单输入项数据清空
this.courseQuery = {};
//查询所有讲师数据
this.getList();
},
removeDataById(id) {
this.$confirm(
"此操作将永久删除该课程,以及该课程下的章节和视频,是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
//点击确定,执行then方法
//调用删除的方法
course.deleteCourseById(id).then((response) => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除成功!",
});
//回到列表页面
this.getList(this.page);
});
}); //点击取消,执行catch方法
},
},
};
script>
//删除课程
@ApiOperation(value = "根据ID删除课程")
@PostMapping("deleteCourse/{courseId}")
public ResultVo deleteCourse(@PathVariable String courseId){
boolean remove = courseService.removeCourse(courseId);
if(remove){
return ResultVo.ok();
}
else{
return ResultVo.error();
}
}
如果用户确定删除,则首先删除video记录,然后删除chapter记录,最后删除Course记录
接口
void removeVideoByCourseId(String courseId);
实现
@Override
public void removeVideoByCourseId(String courseId) {
QueryWrapper<EduVideo> QueryWrapper = new QueryWrapper<>();
QueryWrapper.eq("course_id",courseId);
baseMapper.delete(QueryWrapper);
}
接口
void removeChapterByCourseId(String courseId);
实现
@Override
public void removeChapterByCourseId(String courseId) {
QueryWrapper<EduChapter> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("course_id",courseId);
baseMapper.delete(queryWrapper);
}
注意这里传递的是课程id所有删除小节和章节时不能直接通过removeById的方式删除
@Override
public boolean removeCourse(String courseId) {
//根据课程id删除小节
eduVideoService.removeVideoByCourseId(courseId);
//根据课程id删除章节
chapterService.removeChapterByCourseId(courseId);
//根据课程id删除描述
courseDescriptionService.removeById(courseId);
//根据课程id删除课程本身
int i = baseMapper.deleteById(courseId);
if(i==0){
throw new GuliException(20001,"删除失败");
}
return true;
}
course.js中添加删除方法
deleteCourseById(id) {
return request({
url: '/eduservice/course/deleteCourse/' + id,
method: 'post'
})
}
<el-button type="text" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
removeDataById(id) {
this.$confirm("此操作将永久删除该课程,以及该课程下的章节和视频,是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
//点击确定,执行then方法
//调用删除的方法
course.deleteCourseById(id).then((response) => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除成功!",
});
//回到列表页面
this.getList(this.page);
});
}); //点击取消,执行catch方法
},