Files
tashow-manager/src/app.tsx
2025-10-16 16:32:59 +08:00

309 lines
8.9 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 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, Navigate } from '@umijs/max';
import { Spin } from 'antd';
import React from 'react';
import {
AvatarDropdown,
AvatarName,
Footer,
Question,
SelectLang,
} from '@/components';
import { getInfo } from '@/services/login';
import type { UserInfoVO } from '@/services/login/types';
import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig';
import '@ant-design/v5-patch-for-react-19';
import { getAccessToken, 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;
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();
}
console.log(111);
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
// 设置统一的请求头
const headers: Record<string, string> = {
...options.headers,
Accept: '*',
'tenant-id': tenantId,
};
// 如果有token则添加Authorization头
if (token) {
headers.Authorization = `Bearer ${getAccessToken()}`;
}
return { url, options: { ...options, headers } };
},
],
// 添加参数序列化配置
paramsSerializer: (params) => {
const searchParams = new URLSearchParams();
const appendParams = (key: string, value: any) => {
if (Array.isArray(value)) {
// 特殊处理 createTime 数组,转换为 createTime[0] 和 createTime[1] 格式
if (key === 'createTime') {
value.forEach((val, index) => {
searchParams.append(`${key}[${index}]`, val);
});
} else {
// 其他数组参数保持默认行为
value.forEach((val) => {
searchParams.append(`${key}[]`, val);
});
}
} else if (value !== null && value !== undefined) {
searchParams.append(key, String(value));
}
};
Object.keys(params).forEach((key) => {
appendParams(key, params[key]);
});
return searchParams.toString();
},
};
// 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;
// console.log(findFirstLeafRoute(item), 'item');
if (item.component && item.component.length > 0) {
// 防止配置了路由,但本地暂未添加对应的页面,产生的错误
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 {
// console.log(findFirstLeafRoute(newItem));
return [
{
path: item.path,
name: item.name,
// icon: item.icon,
id: item.menuID,
parentId: pId,
redirect: '/system/tenant/list',
element: (
<React.Suspense
fallback={<Spin style={{ width: '100%', height: '100%' }} />}
>
{Component && <Component />}
</React.Suspense>
),
children: [], // 添加缺失的 children 属性
},
];
}
});
};
const _findFirstLeafRoute = (menuItem: any, parent = '/'): string | null => {
// 如果没有子菜单,返回当前路径
if (!menuItem.children || menuItem.children.length === 0) {
return parent + menuItem.path;
}
// 递归查找第一个叶子节点
for (const child of menuItem.children) {
// const leafRoute = findFirstLeafRoute(child, menuItem.path + "/");
const leafRoute = _findFirstLeafRoute(child, `${menuItem.path}/`);
if (leafRoute) {
return leafRoute;
}
}
return null;
};