前言

本文章只是一个思路的梳理,只扒TS的逻辑过程(模板的暂时不涉及,以后有机会再补上吧),作为个人的源码学习记录,不喜勿喷。

源码来源 Vben Admin

登陆

点击登陆

点击登陆按钮的相关文件在src/views/sys/login/LoginForm.vue中,我们来逐句分析,遇到一些封装的地方再往下翻翻可以看到(建议复制一个相同页面用来往下翻找对照来看)。

import { useUserStore } from '/@/store/modules/user'; // 获取user相关的所有状态管理(Pinia)

const formRef = ref(); // 获取表单实例

const { validForm } = useFormValid(formRef); // 获取表单内容的hooks

async function handleLogin() {
    const data = await validForm(); // 首先获取表单内容,hooks逻辑看下面
    if (!data) return;
    try {
      loading.value = true; // 开启组件加载动画
      const userInfo = await userStore.login({ // 调用Pinia的登陆逻辑,逻辑看下面
        password: data.password,
        username: data.account,
        mode: 'none', //不要默认的错误提示
      });
      if (userInfo) {
        // 有用户信息,就提示登陆成功
        notification.success({
          message: t('sys.login.loginSuccessTitle'),
          description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
          duration: 3,
        });
      }
    } catch (error) {
      createErrorModal({
        title: t('sys.api.errorTip'),
        content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
        getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body,
      });
    } finally {
      loading.value = false;
    }
  }

这里的整体大概逻辑就是:

  1. 获取表单输入内容
  2. 发送ajax登陆
  3. 接着去获取用户信息,有的话就登陆成功(具体怎么拿用户数据的逻辑在下面)

validForm

这个函数在src/views/sys/login/useLogin.vue中:

import { ref, computed, unref, Ref } from 'vue';

export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
  // 泛型T被约束成对象,但是这个等于any我还不知到啥意思,知道的可以评论下
  // 入参类型是具备响应式的任意类型
  async function validForm() {
    const form = unref(formRef);
    // unref官网解释:如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。
    if (!form) return;
    const data = await form.validate(); // 自封组件的hooks,拿取输入的内容并校验(以后在讲自封组件)
    return data as T;
  }

  return { validForm }; // 做成异步调用返回出去
}

疑问:这里校验为什么还要再封一层而不直接调用呢?留个坑以后看他的自封组件后了解一下。

Pinia

user相关的状态管理actions方法,文件在src\store\modules\user.ts,

userStore.login

登陆逻辑。里面涉及到的api interface和请求在这里就不细讲了,放在以后管理api解析。token也是管理以后讲:

async login(
  params: LoginParams & { // LoginParams是入参的类型,以后会专门讲个vben是怎么管理api的。
    goHome?: boolean; // 合并入参:是否跳转到首页
    mode?: ErrorMessageMode; // 合并入参:
  },
): Promise<GetUserInfoModel | null> { // Promise的泛型代表promise变成成功态之后resolve的值, GetUserInfoModel是api的interface
  try {
    const { goHome = true, mode, ...loginParams } = params;
    const data = await loginApi(loginParams, mode); // 发送ajax请求,登陆
    const { token } = data;

    // save token
    this.setToken(token); // token管理以后讲
    return this.afterLoginAction(goHome); // 调用登陆之后做的逻辑函数,后面讲
  } catch (error) {
    return Promise.reject(error);
  }
},

大概逻辑就是:

  1. 发送登陆ajax
  2. 设置token
  3. 调用登陆之后做的事情的函数,返回用户信息

userStore.afterLoginAction

调用登陆之后做的逻辑函数,之前很好奇为什么不直接把登陆后的逻辑写在点击登陆哪里,后来看到在权限控制哪里也调用了这个方法。所以专门封装成一个动作了。

async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
  if (!this.getToken) return null;
  // get user info
  const userInfo = await this.getUserInfoAction(); // 获取用户信息,下面讲

  const sessionTimeout = this.sessionTimeout; // state,是否登陆过期
  if (sessionTimeout) {
    this.setSessionTimeout(false); // 修改sessionTimeout
  } else {
    // 接下来都是权限和路由的处理。以后再讲。
    const permissionStore = usePermissionStore();
    if (!permissionStore.isDynamicAddedRoute) {
      const routes = await permissionStore.buildRoutesAction();
      routes.forEach((route) => {
        router.addRoute(route as unknown as RouteRecordRaw);
      });
      router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
      permissionStore.setDynamicAddedRoute(true);
    }
    goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME)); // 跳转到首页
  }
  return userInfo;
},

大概逻辑:

  1. 获取用户信息
  2. 判断是否登陆过期
  3. 权限处理
  4. 返回用户信息

userStore.getUserInfoAction

获取用户信息

async getUserInfoAction(): Promise<UserInfo | null> {
  if (!this.getToken) return null;
  const userInfo = await getUserInfo(); // ajax请求,获取用户信息
  const { roles = [] } = userInfo; // 拿到角色,关于权限的以后再讲
  if (isArray(roles)) {
    const roleList = roles.map((item) => item.value) as RoleEnum[];
    this.setRoleList(roleList);
  } else {
    userInfo.roles = [];
    this.setRoleList([]);
  }
  this.setUserInfo(userInfo); // 设置用户信息,里面的关于权限以后再讲
  return userInfo;
},

大概逻辑:

  1. 发送ajax请求获取用户信息
  2. 角色权限数据处理
  3. 存储用户信息
  4. 返回用户信息

退出登陆

退出系统

在src\layouts\default\header\components\user-dropdown\index.vue文件中:

function handleLoginOut() {
  userStore.confirmLoginOut(); // 调用退出登陆确认框
}

Pinia

user相关的状态管理actions方法,文件在src\store\modules\user.ts,

userStore.confirmLoginOut

退出登陆确认框逻辑

confirmLoginOut() {
  const { createConfirm } = useMessage(); // 创建确认对话框 hooks
  const { t } = useI18n(); // 多语言实例,以后讲
  createConfirm({
    iconType: 'warning',
    title: () => h('span', t('sys.app.logoutTip')),
    content: () => h('span', t('sys.app.logoutMessage')),
    onOk: async () => {
      await this.logout(true); // 调用对出登陆
    },
  });
},

userStore.logout

async logout(goLogin = false) {
  if (this.getToken) { // 有token
    try {
      await doLogout(); // 发送ajax注销
    } catch {
      console.log('注销Token失败');
    }
  }
  // 清除相关数据
  this.setToken(undefined);
  this.setSessionTimeout(false);
  this.setUserInfo(null);
  goLogin && router.push(PageEnum.BASE_LOGIN); // 是否需要跳转到登录页
},

记住我

居然没找到对应的逻辑。

尾巴

看了这个登陆处理的大概逻辑,有几个想法

  • 好像没看到加密传输处理
  • 把每个动作都很好的做了切割,命名见词答意
  • 如果在登陆逻辑内的权限处理也能统一封装成一个见词答意的方法就更好了
Logo

前往低代码交流专区

更多推荐