refactor: 登陆&获取菜单&登出

This commit is contained in:
RubyLiu 2023-07-04 12:05:26 +08:00 committed by 刘瑞斌
parent 9888af431e
commit 7a8b8677bd
16 changed files with 89 additions and 100 deletions

View File

@ -15,7 +15,7 @@ export default mergeConfig(
'/front': {
target: 'http://101.43.186.75:8081/',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/front/, ''),
rewrite: (path: string) => path.replace(/^\/front/, ''),
},
},
},

View File

@ -1,23 +1,22 @@
import { Message, Modal } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import useUser from '@/store/modules/user';
import { useRouter } from 'vue-router';
import type { ErrorMessageMode } from '#/axios';
export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
const { t } = useI18n();
const userStore = useUser();
let errMessage = '';
switch (status) {
case 400:
errMessage = `${msg}`;
break;
// 401: Not logged in
case 401:
case 401: {
errMessage = msg || t('api.errMsg401');
userStore.logout();
const router = useRouter();
router.push('/login');
break;
}
case 403:
errMessage = t('api.errMsg403');
break;

View File

@ -107,14 +107,12 @@ const transform: AxiosTransform = {
/**
* @description:
*/
requestInterceptors: (config, options) => {
requestInterceptors: (config) => {
// 请求之前处理config
const token = getToken();
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
(config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token;
const { sessionId, csrfToken } = token;
(config as Recordable).headers = { 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken };
}
return config;
},
@ -162,8 +160,6 @@ const transform: AxiosTransform = {
},
};
const { sessionId, csrfToken } = getToken();
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new MSAxios(
deepMerge(
@ -174,7 +170,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
authenticationScheme: '',
baseURL: `${window.location.origin}/${import.meta.env.VITE_API_BASE_URL as string}`,
timeout: 30 * 1000,
headers: { 'Content-Type': ContentTypeEnum.JSON, 'X-AUTH-TOKEN': sessionId, 'CSRF-TOKEN': csrfToken },
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 如果是form-data格式
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
// 数据处理方式

View File

@ -1,19 +1,18 @@
import MSR from '@/api/http/index';
import { LoginUrl, LogoutUrl, GetUserInfoUrl, GetMenuListUrl } from '@/api/requrls/user';
import { LoginUrl, LogoutUrl, GetMenuListUrl, isLoginUrl } from '@/api/requrls/user';
import type { RouteRecordNormalized } from 'vue-router';
import type { LoginData, LoginRes } from '@/models/user';
import type { UserState } from '@/store/modules/user/types';
export function login(data: LoginData) {
return MSR.post<LoginRes>({ url: LoginUrl, data });
}
export function logout() {
return MSR.post<LoginRes>({ url: LogoutUrl });
export function isLogin() {
return MSR.get<LoginRes>({ url: isLoginUrl });
}
export function getUserInfo() {
return MSR.post<UserState>({ url: GetUserInfoUrl });
export function logout() {
return MSR.get<LoginRes>({ url: LogoutUrl });
}
export function getMenuList() {

View File

@ -1,4 +1,4 @@
export const LoginUrl = '/login';
export const LogoutUrl = '/api/user/logout';
export const GetUserInfoUrl = '/api/user/info';
export const isLoginUrl = '/is-login';
export const LogoutUrl = '/signout';
export const GetMenuListUrl = '/api/user/menu';

View File

@ -17,9 +17,9 @@
const { t } = useI18n();
const appStore = useAppStore();
const userStore = useUserStore();
const { logout } = useUser();
const router = useRouter();
const route = useRoute();
const { logout } = useUser();
const { menuTree } = useMenuTree();
const collapsed = computed({
get() {
@ -121,7 +121,6 @@
{
label: t('personal.switchOrg'),
icon: <icon-swap />,
event: () => {},
},
{
divider: <a-divider class="ms-dropdown-divider" />,
@ -129,7 +128,7 @@
{
label: t('personal.exit'),
icon: <icon-export />,
event: logout,
event: () => logout(),
},
];

View File

@ -154,6 +154,7 @@ export default function useTableProps(
setPagination({ current: data.current, total: data.total });
return data;
} catch (err) {
// eslint-disable-next-line no-console
console.log(err);
// TODO 表格异常放到solt的empty
} finally {

View File

@ -11,17 +11,15 @@ export default function useUser() {
const router = useRouter();
const userStore = useUserStore();
const { t } = useI18n();
const logout = async (logoutTo?: string) => {
/**
*
* @param logoutTo
* @returns
*/
const logout = async () => {
await userStore.logout();
const currentRoute = router.currentRoute.value;
Message.success(t('message.logoutSuccess'));
router.push({
name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
query: {
...router.currentRoute.value.query,
redirect: currentRoute.name as string,
},
});
router.push({ name: 'login' });
};
return {
logout,

View File

@ -1,14 +1,15 @@
import Mock from 'mockjs';
import setupMock, { successResponseWrap, failResponseWrap, makeMockUrl } from '@/utils/setup-mock';
import { GetMenuListUrl, LogoutUrl, GetUserInfoUrl, LoginUrl } from '@/api/requrls/user';
import { GetMenuListUrl, LogoutUrl, LoginUrl } from '@/api/requrls/user';
import { isLogin } from '@/utils/auth';
setupMock({
mock: false,
setup() {
// 用户信息
Mock.mock(makeMockUrl(GetUserInfoUrl), () => {
if (isLogin()) {
Mock.mock(makeMockUrl('/api/userinfo'), async () => {
if (await isLogin()) {
const role = window.localStorage.getItem('userRole') || 'admin';
return successResponseWrap({
name: '王立群',
@ -32,7 +33,7 @@ setupMock({
return failResponseWrap(null, '未登录', 50008);
});
// 登
// 登
Mock.mock(makeMockUrl(LoginUrl), () => {
return successResponseWrap({});
});

View File

@ -5,8 +5,35 @@ export interface LoginData {
authenticate: string;
}
export interface UserRole {
id: string;
createTime: number;
createUser: string;
roleId: string;
sourceId: string;
userId: string;
}
// 登录返回
export interface LoginRes {
sessionId: string;
csrfToken: string;
createTime: number;
createUser: string;
email: string;
enabled: boolean;
id: string;
language: string;
lastOrganizationId: string;
lastProjectId: string;
name: string;
phone: string;
platformInfo: string;
seleniumServer: string;
sessionId: string;
source: string;
updateTime: number;
updateUser: string;
userRolePermissions: UserRole[];
userRoleRelations: UserRole[];
userRoles: UserRole[];
}

View File

@ -1,31 +1,17 @@
import type { Router, RouteRecordNormalized } from 'vue-router';
import type { Router } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import usePermission from '@/hooks/usePermission';
import { useAppStore } from '@/store';
import { WHITE_LIST, NOT_FOUND } from '../constants';
export default function setupPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
const appStore = useAppStore();
const Permission = usePermission();
const permissionsAllow = Permission.accessRouter(to);
// 针对来自服务端的菜单配置进行处理
if (!appStore.appAsyncMenus.length && !WHITE_LIST.find((el) => el.name === to.name)) {
await appStore.fetchServerMenuConfig();
}
const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST];
let exist = false;
while (serverMenuConfig.length && !exist) {
const element = serverMenuConfig.shift();
if (element?.name === to.name) exist = true;
const exist = WHITE_LIST.find((el) => el.name === to.name);
if (element?.children) {
serverMenuConfig.push(...(element.children as unknown as RouteRecordNormalized[]));
}
}
if (exist && permissionsAllow) {
if (exist || permissionsAllow) {
next();
} else next(NOT_FOUND);
NProgress.done();

View File

@ -1,32 +1,12 @@
import type { Router, LocationQueryRaw } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import { useUserStore } from '@/store';
import { isLogin } from '@/utils/auth';
export default function setupUserLoginInfoGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
NProgress.start();
const userStore = useUserStore();
if (isLogin()) {
if (userStore.role) {
if (to.name !== 'login' && (await isLogin())) {
next();
} else {
try {
await userStore.info();
next();
} catch (error) {
// 获取用户信息错误则先退出,重定向到登录页
await userStore.logout();
next({
name: 'login',
query: {
redirect: to.name,
...to.query,
} as LocationQueryRaw,
});
}
}
} else {
// 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上
if (to.name === 'login') {

View File

@ -4,6 +4,7 @@ import { AppRouteRecordRaw } from '../types';
const ApiTest: AppRouteRecordRaw = {
path: '/api-test',
name: 'apiTest',
redirect: '/api-test/list',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.apiTest',

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { login as userLogin, logout as userLogout, getUserInfo } from '@/api/modules/user';
import { login as userLogin, logout as userLogout } from '@/api/modules/user';
import { setToken, clearToken } from '@/utils/auth';
import { removeRouteListener } from '@/utils/route-listener';
import useAppStore from '../app';
@ -51,22 +51,17 @@ const useUserStore = defineStore('user', {
this.$reset();
},
// 获取用户信息
async info() {
const res = await getUserInfo();
const appStore = useAppStore();
if (appStore.currentOrgId === '') {
// 第一次进系统才设置组织 ID后续已经持久化存储了
appStore.setCurrentOrgId(res.organization || '');
}
this.setInfo(res);
},
// 登录
async login(loginForm: LoginData) {
try {
const res = await userLogin(loginForm);
setToken(res.sessionId, res.csrfToken);
const appStore = useAppStore();
if (appStore.currentOrgId === '') {
// 第一次进系统才设置组织 ID后续已经持久化存储了
appStore.setCurrentOrgId(res.lastOrganizationId || '');
}
this.setInfo(res);
} catch (err) {
clearToken();
throw err;

View File

@ -1,8 +1,15 @@
import { isLogin as isLoginFun } from '@/api/modules/user';
const SESSION_ID = 'sessionId';
const CSRF_TOKEN = 'csrfToken';
const isLogin = () => {
return !!localStorage.getItem(SESSION_ID);
const isLogin = async () => {
try {
await isLoginFun();
return true;
} catch (err) {
return false;
}
};
// 获取token
const getToken = () => {

View File

@ -101,13 +101,6 @@
setLoading(true);
try {
await userStore.login(values as LoginData);
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'apiTest',
query: {
...othersQuery,
},
});
Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value;
const { username, password } = values;
@ -115,6 +108,13 @@
// The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : '';
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'apiTest',
query: {
...othersQuery,
},
});
} catch (err) {
errorMessage.value = (err as Error).message;
} finally {