// src\router\index.js
{
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' }
},
]
},
// src\views\edu\subject\save.vue
<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/data.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">{{ fileUploadBtnText }}el-button>
el-upload>
el-form-item>
el-form>
div>
template>
<script>
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
fileUploadBtnText: '上传到服务器', // 按钮文字
importBtnDisabled: false, // 按钮是否禁用,
loading: false
}
},
created() {
},
methods: {
// 点击按钮上传文件到接口里面
submitUpload() {
this.importBtnDisabled = true
this.loading = true
this.$refs.upload.submit()
},
// 上传成功
fileUploadSuccess() {
// 提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
},
// 上传失败
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;
}
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
// 添加课程分类
// 获取上传过来的文件,把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
// 上传过来excel文件
subjectService.saveSubject(file, subjectService);
return R.ok();
}
// 课程分类列表(树形)
@GetMapping("getAllSubject")
public R getAllSubject() {
// list集合中的泛型是一级分类
List<OneSubject> list = subjectService.getAllOneTwoSubject();
return R.ok().data("list", list);
}
}
// 接口
public interface EduSubjectService extends IService<EduSubject> {
// 添加课程分类
void saveSubject(MultipartFile file, EduSubjectService subjectService);
// 课程分类列表
List<OneSubject> getAllOneTwoSubject();
}
// 实现类
@Service // 把对象创建交给Spring管理
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) {
e.printStackTrace();
}
}
@Override
public List<OneSubject> getAllOneTwoSubject() {
// 1. 查询所有一级分类 parentid = 0
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id", "0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
// this.list(wrapperOne);
// 2. 查询所有二级分类 parentid != 0
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id", "0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
// 创建List集合,用于存储最终封装数据
List<OneSubject> finalSubjectList = new ArrayList<>();
// 3. 封装一级分类
// 查询出来所有的一级分类List集合遍历,得到每个一级分类对象,获取每个一级分类对象值
// 封装到要求的list集合里面 List finalSubjectList
for(int i = 0; i < oneSubjectList.size(); i++) {
// 得到oneSubjectList 每个 eduSubject 对象
EduSubject eduSubject = oneSubjectList.get(i);
// 把eduSubject里面值获取出来,放到OneSubject对象里面
OneSubject oneSubject = new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
// eduSubject的值复制到对应oneSubject对象里面
BeanUtils.copyProperties(eduSubject, oneSubject);
// 多个OneSubject放到finalSubjectList里面
finalSubjectList.add(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())) {
// 把tSubject值复制到TwoSubject里面,放到twoFinalSubjectList里面
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(tSubject, twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
// 把一级下面所有二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}
// 4. 封装二级分类
return finalSubjectList;
}
}
// src\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>
后台api
@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则可免费观看")
private BigDecimal price;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "课程简介")
private String description;
}
@ApiModelProperty(value = "课程ID")
@TableId(value = "id", type = IdType.INPUT)
private String id;
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService courseService;
// 添加课程基本信息的方法
@PostMapping("addCourseInfo")
public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
courseService.saveCourseInfo(courseInfoVo);
return R.ok();
}
}
// 接口
public interface EduCourseService extends IService<EduCourse> {
void saveCourseInfo(CourseInfoVo courseInfoVo);
}
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
private EduCourseDescriptionService courseDescriptionService;
// 添加课程基本信息的方法
@Override
public void saveCourseInfo(CourseInfoVo courseInfoVo) {
// 1.向课程表添加课程基本信息
// courseInfoVo 对象转换为 eduCourse 对象
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo, eduCourse);
int insert = baseMapper.insert(eduCourse);
if(insert == 0) {
// 添加失败
throw new LemonException(20001,"添加课程信息失败");
}
// 获取添加之后课程id
String cid = eduCourse.getId();
// 2.向课程简介表添加课程简介
EduCourseDescription courseDescription = new EduCourseDescription();
courseDescription.setDescription(courseInfoVo.getDescription());
// 设置描述id就是课程id
courseDescription.setId(cid);
courseDescriptionService.save(courseDescription);
}
}
{
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
}
]
},
// info.vue
<template>
<div class="app-container">
<h2 style="text-align: center;">发布新课程h2>
<el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
<el-step title="填写课程基本信息"/>
<el-step title="创建课程大纲"/>
<el-step title="最终发布"/>
el-steps>
<el-form label-width="120px">
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步el-button>
el-form-item>
el-form>
div>
template>
<script>
export default {
data() {
return {
saveBtnDisabled: false // 保存按钮是否禁用
}
},
created() {
console.log('info created')
},
methods: {
next() {
console.log('next')
this.$router.push({ path: '/course/chapter/1' })
}
}
}
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('info created')
},
methods: {
previous(){
this.$router.push({ path: '/course/info/1' })
},
next() {
this.$router.push({ path: '/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: '/course/chapter/1' })
},
publish() {
console.log('publish')
this.$router.push({ path: '/course/list' })
}
}
}
script>
编辑课程基本信息
// src\api\edu\course.js
import request from '@/utils/request'
export default {
// 1. 添加课程信息
addCourseInfo(courseInfo) {
return request({
url: '/eduservice/course/addCourseInfo',
method: 'post',
data: courseInfo
})
}
}
<el-form label-width="120px">
<el-form-item label="课程标题">
<el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
el-form-item>
<el-form-item label="总课时">
<el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
el-form-item>
<el-form-item label="课程简介">
<el-input v-model="courseInfo.description" placeholder=""/>
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>
<script>
import course from '@/api/edu/course'
export default {
data() {
return {
saveBtnDisabled: false, // 保存按钮是否禁用
courseInfo: {
title: '',
subjectId: '',
teacherId: '',
lessonNum: 0,
description: '',
cover: '',
price: 0
}
}
},
created() {
console.log('info created')
},
methods: {
saveOrUpdate() {
course.addCourseInfo(this.courseInfo)
.then(response => {
// 提示
this.$message({
type: 'success',
message: '添加课程信息成功!'
});
// 跳转到第二步
this.$router.push({ path: '/course/chapter/' + response.data.courseId })
})
}
}
}
</script>
@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
@Autowired
private EduCourseService courseService;
// 添加课程基本信息的方法
@PostMapping("addCourseInfo")
public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
// 返回添加之后课程id,为了后面添加大纲使用
String id = courseService.saveCourseInfo(courseInfoVo);
return R.ok().data("courseId", id);
}
}
public interface EduCourseService extends IService<EduCourse> {
String saveCourseInfo(CourseInfoVo courseInfoVo);
}
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@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 LemonException(20001,"添加课程信息失败");
}
// 获取添加之后课程id
String cid = eduCourse.getId();
// 2.向课程简介表添加课程简介
EduCourseDescription courseDescription = new EduCourseDescription();
courseDescription.setDescription(courseInfoVo.getDescription());
// 设置描述id就是课程id
courseDescription.setId(cid);
courseDescriptionService.save(courseDescription);
return cid;
}
}
// src\api\edu\course.js
export default {
// 2. 查询所有讲师
getListTeacher() {
return request({
url: '/eduservice/teacher/findAll',
method: 'get'
})
}
}
// src\views\edu\course\info.vue
<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>
// 定义data
teacherList: [] // 封装所有的讲师
created() {
// 初始化所有讲师
this.getListTeacher()
},
methods: {
// 查询所有的讲师
getListTeacher() {
course.getListTeacher()
.then(response => {
this.teacherList = response.data.items
})
},
}
subjectOneList: [], // 一级分类
subjectTwoList: [] // 二级分类
<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>
<script>
import subject from '@/api/edu/subject'
methods: {
// 点击某个一级分类,触发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
})
},
}
script>
<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>
<script>
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
}
},
methods: {
// 上传封面成功调用的方法
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
}
}
}
script>