Components

Login Page

login.vue

<template>
  <div ref="login" class="login-body">
    <div class="login-content">
      <img class="login-logo" src="https://s1.ax1x.com/2022/06/29/jnLR2V.png" alt="" />
      <div class="login-input">
        <div class="login-title">欢迎登录</div>
        <el-form
          ref="formRef"
          :model="loginForm"
          :disabled="store.state.isLoading"
          class="login-form"
        >
          <el-form-item
            prop="phone"
            :rules="[
              {
                required: true,
                message: '请输入账号',
                trigger: 'blur'
              }
            ]"
          >
            <el-input v-model="loginForm.phone" maxlength="11" placeholder="请输入账号">
              <template #prefix>
                <img class="input-icon" src="https://s1.ax1x.com/2022/06/29/jnL280.png" alt="" />
              </template>
            </el-input>
          </el-form-item>
          <el-form-item
            prop="password"
            :rules="[
              {
                required: true,
                message: '请输入密码',
                trigger: 'blur'
              }
            ]"
          >
            <el-input v-model="loginForm.password" type="password" placeholder="请输入密码">
              <template #prefix>
                <img class="input-icon" src="https://s1.ax1x.com/2022/06/29/jnLgCq.png" alt="" />
              </template>
            </el-input>
          </el-form-item>
          <div class="codeFlex">
            <el-form-item
              prop="code"
              :rules="[
                {
                  required: true,
                  message: '请输入验证码',
                  trigger: 'blur'
                },
                { validator: checkCode, trigger: 'blur' }
              ]"
            >
              <el-input
                v-model="loginForm.code"
                maxlength="4"
                placeholder="请输入验证码"
                @keyup.enter="submitForm(formRef)"
              >
                <template #prefix>
                  <img class="input-icon" src="https://s1.ax1x.com/2022/06/29/jnL65n.png" alt="" />
                </template>
              </el-input>
            </el-form-item>
            <div class="code_box" @click="changeCode">
              <canvas id="modifyCanvas" ref="modifyCanvas" width="106" height="43"></canvas>
            </div>
          </div>
          <el-form-item>
            <el-button type="success" :loading="store.state.isLoading" @click="submitForm(formRef)"
              >登录
            </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import type { ElForm } from "element-plus";
import { useStore } from "vuex";
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const verificationCode = ref<string[]>([]);

const loginForm = reactive<{
  phone: string;
  password: string;
  code: string;
}>({
  phone: "",
  password: "",
  code: ""
});
const store: any = useStore();
//刷新验证码
const changeCode = async () => {
  draw(verificationCode.value);
  localStorage.setItem("time", JSON.stringify(new Date().getTime()));
};
const checkCode = (rule: any, value: any, callback: any) => {
  const oldtime: string | null = localStorage.getItem("time");
  if (new Date().getTime() > Number(oldtime) + 5 * 60 * 1000) {
    callback(new Error("验证码已过期!请重新获取"));
    return false;
  }
  if (value.toLowerCase() !== verificationCode.value.join("")) {
    callback(new Error("验证码输入错误!"));
  } else {
    callback();
  }
};
//生成并渲染出验证码图形
const draw = (show_num: string[] = ["4", "r", "9", "s"]) => {
  let canvas = document.getElementById("modifyCanvas") as HTMLCanvasElement; //获取到canvas的对象
  let canvas_width = canvas.width;
  let canvas_height = canvas.height;
  let context = canvas.getContext("2d") as CanvasRenderingContext2D; //获取到canvas画图的环境,演员表演的舞台
  canvas.width = canvas_width;
  canvas.height = canvas_height;
  let sCode =
    "a,b,c,d,e,f,g,h,i,j,k,m,n,p,q,r,s,t,u,v,w,x,y,z,A,B,C,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,W,X,Y,Z,1,2,3,4,5,6,7,8,9,0";
  let aCode = sCode.split(",");
  let aLength = aCode.length; //获取到数组的长度
  for (let i = 0; i < 4; i++) {
    //这里的for循环可以控制验证码位数(如果想显示6位数,4改成6即可)
    let j = Math.floor(Math.random() * aLength); //获取到随机的索引值
    // let deg = Math.random() * 30 * Math.PI / 180;//产生0~30之间的随机弧度
    let deg = Math.random() - 0.5; //产生一个随机弧度
    let txt = aCode[j]; //得到随机的一个内容
    show_num[i] = txt.toLowerCase(); //show_num得到的是verificationCode数组的地址,在这给verificationCode赋值了
    let x = 4 + i * 20; //文字在canvas上的x坐标
    let y = 30 + Math.random() * 8; //文字在canvas上的y坐标
    context.font = "bold 23px 微软雅黑";
    context.translate(x, y);
    context.rotate(deg);
    context.fillStyle = randomColor();
    context.fillText(txt, 0, 0);
    context.rotate(-deg);
    context.translate(-x, -y);
  }
  for (let i = 0; i <= 30; i++) {
    //验证码上显示小点
    context.strokeStyle = randomColor();
    context.beginPath();
    let x = Math.random() * canvas_width;
    let y = Math.random() * canvas_height;
    context.moveTo(x, y);
    context.lineTo(x + 1, y + 1);
    context.stroke();
  }
  for (var i = 0; i <= 5; i++) {
    //验证码上显示线条
    context.strokeStyle = randomColor();
    context.beginPath();
    context.moveTo(Math.random() * canvas_width, Math.random() * canvas_height);
    context.lineTo(Math.random() * canvas_width, Math.random() * canvas_height);
    context.stroke();
  }
};
//得到随机的颜色值
const randomColor = (): string => {
  let r = Math.floor(Math.random() * 256);
  let g = Math.floor(Math.random() * 256);
  let b = Math.floor(Math.random() * 256);
  return "rgb(" + r + "," + g + "," + b + ")";
};
// 登录
const submitForm = (formEl: FormInstance | undefined) => {
  console.log(formEl);

  if (!formEl) return;

  formEl.validate(valid => {
    if (valid) {
      // 调用登录接口
    }
  });
};
onMounted(() => {
  changeCode();
  localStorage.setItem("time", JSON.stringify(new Date().getTime()));
});
</script>
<style lang="less" scoped>
div {
  box-sizing: border-box;
}

.login-body {
  width: 100%;
  height: 100%;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background: url("https://s1.ax1x.com/2022/06/29/jnLWvT.md.png") no-repeat;
  background-size: cover;
  min-width: 800px;
  min-height: 500px;

  .login-content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 1200px;
    height: 640px;
    padding: 119px 100px 96px 100px;
    flex-shrink: 0;
    background-color: #ffffff;
    box-shadow: 0px 3px 14px 0px rgba(0, 42, 75, 0.39);
    border-radius: 18px;

    .login-logo {
      width: 449px;
      height: 425px;
      margin-right: 180px;
    }

    .login-input {
      .login-title {
        width: 364px;
        font-size: 47px;
        font-style: italic;
        font-stretch: normal;
        margin-bottom: 48px;
        letter-spacing: 4px;
        color: #45a0b2;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .login-form {
        width: 364px;

        .el-form-item {
          height: 53px;

          .el-input {
            height: 43px;

            :deep(.el-input__inner) {
              height: 43px;
              border-radius: 2px;
            }

            :deep(.el-input__prefix) {
              display: flex;
              align-items: center;
              justify-content: center;
            }

            .input-icon {
              width: 16px;
              height: 22px;
            }
          }

          .el-button--success {
            width: 364px;
            height: 44px;
            background-color: #45a0b2;
            border-radius: 2px;
            font-size: 16px;
            border: none;
          }
        }

        .codeFlex {
          display: flex;
          width: 364px;
          align-items: center;
          justify-content: space-between;

          :deep(.el-form-item) {
            width: 100%;
            margin-right: 10px;
          }

          .code_box {
            cursor: pointer;
            height: 43px;
            width: 106px;
            border: solid 1px #b2c7df;
            margin-bottom: 20px;
          }
        }
      }
    }
  }
}
</style>
<style>
/* 去除浏览器input自动填充的默认样式 */
input:-internal-autofill-selected {
    /* 这里#fff是底色 */
    -webkit-box-shadow: 0 0 0px 1000px #fff inset;
    -webkit-text-fill-color: #333;
}
</style>