1 课程分类管理
1.1 为什么要有课程分类管理
课程分类管理是针对于课程来说的,在线教育工程中主要的产品就是课程,所以我们所操作的都和课程相关的管理;那么看一下和课程相关的其他模块管理:
那么我们的后台管理最终需要发布课程、在添加课程的时候一定会选择课程中的分类和其他的课程相关模块,那么我们需要对课程这些模块进行管理;
1.2 初始化课程分类数据
1.2.1 引入依赖
在guli_common工程pom文件中、引入:
<!--xls--> |
1.2.2 通过代码生成器生成响应代码
运行guli_teacher项目中代码生成器方法:
在第5个策略配置中(61行),绑定要生成的表数据:strategy.setInclude("edu_subject");
运行,此时帮我们生成好了entity, service,controller
1.2.3 修改数据
1.2.3.1 修改entity:
@ApiModelProperty(value = "课程类别ID") … … @ApiModelProperty(value = "创建时间", example = "2019-01-01 8:00:00") |
1.2.3.2 修改Controller
@RestController |
1.3 导入课程API
为了运营人员更好的维护和管理系统、不方便一级二级的手写进数据库中、需要开发人员或者专业人员写入Excl表格中让运营人员通过Excl表格进行课程分类的导入;
1.3.1 Controller
@Autowired |
Service进行读取Excl表格分类数据并保存到数据库中:
考虑几个问题:
- 此Excl是否规范、每一行每一列都需要判断是否有空数据问题;
- 保存到数据库中的数据是否存在;
1.3.2 Service接口
/** |
1.3.3 ServiceImpl实现类:
@Override |
1.3.4 启动使用Swagger测试,成功!
1.4 导入课程前端实现
首先在后台管理页面中需要有课程分类管理的路由设置;
课程分类管理:课程分类列表,课程分类导入,
在运营人员、需要导入课程分类的时候,需要我们提供一个课程分类的模板、根据我们的模块导入才能成功、所以我们应该在导入Excl表格页面中也需要有一个下载模块的路径;
那么这个模块我们应该提前放在OSS中存储、方便管理;
1.4.1、添加路由
{ path: '/subject', component: Layout, redirect: '/subject/list', name: '课程分类管理', meta: { title: '课程分类管理', icon: 'example' }, children: [ { path: 'list', name: '课程分类列表', component: () => import('@/views/subject/list'), meta: { title: '课程分类列表', icon: 'tree' } }, { path: 'import', name: '课程分类导入', component: () => import('@/views/subject/import'), meta: { title: '课程分类导入', icon: 'table' } } ] } |
1.4.2、添加vue组件
1.4.3 表单组件import.vue
1.4.4 js定义数据
<script> export default { data () { return { BASE_API: process.env.BASE_API, // 接口API地址 OSS_PATH: process.env.OSS_PATH, // 阿里云OSS地址 fileUploadBtnText: '上传到服务器', // 按钮文字 importBtnDisabled: false, // 按钮是否禁用, loading: false } } } </script> |
1.4.5 template
<template> <div class="app-container"> <el-form label-width="120px"> <el-form-item label="信息描述"> <el-tag type="info">excel模版说明</el-tag> <el-tag> <i class="el-icon-download"/> <a :href="OSS_PATH + '/avatar/%E8%AF%BE%E7%A8%8B%E5%88%86%E7%B1%BB.xls'">点击下载模版</a> </el-tag> </el-form-item> <el-form-item label="选择Excel"> <el-upload ref="upload" :auto-upload="false" :on-success="fileUploadSuccess" :on-error="fileUploadError" :disabled="importBtnDisabled" :limit="1" :action="BASE_API+'/subject/import'" 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> |
1.4.6 js上传方法
methods: { submitUpload() { this.fileUploadBtnText = '正在上传' this.importBtnDisabled = true this.loading = true this.$refs.upload.submit() }, fileUploadSuccess(response) {
}, fileUploadError(response) {
} } |
1.4.7 回调函数
fileUploadSuccess(response) { if (response.success === true) { this.fileUploadBtnText = '导入成功' this.loading = false this.$message({ type: 'success', message: response.message }) } else { this.fileUploadBtnText = '导入失败' this.loading = false const messages = response.data.messageList let msgString = '<ul>' messages.forEach(msg => { msgString += `<li>${msg}</li>` }) msgString += '</ul>' this.$alert(msgString, response.message, { dangerouslyUseHTMLString: true }) } }, fileUploadError(response) { this.fileUploadBtnText = '导入失败' this.loading = false this.$message({ type: 'error', message: '导入失败' }) } |
1.5 配置Nginx & 测试
在Nginx中/conf/nginx.config文件中配置:
location ~ /subject/ { proxy_pass http://localhost:8001; } |
重启: nginx.exe -s reload;
1.6 课程分类列表显示
当运营者点击课程分类管理——>课程分类列表 时应该把我们数据库中所有正常状态下的课程分类根据一级二级分别显示:
那么显示的样式是什么样子的?
{ "success": true, "code": 20000, "message": "成功", "data": { "subjectList": [ { "id": "1163484667219812354", "title": "前端开发", "children": [ { "id": "1163484667278532609", "title": "html" }, { "id": "1163484667333058561", "title": "css" } … ] }, { "id": "1163484667479859202", "title": "后端开发", "children": [ { "id": "1163484667513413633", "title": "java" }, … ] }, { "id": "1163484667660214274", "title": "数据库", "children": [ { "id": "1163484667693768706", "title": "mysql" }, … ] }, { "id": "1163484667777654786", "title": "大数据", "children": [ { "id": "1163484667815403522", "title": "Hadoop" } ] } ] } } |
以上可知页面中想要的效果是这样的、那么我们在后台获取课程分类列表的时候就需要我们封装成页面想要的数据格式;
1.6.1 组件两个对象
@Data |
@Data |
这样使得这两个对象构成了Tree的关系
1.6.2 Controller:
@GetMapping(“list”) |
1.6.3 Service接口:
/** |
1.6.4 ServiceImpl实现类:
@Override |
1.6.5 Swagger测试—> 成功
1.7 课程分类列表页面显示
1.7.1 创建api
api/edu/subject.js
import request from '@/utils/request' const api_name = '/subject' export default { getNestedTreeList() { return request({ url: `${api_name}/list`, method: 'get' }) } } |
1.7.2 list.vue
<template> <div class="app-container"> <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" /> <el-tree ref="subjectTree" :data="subjectList" :props="defaultProps" :filter-node-method="filterNode" :expand-on-click-node="false" class="filter-tree" default-expand-all node-key="id"> <span slot-scope="{ node, data }" class="custom-tree-node"> <span>{{ node.label }}</span> <span> <!-- 使用Chrome的Vue插件调试 --> <el-button v-if="node.level == 1" type="text" size="mini" @click="() => append(data)">添加</el-button> <el-button v-if="node.level == 2" type="text" size="mini" @click="() => remove(node, data)">删除</el-button> </span> </span> </el-tree> </div> </template> <script> import subject from '@/api/edu/subject' export default { data() { return { filterText: '', subjectList: [], defaultProps: { children: 'children', label: 'title' } } }, watch: { filterText(val) { this.$refs.subjectTree.filter(val) } }, created() { this.fetchNodeList() }, methods: { fetchNodeList() { subject.getNestedTreeList().then(response => { if (response.success === true) { this.subjectList = response.data.subjectList } }) }, filterNode(value, data) { if (!value) return true return data.title.indexOf(value) !== -1 } } } </script> |
1.7.3 优化前端过滤功能
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
过滤大小写
1.8 课程分类列表-测试
1.9 课程分类删除
删除类似与如下操作,在每一个分类后面都可以对此分类操作
1.9.1 Controller:
@DeleteMapping("${id}") |
1.9.2 subject.js
removeById(id){ return request({ url: `${api_name}/${id}`, method: 'delete' }) } |
1.9.3 list.vue
remove(node, data) { console.log(node) console.log(data) this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { return subject.removeById(data.id) }).then(() => { // this.fetchNodeList()// 刷新列表 this.$refs.subjectTree.remove(node) // 删除节点(效率高) this.$message({ type: 'success', message: '删除成功!' }) }).catch((response) => { // 失败 if (response === 'cancel') { this.$message({ type: 'info', message: '已取消删除' }) } else { this.$message({ type: 'error', message: '删除失败' }) } }) } |
1.9.4 测试—> 成功
1.10 课程分类添加—> 一级类目
我们在页面中显示的、只能是显示列表后、添加按钮才跟着列表数据显示、那么可以在页面的列表上面加一个按钮添加一级分类、有了一级分类了才能点击添加会添加二级分类;
点击【添加一级分类】—> 弹出一个文本框
输入分类的title、点击【确定】——>向后台发送数据subject对象(vue中调用subject.js中的一级分类保存方法,向后台post提交数据)——>后台接收EduSubject对象传递到Service执行保存数据库的业务逻辑;
1.10.1 Controller:
@ApiOperation(value = "新增一级分类") |
1.10.2 Service接口:
/** |
1.10.3 ServiceImpl实现类:
@Override |
1.10.4 前端subject.js
saveLevelOne(subject) { return request({ url: `${api_name}/saveLevelOne`, method: 'post', data: subject }) } |
1.10.5 list.vue
appendLevelOne() { subject.saveLevelOne(this.subject).then(response => { this.$message({ type: 'success', message: '保存成功!' }) this.dialogFormVisible = false// 如果保存成功则关闭对话框 this.fetchNodeList()// 刷新列表 }).catch((response) => { // console.log(response) this.$message({ type: 'error', message: '保存失败' }) }) } |
1.10.6 HTML显示
在<el-tree>标签上面:
<el-button type="text" @click="dialogFormVisible = true">添加一级分类</el-button> |
显示的添加分类的文本框:在</el-tree> 标签下添加
<el-dialog :visible.sync="dialogFormVisible" title="添加分类"> <el-form :model="subject" label-width="120px"> <el-form-item label="分类标题"> <el-input v-model="subject.title"/> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="appendLevelOne()">确 定</el-button> </div> </el-dialog> |
1.11 课程分类添加—>二级分类
在页面中显示一级分类的列表,在每一个一级分类title后又【添加二级分类】按钮,当点击【添加二级分类】按钮时、弹出文本框、写入二级分类的title、并且获取一级分类的ID作为保存二级分类的parentId,点击【确定】——> vue中js中根据parentId判断是新增一级分类还是二级分类调用subject.js中saveLevelTwo保存方法使用POST请求想后台提交二级分类的数据——>java后台中Controller接受数据调用Service实现保存二级分类的业务逻辑;
1.11.1 Controller:
@ApiOperation(value = "新增二级分类") |
1.11.2 Service接口:
/** |
1.11.3 Service实现类:
@Override |
1.11.4 前端subject.js
saveLevelTwo(subject) { return request({ url: `${api_name}/saveLevelTwo`, method: 'post', data: subject }) } |
1.11.5 list.vue
<el-button v-if="node.level == 1" type="text" size="mini" @click="() => {dialogFormVisible = true; subject.parentId = data.id}">添加二级分类</el-button> |
注意:点击【添加二级分类】显示添加的文本框,并且把parentId获取到赋值给subject
在<el-dialog>显示的文本框中【确定】按钮修改:
<el-dialog :visible.sync="dialogFormVisible" title="添加分类"> … … <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="append()">确 定</el-button> </div> </el-dialog> |
在methods:方法区中写一个append方法根据parentId判断是添加一级分类还是二级分类
并把二级分类js完成:
append(data) { if (!this.subject.parentId) { this.appendLevelOne() } else { this.appendLevelTwo() } }, appendLevelTwo() { subject.saveLevelTwo(this.subject).then(response => { this.$message({ type: 'success', message: '保存成功!' }) this.dialogFormVisible = false// 如果保存成功则关闭对话框 this.fetchNodeList()// 刷新列表 this.subject.title = ''// 重置类别标题 this.subject.parentId = '' // 重置表单parentId }).catch((response) => { // console.log(response) this.$message({ type: 'error', message: response.data.message }) }) } |