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

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
import type { RequestOptions } from '@@/plugin-request/request'; import type { RequestOptions } from "@@/plugin-request/request";
import type { RequestConfig } from '@umijs/max'; import type { RequestConfig } from "@umijs/max";
import { message, notification } from 'antd'; 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 { enum ErrorShowType {
SILENT = 0, SILENT = 0,
@@ -33,7 +35,7 @@ export const errorConfig: RequestConfig = {
res as unknown as ResponseStructure; res as unknown as ResponseStructure;
if (!success) { if (!success) {
const error: any = new Error(errorMessage); const error: any = new Error(errorMessage);
error.name = 'BizError'; error.name = "BizError";
error.info = { errorCode, errorMessage, showType, data }; error.info = { errorCode, errorMessage, showType, data };
throw error; // 抛出自制的错误 throw error; // 抛出自制的错误
} }
@@ -42,7 +44,7 @@ export const errorConfig: RequestConfig = {
errorHandler: (error: any, opts: any) => { errorHandler: (error: any, opts: any) => {
if (opts?.skipErrorHandler) throw error; if (opts?.skipErrorHandler) throw error;
// 我们的 errorThrower 抛出的错误。 // 我们的 errorThrower 抛出的错误。
if (error.name === 'BizError') { if (error.name === "BizError") {
const errorInfo: ResponseStructure | undefined = error.info; const errorInfo: ResponseStructure | undefined = error.info;
if (errorInfo) { if (errorInfo) {
const { errorMessage, errorCode } = errorInfo; const { errorMessage, errorCode } = errorInfo;
@@ -77,10 +79,10 @@ export const errorConfig: RequestConfig = {
// 请求已经成功发起,但没有收到响应 // 请求已经成功发起,但没有收到响应
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例, // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例 // 而在node.js中是 http.ClientRequest 的实例
message.error('None response! Please retry.'); message.error("None response! Please retry.");
} else { } else {
// 发送请求时出了点问题 // 发送请求时出了点问题
message.error('Request error, please retry.'); message.error("Request error, please retry.");
} }
}, },
}, },
@@ -89,8 +91,8 @@ export const errorConfig: RequestConfig = {
requestInterceptors: [ requestInterceptors: [
(config: RequestOptions) => { (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; const { data } = response as unknown as ResponseStructure;
if (data?.success === false) { if (data?.success === false) {
message.error('请求失败!'); message.error("请求失败!");
} }
return response; return response;
}, },

View File

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

View File

@@ -1,38 +1,49 @@
import { MenuVO } from "../system/menu";
export type UserLoginVO = { export type UserLoginVO = {
username: string username: string;
password: string password: string;
captchaVerification: string captchaVerification: string;
socialType?: string socialType?: string;
socialCode?: string socialCode?: string;
socialState?: string socialState?: string;
} };
export type TokenType = { export type TokenType = {
id: number // 编号 id: number; // 编号
accessToken: string // 访问令牌 accessToken: string; // 访问令牌
refreshToken: string // 刷新令牌 refreshToken: string; // 刷新令牌
userId: number // 用户编号 userId: number; // 用户编号
userType: number //用户类型 userType: number; //用户类型
clientId: string //客户端编号 clientId: string; //客户端编号
expiresTime: number //过期时间 expiresTime: number; //过期时间
} };
export type UserVO = { export type UserVO = {
id: number id: number;
username: string username: string;
nickname: string nickname: string;
deptId: number deptId: number;
email: string email: string;
mobile: string mobile: string;
sex: number sex: number;
avatar: string avatar: string;
loginIp: string loginIp: string;
loginDate: string loginDate: string;
} };
export type RegisterVO = { export type RegisterVO = {
tenantName: string tenantName: string;
username: string username: string;
password: string password: string;
captchaVerification: 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 })
// }