263 lines
7.6 KiB
TypeScript
263 lines
7.6 KiB
TypeScript
import React, { Children, Component, JSX, Suspense } from "react";
|
||
import { Spin } from "antd";
|
||
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
|
||
import { SettingDrawer } from "@ant-design/pro-components";
|
||
import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max";
|
||
import { history, Link, Navigate } from "@umijs/max";
|
||
import {
|
||
AvatarDropdown,
|
||
AvatarName,
|
||
Footer,
|
||
Question,
|
||
SelectLang,
|
||
} from "@/components";
|
||
import { getInfo } from "@/services/login";
|
||
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";
|
||
import { MenuVO } from "./services/system/menu";
|
||
|
||
import {
|
||
transformBackendMenuToFlatRoutes,
|
||
transformMenuToRoutes,
|
||
} from "@/utils/menuUtils";
|
||
const isDev = process.env.NODE_ENV === "development";
|
||
const isDevOrTest = isDev || process.env.CI;
|
||
const loginPath = "/user/login";
|
||
|
||
// 全局存储菜单数据和路由映射
|
||
|
||
/**
|
||
* @see https://umijs.org/docs/api/runtime-config#getinitialstate
|
||
* */
|
||
export async function getInitialState(): Promise<{
|
||
settings?: Partial<LayoutSettings>;
|
||
currentUser?: UserInfoVO;
|
||
loading?: boolean;
|
||
fetchUserInfo?: () => Promise<UserInfoVO | undefined>;
|
||
}> {
|
||
const { wsCache } = useCache();
|
||
const fetchUserInfo = async () => {
|
||
// history.push(loginPath);
|
||
try {
|
||
const token = getAccessToken();
|
||
if (!token) {
|
||
throw new Error("No token found");
|
||
}
|
||
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);
|
||
}
|
||
return undefined;
|
||
};
|
||
// 如果不是登录页面,执行
|
||
const { location } = history;
|
||
|
||
if (
|
||
![loginPath, "/user/register", "/user/register-result"].includes(
|
||
location.pathname
|
||
)
|
||
) {
|
||
const currentUser = wsCache.get(CACHE_KEY.USER);
|
||
if (getAccessToken() && !currentUser) {
|
||
fetchUserInfo();
|
||
}
|
||
|
||
return {
|
||
fetchUserInfo,
|
||
currentUser,
|
||
settings: defaultSettings as Partial<LayoutSettings>,
|
||
};
|
||
}
|
||
return {
|
||
fetchUserInfo,
|
||
settings: defaultSettings as Partial<LayoutSettings>,
|
||
};
|
||
}
|
||
|
||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||
export const layout: RunTimeLayoutConfig = ({
|
||
initialState,
|
||
setInitialState,
|
||
}) => {
|
||
return {
|
||
actionsRender: () => [
|
||
<Question key="doc" />,
|
||
<SelectLang key="SelectLang" />,
|
||
],
|
||
menu: {
|
||
locale: false,
|
||
// 关闭国际化-
|
||
},
|
||
avatarProps: {
|
||
src: initialState?.currentUser?.user.avatar,
|
||
title: <AvatarName />,
|
||
render: (_, avatarChildren) => (
|
||
<AvatarDropdown>{avatarChildren}</AvatarDropdown>
|
||
),
|
||
},
|
||
waterMarkProps: {
|
||
content: initialState?.currentUser?.user.nickname,
|
||
},
|
||
footerRender: () => <Footer />,
|
||
onPageChange: () => {
|
||
const { location } = history;
|
||
// 如果没有登录,重定向到 login
|
||
if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||
history.push(loginPath);
|
||
}
|
||
},
|
||
bgLayoutImgList: [
|
||
{
|
||
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr",
|
||
left: 85,
|
||
bottom: 100,
|
||
height: "303px",
|
||
},
|
||
{
|
||
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr",
|
||
bottom: -68,
|
||
right: -45,
|
||
height: "303px",
|
||
},
|
||
{
|
||
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr",
|
||
bottom: 0,
|
||
left: 0,
|
||
width: "331px",
|
||
},
|
||
],
|
||
menuHeaderRender: undefined,
|
||
// 自定义 403 页面
|
||
unAccessible: <div>unAccessible</div>,
|
||
// 增加一个 loading 的状态
|
||
childrenRender: (children) => {
|
||
// if (initialState?.loading) return <PageLoading />;
|
||
return (
|
||
<>
|
||
{children}
|
||
{isDevOrTest && (
|
||
<SettingDrawer
|
||
disableUrlParams
|
||
enableDarkTheme
|
||
settings={initialState?.settings}
|
||
onSettingChange={(settings) => {
|
||
setInitialState((preInitialState) => ({
|
||
...preInitialState,
|
||
settings,
|
||
}));
|
||
}}
|
||
/>
|
||
)}
|
||
</>
|
||
);
|
||
},
|
||
...initialState?.settings,
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @name request 配置,可以配置错误处理
|
||
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
|
||
* @doc https://umijs.org/docs/max/request#配置
|
||
*/
|
||
export const request: RequestConfig = {
|
||
baseURL: isDev ? "" : "https://proapi.azurewebsites.net",
|
||
...errorConfig,
|
||
// 添加请求拦截器
|
||
requestInterceptors: [
|
||
(url, options) => {
|
||
// 为所有请求添加 API 前缀
|
||
if (url && !url.startsWith(process.env.API_PREFIX || "/admin-api")) {
|
||
url = (process.env.API_PREFIX || "/admin-api") + url;
|
||
}
|
||
// 获取存储在本地的 token 和 tenantId
|
||
const token = getAccessToken();
|
||
const tenantId = getTenantId(); // 默认租户ID为1
|
||
console.log(tenantId);
|
||
// 设置统一的请求头
|
||
const headers: Record<string, string> = {
|
||
...options.headers,
|
||
Accept: "*",
|
||
"tenant-id": tenantId,
|
||
};
|
||
// 如果有token,则添加Authorization头
|
||
if (token) {
|
||
headers["Authorization"] = `Bearer ${getAccessToken()}`;
|
||
}
|
||
return { url, options: { ...options, headers } };
|
||
},
|
||
],
|
||
};
|
||
// umi 4 使用 modifyRoutes
|
||
export function patchClientRoutes({ routes }: { routes: any }) {
|
||
const { wsCache } = useCache();
|
||
const globalMenus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
||
const routerIndex = routes.findIndex((item: any) => item.path === "/");
|
||
const parentId = routes[routerIndex].id;
|
||
|
||
if (globalMenus) {
|
||
routes[routerIndex]["routes"].push(...loopMenuItem(globalMenus, parentId));
|
||
}
|
||
}
|
||
|
||
const loopMenuItem = (menus: any[], pId: number | string): any[] => {
|
||
return menus.flatMap((item) => {
|
||
let Component: React.ComponentType<any> | null = null;
|
||
if (item.component && item.component.length > 0) {
|
||
// 防止配置了路由,但本地暂未添加对应的页面,产生的错误
|
||
console.log(item.component);
|
||
Component = React.lazy(() => {
|
||
const importComponent = () => import(`@/pages/${item.component}`);
|
||
const import404 = () => import("@/pages/404");
|
||
return importComponent().catch(import404);
|
||
});
|
||
}
|
||
if (item.children && item.children.length > 0) {
|
||
return [
|
||
{
|
||
path: item.path,
|
||
name: item.name,
|
||
icon: item.icon,
|
||
id: item.id,
|
||
parentId: pId,
|
||
children: [
|
||
{
|
||
path: item.url,
|
||
element: <Navigate to={item.children[0].url} replace />,
|
||
},
|
||
...loopMenuItem(item.children, item.menuID),
|
||
],
|
||
},
|
||
];
|
||
} else {
|
||
return [
|
||
{
|
||
path: item.path,
|
||
name: item.name,
|
||
icon: item.icon,
|
||
id: item.menuID,
|
||
parentId: pId,
|
||
element: (
|
||
<React.Suspense
|
||
fallback={<Spin style={{ width: "100%", height: "100%" }} />}
|
||
>
|
||
{Component && <Component />}
|
||
</React.Suspense>
|
||
),
|
||
children: [], // 添加缺失的 children 属性
|
||
},
|
||
];
|
||
}
|
||
});
|
||
};
|