【SpringBoot+Vue】x-admin管理系统跟做

技术栈

前端技术说明
Vue前端框架
Vuex全局状态管理框架
ElementUI前端UI框架
Axios前端HTTP框架
vue-element-admin项目脚手架
后端技术说明
SpringBoot容器+MVC框架
MyBatisORM框架
MyBatis-plusMyBatis增强工具
Redis非关系型数据库

数据库准备

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `menu_id` int(11) NOT NULL AUTO_INCREMENT,
  `component` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `redirect` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `parent_id` int(11) NULL DEFAULT NULL,
  `is_leaf` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `hidden` tinyint(1) NULL DEFAULT NULL,
  PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 'Layout', '/user', '/user/list', 'userManage', '用户管理', 'userManage', 0, 'N', 0);
INSERT INTO `sys_menu` VALUES (2, 'user/user', 'list', NULL, 'userList', '用户列表', 'userList', 1, 'Y', 0);
INSERT INTO `sys_menu` VALUES (3, 'user/role', 'role', NULL, 'roleList', '角色列表', 'role', 1, 'Y', 0);
INSERT INTO `sys_menu` VALUES (4, 'user/permission', 'permission', NULL, 'permissionList', '权限列表', 'permission', 1, 'Y', 0);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `role_id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `role_desc` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '超级管理员');
INSERT INTO `sys_role` VALUES (2, 'hr', '人事专员');
INSERT INTO `sys_role` VALUES (3, 'normal', '普通员工');

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NULL DEFAULT NULL,
  `menu_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` int(1) NULL DEFAULT NULL,
  `avater` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `deleted` int(1) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin1', '$2a$10$QSDIu2LNrHsj.YqC2rrwEOtTCcoZWUphwRbwe0VIKVkJXaMD2Qo8y', '123456', '12345456', 1, '', 0);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

后端框架装备

1.项目框架搭建

创建springboot 2.6项目

在这里插入图片描述

编辑pom.xml文件导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cyfy</groupId>
    <artifactId>x-admin-serve</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>x-admin-serve</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>
    <dependencies>
        <!-- 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>
         <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.7</version>
        </dependency>
        <!--    密码加密工具    -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.cyfy.XAdminServeApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

编辑src/main/resources目录下的application.yml配置文件(没有就创建)

server:
  port:8080

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql:///my_demo
  redis:
    port: 6379
    host: localhost

logging:
  level:
    com.cyfy: debug
    
#  设置逻辑删除字段
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

在这里插入图片描述

运行项目。查看是否可正常启动

在这里插入图片描述

2.代码生成器

在src/test/java目录下,创建CodeGenerator.java文件,编写引用mybatis-plus代码生成器生成代码

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

public class CodeGenerator {
    public static void main(String[] args) {
        String url = "jdbc:mysql:///my_demo";   // 数据库链接地址
        String username = "root";               // 数据库用户名
        String password = "123456";             // 数据库密码
        String moduleName = "sys";              // 父包模块
        String project_abs_path = "E:\\项目\\神盾管理系统\\x-admin\\x-admin-serve\\src\\main\\";  // 项目绝对路径
        String mapperLocation = project_abs_path + "resources\\mapper\\" + moduleName;   // xxxMapper.xml文件输出地址
        String tables = "sys_user,sys_role,sys_menu,sys_user_role,sys_role_menu";     // 需要生成代码的表
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author("cyfy") // 设置作者
                         // .enableSwagger() // 开启 swagger 模式
                            .outputDir(project_abs_path + "java"); // 指定输出目录
                })
                .packageConfig(builder ->
                        builder.parent("com.cyfy") // 设置父包名
                                .moduleName(moduleName) // 设置父包模块名
                                .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)) // 设置mapperXml生成路径
                )
                .strategyConfig(builder ->
                        builder.addInclude(tables) // 设置需要生成的表名
                                .addTablePrefix("sys_") // 设置过滤表前缀
                )
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

执行生成器代码后,项目新增对应代码文件

在这里插入图片描述

测试

编辑UserController.java文件,编写获取用户表数据接口

import com.cyfy.sys.entity.User;
import com.cyfy.sys.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;

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

修改项目启动器文件,增加@MapperScan注解

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.cyfy.*.mapper")
public class XAdminServeApplication {
    public static void main(String[] args) {
        SpringApplication.run(XAdminServeApplication.class, args);
    }
}

运行项目,访问接口是否正常

在这里插入图片描述

后端开发

1.公共响应类

在com/xxx目录下新建common/vo目录,并新建Result.java文件处理返回给前端的数据

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

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

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

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

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

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

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

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

    public static<T> Result<T> fail( String Message){
        return new Result<>(20001,Message,null);
    }
}

测试:修改getAllUser()方法

    @GetMapping("/all")
    public Result<List<User>> getAllUser(){
        List<User> list = userService.list();
        return Result.success(list,"查询成功");
    }

运行效果

在这里插入图片描述

2.Redis配置

在common目录下新建tools目录,并新建MyRedisConfig.java文件

package com.cyfy.common.tools;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

@Configurable
public class MyRedisConfig {

    @Resource
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate redisTemplate(){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 设置键的序列化方法
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<Object>(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(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
        om.configure(MapperFeature.USE_ANNOTATIONS,false);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        serializer.setObjectMapper(om);
        return redisTemplate;
    }
}

3.跨域处理

在common/tools目录下,新建MyCorsConfig.java文件,用于处理跨域请求问题

package com.cyfy.common.tools;

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.1. 允许的域,不要写*,否则cookie就无法使用
        config.addAllowedOrigin("http://localhost:8888"); // 这里填写请求的前端服务器
        // 1.2. 是否发送cookie信息
        config.setAllowCredentials(true);
        // 1.3. 允许的请求方式,常用方式有GET、POST
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        // 1.4. 允许的头信息
        config.addAllowedHeader("*");

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

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

也可在控制器类上加@CrossOrigin注解处理,减少配置类

4.mybatis-plus拦截器

在common/tools目录下,新建MpConfig.java文件

package com.cyfy.common.tools;

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;
    }
}

5.用户相关接口

编写UserController.java文件

package com.cyfy.sys.controller;

import com.cyfy.common.vo.Result;
import com.cyfy.sys.entity.User;
import com.cyfy.sys.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

/**
 *  前端控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;

    /**
     * 用户登录接口
     */
    @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,"用户名或密码错误");
    }

    /**
     * 用户信息查询接口
     */
    @GetMapping("/info")
    public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){
        Map<String,Object> data = userService.getUserInfo(token);

        if(data != null){
            return Result.success(data);
        }
        return Result.fail(20003,"登录信息已失效,请重新登录");
    }

    /**
     * 用户注销接口
     */
    @PostMapping("/logout")
    public Result<?> logout(@RequestHeader("X-Token") String token){
        userService.logout(token);
        return Result.success();
    }

    /**
     * 获取用户列表接口
     */
    @GetMapping("/list")
    public Result<Map<String,Object>> getUserList(@RequestParam(value = "username",required = false) String username,
                                                  @RequestParam(value = "phone",required = false) String phone,
                                                  @RequestParam(value = "pageNo") Long pageNo,
                                                  @RequestParam(value = "pageSize") Long pageSize){
        Map<String, Object> data = userService.getUserList(username,phone,pageNo,pageSize);
        return Result.success(data);
    }

    @PostMapping("/add")
    public Result<?> addUser(@RequestBody User user){
        if(userService.addUser(user)) {
            return Result.success("新增用户成功");
        }
        return Result.fail("新增用户失败");
    }

    @PutMapping("/upd")
    public Result<?> updateUser(@RequestBody User user){
        if(userService.updateUser(user)) {
            return Result.success("修改用户成功");
        }
        return Result.fail("修改用户失败");
    }

    @GetMapping("/{id}")
    public Result<User> getUserById(@PathVariable("id")Integer id){
        User data = userService.getUserById(id);
        return Result.success(data);
    }

    @DeleteMapping("/{id}")
    public Result<User> deleteUserById(@PathVariable("id")Integer id){
        if(userService.deleteUserById(id)){
            return Result.success("删除用户成功");
        }
        return Result.fail("删除用户失败");
    }
}

编写IUserService.java文件

package com.cyfy.sys.service;

import com.cyfy.sys.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.Map;

/**
 *  服务类
 */
public interface IUserService extends IService<User> {
    // 用户登录
    Map<String,Object> login(User user);
    // 根据token获取用户信息
    Map<String, Object> getUserInfo(String token);
    // 注销登录
    void logout(String token);
    // 获取用户列表
    Map<String, Object> getUserList(String username,String phone,Long pageNo,Long pageSize);
    // 添加用户
    boolean addUser(User user);
    // 修改用户
    boolean updateUser(User user);
    // 根据id查询用户
    User getUserById(Integer id);
    // 根据id删除指定用户
    boolean deleteUserById(Integer id);
}

编写UserServiceImpl.java文件

package com.cyfy.sys.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cyfy.sys.entity.User;
import com.cyfy.sys.mapper.UserMapper;
import com.cyfy.sys.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 *  服务实现类
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Map<String, Object> login(User user) {
        // 根据用户名查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername,user.getUsername());
        // 根据username查询是否存在对应用户
        User loginUser = this.baseMapper.selectOne(wrapper);
        System.out.println(user.getPassword());
        System.out.println(loginUser.getPassword());

        // 结果不为空时,且加密密码正确:生成token,并将用户信息存入redis
        if(loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())){
            // 这里暂时使用UUID,最终需要使用jwt
            String key = "user" + UUID.randomUUID();

            loginUser.setPassword(null);    // 将密码设置为空,不存入redis
            // 存入redis
            redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);

            // 返回处理好的token
            Map<String,Object> data = new HashMap<>();
            data.put("token",key);
            return data;
        }
        return null;
    }

    @Override
    public Map<String, Object> getUserInfo(String token) {
        // 根据Token从Redis中获取用户信息
        Object obj = redisTemplate.opsForValue().get(token);

        if(obj != null){
            // 将字符串转为User对象
            User loginUser = JSON.parseObject(JSON.toJSONString(obj),User.class);
            Map<String,Object> data = new HashMap<>();
            data.put("name",loginUser.getUsername());
            data.put("avatar",loginUser.getAvater());
            // 通过ID查询用户角色
            List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
            data.put("roles",roleList);
            return data;
        }
        return null;
    }

    @Override
    public void logout(String token) {
        // 从redis中清除指定token
        redisTemplate.delete(token);
    }

    @Override
    public Map<String, Object> getUserList(String username,String phone,Long pageNo,Long pageSize) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
        wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);

        // 分页处理
        Page<User> page = new Page<>(pageNo,pageSize);
        this.page(page,wrapper);
        Map<String,Object> data = new HashMap<>();
        data.put("total",page.getTotal());
        data.put("row",page.getRecords());
        return data;
    }

    @Override
    public boolean addUser(User user) {
        // 根据用户名查询
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername,user.getUsername());
        // 根据username查询是否存在对应用户
        User loginUser = this.baseMapper.selectOne(wrapper);
        // 如果用户名存在,返回false
        if(loginUser != null){
            return false;
        }
        //  加密密码
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        return this.save(user);
    }

    @Override
    public boolean updateUser(User user) {
        //  加密密码
        user.setPassword(null);
        return this.updateById(user);
    }

    @Override
    public User getUserById(Integer id) {
        User user = this.getById(id);
        user.setPassword(null);
        return user;
    }

    @Override
    public boolean deleteUserById(Integer id) {
        // 先查询用户是否存在
        if(this.getById(id) != null){
            return this.removeById(id);
        }
        return false;
    }
}

编写UserMapper.java文件

package com.cyfy.sys.mapper;

import com.cyfy.sys.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.List;

/**
 *  Mapper 接口
 */
public interface UserMapper extends BaseMapper<User> {
    List<String> getRoleNameByUserId(Integer id);
}

编写UserMapper.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cyfy.sys.mapper.UserMapper">
    <select id="getRoleNameByUserId" parameterType="Integer" resultType="String">
        SELECT
            b.`role_name`
        FROM  sys_user_role a, sys_role b
        WHERE
            a.role_id = b.`role_id` AND a.user_id = #{userId}
    </select>
</mapper>

修改XAdminServeApplication.java文件,加个加密方法

package com.cyfy;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootApplication
@MapperScan("com.cyfy.*.mapper")
public class XAdminServeApplication {

    public static void main(String[] args) {
        SpringApplication.run(XAdminServeApplication.class, args);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

使用Postman测试接口(记得开启redis

在这里插入图片描述

前端框架准备

1.脚手架搭建

详见【Vue】vue-admin-template项目搭建,需保持node版本一致,可以使用nvm进行版本管理

2.使用VS Code打开项目

在这里插入图片描述

安装依赖:npm install --no-fund

在这里插入图片描述

运行项目:npm run dev

在这里插入图片描述

在这里插入图片描述

3.修改后端请求接口

修改.env.development文件

# just a flag
ENV = 'development'

# base api
VUE_APP_BASE_API = 'http://localhost:8080'

修改vue.config.js文件,注释使用模拟数据

// before: require('./mock/mock-server.js')  // 模拟数据

测试:修改src/api/user.js文件,更正请求url

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/user/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/user/logout',
    method: 'post'
  })
}

运行效果:可通过数据库的账号登录

在这里插入图片描述

前端开发

1.登录页修改

修改src/views/login目录下的index.vue文件(没啥修改内容,就是将英文内容改为中文,稍微调整一下布局)

<template>
  <div class="login-container">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">

      <div class="title-container">
        <h3 class="title">欢迎登录神盾局管理系统</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="用户名"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="密码"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
        </span>
      </el-form-item>

      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
    </el-form>
  </div>
</template>

<script>
import { validUsername } from '@/utils/validate'

export default {
  name: 'Login',
  data() {
    const validateUsername = (rule, value, callback) => {
      if (!validUsername(value)) {
        callback(new Error('请输入正确的用户名'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (value.length < 6) {
        callback(new Error('密码不能少于6位'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
      loading: false,
      passwordType: 'password',
      redirect: undefined
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },
  methods: {
    showPwd() {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' })
            this.loading = false
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
  }
}
</script>

<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg:#283443;
$light_gray:#fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>

<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  display: flex;                    // 布局类型:弹性布局
  align-items: center;              // 调整元素在侧轴的对齐方式:居中
  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 35px 50px 20px;        // 内边距:上 中 下
    margin: 0 auto;                 // 外边距
    overflow: hidden;
    background-color: #283443;  // 设置背景色
    border-radius: 8px;             // 设置边框圆角
    opacity: 0.9;                   // 设置透明度
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

运行效果

在这里插入图片描述

2.菜单初始化

在src/views目录下创建sys模块目录、test模块目录(充数,暂不实现)

在sys模块目录下创建user/index.vuerole/index.vue两个组件文件,test模块目录下创建test1.vuetest2.vuetest3.vue

在这里插入图片描述

修改src/router/index.js文件,修改路由配置

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'

export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' }
    }]
  },

  {
    path: '/sys',
    component: Layout,
    redirect: '/sys/user',
    name: 'sysMange',
    meta: { title: '系统管理', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'user',
        name: 'User',
        component: () => import('@/views/sys/user/index'),
        meta: { title: '用户管理', icon: 'table' }
      },
      {
        path: 'role',
        name: 'Role',
        component: () => import('@/views/sys/role/index'),
        meta: { title: '角色管理', icon: 'tree' }
      }
    ]
  },

  {
    path: '/test',
    component: Layout,
    redirect: '/test/test1',
    name: 'test',
    meta: { title: '测试模块', icon: 'form' },
    children: [
      {
        path: 'test1',
        name: 'Test1',
        component: () => import('@/views/test/test1'),
        meta: { title: '功能点一', icon: 'form' }
      },
      {
        path: 'test2',
        name: 'Test2',
        component: () => import('@/views/test/test2'),
        meta: { title: '功能点二', icon: 'form' }
      },
      {
        path: 'test3',
        name: 'Test3',
        component: () => import('@/views/test/test3'),
        meta: { title: '功能点三', icon: 'form' }
      },
    ]
  },

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

运行效果

在这里插入图片描述

3.标签栏导航

3.1.复制对应文件到对应目录

@/layout/components/TagsView

@/store/modules/tagsView.js

@/store/modules/permission.js

3.2.修改@/layoutcomponents/AppMain.vue文件

<template>
  <section class="app-main">
    <transition name="fade-transform" mode="out-in">
      <!-- 加入标签栏组件  -->
      <keep-alive :include="cachedViews">
        <router-view :key="key" />
      </keep-alive>
    </transition>
  </section>
</template>

<script>
export default {
  name: 'AppMain',
  computed: {
    key() {
      return this.$route.path
    },
    cachedViews(){
      return this.$store.state.tagsView.cachedViews
    }
  }
}
</script>

3.3.修改@store/getters.js文件

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,

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

export default getters

3.4.修改@layout/components/index.js文件

export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
export { default as TagsView } from './TagsView/index.vue'

3.5.修改@store/index.js文件

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    tagsView,
    permission
  },
  getters
})

export default store

3.6.Affix固钉

修改@/router/index.js文件,在需要固定的标签页中添加affix: true属性

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' ,affix: true }
    }]
  },

运行效果

在这里插入图片描述

4.用户管理页实现

编写views/sys/user/index.vue文件

<template>
  <div>
    <!-- 搜索栏 -->
    <el-card class="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 @click="getUserList" type="primary" round icon="el-icon-search">查询</el-button>
        </el-col>
        <el-col :span="4" align="right">
          <el-button @click="openUserVisible(null)" type="primary" icon="el-icon-plus" circle></el-button>
        </el-col>
      </el-row>
    </el-card>
    <!-- 结果列表 -->
    <el-card>
      <el-table :data="userList" stripe style="width:100%">
        <el-table-column label="#" width="80">
          <template slot-scope="scope">
            {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column prop="id" label="用户ID" width="80" />
        <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 @click="openUserVisible(scope.row.id)" type="primary" icon="el-icon-edit" size="mini" circle />
            <el-button @click="deleteUser(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle />
          </template>
        </el-table-column>
      </el-table>
    </el-card>

    <!-- 分页组件 -->
    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
      :current-page="searchModel.pageNo" :page-sizes="[5, 10, 20, 50]" :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper" :total="total">
    </el-pagination>

    <!-- 用户信息编辑对话框 -->
    <el-dialog @close="clearUserForm" :title="title" :visible.sync="userFormVisible">
      <el-form :model="userForm" ref="userForm" :rules="rules">
        <el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
          <el-input v-model="userForm.username" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item v-if="userForm.id == null || userForm.id === undefined" label="登录密码" prop="password" :label-width="formLabelWidth">
          <el-input type="password" v-model="userForm.password" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="联系电话" :label-width="formLabelWidth">
          <el-input v-model="userForm.phone" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="状态" :label-width="formLabelWidth">
          <el-switch
            v-model="userForm.status"
            :active-value="1"
            :inactive-value="0"
          ></el-switch>
        </el-form-item>
        <el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
          <el-input v-model="userForm.email" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="userFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveUserFrom">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import userApi from '@/api/userManage'
export default {
  components: {
  },
  data() {
    // 自定义验证规则
    var checkEmail = (rule, value, callback)=>{
      var reg = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*\.[a-z]{2,}$/
      if(!reg.test(value)){
        return callback(new Error('电子邮箱格式错误'));
      }
      callback();
    }
    return {
      total: 0,
      searchModel: {
        username: "",
        phone: "",
        pageNo: 1,
        pageSize: 10,
      },
      userList: [],
      title:"",
      userFormVisible: false,
      userForm:{},
      formLabelWidth: '130px',
      rules:{
        username:[
          {required:true, message: '请输入用户名', trigger: 'blur'},
          {min:5,max: 12, message: '长度在5到12个字符', trigger: 'blur'},
        ],
        password:[
          {required:true, message: '请输入初始登录密码', trigger: 'blur'},
          {min:6,max: 16, message: '长度在6到16个字符', trigger: 'blur'},
        ],
        email:[
          {required:true, message: '请输入电子邮箱', trigger: 'blur'},
          {validator:checkEmail, trigger: 'blur'},
        ],
      }
    }
  },
  methods: {
    // 删除用户
    deleteUser(user){
      this.$confirm(`你确认删除用户${user.username}?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          userApi.deleteUserById(user.id).then(response => {
            this.$message({
              type: 'success',
              message: response.message
            });
            this.getUserList();
          })
          
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });          
        });
    },
    // 添加用户
    saveUserFrom(){
      // 触发表单验证
      this.$refs.userForm.validate((valid)=>{
        if(valid){
          // 提交请求给后台
          userApi.saveUser(this.userForm).then(response =>{
            // 成功提示
            this.$message({
              message: response.message,
              type: 'success'
            });
            // 关闭对话框
            this.userFormVisible = false;
            // 刷新表格
            this.getUserList();
          })
        }else{
            this.$message({
              message: "数据提交错误",
              type: 'error'
            });
          return false;
        }
      })
      
    },
    handleSizeChange(pageSize) {
      this.searchModel.pageSize = pageSize;
      this.getUserList();
    },
    handleCurrentChange(pageNo) {
      this.searchModel.pageSize = pageNo;
      this.getUserList();
    },
    getUserList() {
      userApi.getUserList(this.searchModel).then(response => {
        this.userList = response.data.row;
        this.total = response.data.total;
      });
    },
    openUserVisible(id) {
      if(id === null){
        this.title = "新增用户";
      }else{
        this.title = "修改用户";
        // 根据id查询用户
        userApi.getUserById(id).then(response =>{
          this.userForm = response.data
        })
      }
      this.userFormVisible = true;
    },
    clearUserForm(){
      this.userForm = {}
      this.$refs.userForm.clearValidate();
    }
  },
  created() {
    this.getUserList();
  }
}
</script>

<style scoped>
.search .el-input {
  width: 200px;
  margin-right: 10px;
}
.el-dialog .el-input{
  width: 85%;
}
</style>

在src/api目录下新建userManage.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,
      }
    })
  },
  addUser(user){
    return request({
      url: '/user/add',
      method: 'post',
      data:user
    })
  },
  saveUser(user){
    if(user.id === null || user.id === undefined){
      return this.addUser(user)
    }else{
      return this.updateUser(user);
    }
  },
  updateUser(user){
    return request({
      url: '/user/upd',
      method: 'put',
      data: user
    })
  },
  getUserById(id){
    return request({
      url: `/user/${id}`,
      method: 'get'
    })
  },
  deleteUserById(id){
    return request({
      url: `/user/${id}`,
      method: 'delete'
    })
  },
}

5.用户管理页组件优化

当前页面所有内容都集中在index.vue中,代码多看起码繁杂,不适合后续拓展及优化,所有需内容按模块拆分成组件文件

在src/utils目录下新建eventbus.js文件,实现组件之间数据交互的方法

import Vue from "vue"
const EventBus = new Vue();
Object.defineProperties(Vue.prototype,{
    $bus:{
        get(){
            return EventBus;
        }
    }
})

main.js文件中引入eventbus

// 引入eventbus
import "./utils/eventbus"

在src/views/sys/user目录下新建components目录,用于存放当前页面的组件文件

新建SearchBar.vue文件,用于存放搜索框部分

<template>
    <!-- 搜索栏 -->
    <el-card class="search">
        <el-row>
            <el-col :span="20">
                <el-input v-model="searchContext.username" placeholder="用户名" clearable />
                <el-input v-model="searchContext.phone" placeholder="电话" clearable />
                <el-button @click="searchUser" type="primary" round icon="el-icon-search">查询</el-button>
            </el-col>
            <el-col :span="4" align="right">
                <el-button @click="openUserVisible" type="primary" icon="el-icon-plus" circle></el-button>
            </el-col>
        </el-row>
    </el-card>
</template>

<script>
export default {
    data(){
        return {
            searchContext: {
                username: "",
                phone: "",
            },
        }
    },
    methods:{
        searchUser(){
            this.$bus.$emit("searchUser",this.searchContext)
        },
        openUserVisible(){
            this.$bus.$emit("openUserVisible",null)
        }
    }
}
</script>

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

新建ResultList.vue文件,用于存放结果展示部分

<template>
  <!-- 结果列表 -->
  <el-card>
    <el-table :data="userList" stripe style="width:100%">
      <el-table-column label="#" width="80">
        <template slot-scope="scope">
          {{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="id" label="用户ID" width="80" />
      <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 @click="openUserVisible(scope.row.id)" type="primary" icon="el-icon-edit" size="mini" circle />
          <el-button @click="deleteUser(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle />
        </template>
      </el-table-column>
    </el-table>
  </el-card>
</template>

<script>
import userApi from '@/api/userManage'
export default {
  data() {
    return {
      userList: [],
      searchModel: {
        pageNo: 1,
        pageSize: 10,
      },
    }
  },
  methods: {
    // 删除用户
    deleteUser(user) {
      this.$confirm(`你确认删除用户${user.username}?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        userApi.deleteUserById(user.id).then(response => {
          this.$message({
            type: 'success',
            message: response.message
          });
          this.getUserList();
        })

      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    // 获取数据
    getUserList() {
      userApi.getUserList(this.searchModel).then(response => {
        this.userList = response.data.row;
        this.$bus.$emit("getTotal",response.data.total)
      });
    },
    // 修改用户
    openUserVisible(id){
      console.log(1111111111111);
      this.$bus.$emit("openUserVisible",id)
    }
  },
  mounted(){
    // 查询数据
    this.$bus.$on("searchUser",searchContext =>{
      this.searchModel.username = searchContext.username;
      this.searchModel.phone = searchContext.phone;
      this.getUserList();
    })
    // 更新页面
    this.$bus.$on("refresh",() =>{
      this.getUserList();
    })
    // 分页
    this.$bus.$on("page_turning",searchModel =>{
      this.searchModel.pageNo = searchModel.pageNo;
      this.searchModel.pageSize = searchModel.pageSize;
      this.getUserList();
    })
  },
  created() {
    this.getUserList();
  }
}
</script>

新建ResultPage.vue文件,用于存放分页部分

<template>
    <!-- 分页组件 -->
    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
        :current-page="searchModel.pageNo" :page-sizes="[5, 10, 20, 50]" :page-size="searchModel.pageSize"
        layout="total, sizes, prev, pager, next, jumper" :total="total">
    </el-pagination>
</template>

<script>
export default {
    data() {
        return {
            total: 0,
            searchModel: {
                pageNo: 1,
                pageSize: 10,
            }
        }
    },
    methods: {
        handleSizeChange(pageSize) {
            this.searchModel.pageSize = pageSize;
            this.$bus.$emit("page_turning",this.searchModel)
        },
        handleCurrentChange(pageNo) {
            this.searchModel.pageNo = pageNo;
            this.$bus.$emit("page_turning",this.searchModel)
        },
    },
    mounted(){
        this.$bus.$on("getTotal",total =>{
            this.total = total
        })
    }
}
</script>

新建UserDialog.vue文件,用于存放用户信息编辑对话框部分

<template>
    <div>
        <!-- 用户信息编辑对话框 -->
        <el-dialog @close="clearUserForm" :title="title" :visible.sync="userFormVisible">
            <el-form :model="userForm" ref="userForm" :rules="rules">
                <el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
                    <el-input v-model="userForm.username" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item v-if="userForm.id == null || userForm.id === undefined" label="登录密码" prop="password"
                    :label-width="formLabelWidth">
                    <el-input type="password" v-model="userForm.password" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item label="联系电话" :label-width="formLabelWidth">
                    <el-input v-model="userForm.phone" autocomplete="off"></el-input>
                </el-form-item>
                <el-form-item label="状态" :label-width="formLabelWidth">
                    <el-switch v-model="userForm.status" :active-value="1" :inactive-value="0"></el-switch>
                </el-form-item>
                <el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
                    <el-input v-model="userForm.email" autocomplete="off"></el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="userFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveUserFrom">确 定</el-button>
            </div>
        </el-dialog>
    </div>
</template>

<script>
import userApi from '@/api/userManage'
export default {
    data() {
        // 自定义验证规则
        var checkEmail = (rule, value, callback) => {
            var reg = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*\.[a-z]{2,}$/
            if (!reg.test(value)) {
                return callback(new Error('电子邮箱格式错误'));
            }
            callback();
        }
        return {
            title: "",
            userFormVisible: false,
            userForm: {},
            formLabelWidth: '130px',
            rules: {
                username: [
                    { required: true, message: '请输入用户名', trigger: 'blur' },
                    { min: 5, max: 12, message: '长度在5到12个字符', trigger: 'blur' },
                ],
                password: [
                    { required: true, message: '请输入初始登录密码', trigger: 'blur' },
                    { min: 6, max: 16, message: '长度在6到16个字符', trigger: 'blur' },
                ],
                email: [
                    { required: true, message: '请输入电子邮箱', trigger: 'blur' },
                    { validator: checkEmail, trigger: 'blur' },
                ],
            }
        }
    },
    methods: {
        // 添加或修改用户
        saveUserFrom() {
            // 触发表单验证
            this.$refs.userForm.validate((valid) => {
                if (valid) {
                    // 提交请求给后台
                    userApi.saveUser(this.userForm).then(response => {
                        // 成功提示
                        this.$message({
                            message: response.message,
                            type: 'success'
                        });
                        // 关闭对话框
                        this.userFormVisible = false;
                        // 刷新表格
                        this.$bus.$emit("refresh")
                    })
                } else {
                    this.$message({
                        message: "数据提交错误",
                        type: 'error'
                    });
                    return false;
                }
            })
        },
        // 根据是否存在id显示弹出信息
        openUserVisible(id) {
            if (id === null) {
                this.title = "新增用户";
            } else {
                this.title = "修改用户";
                // 根据id查询用户
                userApi.getUserById(id).then(response => {
                    this.userForm = response.data
                })
            }
            this.userFormVisible = true;
        },
        // 清除弹窗内容
        clearUserForm() {
            this.userForm = {}
            this.$refs.userForm.clearValidate();
        }
    },
    mounted(){
        this.$bus.$on("openUserVisible",id =>{
            console.log(22222222);
            this.openUserVisible(id)
        })
    }
}
</script>

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

修改user/index.vue文件

<template>
  <div>
    <SearchBar/>
    <ResultList/>
    <ResultPage/>
    <UserDialog/>
  </div>
</template>

<script>
import SearchBar from './components/SearchBar'
import ResultList from './components/ResultList'
import ResultPage from './components/ResultPage'
import UserDialog from './components/UserDialog'
export default {
  components: {
    SearchBar,ResultList,ResultPage,UserDialog
  }
}
</script>

运行效果正常

在这里插入图片描述

报错处理

1.使用TagsView组件控制台报错

报错截图

在这里插入图片描述

报错原因

未将TagsView所依赖的组件permission组件注册到store中,导致TagsView组件在找permission.routes时没找到

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927639.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI智算-正式上架GPU资源监控概览 Grafana Dashboard

下载链接 https://grafana.com/grafana/dashboards/22424-ai-gpu-20241127/

异步处理优化:多线程线程池与消息队列的选择与应用

目录 一、异步处理方式引入 &#xff08;一&#xff09;异步业务识别 &#xff08;二&#xff09;明确异步处理方式 二、多线程线程池&#xff08;Thread Pool&#xff09; &#xff08;一&#xff09;工作原理 &#xff08;二&#xff09;直面优缺点和适用场景 1.需要快…

用到动态库的程序运行过程

当我们写好了一段代码然后编译运行后会生成可执行文件&#xff0c;该文件会存在磁盘的当前目录下&#xff0c;而当我们开始运行这段程序时&#xff0c;操作系统&#xff08;加载器&#xff09;需要将其从磁盘加载进内存然后执行相关操作&#xff0c;而对于用到动态库的程序&…

Windows使用多个JDK的方法

原文网址&#xff1a;Windows使用多个JDK的方法-CSDN博客 简介 本文介绍Windows如何使用多个JDK。 原先已经有了JDK8&#xff0c;现在想用JDK21。但有的项目依然是JDK8&#xff0c;所以两个JDK需要共存。 解决方案 第一步&#xff1a;改环境变量 右键此电脑> 属性>…

RDIFramework.NET CS敏捷开发框架 SOA服务三种访问(直连、WCF、WebAPI)方式

1、介绍 在软件开发领域&#xff0c;尤其是企业级应用开发中&#xff0c;灵活性、开放性、可扩展性往往是项目成功的关键因素。对于C/S项目&#xff0c;如何高效地与后端数据库进行交互&#xff0c;以及如何提供多样化的服务访问方式&#xff0c;是开发者需要深入考虑的问题。…

《数字图像处理基础》学习07-图像几何变换之最近邻插值法放大图像

目录 一&#xff0c;概念 二&#xff0c;题目及matlab实现 1&#xff0c;解题思路 2&#xff0c;matlab实现 1&#xff09;matlab思路 2&#xff09;完整代码 三&#xff0c;放大图像及matlab实现 一&#xff0c;概念 通过上一篇&#xff0c;我已经学习了使用最邻近插…

LWIP和FATFS 实现 FTP 服务端

目录 一、前言 二、LWIP 和 FTP 简介 1.LWIP 2.FTP 三、实现 FTP 服务端的主要步骤 1.初始化 LWIP 2.创建 FTP 服务器任务 3.处理客户端连接 4.实现 FTP 命令处理 5.文件系统操作 6.错误处理和日志记录 四、示例代码 1.创建FTP任务 2. FTP任务代码 3.处理交互数据…

PyCharm中Python项目打包并运行到服务器的简明指南

目录 一、准备工作 二、创建并设置Python项目 创建新项目 配置项目依赖 安装PyInstaller 三、打包项目 打包为可执行文件 另一种打包方式(使用setup.py) 四、配置服务器环境 五、上传可执行文件到服务器 六、在服务器上运行项目 配置SSH解释器 配置部署 上传代…

PHP 方头像转为圆图

业务需要把创建海报上的用户头像由方形转为圆形&#xff0c;前端的样式设置不能用。 故采用GD的函数来对方图进行裁剪处理为圆图。 目录 裁剪函数 本地图片 远程图片 效果 参考文章 总结 裁剪函数 从网上找的一个裁剪图片的函数。 代码如下&#xff1a; /* * 将图片切…

Java--数组的定义与使用

1.数组的基本概念 1.1为什么用数组 在程序设计中,每一个数据总是对应一个变量.当数据量越大,就需要更多的变量来存储.我们将相同类型的数据存储到一个集合中,就可以更方便我们对数据进行访问,同时可以减少不断定义变量.这个集合就叫做数组 1.2数组的定义 数组是一种基本的数…

手机实时提取SIM卡打电话的信令声音-蓝牙电话如何适配eSIM卡的手机

手机实时提取SIM卡打电话的信令声音 --蓝牙电话如何适配eSIM卡的手机 一、前言 蓝牙电话的海外战略中&#xff0c;由于海外智能手机市场中政策的差异性&#xff0c;对内置eSIM卡的手机进行支持是非常合理的需求。Android系列手机中&#xff0c;无论是更换通信运营商&#xf…

《操作系统 - 清华大学》6 -4:局部页面置换算法:时钟页面置换算法 (Clock)

文章目录 1.时钟置换算法的工作原理2.时钟置换算法的具体实现过程3. 时钟置换算法示例 1.时钟置换算法的工作原理 需要考虑有没有新的办法&#xff0c;既能有 LRU 算法这种效果&#xff0c;产生缺页次数比较少&#xff0c;同时实现的效率比较简洁和方便&#xff0c;有点类似于…

Centos7安装MySQL8.0详细教程(压缩包安装方式)

本章教程&#xff0c;主要介绍如何在Centos7上安装MySQL8.0版本数据库&#xff08;压缩包安装方式&#xff09; 一、卸载系统自带的 Mariadb 1、查询 rpm -qa|grep mariadb2.、卸载 如果有查询结果&#xff0c;就进行卸载&#xff0c;没有就跳过该步骤。 rpm -e --nodeps mar…

brew安装mongodb和php-mongodb扩展新手教程

1、首先保证macos下成功安装了Homebrew&#xff0c; 在终端输入如下命令&#xff1a; brew search mongodb 搜索是不是有mongodb资源&#xff0c; 演示效果如下&#xff1a; 2、下面来介绍Brew 安装 MongoDB&#xff0c;代码如下&#xff1a; brew tap mongodb/brew brew in…

Flink四大基石之CheckPoint(检查点) 的使用详解

目录 一、Checkpoint 剖析 State 与 Checkpoint 概念区分 设置 Checkpoint 实战 执行代码所需的服务与遇到的问题 二、重启策略解读 重启策略意义 代码示例与效果展示 三、SavePoint 与 Checkpoint 异同 操作步骤详解 四、总结 在大数据流式处理领域&#xff0c;Ap…

springboot旅游管理系统的设计与实现

springboot旅游管理系统的设计与实现 如需源码pc端&#x1f449;&#x1f449;&#x1f449;资源 手机端&#x1f449;&#x1f449;&#x1f449;资源 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于…

16asm - 汇编介绍 和 debug使用

文章目录 前言硬件运行机制微机系统硬件组成计算机系统组成8086cpu组织架构dosbox安装配置debug debug使用R命令D命令E命令U命令T命令A命令标志寄存器 总结 前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解 十六位汇编 和 debug调试器的使用 硬件运行…

多级缓存设计实践

缓存是什么&#xff1f; 缓存技术是一种用于加速数据访问的优化策略。它通过将频繁访问的数据存储在高速存储介质&#xff08;如内存&#xff09;中&#xff0c;减少对慢速存储设备&#xff08;如硬盘或远程服务器&#xff09;的访问次数&#xff0c;从而提升系统的响应速度和…

鸿蒙开发-HMS Kit能力集(地图服务、华为支付服务)

地图服务 Map Kit&#xff08;地图服务&#xff09;是鸿蒙生态下的一个地图服务&#xff0c;为开发者提供强大而便捷的地图能力&#xff0c;助力全球开发者实现个性化地图呈现、地图搜索和路线规划等功能&#xff0c;轻松完成地图构建工作。 Map Kit提供了千万级别的 Poi&…

【k8s深入学习之 event 记录】初步了解 k8s event 记录机制

event 事件记录初始化 一般在控制器都会有如下的初始化函数&#xff0c;初始化 event 记录器等参数 1. 创建 EventBroadcaster record.NewBroadcaster(): 创建事件广播器&#xff0c;用于记录和分发事件。StartLogging(klog.Infof): 将事件以日志的形式输出。StartRecording…