Vue简易登陆页面带验证码

85 阅读4分钟

SringBoot后端配置验证码及接口编写,方便后期页面快速开发。

后端验证码类

public ResponseEntity<Object> getCode() {
    // 获取运算的结果
    Captcha captcha = loginProperties.getCaptcha();
    String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
    //当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
    String captchaValue = captcha.text();
    if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) {
        captchaValue = captchaValue.split("\.")[0];
    }
    // 保存
    redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
    // 验证码信息
    Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
        put("img", captcha.toBase64());
        put("uuid", uuid);
    }};
    return ResponseEntity.ok(imgResult);
}

枚举类

public enum LoginCodeEnum {
    /**
     * 算数
     */
    ARITHMETIC,
    /**
     * 中文
     */
    CHINESE,
    /**
     * 中文闪图
     */
    CHINESE_GIF,
    /**
     * 闪图
     */
    GIF,
    SPEC
}
@Data
public class LoginCode {

    /**
     * 验证码配置
     */
    private LoginCodeEnum codeType;
    /**
     * 验证码有效期 分钟
     */
    private Long expiration = 2L;
    /**
     * 验证码内容长度
     */
    private int length = 2;
    /**
     * 验证码宽度
     */
    private int width = 111;
    /**
     * 验证码高度
     */
    private int height = 36;
    /**
     * 验证码字体
     */
    private String fontName;
    /**
     * 字体大小
     */
    private int fontSize = 25;

    public LoginCodeEnum getCodeType() {
        return codeType;
    }
}
public class LoginProperties {

    /**
     * 账号单用户 登录
     */
    private boolean singleLogin = false;

    private LoginCode loginCode;

    public static final String cacheKey = "USER-LOGIN-DATA";

    public boolean isSingleLogin() {
        return singleLogin;
    }

    /**
     * 获取验证码生产类
     *
     * @return /
     */
    public Captcha getCaptcha() {
        if (Objects.isNull(loginCode)) {
            loginCode = new LoginCode();
            if (Objects.isNull(loginCode.getCodeType())) {
                loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
            }
        }
        return switchCaptcha(loginCode);
    }

    /**
     * 依据配置信息生产验证码
     *
     * @param loginCode 验证码配置信息
     * @return /
     */
    private Captcha switchCaptcha(LoginCode loginCode) {
        Captcha captcha;
        switch (loginCode.getCodeType()) {
            case ARITHMETIC:
                // 算术类型 https://gitee.com/whvse/EasyCaptcha
                captcha = new FixedArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
                // 几位数运算,默认是两位
                captcha.setLen(loginCode.getLength());
                break;
            case CHINESE:
                captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
                captcha.setLen(loginCode.getLength());
                break;
            case CHINESE_GIF:
                captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
                captcha.setLen(loginCode.getLength());
                break;
            case GIF:
                captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
                captcha.setLen(loginCode.getLength());
                break;
            case SPEC:
                captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
                captcha.setLen(loginCode.getLength());
                break;
            default:
                throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
        }
        if(StringUtils.isNotBlank(loginCode.getFontName())){
            captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
        }
        return captcha;
    }

    static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
        public FixedArithmeticCaptcha(int width, int height) {
            super(width, height);
        }

        @Override
        protected char[] alphas() {
            // 生成随机数字和运算符
            int n1 = num(1, 10), n2 = num(1, 10);
            int opt = num(3);

            // 计算结果
            int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
            // 转换为字符运算符
            char optChar = "+-x".charAt(opt);

            this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
            this.chars = String.valueOf(res);

            return chars.toCharArray();
        }
    }
}

简易的前端页面

<template>
  <div class='hr-login'>
    <div class="page-head">
      <img src="../assets/....." alt="">
    </div>
    <div class="login-form">
      <div class="text-logo">简易登陆页面</div>
      <el-input class="phone-inp login-inp" v-model="loginForm.username" placeholder="输入手机号" clearable></el-input>
      <el-input class="login-inp" placeholder="输入密码" v-model="loginForm.password" show-password></el-input>
      <div class="code-inp login-inp">
        <el-input class="code" v-model="loginForm.code" placeholder="输入验证码"></el-input>
        <div class="get-code">
          <img
            :src="codeUrl"
            @click="getCode"
          >
        </div>
      </div>
      <div class="protocol">
        <el-checkbox v-model="agree"></el-checkbox>
        <div class="protocol-text">
          <span @click="agreeProtocol">我已阅读并同意</span><span class="link">法律声明及隐私权政策</span><span></span><span class="link">用户协议</span>
        </div>
      </div>
      <div class="login-btn" @click="handleLogin">立即登录</div>
      <div class="tip">
        <span class="">未注册手机号将自动创建账户</span>
        <span class="link" @click="showCustomerService">联系     ?</span>
      </div>
    </div>
    <el-dialog :visible.sync="showDialog" width="424px">
      <div class="dialog-body">
        <div class="dialog-tip-text">打开微信扫一扫,添加      顾问</div>
        <img src="../assets/     " alt="">
      </div>
    </el-dialog>
  </div>
</template>
<script>
//  调用后端接口获取验证码及图片
import {getCodeImg} from "@/api/login";
import Cookies from "js-cookie";
// 加密类
import {encrypt} from "@/utils/rsaEncrypt";
import qs from "qs";

export default {
  data() {
    return {
      codeUrl: "",
      cookiePass: "",
      loginForm: {
        username: "",
        password: "",
        rememberMe: false,
        code: "",
        uuid: "",
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "用户名不能为空" },
        ],
        password: [
          { required: true, trigger: "blur", message: "密码不能为空" },
        ],
        code: [
          { required: true, trigger: "change", message: "验证码不能为空" },
        ],
      },
      loading: false,
      redirect: undefined,
      agree:false,
    }
  },
  created() {
    // 获取验证码
    this.getCode();
    // 获取用户名密码等Cookie
    this.getCookie();
   
  },
  methods: {
    getCode() {
      getCodeImg().then((res) => {
        this.codeUrl = res.img;
        this.loginForm.uuid = res.uuid;
      });
    },
    getCookie() {
      const username = Cookies.get("username");
      let password = Cookies.get("password");
      const rememberMe = Cookies.get("rememberMe");
      // 保存cookie里面的加密后的密码
      this.cookiePass = password === undefined ? "" : password;
      password = password === undefined ? this.loginForm.password : password;
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password,
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
        code: "",
      };
    },
    handleLogin() {
      const user = {
        username: this.loginForm.username,
        password: this.loginForm.password,
        rememberMe: this.loginForm.rememberMe,
        code: this.loginForm.code,
        uuid: this.loginForm.uuid,
      };
      if (user.password !== this.cookiePass) {
        user.password = encrypt(user.password);
      }
      this.loading = true;
      if (user.rememberMe) {
        Cookies.set("username", user.username, {
          expires: Config.passCookieExpires,
        });
        Cookies.set("password", user.password, {
          expires: Config.passCookieExpires,
        });
        Cookies.set("rememberMe", user.rememberMe, {
          expires: Config.passCookieExpires,
        });
      } else {
        Cookies.remove("username");
        Cookies.remove("password");
        Cookies.remove("rememberMe");
      }
      //登陆成功后逻辑自行修改
      this.$store
        .dispatch("Login", user)
        .then(() => {
          this.loading = false;
          this.$router.push({ path: "/" });
        })
        .catch(() => {
          this.loading = false;
          this.getCode();
        });
    },
    submit() {
      this.$router.push({ path: '/' })
    }
  },
}
</script>
<style lang='scss' scoped>
.hr-login {
  position: relative;
  .page-head {
    box-sizing: border-box;
    height: 80px;
    line-height: 80px;
    padding-left: 80px;
    border-bottom: 0.5px solid #E0E0E0;
    img {
      width: 120px;
      height: 20px;
    }
  }
  .login-form {
    width: 400px;
    margin: 120px auto 0;
    text-align: center;
    .text-logo {
      margin-bottom: 32px;
      color: rgb(0 0 0 / 90%);
      font-size: 32px;
      font-weight: 500;
      font-family: PingFang SC;
    }
    .code-inp {
      position: relative;
      .get-code {
        z-index: 100;
        position: absolute;
        top: 50%;
        right: 0;
        transform: translateY(-50%);
        width: 120px;
        text-align: center;
        color: rgb(0 0 0 / 30%);
        font-size: 14px;
        cursor: pointer;
      }
    }
    .protocol {
      display: flex;
      align-items: center;
      justify-content: center;
      margin-top: 32px;
      cursor: pointer;
      .protocol-text {
        margin-left: 6px;
        color: rgb(0 0 0 / 30%);
        font-size: 12px;
        .link {
          color: rgb(0 0 0 / 70%);
        }
      }
    }
    .login-btn {
      width: 100%;
      height: 50px;
      margin-top: 32px;
      line-height: 50px;
      text-align: center;
      background-color: #E8B957;
      border-radius: 12px;
      color: rgb(0 0 0 / 90%);
      font-size: 16px;
      font-weight: 500;
      cursor: pointer;
      user-select: none;
    }
    .tip {
      margin-top: 96px;
      color: rgb(0 0 0 / 50%);
      font-size: 14px;
      .link {
        color: #E8B957;
        cursor: pointer;
      }
    }
  }
  .dialog-body {
    text-align: center;
    padding-bottom: 30px;
    .dialog-tip-text {
      margin-bottom: 32px;
      color: rgb(0 0 0 / 90%);
      font-size: 16px;
      font-weight: 500;
    }
    img {
      width: 200px;
    }
  }
}
</style>
<style lang='scss'>
.hr-login {
  .login-form {
    .login-inp {
      margin-top: 16px;
      height: 48px;
      .el-input__inner {
        height: 48px;
      }
    }
    .code-inp {
      .login-inp .el-input__inner {
        padding-right: 120px;
      }
    }
    .el-checkbox__input .el-checkbox__inner {
      border-radius: 50%;
    }
    .el-checkbox__input.is-checked .el-checkbox__inner {
      background: #E8B957;
      border-color: #E8B957;
    }
    .el-checkbox__input.is-focus .el-checkbox__inner {
      border-color: #E8B957;
    }
    .el-checkbox__input .el-checkbox__inner:hover {
      border-color: #E8B957;
    }
  }
}
</style>