前后端实现课程分类管理、Excel导入与CRUD代码实例

68 篇文章 0 订阅
3 篇文章 0 订阅

1 课程分类管理

1.1 为什么要有课程分类管理

课程分类管理是针对于课程来说的,在线教育工程中主要的产品就是课程,所以我们所操作的都和课程相关的管理;那么看一下和课程相关的其他模块管理:

那么我们的后台管理最终需要发布课程、在添加课程的时候一定会选择课程中的分类和其他的课程相关模块,那么我们需要对课程这些模块进行管理;

1.2 初始化课程分类数据

1.2.1 引入依赖

在guli_common工程pom文件中、引入:

<!--xls-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.9</version>
</dependency>
<!--xlsx-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.9</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

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")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;

@ApiModelProperty(value = "创建时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

1.2.3.2 修改Controller

@RestController
@RequestMapping("/subject")
@CrossOrigin
public class EduSubjectController {
}

1.3 导入课程API

为了运营人员更好的维护和管理系统、不方便一级二级的手写进数据库中、需要开发人员或者专业人员写入Excl表格中让运营人员通过Excl表格进行课程分类的导入;

1.3.1 Controller

@Autowired
private EduSubjectService subjectService;

@PostMapping("import")
public Result importExl(MultipartFile file){
    List<String> errorMessage = subjectService.importExl(file);
    if(errorMessage.size() == 0){
        return Result.ok();
    } else{
        return Result.error().data("message",errorMessage);
    }
}

Service进行读取Excl表格分类数据并保存到数据库中:

考虑几个问题:

  1. 此Excl是否规范、每一行每一列都需要判断是否有空数据问题;
  2. 保存到数据库中的数据是否存在;

1.3.2 Service接口

/**
 * 导入Excl表格
 * @param file
 * @return
 */
List<String> importExl(MultipartFile file);

1.3.3 ServiceImpl实现类:

@Override
public List<String> importExl(MultipartFile file) {
    List<String> msg = new ArrayList<>();
    try {
        //1、获取文件流
        InputStream inputStream = file.getInputStream();
        //2、创建workbook
        Workbook workbook = new HSSFWorkbook(inputStream);
        //3 根据workbook获取sheet
        Sheet sheet = workbook.getSheetAt(0);
        //获取表格最后一个索引位置也就是行数
        int lastRowNum = sheet.getLastRowNum();
        System.err.println("获取行数:" + lastRowNum);
        if (lastRowNum <= 1) {
            msg.add("请填写数据");
            return msg;
        }
        //遍历行数
        for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
            Row rowData = sheet.getRow(rowNum);
            //根据每一行获取列表数据
            //获取第一列
            Cell cellOne = rowData.getCell(0);
            //根据cell获取第一列里面内容
            if(cellOne == null) {
                msg.add("第"+rowNum+"行第一列数据为空");
                //跳到下一行进行操作
                continue;
            }
            String cellOneValue = cellOne.getStringCellValue();
            if(StringUtils.isEmpty(cellOneValue)) {
                msg.add("第"+rowNum+"行第一列数据为空");
                //跳到下一行进行操作
                continue;
            }

            //cellOneValue 是一级分类名称
            //判断添加的一级分类名称在数据库表里面是否有相同的,如果没有添加
            EduSubject eduSubject = this.existOneSubject(cellOneValue);
            //为了后面添加二级分类,一级分类id
            String pid = null;
            if(eduSubject == null) { //表没有相同的一级分类
                //添加到数据库里面
                EduSubject subjectOne = new EduSubject();
                subjectOne.setTitle(cellOneValue);
                subjectOne.setParentId("0");
                baseMapper.insert(subjectOne);
                //添加一级分类之后,获取添加之后id值
                pid = subjectOne.getId();
            } else {
                //有相同的一级分类
                pid = eduSubject.getId();
            }

            //获取第二列
            Cell cellTwo = rowData.getCell(1);
            if(cellTwo==null) {
                msg.add("第"+rowNum+"行第二列数据为空");
                continue;
            }
            // 根据cell获取第二列里面内容
            String cellTwoValue = cellTwo.getStringCellValue();
            if(StringUtils.isEmpty(cellTwoValue)) {
                msg.add("第"+rowNum+"行第二列数据为空");
                continue;
            }
            //cellTwoValue 是二级分类名称
            EduSubject eduSubjectTwo = this.existTwoSubject(cellTwoValue, pid);
            if(eduSubjectTwo == null) {
                //没有相同的二级分类,实现二级分类添加
                EduSubject subjectTwo = new EduSubject();
                subjectTwo.setTitle(cellTwoValue);
                subjectTwo.setParentId(pid);
                baseMapper.insert(subjectTwo);
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
    return msg;
}

//2 判断二级分类名称是否有相同的
private EduSubject existTwoSubject(String name,String pid) {
    QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
    wrapper.eq("title",name);
    wrapper.eq("parent_id",pid);

    return baseMapper.selectOne(wrapper);
}

//1 判断一级分类名称是否相同
private EduSubject existOneSubject(String name) {
    //查询数据库判断
    QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
    wrapper.eq("title",name);
    wrapper.eq("parent_id","0");
    EduSubject eduSubject = baseMapper.selectOne(wrapper);
    return eduSubject;
}

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
public class OneSubject {

    private String id;

    private String title;

    private List<TwoSubject> children = new ArrayList<TwoSubject>();

}

@Data
public class TwoSubject {

    private String id;

    private String title;

}

这样使得这两个对象构成了Tree的关系

1.6.2 Controller:

@GetMapping(“list”)
public Result getSubjectList(){
    List<OneSubject> list = subjectService.getTree();
    return Result.ok().data("subjectList",list);
}

1.6.3 Service接口:

/**
 * 获取所有的课程分类树
 * @return
 */
List<OneSubject> getTree();

1.6.4 ServiceImpl实现类:

@Override
public List<OneSubject> getTree() {
    List<OneSubject> oneSubjectList = new ArrayList<>();
    //1、把一级分类查询出来、并查询一级分类下的二级分类
    QueryWrapper wrapper = new QueryWrapper();
    wrapper.eq("parent_id",0);
    List<EduSubject> list = baseMapper.selectList(wrapper);
    for (EduSubject subject1 : list) {
        OneSubject os = new OneSubject();
        BeanUtils.copyProperties(subject1,os);
        //查询下级列表
        QueryWrapper wr = new QueryWrapper();
        wr.eq("parent_id",os.getId());
        List<EduSubject> twoList = baseMapper.selectList(wr);
        for (EduSubject subject2 : twoList) {
            TwoSubject ts = new TwoSubject();
            //复制
            BeanUtils.copyProperties(subject2,ts);
            os.getChildren().add(ts);
        }
        //添加到OneSubject集合中
        oneSubjectList.add(os);
    }

    return oneSubjectList;
}

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}")
public Result deleteById(@PathVariable  String id){
    boolean b = subjectService.removeById(id);
    if(b){
        return Result.ok();
    } else {
        return Result.error();
    }
}

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 = "新增一级分类")
@PostMapping("saveLevelOne")
public Result saveLevelOne(
        @ApiParam(name = "subject", value = "课程分类对象", required = true)
        @RequestBody EduSubject subject){

    boolean result = subjectService.saveLevelOne(subject);
    if(result){
        return Result.ok();
    }else{
        return Result.error().message("删除失败");
    }
}

1.10.2 Service接口:

/**
 * 保存课程分类
 * @param subject
 * @return
 */
boolean saveLevelOne(EduSubject subject);

1.10.3 ServiceImpl实现类:

@Override
public boolean saveLevelOne(EduSubject subject) {

    EduSubject subjectLevelOne = this.selectSubjectByName(subject.getTitle());

    if(subjectLevelOne == null){
        return super.save(subject);
    }

    return false;
}

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 = "新增二级分类")
@PostMapping("saveLevelTwo")
public Result saveLevelTwo(
        @ApiParam(name = "subject", value = "课程分类对象", required = true)
        @RequestBody EduSubject subject){

    boolean result = subjectService.saveLevelTwo(subject);
    if(result){
        return Result.ok();
    }else{
        return Result.error().message("保存失败");
    }
}

1.11.2 Service接口:

/**
 * 保存二级课程分类
 * @param subject
 * @return
 */
boolean saveLevelTwo(EduSubject subject);

1.11.3 Service实现类:

@Override
public boolean saveLevelTwo(EduSubject subject) {
    EduSubject subjectLevelTwo = this.selectSubjectByNameAndParentId(subject.getTitle(), subject.getParentId());
    if(subjectLevelTwo == null){
        return this.save(subject);
    }else{
        throw new EduException(20001, "类别已存在");
    }
}

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

            })

        })

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵然间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值