谷粒学院第七天
一、添加课程分类前端实现
1、配置路由
(1)src/router/index.js
//课程分类
{
path: 'subject',
component: Layout,
redirect: '/edu/subject/list',
name: 'Subject',
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)添加组件
2、编写 import.vue
(1)js 定义数据
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
importBtnDisabled: false, // 按钮是否禁用,
loading: false,
};
}
(2)template
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="信息描述">
<el-tag type="info">excel模版说明</el-tag>
<el-tag>
<i class="el-icon-download" />
<a :href="'/static/1.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/edu-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>
(3)上传方法
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: "导入失败",
});
},
}
二、课程分类列表显示(树形)
1、后端实现(重要)
(1)编写实体类
/**
* 一级分类
* @ClassName OneSubject
* @Author YH
* @Date 2021/8/26
* @Version 1.0
*/
@Data
public class OneSubject {
private String id;
private String title;
/**
* 一个一级分类中可能有多个二级分类
*/
private List<TwoSubject> children = new ArrayList<>();
}
/**
* 二级分类
* @ClassName TwoSubject
* @Author YH
* @Date 2021/8/26
* @Version 1.0
*/
@Data
public class TwoSubject {
private String id;
private String title;
}
(2)编写 Controller 方法
/**
* 课程分类列表显示(树形)
*/
@PostMapping("/getAllSubject")
public Result getAllSubject() {
/**
* 因为一级分类中包含二级分类
*/
List<OneSubject> subjectList = eduSubjectService.getAllOneTwoSubject();
Map<String, Object> map = new HashMap<>();
map.put("subjectList", subjectList);
return Result.ok().data(map);
}
(3)编写 Servie 接口方法
/**
* 课程分类列表显示(树形)
* @return
*/
List<OneSubject> getAllOneTwoSubject();
(4)编写 ServiceImpl
@Autowired
private EduSubjectMapper eduSubjectMapper;
@Override
public List<OneSubject> getAllOneTwoSubject() {
// 查询所有一级分类
QueryWrapper<EduSubject> oneWrapper = new QueryWrapper<>();
oneWrapper.eq("parent_id", 0);
List<EduSubject> oneSubjectList = eduSubjectMapper.selectList(oneWrapper);
// 查询所有二级分类
QueryWrapper<EduSubject> twoWrapper = new QueryWrapper<>();
twoWrapper.ne("parent_id", "0");
List<EduSubject> twoSubjectList = eduSubjectMapper.selectList(twoWrapper);
// 存储最终结果
List<OneSubject> result = new ArrayList<>();
// 封装一级分类,利用工具类
for (EduSubject eduSubject : oneSubjectList) {
OneSubject oneSubject = new OneSubject();
BeanUtils.copyProperties(eduSubject, oneSubject);
result.add(oneSubject);
// 封装二级分类
List<TwoSubject> children = new ArrayList<>();
for (EduSubject twoSubject : twoSubjectList) {
// 判断其父级 id 是否等于
if (twoSubject.getParentId().equals(eduSubject.getId()) == true) {
TwoSubject tempTwoSubject = new TwoSubject();
BeanUtils.copyProperties(twoSubject, tempTwoSubject);
children.add(tempTwoSubject);
}
}
oneSubject.setChildren(children);
}
return result;
}
(4)测试结果
2、前端实现
(1)创建 api
api/teacher/subject.js
import request from '@/utils/request' //引入已经封装好的axios 和 拦截器
export default{
//课程分类
getSubjectList(){
return request({
url:"/eduservice/edu-subject/getAllSubject",
method: 'get'
})
}
}
(2)list.vue
<template>
<div class="app-container">
<el-input
v-model="filterText"
placeholder="Filter keyword"
style="margin-bottom: 30px"
/>
<el-tree
ref="tree2"
:data="data"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>
</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.tree2.filter(val);
},
},
methods: {
getAllSubjectList(){
subject.getSubjectList()
.then(resp=>{
this.data = resp.data.list
})
},
// 优化前端过滤功能
filterNode(value, data) {
if (!value) return true;
return data.title.indexOf(value) !== -1;
},
},
created() {
this.getAllSubjectList()
},
};
</script>
(3)优化前端过滤功能
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
(4)给之前的添加课程分类添加路由跳转
三、课程发布需求分析
1、课程发布流程说明
第一步:
第二步:
第三步:
2、课程相关表关系(重要)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqAg73MO-1629975089935)(C:\Users\YH\AppData\Roaming\Typora\typora-user-images\image-20210826114630093.png)]
3、添加课程分析
(1)使用代码生成器生成相关代码
strategy.setInclude("edu_course", "edu_course_description", "edu_video", "edu_chapter");//根据数据库哪张表生成,有多张表就加逗号继续填写
(2)细节问题
(3)编写 vo 实体类
@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "课程ID")
private String id;
@ApiModelProperty(value = "课程讲师ID")
private String teacherId;
@ApiModelProperty(value = "课程专业ID")
private String subjectId;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
private BigDecimal price;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "课程简介")
private String description;
}
(4)编写 Controller
@CrossOrigin
@RestController
@RequestMapping("/eduservice/edu-course")
public class EduCourseController {
@Autowired
private EduCourseService eduCourseService;
/**
* 添加课程
* @param courseInfoVo
* @return
*/
@PostMapping("/addCourseInfo")
public Result addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
eduCourseService.addCourseInfo(courseInfoVo);
return Result.ok();
}
}
(5)编写 Service、ServiceImpl
public interface EduCourseService extends IService<EduCourse> {
void addCourseInfo(CourseInfoVo courseInfoVo);
}
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
@Autowired
private EduCourseMapper eduCourseMapper;
@Autowired
private EduCourseDescriptionMapper eduCourseDescriptionMapper;
@Override
public void addCourseInfo(CourseInfoVo courseInfoVo) {
// 向课程表添加信息
EduCourse eduCourse = new EduCourse();
BeanUtils.copyProperties(courseInfoVo, eduCourse);
int insert = eduCourseMapper.insert(eduCourse);
String courseId = eduCourse.getId();
if (insert != 1) {
throw new GuliException(200, "添加课程失败");
}
// 向课程简介表中添加信息
EduCourseDescription eduCourseDescription = new EduCourseDescription();
eduCourseDescription.setDescription(courseInfoVo.getDescription());
eduCourseDescription.setId(courseId);
eduCourseDescriptionMapper.insert(eduCourseDescription);
}
}
(6)添加 MP 自动生成时间和主键生成策略
4、添加课程信息前端
(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)添加组件
(3)课程信息页面
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({path:'/course/chapter/1'})
},
},
created(){
}
};
</script>
<style></style>
(4)课程大纲页面
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({ path: "/course/info/1" });
},
next() {
//跳转到第三步
this.$router.push({ path: "/course/publish/1" });
},
},
created() {},
};
</script>
<style>
</style>
(5)课程大纲页面
.<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({ path: "/course/chapter/1" });
},
publish(){
this.$router.push({ path: "/course/list" });
}
},
};
</script>
<style>
</style>
(6)定义 API
import request from '@/utils/request' //引入已经封装好的axios 和 拦截器
export default{
//添加课程信息功能
addCourseInfo(courseInfo){
return request({
url:"/eduservice/edu-course/addCourseInfo",
method: 'post',
data: courseInfo,
})
}
}
(7)组件模板
.<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: "",
lessonNum: 0,
description: "",
cover: "",
price: 0,
},
};
},
methods: {
saveOrUpdate() {
course.addCourseInfo(this.courseInfo).then(resp => {
this.$message({
message: "添加课程信息成功",
type: "success",
})
//跳转到第二步,并带着这个课程生成的id
this.$router.push({ path: "/course/chapter/"+resp.data.courseId });
});
},
},
created() {},
};
</script>
<style>
</style>
5、添加课程信息前端完善(显示讲师)
(1)编写组件
<!--课程讲师-->
<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>
(2)定义 API
//查询所有讲师
getAllTeacher(){
return request({
url:"/eduservice/edu-teacher/findAll",
method: 'get',
})
}
(3)引入 api
import course from "@/api/teacher/course.js";
(4)组件 js
export default {
data() {
return {
...
teacherLists: [], //封装所有讲师数据
};
},
methods: {
//查询所有讲师
getListTeacher() {
course.getAllTeacher().then((resp) => {
this.teacherLists = resp.data.items;
});
},
....
created() {
//初始化所有讲师
this.getListTeacher();
},
};
</script>
6、添加课程信息前端完善(显示分类)
(1)组件数据定义
subjectOneLists: [], //封装所以一级分类数据
subjectTwoLists: [], //封装二级分类数据
(2)组件内容
<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>
(3)引入组件
import subject from "@/api/teacher/subject.js";
(4)定义方法
//课程分类
getSubjectList(){
return request({
url:"/eduservice/edu-subject/getAllSubject",
method: 'get'
})
}
(5)组件模板
<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>
(6)注册 change 事件
(7)定义 Change 方法
//点击某个一级分类,触发change事件,显示对应的二级分类
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;
this.courseInfo.subjectId = "";
}
}
}
(8)在 Created 中调用
created() {
//初始化所有讲师
this.getListTeacher();
//初始化一级分类
this.getOneSubject();
}
7、添加课程信息前端完善(封面上传)
(1)定义 data 数据
BASE_API: process.env.BASE_API, // 接口API地址
(2)编写组件
<!-- 课程封面 TODO -->
<el-form-item label="课程封面">
<el-upload
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:action="BASE_API + '/edu_oss/fileoss/upload'"
class="avatar-uploader"
>
<img :src="courseInfo.cover" />
</el-upload>
</el-form-item>
(3)结果回调
//上传封面成功调用的方法
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;
}