本文章只是一个思路的梳理,只扒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;
}
}
这里的整体大概逻辑就是:
这个函数在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 }; // 做成异步调用返回出去
}
疑问:这里校验为什么还要再封一层而不直接调用呢?留个坑以后看他的自封组件后了解一下。
user相关的状态管理actions方法,文件在src\store\modules\user.ts,
登陆逻辑。里面涉及到的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);
}
},
大概逻辑就是:
调用登陆之后做的逻辑函数,之前很好奇为什么不直接把登陆后的逻辑写在点击登陆哪里,后来看到在权限控制哪里也调用了这个方法。所以专门封装成一个动作了。
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;
},
大概逻辑:
获取用户信息
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;
},
大概逻辑:
在src\layouts\default\header\components\user-dropdown\index.vue文件中:
function handleLoginOut() {
userStore.confirmLoginOut(); // 调用退出登陆确认框
}
user相关的状态管理actions方法,文件在src\store\modules\user.ts,
退出登陆确认框逻辑
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); // 调用对出登陆
},
});
},
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); // 是否需要跳转到登录页
},
居然没找到对应的逻辑。
看了这个登陆处理的大概逻辑,有几个想法