Files
tashow-manager/src/app.tsx
2025-09-12 15:37:07 +08:00

263 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 属性
},
];
}
});
};