From 2bb11b49fef4a9b92be17e3f56f386120c6cc776 Mon Sep 17 00:00:00 2001 From: wuxichen <17301714657@163.com> Date: Wed, 10 Sep 2025 17:13:35 +0800 Subject: [PATCH] feat: loginOut --- src/app.tsx | 15 ++-- .../RightContent/AvatarDropdown.tsx | 9 ++- src/pages/user/login/index.tsx | 6 +- src/requestErrorConfig.ts | 24 ++++--- src/services/login/index.ts | 14 +--- src/services/login/types.ts | 71 +++++++++++-------- src/services/system/menu/index.ts | 49 +++++++++++++ 7 files changed, 124 insertions(+), 64 deletions(-) create mode 100644 src/services/system/menu/index.ts diff --git a/src/app.tsx b/src/app.tsx index 55b50c5..9b47b58 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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; - currentUser?: { user: UserVO }; + currentUser?: UserInfoVO; loading?: boolean; - fetchUserInfo?: () => Promise<{ user: UserVO } | undefined>; + fetchUserInfo?: () => Promise; }> { + 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); } diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index a78507d..06eadac 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -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 = ({ /** * 退出登录,并且将当前的 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 = ({ }); /** 此方法会跳转到 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 = ({ flushSync(() => { setInitialState((s) => ({ ...s, currentUser: undefined })); }); - loginOut(); + onLoginOut(); return; } history.push(`/account/${key}`); diff --git a/src/pages/user/login/index.tsx b/src/pages/user/login/index.tsx index 56a5c35..e12f08b 100644 --- a/src/pages/user/login/index.tsx +++ b/src/pages/user/login/index.tsx @@ -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 || "登录失败,请重试!"); diff --git a/src/requestErrorConfig.ts b/src/requestErrorConfig.ts index ff356eb..246f898 100644 --- a/src/requestErrorConfig.ts +++ b/src/requestErrorConfig.ts @@ -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; }, diff --git a/src/services/login/index.ts b/src/services/login/index.ts index c3e22e3..864eb04 100644 --- a/src/services/login/index.ts +++ b/src/services/login/index.ts @@ -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>( + return request>( "/system/auth/get-permission-info", { method: "GET", diff --git a/src/services/login/types.ts b/src/services/login/types.ts index b5790e6..c4a815b 100644 --- a/src/services/login/types.ts +++ b/src/services/login/types.ts @@ -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; + roles: string[]; + isSetUser: boolean; + user: UserVO; + menus: MenuVO[]; } diff --git a/src/services/system/menu/index.ts b/src/services/system/menu/index.ts new file mode 100644 index 0000000..e94500e --- /dev/null +++ b/src/services/system/menu/index.ts @@ -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 }) +// }