引言

       在现代Web应用中,提供多种登录方式已成为一种标准做法,这不仅能提升用户体验,还能满足不同用户的需求。本文将详细介绍如何使用Vue.js框架结合Element UI组件库,实现一个包含账号登录和手机号验证码登录两种方式的登录页面。通过本文,你将能够掌握如何根据用户选择动态切换登录表单、如何进行表单验证以及如何实现验证码的发送和倒计时功能。

功能需求
  1. 账号登录:用户通过输入用户名和密码进行登录。
  2. 验证码登录:用户通过输入手机号码并接收验证码进行登录。
效果图

Vue.js + Element UI 实现多方式登录功能(账号/手机号验证码登录)_表单

Vue.js + Element UI 实现多方式登录功能(账号/手机号验证码登录)_javascript_02

Vue.js + Element UI 实现多方式登录功能(账号/手机号验证码登录)_验证码_03

1. 登录页面设计

src/components目录下创建一个名为Login.vue的文件,用于实现登录页面。

1.1 模板部分(Template)

使用Element UI的el-form组件构建登录表单,通过v-if指令根据flag变量的值动态显示不同的登录方式(账号登录或手机号验证码登录)

<template>
  <div class="login">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      class="login-form"
      :inline="true"
    >
      <!-- 标题部分 -->
      <div class="title">
        <div
          @click="showPhone"
          v-if="flag == 'verificationCode'"
          class="title-back"
        >
          <i class="el-icon-arrow-left"></i>返回
        </div>
        <div class="title-span">{{ formTitle }}</div>
      </div>

      <!-- 账号登录表单 -->
      <div class="formCont" v-if="flag == 'account'">
        <el-form-item prop="username" label="用户名:">
          <el-input
            v-model="loginForm.username"
            type="text"
            auto-complete="off"
            placeholder="用户名"
          >
          </el-input>
        </el-form-item>
        <el-form-item prop="password" label="密码:">
          <el-input
            v-model="loginForm.password"
            type="password"
            auto-complete="off"
            placeholder="密码"
            show-password
            @keyup.enter.native="handleLogin"
          >
          </el-input>
        </el-form-item>
        <div class="button">
          <el-form-item style="width: 100%">
            <el-button
              size="medium"
              type="primary"
              style="width: 100%"
              @click.native.prevent="handleLogin"
            >
              登 录
            </el-button>
          </el-form-item>
        </div>
      </div>

      <!-- 手机号码登录表单 -->
      <div class="formCont phone-cont" v-if="flag == 'phone'">
        <el-form-item prop="phoneNumber">
          <div class="text">请使用人事处登记手机号进行登录</div>
          <el-input
            v-model="loginForm.phoneNumber"
            type="text"
            auto-complete="off"
            placeholder="请输入手机号码"
          >
          </el-input>
        </el-form-item>
        <div class="button">
          <el-form-item style="width: 100%">
            <el-button
              size="medium"
              type="primary"
              style="width: 100%"
              @click.native.prevent="handlePhoneNumber"
            >
              验证手机号码
            </el-button>
          </el-form-item>
        </div>
      </div>

      <!-- 验证码输入部分 -->
      <div v-if="flag == 'verificationCode'" class="verification-code">
        <div class="tx">输入验证码</div>
        <div class="tx">我们已向{{ phoneNumber }} 发送验证码</div>
        <div class="tx">请查看短信并输入验证码</div>
        <div class="six-digit-wrapper">
          <input
            v-for="(item, index) in digits"
            :key="index"
            :ref="`ref${index}`"
            class="input"
            v-model="item.value"
            type="text"
            oninput="value=value.replace(/[^\d]/g,'')"
            @input="onInput(index)"
            @keyup.delete="onDelete(index)"
            maxlength="1"
          />
        </div>
        <div
          class="anew-verification-code anew-verification-code-send"
          v-if="show"
          @click="handleGetCode"
        >
          重新获取验证码
        </div>
        <div class="anew-verification-code" v-else>
          {{ count }}秒后重新获取短信
        </div>
      </div>

      <!-- 切换登录方式按钮 -->
      <div class="btn-cont" @click="showPhone" v-if="flag == 'account'">
        验证码登录
      </div>
      <div class="btn-cont" @click="showAccount" v-if="flag == 'phone'">
        账号字码登录
      </div>
    </el-form>
  </div>
</template>
1.2 脚本部分(Script)

在脚本部分,定义登录表单的数据模型、验证规则以及方法。

<script>
export default {
  name: "Login",
  components: {},
  data() {
    return {
      loginForm: {
        username: "",
        password: "",
        phoneNumber: "",
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" },
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" },
        ],
        phoneNumber: [
          { required: true, message: "手机号码不能为空", trigger: "blur" },
          {
            pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
            message: "手机号码格式错误",
            trigger: "blur",
          },
        ],
      },
      formTitle: "管理员账号登录",
      flag: "account",
      phoneNumber: "",
      show: true, // 控制倒计时状态
      count: 60, // 倒计时秒数
      timer: null, //计时器
      digits: [
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
      ],
    };
  },
  // 省略了部分代码,完整代码见下文
};
</script>
2. 功能实现
2.1 账号登录

用户选择账号登录时,表单显示用户名和密码输入框。通过Element UI的el-form组件的validate方法进行表单验证,验证通过后调用登录接口。

//管理员账号登录
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          //你的调用登录接口的代码
        }
      });
    },
2.2 手机号验证码登录

用户选择手机号验证码登录时,表单显示手机号输入框。用户输入手机号后,点击“验证手机号码”按钮,调用发送验证码接口,并启动倒计时。

// 验证手机号码
    handlePhoneNumber() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.formTitle = "验证手机号码";
          this.phoneNumber = this.loginForm.phoneNumber;
          this.flag = "verificationCode";
          this.handleGetCode();
        }
      });
    },
    // 获取短信验证码
    handleGetCode() {
      // 验证码倒计时
      if (!this.timer) {
        //调取你的发送验证码的接口
        sendCode({ phone: this.phoneNumber }).then((res) => {
          // 处理发送验证码的响应
        });

        this.count = 60;
        this.show = false;
        this.timer = setInterval(() => {
          if (this.count > 0 && this.count <= 60) {
            this.count--;
          } else {
            this.show = true;
            clearInterval(this.timer);
            this.timer = null;
          }
        }, 1000);
      }
    },

在验证码输入框中,通过监听inputkeyup.delete事件,实现光标的自动移动和删除功能。

// 输入验证码时的处理
    onInput(index) {
      // index < 5 ,如果是第6格,不触发光标移动至下一个输入框。
      if (this.digits[index].value && index < 5) {
        this.$refs["ref" + (index + 1)][0].focus();
      } else {
        let resultString = this.digits.map((obj) => obj.value).join("");
        if (resultString.length == 6) {
          let data = { phone: this.phoneNumber, code: resultString };
          //调取你的验证码登录接口
          mwlogin(data).then((res) => {
            // 处理验证码登录的响应
          });
        }
      }
    },
    // 删除验证码时的处理
    onDelete(index) {
      // 如果是第1格,不触发光标移动至上一个输入框
      if (index > 0) {
        this.$refs["ref" + (index - 1)][0].focus();
      }
    },
3. 样式调整

根据需要调整样式,确保表单在不同设备上的显示效果。

4. 测试与优化
  • 测试:确保在不同情况下(如输入错误、验证码发送失败等)都有相应的提示和处理。
  • 优化:根据实际需求优化用户体验,如添加加载动画、错误提示等。
完整代码
<template>
  <div class="login">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      class="login-form"
      :inline="true"
    >
      <!-- 标题部分 -->
      <div class="title">
        <div
          @click="showPhone"
          v-if="flag == 'verificationCode'"
          class="title-back"
        >
          <i class="el-icon-arrow-left"></i>返回
        </div>
        <div class="title-span">{{ formTitle }}</div>
      </div>

      <!-- 账号登录表单 -->
      <div class="formCont" v-if="flag == 'account'">
        <el-form-item prop="username" label="用户名:">
          <el-input
            v-model="loginForm.username"
            type="text"
            auto-complete="off"
            placeholder="用户名"
          >
          </el-input>
        </el-form-item>
        <el-form-item prop="password" label="密码:">
          <el-input
            v-model="loginForm.password"
            type="password"
            auto-complete="off"
            placeholder="密码"
            show-password
            @keyup.enter.native="handleLogin"
          >
          </el-input>
        </el-form-item>
        <div class="button">
          <el-form-item style="width: 100%">
            <el-button
              size="medium"
              type="primary"
              style="width: 100%"
              @click.native.prevent="handleLogin"
            >
              登 录
            </el-button>
          </el-form-item>
        </div>
      </div>

      <!-- 手机号码登录表单 -->
      <div class="formCont phone-cont" v-if="flag == 'phone'">
        <el-form-item prop="phoneNumber">
          <div class="text">请使用人事处登记手机号进行登录</div>
          <el-input
            v-model="loginForm.phoneNumber"
            type="text"
            auto-complete="off"
            placeholder="请输入手机号码"
          >
          </el-input>
        </el-form-item>
        <div class="button">
          <el-form-item style="width: 100%">
            <el-button
              size="medium"
              type="primary"
              style="width: 100%"
              @click.native.prevent="handlePhoneNumber"
            >
              验证手机号码
            </el-button>
          </el-form-item>
        </div>
      </div>

      <!-- 验证码输入部分 -->
      <div v-if="flag == 'verificationCode'" class="verification-code">
        <div class="tx">输入验证码</div>
        <div class="tx">我们已向{{ phoneNumber }} 发送验证码</div>
        <div class="tx">请查看短信并输入验证码</div>
        <div class="six-digit-wrapper">
          <input
            v-for="(item, index) in digits"
            :key="index"
            :ref="`ref${index}`"
            class="input"
            v-model="item.value"
            type="text"
            oninput="value=value.replace(/[^\d]/g,'')"
            @input="onInput(index)"
            @keyup.delete="onDelete(index)"
            maxlength="1"
          />
        </div>
        <div
          class="anew-verification-code anew-verification-code-send"
          v-if="show"
          @click="handleGetCode"
        >
          重新获取验证码
        </div>
        <div class="anew-verification-code" v-else>
          {{ count }}秒后重新获取短信
        </div>
      </div>

      <!-- 切换登录方式按钮 -->
      <div class="btn-cont" @click="showPhone" v-if="flag == 'account'">
        验证码登录
      </div>
      <div class="btn-cont" @click="showAccount" v-if="flag == 'phone'">
        账号字码登录
      </div>
    </el-form>
  </div>
</template>

<script>
import { sendCode, mwlogin } from "@/api/login";
export default {
  name: "Login",
  components: {},
  data() {
    return {
      loginForm: {
        username: "",
        password: "",
        phoneNumber: "",
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" },
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" },
        ],
        phoneNumber: [
          { required: true, message: "手机号码不能为空", trigger: "blur" },
          {
            pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
            message: "手机号码格式错误",
            trigger: "blur",
          },
        ],
      },
      formTitle: "管理员账号登录",
      flag: "account",
      phoneNumber: "",
      show: true, // 控制倒计时状态
      count: 60, // 倒计时秒数
      timer: null, //计时器
      digits: [
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
        {
          value: "",
        },
      ],
    };
  },
  watch: {},
  mounted() {},
  created() {},
  methods: {
    //管理员账号登录
    handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          //你的调用登录接口的代码
        }
      });
    },
    // 切换到手机号码登录
    showPhone() {
      this.formTitle = "管理员手机号码登录";
      this.flag = "phone";
    },
    // 切换到账号登录
    showAccount() {
      this.formTitle = "管理员账号登录";
      this.flag = "account";
    },
    // 验证手机号码
    handlePhoneNumber() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.formTitle = "验证手机号码";
          this.phoneNumber = this.loginForm.phoneNumber;
          this.flag = "verificationCode";
          this.handleGetCode();
        }
      });
    },
    // 获取短信验证码
    handleGetCode() {
      // 验证码倒计时
      if (!this.timer) {
        //调取你的发送验证码的接口
        sendCode({ phone: this.phoneNumber }).then((res) => {
          // 处理发送验证码的响应
        });

        this.count = 60;
        this.show = false;
        this.timer = setInterval(() => {
          if (this.count > 0 && this.count <= 60) {
            this.count--;
          } else {
            this.show = true;
            clearInterval(this.timer);
            this.timer = null;
          }
        }, 1000);
      }
    },
    // 输入验证码时的处理
    onInput(index) {
      // index < 5 ,如果是第6格,不触发光标移动至下一个输入框。
      if (this.digits[index].value && index < 5) {
        this.$refs["ref" + (index + 1)][0].focus();
      } else {
        let resultString = this.digits.map((obj) => obj.value).join("");
        if (resultString.length == 6) {
          let data = { phone: this.phoneNumber, code: resultString };
          //调取你的验证码登录接口
          mwlogin(data).then((res) => {
            // 处理验证码登录的响应
          });
        }
      }
    },
    // 删除验证码时的处理
    onDelete(index) {
      // 如果是第1格,不触发光标移动至上一个输入框
      if (index > 0) {
        this.$refs["ref" + (index - 1)][0].focus();
      }
    },
  },
};
</script>

<style rel="stylesheet/scss" lang="scss">
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-image: url("https://img202.yun300.cn/img/s05bg.jpg?tenantId=238938&viewType=1&k=1632806193000");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  position: relative;
}
.btn-cont {
  font-family: "Arial Normal", "Arial", sans-serif;
  font-weight: 400;
  font-style: normal;
  color: #ca512f;
  line-height: 20px;
  text-align: center;
  margin-top: 30px;
  cursor: pointer;
}
.title {
  text-align: left;
  margin: 0 auto 35px;
  height: 40px;
  padding-top: 10px;
  display: flex;
  .title-back {
    color: #ca512f;
  }
  .title-span {
    position: relative;
    margin: 0 auto 32px auto;
    width: -webkit-fit-content;
    width: -moz-fit-content;
    width: fit-content;
    height: -webkit-fit-content;
    height: -moz-fit-content;
    height: fit-content;
    background: -webkit-gradient(
      linear,
      left top,
      left bottom,
      color-stop(50%, #e2eff6),
      to(#8cbcdf)
    );
    background: linear-gradient(180deg, #e2eff6 50%, #8cbcdf);
    color: transparent;
    -webkit-background-clip: text;
    font-size: 38px;
    letter-spacing: 2px;
    font-family: cursive;
    font-weight: 700;
  }
}
.verification-code {
  text-align: center;
  font-family: "Arial Normal", "Arial", sans-serif;
  font-weight: 400;
  font-style: normal;
  font-size: 18px;
  letter-spacing: normal;
  .tx {
    margin-bottom: 16px;
    color: #ffffff;
  }
  .anew-verification-code {
    color: #ca512f;
  }
  .anew-verification-code-send {
    cursor: pointer;
  }
}
.login .login-form {
  position: absolute;
  top: 30vh;
  left: 50%;
  transform: translateX(-50%);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.2);
  width: 503px;
  height: 400px;
  .formCont {
    padding: 0 40px 0;
  }
  .el-input {
    height: 40px;
    width: 300px;
    input {
      height: 40px;
      border: 1px solid RGBA(28, 57, 77, 0.6);
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
  .phone-cont {
    .text {
      font-family: "微软雅黑", sans-serif;
      font-weight: 400;
      font-size: 14px;
      color: #ffffff;
      text-align: center;
    }

    .el-input {
      width: 423px;
    }
  }
}

.login-code {
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.login {
  .login-code-img {
    height: 38px;
    width: 75px;
    margin-left: 25px;
  }
  .el-form-item.is-required:not(.is-no-asterisk) > .el-form-item__label:before {
    display: none;
  }
  .el-form-item--medium .el-form-item__label {
    color: #fff;
    width: 64px;
    height: 22px;
    text-align: left;
    font-size: 15px;
    font-weight: unset;
  }
  .el-form-item {
    margin: 22px 10px 22px 0;
  }
  .button {
    .el-form-item {
      margin-bottom: unset;
    }
    .el-form-item__content {
      width: 100%;
    }
    .el-button--primary {
      background: linear-gradient(
        to top right,
        rgb(231, 133, 106) rgb(202, 81, 47, 1)
      );
    }
  }

  .el-tabs__item.is-active {
    color: rgba(0, 204, 255, 1) !important;
  }
  .el-tabs--top .el-tabs__item {
    color: RGBA(3, 129, 170, 1);
    padding: unset;
  }

  .six-digit-wrapper {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    margin: 20px 0 50px 0;
    .input {
      display: flex;
      width: 60px;
      margin-left: 10px;
      height: 60px;
      font-size: 24px;
      color: #333333;
      background-color: #f2f2f2;
      text-align: center;
      outline: none; // 去除选中状态边框
      border: solid 1px #d2d2d2;
      border-top: 0px;
      border-left: 0px;
      border-right: 0px;
    }
  }
}
</style>
<style scoped lang="scss">
::v-deep {
  .el-dialog {
    .el-dialog__header {
      background: #ffffff !important;
      padding: 0;
      height: 30px;
    }
  }
}
</style>
总结

本文详细介绍了如何使用Vue.js和Element UI实现一个多方式登录功能的登录页面。通过本文的学习,你可以掌握如何根据用户选择动态切换表单、如何进行表单验证以及如何实现验证码的发送和倒计时功能。希望本文对你有所帮助,如果你在实现过程中遇到任何问题,欢迎在评论区留言讨论。