feat: loginOut

This commit is contained in:
2025-09-10 17:13:35 +08:00
parent 55cd1f349d
commit 2bb11b49fe
7 changed files with 124 additions and 64 deletions

View File

@@ -12,11 +12,12 @@ import {
SelectLang,
} from "@/components";
import { getInfo } from "@/services/login";
import type { UserVO, TokenType } from "@/services/login/types";
import type { UserVO, TokenType, UserInfoVO } from "@/services/login/types";
import defaultSettings from "../config/defaultSettings";
import { errorConfig } from "./requestErrorConfig";
import "@ant-design/v5-patch-for-react-19";
import { getAccessToken, getRefreshToken, getTenantId } from "./utils/auth";
import { CACHE_KEY, useCache } from "./hooks/web/useCache";
const isDev = process.env.NODE_ENV === "development";
const isDevOrTest = isDev || process.env.CI;
@@ -27,10 +28,11 @@ const loginPath = "/user/login";
* */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
currentUser?: { user: UserVO };
currentUser?: UserInfoVO;
loading?: boolean;
fetchUserInfo?: () => Promise<{ user: UserVO } | undefined>;
fetchUserInfo?: () => Promise<UserInfoVO | undefined>;
}> {
const { wsCache } = useCache();
const fetchUserInfo = async () => {
// history.push(loginPath);
try {
@@ -38,8 +40,10 @@ export async function getInitialState(): Promise<{
if (!token) {
throw new Error("No token found");
}
const msg = await getInfo();
return msg.data;
const { data } = await getInfo();
wsCache.set(CACHE_KEY.USER, data);
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
return data;
} catch (_error) {
history.push(loginPath);
}
@@ -89,7 +93,6 @@ export const layout: RunTimeLayoutConfig = ({
onPageChange: () => {
const { location } = history;
// 如果没有登录,重定向到 login
console.log(initialState);
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
}

View File

@@ -11,6 +11,8 @@ import React from "react";
import { flushSync } from "react-dom";
import { loginOut } from "@/services/login";
import HeaderDropdown from "../HeaderDropdown";
import { removeToken } from "@/utils/auth";
import { deleteUserCache } from "@/hooks/web/useCache";
export type GlobalHeaderRightProps = {
menu?: boolean;
@@ -48,8 +50,10 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
/**
* 退出登录,并且将当前的 url 保存
*/
const loginOut = async () => {
const onLoginOut = async () => {
await loginOut();
removeToken();
deleteUserCache(); // 删除用户缓存
const { search, pathname } = window.location;
const urlParams = new URL(window.location.href).searchParams;
const searchParams = new URLSearchParams({
@@ -57,7 +61,6 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
});
/** 此方法会跳转到 redirect 参数所在的位置 */
const redirect = urlParams.get("redirect");
// Note: There may be security issues, please note
if (window.location.pathname !== "/user/login" && !redirect) {
history.replace({
pathname: "/user/login",
@@ -75,7 +78,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
flushSync(() => {
setInitialState((s) => ({ ...s, currentUser: undefined }));
});
loginOut();
onLoginOut();
return;
}
history.push(`/account/${key}`);

View File

@@ -14,7 +14,7 @@ import {
ProFormCheckbox,
ProFormText,
} from "@ant-design/pro-components";
import { history, useModel } from "@umijs/max";
import { history, useModel, useNavigate } from "@umijs/max";
import { Button, Divider, Space, Tabs, theme, message } from "antd";
import type { CSSProperties } from "react";
import { useState } from "react";
@@ -34,6 +34,7 @@ const Page = () => {
const { token } = theme.useToken();
const [messageApi, contextHolder] = message.useMessage();
const { initialState, setInitialState } = useModel("@@initialState");
const navigate = useNavigate();
// 获取租户 ID
const getTenantId = async (name: string) => {
const res = await getTenantIdByName({ name });
@@ -72,12 +73,11 @@ const Page = () => {
if (res.code === 0) {
// 登录成功
messageApi.success("登录成功!");
// 跳转到首页
authUtil.setToken(res.data);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
window.location.href = urlParams.get("redirect") || "/";
navigate(urlParams.get("redirect") || "/");
} else {
// 登录失败
messageApi.error(res.msg || "登录失败,请重试!");

View File

@@ -1,7 +1,9 @@
import type { RequestOptions } from '@@/plugin-request/request';
import type { RequestConfig } from '@umijs/max';
import { message, notification } from 'antd';
import type { RequestOptions } from "@@/plugin-request/request";
import type { RequestConfig } from "@umijs/max";
import { message, notification } from "antd";
import { deleteUserCache } from "@/hooks/web/useCache";
const tenantEnable = process.env.VITE_APP_TENANT_ENABLE;
// const { result_code, base_url, request_timeout } = config;
// 错误处理方案: 错误类型
enum ErrorShowType {
SILENT = 0,
@@ -33,7 +35,7 @@ export const errorConfig: RequestConfig = {
res as unknown as ResponseStructure;
if (!success) {
const error: any = new Error(errorMessage);
error.name = 'BizError';
error.name = "BizError";
error.info = { errorCode, errorMessage, showType, data };
throw error; // 抛出自制的错误
}
@@ -42,7 +44,7 @@ export const errorConfig: RequestConfig = {
errorHandler: (error: any, opts: any) => {
if (opts?.skipErrorHandler) throw error;
// 我们的 errorThrower 抛出的错误。
if (error.name === 'BizError') {
if (error.name === "BizError") {
const errorInfo: ResponseStructure | undefined = error.info;
if (errorInfo) {
const { errorMessage, errorCode } = errorInfo;
@@ -77,10 +79,10 @@ export const errorConfig: RequestConfig = {
// 请求已经成功发起,但没有收到响应
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
message.error('None response! Please retry.');
message.error("None response! Please retry.");
} else {
// 发送请求时出了点问题
message.error('Request error, please retry.');
message.error("Request error, please retry.");
}
},
},
@@ -89,8 +91,8 @@ export const errorConfig: RequestConfig = {
requestInterceptors: [
(config: RequestOptions) => {
// 拦截请求配置,进行个性化处理。
const url = config?.url?.concat('?token=123');
return { ...config, url };
return { ...config };
},
],
@@ -101,7 +103,7 @@ export const errorConfig: RequestConfig = {
const { data } = response as unknown as ResponseStructure;
if (data?.success === false) {
message.error('请求失败!');
message.error("请求失败!");
}
return response;
},

View File

@@ -71,21 +71,13 @@ export async function getTenantByWebsite(
...(options || {}),
});
}
// 登出
// export const loginOut = () => {
// return request.post({ url: "/system/auth/logout" });
// };
export async function loginOut(
body: API.UserLoginVO,
options?: { [key: string]: any }
) {
export async function loginOut() {
return request("/system/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
// 获取用户权限信息
@@ -93,7 +85,7 @@ export async function loginOut(
// return request.get({ url: "/system/auth/get-permission-info" });
// };
export async function getInfo(options?: { [key: string]: any }) {
return request<IResponse<{ user: API.UserVO }>>(
return request<IResponse<API.UserInfoVO>>(
"/system/auth/get-permission-info",
{
method: "GET",

View File

@@ -1,38 +1,49 @@
import { MenuVO } from "../system/menu";
export type UserLoginVO = {
username: string
password: string
captchaVerification: string
socialType?: string
socialCode?: string
socialState?: string
}
username: string;
password: string;
captchaVerification: string;
socialType?: string;
socialCode?: string;
socialState?: string;
};
export type TokenType = {
id: number // 编号
accessToken: string // 访问令牌
refreshToken: string // 刷新令牌
userId: number // 用户编号
userType: number //用户类型
clientId: string //客户端编号
expiresTime: number //过期时间
}
id: number; // 编号
accessToken: string; // 访问令牌
refreshToken: string; // 刷新令牌
userId: number; // 用户编号
userType: number; //用户类型
clientId: string; //客户端编号
expiresTime: number; //过期时间
};
export type UserVO = {
id: number
username: string
nickname: string
deptId: number
email: string
mobile: string
sex: number
avatar: string
loginIp: string
loginDate: string
}
id: number;
username: string;
nickname: string;
deptId: number;
email: string;
mobile: string;
sex: number;
avatar: string;
loginIp: string;
loginDate: string;
};
export type RegisterVO = {
tenantName: string
username: string
password: string
captchaVerification: string
tenantName: string;
username: string;
password: string;
captchaVerification: string;
};
export interface UserInfoVO {
// USER 缓存
permissions: Set<string>;
roles: string[];
isSetUser: boolean;
user: UserVO;
menus: MenuVO[];
}

View File

@@ -0,0 +1,49 @@
import { request } from "@umijs/max";
export interface MenuVO {
id: number;
name: string;
permission: string;
type: number;
sort: number;
parentId: number;
path: string;
icon: string;
component: string;
componentName?: string;
status: number;
visible: boolean;
keepAlive: boolean;
alwaysShow?: boolean;
createTime: Date;
}
// 查询菜单(精简)列表
// export const getSimpleMenusList = () => {
// return request.get({ url: '/system/menu/simple-list' })
// }
// // 查询菜单列表
// export const getMenuList = (params) => {
// return request.get({ url: '/system/menu/list', params })
// }
// // 获取菜单详情
// export const getMenu = (id: number) => {
// return request.get({ url: '/system/menu/get?id=' + id })
// }
// // 新增菜单
// export const createMenu = (data: MenuVO) => {
// return request.post({ url: '/system/menu/create', data })
// }
// // 修改菜单
// export const updateMenu = (data: MenuVO) => {
// return request.put({ url: '/system/menu/update', data })
// }
// // 删除菜单
// export const deleteMenu = (id: number) => {
// return request.delete({ url: '/system/menu/delete?id=' + id })
// }