【SpringBoot+Vue】前后端分离项目实战 超详细笔记

title:神盾局特工管理系统

功能:系统管理,查询用户、增删改查。

项目特点:简单简单蛋蛋蛋

B站视频地址:https://www.bilibili.com/video/BV1dG4y1T7yp
作者原帖:https://blog.csdn.net/m0_37613503/article/details/132610271

GitHub源代码总结

一、项目概述

  1. 目标:理解前后端,具备独立搭建前后端分离项目的能力。
  2. 开发模式:前端+后端+数据库。
  3. 技术栈:
    1. 前端:vue+vuex+ElementUI+Axios+vue-element-admin说明:前端框架、全局状态管理框架、前端UI框架、前端HTTP框架、项目脚手架。
    2. 后端:springBoot+Mybatis+Mybatis-plus+Redis 说明:容器+MVC框架、ORM框架、MyBatis增强工具、非关系型数据库。

二、数据库xdb

  1. 创建数据库:xdb

  2. 创建用户表、角色表、菜单表、用户角色映射表、角色菜单映射表

    https://blog.csdn.net/m0_37613503/article/details/128961401

三、前端笔记

(1)node环境
  1. node要求16.12.0版本以下,否则和vue-admin-template不兼容。

  2. 可以使用nvm降低node版本,https://juejin.cn/post/7094576504243224612

  3. 可以通过nvm install xxx的操作,安装不同版本的node,nvm ls查看本地已经安装过的node版本,再nvm use xxx就可以快速切换node版本啦

  4. 注意:不用配置环境变量,注意新版本的node文件夹中应该包含的文件。

  5. 设置npm的全局安装路径和缓存路径的。

    npm config set prefix “您想创建文件的global”
    npm config set cache “您想创建文件的cache”。
    在这里插入图片描述

(2)下载vue-admin-template
  1. 官网:https://panjiachen.github.io/vue-element-admin-site/zh/guide/
  2. 使用基础模板
    在这里插入图片描述
(3)项目初始化
  1. npm install下载moudle
  2. npm run dev运行项目
(4)修改文字、图片、菜单、导航栏
(5)增加选项卡,标签栏导航——代码基本固定的
  1. npm install下载moudle

    • install卡住问题

    第一种方案、首先检查npm代理,是否已经使用国内镜像
    // 执行以下命令查看是否为国内镜像

    npm config get registry

    出现如下所示,表明已经为国内镜像无需再修改

    如果不是则换成国内镜像,执行以下命令

    npm config set registry=https://registry.npmmirror.com

    原文链接:https://blog.csdn.net/shi450561200/article/details/134354871

  2. npm run dev运行项目

(6)梳理项目结构
├── build                      # 构建相关
├── mock                       # 项目mock 模拟数据
├── plop-templates             # 基本模板
├── public                     # 静态资源
│   │── favicon.ico            # favicon图标
│   └── index.html             # html模板
├── src                        # 源代码
│   ├── api                    # 所有请求
│   ├── assets                 # 主题 字体等静态资源
│   ├── components             # 全局公用组件
│   ├── directive              # 全局指令
│   ├── filters                # 全局 filter
│   ├── icons                  # 项目所有 svg icons
│   ├── lang                   # 国际化 language
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── vendor                 # 公用vendor
│   ├── views                  # views 所有页面
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
├── tests                      # 测试
├── .env.xxx                   # 环境变量配置
├── .eslintrc.js               # eslint 配置项
├── .babelrc                   # babel-loader 配置
├── .travis.yml                # 自动化CI配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json
(7)修改一些配置项
  1. 修改前端项目部署端口→vue.config.js中修改
  2. lintaOnSave的修改→在vue.config.js中,改成false;
  3. 是否打开默认浏览器:devServer→在vue.config.js中,open的value值改成true;
  4. mock模拟数据的服务也在vue.config.js中,在后端搭起后要关掉。
(8)添加标签栏导航
  1. 官方具体网址:快捷导航(标签栏导航) | vue-element-admin (panjiachen.github.io)
  2. 具体过程:
    1. 首先在@/layout/components/AppMain.vue中添加
      在这里插入图片描述
<keep-alive :include="cachedViews">

  <router-view :key="key" />

</keep-alive>
cachedViews() {

  return this.$store.state.tagsView.cachedViews

}
  2. 复制vue-element-admin项目中的文件到相应的目录中

      @/layout/components/TagsView  


      @/store/modules/tagsView.js  

@/store/modules/permission.js
3. 修改vuex中store文件夹下的getters.js
在这里插入图片描述

visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,   
permission_routes: state => state.permission.routes

  4. 将tagsView放到全局状态管理器中,在store的index.js中引入tagsView

在这里插入图片描述

  5. 在布局中导出tagsView,在文件@layout\components\index.js新增
export { default as TagsView } from './TagsView'

  6. 在布局中使用tagsView

在这里插入图片描述

  7. Affix 固钉

在这里插入图片描述

  遇到一个报错:/deep/深度选择器报错(样式穿透的一种方法),是node版本过高的原因,可以改为::v-deep
(9)梳理登录接口

❀ 接口梳理

  • 后端的响应数据
{"code":20000,"data":{"token":"admin-token"}}
{
    "code": 20000,
    "data": {
        "roles": [
            "admin"
        ],
        "introduction": "I am a super administrator",
        "avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
        "name": "Super Admin"
    }
}
{"code":20000,"data":"success"}

四、后端笔记

(1)项目初始化
1. 创建springboot项目:2.7.8
2. 添加pom依赖
<!-- web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.2</version>
</dependency>
<!-- freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3. yml
server:
  port: 9999

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql:///xdb

logging:
  level:
    com.fangfang: debug

4. idea版本太低与低版本的jdk和springboot不兼容

    重装idea新版:[https://blog.csdn.net/bobby102/article/details/136325397](https://blog.csdn.net/bobby102/article/details/136325397)
5. **完成之后运行一下**

(2)使用MyBatis-plus代码生成器

在这里插入图片描述
- 在该目录下建立一个代码生成器类CodeGenerator,运行代码快速生成基础包:控制器、实体类、mapper、和service等等

    public static void main(String[] args) {
        //代码生成器,可以复用。下次只需要改前面自定义的module名字、mapper路径以及表名。
        String url = "jdbc:mysql:///xdb";
        String username = "root";
        String password = "root";
        String author = "fangfang";
        String outputDir = "D:\\Code\\IdeaProjects\\Xdun\\x-admin\\src\\main\\java";
        String basePackage = "com.fangfang";
        String moduleName = "sys";
        String mapperLocation = "D:\\Code\\IdeaProjects\\Xdun\\x-admin\\src\\main\\resources\\mapper\\" + moduleName;
        String tableName = "x_user,x_menu,x_role,x_role_menu,x_user_role";
        String tablePrefix = "x_";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author(author) // 设置作者
//                            不开启Swagger模式、不覆盖
//                            .enableSwagger() // 开启 swagger 模式
//                            .fileOverride() // 覆盖已生成文件
                            .outputDir(outputDir); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent(basePackage) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude(tableName) // 设置需要生成的表名
                            .addTablePrefix(tablePrefix); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

- 最后记得在入口文件扫描mapper

![](https://secure2.wostatic.cn/static/eeqmHxT2u3pcRjbvZm1FBd/image.png)
@MapperScan("com.fangfang.*.mapper")
- 运行入口文件 测试一下有没有问题(一直没测试失败的原因好像是:没有添加数据源)
(2)测试
  1. 使用自带的测试类测试UserMapper是否能使用
package com.fangfang;

import com.fangfang.sys.entity.User;
import com.fangfang.sys.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.List;

@SpringBootTest
class XAdminApplicationTests {
    //    注入资源
    @Resource
    UserMapper userMapper;

    @Test
    void contextLoads() {
        List<User> users = userMapper.selectList(null);
        for (User user : users) {
            System.out.println(user);
        }
//        users.forEach(System.out::println);
    }

}

  1. controller暴露接口,测试service
**//@Controller
//@Controller 注解会默认输出视图,而前后端对接需要输出json对象,应该使用 @RestController**
@RestController

@RequestMapping("/user")
public class UserController {
//    暴露接口,测试service
    @Autowired
    private IUserService userService;

    @GetMapping("/all")
    public List<User> getAll() {
       return userService.list();
    }
}

另一种方法:


@RequestMapping("/user")
public class UserController {
//    暴露接口,测试service
    @Autowired
    private IUserService userService;

    @GetMapping("/all")
    @ResponseBody
    //可以使用ResponseBody来将返回的视图变成json对象
    public List<User> getAll() {
        return userService.list();
    }
}
(3)设置公共响应类,统一返回数据格式。(目的:方便与前端对接)
  1. 设置公共响应类
    在这里插入图片描述
package com.fangfang.common.vo;
//vo:value object 值对象

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
//生成数据的getter/setter方法
@NoArgsConstructor
//无参构造
@AllArgsConstructor
//全参构造
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success() {
        return new Result<T>(20000, "success", null);
    }
    public static <T> Result<T> success(T data) {
        return new Result<T>(20000, "success", data);
    }
    public static <T> Result<T> success(String message) {
        return new Result<T>(20000, message, null);
    }
    public static <T> Result<T> success(String message, T data) {
        return new Result<T>(20000, message, data);
    }

    public static <T> Result<T> fail() {
        return new Result<T>(20001, "fail", null);
    }
    public static <T> Result<T> fail(String message) {
        return new Result<T>(20001, message, null);
    }
    public static <T> Result<T> fail(Integer code) {
        return new Result<T>(code, "fail", null);
    }
    public static <T> Result<T> fail(Integer code, String message) {
        return new Result<T>(code, message, null);
    }
}

  1. 使用公共响应类返回统一的数据(同理还有另一种方法)
//@Controller
//@Controller 注解会默认输出试图,而前后端对接需要输出json对象,应该使用 @RestController
@RestController
@RequestMapping("/user")
public class UserController {
//    暴露接口,测试service
    @Autowired
    private IUserService userService;

    @GetMapping("/all")
    public Result<List<User>> getAll() {
        return Result.success("查询成功",userService.list());
    }
}
(4)登录相关接口
  1. 登录(用到postman/redis/redis desktop manager工具)
    1. controller 新增登录接口类
//    新增登录接口,注意@RequestBody注解,转换成json字符串
    @PostMapping("/login")
    public Result<Map<String,Object>> login(@RequestBody User user) {
        Map<String,Object> data = userService.login(user);
        if (data!=null){
            return Result.success(data);
        }
        return Result.fail(20002,"用户名或密码错误");
    }
  2. service 生成登录接口
public interface IUserService extends IService<User> {

//    生成登录接口
    Map<String, Object> login(User user);
}
  3. service用户登录对应的实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

//    注入redis对应配置对象
    @Autowired
    private RedisTemplate redisTemplate;

//    用户登录对应的实现类
    @Override
    public Map<String, Object> login(User user) {
//        根据用户名密码查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername,user.getUsername());
        wrapper.eq(User::getPassword,user.getPassword());
        User loginUser = this.baseMapper.selectOne(wrapper);
//        结果不为空,则生成token,并将用户信息存入redis
        if (loginUser!=null){
//            暂时用uuid,终极方案是jwt
            String key = "user" + UUID.randomUUID();
//            存入redis
            loginUser.setPassword(null);
            redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);

//            返回数据
            Map<String, Object> data = new HashMap<>();
            data.put("token",key);
            return data;
        }

        return null;
    }
}
  4. redis配置存储token信息

在这里插入图片描述

@Configuration
public class MyRedisConfig {

//    注入连接工厂
    @Resource
    private RedisConnectionFactory factory;

//    配置bean
    @Bean
    public RedisTemplate redisTemplate(){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//        设置连接工厂
        redisTemplate.setConnectionFactory(factory);

//        序列化处理,键值对
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        redisTemplate.setValueSerializer(serializer);

//        对象映射:对特定数据如日期,时区做一些设置,反序列化就会简单
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        om.setTimeZone(TimeZone.getDefault());
        om.configure(MapperFeature.USE_ANNOTATIONS, false);
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        serializer.setObjectMapper(om);

        return redisTemplate;
    }
}
  5. postman和Redis Desktop manager测试redis和接口

在这里插入图片描述

  1. 获取用户信息(逻辑:根据携带的token从redis中获取用户信息,然后返回给浏览器)
    1. controller层
    @GetMapping("/info")
    @ResponseBody
    public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token) {
//        根据token获取用户信息,redis
        Map<String, Object> data = userService.getUserInfo(token);
        
        if(data!=null){
            return Result.success(data);
        }
        return Result.fail(20003,"用户信息获取失败");
    }

  2. service层 (接口和实现类)
    @Override
    public Map<String, Object> getUserInfo(String token) {
        Object obj = redisTemplate.opsForValue().get(token);
        if(obj != null) {
            // 导入alibaba fastjson包,反序列化
            User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class);
            HashMap<String, Object> data = new HashMap<>();
            data.put("name", loginUser.getUsername());
            data.put("avatar", loginUser.getAvatar());
            // 角色需要关联查询,使用userId——>得到roleId,在userMapper写入sql查询
            List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
            data.put("roles",roleList);
            return data;
        }
        return null;
    }

    <!--  fastjson  -->
    <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.51</version>
    </dependency>
  3. mapper.xml添加,在userMapper接口添加方法
    <select id="getRoleNameByUserId" parameterType="Integer" resultType="String">
        select
            b.role_name
        from x_user_role a,x_role b
        where a.role_id=b.role_id
          and a.user_id= #{userId}
    </select>

public interface UserMapper extends BaseMapper<User> {
    public List<String> getRoleNameByUserId(Integer userId);

}
  4. 测试postman

在这里插入图片描述

  1. 注销(将token从redis中删除)
    1. controller层
    @PostMapping("/logout")
    @ResponseBody  // 炒鸡重要
    public Result<?> logout(@RequestHeader("X-Token") String token) {
        userService.logout(token);
        return Result.success("注销成功");
    }
  2. service层
public void logout(String token) {
    redisTemplate.delete(token);
}

      404报错,没有返回正确的响应消息,但是token会被删除。
// 说明没有返回正确的值,是由于
// @Controller 注解会默认输出试图,而前后端对接需要输出json对象,
// 应该在类开头使用 @RestController注解
// 或者在方法前面加上 @ResponseBody


(5)跨域处理
  1. No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

    这个错误与 CORS(跨域资源共享) 策略有关。这是一种由浏览器实施的安全机制,用来限制网页向不同域名的资源发出请求,除非目标域明确允许跨域请求。

    原因:当你的网页(来源域)试图向不同域、协议或端口的资源发出请求时,服务器响应中没有包含 Access-Control-Allow-Origin 头信息,而浏览器需要这个头信息来允许跨域请求,因此报错。

  2. 使用nginx反向代理或者cors 跨域问题 | vue-element-admin (panjiachen.github.io)

    前端修改api的url值,以及开发配置中修改baseApi

    在这里插入图片描述

    后端在config中新建MyCorsConfig类

package com.fangfang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class MyCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //1) 允许的域,不要写*,否则cookie就无法使用了
        config.addAllowedOrigin("http://localhost:8888"); //这里填写请求的前端服务器
        //2) 是否发送Cookie信息
        config.setAllowCredentials(true);
        //3) 允许的请求方式
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        // 4)允许的头信息
        config.addAllowedHeader("*");

        //2.添加映射路径,我们拦截一切请求
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
}


  测试前后端对接:能够对接上。完成登录查询功能。
(6)用户管理页面布局(前端代码)
  1. 首先布局user.vue页面,包括搜索栏,内容列表,分页组件
<template>
  <div>
    <!--  搜索栏  -->
    <el-card id="search">
      <el-row>
        <el-col :span="20">
          <el-input v-model="searchModel.username" placeholder="用户名" />
          <el-input v-model="searchModel.phone" placeholder="电话" />
          <el-button type="primary" round icon="el-icon-search">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button type="primary" icon="el-icon-plus" circle />
        </el-col>
      </el-row>
    </el-card>
    <!--  结果列表  -->
    <el-card>
      <el-table
        :data="userList"
        stripe
        style="width: 100%"
      >
        <el-table-column
          type="index"
          label="#"
          width="180"
        />
        <el-table-column
          prop="id"
          label="用户ID"
          width="180"
        />
        <el-table-column
          prop="username"
          label="用户名"
          width="180"
        />
        <el-table-column
          prop="phone"
          label="电话"
          width="180"
        />
        <!--    剩下宽度给电子邮件    -->
        <el-table-column
          prop="email"
          label="电子邮件"
        />
        <el-table-column
          label="操作"
          width="180"
        />
      </el-table>
    </el-card>
    <!--  底部pagination分页栏  -->
    <el-pagination
      :current-page="searchModel.pageSize"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      total: 0,
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      userList: []
    }
  },
  methods: {
    handleSizeChange() {

    },
    handleCurrentChange() {

    }
  }
}
</script>

<style scoped>
#search .el-input {
  width: 200px;
  margin-right: 20px;
}
</style>

  1. app.vue中全局样式保证页面美观
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
.app-main{
  padding:10px;
}
.el-card{
  margin-bottom: 10px;
}
</style>

  1. 在main.js中修改下element-ui的语言
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
(7)用户管理接口
接口说明
查询用户列表分页查询
新增用户
根据id查询用户
修改用户
删除用户逻辑删除

7.1 查询用户列表
(1)controller

@GetMapping("/list")
    @ResponseBody
    public Result<?> getUserList(@RequestParam(value = "username", required = false) String username,
                                                   @RequestParam(value = "phone", required = false) String phone,
                                                   @RequestParam("pageNo") Long pageNo,
                                                   @RequestParam("pageSize") Long pageSize) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StringUtils.hasLength(username), User::getUsername, username);
        wrapper.eq(StringUtils.hasLength(phone), User::getPhone, phone);

        // baomidou里的Page
        // 还需要分页拦截器的配置 https://baomidou.com/plugins/
        Page<User> page = new Page<>(pageNo, pageSize);
        userService.page(page, wrapper);

        Map<String, Object> data = new HashMap<>();
        data.put("total", page.getTotal());
        data.put("rows", page.getRecords());

        System.out.println(data);
        return Result.success(data);
    }

(2)分页拦截器 插件主体 | MyBatis-Plus (baomidou.com)

package com.fangfang.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MpConfig {
    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

(3)前后端对接
1. 前端api文件下定义userManager.js

import request from '@/utils/request'

export default {
  getUserList(searchModel) {
    return request({
      url: 'user/list',
      method: 'get',
      params: {
        pageNo: searchModel.pageNo,
        pageSize: searchModel.pageSize,
        username: searchModel.username,
        phone: searchModel.phone
      }
    })
  }
}

2. 前端user.vue使用api,处理分页序号
<template>
  <div>
    <!--  搜索栏  -->
    <el-card id="search">
      <el-row>
        <el-col :span="20">
          <el-input v-model="searchModel.username" placeholder="用户名" clearable />
          <el-input v-model="searchModel.phone" placeholder="电话" clearable />
          <el-button type="primary" round icon="el-icon-search" @click="getUserList">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button type="primary" icon="el-icon-plus" circle />
        </el-col>
      </el-row>
    </el-card>
    <!--  结果列表  -->
    <el-card>
      <el-table
        :data="userList"
        stripe
        style="width: 100%"
      >
        <el-table-column
          label="#"
          width="180"
        >
          <!--   作用域插槽     -->
          <template slot-scope="scope">
            <!--    (pageNo-1)*pageSize + index +1    -->
            {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          label="用户ID"
          width="180"
        />
        <el-table-column
          prop="username"
          label="用户名"
          width="180"
        />
        <el-table-column
          prop="phone"
          label="电话"
          width="180"
        />
        <!--    剩下宽度给电子邮件    -->
        <el-table-column
          prop="email"
          label="电子邮件"
        />
        <el-table-column
          label="操作"
          width="180"
        />
      </el-table>
    </el-card>
    <!--  底部pagination分页栏  -->
    <el-pagination
      :current-page="searchModel.pageSize"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script>
import userApi from '../../api/userManager'

export default {
  data() {
    return {
      total: 0,
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      userList: []
    }
  },
  created() {
    this.getUserList()
  },
  methods: {
    handleSizeChange(pageSize) {
      this.searchModel.pageSize = pageSize
      this.getUserList()
    },
    handleCurrentChange(pageNo) {
      this.searchModel.pageNo = pageNo
      this.getUserList()
    },
    getUserList() {
      userApi.getUserList(this.searchModel).then(res => {
        this.userList = res.data.rows
        this.total = res.data.total
      })
    }
  }
}
</script>

<style scoped>
#search .el-input {
  width: 200px;
  margin-right: 20px;
}
</style>

7.2 新增用户

(1)后端
    @PostMapping
    @ResponseBody
    public Result<?> addUser(@RequestBody User user) {
        userService.save(user);
        return Result.success("新增用户成功");
    }
(2)前端+前后端对接

  API接口
  addUser(user) {
    return request({
      url: '/user',
      method: 'post',
      data: user
    })
  }
}
  涉及到用户编辑新增对话框,属性绑定,点击事件,表单常规验证自定义验证,调用接口,注意保存用户之后的事件顺序
<template>
  <div>
    <!--  搜索栏  -->
    <el-card id="search">
      <el-row>
        <el-col :span="20">
          <el-input v-model="searchModel.username" placeholder="用户名" clearable />
          <el-input v-model="searchModel.phone" placeholder="电话" clearable />
          <el-button type="primary" round icon="el-icon-search" @click="getUserList">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button type="primary" icon="el-icon-plus" circle @click="openEditUI" />
        </el-col>
      </el-row>
    </el-card>
    <!--  结果列表  -->
    <el-card>
      <el-table
        :data="userList"
        stripe
        style="width: 100%"
      >
        <el-table-column
          label="#"
          width="180"
        >
          <!--   作用域插槽     -->
          <template slot-scope="scope">
            <!--    (pageNo-1)*pageSize + index +1    -->
            {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          label="用户ID"
          width="180"
        />
        <el-table-column
          prop="username"
          label="用户名"
          width="180"
        />
        <el-table-column
          prop="phone"
          label="电话"
          width="180"
        />
        <el-table-column
          prop="status"
          label="用户状态"
          width="180"
        >
          <template slot-scope="scope">
            <el-tag v-if="scope.row.status===1">正常</el-tag>
            <el-tag v-else type="danger">禁用</el-tag>
          </template>
        </el-table-column>
        <!--    剩下宽度给电子邮件    -->
        <el-table-column
          prop="email"
          label="电子邮件"
        />
        <el-table-column
          label="操作"
          width="180"
        />
      </el-table>
    </el-card>
    <!--  底部pagination分页栏  -->
    <el-pagination
      :current-page="searchModel.pageSize"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
    <!--  用户编辑新增对话框  -->
    <el-dialog :title="title" :visible.sync="dialogFormVisible" @close="clearForm">
      <el-form ref="userFormRef" :model="userForm" :rules="rules">
        <el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
          <el-input v-model="userForm.username" autocomplete="off" />
        </el-form-item>
        <el-form-item label="登录密码" prop="password" :label-width="formLabelWidth">
          <el-input v-model="userForm.password" type="password" autocomplete="off" />
        </el-form-item>
        <el-form-item label="联系电话" :label-width="formLabelWidth">
          <el-input v-model="userForm.phone" autocomplete="off" />
        </el-form-item>
        <el-form-item label="用户状态" :label-width="formLabelWidth">
          <el-switch
            v-model="userForm.status"
            :active-value="1"
            inactive-value="0"
          />
        </el-form-item>
        <el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
          <el-input v-model="userForm.email" autocomplete="off" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveUser">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import userApi from '../../api/userManager'

export default {
  data() {
    return {
      formLabelWidth: '20%',
      userForm: {},
      dialogFormVisible: false,
      title: '',
      total: 0,
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      userList: [],
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入登录初始密码', trigger: 'blur' },
          { min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入电子邮件', trigger: 'blur' },
          { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  created() {
    this.getUserList()
  },
  methods: {
    handleSizeChange(pageSize) {
      this.searchModel.pageSize = pageSize
      this.getUserList()
    },
    handleCurrentChange(pageNo) {
      this.searchModel.pageNo = pageNo
      this.getUserList()
    },
    getUserList() {
      userApi.getUserList(this.searchModel).then(res => {
        this.userList = res.data.rows
        this.total = res.data.total
      })
    },
    openEditUI() {
      this.title = '新增用户'
      this.dialogFormVisible = true
    },
    clearForm() {
      this.userForm = {}
      // 关闭对话框清除验证
      this.$refs.userFormRef.clearValidate()
    },
    saveUser() {
      // 1. 触发表单验证
      this.$refs.userFormRef.validate((valid) => {
        if (valid) {
          // 2.提交请求给后台
          userApi.addUser(this.userForm).then(res => {
            // 成功提示
            this.$message({
              message: res.message,
              type: 'success'
            })
            // 关闭对话框
            this.dialogFormVisible = false
            // 刷新表格
            this.getUserList()
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
      // 3. 关闭对话框
      this.dialogFormVisible = false
    }
  }
}
</script>

<style scoped>
#search .el-input {
  width: 200px;
  margin-right: 20px;
}

.el-dialog .el-input {
  width: 80%;
}
</style>

(3)密码加密处理,用BCryptPasswordEncoder,涉及登录逻辑改动
  1. 添加依赖
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
    </dependency>
  2. 启动类配置一个Bean,返回一个密码控制类
  // 启动类配置一个Bean,返回一个密码控制类
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
  3. userController修改添加用户接口,密码加密
    @PostMapping
    @ResponseBody
    public Result<?> addUser(@RequestBody User user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        userService.save(user);
        return Result.success("新增用户成功");
    }
  4. 修改登录逻辑
@Override
    public Map<String, Object> login(User user) {
        // 根据用户名查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, user.getUsername());
        User loginUser = this.baseMapper.selectOne(wrapper);
        // 结果不为空,并且密码和传入匹配,则生成token,并将用户信息存入redis
        if (loginUser != null && passwordEncoder.matches(user.getPassword(), loginUser.getPassword())) {
            String key = loginUser.getUsername()+ UUID.randomUUID();

            loginUser.setPassword(null);

            redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);

            HashMap<String, Object> data = new HashMap<>();
            data.put("token", key);
            return data;
        }
        return null;
    }
7.3 修改用户

  此处不提供密码更新,自行扩展,可以去实现前端右上角菜单的个人信息功能

  1. 后端添加updateUser接口,getUserById接口
    @PutMapping
    @ResponseBody
    public Result<?> updateUser(@RequestBody User user) {
        user.setPassword(null);
        // updateById方法默认:如果该字段为空不更新,password不更新       
        userService.updateById(user);
        return Result.success("修改用户成功");
    }

    @GetMapping("/{id}")
    @ResponseBody
    public Result<User> getUserById(@PathVariable("id") Integer id) {
        return Result.success(userService.getById(id));
    }
  2. 前端API文档,代码复用
  getUserById(id) {
    return request({
      // url:'url' + id
      url: `/user/${id}`,
      method: 'get'
    })
  },
  saveUser(user) {
    if (user.id == null && user.id === undefined) {
      return this.addUser(user)
    }
    return this.updateUser(user)
  },
  updateUser(user) {
    return request({
      url: '/user',
      method: 'put',
      data: user
    })
  }
<template>
  <div>
    <!--  搜索栏  -->
    <el-card id="search">
      <el-row>
        <el-col :span="20">
          <el-input v-model="searchModel.username" placeholder="用户名" clearable />
          <el-input v-model="searchModel.phone" placeholder="电话" clearable />
          <el-button type="primary" round icon="el-icon-search" @click="getUserList">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button type="primary" icon="el-icon-plus" circle @click="openEditUI(null)" />
        </el-col>
      </el-row>
    </el-card>
    <!--  结果列表  -->
    <el-card>
      <el-table
        :data="userList"
        stripe
        style="width: 100%"
      >
        <el-table-column
          label="#"
          width="180"
        >
          <!--   作用域插槽     -->
          <template slot-scope="scope">
            <!--    (pageNo-1)*pageSize + index +1    -->
            {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          label="用户ID"
          width="180"
        />
        <el-table-column
          prop="username"
          label="用户名"
          width="180"
        />
        <el-table-column
          prop="phone"
          label="电话"
          width="180"
        />
        <el-table-column
          prop="status"
          label="用户状态"
          width="180"
        >
          <template slot-scope="scope">
            <el-tag v-if="scope.row.status===1">正常</el-tag>
            <el-tag v-else type="danger">禁用</el-tag>
          </template>
        </el-table-column>
        <!--    剩下宽度给电子邮件    -->
        <el-table-column
          prop="email"
          label="电子邮件"
        />
        <el-table-column
          label="操作"
          width="180"
        >
          <template slot-scope="scope">
            <el-button type="primary" icon="el-icon-edit" size="mini" circle @click="openEditUI(scope.row.id)" />
            <el-button type="danger" icon="el-icon-delete" size="mini" circle />
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!--  底部pagination分页栏  -->
    <el-pagination
      :current-page="searchModel.pageSize"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
    <!--  用户编辑新增对话框  -->
    <el-dialog :title="title" :visible.sync="dialogFormVisible" @close="clearForm">
      <el-form ref="userFormRef" :model="userForm" :rules="rules">
        <el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
          <el-input v-model="userForm.username" autocomplete="off" />
        </el-form-item>
        <el-form-item v-if="userForm.id==null || userForm.id==undefined" label="登录密码" prop="password" :label-width="formLabelWidth">
          <el-input v-model="userForm.password" type="password" autocomplete="off" />
        </el-form-item>
        <el-form-item label="联系电话" :label-width="formLabelWidth">
          <el-input v-model="userForm.phone" autocomplete="off" />
        </el-form-item>
        <el-form-item label="用户状态" :label-width="formLabelWidth">
          <el-switch
            v-model="userForm.status"
            :active-value="1"
            inactive-value="0"
          />
        </el-form-item>
        <el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
          <el-input v-model="userForm.email" autocomplete="off" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveUser">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import userApi from '../../api/userManager'

export default {
  data() {
    return {
      formLabelWidth: '20%',
      userForm: {},
      dialogFormVisible: false,
      title: '',
      total: 0,
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      userList: [],
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入登录初始密码', trigger: 'blur' },
          { min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入电子邮件', trigger: 'blur' },
          { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  created() {
    this.getUserList()
  },
  methods: {
    handleSizeChange(pageSize) {
      this.searchModel.pageSize = pageSize
      this.getUserList()
    },
    handleCurrentChange(pageNo) {
      this.searchModel.pageNo = pageNo
      this.getUserList()
    },
    getUserList() {
      userApi.getUserList(this.searchModel).then(res => {
        this.userList = res.data.rows
        this.total = res.data.total
      })
    },
    openEditUI(id) {
      if (id == null) {
        this.title = '新增用户'
      } else {
        this.title = '修改用户'
        // 根据id查询用户数据
        userApi.getUserById(id).then(res => {
          this.userForm = res.data
        })
      }
      this.dialogFormVisible = true
    },
    clearForm() {
      this.userForm = {}
      // 关闭对话框清除验证
      this.$refs.userFormRef.clearValidate()
    },
    saveUser() {
      // 1. 触发表单验证
      this.$refs.userFormRef.validate((valid) => {
        if (valid) {
          // 2.提交请求给后台
          userApi.saveUser(this.userForm).then(res => {
            // 成功提示
            this.$message({
              message: res.message,
              type: 'success'
            })
            // 关闭对话框
            this.dialogFormVisible = false
            // 刷新表格
            this.getUserList()
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
      // 3. 关闭对话框
      this.dialogFormVisible = false
    }
  }
}
</script>

<style scoped>
#search .el-input {
  width: 200px;
  margin-right: 20px;
}

.el-dialog .el-input {
  width: 80%;
}
</style>

7.4 删除用户

  处理业务的时候涉及到物理删除和逻辑删除

  方式一:物理删除:
    @DeleteMapping("/{id}")
    @ResponseBody
    public Result<User> deleteUserById(@PathVariable("id") Integer id){
        userService.removeById(id);
        return Result.success("删除用户成功");
    }
  方式二:利用MyBatisPlus做逻辑删除处理(userController.java不变)
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  前端添加API,user.vue
  deleteUserById(id) {
    return request({
      // url:'url' + id
      url: `/user/${id}`,
      method: 'delete'
    })
  }
<template>
  <div>
    <!--  搜索栏  -->
    <el-card id="search">
      <el-row>
        <el-col :span="20">
          <el-input v-model="searchModel.username" placeholder="用户名" clearable />
          <el-input v-model="searchModel.phone" placeholder="电话" clearable />
          <el-button type="primary" round icon="el-icon-search" @click="getUserList">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button type="primary" icon="el-icon-plus" circle @click="openEditUI(null)" />
        </el-col>
      </el-row>
    </el-card>
    <!--  结果列表  -->
    <el-card>
      <el-table
        :data="userList"
        stripe
        style="width: 100%"
      >
        <el-table-column
          label="#"
          width="180"
        >
          <!--   作用域插槽     -->
          <template slot-scope="scope">
            <!--    (pageNo-1)*pageSize + index +1    -->
            {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          label="用户ID"
          width="180"
        />
        <el-table-column
          prop="username"
          label="用户名"
          width="180"
        />
        <el-table-column
          prop="phone"
          label="电话"
          width="180"
        />
        <el-table-column
          prop="status"
          label="用户状态"
          width="180"
        >
          <template slot-scope="scope">
            <el-tag v-if="scope.row.status===1">正常</el-tag>
            <el-tag v-else type="danger">禁用</el-tag>
          </template>
        </el-table-column>
        <!--    剩下宽度给电子邮件    -->
        <el-table-column
          prop="email"
          label="电子邮件"
        />
        <el-table-column
          label="操作"
          width="180"
        >
          <template slot-scope="scope">
            <el-button type="primary" icon="el-icon-edit" size="mini" circle @click="openEditUI(scope.row.id)" />
            <el-button type="danger" icon="el-icon-delete" size="mini" circle @click="deleteUser(scope.row)" />
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!--  底部pagination分页栏  -->
    <el-pagination
      :current-page="searchModel.pageSize"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
    <!--  用户编辑新增对话框  -->
    <el-dialog :title="title" :visible.sync="dialogFormVisible" @close="clearForm">
      <el-form ref="userFormRef" :model="userForm" :rules="rules">
        <el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
          <el-input v-model="userForm.username" autocomplete="off" />
        </el-form-item>
        <el-form-item v-if="userForm.id==null || userForm.id==undefined" label="登录密码" prop="password" :label-width="formLabelWidth">
          <el-input v-model="userForm.password" type="password" autocomplete="off" />
        </el-form-item>
        <el-form-item label="联系电话" :label-width="formLabelWidth">
          <el-input v-model="userForm.phone" autocomplete="off" />
        </el-form-item>
        <el-form-item label="用户状态" :label-width="formLabelWidth">
          <el-switch
            v-model="userForm.status"
            :active-value="1"
            inactive-value="0"
          />
        </el-form-item>
        <el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
          <el-input v-model="userForm.email" autocomplete="off" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveUser">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import userApi from '../../api/userManager'

export default {
  data() {
    return {
      formLabelWidth: '20%',
      userForm: {},
      dialogFormVisible: false,
      title: '',
      total: 0,
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      userList: [],
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入登录初始密码', trigger: 'blur' },
          { min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入电子邮件', trigger: 'blur' },
          { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  created() {
    this.getUserList()
  },
  methods: {
    handleSizeChange(pageSize) {
      this.searchModel.pageSize = pageSize
      this.getUserList()
    },
    handleCurrentChange(pageNo) {
      this.searchModel.pageNo = pageNo
      this.getUserList()
    },
    getUserList() {
      userApi.getUserList(this.searchModel).then(res => {
        this.userList = res.data.rows
        this.total = res.data.total
      })
    },
    openEditUI(id) {
      if (id == null) {
        this.title = '新增用户'
      } else {
        this.title = '修改用户'
        // 根据id查询用户数据
        userApi.getUserById(id).then(res => {
          this.userForm = res.data
        })
      }
      this.dialogFormVisible = true
    },
    clearForm() {
      this.userForm = {}
      // 关闭对话框清除验证
      this.$refs.userFormRef.clearValidate()
    },
    saveUser() {
      // 1. 触发表单验证
      this.$refs.userFormRef.validate((valid) => {
        if (valid) {
          // 2.提交请求给后台
          userApi.saveUser(this.userForm).then(res => {
            // 成功提示
            this.$message({
              message: res.message,
              type: 'success'
            })
            // 关闭对话框
            this.dialogFormVisible = false
            // 刷新表格
            this.getUserList()
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
      // 3. 关闭对话框
      this.dialogFormVisible = false
    },
    deleteUser(user) {
      this.$confirm(`您确认删除用户 ${user.username}`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        userApi.deleteUserById(user.id).then(res => {
          this.$message({
            type: 'success',
            message: res.message
          })
          this.getUserList()
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    }
  }
}
</script>

<style scoped>
#search .el-input {
  width: 200px;
  margin-right: 20px;
}

.el-dialog .el-input {
  width: 80%;
}
</style>

完结撒花!!!代码在:

【资源说明】 1、该资源包括项目的全部源码,下载可以直接使用! 2、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 3、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip 基于springboot+vue开发前后端分离的开源开发框架源码.zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值