SpringBoot + VUE + EleMent UI 实现系统增删改查的一些基本操作

兔子系统2.0

        这里是接着上次注册登录之后继续写的一些主页面,用户信息修改(基本信息、密码、安全问题和头像上传)、用户管理的及退出登录,并且对于用户表格做了搜索、分页和导出Excel的操作。

        也是一边写一边进行调整,可能跟上次相比,代码有些改动,不过这边尽量把代码全部提供出来,并且加了大量注释方便小白阅读理解(目录比较长,就不加在这里了,需要的话可以看左边的)。

展示:

兔子系统 2.0 基本操作演示

一、数据库表设计

用户表

       我这里把上次说的头像的更换给补上了,因为我这里是本地写的,没有服务器什么的,所以就直接把头像图片文件存到数据库了,一般是存到系统部署的服务器上。

小知识:
    在 Oracle 数据库中,BLOB(Binary Large Object)是一种用于存储大量二进制数据的数据类型。它的主要用途包括:
    存储多媒体数据:BLOB 可以存储图像、音频、视频等多媒体文件,适用于需要保存大量二进制数据的应用场景。
    存储文件:可以用来保存文档、PDF、Office 文件等非结构化数据,方便在数据库中管理和检索。
    处理大数据量:BLOB 可以存储最大达 4 GB 的数据,适合需要处理大量数据的应用程序。
    与其他数据类型的结合:可以与其他数据类型(如 VARCHAR2)一起使用,实现更复杂的数据存储和检索需求。
-- 创建用户表
CREATE TABLE CUSTOMER (
  ID VARCHAR2(32) NOT NULL,
  ACCOUNT VARCHAR2(9) NOT NULL,
  ROLE VARCHAR2(1) DEFAULT '2' NOT NULL,
  HEAD_PORTRAIT_NAME VARCHAR2(200),
  HEAD_PORTRAIT_FILE BLOB,
  NICK_NAME VARCHAR2(32) NOT NULL,
  PASSWORD VARCHAR2(64) NOT NULL,
  SALT VARCHAR2(32) NOT NULL,
  BIRTHDAY Date,
  AGE VARCHAR2(3),
  SEX VARCHAR(1),
  SAFETY_PROBLEM1_ID VARCHAR2(64),
  SAFETY_PROBLEM2_ID VARCHAR2(64),
  SAFETY_PROBLEM3_ID VARCHAR2(64),
  SAFETY_PROBLEM1_ANSWER VARCHAR2(32),
  SAFETY_PROBLEM2_ANSWER VARCHAR2(32),
  SAFETY_PROBLEM3_ANSWER VARCHAR2(32),
  CREATE_TIME Date DEFAULT SYSDATE NOT NULL, 
  UPDATE_TIME Date DEFAULT SYSDATE NOT NULL,
  IS_DELETE varchar(1) DEFAULT 'N' NOT NULL
);
-- 各个字段的注释
COMMENT ON TABLE CUSTOMER IS '用户表';
COMMENT ON COLUMN CUSTOMER.ID IS '用户表ID';
COMMENT ON COLUMN CUSTOMER.ACCOUNT IS '账号';
COMMENT ON COLUMN CUSTOMER.ROLE IS '角色 0 超级管理员;1 管理员;2 普通用户';
COMMENT ON COLUMN CUSTOMER.HEAD_PORTRAIT_NAME IS '头像图片名字';
COMMENT ON COLUMN CUSTOMER.HEAD_PORTRAIT_FILE IS '头像二进制文件';
COMMENT ON COLUMN CUSTOMER.NICK_NAME IS '昵称';
COMMENT ON COLUMN CUSTOMER.PASSWORD IS '密码';
COMMENT ON COLUMN CUSTOMER.SALT IS '盐加密';
COMMENT ON COLUMN CUSTOMER.BIRTHDAY IS '出生日期';
COMMENT ON COLUMN CUSTOMER.AGE IS '年龄';
COMMENT ON COLUMN CUSTOMER.SEX IS '性别 0 女;1 男;2 保密';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM1_ID IS '安全问题1id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM2_ID IS '安全问题2id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM3_ID IS '安全问题3id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM1_ANSWER IS '安全问题1答案';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM2_ANSWER IS '安全问题2答案';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM3_ANSWER IS '安全问题3答案';
COMMENT ON COLUMN CUSTOMER.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN CUSTOMER.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN CUSTOMER.IS_DELETE IS '是否删除 Y 已删除;N 未删除';

安全问题表

-- 创建安全问题表
CREATE TABLE SAFETYPROBLEM (
  ID VARCHAR2(32) NOT NULL,
  PROBLEM VARCHAR2(64) NOT NULL,
  CREATE_TIME Date DEFAULT SYSDATE NOT NULL, 
  UPDATE_TIME Date DEFAULT SYSDATE NOT NULL,
  IS_DELETE varchar(1) DEFAULT 'N' NOT NULL
);

-- 各个字段的注释
COMMENT ON TABLE SAFETYPROBLEM IS '安全问题表';
COMMENT ON COLUMN SAFETYPROBLEM.ID IS '安全问题表ID';
COMMENT ON COLUMN SAFETYPROBLEM.PROBLEM IS '问题';
COMMENT ON COLUMN SAFETYPROBLEM.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN SAFETYPROBLEM.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN SAFETYPROBLEM.IS_DELETE IS '是否删除 Y 已删除;N 未删除';

角色表

-- 创建角色表
CREATE TABLE ROLE (
  ID VARCHAR2(32) NOT NULL,
  ROLE_NAME VARCHAR2(64) NOT NULL,
  CREATE_TIME Date DEFAULT SYSDATE NOT NULL,
  UPDATE_TIME Date DEFAULT SYSDATE NOT NULL,
  IS_DELETE varchar(1) DEFAULT 'N' NOT NULL
);

-- 各个字段的注释
COMMENT ON TABLE ROLE IS '角色表';
COMMENT ON COLUMN ROLE.ID IS '角色表ID';
COMMENT ON COLUMN ROLE.ROLE_NAME IS '角色名';
COMMENT ON COLUMN ROLE.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN ROLE.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN ROLE.IS_DELETE IS '是否删除 Y 已删除;N 未删除';

二、后端配置文件

 application.yml

       application.yml 是 Spring Boot 项目的配置文件之一,主要用于定义应用程序的各种配置参数。使用 YAML 格式可以使配置更易读、层次分明。

## 指定web容器访问端口号
rabbit:
  name: 兔子系统
  version: v-1.0
## web容器端口号
server:
  port: 8081
## 配置数据库连接
spring:
  servlet:
    multipart:
      max-file-size: 40MB
      max-request-size: 40MB
      ## 设置单个文件上传的最大内存(头像上传用的)
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@//localhost:1521/ORCL
    username: 数据库用户名
    password: 密码
## 配置mybatis中mapper.xml文件扫描
mybatis:
  type-aliases-package: com.rabbitSystem.carrot.pojo.sel.*
  mapper-locations: classpath:mapper/*.xml   # mapper.xml文件映射

pom.xml

       pom.xml 是 Maven 项目的核心配置文件,Maven 是一个用于项目管理和构建的工具,文件定义了项目的基本信息、依赖项、插件、构建设置等。

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version> <!-- 降级到适用于 JDK 1.8 的版本 -->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.rabbitsystem</groupId>
    <artifactId>carrot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbit-system</name>
    <description>rabbit-system</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>1.8</java.version> <!-- 保持 JDK 1.8 -->
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version> <!-- 使用与 Spring Boot 2.7.x 兼容的 MyBatis 版本 -->
        </dependency>

        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId> <!-- 使用适合 JDK 1.8 的 ojdbc8 -->
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.2.0</version> <!-- 使用与 Spring Boot 2.7.x 兼容的 MyBatis 版本 -->
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.12</version> <!-- 降级到适用于 JDK 1.8 的版本 -->
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

三、后端代码

       目录结构(common里面的都是一些工具类,后面代码的提供顺序是根据写代码的步骤来的,不是根据这个图片顺序来的,望理解)

1、工具类

1、AjaxResult

       用于返回给前端 AjaxResult 类型数据的工具类。

package com.rabbitSystem.carrot.common.core;

import com.rabbitSystem.carrot.common.utils.StringUtils;

import java.util.HashMap;

/**
 * 操作消息提醒
 */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";

    /**
     * 数据总数量
     */
    public static final String DATA_COUNT = "count";

    /**
     * 状态类型
     */
    public enum Type {
        /**
         * 成功
         */
        SUCCESS(200),
        /**
         * 警告
         */
        WARN(301),
        /**
         * 校验失败
         */
        FAILED(-1),
        /**
         * 错误
         */
        ERROR(500);
        private final int value;

        Type(int value) {
            this.value = value;
        }

        public int value() {
            return this.value;
        }
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     */
    public AjaxResult(Type type, String msg) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(Type type, String msg, Object data) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param data 数据对象
     */
    public AjaxResult(Object data) {
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * @param type  状态类型
     * @param msg   返回内容
     * @param data  数据对象
     * @param count 数据总数量
     */
    public AjaxResult(Type type, String msg, Object data,Integer count) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
        if(count!=null){
            super.put(DATA_COUNT,count);
        }
    }

    /**
     * 方便链式调用
     *
     * @param key   键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(Type.SUCCESS, msg, data);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult warn(String msg) {
        return AjaxResult.warn(msg, null);
    }

    /**
     * 返回警告消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult warn(String msg, Object data) {
        return new AjaxResult(Type.WARN, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(Type.ERROR, msg, data);
    }
}

2、CorsConfig

       解决前后端跨域的问题。

package com.rabbitSystem.carrot.common.core;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
/**
 * 解决跨域问题
 */
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*") // 允许任意域名访问,或者替换为特定的域名
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
                .allowedHeaders("*") // 允许的请求头
                .maxAge(3600); // 可选的,设置预检请求的缓存时间
    }
}

3、PasswordEncoder

       密码加密的工具类。

package com.rabbitSystem.carrot.common.utils;

import java.security.MessageDigest;

/**
 * @Description: 密码加密
 */
public class PasswordEncoder {

	//返回十六进制数字字符串
	private final static String[] HEX_DIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
			"e", "f" };

	private final static String MD5 = "MD5";

	private Object salt;
	private String algorithm;

	public PasswordEncoder(Object salt) {
		this(salt, MD5);
	}

	public PasswordEncoder(Object salt, String algorithm) {
		this.salt = salt;
		this.algorithm = algorithm;
	}

	/**
	 * 密码加密
	 *
	 * @param rawPass
	 * @return
	 */
	public String encode(String rawPass) {
		String result = null;
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			// 加密后的字符串
			result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));
		} catch (Exception ex) {
		}
		return result;
	}

	/**
	 * 密码匹配验证
	 *
	 * @param encPass
	 *            密文
	 * @param rawPass
	 *            明文
	 * @return
	 */
	public boolean matches(String encPass, String rawPass) {
		String pass1 = "" + encPass;
		String pass2 = encode(rawPass);

		return pass1.equals(pass2);
	}

	/**
	 * 密码匹配验证
	 *
	 * @param encPass
	 *            密文
	 * @param rawPass
	 *            密文
	 * @return
	 */
	public boolean matchesNotSalt(String encPass, String rawPass) {
		String pass1 = "" + encPass;
		String pass2 = rawPass;
		return pass1.equals(pass2);
	}

	private String mergePasswordAndSalt(String password) {
		if (password == null) {
			password = "";
		}

		if ((salt == null) || "".equals(salt)) {
			return password;
		} else {
			return password + "{" + salt.toString() + "}";
		}
	}

	/**
	 * 转换字节数组为16进制字串
	 *
	 * @param b
	 *            字节数组
	 * @return 16进制字串
	 */
	private String byteArrayToHexString(byte[] b) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++) {
			resultSb.append(byteToHexString(b[i]));
		}
		return resultSb.toString();
	}

	/**
	 * 将字节转换为16进制
	 *
	 * @param b
	 * @return
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return HEX_DIGITS[d1] + HEX_DIGITS[d2];
	}

}

4、PasswordUtils

       密码解密的工具类。

package com.rabbitSystem.carrot.common.utils;

import java.util.UUID;

/**
 * @Description: 密码工具类
 */
public class PasswordUtils {

	/**
	 * 匹配密码
	 * @param salt 盐
	 * @param rawPass 明文
	 * @param encPass 密文
	 * @return
	 */
	public static boolean matches(String salt, String rawPass, String encPass) {
		return new PasswordEncoder(salt).matches(encPass, rawPass);
	}

	/**
	 * 明文密码加密
	 * @param rawPass 明文
	 * @param salt
	 * @return
	 */
	public static String encode(String rawPass, String salt) {
		return new PasswordEncoder(salt).encode(rawPass);
	}

	/**
	 * 获取加密盐
	 * @return
	 */
	public static String getSalt() {
		return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
	}
}

5、SnowflakeIdWorker

       生成雪花id的工具类。

package com.rabbitSystem.carrot.common.utils;


import org.apache.log4j.Logger;

import java.util.Random;

/**
 * SnowFlake算法的优点:
 *    ① 高性能高可用:生成时不依赖于数据库,完全在内存中生成。
 *    ② 容量大:每秒中能生成数百万的自增ID。
 *    ③ ID自增:存入数据库中,索引效率高。
 * SnowFlake算法的缺点:
 *      依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。
 */
public class SnowflakeIdWorker {

    private Logger logger = Logger.getLogger(SnowflakeIdWorker.class);

    //保存数字0-9 和 大小写字母
    private final String STR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    //机器ID  2进制5位  32位减掉1位 31个
    private final long WORKER_ID;

    //设置一个时间初始值    2^41 - 1   差不多可以用69年
    private final static long TWE_POCH = 1288834974657L;

    //代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
    private long SEQUENCE = 0L;
    //5位的机器id
    private final static long WORKER_ID_BITS = 5L;

    //这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
    public final static long MAX_WORKER_ID = -1L ^ -1L << WORKER_ID_BITS;

    //每毫秒内产生的id数
    private final static long SEQUENCE_BITS = 10L;
    private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private final static long TIME_STAMPLEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    public final static long SEQUENCE_MASK = -1L ^ -1L << SEQUENCE_BITS;

    //记录产生时间毫秒数,判断是否是同1毫秒
    private long LAST_TIMESTAMP = -1L;
    public SnowflakeIdWorker(final long workerId) {
        // 检查机器id是否超过31 不能小于0
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "worker Id can't be greater than %d or less than 0",
                    MAX_WORKER_ID));
        }
        this.WORKER_ID = workerId;
    }

    // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
    public synchronized String nextId() {
        // 获取当前时间戳,单位是毫秒
        long timestamp = this.timeGen();
        if (this.LAST_TIMESTAMP == timestamp) {
            /** 一个毫秒内最多只能有4096个数字,无论你传递多少进来,
                这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
             */
            this.SEQUENCE = (this.SEQUENCE + 1) & SEQUENCE_MASK;
            //当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
            if (this.SEQUENCE == 0) {
              //  logger.info("###########" + SEQUENCE_MASK);
                timestamp = this.tilNextMillis(this.LAST_TIMESTAMP);
            }
        } else {
            this.SEQUENCE = 0;
        }
        if (timestamp < this.LAST_TIMESTAMP) {
            try {
                throw new Exception(
                        String.format(
                                "Clock moved backwards. Refusing to generate id for %d milliseconds",
                                this.LAST_TIMESTAMP - timestamp));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        this.LAST_TIMESTAMP = timestamp;
        // 这儿就是最核心的二进制位运算操作,生成一个64bit的id
        // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
        // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
        long nextId = ((timestamp - TWE_POCH << TIME_STAMPLEFT_SHIFT))
                | (this.WORKER_ID << WORKER_ID_SHIFT) | (this.SEQUENCE);
      /*  logger.info("timestamp:" + timestamp + ",timestampLeftShift:"
                + TIME_STAMPLEFT_SHIFT + ",nextId:" + nextId + ",workerId:"
                + WORKER_ID + ",sequence:" + SEQUENCE);*/
        return randomNextId(nextId);
    }

    /**
     * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    //获取当前时间戳
    private long timeGen() {
        return System.currentTimeMillis();
    }

    private String randomNextId(Long nextId){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            //创建一个新的随机数生成器
            Random random = new Random();
            //返回[0,string.length)范围的int值    作用:保存下标
            int index = random.nextInt(STR.length());
            //charAt() : 返回指定索引处的 char 值   ==》赋值给char字符对象ch
            char ch = STR.charAt(index);
            char id = String.valueOf(nextId).charAt(i);
            sb.append(id);
            // append(char c) :将 char 参数的字符串表示形式追加到此序列  ==》即将每次获取的ch值作拼接
            sb.append(ch);
        }
        return sb.toString();
    }

    /**
     * @Description: 获取唯一id
     * @param workerId 工作id
     */
    public static String getId(String workerId){
        if(workerId == null && "".equals(workerId)){
            workerId = "2";
        }
        SnowflakeIdWorker worker = new SnowflakeIdWorker(Long.valueOf(workerId));
        return worker.nextId();
    }

    /**
     * %0是在num转化为字符后,长度达不到length的时候,前面以0补足。
     * d是个占位符,会被参数num所替换。
     *
     * @param num       传递的数字
     * @param length   参数length代表的是格式化后字符串的总长度
     * @return
     */
    public static String leftFormatInteger(int num, int length) {
        //String.format用来格式化字符串(按指定的规则连接字符串或输出其它变量并返回新的字符串)
        return String.format("%0"+length+"d", num);
    }

    public static void main(String[] args){
        System.out.println(leftFormatInteger(3,9) + SnowflakeIdWorker.getId("0008"));
        String id = SnowflakeIdWorker.getId("000001");
        System.out.println(id +"\t"  + id.length());
        System.out.println("000051l2f9R8n3X8j6j9k13808L7Z0y312a3d".length());


    }
}

6、StringUtils

       字符串工具类,针对字符串的一些操作。

package com.rabbitSystem.carrot.common.utils;


import java.util.Collection;
import java.util.Map;

/**
 * 字符串工具类
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {
    /**
     * 空字符串
     */
    private static final String NULLSTR = "";

    /**
     * 下划线
     */
    private static final char SEPARATOR = '_';

    /**
     * 获取参数不为空值
     *
     * @param value defaultValue 要判断的value
     * @return value 返回值
     */
    public static <T> T nvl(T value, T defaultValue) {
        return value != null ? value : defaultValue;
    }

    /**
     * * 判断一个Collection是否为空, 包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Collection<?> coll) {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * * 判断一个Collection是否非空,包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Collection<?> coll) {
        return !isEmpty(coll);
    }

    /**
     * * 判断一个对象数组是否为空
     *
     * @param objects 要判断的对象数组
     *                * @return true:为空 false:非空
     */
    public static boolean isEmpty(Object[] objects) {
        return isNull(objects) || (objects.length == 0);
    }

    /**
     * * 判断一个对象数组是否非空
     *
     * @param objects 要判断的对象数组
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Object[] objects) {
        return !isEmpty(objects);
    }

    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Map<?, ?> map) {
        return isNull(map) || map.isEmpty();
    }

    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    /**
     * * 判断一个字符串是否为空串
     *
     * @param str String
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(String str) {
        return isNull(str) || NULLSTR.equals(str.trim());
    }

    /**
     * * 判断一个字符串是否为非空串
     *
     * @param str String
     * @return true:非空串 false:空串
     */
    public static boolean isNotEmpty(String str) {
        return !isEmpty(str);
    }

    /**
     * * 判断一个对象是否为空
     *
     * @param object Object
     * @return true:为空 false:非空
     */
    public static boolean isNull(Object object) {
        return object == null;
    }

    /**
     * * 判断一个对象是否非空
     *
     * @param object Object
     * @return true:非空 false:空
     */
    public static boolean isNotNull(Object object) {
        return !isNull(object);
    }

    /**
     * * 判断一个对象是否是数组类型(Java基本型别的数组)
     *
     * @param object 对象
     * @return true:是数组 false:不是数组
     */
    public static boolean isArray(Object object) {
        return isNotNull(object) && object.getClass().isArray();
    }

    /**
     * 去空格
     */
    public static String trim(String str) {
        return (str == null ? "" : str.trim());
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @return 结果
     */
    public static String substring(final String str, int start) {
        if (str == null) {
            return NULLSTR;
        }

        if (start < 0) {
            start = str.length() + start;
        }

        if (start < 0) {
            start = 0;
        }
        if (start > str.length()) {
            return NULLSTR;
        }

        return str.substring(start);
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @param end   结束
     * @return 结果
     */
    public static String substring(final String str, int start, int end) {
        if (str == null) {
            return NULLSTR;
        }

        if (end < 0) {
            end = str.length() + end;
        }
        if (start < 0) {
            start = str.length() + start;
        }

        if (end > str.length()) {
            end = str.length();
        }

        if (start > end) {
            return NULLSTR;
        }

        if (start < 0) {
            start = 0;
        }
        if (end < 0) {
            end = 0;
        }

        return str.substring(start, end);
    }

    /**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     *
     * @param template 文本模板,被替换的部分用 {} 表示
     * @param params   参数值
     * @return 格式化后的文本
     */
   /* public static String format(String template, Object... params) {
        if (isEmpty(params) || isEmpty(template)) {
            return template;
        }
        return StrFormatter.format(template, params);
    }*/

    /**
     * 下划线转驼峰命名
     */
    public static String toUnderScoreCase(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        // 前置字符是否大写
        boolean preCharIsUpperCase = true;
        // 当前字符是否大写
        boolean curreCharIsUpperCase = true;
        // 下一字符是否大写
        boolean nexteCharIsUpperCase = true;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (i > 0) {
                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
            } else {
                preCharIsUpperCase = false;
            }

            curreCharIsUpperCase = Character.isUpperCase(c);

            if (i < (str.length() - 1)) {
                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
            }

            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
                sb.append(SEPARATOR);
            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
                sb.append(SEPARATOR);
            }
            sb.append(Character.toLowerCase(c));
        }
        return sb.toString();
    }

    /**
     * 是否包含字符串
     *
     * @param str  验证字符串
     * @param strs 字符串组
     * @return 包含返回true
     */
    public static boolean inStringIgnoreCase(String str, String... strs) {
        if (str != null && strs != null) {
            for (String s : strs) {
                if (str.equalsIgnoreCase(trim(s))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
     *
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        // 快速检查
        if (name == null || name.isEmpty()) {
            // 没必要转换
            return "";
        } else if (!name.contains("_")) {
            // 不含下划线,仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        // 用下划线将原始字符串分割
        String[] camels = name.split("_");
        for (String camel : camels) {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 驼峰式命名法
     * 例如:user_name->userName
     */
    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }
        if (s.indexOf(SEPARATOR) == -1) {
            return s;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj) {
        return (T) obj;
    }
}

2、实体类

1、CustomerSelPojo

       用户的实体类。

package com.rabbitSystem.carrot.pojo.sel;

import lombok.Data;

@Data
public class CustomerSelPojo {

  // 用户信息
  private String id;
  private String account;
  private String role;
  private String roleName;
  private String headPortraitName;
  private byte[] headPortraitFile; // 头像存储的是二进制文件
  private String nickName;
  private String password;
  private String salt;
  private String birthday;
  private String age;
  private String sex;
  private String safetyProblem1Id;
  private String safetyProblem2Id;
  private String safetyProblem3Id;
  private String safetyProblem1Answer;
  private String safetyProblem2Answer;
  private String safetyProblem3Answer;
  private String createTime;
  private String updateTime;
  private String isDelete;

  // 功能
  private int i; // 注册时新增个数
  private int totalCount; //用户总数量
  private String currentPage; //当前页码
  private String pageSize; //每页条数
  private String searchValue; //用于页面的自由搜索条件
  private String image; //用于接收前端传回来的图片

}

2、RolePojo

       角色的实体类。

package com.rabbitSystem.carrot.pojo.sel;

import lombok.Data;

@Data
public class RolePojo {

  private String id;
  private String roleName;
  private String createTime;
  private String updateTime;
  private String isDelete;

}

3、SafetyProblemPojo

       安全问题实体类。

package com.rabbitSystem.carrot.pojo.sel;

import lombok.Data;

@Data
public class SafetyProblemPojo {

    private String id;
    private String problem;
    private String createTime;
    private String updateTime;
    private String isDelete;
    private int i;
}

(另外三个就不展示了,主要就用的这三个pojo里面的)

3、控制层

1、CustomerController

       用户操作的控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

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

@RestController
@RequestMapping("/CustomerController")
/**
 * 用户的控制层
 */
public class CustomerController {

    /**
     * 调用业务访问层对象
     */
    @Resource
    private CustomerService customerService;

    /**
     * GetMapping:处理 GET 请求(查询一般用 get 请求)
     * 查询所有用户信息的方法
     */
    @PostMapping("/selCustomers")
    public AjaxResult selCustomers(@RequestBody CustomerSelPojo customerSelPojo) {
        // 获取客户数据,支持分页
        List<CustomerSelPojo> customerSelPojoList = customerService.selCustomers(customerSelPojo);
        // 返回结果
        String msg = customerSelPojoList != null ? "查询成功!" : "查询失败!";
        return new AjaxResult(customerSelPojoList != null ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg, customerSelPojoList);
    }

    /**
     * 查询所有用户数量的方法
     * @return
     */
    @PostMapping("/selCustomersCount")
    public AjaxResult selCustomersCount(@RequestBody CustomerSelPojo customerSelPojo) {
        int customersCount = customerService.selCustomersCount(customerSelPojo);
        String msg;
        if (customersCount != 0){
            msg = "查询成功!";
        }else {
            msg = "查询失败!";
        }
        return new AjaxResult(customersCount != 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg, customersCount);
    }

    /**
     * 根据id删除用户的方法
     * @return
     */
    @GetMapping("/delCustomerById/{id}")
    private AjaxResult delCustomerById(@PathVariable("id") String id){
        int customersCount = customerService.delCustomerById(id);
        String msg;
        if (customersCount > 0){
            msg = "删除成功!";
        }else {
            msg = "删除失败!";
        }
        return new AjaxResult(customersCount > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg, customersCount);
    }

    /**
     * 上传用户的头像的方法
     * @return
     */
    @PostMapping("/uploadHeadPortrait")
    public AjaxResult uploadHeadPortrait(@RequestParam("image") MultipartFile file,@RequestParam("id") String id) {
        if (file.isEmpty()) {
            return new AjaxResult(AjaxResult.Type.ERROR, "失败", ResponseEntity.badRequest().body("请上传一个文件"));
        } else {
            try {
                CustomerSelPojo customerSelPojo = new CustomerSelPojo();
                customerSelPojo.setId(id);
                customerSelPojo.setHeadPortraitName(file.getOriginalFilename());
                // 如果要存储文件内容,可以考虑使用数据库的 BLOB 类型
                byte[] fileBytes = file.getBytes();
                // 直接存储字节数组,或进行其他处理
                customerSelPojo.setHeadPortraitFile(fileBytes);
                // 保存到数据库,假设你有 save 方法
                int i = customerService.uploadHeadPortrait(customerSelPojo);  // 需要实现实际保存逻辑
                if (i == 1){
                    return new AjaxResult(ResponseEntity.status(200).body(customerSelPojo.getHeadPortraitFile()));
//                    return new AjaxResult(ResponseEntity.ok("文件上传成功: " + customerSelPojo.getHeadPortraitFile()));
                }
                return new AjaxResult(ResponseEntity.status(500).body("文件上传失败"));
            } catch (IOException e) {
                e.printStackTrace();
                return new AjaxResult(ResponseEntity.status(500).body("文件上传失败"));
            }
        }
    }

    /**
     * 修改用户信息的方法
     * @return
     */
    @PostMapping("/updInformation")
    public AjaxResult updInformation(@RequestBody CustomerSelPojo customerSelPojo) {
        int i = customerService.updInformation(customerSelPojo);
        String msg = "";
        if (i == 1){
            msg = "修改成功!";
        }else {
            msg = "修改失败";
        }
        return new AjaxResult(i > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg);
    }

    /**
     * 修改用户安全问题的方法
     * @return
     */
    @PostMapping("/updSafetyProblem")
    public AjaxResult updSafetyProblem(@RequestBody CustomerSelPojo customerSelPojo) {
        int i = customerService.updSafetyProblem(customerSelPojo);
        String msg = "";
        if (i == 1){
            msg = "修改成功!";
        }else {
            msg = "修改失败";
        }
        return new AjaxResult(i > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR, msg);
    }

}

2、HomePageController

       主页面加载用户信息的控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/HomePageController")
public class HomePageController {

    @Resource
    private CustomerService customerService;

    /**
     * 根据账号加载用户的所有信息
     * @param account
     * @return
     */
    @GetMapping("/loadCustomerInformation/{account}")
    private AjaxResult loadCustomerInformation(@PathVariable("account") String account){
        CustomerSelPojo customerSelPojo = customerService.selCustomer(account);
        String msg = "";
        if (customerSelPojo != null){
            customerSelPojo.setI(1);
            msg = "查询成功";
        }else {
            customerSelPojo.setI(0);
            msg = "查询失败";
        }
        return new AjaxResult(customerSelPojo.getI() == 1 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg,customerSelPojo);
    }
}

3、LoginAndRegisterController

       注册登录的控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.LoginAndRegisterService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * 标识控制层
 * @RestController 组合注解, @Controller +  @ResponseBody ,
 * 在类头部指定@RestController注解,就表示该控制器下所有的方法均以 json格式类型从服务端响应给客户端(前台)
 */
@RestController
/**
 * 处理 HTTP 请求
 */
@RequestMapping("/LoginAndRegisterController")
/**
 * 注册登录控制层
 */
public class LoginAndRegisterController {

    @Resource
    private LoginAndRegisterService loginAndRegisterService;

    /**
     * PostMapping:处理 Post 请求(插入一般用 post 请求)
     * 用户注册的方法
     */
    @PostMapping("/register")
    public AjaxResult register(@RequestBody CustomerSelPojo customerSelPojo){
        customerSelPojo = loginAndRegisterService.register(customerSelPojo);
        String msg;
        if (customerSelPojo.getI()>0){
            msg = "注册成功,您的账号为:"+ customerSelPojo.getAccount();
        }else {
            msg = "注册失败,请重试!";
        }
        return new AjaxResult(customerSelPojo.getI() > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, customerSelPojo);
    }

    /**
     * 登录
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/login")
    public AjaxResult login(@RequestBody CustomerSelPojo customerSelPojo){
        customerSelPojo = loginAndRegisterService.login(customerSelPojo);
        String msg;
        if (customerSelPojo.getI()== 0){
            msg = "当前账号不存在!";
        }else if (customerSelPojo.getI()== 1){
            msg = "登录成功!";
        }else {
            msg = "密码错误!";
        }
        return new AjaxResult(customerSelPojo.getI() == 1 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, customerSelPojo);
    }
}

4、RetrievePasswordController

       忘记密码的控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/RetrievePasswordController")
public class RetrievePasswordController {

    @Resource
    private CustomerService customerService;

    /**
     * 找回密码校验账号和安全问题
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/retrievePassword")
    public AjaxResult selCustomers(@RequestBody CustomerSelPojo customerSelPojo){
        String safetyProblem1Id = customerSelPojo.getSafetyProblem1Id();
        String safetyProblem2Id = customerSelPojo.getSafetyProblem2Id();
        String safetyProblem3Id = customerSelPojo.getSafetyProblem3Id();
        String safetyProblem1Answer = customerSelPojo.getSafetyProblem1Answer();
        String safetyProblem2Answer = customerSelPojo.getSafetyProblem2Answer();
        String safetyProblem3Answer = customerSelPojo.getSafetyProblem3Answer();

        customerSelPojo = customerService.selCustomer(customerSelPojo.getAccount());
        if (customerSelPojo == null){
            return new AjaxResult(AjaxResult.Type.ERROR,"该账号不存在!");
        }else if (safetyProblem1Id.equals(customerSelPojo.getSafetyProblem1Id())
                && safetyProblem2Id.equals(customerSelPojo.getSafetyProblem2Id())
                && safetyProblem3Id.equals(customerSelPojo.getSafetyProblem3Id())
                && safetyProblem1Answer.equals(customerSelPojo.getSafetyProblem1Answer())
                && safetyProblem2Answer.equals(customerSelPojo.getSafetyProblem2Answer())
                && safetyProblem3Answer.equals(customerSelPojo.getSafetyProblem3Answer())){
            return new AjaxResult(AjaxResult.Type.SUCCESS,"校验成功!", customerSelPojo);
        }else {
            return new AjaxResult(AjaxResult.Type.ERROR,"您选择的问题及答案不正确!");
        }
    }


    /**
     * 根据账号修改密码
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/updPassword")
    public AjaxResult updPassword(@RequestBody CustomerSelPojo customerSelPojo){
        int i = customerService.updPassword(customerSelPojo);
        String msg = "";
        if (i==1){
            msg = "修改成功";
        }else {
            msg = "修改失败";
        }
        return new AjaxResult( i == 1 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg);
    }
}

5、RoleController

       角色控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.RolePojo;
import com.rabbitSystem.carrot.service.RoleService;
import org.springframework.web.bind.annotation.*;

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

/**
 * 角色控制层
 */
@RestController
@RequestMapping("/RoleController")
public class RoleController {

    @Resource
    private RoleService roleService;

    /**
     * 查询所有的角色
     * @return
     */
    @GetMapping("/selRoles")
    public AjaxResult selRoles(){
        List<RolePojo> rolePojoList = roleService.selRoles();
        String msg;
        if (rolePojoList != null){
            msg = "查询成功";
        }else {
            msg = "查询失败";
        }
        return new AjaxResult(rolePojoList != null ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg,rolePojoList);
    }


}

6、SafetyProblemController

       安全问题控制层。

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.SafetyProblem;
import com.rabbitSystem.carrot.service.SafetyProblemService;
import org.springframework.web.bind.annotation.*;

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

/**
 * 安全问题控制层
 */
@RestController
@RequestMapping("/SafetyProblemController")
public class SafetyProblemController {

    @Resource
    private SafetyProblemService safetyProblemService;

    /**
     * PostMapping:处理 Post 请求(插入一般用 post 请求)
     * 用户注册的方法
     */
    @GetMapping("/selSafetyProblems")
    public AjaxResult selSafetyProblems(){
        List<SafetyProblem> safetyProblemList = safetyProblemService.selSafetyProblems();
        int i = 0;
        if (safetyProblemList != null){
            i = 1;
        }
        String msg = i == 0 ? "查询失败" : "查询成功";
        return new AjaxResult(i > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, safetyProblemList);
    }
}

4、业务层

1、CustomerService 及 CustomerImpl

       用户业务层。

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;

import java.util.List;

public interface CustomerService {

    /**
     * 查询所有用户信息
     * @return
     */
    List<CustomerSelPojo> selCustomers(CustomerSelPojo customerSelPojo);

    /**
     * 根据账号查询个人用户信息
     * @param account
     * @return
     */
    CustomerSelPojo selCustomer(String account);

    /**
     * 根据账号修改密码
     * @param customerSelPojo
     * @return
     */
    int updPassword(CustomerSelPojo customerSelPojo);

    /**
     * 查询所有用户数
     * @return
     */
    int selCustomersCount(CustomerSelPojo customerSelPojo);

    /**
     * 根据id删除用户
     * @param id
     * @return
     */
    int delCustomerById(String id);

    /**
     * 上传头像
     * @param customerSelPojo
     * @return
     */
    int uploadHeadPortrait(CustomerSelPojo customerSelPojo);

    /**
     * 修改个人信息
     * @param customerSelPojo
     * @return
     */
    int updInformation(CustomerSelPojo customerSelPojo);

    /**
     * 修改个人安全问题
     * @param customerSelPojo
     * @return
     */
    int updSafetyProblem(CustomerSelPojo customerSelPojo);
}

       用户业务层实现类。

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.common.utils.PasswordUtils;
import com.rabbitSystem.carrot.mapper.CustomerMapper;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class CustomerImpl implements CustomerService {

    //创建数据访问层对象
    @Resource
    private CustomerMapper customerMapper;

    @Override
    public List<CustomerSelPojo> selCustomers(CustomerSelPojo customerSelPojo) {
        return customerMapper.selCustomers(customerSelPojo);
    }

    @Override
    public CustomerSelPojo selCustomer(String account) {
        CustomerSelPojo  customerSelPojo= customerMapper.selCustomer(account);
        return customerSelPojo;
    }

    @Override
    public int updPassword(CustomerSelPojo customerSelPojo) {
        String password = customerSelPojo.getPassword();
        // 密码加密
        String salt = PasswordUtils.getSalt();
        String encPass = PasswordUtils.encode(password, salt);
        customerSelPojo.setPassword(encPass);
        customerSelPojo.setSalt(salt);
        int  i = customerMapper.updPassword(customerSelPojo);
        return i;
    }

    @Override
    public int selCustomersCount(CustomerSelPojo customerSelPojo) {
        return  customerMapper.selCustomersCount(customerSelPojo);
    }

    @Override
    public int delCustomerById(String id) {
        return customerMapper.delCustomerById(id);
    }

    @Override
    public int uploadHeadPortrait(CustomerSelPojo customerSelPojo) {
        return customerMapper.uploadHeadPortrait(customerSelPojo);
    }

    @Override
    public int updInformation(CustomerSelPojo customerSelPojo) {
        return customerMapper.updInformation(customerSelPojo);
    }

    @Override
    public int updSafetyProblem(CustomerSelPojo customerSelPojo) {
        return customerMapper.updSafetyProblem(customerSelPojo);
    }
}

2、LoginAndRegisterService 及 LoginAndRegisterImpl

       登录注册业务层。

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;

public interface LoginAndRegisterService {
    CustomerSelPojo register(CustomerSelPojo customerSelPojo);

    CustomerSelPojo login(CustomerSelPojo customerSelPojo);
}

       登录注册实现类。

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.common.utils.PasswordUtils;
import com.rabbitSystem.carrot.common.utils.SnowflakeIdWorker;
import com.rabbitSystem.carrot.mapper.LoginAndRegisterMapper;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import com.rabbitSystem.carrot.service.LoginAndRegisterService;
import org.springframework.stereotype.Service;

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

@Service
public class LoginAndRegisterImpl implements LoginAndRegisterService {

    @Resource
    private CustomerService customerService;

    @Resource
    private LoginAndRegisterMapper loginAndRegisterMapper;
    @Override
    public CustomerSelPojo register(CustomerSelPojo customerSelPojo) {
        // 生成用户id
        String userId = SnowflakeIdWorker.getId("00001");
        customerSelPojo.setId(userId);
        // 密码加密
        String salt = PasswordUtils.getSalt();
        String encPass = PasswordUtils.encode(customerSelPojo.getPassword(), salt);
        customerSelPojo.setPassword(encPass);
        customerSelPojo.setSalt(salt);
        // 生成账号
        Random random = new Random();
        String account;
        do {
            int randomNumber = random.nextInt(1000000000);
            account = String.format("%09d", randomNumber);
        } while (customerService.selCustomer(account) != null);
        customerSelPojo.setAccount(account);
        int i = loginAndRegisterMapper.register(customerSelPojo);
        customerSelPojo.setI(i);
        return customerSelPojo;
    }

    @Override
    public CustomerSelPojo login(CustomerSelPojo customerSelPojo) {
        String account = customerSelPojo.getAccount();
        String password = customerSelPojo.getPassword();
        CustomerSelPojo primitiveUser = customerService.selCustomer(account);
        if (primitiveUser == null) {
            customerSelPojo.setI(0);
        } else {
            /**
             * 根据查找用户名返回的盐和密码,跟前端传回来的进行校验
             */
            if (PasswordUtils.matches(primitiveUser.getSalt(), password, primitiveUser.getPassword())) {
                customerSelPojo.setI(1);
            }else{
                customerSelPojo.setI(2);
            }
        }
        return customerSelPojo;
    }
}

3、RoleService 及 RoleImpl

     角色业务层。

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.sel.RolePojo;

import java.util.List;

public interface RoleService {
    /**
     * 查询所有的角色
     * @return
     */
    List<RolePojo> selRoles();
}

     角色业务层实现类。

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.mapper.RoleMapper;
import com.rabbitSystem.carrot.pojo.sel.RolePojo;
import com.rabbitSystem.carrot.service.RoleService;
import org.springframework.stereotype.Service;

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

@Service
public class RoleImpl implements RoleService {

    @Resource
    private RoleMapper roleMapper;

    @Override
    public List<RolePojo> selRoles() {
        return roleMapper.selRoles();
    }
}

4、SafetyProblemService 及 SafetyProblemImpl

     安全问题业务层。

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.SafetyProblem;

import java.util.List;

public interface SafetyProblemService {
    /**
     * 查序所有的安全问题
     * @return
     */
    List<SafetyProblem> selSafetyProblems();
}

     安全问题业务层实现类。

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.mapper.SafetyProblemMapper;
import com.rabbitSystem.carrot.pojo.SafetyProblem;
import com.rabbitSystem.carrot.service.SafetyProblemService;
import org.springframework.stereotype.Service;

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

@Service
public class SafetyProblemImpl implements SafetyProblemService {

    @Resource
    private SafetyProblemMapper safetyProblemMapper;
    @Override
    public List<SafetyProblem> selSafetyProblems() {
        List<SafetyProblem>  safetyProblemList= safetyProblemMapper.selSafetyProblems();
        return safetyProblemList;
    }
}

5、映射层

1、CustomerMapper

     用户的映射层。

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface CustomerMapper {
    List<CustomerSelPojo> selCustomers(CustomerSelPojo customerSelPojo);

    CustomerSelPojo selCustomer(String account);

    int updPassword(CustomerSelPojo customerSelPojo);

    int selCustomersCount(CustomerSelPojo customerSelPojo);

    int delCustomerById(String id);

    int uploadHeadPortrait(CustomerSelPojo customerSelPojo);

    int updInformation(CustomerSelPojo customerSelPojo);

    int updSafetyProblem(CustomerSelPojo customerSelPojo);
}

2、LoginAndRegisterMapper

     登录注册映射层。

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoginAndRegisterMapper {

    int register(CustomerSelPojo customerSelPojo);
}

3、RoleMapper

     角色映射层。

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.sel.RolePojo;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface RoleMapper {
    List<RolePojo> selRoles();
}

4、SafetyProblemMapper

     安全问题映射层。

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.SafetyProblem;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface SafetyProblemMapper {
    List<SafetyProblem> selSafetyProblems();
}

6、持久层

1、CustomerMapper.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.rabbitSystem.carrot.mapper.CustomerMapper">

    <resultMap id="customerResultMap" type="com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo">
        <result property="id" column="ID"/>
        <result property="account" column="ACCOUNT"/>
        <result property="role" column="ROLE"/>
        <result property="roleName" column="ROLE_NAME"/>
        <result property="headPortraitName" column="HEAD_PORTRAIT_NAME"/>
        <result property="headPortraitFile" column="HEAD_PORTRAIT_FILE"/>
        <result property="nickName" column="NICK_NAME"/>
        <result property="password" column="PASSWORD"/>
        <result property="salt" column="SALT"/>
        <result property="birthday" column="BIRTHDAY"/>
        <result property="age" column="AGE"/>
        <result property="sex" column="SEX"/>
        <result property="safetyProblem1Id" column="SAFETY_PROBLEM1_ID"/>
        <result property="safetyProblem2Id" column="SAFETY_PROBLEM2_ID"/>
        <result property="safetyProblem3Id" column="SAFETY_PROBLEM3_ID"/>
        <result property="safetyProblem1Answer" column="SAFETY_PROBLEM1_ANSWER"/>
        <result property="safetyProblem2Answer" column="SAFETY_PROBLEM2_ANSWER"/>
        <result property="safetyProblem3Answer" column="SAFETY_PROBLEM3_ANSWER"/>
        <result property="createTime" column="CREATE_TIME"/>
        <result property="updateTime" column="UPDATE_TIME"/>
        <result property="isDelete" column="IS_DELETE"/>
    </resultMap>

    <select id="selCustomers" resultMap="customerResultMap">
        SELECT T.ID, T.ACCOUNT, R.ROLE_NAME, T.HEAD_PORTRAIT_NAME,T.HEAD_PORTRAIT_FILE, T.NICK_NAME, T.PASSWORD, T.SALT,
               TO_CHAR(T.BIRTHDAY, 'YYYY-MM-DD') AS BIRTHDAY,T.AGE, T.SEX, T.SAFETY_PROBLEM1_ID, T.SAFETY_PROBLEM2_ID,
               T.SAFETY_PROBLEM3_ID, T.SAFETY_PROBLEM1_ANSWER,T.SAFETY_PROBLEM2_ANSWER, T.SAFETY_PROBLEM3_ANSWER,
               T.CREATE_TIME, T.UPDATE_TIME, T.IS_DELETE
        FROM CUSTOMER T
        LEFT JOIN ROLE R ON R.ID = T.ROLE
        <where>
            <if test="searchValue != '' and  searchValue != null">
                AND (T.ACCOUNT LIKE '%' || #{searchValue} || '%' OR R.ROLE_NAME LIKE '%' || #{searchValue} || '%'
                OR T.NICK_NAME LIKE '%' || #{searchValue} || '%' OR T.AGE LIKE '%' || #{searchValue} || '%'
                OR T.SEX LIKE '%' || #{searchValue} || '%')
            </if>
            <if test="sex != '' and  sex != null">
                AND T.SEX = #{sex}
             </if>
            <if test="role != '' and  role != null">
                AND T.ROLE = #{role}
            </if>
            AND T.IS_DELETE = 'N'
        </where>
        ORDER BY T.CREATE_TIME,T.ACCOUNT
        <if test="currentPage != '' and currentPage != null and pageSize != '' and pageSize != null">
            OFFSET (#{currentPage} - 1) * #{pageSize} ROWS
            FETCH FIRST #{pageSize} ROWS ONLY
        </if>
    </select>

    <select id="selCustomer" resultType="com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo">
        select T.ID id,T.ACCOUNT account,T.ROLE role,T.HEAD_PORTRAIT_NAME headPortraitName,
               T.HEAD_PORTRAIT_FILE headPortraitFile,T.NICK_NAME nickName,T.PASSWORD password,T.SALT salt,
               TO_CHAR(T.BIRTHDAY, 'YYYY-MM-DD') AS birthday,T.AGE age,T.SEX sex,T.SAFETY_PROBLEM1_ID safetyProblem1Id,
               T.SAFETY_PROBLEM2_ID safetyProblem2Id,T.SAFETY_PROBLEM3_ID safetyProblem3Id,
               T.SAFETY_PROBLEM1_ANSWER safetyProblem1Answer,T.SAFETY_PROBLEM2_ANSWER safetyProblem2Answer,
               T.SAFETY_PROBLEM3_ANSWER safetyProblem3Answer,
               T.CREATE_TIME createTime,T.UPDATE_TIME updateTime,T.IS_DELETE isDelete
        FROM CUSTOMER T
        LEFT JOIN ROLE R ON R.ID = T.ROLE
        WHERE T.ACCOUNT = #{ACCOUNT}
    </select>

    <update id="updPassword">
        UPDATE CUSTOMER T
        SET T.PASSWORD = #{password},T.SALT = #{salt}
        WHERE T.ACCOUNT = #{account}
    </update>

    <select id="selCustomersCount" resultType="int">
        SELECT COUNT(*)
        FROM CUSTOMER T
        LEFT JOIN ROLE R ON R.ID = T.ROLE
        <where>
            <if test="searchValue != '' and  searchValue != null">
                AND (T.ACCOUNT LIKE '%' || #{searchValue} || '%' OR R.ROLE_NAME LIKE '%' || #{searchValue} || '%'
                OR T.NICK_NAME LIKE '%' || #{searchValue} || '%' OR T.AGE LIKE '%' || #{searchValue} || '%'
                OR T.SEX LIKE '%' || #{searchValue} || '%')
            </if>
            <if test="sex != '' and  sex != null">
                AND T.SEX = #{sex}
            </if>
            <if test="role != '' and  role != null">
                AND T.ROLE = #{role}
            </if>
            AND T.IS_DELETE = 'N'
        </where>
    </select>

    <update id="delCustomerById">
        UPDATE CUSTOMER T
        SET T.IS_DELETE = 'Y'
        WHERE T.ID = #{id}
    </update>

    <update id="uploadHeadPortrait">
        UPDATE CUSTOMER T
        SET T.HEAD_PORTRAIT_NAME = #{headPortraitName},T.HEAD_PORTRAIT_FILE = #{headPortraitFile}
        WHERE T.ID = #{id}
    </update>

    <update id="updInformation">
    update CUSTOMER T
    <trim prefix="SET" suffixOverrides=",">
        <if test="nickName != null">T.NICK_NAME = #{nickName},</if>
        <if test="sex != null">T.SEX = #{sex},</if>
        <if test="age != null">T.AGE = #{age},</if>
        <if test="birthday != null">T.BIRTHDAY = TO_DATE(#{birthday}, 'YYYY-MM-DD'),</if>
    </trim>
        where T.ACCOUNT = #{account}
    </update>

    <update id="updSafetyProblem">
        update CUSTOMER T
        SET T.SAFETY_PROBLEM1_ID = #{safetyProblem1Id},
            T.SAFETY_PROBLEM2_ID = #{safetyProblem2Id},
            T.SAFETY_PROBLEM3_ID = #{safetyProblem3Id},
            T.SAFETY_PROBLEM1_ANSWER = #{safetyProblem1Answer},
            T.SAFETY_PROBLEM2_ANSWER = #{safetyProblem2Answer},
            T.SAFETY_PROBLEM3_ANSWER = #{safetyProblem3Answer}
        WHERE T.ACCOUNT = #{account}
    </update>
</mapper>

2、LoginAndRegisterMapper.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.rabbitSystem.carrot.mapper.LoginAndRegisterMapper">

    <insert id="register">
        INSERT INTO CUSTOMER T (
            T.ID, T.ACCOUNT, T.NICK_NAME, T.PASSWORD, T.SALT,
            T.BIRTHDAY, T.AGE, T.SEX,
            T.SAFETY_PROBLEM1_ID, T.SAFETY_PROBLEM2_ID, T.SAFETY_PROBLEM3_ID,
            T.SAFETY_PROBLEM1_ANSWER, T.SAFETY_PROBLEM2_ANSWER, T.SAFETY_PROBLEM3_ANSWER
        ) VALUES (
            #{id}, #{account}, #{nickName}, #{password}, #{salt},
            TO_DATE(#{birthday}, 'YYYY-MM-DD'), #{age}, #{sex},
            #{safetyProblem1Id}, #{safetyProblem2Id}, #{safetyProblem3Id},
            #{safetyProblem1Answer}, #{safetyProblem2Answer}, #{safetyProblem3Answer}
        )
    </insert>

</mapper>

3、RoleMapper.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.rabbitSystem.carrot.mapper.RoleMapper">

    <resultMap id="roleResultMap" type="com.rabbitSystem.carrot.pojo.sel.RolePojo">
        <result property="id" column="ID"/>
        <result property="roleName" column="ROLE_NAME"/>
        <result property="createTime" column="CREATE_TIME"/>
        <result property="updateTime" column="UPDATE_TIME"/>
        <result property="isDelete" column="IS_DELETE"/>
    </resultMap>

    <select id="selRoles" resultMap="roleResultMap">
        SELECT T.ID,T.ROLE_NAME,T.CREATE_TIME,T.UPDATE_TIME,T.IS_DELETE FROM ROLE T
    </select>
</mapper>

4、SafetyProblemMapper.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.rabbitSystem.carrot.mapper.RoleMapper">

    <resultMap id="roleResultMap" type="com.rabbitSystem.carrot.pojo.sel.RolePojo">
        <result property="id" column="ID"/>
        <result property="roleName" column="ROLE_NAME"/>
        <result property="createTime" column="CREATE_TIME"/>
        <result property="updateTime" column="UPDATE_TIME"/>
        <result property="isDelete" column="IS_DELETE"/>
    </resultMap>

    <select id="selRoles" resultMap="roleResultMap">
        SELECT T.ID,T.ROLE_NAME,T.CREATE_TIME,T.UPDATE_TIME,T.IS_DELETE FROM ROLE T
    </select>
</mapper>

四、前端配置文件相关

1、index.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>兔子系统</title>
</head>
<body>
    <div id="app" style="margin: 0;padding: 0;width: 100%;height: 100vh;"></div>
</body>
<style>
    body{
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100vh;
    }
</style>
</html>

2、HelloWorld.vue

<template>
  <div class="hello" id="app">
    <h1>{{ msg }}</h1>
    <p>
      For a guide and recipes on how to configure / customize this project,<br>
      check out the
      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
    </p>
    <h3>Installed CLI Plugins</h3>
    <ul>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
    </ul>
    <h3>Essential Links</h3>
    <ul>
      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
    </ul>
    <h3>Ecosystem</h3>
    <ul>
      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

3、index.js

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import App from '@/App';
import CustomerPage from '@/module/rabbit-system/views/CustomerPage';
import LoginPage from '@/module/rabbit-system/views/LoginPage';
import DataScreen from '@/module/rabbit-system/views/DataScreen';
import HomePage from '@/module/rabbit-system/views/HomePage';
import RegisterPage from '@/module/rabbit-system/views/RegisterPage';
import ForgetPasswordPage from '@/module/rabbit-system/views/RetrievePassword';
import NotFound from '@/NotFoundPage';

// 检查用户是否已登录
function isAuthenticated() {
    return !!sessionStorage.getItem('account'); // 假设存储用户信息的键为 'account'
}

const routes = [
    { path: '/', component: App },
    { path: '/CustomerPage', component: CustomerPage, meta: { requiresAuth: true } },// 添加认证需要
    { path: '/LoginPage', component: LoginPage },
    { path: '/RegisterPage', component: RegisterPage },
    { path: '/ForgetPasswordPage', component: ForgetPasswordPage },
    { path: '/DataScreen', component: DataScreen, meta: { requiresAuth: true } }, // 添加认证需要
    { path: '/HomePage', component: HomePage, meta: { requiresAuth: true } }, // 添加认证需要
    { path: '/:pathMatch(.*)*', component: NotFound },
];

const router = createRouter({
    history: createWebHistory(),
    routes,
});

// 全局前置守卫
router.beforeEach((to, from, next) => {
    // 检查目标路由是否需要认证
    if (to.matched.some(record => record.meta.requiresAuth)) {
        // 如果目标路由需要认证且用户未认证
        if (!isAuthenticated()) {
            next('/LoginPage'); // 重定向到登录页面
        } else {
            next(); // 允许访问目标路由
        }
    } else {
        next(); // 允许访问无认证要求的路由
    }
});

export default router;

4、App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

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

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

5、main.js

import { createApp } from 'vue';
import RabbitSystemVueApp from './RabbitSystemVueApp'; // 引入主 Vue 组件
import router from './router'; // 引入路由配置
import ElementPlus from 'element-plus'; // 引入 Element Plus UI 库
import 'element-plus/dist/index.css'; // 引入 Element Plus 的样式文件
import zhCn from 'element-plus/es/locale/lang/zh-cn'; // 引入中文语言包

// 创建 Vue 应用实例
const app = createApp(RabbitSystemVueApp);

// 使用插件
app.use(router); // 注册路由插件
app.use(ElementPlus, { locale: zhCn }); // 注册 Element Plus 插件并引用中文包
// 挂载应用到 DOM 元素
app.mount('#app'); // 将 Vue 应用挂载到 id 为 'app' 的 DOM 元素上

6、NotFoundPage.vue

<!-- src/components/NotFound.vue -->
<template>
  <div class="not-found">
    <div class="container">
      <h1>404</h1>
      <p>哎呀!我们找不到你要的页面。</p>
      <router-link class="home-link" to="/LoginPage">返回首页</router-link>
    </div>
  </div>
</template>

<script setup>
</script>

<style scoped>
.not-found {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: linear-gradient(135deg, #61E7FFFF, #d7a1b2);
  color: #fff;
  font-family: 'Arial', sans-serif;
}

.container {
  text-align: center;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 10px;
  padding: 40px;
  max-width: 500px;
  width: 100%;
}

h1 {
  font-size: 120px;
  margin: 0;
  font-weight: bold;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}

p {
  font-size: 18px;
  margin: 20px 0;
  line-height: 1.5;
}

.home-link {
  display: inline-block;
  padding: 10px 20px;
  margin-top: 20px;
  font-size: 16px;
  color: #fff;
  background-color: #42b983;
  border-radius: 5px;
  text-decoration: none;
  transition: background-color 0.3s, transform 0.3s;
}

.home-link:hover {
  background-color: #359d77;
  transform: scale(1.05);
}

.home-link:focus {
  outline: none;
}
</style>

7、RabbitSystemVueApp.vue

<template>
  <router-view>

  </router-view> <!-- 渲染匹配的路由组件 -->
</template>

<script>

</script>

<style scoped>

</style>

8、vue.config.js

const { defineConfig } = require('@vue/cli-service');
const { DefinePlugin } = require('webpack');
const path = require('path');

module.exports = defineConfig({
  // 配置开发服务器
  devServer: {
    port: 8082, // 设置开发服务器的端口号为 8082
  },
  // 是否转译依赖(依赖于 babel)
  transpileDependencies: true,

  // 配置公共路径,根据环境变量设置不同的路径
  publicPath: process.env.NODE_ENV === 'production' ? '/myapp/' : '/',

  // 配置 webpack
  configureWebpack: {
    plugins: [
      new DefinePlugin({
        // 定义全局常量,用于在生产环境中关闭 Vue 的生产模式水合不匹配的详细信息
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(false)
      }),
    ],
  },
});

9、package.json

执行命令:npm init -y 使用默认值快速生成 

五、前端代码

1、工具

安装xlsx库 npm install xlsx  //用来导出用户信息 Excel 表格的。

1、utils.js 工具js

     放一些通用的方法

// 批量处理数字英文转换(传入的是用户信息集合)
export function processCustomerData(customers) {
    customers.forEach(customer => {
        // 处理 sex
        if (customer.sex === "0") {
            customer.sex = "女";
        } else if (customer.sex === "1") {
            customer.sex = "男";
        } else if (customer.sex === "2") {
            customer.sex = "保密";
        }
        // 处理 isDelete
        if (customer.isDelete === "N") {
            customer.isDelete = "是";
        } else if (customer.isDelete === "Y") {
            customer.isDelete = "否";
        }
    });
    return customers;
}

// 根据生日获得年龄
export function getAgeByBirthday(birthday) {
    if (!birthday) return null; // 如果生日未选择则返回 null
    const today = new Date();
    const birthDate = new Date(birthday);
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    // 如果生日还未到,年龄减 1
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
        age--;
    }
    return age;
}

// 匹配中文英文和数字
export function isValidInput(str) {
    // 正则表达式:允许中文、英文和数字
    const pattern = /^[\u4E00-\u9FA5A-Za-z0-9]+$/;
    return pattern.test(str);
}

//计算字符串的长度
export function charsLengthUtil(str){
    //中文长度
    let cLength = 0;
    //英文长度
    let yLength = 0;
    // 匹配中文
    const pattern = /[\u4E00-\u9FA5]/;
    // 计量字符串总长度
    let allLength;
    for (let i = 0; i < str.length; i++) {
        const char = str[i];
        if (pattern.test(char)) {
            cLength += 1;
        } else {
            yLength += 0.5;
        }
    }
    allLength = cLength + yLength;
    return allLength;
}

// 将选择的日期格式化为 YYYY-MM-DD
export function dateFormatConversion(birthday){
    // 获取选择的日期
    const localDate = new Date(birthday); // 假设 updBirthday.value 是 Date 对象
    // 获取 UTC 日期部分
    const utcDate = new Date(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate()));
    // 格式化为 YYYY-MM-DD
    const formattedBirthday = utcDate.toISOString().split('T')[0];
    return formattedBirthday
}

2、页面

1、LoginPage.vue

     登录页面。

<template>
  <div v-loading = "loading" style="background: linear-gradient(to bottom, green, orange);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px;margin-top: 100px">
      <span style="font-size: 60px;color: #FFA500;">欢迎登录兔子系统</span>

      <div style="margin-top: 150px">
        账号:<el-input v-model="account" style="width: 240px" placeholder="请输入账号" />
      </div>
      <div style="margin-top: 10px">
        密码:<el-input v-model="password" type="password" style="width: 240px" placeholder="请输入密码" show-password/>
      </div>
      <div class="mb-4" style="margin-top: 10px">
        <el-button type="primary" @click="register">注册</el-button>
        <el-button type="success" @click="login">登录</el-button>
      </div>
      <div style="margin-top: 10px">
        <el-link type="danger" style="font-size: 10px" @click="forgetPassword">忘记密码?</el-link>
    </div>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
import router from "@/router";
import {loginJS} from "@/module/rabbit-system/api/registerAndLogin";
import {ElMessageBox} from "element-plus";

export default {
  name: 'LoginPage',
  setup() {
    const form ={
      account: "",
      password: "",
    };
    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 定义响应式变量
    const loading = ref(false);
    const account = ref("");
    const password = ref("");
    const register = () => {
      // router.replace('/RegisterPage'); 浏览器可以返回,不销毁当前页面
      router.replace('/RegisterPage'); // 浏览器不可以返回,销毁当前页面
    };

    // 一个提示弹窗的方法
    const popUpWindows = (msg) => {
      ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      })
    };
    // 登录按钮
    const login = async() => {
      if (account.value.length === 0){
        popUpWindows("请输入您的账号");
      }else if (password.value.length === 0){
        popUpWindows("请输入您的密码");
      }else {
        form.account = account.value;
        form.password = password.value;
        const accountSessionStorage = account.value;
        // 开始登录
        loading.value = true;
        const data = await loginJS(form);
        await delay(1000);
        if (data.data.i === 0){
          loading.value = false;
          popUpWindows(data.msg);
        }else if (data.data.i === 1){
          loading.value = false;
          // 存储 sessionStorage ,他是 Web 存储机制,用于在用户会话期间存储数据,就是当前页面在就在,页面关闭就没了,可以存储一些用户信息等
          sessionStorage.setItem('account', accountSessionStorage); // 将用户的账号存储到 sessionStorage
          await router.push('/HomePage');
          popUpWindows(data.msg);
        }else {
          loading.value = false;
          popUpWindows(data.msg);
        }
      }
    };

    // 忘记密码
    const forgetPassword = () => {
      router.replace('/ForgetPasswordPage');
    };

    return {
      loading,
      account,
      password,
      login,
      register,
      popUpWindows,
      forgetPassword,
    };
  }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

2、RegisterPage.vue

     注册页面。

<template>
  <div v-loading = "loading" style="background: linear-gradient(to top, green, orange);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px; left: 200px;position: fixed;">
      <span style="font-size: 60px;color: #FFA500;">欢迎注册兔子系统</span>
    </div>
    <div style="margin-left: 600px;">
      <div style="margin-top: 100px">
        <el-avatar :src="require('@/assets/兔子来了.jpeg')" style="width: 100px;height: 100px"/>
      </div>
      <div style="margin-top: 20px">
        请输入昵称:<el-input v-model="nickName" style="width: 240px" placeholder="请输入昵称" />
      </div>
      <div style="margin-top: 10px">
        请选择性别:<el-radio-group v-model="sex"> <!--v-model="sex" 用于双向绑定-->
        <el-radio value="1" border style="width: 74px;">男</el-radio>
        <el-radio value="0" border style="width: 74px;margin-left: -20px">女</el-radio>
        <el-radio value="2" border style="width: 74px;margin-left: -20px">保密</el-radio>
      </el-radio-group>
      </div>
      <div style="margin-top: 10px">
        请选择生日:<el-date-picker
          type="date"
          placeholder="出生日期"
          style="width: 240px"
          v-model="birthday"
      />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
          v-model="password1"
          style="width: 240px"
          type="password"
          placeholder="请输入密码"
          show-password
      />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
          v-model="password2"
          style="width: 240px"
          type="password"
          placeholder="请输入密码"
          show-password
      />
      </div>
      <div style="margin-top: 5px;">
        <!-- v-if:当你需要根据条件动态地添加或移除元素时,使用 v-if。它会完全从 DOM 中移除元素,不会占用空间。-->
        <el-text v-if="password1 !== password2" type="danger">两次输入的密码不一致!</el-text>
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem1 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem1Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem2 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem2Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem3 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem3Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div class="mb-4" style="margin-top: 10px;">
        <!-- :style="{ width: '340px' }" 设置按钮宽度 -->
        <el-button type="primary" @click="goBack">返回</el-button>
        <el-button type="primary" @click="register">注册</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import {onMounted, ref, } from 'vue';
import router from "@/router";
import {ElMessageBox} from "element-plus";
import {registerJS, selSafetyProblemsJS} from "@/module/rabbit-system/api/registerAndLogin";
import {isValidInput, charsLengthUtil, getAgeByBirthday} from "@/module/uitls"
export default {
  name: 'LoginPage',
  setup() {
    // 页面一打开就执行
    onMounted(() => {
      loadProblems();
    });
    const form ={
      nickName: "",
      sex: "",
      age: "",
      birthday: "",
      password: "",
      safetyProblem1Id: "",
      safetyProblem2Id: "",
      safetyProblem3Id: "",
      safetyProblem1Answer: "",
      safetyProblem2Answer: "",
      safetyProblem3Answer: "",
    };
    // 定义响应式变量
    const loading = ref(false);
    const nickName = ref("");
    // v-model="sex" 用于双向绑定 ,这里是给组件赋一个默认的值
    const sex = ref('')
    const birthday = ref("");
    const password1 = ref("");
    const password2 = ref("");
    let safetyProblem1 = ref('请选择安全问题1'); // 确保在这里声明 safetyProblem1
    let safetyProblem2 = ref('请选择安全问题2');
    let safetyProblem3 = ref('请选择安全问题3');
    let safetyProblem1Id = ref('');
    let safetyProblem2Id = ref('');
    let safetyProblem3Id = ref('');
    const safetyProblem1Answer = ref("");
    const safetyProblem2Answer = ref("");
    const safetyProblem3Answer = ref("");

    // let 可以在下面的函数内部重新赋值
    let message;
    // 一个提示弹窗的方法
    const popUpWindows = (msg) => {
      ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      })
    };

    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 重置表单数据
    const resetForm = () => {
      nickName.value = "";
      sex.value = "";
      birthday.value = "";
      password1.value = "";
      password2.value = "";
      safetyProblem1.value = '请选择安全问题1';
      safetyProblem2.value = '请选择安全问题2';
      safetyProblem3.value = '请选择安全问题3';
      safetyProblem1Id.value = '';
      safetyProblem2Id.value = '';
      safetyProblem3Id.value = '';
      safetyProblem1Answer.value = "";
      safetyProblem2Answer.value = "";
      safetyProblem3Answer.value = "";
    };

    // 加载安全问题
    let questions = ref([]);
    const loadProblems = async () => {
      try {
        const response = await selSafetyProblemsJS();
        const dd  = response.data.data;
        questions.value = dd;
      } catch (error) {
        console.error('获取问题失败:', error);
      }
    };
    // 选择安全问题后将安全问题及id渲染到标签里
    const customerSafetyPlus = (command) => {
      const [order,problem, id] = command.split('/');
      if (order === '1'){
        safetyProblem1Id.value = id;
        safetyProblem1.value = problem;
      }else if (order === '2'){
        safetyProblem2Id.value = id;
        safetyProblem2.value = problem;
      }else if (order === '3'){
        safetyProblem3Id.value = id;
        safetyProblem3.value = problem;
      }
    }

    // 返回按钮
    const goBack = () => {
      router.replace('/LoginPage');
    };
    // 注册按钮
    const register = async() => {
      if (nickName.value == "" || sex.value == "" || birthday.value == "" || password1.value == "" || password2.value == "") {
        message = "请完善用户信息!";
        popUpWindows(message);
      }else if (isValidInput(nickName.value) === false) {
        message = "昵称请使用中文英文和数字!";
        popUpWindows(message);
      } else if (charsLengthUtil(nickName.value) > 7) {
        message = "昵称不得超过7个字符长度!";
        popUpWindows(message);
      } else if (password1.value.length < 6 || password2.value.length < 6 || password2.value.length > 20 || password2.value.length > 20) {
        message = "密码长度不得小于6位,不得大于20位!";
        popUpWindows(message);
      } else if (password1.value !== password2.value) {
        message = "两次输入的密码不一致!";
        popUpWindows(message);
      } else if (getAgeByBirthday(birthday.value) < 18 && getAgeByBirthday(birthday.value) != null) {
        message = "年龄必须大于18岁!";
        popUpWindows(message);
      } else if (safetyProblem1.value == '请选择安全问题1' || safetyProblem2.value == '请选择安全问题2' || safetyProblem3.value == '请选择安全问题3') {
        message = "请选择3个安全问题!";
        popUpWindows(message);
      } else if (safetyProblem1Answer.value.length == 0) {
        message = "请回答第一个问题!";
        popUpWindows(message);
      } else if (safetyProblem2Answer.value.length == 0) {
        message = "请回答第二个问题!";
        popUpWindows(message);
      } else if (safetyProblem3Answer.value.length == 0) {
        message = "请回答第三个问题!";
        popUpWindows(message);
      }else if (safetyProblem1Answer.value.length > 20 || safetyProblem2Answer.value.length > 20 || safetyProblem3Answer.value.length > 20) {
        message = "答案字数不得超过20个字!";
        popUpWindows(message);
      } else if (isValidInput(safetyProblem1Answer.value) === false || isValidInput(safetyProblem2Answer.value) === false || isValidInput(safetyProblem3Answer.value) === false) {
        message = "昵称请使用中文英文和数字!";
        popUpWindows(message);
      }else {
        // 获取选择的日期
        const localDate = new Date(birthday.value); // 假设 updBirthday.value 是 Date 对象
        // 获取 UTC 日期部分
        const utcDate = new Date(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate()));
        // 格式化为 YYYY-MM-DD
        const formattedBirthday = utcDate.toISOString().split('T')[0];
        form.nickName= nickName.value;
        form.sex= sex.value;
        form.age= getAgeByBirthday(birthday.value);
        form.birthday = formattedBirthday;
        form.password= password1.value;
        form.safetyProblem1Id= safetyProblem1Id.value;
        form.safetyProblem2Id= safetyProblem2Id.value;
        form.safetyProblem3Id= safetyProblem3Id.value;
        form.safetyProblem1Answer= safetyProblem1Answer.value;
        form.safetyProblem2Answer= safetyProblem2Answer.value;
        form.safetyProblem3Answer= safetyProblem3Answer.value;
        // 开始注册
        loading.value = true;
        const data = await registerJS(form);
        await delay(1000);
        loading.value = false;
        if (data.data.i > 0){
          popUpWindows(data.msg);
          await router.replace('/LoginPage');
        }else {
          popUpWindows(data.msg);
          resetForm();  // 清空表单数据
        }
      }
      loading.value = false;
    };

    return {
      loading,
      questions,
      nickName,
      sex,
      birthday,
      password1,
      password2,
      safetyProblem1,
      safetyProblem2,
      safetyProblem3,
      safetyProblem1Id,
      safetyProblem2Id,
      safetyProblem3Id,
      safetyProblem1Answer,
      safetyProblem2Answer,
      safetyProblem3Answer,
      goBack,
      register,
      popUpWindows,
      customerSafetyPlus
    };
  }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

3、RetrievePassword.vue

     忘记密码页面

<template>
  <div v-loading = "loading" style="background: linear-gradient(to top, green, orange);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px; left: 200px;position: fixed;">
      <span style="font-size: 60px;color: #FFA500;">欢迎注册兔子系统</span>
    </div>
    <div style="margin-left: 600px;">
      <div style="margin-top: 100px">
        <el-avatar :src="require('@/assets/兔子来了.jpeg')" style="width: 100px;height: 100px"/>
      </div>
      <div style="margin-top: 20px">
        请输入昵称:<el-input v-model="nickName" style="width: 240px" placeholder="请输入昵称" />
      </div>
      <div style="margin-top: 10px">
        请选择性别:<el-radio-group v-model="sex"> <!--v-model="sex" 用于双向绑定-->
        <el-radio value="1" border style="width: 74px;">男</el-radio>
        <el-radio value="0" border style="width: 74px;margin-left: -20px">女</el-radio>
        <el-radio value="2" border style="width: 74px;margin-left: -20px">保密</el-radio>
      </el-radio-group>
      </div>
      <div style="margin-top: 10px">
        请选择生日:<el-date-picker
          type="date"
          placeholder="出生日期"
          style="width: 240px"
          v-model="birthday"
      />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
          v-model="password1"
          style="width: 240px"
          type="password"
          placeholder="请输入密码"
          show-password
      />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
          v-model="password2"
          style="width: 240px"
          type="password"
          placeholder="请输入密码"
          show-password
      />
      </div>
      <div style="margin-top: 5px;">
        <!-- v-if:当你需要根据条件动态地添加或移除元素时,使用 v-if。它会完全从 DOM 中移除元素,不会占用空间。-->
        <el-text v-if="password1 !== password2" type="danger">两次输入的密码不一致!</el-text>
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem1 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem1Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem2 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem2Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem3 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem3Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div class="mb-4" style="margin-top: 10px;">
        <!-- :style="{ width: '340px' }" 设置按钮宽度 -->
        <el-button type="primary" @click="goBack">返回</el-button>
        <el-button type="primary" @click="register">注册</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import {onMounted, ref, } from 'vue';
import router from "@/router";
import {ElMessageBox} from "element-plus";
import {registerJS, selSafetyProblemsJS} from "@/module/rabbit-system/api/registerAndLogin";
import {isValidInput, charsLengthUtil, getAgeByBirthday} from "@/module/uitls"
export default {
  name: 'LoginPage',
  setup() {
    // 页面一打开就执行
    onMounted(() => {
      loadProblems();
    });
    const form ={
      nickName: "",
      sex: "",
      age: "",
      birthday: "",
      password: "",
      safetyProblem1Id: "",
      safetyProblem2Id: "",
      safetyProblem3Id: "",
      safetyProblem1Answer: "",
      safetyProblem2Answer: "",
      safetyProblem3Answer: "",
    };
    // 定义响应式变量
    const loading = ref(false);
    const nickName = ref("");
    // v-model="sex" 用于双向绑定 ,这里是给组件赋一个默认的值
    const sex = ref('')
    const birthday = ref("");
    const password1 = ref("");
    const password2 = ref("");
    let safetyProblem1 = ref('请选择安全问题1'); // 确保在这里声明 safetyProblem1
    let safetyProblem2 = ref('请选择安全问题2');
    let safetyProblem3 = ref('请选择安全问题3');
    let safetyProblem1Id = ref('');
    let safetyProblem2Id = ref('');
    let safetyProblem3Id = ref('');
    const safetyProblem1Answer = ref("");
    const safetyProblem2Answer = ref("");
    const safetyProblem3Answer = ref("");

    // let 可以在下面的函数内部重新赋值
    let message;
    // 一个提示弹窗的方法
    const popUpWindows = (msg) => {
      ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      })
    };

    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 重置表单数据
    const resetForm = () => {
      nickName.value = "";
      sex.value = "";
      birthday.value = "";
      password1.value = "";
      password2.value = "";
      safetyProblem1.value = '请选择安全问题1';
      safetyProblem2.value = '请选择安全问题2';
      safetyProblem3.value = '请选择安全问题3';
      safetyProblem1Id.value = '';
      safetyProblem2Id.value = '';
      safetyProblem3Id.value = '';
      safetyProblem1Answer.value = "";
      safetyProblem2Answer.value = "";
      safetyProblem3Answer.value = "";
    };

    // 加载安全问题
    let questions = ref([]);
    const loadProblems = async () => {
      try {
        const response = await selSafetyProblemsJS();
        const dd  = response.data.data;
        questions.value = dd;
      } catch (error) {
        console.error('获取问题失败:', error);
      }
    };
    // 选择安全问题后将安全问题及id渲染到标签里
    const customerSafetyPlus = (command) => {
      const [order,problem, id] = command.split('/');
      if (order === '1'){
        safetyProblem1Id.value = id;
        safetyProblem1.value = problem;
      }else if (order === '2'){
        safetyProblem2Id.value = id;
        safetyProblem2.value = problem;
      }else if (order === '3'){
        safetyProblem3Id.value = id;
        safetyProblem3.value = problem;
      }
    }

    // 返回按钮
    const goBack = () => {
      router.replace('/LoginPage');
    };
    // 注册按钮
    const register = async() => {
      if (nickName.value == "" || sex.value == "" || birthday.value == "" || password1.value == "" || password2.value == "") {
        message = "请完善用户信息!";
        popUpWindows(message);
      }else if (isValidInput(nickName.value) === false) {
        message = "昵称请使用中文英文和数字!";
        popUpWindows(message);
      } else if (charsLengthUtil(nickName.value) > 7) {
        message = "昵称不得超过7个字符长度!";
        popUpWindows(message);
      } else if (password1.value.length < 6 || password2.value.length < 6 || password2.value.length > 20 || password2.value.length > 20) {
        message = "密码长度不得小于6位,不得大于20位!";
        popUpWindows(message);
      } else if (password1.value !== password2.value) {
        message = "两次输入的密码不一致!";
        popUpWindows(message);
      } else if (getAgeByBirthday(birthday.value) < 18 && getAgeByBirthday(birthday.value) != null) {
        message = "年龄必须大于18岁!";
        popUpWindows(message);
      } else if (safetyProblem1.value == '请选择安全问题1' || safetyProblem2.value == '请选择安全问题2' || safetyProblem3.value == '请选择安全问题3') {
        message = "请选择3个安全问题!";
        popUpWindows(message);
      } else if (safetyProblem1Answer.value.length == 0) {
        message = "请回答第一个问题!";
        popUpWindows(message);
      } else if (safetyProblem2Answer.value.length == 0) {
        message = "请回答第二个问题!";
        popUpWindows(message);
      } else if (safetyProblem3Answer.value.length == 0) {
        message = "请回答第三个问题!";
        popUpWindows(message);
      }else if (safetyProblem1Answer.value.length > 20 || safetyProblem2Answer.value.length > 20 || safetyProblem3Answer.value.length > 20) {
        message = "答案字数不得超过20个字!";
        popUpWindows(message);
      } else if (isValidInput(safetyProblem1Answer.value) === false || isValidInput(safetyProblem2Answer.value) === false || isValidInput(safetyProblem3Answer.value) === false) {
        message = "昵称请使用中文英文和数字!";
        popUpWindows(message);
      }else {
        // 获取选择的日期
        const localDate = new Date(birthday.value); // 假设 updBirthday.value 是 Date 对象
        // 获取 UTC 日期部分
        const utcDate = new Date(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate()));
        // 格式化为 YYYY-MM-DD
        const formattedBirthday = utcDate.toISOString().split('T')[0];
        form.nickName= nickName.value;
        form.sex= sex.value;
        form.age= getAgeByBirthday(birthday.value);
        form.birthday = formattedBirthday;
        form.password= password1.value;
        form.safetyProblem1Id= safetyProblem1Id.value;
        form.safetyProblem2Id= safetyProblem2Id.value;
        form.safetyProblem3Id= safetyProblem3Id.value;
        form.safetyProblem1Answer= safetyProblem1Answer.value;
        form.safetyProblem2Answer= safetyProblem2Answer.value;
        form.safetyProblem3Answer= safetyProblem3Answer.value;
        // 开始注册
        loading.value = true;
        const data = await registerJS(form);
        await delay(1000);
        loading.value = false;
        if (data.data.i > 0){
          popUpWindows(data.msg);
          await router.replace('/LoginPage');
        }else {
          popUpWindows(data.msg);
          resetForm();  // 清空表单数据
        }
      }
      loading.value = false;
    };

    return {
      loading,
      questions,
      nickName,
      sex,
      birthday,
      password1,
      password2,
      safetyProblem1,
      safetyProblem2,
      safetyProblem3,
      safetyProblem1Id,
      safetyProblem2Id,
      safetyProblem3Id,
      safetyProblem1Answer,
      safetyProblem2Answer,
      safetyProblem3Answer,
      goBack,
      register,
      popUpWindows,
      customerSafetyPlus
    };
  }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

4、HomePage.vue

     登陆后的主页面。

<template>
  <div v-loading = "loading" style="background: gray; width: 100%; height: 100vh; position: fixed;">
    <div style="background-color: green; width: 100%; height: 80px; display: flex; align-items: center; justify-content: space-between;">
      <h1 style=" width: 140px; display: block; border: none; outline: none;margin-left: 50px;color: white">兔子系统</h1>
      <div style="width: 800px;height: 70px;background-color: orange;display: flex; flex-direction: column;border-radius:15px">
        <el-text style="color: red;margin-top: 5px">公告:</el-text>
        <el-text style="align-self: flex-start;text-align: left;text-indent: 2em;color: white;margin-left: 10px;margin-right: 10px">我会让时间去证明,只有爱才能够战胜一切武力,只有爱才能够填补人们心中无穷无尽的欲望黑洞,也只有爱才能让这个世界达到永恒的和平。</el-text>
      </div>
      <div style="display: flex; align-items: center;cursor: pointer" @click="personalInformationShow = true">
        <img v-if="headPortraitFile"
             :src="`${headPortraitFile}`"
             alt="头像"
             style="width: 60px; height: 60px; border-radius: 50%;margin-right: 10px" />
        <div style="display: flex; flex-direction: column; margin-right: 20px;">
          <el-text style="align-self: flex-start;color: white">昵称:{{ nickName }}</el-text>
          <el-text style="align-self: flex-start;color: white">账号:{{ account }}</el-text>
        </div>
      </div>
    </div>
    <div style="width: 100%; height: 100vh; position: fixed; display: flex; margin-top: 5px;left: 0;">
      <div style="background-color: green; width: 200px; height: 100%; margin: 0; display: inline-block;">
        <el-row class="tac"> <!-- 行组件 -->
          <el-col :span="12"> <!-- 列组件 -->
            <el-menu style="width: 200px;" :unique-opened="true"> <!-- 单个垂直菜单组件 --> <!-- unique-opened="true" 始终最多只有一个菜单是展开的-->
              <el-sub-menu index="1">
                <template #title>
                  <span>系统管理</span>
                </template>
                <el-menu-item index="1-1" @click="customerManagement">用户管理</el-menu-item>
              </el-sub-menu>
<!--              <el-sub-menu index="2">-->
<!--                <template #title>-->
<!--                  <span>即时通讯</span>-->
<!--                </template>-->
<!--                <el-menu-item index="2-1">好友</el-menu-item>-->
<!--                <el-menu-item index="2-2">聊天</el-menu-item>-->
<!--              </el-sub-menu>-->
            </el-menu>
          </el-col>
        </el-row>
      </div>
      <div style="background-color: gray; width: 100%; height: 100vh; margin-left: 5px;">
        <iframe id="iframe" style="background-color: white; width: 100%; height: 100vh; border: none;" src="/DataScreen"></iframe>
      </div>
    </div>
  </div>

  <!-- 个人信息的遮罩层 -->
  <el-drawer v-model="personalInformationShow" :with-header="false" style="display: flex; flex-direction: column; height: 100%;background-color: #42b983">
    <div style="flex: 1; display: flex; flex-direction: column; justify-content: flex-start;">
      <div style="display: flex; align-items: center;">
        <img v-if="headPortraitFile"
             :src="`${headPortraitFile}`"
             alt="头像"
             style="width: 60px; height: 60px; border-radius: 50%;margin-right: 10px" />
        <div style="display: flex; flex-direction: column; margin-right: 20px;">
          <el-text style="align-self: flex-start;">昵称:{{ nickName }}</el-text>
          <el-text style="align-self: flex-start;">账号:{{ account }}</el-text>
        </div>
      </div>
      <div style="display: flex; flex-direction: column; margin-right: 10px;">
        <el-text style="align-self: flex-start;margin-top: 5px">年龄:{{ age }}</el-text>
        <el-text style="align-self: flex-start;margin-top: 5px">出生日期:{{ birthday }}</el-text>
        <el-text style="align-self: flex-start;margin-top: 5px">性别:{{ sex }}</el-text>
        <el-text style="align-self: flex-start;margin-top: 5px">注册时间:{{ createTime }}</el-text>
      </div>
    </div>
    <div style="height: 70%"></div>
    <div style="padding: 10px; text-align: center; background-color: #359d77 ;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); border-radius: 5px;">
      <el-button type="primary" style="width: 180px" @click="setting()">设置</el-button>
      <el-button type="primary" style="width: 180px" @click="logOut()">退出登录</el-button>
    </div>
  </el-drawer>

  <!-- 设置页面 -->
  <el-dialog :close-on-click-modal="false" v-model="settingShow" width="200px" style="margin-top: 120px" append-to-body><!--要支持动态绑定就在前面加个 : ,如::title="detailTile"-->
    <span style="font-size: 20px">设置</span>
    <div>
      <el-button @click="updPersonalHeadPortrait()" style="margin-top: 10px">更换头像</el-button>
    </div>
    <div>
      <el-button @click="updPersonalInformation()" style="margin-top: 10px">修改个人信息</el-button>
    </div>
    <div>
      <el-button @click="updPassword()" style="margin-top: 10px">修改密码</el-button>
    </div>
    <div>
      <el-button @click="updSafetyProblem()" style="margin-top: 10px">修改安全问题</el-button>
    </div>
    <div style="text-align: right;margin-top: 15px">
      <el-button @click="cancelShow()">取消</el-button>
    </div>
  </el-dialog>

  <!--  替换头像页面 -->
  <el-dialog :close-on-click-modal="false" v-model="updPersonalHeadPortraitShow" width="500" style="margin-top: 120px" append-to-body>
    <span style="font-size: 20px">更换头像</span>
    <div style="display: flex; align-items: center;">
      <img v-if="headPortraitFile"
           :src="`${headPortraitFile}`"
           alt="头像"
           style="width: 100px; height: 100px;margin-left: 10px;margin-top: 10px;border-radius: 50%" />
      <!--   <el-avatar> 组件用于展示用户的头像或图片。在你的代码中,它的作用如下:
                src 属性:这个属性绑定到 headPortraitFile,表示头像的图片源。当用户选择了一张图片后,headPortraitFile 会更新为该图片的 URL,从而使 <el-avatar> 显示用户选择的图片。
                style 属性:设置了头像的宽度和高度为 100px,确保头像在界面上以合适的尺寸显示。
                v-if="headPortraitFile":这个指令确保只有在 headPortraitFile(图片的src) 存在时,头像才会显示。这意味着在用户未选择图片之前,头像不会占用界面空间。   -->
      <div style="margin-top: 5px;margin-left: 10px">
        <div>
          <input type="file" @change="selectHeadPortrait" accept="image/*" style="width: 68px;"/>
          <span v-if="selectedFileName" style="margin-left: 5px">{{ selectedFileName }}</span>
          <!--    允许用户选择本地的图片,并且用户只能选择图片文件  -->
        </div>
        <div style="margin-top: 10px">
          <button @click="uploadHeadPortrait" v-if="updHeadPortraitFile">替换头像</button>
        </div>
      </div>
    </div>
    <div style="text-align: right;margin-top: 10px;margin-right: 14px">
      <el-button @click="cancelShow()">取消</el-button>
    </div>
  </el-dialog>

  <!-- 修改个人信息页面 -->
  <el-dialog :close-on-click-modal="false" v-model="updPersonalInformationShow" width="604" style="margin-top: 120px" append-to-body><!--要支持动态绑定就在前面加个 : ,如::title="detailTile"-->
    <span style="font-size: 20px">修改个人信息</span>
    <div style="margin-top: 20px;margin-left: 10px">
      账号:<el-input v-model="updAccount" style="width: 220px;margin-right: 20px" disabled/>
      昵称:<el-input v-model="updNickName" style="width: 220px;"/>
    </div>
    <div style="margin-top: 10px;margin-left: 10px">
      性别:<el-radio-group v-model="updSex" style="margin-right: 20px"> <!--v-model="sex" 用于双向绑定-->
      <el-radio value="0" border style="width: 60px;">女</el-radio>
      <el-radio value="1" border style="width: 60px;margin-left: -20px">男</el-radio>
      <el-radio value="2" border style="width: 80px;margin-left: -20px">保密</el-radio>
    </el-radio-group>
      生日:<el-date-picker type="date" style="width: 220px;" v-model="updBirthday"/>
    </div>
    <div style="text-align: right;margin-top: 10px;margin-right: 14px">
      <el-button @click="cancelShow()">取消</el-button>
      <el-button @click="updInformation()">提交</el-button>
    </div>
  </el-dialog>

  <!-- 安全问题校验页面 -->
  <el-dialog :close-on-click-modal="false" v-model="safetyProblemVerifyShow" width="370px" style="margin-top: 120px" append-to-body>
    <span style="font-size: 20px">请选择并回答原来的安全问题</span>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem1 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key="item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem2 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem3 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div class="mb-4" style="margin-top: 10px;text-align: center;">
      <el-button type="primary" @click="cancelShow">取消</el-button>
      <el-button type="primary" @click="verify">开始校验</el-button>
    </div>
  </el-dialog>
  <!--修改密码页面-->
  <el-dialog v-model="updPasswordForm" title="请输入您的新密码" width="250" style="margin-top: 30vh;" :close-on-click-modal="false"> <!-- close-on-click-modal 控制点击外部是否关闭-->
    <el-input v-model="newPassword1" type="password" autocomplete="off" placeholder="请输入您的新密码"/><!-- autocomplete="off" 防止自动填充 -->
    <el-input v-model="newPassword2" type="password" autocomplete="off" placeholder="请再次输入您的新密码" style="margin-top: 5px"/><!-- autocomplete="off" 防止自动填充 -->
    <div style="margin-top: 5px;">
      <el-text v-if="newPassword1 !== newPassword2" type="danger">两次输入的密码不一致!</el-text>
    </div>
    <el-button style="margin-top: 10px" @click="updPasswordForm = false">取消</el-button>
    <el-button style="margin-top: 10px" @click="updPasswordSubmit">确定</el-button>
  </el-dialog>
  <!--修改安全问题页面-->
  <el-dialog :close-on-click-modal="false" v-model="updSafetyProblemVerifyShow" width="370px" style="margin-top: 120px" append-to-body>
    <span style="font-size: 20px">请重新选择安全问题并输入答案</span>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem1 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem2 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div style="margin-top: 10px;">
      <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
        <span style="width: 274px">{{ safetyProblem3 }}</span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item.id">
              {{ item.problem }}
            </el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
    <div>
      <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
    </div>
    <div class="mb-4" style="margin-top: 10px;text-align: center;">
      <!-- :style="{ width: '340px' }" 设置按钮宽度 -->
      <el-button type="primary" @click="cancelShow">取消</el-button>
      <el-button type="primary" @click="updSafetyProblemSubmit()">提交</el-button>
    </div>
  </el-dialog>
<!--退出登录提示-->
  <el-dialog :close-on-click-modal="false" v-model="logOutShow" width="370px" style="margin-top: 300px" append-to-body>
    <span style="font-size: 20px">确定退出吗?</span>
    <div class="mb-4" style="margin-top: 10px;text-align: center;">
      <el-button @click="cancelShow">取消</el-button>
      <el-button @click="logOutSubmit">确定</el-button>
    </div>
  </el-dialog>
</template>

<script>
import { onMounted, ref} from "vue";
import {loadCustomerInformationJS} from "../api/home"
import {ElMessageBox} from "element-plus";
import {uploadHeadPortraitJS,updInformationJS,updSafetyProblemJS} from "../api/customer"
import {selSafetyProblemsJS} from "@/module/rabbit-system/api/registerAndLogin";
import {retrievePasswordJs, updPasswordJS} from "@/module/rabbit-system/api/retrievePassword";
import router from "@/router";
import {charsLengthUtil, dateFormatConversion, getAgeByBirthday, isValidInput} from "@/module/uitls";
export default {
  name: "HomePage",
  setup(){
    const loading = ref(false); //加载
    const personalInformationShow = ref(false); // 控制个人信息抽屉弹窗
    const settingShow = ref(false); // 控制个人信息设置弹窗
    const updPersonalInformationShow = ref(false);  //控制修改个人信息的页面
    const updPersonalHeadPortraitShow = ref(false);
    const selectedFile = ref(null);
    const headPortraitFile = ref("");  //原始头像
    const updHeadPortraitFile = ref(false); //替换头像时的控制替换头像按钮是否显示
    const selectedFileName = ref(''); // 用于存储选择的文件名
    let nickName = ref("");
    let updNickName = ref("");
    let account = ref("");
    let updAccount = ref("");
    let age = ref("");
    let sex = ref("");
    let updSex = ref("");
    let createTime = ref("");
    let birthday = ref("");
    let updBirthday = ref("");

    let safetyProblemVerifyShow = ref(false);
    let safetyProblem1 = ref('请选择安全问题1');
    let safetyProblem2 = ref('请选择安全问题2');
    let safetyProblem3 = ref('请选择安全问题3');
    let safetyProblem1Id = ref('');
    let safetyProblem2Id = ref('');
    let safetyProblem3Id = ref('');
    const safetyProblem1Answer = ref("");
    const safetyProblem2Answer = ref("");
    const safetyProblem3Answer = ref("");
    const newPassword1 = ref("");
    const newPassword2 = ref("");
    const updPasswordForm = ref(false);
    const isShow = ref("");
    const updSafetyProblemVerifyShow = ref(false);
    const logOutShow = ref(false);
    // 一个提示弹窗的方法
    const popUpWindows = async (msg) => {
      await ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      });
    };
    // 页面一打开就执行的方法
    onMounted(() => {
      loadCustomerInformation(sessionStorage.getItem("account"));
    });
    // 加载用户的所有数据
    const loadCustomerInformation = async(sessionAccount) => {
      loading.value = true;
      const data = await loadCustomerInformationJS(sessionAccount);
      if (data.code === 200){
        const customer = data.data;
        nickName.value = customer.nickName;
        account.value = customer.account;
        age.value = customer.age;
        if (customer.sex==='0'){
          sex.value = "女";
        }else if(customer.sex==='1') {
          sex.value = "男";
        }else {
          sex.value = "保密";
        }
        if (customer.headPortraitFile == null){
          headPortraitFile.value = require('@/assets/兔子来了.jpeg');
          sessionStorage.setItem('headPortraitFile',require('@/assets/兔子来了.jpeg'));
        }else {
          headPortraitFile.value = "data:image/jpeg;base64," + customer.headPortraitFile;
          sessionStorage.setItem('headPortraitFile',"data:image/jpeg;base64," + customer.headPortraitFile);
        }
        createTime.value = customer.createTime;
        birthday.value = customer.birthday;
        sessionStorage.setItem('id', customer.id);
        loading.value = false;
      }else {
        loading.value = false;
        await popUpWindows(data.msg);
      }
    };
    // 点击用户管理
    const customerManagement = async() => {
      const iframe = document.getElementById('iframe');
      iframe.src = "/CustomerPage";
    };

    // 用户信息的设置
    const setting = async() => {
      personalInformationShow.value = false;
      settingShow.value = true;
    };

    // 退出登录提示
    const logOut = async() => {
      personalInformationShow.value = false;
      logOutShow.value = true;
    }

    // 退出登录确定按钮
    const logOutSubmit = async() => {
      // 清除所有的 sessionStorage
      sessionStorage.clear();
      await router.replace('/LoginPage');
    }


    // 取消用户信息设置的页面
    const cancelShow = async() => {
      headPortraitFile.value = sessionStorage.getItem("headPortraitFile");
      updPersonalInformationShow.value = false;
      updPersonalHeadPortraitShow.value = false;
      settingShow.value = false;
      safetyProblemVerifyShow.value = false;
      updPasswordForm.value = false;
      updSafetyProblemVerifyShow.value = false;
      logOutShow.value = false;
      await emptyForm();
    };

    // 修改用户信息
    const updPersonalInformation = async() => {
      updBirthday.value = birthday.value;
      updAccount.value = account.value;
      if (sex.value === "女"){
        updSex.value = "0";
      }else if(sex.value=== "男") {
        updSex.value = "1";
      }else {
        updSex.value = "2";
      }
      updNickName.value = nickName.value;
      settingShow.value = false;
      updPersonalInformationShow.value = true;
    };

    // 修改用户密码
    const updPassword = async() => {
      isShow.value = "3";
      await loadProblems();
      settingShow.value = false;
      safetyProblemVerifyShow.value = true;
    };

    // 修改用户安全问题
    const updSafetyProblem = async() => {
      isShow.value = "4";
      await loadProblems();
      settingShow.value = false;
      safetyProblemVerifyShow.value = true;
    };

    // 更换头像
    const updPersonalHeadPortrait = async() => {
      updHeadPortraitFile.value = false;
      settingShow.value = false;
      updPersonalHeadPortraitShow.value = true;
    };

    // 选择头像的方法
    // 一个常见的问题,浏览器在选择相同文件时不会触发 change 事件。为了处理这种情况,可以在文件选择之前手动重置输入框的值。这可以通过将输入框的 value 设置为一个空字符串来实现。
    const selectHeadPortrait = (event) => {
      const files = event.target.files;
      if (files.length === 0) {  //未选择任何文件
        return;
      }
      const validExtensions = ['jpg', 'jpeg', 'png', 'gif'];
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const fileExtension = file.name.split('.').pop().toLowerCase();
        if (validExtensions.includes(fileExtension)) {
          updHeadPortraitFile.value = true;
          selectedFile.value = file;
          headPortraitFile.value = URL.createObjectURL(file);
          selectedFileName.value = file.name; // 保存文件名
        } else {
          popUpWindows(`${file.name} 不是有效的图片文件`);
        }
      }
      // 重置 input 的值
      event.target.value = '';
    };

    // 上传头像(替换头像)
    const MAX_SIZE = 40 * 1024 * 1024; // 40 MB
    const uploadHeadPortrait = async () => {
      const formData = new FormData();
      if (selectedFile.value.size > MAX_SIZE) {
        await popUpWindows(`${selectedFile.value.name} 文件大小超过限制`);
      }else {
        loading.value = true;
        const id = sessionStorage.getItem("id");
        formData.append("id", id);
        formData.append('image', selectedFile.value);
        try {
          const response = await uploadHeadPortraitJS(formData);
          console.log(response);
          if (response.statusCodeValue === 200){
            updPersonalHeadPortraitShow.value = false;
            // 更新保存用户头像文件的 session
            sessionStorage.setItem("headPortraitFile","data:image/jpeg;base64," + response.body) ;
            loading.value = false;
            await popUpWindows("替换成功");
          }
        } catch (error) {
          loading.value = false;
          console.error('上传头像失败:', error);
        }
      }
    };

    // 修改个人信息
    const updInformation = async () => {
      if (updNickName.value === ''){
        await popUpWindows("昵称不能为空");
      }else if (isValidInput(updNickName.value) === false) {
        await popUpWindows("昵称请使用中文英文和数字!");
      }else if (charsLengthUtil(updNickName.value) > 7) {
        await popUpWindows("昵称不得超过7个字符长度!");
      }else if (getAgeByBirthday(updBirthday.value) < 18 &&getAgeByBirthday(updBirthday.value) != null) {
        await popUpWindows("年龄必须大于18岁!");
      }else {
        loading.value = true;
        const information ={
          account: "",
          nickName: "",
          sex: "",
          birthday: "",
          age: ""
        };
        information.account = updAccount.value;
        information.nickName = updNickName.value;
        information.sex = updSex.value;
        if (updBirthday.value === birthday.value){
          information.birthday = null;
          information.age = null;
        }else{
          information.birthday= dateFormatConversion(updBirthday.value);
          information.age = getAgeByBirthday(updBirthday.value);
        }
        const data = await updInformationJS(information);
        if (data.code == 200){
          loading.value = false;
          nickName.value = updNickName.value;
          if (updSex.value === "0") {
            sex.value = "女";
          } else if (updSex.value === "1") {
            sex.value = "男";
          } else if (updSex.value === "2") {
            sex.value = "保密";
          }
          birthday.value = dateFormatConversion(updBirthday.value);
          age.value = getAgeByBirthday(updBirthday.value);
          updPersonalInformationShow.value = false;
          await popUpWindows(data.msg);
        }else {
          loading.value = false;
          await popUpWindows(data.msg);
        }
      }
    }

    // 加载安全问题
    let questions = ref([]);
    const loadProblems = async () => {
      try {
        const response = await selSafetyProblemsJS();
        const dd  = response.data.data;
        questions.value = dd;
      } catch (error) {
        console.error('获取问题失败:', error);
      }
    };

    // 选择安全问题后将安全问题及id渲染到标签里
    const customerSafetyPlus = (command) => {
      const [order,problem, id] = command.split('/');
      if (order === '1'){
        safetyProblem1Id.value = id;
        safetyProblem1.value = problem;
      }else if (order === '2'){
        safetyProblem2Id.value = id;
        safetyProblem2.value = problem;
      }else if (order === '3'){
        safetyProblem3Id.value = id;
        safetyProblem3.value = problem;
      }
    }

    // 安全问题校验
    const verify = async () => {
      if (safetyProblem1.value == '请选择安全问题1' || safetyProblem2.value == '请选择安全问题2' || safetyProblem3.value == '请选择安全问题3') {
        await popUpWindows("请选择3个安全问题!");
      } else if (safetyProblem1Answer.value.length == 0) {
        await popUpWindows("请回答第一个问题!");
      } else if (safetyProblem2Answer.value.length == 0) {
        await popUpWindows("请回答第二个问题!");
      } else if (safetyProblem3Answer.value.length == 0) {
        await popUpWindows("请回答第三个问题!");
      } else {
        loading.value = true;
        const verifyForm ={
          account: "",
          safetyProblem1Id: "",
          safetyProblem2Id: "",
          safetyProblem3Id: "",
          safetyProblem1Answer: "",
          safetyProblem2Answer: "",
          safetyProblem3Answer: "",
          password:"",
        };
        verifyForm.account= account.value;
        verifyForm.safetyProblem1Id= safetyProblem1Id.value;
        verifyForm.safetyProblem2Id= safetyProblem2Id.value;
        verifyForm.safetyProblem3Id= safetyProblem3Id.value;
        verifyForm.safetyProblem1Answer= safetyProblem1Answer.value;
        verifyForm.safetyProblem2Answer= safetyProblem2Answer.value;
        verifyForm.safetyProblem3Answer= safetyProblem3Answer.value;
        const data = await retrievePasswordJs(verifyForm);
        if (data.code == 200){
          safetyProblemVerifyShow.value = false;
          loading.value = false;
          await emptyForm();
          await popUpWindows(data.msg);
          if (isShow.value === "3"){
            updPasswordForm.value = true;
          }else if (isShow.value === "4"){
            updSafetyProblemVerifyShow.value = true;
          }
        }else {
          loading.value = false;
          await popUpWindows(data.msg);
        }
      }
    }

    // 清空表单数据
    const emptyForm = async () => {
      safetyProblem1.value = "请选择安全问题1";
      safetyProblem2.value = "请选择安全问题2";
      safetyProblem3.value = "请选择安全问题3";
      safetyProblem1Id.value = "";
      safetyProblem2Id.value = "";
      safetyProblem3Id.value = "";
      safetyProblem1Answer.value = "";
      safetyProblem2Answer.value = "";
      safetyProblem3Answer.value = "";
    }

    // 修改密码
    const updPasswordSubmit = async () => {
      if (newPassword1.value != newPassword2.value){
        await popUpWindows("两次输入的密码不一致!");
      }else if (newPassword1.value.length < 6 || newPassword1.value.length > 20){
        await popUpWindows("密码的长度应该在6到20之间!");
      }else {
        loading.value = true;
        const accountAndPassword ={
          account: "",
          password:"",
        };
        accountAndPassword.account = account.value;
        accountAndPassword.password = newPassword1.value;
        const data = await updPasswordJS(accountAndPassword);
        newPassword1.value = "";
        newPassword2.value = "";
        updPasswordForm.value = false;
        loading.value = false;
        if (data.code == 200){
          await popUpWindows(data.msg);
        }else {
          await popUpWindows(data.msg);
        }
      }
    };

    // 修改安全问题
    const updSafetyProblemSubmit = async () => {
      loading.value = true;
      const newSafetyProblem ={
        account: "",
        safetyProblem1Id: "",
        safetyProblem2Id: "",
        safetyProblem3Id: "",
        safetyProblem1Answer: "",
        safetyProblem2Answer: "",
        safetyProblem3Answer: "",
      };
      newSafetyProblem.account = account.value;
      newSafetyProblem.safetyProblem1Id = safetyProblem1Id.value;
      newSafetyProblem.safetyProblem2Id = safetyProblem2Id.value;
      newSafetyProblem.safetyProblem3Id = safetyProblem3Id.value;
      newSafetyProblem.safetyProblem1Answer = safetyProblem1Answer.value;
      newSafetyProblem.safetyProblem2Answer = safetyProblem2Answer.value;
      newSafetyProblem.safetyProblem3Answer = safetyProblem3Answer.value;
      const data = await updSafetyProblemJS(newSafetyProblem);
      if (data.code == 200){
        updSafetyProblemVerifyShow.value = false;
        await emptyForm();
        await popUpWindows(data.msg);
      }else {
        await popUpWindows(data.msg);
      }
      loading.value = false;
    }

    return {
      loading,
      account,
      updAccount,
      nickName,
      updNickName,
      age,
      birthday,
      updBirthday,
      sex,
      updSex,
      createTime,
      customerManagement,
      personalInformationShow,
      settingShow,
      setting,
      updPersonalInformationShow,
      updPersonalHeadPortraitShow,
      updPersonalInformation,
      updPersonalHeadPortrait,
      cancelShow,
      selectHeadPortrait,
      selectedFile,
      uploadHeadPortrait,
      headPortraitFile,
      selectedFileName,
      updHeadPortraitFile,
      updInformation,
      questions,
      updPassword,
      safetyProblemVerifyShow,
      safetyProblem1,
      safetyProblem2,
      safetyProblem3,
      safetyProblem1Id,
      safetyProblem2Id,
      safetyProblem3Id,
      safetyProblem1Answer,
      safetyProblem2Answer,
      safetyProblem3Answer,
      customerSafetyPlus,
      verify,
      updPasswordForm,
      newPassword1,
      newPassword2,
      updPasswordSubmit,
      updSafetyProblem,
      updSafetyProblemVerifyShow,
      updSafetyProblemSubmit,
      logOut,
      logOutShow,
      logOutSubmit,
    };
  }
}
</script>

<style scoped>
/* 这里可以添加其他样式 */
</style>

5、DataScreen.vue

     首页面展示。

<template>
  大屏数据展示
</template>

<script>
export default {
  name: "DataScreen"
}
</script>

<style scoped>

</style>

6、CustomerPage.vue

     用户管理页面

<template>
  <div>
    <el-form :inline="true" style="text-align: left;margin-left: 10px;position: relative; top: 10px;"> <!--:inline="true" :把表格里的元素都放在同一排-->
      <el-form-item prop="condition" label="搜索">
        <el-input v-model="searchValue" placeholder="请输入搜索条件"/>
      </el-form-item>
      <el-form-item label="角色">
        <el-select v-model="selectedRole" placeholder="请选择" :style="{width: '120px'}"> <!-- 修改v-model绑定项 selectedRole: null,  // 用于存储选中的角色  -->
          <el-option
              v-for="role in selectRole"
              :key="role.key"
              :label="role.name"
              :value="role.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="性别">
        <el-select v-model="selectedSex" placeholder="请选择" :style="{width: '90px'}"> <!-- 修改v-model绑定项  -->
          <el-option
              v-for="sex in selectSex"
              :key="sex.key"
              :label="sex.name"
              :value="sex.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="search()">搜索</el-button>
        <el-button @click="reset()">重置</el-button>
      </el-form-item>
    </el-form>
    <div>
      <el-row>
        <el-col :span="1.5">
          <el-button @click="exportExcel()" type="success" plain style="width: 100px;margin-left: 10px">导出所有用户</el-button>
        </el-col>
      </el-row>
    </div>
    <!-- 用户列表表格 -->
    <el-table v-loading="loading" :data="customerList" style="width: calc(100% - 20px);height: 510px;left: 10px;right: 10px;top: 10px;border: 1px solid gray;" :style="{ overflow: 'auto' }" scrollbar-always-on >
      <el-table-column fixed="left" type="index" label="序号" align="center" width="60px"/>
      <el-table-column label="id" align="center" width="100px" prop="id" v-if="false"/>
      <!--  使用 v-if 确保 headPortraitFile 存在,避免在没有头像时出现空白的 img 标签-->
      <el-table-column width="100" label="头像" align="center">
        <template #default="scope">
          <img v-if="scope.row.headPortraitFile"
               :src="scope.row.headPortraitFile"
               alt="头像"
               style="width: 30px; height: 30px; border-radius: 50%; vertical-align: middle;" />
        </template>
      </el-table-column>


      <el-table-column label="账号" align="center" width="100px" prop="account"/>
      <el-table-column label="角色" align="center" width="100px" prop="roleName"/>
      <el-table-column label="昵称" align="center" width="165px" prop="nickName"/>
      <el-table-column label="出生日期" align="center" width="100px" prop="birthday"/>
      <el-table-column label="年龄" align="center" width="60px" prop="age"/>
      <el-table-column label="性别" align="center" width="60px" prop="sex"/>
      <el-table-column label="创建时间" align="center" width="160px" prop="createTime"/>
      <el-table-column label="更新时间" align="center" width="160px" prop="updateTime"/>
      <el-table-column label="更新时间" align="center" width="160px" prop="updateTime"/>
      <el-table-column label="更新时间" align="center" width="160px" prop="updateTime"/>
      <el-table-column label="更新时间" align="center" width="160px" prop="updateTime"/>
      <el-table-column label="操作" fixed="right" min-width="140px" align="center">
        <template #default="obj">
          <el-button size="small" @click="details(obj.row)">
            详情
          </el-button>
          <el-button
              size="small"
              type="danger"
              @click="delCustomer(obj.row)"
          >
            删除
          </el-button><!--@click="delCustomer(obj.$index, obj.row)" obj.$index:序号;obj.row:这一行的内容-->
        </template>
      </el-table-column>
    </el-table>

<!--   控制页数和分页 -->
    <div class="demo-pagination-block" style="margin-top: 20px;display: flex; justify-content: center;">
      <el-pagination
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :page-sizes="[10, 50, 100, 200]"
          layout="total, sizes, prev, pager, next, jumper"
          :total="customersCount"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      />
    </div>
  </div>

<!-- 详情弹窗 -->
  <el-dialog :close-on-click-modal="false" :title="detailTile" v-model="detailsShow" width="622px" style="margin-top: 50px" append-to-body><!--要支持动态绑定就在前面加个 : ,如::title="detailTile"-->
    <div style="display: flex; justify-content: center; align-items: center;">
      <img v-if="headPortraitFile"
           :src="headPortraitFile"
           alt="头像"
           @click="viewImage(headPortraitFile)"
           style="width: 100px; height: 100px; border-radius: 50%; cursor: pointer" />
    </div>
    <div style="margin-top: 25px">
      账号:<el-input v-model="account" style="width: 240px;margin-right: 20px" disabled/>
      角色:<el-input v-model="roleName" style="width: 240px;" disabled/>
    </div>
    <div style="margin-top: 10px">
      账号:<el-input v-model="nickName" style="width: 240px;margin-right: 20px" disabled/>
      生日:<el-input v-model="birthday" style="width: 240px;" disabled/>
    </div>
    <div style="margin-top: 10px">
      年龄:<el-input v-model="age" style="width: 240px;margin-right: 20px" disabled/>
      性别:<el-input v-model="sex" style="width: 240px;" disabled/>
    </div>
    <div style="margin-top: 10px">
      创建时间:<el-input v-model="createTime" style="width: 212px;margin-right: 20px" disabled/>
      更新时间:<el-input v-model="updateTime" style="width: 212px;" disabled/>
    </div>
    <div style="margin-top: 15px; display: flex; justify-content: flex-end;margin-right: 4px">
      <el-button @click="detailsShow = false" type="primary" plain>取消</el-button>
    </div>
  </el-dialog>

  <!-- 下面是放大容器和图片(点击头像用的) -->
  <div v-if="isLightboxVisible"
       style="position: fixed; top: 0; left: 0; right: 0; bottom: 0;
                background: rgba(0, 0, 0, 0.8); display: flex;
                justify-content: center; align-items: center; z-index: 1000;"
       @click="closeImage">
    <img
        :src="imageShow"
        style="max-width: 70%; max-height: 70%; border-radius: 10px;margin-top: -80px"
    />
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';
import { selCustomersJs, selCustomersCountJS, delCustomerById } from '@/module/rabbit-system/api/customer';
import { selRolesJS } from '@/module/rabbit-system/api/role';
import { processCustomerData } from '@/module/uitls';
import {ElMessageBox} from "element-plus";
import * as XLSX from 'xlsx';


export default {
  name: 'CustomerPage',
  setup() {
    const isLightboxVisible = ref(false);
    const imageShow = ref('');

    // 点击头像变大,展示头像的方法
    const viewImage = (image) => {
      detailsShow.value = false;
      imageShow.value = image;
      isLightboxVisible.value = true;
    };
    // 点其他地方头像变小
    const closeImage = () => {
      detailsShow.value = true;
      isLightboxVisible.value = false;
    };

    // 返回分页所需数据的表单
    const pagingForm ={
      currentPage: "",
      pageSize: "",
      searchValue:"",
      sex:"",
      role:"",
    };

    const loading = ref(true); //是否进入加载的控制
    const customerList = ref([]);
    const currentPage = ref(1);      // 当前页码
    let pageSize = ref(10); // 每页显示的条目数
    let customersCount = ref(0);  // 总条目数
    let headPortraitFile = ref("");
    let nickName = ref("");
    let detailsShow = ref(false);
    let selectedSex = ref("");
    let selectedRole = ref("");
    let selectRole = ref([]);
    let searchValue = ref("");  //自定义搜索条件
    //详情里的信息
    let detailTile = ref("");
    let account = ref("");
    let roleName = ref("");
    let birthday = ref("");
    let age = ref("");
    let sex = ref("");
    let createTime = ref("");
    let updateTime = ref("");
    // 页面加载时调用方法
    onMounted(() => {
      selCustomersVue();
      selRolesVue();
    });

    // 加载角色筛选条件信息
    const selRolesVue = async() => {
      const data = await selRolesJS();
      // 转换为目标格式
      selectRole.value = data.data.map((role, index) => ({
        key: index,        // 使用当前索引作为 key
        name: role.roleName, // 使用 roleName 作为 name
        value: role.id     // 使用 id 作为 value
      }));
      // 添加额外的选项
      selectRole.value.push({ key: selectRole.value.length, name: '',value: ''});
      // console.log(selectRole.value);
    }

    // 搜索按钮
    const search = async() => {
      loading.value = true;
      pagingForm.currentPage = currentPage.value;
      pagingForm.pageSize = pageSize.value;
      pagingForm.sex = selectedSex.value;
      pagingForm.role = selectedRole.value;
      pagingForm.searchValue = searchValue.value;
      const response = await selCustomersJs(pagingForm);
      // 批量处理数据
      const customers = processCustomerData(response.data.data);
      // 遍历所有的元素,如果没有换过头像的,就默认一个头像,换过的就用换的头像
      customers.forEach(customer => {
        if (customer.headPortraitFile == null) {
          customer.headPortraitFile = require('@/assets/兔子来了.jpeg');
        } else {
          // 需要将 headPortrait 的二进制值格式化为一个完整的 Base64 图片 URL。
          // 假设你的数据中的 headPortraitFile 字段存储的是一个 Base64 编码的图片数据
          customer.headPortraitFile = "data:image/jpeg;base64," + customer.headPortraitFile;
        }
      });
      customerList.value = customers;
      customersCount.value = await selCustomersCount(pagingForm);
      loading.value = false;
    }

    // 重置按钮
    const reset = async() => {
      loading.value = true;
      pagingForm.currentPage = currentPage.value;
      pagingForm.pageSize = pageSize.value;
      pagingForm.sex = "";
      pagingForm.role = "";
      pagingForm.searchValue = "";
      selectedSex.value = "";
      selectedRole.value = "";
      searchValue.value = "";
      const response = await selCustomersJs(pagingForm);
      // 批量处理数据
      const customers = processCustomerData(response.data.data);
      // 遍历所有的元素,如果没有换过头像的,就默认一个头像,换过的就用换的头像
      customers.forEach(customer => {
        if (customer.headPortraitFile == null) {
          customer.headPortraitFile = require('@/assets/兔子来了.jpeg');
        } else {
          // 需要将 headPortrait 的二进制值格式化为一个完整的 Base64 图片 URL。
          // 假设你的数据中的 headPortraitFile 字段存储的是一个 Base64 编码的图片数据
          customer.headPortraitFile = "data:image/jpeg;base64," + customer.headPortraitFile;
        }
      });
      customerList.value = customers;
      customersCount.value = await selCustomersCount(pagingForm);
      loading.value = false;
    }

    // 导出所有用户的 excel 按钮操作
    const exportExcel = async() => {
      // 清空查询所有用户的条件(包括分页,即所有数据)
      pagingForm.currentPage = "";
      pagingForm.pageSize = "";
      pagingForm.searchValue = "";
      pagingForm.sex = "";
      pagingForm.role = "";
      const allCustomerList = await selCustomersJs(pagingForm);
      // 处理一下数据
      const allCustomerListPlus = processCustomerData(allCustomerList.data.data);
      const exportCustomers = allCustomerListPlus.map((customer, index) => ({
        序号: index + 1,  // 生成从1开始的序号
        账号: customer.account,
        角色: customer.roleName,
        昵称: customer.nickName,
        出生日期: customer.birthday,
        年龄: customer.age,
        性别: customer.sex,
        创建时间: customer.createTime,
        更新时间: customer.updateTime
      }));
      // 将数据转换为工作表
      const ws = XLSX.utils.json_to_sheet(exportCustomers);
      // 设置列的宽度
      ws['!cols'] = [
        { wch: 5 }, // 序号宽度
        { wch: 10 }, // 账号宽度
        { wch: 11 }, // 角色宽度
        { wch: 20 }, // 昵称宽度
        { wch: 11 }, // 出生日期宽度
        { wch: 5 }, // 年龄宽度
        { wch: 5 }, // 性别宽度
        { wch: 21 }, // 创建时间宽度
        { wch: 21 }, // 更新时间宽度
      ];
      // 创建一个新的工作簿
      const wb = XLSX.utils.book_new();
      XLSX.utils.book_append_sheet(wb, ws, '数据');
      // 获取当前时间
      // 获取当前年份、月份和日期
      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始
      const day = String(now.getDate()).padStart(2, '0');
      const formattedDate = `${year}${month}${day}`; // 输出格式:YYYYMMDD
      // 获取当前时间(小时、分钟、秒)
      const hours = String(now.getHours()).padStart(2, '0');
      const minutes = String(now.getMinutes()).padStart(2, '0');
      const seconds = String(now.getSeconds()).padStart(2, '0');
      const formattedTime = `${hours}${minutes}${seconds}`;  // 输出格式:HHmmss
      // 最终样式
      const formattedDateTime = `${formattedDate}${formattedTime}`; // 输出格式:YYYYMMDDHHmmss
      // 导出文件
      XLSX.writeFile(wb, "用户信息导出"+formattedDateTime+".xlsx");
    }



    // 计算所有用户的数量
    const selCustomersCount = async (pagingForm) => {
      const customersCount = await selCustomersCountJS(pagingForm);
      return customersCount;
    }

    // 获取用户数据
    const selCustomersVue = async () => {
      loading.value = true;
      try {
        pagingForm.currentPage = currentPage.value;
        pagingForm.pageSize = pageSize.value;
        const response = await selCustomersJs(pagingForm);
        // 批量处理数据
        const customers = processCustomerData(response.data.data);
        // 遍历所有的元素,如果没有换过头像的,就默认一个头像,换过的就用换的头像
        customers.forEach(customer => {
          if (customer.headPortraitFile == null) {
            customer.headPortraitFile = require('@/assets/兔子来了.jpeg');
          } else {
            // 需要将 headPortrait 的二进制值格式化为一个完整的 Base64 图片 URL。
            // 假设你的数据中的 headPortraitFile 字段存储的是一个 Base64 编码的图片数据
            customer.headPortraitFile = "data:image/jpeg;base64," + customer.headPortraitFile;
          }
        });
        customerList.value = customers;
        customersCount.value = await selCustomersCount(pagingForm);
      } catch (error) {
        console.error('Error fetching customers:', error);
      } finally {
        loading.value = false;
      }
    };

    // 每页显示的用户数量
    const handleSizeChange = async (newSize) => {
      pageSize.value = newSize;       // 更新每页条目数
      currentPage.value = 1;           // 重置为第一页
      await selCustomersVue();                     // 重新获取数据
    }

    // 前往多少页
    const handleCurrentChange = async (newPage) => {
      currentPage.value = newPage;     // 更新当前页码
      await selCustomersVue();                     // 重新获取数据
    }

    // 详情
    const details = async (row) => {
      detailTile.value = "用户:"+row.nickName;
      account.value = row.account;
      nickName.value = row.nickName;
      roleName.value = row.roleName;
      birthday.value = row.birthday;
      age.value = row.age;
      sex.value = row.sex;
      headPortraitFile.value = row.headPortraitFile;
      createTime.value = row.createTime;
      updateTime.value = row.updateTime;
      detailsShow.value = true; // 直接赋值为布尔值     // 更新当前页码
    }

    // 删除用户
    const delCustomer = async (row) => {
      // 弹窗
      ElMessageBox.confirm(
          "确认删除账号"+row.account+"吗?",           // 消息内容
          '温馨提示',    // 标题
          {
            confirmButtonText: '确认', // 确认按钮文本
            cancelButtonText: '取消',  // 取消按钮文本
            type: 'warning',           // 提示类型,可以是 'warning'、'success'、'info'、'error'
            closeOnClickModal: false, // 点其他地方不关闭弹窗
            customStyle:{
              top: '-150px'
            }
          }
      )
          .then(async () => {
            // 用户点击确认
            loading.value = true;
            const id = await row.id;
            const data = await delCustomerById(id);
            if (data.code === 200) {
              await selCustomersVue();
              loading.value = false;
              await ElMessageBox.alert("删除成功", '温馨提示', {
                confirmButtonText: '确认',
                customStyle: {
                  top: '-150px'
                }
              })
            } else {
              loading.value = false;
              await ElMessageBox.alert("删除失败", '温馨提示', {
                confirmButtonText: '确认',
                customStyle: {
                  top: '-150px'
                }
              })
            }
          })
          .catch(() => {
            // 用户点击取消
          });
    }


    return {
      loading,
      customerList,
      pageSize,
      customersCount,
      currentPage,
      handleSizeChange,
      handleCurrentChange,
      headPortraitFile,
      delCustomer,
      nickName,
      details,
      account,
      detailsShow,
      detailTile,
      roleName,
      birthday,
      age,
      sex,
      createTime,
      updateTime,
      selectedSex,
      selectSex: [             // 性别列表
        { key: 0,name: '女' ,value: '0'},
        { key: 1,name: '男' ,value: '1'},
        { key: 2,name: '保密' ,value: '2'},
        { key: 3,name: '' ,value: ''},
      ],
      selectedRole,
      selectRole,
      searchValue,
      reset,
      exportExcel,
      search,
      isLightboxVisible,
      imageShow,
      viewImage,
      closeImage
    };
  },
};
</script>

<style scoped>
/* 添加样式 */
</style>

3、跟后端交互的 js

1、registerAndLogin.js

     登陆注册相关的js。

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
        baseURL: 'http://localhost:8081', // 设置请求的基础 URL
        timeout: 10000, // 请求超时时间(毫秒)
});
// 使用 Axios 发送请求后,你会得到一个 Promise 对象。这个对象的 .then 方法可以用来处理请求的结果。当你收到响应数据时,它会被封装在 response 对象中。你可以从这个对象中提取你需要的数据。
// 定义一个异步函数,用于注册
export async function registerJS(formData) {
        try {
                // 发起 POST 请求,进行注册
                const response = await instance.post('/LoginAndRegisterController/register', formData);

                // 提取并返回响应中的数据
                if (response.status === 200) {
                        return response.data; // 返回 '123456' 或其他响应数据
                } else {
                        throw new Error(`Unexpected status code: ${response.status}`);
                }
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}
// 登录
export async function loginJS(formData) {
        try {
                // 发起 POST 请求,进行注册
                const response = await instance.post('/LoginAndRegisterController/login', formData);

                // 提取并返回响应中的数据
                if (response.status === 200) {
                        return response.data; // 返回 '123456' 或其他响应数据
                } else {
                        throw new Error(`Unexpected status code: ${response.status}`);
                }
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}

// 查询安全问题
export async function selSafetyProblemsJS() {
        try {
                const response = await instance.get('/SafetyProblemController/selSafetyProblems', {
                        headers: {
                                'Content-Type': 'application/json', // 设置请求头的内容类型为 JSON
                        }
                });
                return response; // // 假设返回的数据格式是 [{ command: 'problem1', label: '问题1、你的母亲姓什么?' }, ...]
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}

2、retrievePassword.js

     忘记密码的js。

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});
// 定义一个异步函数,校验输入的账号和安全问题是否正确
export async function retrievePasswordJs(formData) {
    try {
        // 发起 POST 请求,进行注册
        const response = await instance.post('/RetrievePasswordController/retrievePassword', formData);

        // 提取并返回响应中的数据
        if (response.status === 200) {
            return response.data; // 返回 '123456' 或其他响应数据
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        // 捕获并处理请求错误
        console.error('Error fetching register:', error.message); // 打印错误信息到控制台
        throw error; // 重新抛出错误,以便调用者可以进一步处理
    }
}

//修改密码
export async function updPasswordJS(formData) {
    try {
        // 发起 POST 请求,进行注册
        const response = await instance.post('/RetrievePasswordController/updPassword', formData);

        // 提取并返回响应中的数据
        if (response.status === 200) {
            return response.data; // 返回 '123456' 或其他响应数据
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        // 捕获并处理请求错误
        console.error('Error fetching register:', error.message); // 打印错误信息到控制台
        throw error; // 重新抛出错误,以便调用者可以进一步处理
    }
}

3、home.js

     主页面的 js。

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});

export async function loadCustomerInformationJS(account) {
    try {
        const response = await instance.get(`/HomePageController/loadCustomerInformation/${account}`);
        if (response.status === 200) {
            return response.data;
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        console.error('Error fetching customer information:', error.message);
        throw error;
    }
}

4、customer.js

     用户相关的 js。

import axios from 'axios'; // 导入 axios 库,用于发起 HTTP 请求

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});

// 定义一个异步函数,用于获取客户数据
export async function selCustomersJs(form) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.post('/CustomerController/selCustomers',form);
        // 返回响应的数据
        return response;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}

// 定义一个异步函数,用于获取客户的总数量
export async function selCustomersCountJS(form) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.post(`/CustomerController/selCustomersCount`,form);
        // 返回响应的数据
        return response.data.data;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}

// 根据id删除用户
export async function delCustomerById(id) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.get(`/CustomerController/delCustomerById/${id}`);
        // 返回响应的数据
        return response.data;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}

// 定义一个异步函数,用于上传头像
export async function uploadHeadPortraitJS(formData) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.post(`/CustomerController/uploadHeadPortrait`,formData);
        // 返回响应的数据
        return response.data.data;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}

// 定义一个异步函数,修改个人信息
export async function updInformationJS(formData) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.post(`/CustomerController/updInformation`,formData);
        // 返回响应的数据
        return response.data;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}

// 定义一个异步函数,修改个人安全问题
export async function updSafetyProblemJS(formData) {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.post(`/CustomerController/updSafetyProblem`,formData);
        // 返回响应的数据
        return response.data;
    } catch (error) {
        console.error('Error fetching customers:', error);
        throw error;
    }
}


5、role.js

     角色相关的 js。

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});

export async function selRolesJS() {
    try {
        const response = await instance.get(`/RoleController/selRoles`);
        if (response.status === 200) {
            return response.data;
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        console.error('Error fetching customer information:', error.message);
        throw error;
    }
}

     这些大致就是全部的代码了,有疑问的话可以在评论区讨论,如果写法上有不妥或者更好的写法也可以告知,感谢。

结束...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值