【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能

该笔记是记录听课的笔记,在上一篇文章的基础上加上了前端的开发,实现前后端完整功能的实现。

文章目录

  • 前言
  • 一、插入自动填充
    • 1.编写代码
    • 2.测试
  • 二、更新自动填充
  • 三、逻辑删除
  • 四、分页查询+条件查询
    • 1.代码
    • 2.分页的实现
  • 五、前端Vue+ElementUI
    • 1.一些基础知识
    • 2.后端的登录+token的实现
    • 3.前端
  • 六、总体代码
  • 结束语


前言

此项目使用的是VUE+ELEMENTUI以及Springboot+mybatis-plus的技术进行的开发,基于IDEA软件。此笔记在上一篇文章:【项目笔记】后端框架的搭建用法以及一些常用功能的实现,使用Springboot+mybatis-plus等技术的基础上,记录了前端联合后端的开发。

一、插入自动填充

1.编写代码

此项目希望插入新用户的时候统一设置初始化密码,即在插入用户的时候不用输入密码自动填充
在后端的Admin类中password属性设置了自动填充。
直接在@TableField()括号里加上fill = FieldFill.INSERT
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第1张图片
在这里插入图片描述
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第2张图片
插入式填充:注意字段、类型和值
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第3张图片Admin.java在上一篇文章里自动生成架构的时候,后面的字段就直接生成为自动填充值的架构,故一点开就会发现后面的value、version等属性直接就有fill=FieldFill.INSERT【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第4张图片
ps:逻辑删除字段需要加上@TableLogic注解
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第5张图片
生成架构的关于自动填充的代码:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第6张图片
自动加了注解但还是需要在MyMetaObjectHandler中的insertFill插入填充的方法中编写设置了自动填充的字段、类型以及值
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第7张图片

2.测试

使用postman工具发起自动填充测试:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第8张图片

二、更新自动填充

此项目设置在进行更新操作后,update_time自动进行填充此时的更新时间。
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第9张图片
当进行更新后,Date.class类型的updateTime这个字段将更新为现在的时间new Date()
在这里插入图片描述

三、逻辑删除

ps:平时测试后端功能的时候,url地址栏填写格式
url:http://localhost:8081/一级路径(到类)/二级路径(到方法)

【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第10张图片

删除的时候不是删除,而是将isdelete设置为1,而且将修改时间更新
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第11张图片

四、分页查询+条件查询

在上一篇文章写了分页查询和条件查询分开的,此处将分页和条件进行合并查询。

1.代码

在这里插入图片描述

 @PostMapping("/selectAdminByConditionAndPage/{currentPage}/{pageSize}")
    public Result selectAdminByConditionAndPage(@PathVariable Long currentPage, @PathVariable Long pageSize, @RequestBody AdminCondition adminCondition) {
        String name = adminCondition.getName();
        String username = adminCondition.getUsername();
        String phone = adminCondition.getPhone();
        String beginTime = adminCondition.getBeginTime();
        String endTime = adminCondition.getEndTime();
        QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
        if (!name.isEmpty()) {
            adminQueryWrapper.like("name", name);
        }
        if (!username.isEmpty()) {
            adminQueryWrapper.like("username", username);
        }
        if (!phone.isEmpty()) {
            adminQueryWrapper.like("phone", phone);
        }
        if (!beginTime.isEmpty() && !endTime.isEmpty()) {
            adminQueryWrapper.between("create_time", beginTime, endTime);
        }
        adminQueryWrapper.orderByDesc("create_time");

        Page<Admin> adminPage = new Page<>(currentPage, pageSize);

//        List list = adminService.list(adminQueryWrapper);

        Page<Admin> page = adminService.page(adminPage, adminQueryWrapper);
        List<Admin> list = page.getRecords();
        long total = page.getTotal();
        return Result.success().data("items", list).data("total", total);//返回两个结果,一个是结果集一个是总数
    }

ps:加上required = false,查询的时候可以加上查询条件,也可以没有查询条件
在这里插入图片描述
对象里面的参数为null和这个对象为null都是代表没有条件的意思

2.分页的实现

在mybatis-plus中找到分页插件
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第12张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第13张图片
实现分页查询还需要新建一个类:可以在此类中添加想操作的插件
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第14张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第15张图片
ps:Version乐观锁可以用于不能两个人同时选择某一个东西。至此后端结束。

五、前端Vue+ElementUI

1.一些基础知识

Public打包的
Src项目主要内容
Api负责接口的管理

Vue不是跳转而是在一个页面中不断渲染
API是负责发送的接口 controller是负责接收的接口
希望别人可以用就必须export导出
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第16张图片
Vue通过路由进行跳转的,从导入组件import,children可能是二级菜单。
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第17张图片
Views就是写的页面,建议一个文件夹对应一个页面,页面都叫index,只用修改文件夹的名字即可。
Token类似门票

Templete里面有且只有一个div,放标签。取代了body标签。
Script写js
Style写css
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第18张图片
跨域:ip不同端口不同,在vue.config.js中
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第19张图片
只要请求的url中有/dev-api就跨域:跨域连接到后端
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第20张图片
只用将这段插入config.js中

proxy: {
      '/dev-api': {
        target: 'http://localhost:8081/',
        ws: true,
        changeOrigin: true,//允许跨域
        pathRewrite: {
          '^/dev-api': ''
        }
      }
    },

【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第21张图片

2.后端的登录+token的实现

后端:AdminController中的login方法

 @PostMapping("/login")
    public Result login(@RequestBody Admin admin){
        String username = admin.getUsername();
        String password = admin.getPassword();
        QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
        if (!username.isEmpty()) {
            adminQueryWrapper.eq("username", username);
        }else {
            return Result.fail().message("账号不能为空");
        }

        Admin one = adminService.getOne(adminQueryWrapper);//getone用于唯一的学号确定某一个人
        if (one == null) {
            return Result.fail().message("该账号不存在");
        }

        if (one.getPassword().equals(password)) {
            Map<String, Object> data = new HashMap<>();
            data.put("id", one.getId());
            String token = JWTUtils.generateToken(data);
            return Result.success().message("登录成功").data("token", token);
        }

        return Result.fail().message("登陆失败");
    }

此处需要提到token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

token的依赖:


<dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.1version>
        dependency>

关于token的方法:

 @GetMapping("/info")
    public Result info(@RequestParam("token") String token){
        if (token == null) {
            return Result.fail().message("token不能为空");
        }
        Map<String, Object> data = JWTUtils.parseToken(token);
        if (data == null) {
            return Result.fail().message("token错误");
        }
        String id = (String) data.get("id");
        Admin admin = adminService.getById(id);
        return Result.success().data("name", admin.getName()).data("avatar", "");
    }

3.前端

实际vue代码以及其对应页面上的展示元素
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第22张图片
图标的使用可以在这个网站:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第23张图片
给属性绑定一个变量:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第24张图片

传对象用data,传参数用param
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第25张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第26张图片
**ps:在字符串里面插入变量:不用单引号,而是使用反引号` **

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`   // Hello Bob, how are you today?

【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第27张图片
后端写的返回data(“items”,list),则前端的adminList会去接收后端返回的items对应的list
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第28张图片

【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第29张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第30张图片
可以通过{{scope.row.xxx}}拿到某条数据的xxx
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第31张图片
此处是对拿到的对象的phone属性进行取子串的操作
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第32张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第33张图片
数据的双向绑定:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第34张图片
无限封装成一个方法
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第35张图片
HandleDelete是处理
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第36张图片
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第37张图片

图表可以使用echarts:
【项目笔记】前端Vue+Element-UI加上后端Springboot+mybatis-plus技术,完善各功能_第38张图片
注意,要export后其他页面才能import导入

六、总体代码

index.vue

<template>
  <div class="app-container">

    <el-button type="primary" @click="handleInsertAdmin()">添加员工</el-button>

    <el-table ref="multipleTable" :data="adminList" tooltip-effect="dark" style="width: 100%; margin-top: 10px;" border>
      <el-table-column type="selection" width="55">
      </el-table-column>
      <el-table-column prop="username" label="学号">
      </el-table-column>
      <el-table-column prop="name" label="姓名">
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间">
      </el-table-column>
      <el-table-column>
        <template slot-scope="scope">
          <el-button type="danger" size="mini" @click="">编辑</el-button>
          <el-button type="danger" size="mini" @click="handleDeleteAdmin(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>


    <el-upload ref="uploadFile" class="avatar-uploader" action="http://localhost:9528/dev-api/example.demo/users/uploadFile" :auto-upload="false" accept="image/png"
      :on-change="handelFileChange" :on-success="handleUploadSuccess">
      <img  v-if="imageUrl" :src="imageUrl" class="avatar">
      <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>

    <el-button @click="handleUploadFile()">上传</el-button>




    <el-dialog title="收货地址" :visible.sync="dialogFormVisible">
      <el-form :model="adminEntity">
        <el-form-item label="姓名">
          <el-input v-model="adminEntity.name"></el-input>
        </el-form-item>
        <el-form-item label="学号">
          <el-input v-model="adminEntity.username"></el-input>
        </el-form-item>
        <el-form-item label="电话">
          <el-input v-model="adminEntity.phone"></el-input>
        </el-form-item>
        <el-form-item label="权限">
          <el-select v-model="adminEntity.level">
            <el-option v-for="(item, index) in levelList" :label="item.label" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleInsertAdminEntity()">确 定</el-button>
      </div>
    </el-dialog>

  </div>
</template>
<script>
import adminApi from '@/api/admin'
export default {
  data() {
    return {
      isSelete : false,
      adminEntity: {},
      adminCondition: {
        name: '',
        username: '',
        phone: '',
        beginTime: '',
        endTime: ''
      },
      currentPage: 1,
      pageSize: 20,
      adminList: [],
      dialogFormVisible: false,
      imageUrl : '',
      levelList: [
        {
          value: 1,
          label: '员工'
        },
        {
          value: 2,
          label: '管理员'
        },
        {
          value: 3,
          label: '超级管理员'
        },
      ]
    }
  },
  created() {
    this.getAdminList()
  },
  methods: {
    getAdminList() {
      adminApi.selectAdminByConditionAndPage(this.adminCondition, this.currentPage, this.pageSize).then(res => {
        this.adminList = res.data.items
      })
    },
    handleInsertAdmin() {
      this.dialogFormVisible = true
    },
    handleInsertAdminEntity() {
      adminApi.insertAdmin(this.adminEntity).then(res => {
        if (res.success) {
          this.getAdminList()
        }
        this.dialogFormVisible = false
        this.adminEntity = {}
      })
    },
    handleDeleteAdmin(id) {
      adminApi.deleteAdmin(id).then(res => {
        if (res.success) {
          this.getAdminList()
        }
      })
    },
    handelFileChange(file, fileList) {
      // 当用户选择图片后,将图片回显到页面,实现预览功能
      this.imageUrl = URL.createObjectURL(file.raw)
    },
    handleUploadFile() {
      this.$refs.uploadFile.submit()
    },
    handleUploadSuccess(response, file, fileList) {
      if (response.success) {
        // 成功消息提示框
        this.$message({
          message: '上传成功',
          type: 'success'
        });
      }else {
        // 失败消息提示框
        this.$message.error(response.message);
      }
    },
  }
}
</script>
<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    margin-top: 10px;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
</style>

admin.js

import request from '@/utils/request'


const adminApi = {
    selectAdminByConditionAndPage(adminConditon, currentPage, pageSize) {
        return request({
            url: `/example.demo/admin/selectAdminByConditionAndPage/${currentPage}/${pageSize}`,
            method: 'post',
            data: adminConditon
        })
    },
    insertAdmin(admin) {
        return request({
            url: `/example.demo/admin/insertAdmin`,
            method: 'post',
            data: admin
        })
    },
    deleteAdmin(id) {
        return request({
            url: `/example.demo/admin/deleteAdmin/${id}`,
            method: 'get',
        })
    }
}

export default adminApi

AdminController.java

package com.example.demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Admin;
import com.example.demo.entity.Users;
import com.example.demo.entity.condition.AdminCondition;
import com.example.demo.entity.condition.UsersCondition;
import com.example.demo.service.AdminService;
import com.example.demo.utils.JWTUtils;
import com.example.demo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/example.demo/admin")
public class AdminController {

    @Autowired
    private AdminService adminService;

    @PostMapping("/insertAdmin")
    public Result insertAdmin(@RequestBody Admin admin) {
        boolean save = adminService.save(admin);
        if (save) {
            return Result.success().message("添加成功");
        }
        return Result.fail().message("添加失败");
    }


    @GetMapping("/deleteAdmin/{id}")
    public Result deleteAdmin(@PathVariable String id) {
        boolean flag = adminService.removeById(id);
        if (flag) {
            return Result.success();
        }
        return Result.fail();
    }

    @PostMapping("/updateAdmin")
    public Result updateAdmin(@RequestBody Admin admin) {
        boolean flag = adminService.updateById(admin);
        if (flag) {
            return Result.success().message("修改成功");
        }
        return Result.fail().message("修改失败");
    }

    @GetMapping("/selectAdmin")
    public Result selectAdmin(){
        List<Admin> list = adminService.list();
        return Result.success().data("items", list);
    }

    @PostMapping("/selectAdminByConditionAndPage/{currentPage}/{pageSize}")
    public Result selectAdminByConditionAndPage(@PathVariable Long currentPage, @PathVariable Long pageSize, @RequestBody AdminCondition adminCondition) {
        String name = adminCondition.getName();
        String username = adminCondition.getUsername();
        String phone = adminCondition.getPhone();
        String beginTime = adminCondition.getBeginTime();
        String endTime = adminCondition.getEndTime();
        QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
        if (!name.isEmpty()) {
            adminQueryWrapper.like("name", name);
        }
        if (!username.isEmpty()) {
            adminQueryWrapper.like("username", username);
        }
        if (!phone.isEmpty()) {
            adminQueryWrapper.like("phone", phone);
        }
        if (!beginTime.isEmpty() && !endTime.isEmpty()) {
            adminQueryWrapper.between("create_time", beginTime, endTime);
        }
        adminQueryWrapper.orderByDesc("create_time");

        Page<Admin> adminPage = new Page<>(currentPage, pageSize);

//        List list = adminService.list(adminQueryWrapper);

        Page<Admin> page = adminService.page(adminPage, adminQueryWrapper);
        List<Admin> list = page.getRecords();
        long total = page.getTotal();
        return Result.success().data("items", list).data("total", total);
    }

    @PostMapping("/login")
    public Result login(@RequestBody Admin admin){
        String username = admin.getUsername();
        String password = admin.getPassword();
        QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
        if (!username.isEmpty()) {
            adminQueryWrapper.eq("username", username);
        }else {
            return Result.fail().message("账号不能为空");
        }

        Admin one = adminService.getOne(adminQueryWrapper);
        if (one == null) {
            return Result.fail().message("该账号不存在");
        }

        if (one.getPassword().equals(password)) {
            Map<String, Object> data = new HashMap<>();
            data.put("id", one.getId());
            String token = JWTUtils.generateToken(data);
            return Result.success().message("登录成功").data("token", token);
        }

        return Result.fail().message("登陆失败");
    }

    @GetMapping("/info")
    public Result info(@RequestParam("token") String token){
        if (token == null) {
            return Result.fail().message("token不能为空");
        }
        Map<String, Object> data = JWTUtils.parseToken(token);
        if (data == null) {
            return Result.fail().message("token错误");
        }
        String id = (String) data.get("id");
        Admin admin = adminService.getById(id);
        return Result.success().data("name", admin.getName()).data("avatar", "");
    }
    /*上传文件*/
    @PostMapping("/uploadFile")
    public Result uploadFile(@RequestParam("file") MultipartFile multipartFile){
        //根路径
        String bassPath = "D:\\IdeaProjects\\demo\\src\\main\\resources\\files"; //基本路径
        // 时间路径,格式化时间,以时间对文件夹进行分类
        String dataPath = new SimpleDateFormat("YYYY\\MM\\dd").format(new Date());
        //基本路径加上时间路径形成上传的文件夹,先创建文件夹再创建文件
        String dirPath = bassPath +"\\"+ dataPath;
        //System.out.println(dirPath);
        File dirFile = new File(dirPath);//使用io的File
        if(!dirFile.exists()){
            boolean mkdirs = dirFile.mkdirs();
            if(!mkdirs){//先创建文件夹再创建文件
                return Result.fail().message("文件夹创建失败");
            }
        }
        //originalname才是文件名,获取文件名
        String originalFilename = multipartFile.getOriginalFilename();
        if(originalFilename == null){
            return Result.fail().message("文件名为空");
        }
        //对文件名通过"."进行分割 比如"文件.exe" => {"文件","exe"}
        String[] fileName = originalFilename.split("\\.");
        //每次上传的文件前面添加了随机值防止文件上传覆盖,通过UUID随机生成的字符串并且去除其中的横线,再加上文件类型组成完整的文件名
        String targetFileName = UUID.randomUUID().toString().replace("-","") + "." + fileName[1];
        File targetFile = new File(dirFile, targetFileName);
        try(
                InputStream inputStream = multipartFile.getInputStream();
                OutputStream outputStream = Files.newOutputStream(targetFile.toPath());
        ){
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = inputStream.read(buffer)) != -1){ //拷贝,-1就表示已经读取完毕
                outputStream.write(buffer,0,len);
            }
        }catch (Exception e){
            return Result.fail().message("保存失败");
        }
        String path = targetFile.getPath();
        return Result.success().data("url",path);
    }
}

结束语

浅浅记录一下师兄给我们讲解的vue前端以及前后端是如何结合的,第一版笔记可能有点粗糙,后面会一点点进行精细的修改。

你可能感兴趣的:(项目笔记,mybatis,spring,boot,前端)