瞧不起CRUD?Vue 神带你领略王者级CRUD程序媛的风采

最近在学习Spring Boot + Vue的整合,通过尚硅谷的一个谷粒学院项目进行练习。

一、课程介绍

系统后端接口部分,使用目前流行的SpringBoot+SpringCloud进行微服务架构,使用Feign、Gateway、Hystrix,以及阿里巴巴的Nacos等组件搭建了项目的基础环境。项目中还使用MyBatisPlus进行持久层的操作,使用了OAuth2+JWT实现了分布式的访问,项目中整合了SpringSecurity进行了权限控制。除此之外,项目中使用了阿里巴巴的EasyExcel实现对Excel的读写操作,使用了Redis进行首页数据的缓存,使用Git进行代码的版本控制,还整合了Swagger生成接口文档 。

系统前端部分,使用主流的前端框架Vue,使用Es6的开发规范,采用模块化的开发模式,搭建页面环境使用了Nuxt框架和vue-admin-template模板,使用Element-ui进行页面布局。前端环境中使用Npm进行依赖管理,使用Babel进行代码转换,使用Webpack进行静态资源的打包,采用axios进行Ajax请求调用,使用了ECharts进行数据的图表展示。

二、前置知识梳理

1、Swagger

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

作用:

(1)接口的文档在线自动生成。

(2)功能测试。

方法测试

2、轻量级后台管理系统基础模板,vue-admin-template

3、MySQL数据库

建表语句:

CREATE TABLE `edu_teacher` (
  `id` char(19) NOT NULL COMMENT '讲师ID',
  `name` varchar(20) NOT NULL COMMENT '讲师姓名',
  `intro` varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介',
  `career` varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师',
  `level` int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师',
  `avatar` varchar(255) DEFAULT NULL COMMENT '讲师头像',
  `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
  `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='讲师';

4、nginx

(1)简介

nginx (engine x) 是一个高性能的HTTP和反向代理web服务器。

(2)Nginx的主要作用

  • 请求转发
  • 负载均衡
  • 动态分离

(3)启动

cmd路径,执行nginx.exe命令即可。

(4)配置

目前暂时只有两个子端口,9001为nginx端口号,8001为讲师模块,8002为阿里云oss上传头像模块。

三、Spring Boot + Vue 实现CRUD

1、EduTeacherController

package com.atguigu.eduservice.controller;

import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.EduTeacher;
import com.atguigu.eduservice.entity.vo.TeacherQuery;
import com.atguigu.eduservice.service.EduTeacherService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Api(description="讲师管理")
@RestController
@RequestMapping("/eduservice/teacher")
@CrossOrigin
public class EduTeacherController {
    @Autowired
    private EduTeacherService teacherService;

    @ApiOperation(value = "所有讲师列表")
    @GetMapping("findAll")
    public R findAllTeacher(){
        List<EduTeacher> list = teacherService.list(null);
        return R.Ok().data("items",list);
    }

    @ApiOperation(value = "根据ID查询讲师")
    @GetMapping("findById{id}")
    public R findById(@ApiParam(name="id",value="讲师ID",required=true) @PathVariable String id){
        EduTeacher teacher = teacherService.getById(id);
        return R.Ok().data("items",teacher.getJson());
    }

    @ApiOperation(value = "根据ID删除讲师")
    //逻辑删除讲师的方法
    @DeleteMapping("{id}")
    public R removeTeacher(@ApiParam(name="id",value="讲师ID",required=true) @PathVariable String id){
        Boolean flag = teacherService.removeById(id);
        if(flag){
            return R.Ok();
        }else{
            return R.error();
        }
    }

    //分页
    @GetMapping("pageTeacher/{current}/{limit}")
    public R pageListTeacher(@PathVariable long current,@PathVariable long limit){
        //创建page对象
        Page<EduTeacher> pageTeacher = new Page<>(current,limit);
        //调用方法的时候,底层封装,把分页所有数据封装到pageTeacher中
        teacherService.page(pageTeacher,null);
        long total = pageTeacher.getTotal();
        List<EduTeacher> records = pageTeacher.getRecords();
        //Map map = new HashMap();
        //map.put("total",total);
        //map.put("rows",records);
        return R.Ok().data("total",total).data("rows",records);
    }

    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current,@PathVariable long limit,
                                  @RequestBody(required = false) TeacherQuery teacherQuery){
        //创建一个page对象
        Page<EduTeacher> pageTeacher = new Page<>(current,limit);
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        //多条件组合查询
        //动态SQL
        //判断条件值是否为空,不为空就拼接
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        if(!StringUtils.isEmpty(name)){
            wrapper.like("name",name);
        }
        if(!StringUtils.isEmpty(level)){
            wrapper.eq("level",level);
        }
        if(!StringUtils.isEmpty(begin)){
            wrapper.ge("gmt_create",begin);
        }
        if(!StringUtils.isEmpty(end)){
            wrapper.le("gmt_modified",end);
        }
        wrapper.orderByDesc("gmt_create");
        //wrapper.
        teacherService.page(pageTeacher,wrapper);
        long total = pageTeacher.getTotal();
        List<EduTeacher> records = pageTeacher.getRecords();
        return R.Ok().data("total",total).data("rows",records);
    }

    //添加讲师接口的方法
    @PostMapping("addTeacher")
    public R addTeacher(@RequestBody EduTeacher eduTeacher){
        boolean save = teacherService.save(eduTeacher);
        if(save){
            return R.Ok();
        }else {
            return R.error();
        }
    }

    @GetMapping("getTeacher/{id}")
    public R getTeacher(@PathVariable String id){
        EduTeacher byId = teacherService.getById(id);
        return R.Ok().data("teacher",byId);
    }

    @PostMapping("updateTeacher")
    public R updateTeacher(@RequestBody EduTeacher eduTeacher){
        boolean flag = teacherService.updateById(eduTeacher);
        if(flag){
            return R.Ok();
        }else {
            return R.error();
        }
    }
}

2、前端讲师列表

<template>
    <div>
        <el-form label-width="120px">
            <el-form-item label="讲师名称">
                <el-input v-model="teacher.name"/>
            </el-form-item>
            <el-form-item label="讲师排序">
                <el-input-number v-model="teacher.sort" controls-position="right" min="0"/>
            </el-form-item>
            <el-form-item label="讲师头衔">
                <el-select v-model="teacher.level" clearable placeholder="请选择">
                <!--
                    数据类型一定要和取出的json中的一致,否则没法回填
                    因此,这里value使用动态绑定的值,保证其数据类型是number
                -->
                <el-option :value="1" label="高级讲师"/>
                <el-option :value="2" label="首席讲师"/>
                </el-select>
            </el-form-item>
            <el-form-item label="讲师资历">
                <el-input v-model="teacher.career"/>
            </el-form-item>
            <el-form-item label="讲师简介">
                <el-input v-model="teacher.intro" :rows="10" type="textarea"/>
            </el-form-item>

            <!-- 讲师头像:TODO -->
            <!-- 讲师头像 -->
            <el-form-item label="讲师头像">

                <!-- 头衔缩略图 -->
                <pan-thumb :image="teacher.avatar"/>
                <!-- 文件上传按钮 -->
                <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
                </el-button>

                <!--
                  v-show:是否显示上传组件
                  :key:类似于id,如果一个页面多个图片上传控件,可以做区分
                  :url:后台上传的url地址
                  @close:关闭上传组件
                  @crop-upload-success:上传成功后的回调 
                  <input type="file" name="file"/>
                -->
                <image-cropper
                              v-show="imagecropperShow"
                              :width="300"
                              :height="300"
                              :key="imagecropperKey"
                              :url="BASE_API+'/eduoss/fileoss'"
                              field="file"
                              @close="close"
                              @crop-upload-success="cropSuccess"/>
            </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 teacherApi from '@/api/edu/teacher.js'
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
export default {
  data() {
    return {
      teacher: {
        name: '',
        sort: 0,
        level: 1,
        career: '',
        intro: '',
        avatar: ''
      },
      //上传弹框组件是否显示
      imagecropperShow:false,
      imagecropperKey:0,//上传组件key值
      BASE_API:process.env.BASE_API, //获取dev.env.js里面地址
      saveBtnDisabled:false  // 保存按钮是否禁用,
    }
  },
  created(){
    this.init()
  },
  watch: {  //监听
    $route(to, from) { //路由变化方式,路由发生变化,方法就会执行
      this.init()
    }
  },
  methods: {
    close() { //关闭上传弹框的方法
        this.imagecropperShow=false
        //上传组件初始化
        this.imagecropperKey = this.imagecropperKey+1
    },
    //上传成功方法
    cropSuccess(data) {
      this.imagecropperShow=false
      //上传之后接口返回图片地址
      this.teacher.avatar = data.url
      this.imagecropperKey = this.imagecropperKey+1
    },
    init() {
      //判断路径有id值,做修改
      if(this.$route.params && this.$route.params.id) {
          //从路径获取id值
          const id = this.$route.params.id
          //调用根据id查询的方法
          this.getInfo(id)
      } else { //路径没有id值,做添加
        //清空表单
        this.teacher = {}
      }
    },
    //根据讲师id查询的方法
    getInfo(id) {
      teacherApi.getTeacherInfo(id)
        .then(response => {
          this.teacher = response.data.teacher
        })
    },
    saveOrUpdate() {
      //根据teacher中是否有ID
      if(!this.teacher.id){
        this.saveTeacher();
      }else{
          this.updateTeacher();
      }
    },
    updateTeacher() {
      teacherApi.updateTeacherInfo(this.teacher)
      .then(response => {
            //提示信息
            this.$message({
                type: 'success',
                message: '修改成功!'
            });
            //回到列表页面,路由跳转
            this.$router.push({ path: '/teacher/table' })
      })
    },
    // 保存
    saveTeacher() {
        teacherApi.addTeacher(this.teacher)
        .then(response => {
            //提示信息
            this.$message({
                type: 'success',
                message: '添加成功!'
            });
            //回到列表页面,路由跳转
            this.$router.push({ path: '/teacher/table' })
        })
    }
  }
}
</script>

3、添加讲师页面

<template>
    <div class="app-container">
         <!--查询表单-->
        <el-form :inline="true" class="demo-form-inline">
            <el-form-item>
                <el-input v-model="teacherQuery.name" placeholder="讲师名"/>
            </el-form-item>

            <el-form-item>
                <el-select v-model="teacherQuery.level" clearable placeholder="讲师头衔">
                    <el-option :value="1" label="高级讲师"/>
                    <el-option :value="2" label="首席讲师"/>
                </el-select>
            </el-form-item>

            <el-form-item label="添加时间">
                <el-date-picker
                    v-model="teacherQuery.begin"
                    type="datetime"
                    placeholder="选择开始时间"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    default-time="00:00:00"
                />
            </el-form-item>
            <el-form-item>
                <el-date-picker
                    v-model="teacherQuery.end"
                    type="datetime"
                    placeholder="选择截止时间"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    default-time="00:00:00"
                />
            </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="name" label="名称" width="80" />
            
            <el-table-column label="头衔" width="80">
                <template slot-scope="scope">
                    {{ scope.row.level===1?'高级讲师':'首席讲师' }}
                </template>
            </el-table-column>

            <el-table-column prop="intro" label="资历" />

            <el-table-column prop="gmtCreate" label="添加时间" width="160"/>

            <el-table-column prop="sort" label="排序" width="60" />

            <el-table-column label="操作" width="200" align="center">
                <template slot-scope="scope">
                <router-link :to="'/teacher/edit/'+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 teacher from '@/api/edu/teacher.js'
export default {
    data() { //定义变量
        return {
            list:null,
            page:1,
            limit:10,
            total:0,
            teacherQuery:{}    
        }
    },
    created() {//调用方法
        this.getList()
    },
    methods: {//定义方法
        //讲师列表的方法
        getList(page =1){
            this.page = page
            teacher.getTeacherListPage(this.page,this.limit,this.teacherQuery)
                .then(response => {//请求成功
                    //response接收返回的数据
                    this.list = response.data.rows
                    this.total = response.data.total
                    console.log(this.list)
                    console.log(this.total)
                })
                .catch(error => {//请求失败
                    console.log(error)
                })
        },
        resetData() {
            this.teacherQuery = {}
            this.getList()
        },
        removeDataById(id) {
            // debugger
            // console.log(memberId)
            this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                teacher.deleteTeacherId(id)
                    .then(response =>{
                    this.$message({
                        type: 'success',
                        message: '删除成功!'
                    });
                    this.getList()
                })
            })
        }
    }
}
</script>

4、teacher.js

import request from '@/utils/request'

export default{
    //1 讲师列表(条件查询分页)
    getTeacherListPage(current,limit,teacherQuery) {
        return request({
          url: `/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
          method: 'post',
          //后端用RequestBody,JSON传递
          //data表示把对象转换成json进行传递到接口里面
          data: teacherQuery
        })
    },
    deleteTeacherId(id){
        return request({
            url: `/eduservice/teacher/${id}`,
            method: 'delete'
          })
    },
    addTeacher(teacher){
        return request({
            url: `/eduservice/teacher/addTeacher`,
            method: 'post',
            data: teacher
        })
    },
    getTeacherInfo(id){
        return request({
            url: `/eduservice/teacher/getTeacher/${id}`,
            method: 'get'
        })
    },
    updateTeacherInfo(teacher){
        return request({
            url: '/eduservice/teacher/updateTeacher',
            method: 'post',
            data: teacher
        })
    }
}

以上为SpringBoot + Vue实现CRUD的主要代码!

5、页面效果展示

上一篇:上云就上阿里云,提升CRUD程序媛B格必备技能!

下一篇:超详细的springBoot学习笔记

猜你喜欢

转载自blog.csdn.net/guorui_java/article/details/107010007
今日推荐