Skip to content

🔐 useLogin 登录 Hook 使用指南

🎯 功能简介

useLogin 是一个强大的登录功能 Hook,支持账号密码登录和短信验证码登录两种方式。它提供了完整的登录流程管理,包括表单验证、状态管理、错误处理等功能。

🚀 核心特性

  • 📱 支持账号密码和短信验证码两种登录方式
  • 🔒 内置表单验证和错误处理
  • ⏱️ 短信验证码倒计时功能
  • 🔄 登录状态管理
  • �� Token 和用户信息存储
  • 🔗 支持登录后重定向

📝 使用方法

基本配置

typescript
import { useLogin } from '@pt/common-ui';
const { 
  loading,
  formAccount,
  handleAccountLogin 
} = useLogin({
  clientId: 'your_client_id',
  redirectUrl: 'optional_redirect_url'
}, () => {
  console.log('登录成功回调');
});

完整示例及效果

vue
<template>
  <div class="w-100vw h-100vh">
    <a-form
      class="p-c w-100%"
      :model="formAccount"
      name="basic"
      :label-col="{ span: 8 }"
      :wrapper-col="{ span: 8 }"
      autocomplete="off"
      @finish="onLogin"
      @finishFailed="onLoginFailed"
    >
      <a-form-item
        label="Username"
        name="username"
        :rules="[{ required: true, message: 'Please input your username!' }]"
      >
        <a-input v-model:value="formAccount.username" />
      </a-form-item>

      <a-form-item
        label="Password"
        name="password"
        :rules="[{ required: true, message: 'Please input your password!' }]"
      >
        <a-input-password v-model:value="formAccount.password" />
      </a-form-item>

      <a-form-item name="isVerifyPass" :wrapper-col="{ offset: 8, span: 16 }">
        <a-checkbox v-model:checked="formAccount.isVerifyPass">选中模拟滑块验证</a-checkbox>
      </a-form-item>

      <a-form-item :wrapper-col="{ offset: 8, span: 16 }">
        <a-button type="primary" html-type="submit" :loading="loading">登录</a-button>
      </a-form-item>
    </a-form>
  </div>
</template>

<script setup lang="ts">
import { useLogin } from '@pt/common-ui';

const { loading, formAccount, handleAccountLogin } = useLogin({
  clientId: 'tudou_sa',
}, () => {
  console.log('登录成功');
});

// 设置默认值(可选)
Object.assign(formAccount, {
  username: '13259962526',
  password: 'qaz13579@',
  isVerifyPass: true,
});

const onLogin = (values: any) => {
  handleAccountLogin();
};

const onLoginFailed = (errorInfo: any) => {
  console.log('Failed:', errorInfo);
}
</script>

运行效果如下:

useLogin

⌨️ 完整源码实现

typescript
/**
 * @description 登录能力实现
 * */
import { ref, reactive, Ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import md5 from 'js-md5';
import { message } from 'ant-design-vue';
import { storage, shuffleBase64Encode } from '@pt/utils';
import { CLIENT_ID } from '@/config/config';

import { safeBase64ToStr } from '@/utils';

// 这些 API 接口需要从原项目中复制或引入
import { getLoginSmsApi, loginApi, loginBySmsApi } from '@/api/modules/login';

// 登录验证器类型定义
export interface LoginValidator {
  validateMobile: (rule: any, value: string) => Promise<void>;
  validateDrag: (rule: any, value: boolean) => Promise<void>;
}

// 登录相关状态类型定义
export interface LoginState {
  loading: Ref<boolean>;
  activeKey: Ref<string>;
  formAccount: {
    username: string;
    password: string;
    isVerifyPass: boolean;
  };
  formCaptcha: {
    mobile: string;
    captcha: string;
  };
  captchaTime: Ref<number>;
}

// 登录设置类型
export interface LoginConfig {
  clientId?: string;
  redirectUrl?: string | null;
}

export const useLogin = (
  config: LoginConfig = {},
  onLoginSuccess: () => void
) => {
  const router = useRouter();
  const route = useRoute();

  // 获取登录页面配置
  const loginConfig = config;

  const oClientId = loginConfig.clientId || CLIENT_ID;

  // 登录状态
  const loading = ref(false);
  const activeKey = ref('account');

  // 账号登录表单
  const formAccount = reactive({
    username: '',
    password: '',
    isVerifyPass: false,
  });

  // 验证码登录表单
  const formCaptcha = reactive({
    mobile: '',
    captcha: '',
  });

  // 验证码倒计时
  const captchaTime = ref(0);

  // 验证器
  const validators: LoginValidator = {
    // 手机号验证
    validateMobile(rule: any, value: string) {
      if (!value) {
        return Promise.reject('请输入手机号');
      }
      const mobileReg =
        /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
      if (!mobileReg.test(value)) {
        return Promise.reject('请输入正确的手机号');
      }
      return Promise.resolve();
    },

    // 拖动验证
    validateDrag(rule: any, value: boolean) {
      if (!value) {
        return Promise.reject('请滑动验证');
      }
      return Promise.resolve();
    },
  };

  // 获取短信验证码
  const handleCaptcha = async (mobile: string): Promise<boolean> => {
    try {
      if (!mobile) {
        message.error('请输入手机号');
        return false;
      }

      const mobileReg =
        /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
      if (!mobileReg.test(mobile)) {
        message.error('请输入正确的手机号');
        return false;
      }

      loading.value = true;
      const { msg, data } = await getLoginSmsApi({ username: mobile });
      loading.value = false;

      captchaTime.value = 60;
      message.success(msg);

      // 启动倒计时
      const timer = setInterval(() => {
        if (captchaTime.value === 0) {
          clearInterval(timer);
          return;
        }
        captchaTime.value -= 1;
      }, 1000);

      return true;
    } catch (error) {
      loading.value = false;
      message.error('获取验证码失败');
      return false;
    }
  };

  // 处理账号密码登录
  const handleAccountLogin = async (): Promise<boolean> => {
    const { username, password, isVerifyPass } = formAccount;
    try {
      if (!username || !password) {
        message.error('请输入账号和密码');
        return false;
      }

      if (!isVerifyPass) {
        message.error('请完成滑动验证');
        return false;
      }

      loading.value = true;

      // 调用登录接口
      const apiParams = {
        name: username,
        client: oClientId,
        // @ts-ignore
        pwd: shuffleBase64Encode(md5(password)),
      };

      const { msg, data } = await loginApi(apiParams);

      if (!data) {
        loading.value = false;
        message.error(msg);
        return false;
      }

      // 保存用户信息并跳转
      await saveTokenAndRedirect(data);
      return true;
    } catch (error) {
      loading.value = false;
      message.error('登录失败');
      return false;
    }
  };

  // 处理短信验证码登录
  const handleSmsLogin = async (): Promise<boolean> => {
    const { mobile, captcha } = formCaptcha;
    try {
      if (!mobile || !captcha) {
        message.error('请输入手机号和验证码');
        return false;
      }

      loading.value = true;

      // 调用验证码登录接口
      const apiParams = {
        name: mobile,
        validCode: captcha,
      };

      const { msg, data } = await loginBySmsApi(apiParams);

      if (!data) {
        loading.value = false;
        message.error(msg);
        return false;
      }

      message.success(msg);

      // 保存用户信息并跳转
      await saveTokenAndRedirect(data);
      return true;
    } catch (error) {
      loading.value = false;
      message.error('登录失败');
      return false;
    }
  };

  // 保存token和用户信息并进行页面跳转
  const saveTokenAndRedirect = async (data: any) => {
    // 提取用户信息
    let userInfo = {
      tenantId: data.principalInfo.tenantId,
      userId: data.principalInfo.userId,
      userName: data.principalInfo.userName,
    };

    // 存储token和用户信息
    storage.set({
      expires_time: data.tokenInfo.tokenSessionTimeout / (60 * 60 * 24),
      token: data.tokenInfo.tokenValue,
      access_token: data.tokenInfo.tokenValue, // 兼容旧的SAAS应用
      userInfo: JSON.stringify(userInfo),
    });

    // 在此位置添加一个登录成功回调
    if (onLoginSuccess) {
      onLoginSuccess();
    }
    // 处理重定向
    const redirect =
      loginConfig.redirectUrl || (route.query.redirect as string);

    if (redirect) {
      const goUrl = safeBase64ToStr(redirect);
      window.location.href = goUrl;
    } else {
      router.push('/');
    }

    loading.value = false;
  };

  // 获取所有登录状态,用于UI渲染
  const getLoginState = (): LoginState => {
    return {
      loading,
      activeKey,
      formAccount,
      formCaptcha,
      captchaTime,
    };
  };

  // 重置状态
  const resetState = () => {
    loading.value = false;
    activeKey.value = 'account';

    Object.assign(formAccount, {
      username: '',
      password: '',
      isVerifyPass: false,
    });

    Object.assign(formCaptcha, {
      mobile: '',
      captcha: '',
    });

    captchaTime.value = 0;
  };

  return {
    // 状态相关
    loading,
    activeKey,
    formAccount,
    formCaptcha,
    captchaTime,
    getLoginState,
    resetState,

    // 验证器
    validators,

    // 功能方法
    handleCaptcha,
    handleAccountLogin,
    handleSmsLogin,
  };
};

🔧 配置选项

参数类型必填说明
clientIdstring客户端ID
redirectUrlstring登录成功后的重定向地址

💡 最佳实践

  1. 在登录表单中使用 loading 状态来控制按钮的禁用状态
  2. 使用 formAccountformCaptcha 来管理表单数据
  3. 通过 validators 进行表单验证
  4. 使用 resetState 在组件卸载时清理状态
  5. 在登录成功后处理重定向逻辑

⚠️ 注意事项

  1. 确保在使用前正确配置 clientId
  2. 密码会经过 MD5 加密和 Base64 编码处理
  3. 登录成功后会自动存储 token 和用户信息
  4. 支持自定义重定向地址,优先级:配置 > URL参数 > 默认首页

🔗 相关资源

Released under the MIT License.