270 lines
7.7 KiB
TypeScript
270 lines
7.7 KiB
TypeScript
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 } from '@umijs/max';
|
||
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 { useDictStore } from '@/hooks/stores/dict';
|
||
import { getAccessToken, getTenantId } from '@/utils/auth';
|
||
import { CACHE_KEY, useCache } from './hooks/web/useCache';
|
||
import type { MenuVO } from './services/system/menu';
|
||
import { loopMenuItem } 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;
|
||
menus?: MenuVO[];
|
||
fetchUserInfo?: () => Promise<UserInfoVO | undefined>;
|
||
}> {
|
||
const { wsCache } = useCache();
|
||
const dictStore = useDictStore();
|
||
const fetchUserInfo = async () => {
|
||
// history.push(loginPath);
|
||
try {
|
||
const token = getAccessToken();
|
||
if (!token) {
|
||
throw new Error('No token found');
|
||
}
|
||
const data = await getInfo();
|
||
console.log(data, 'data');
|
||
wsCache.set(CACHE_KEY.USER, data);
|
||
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
|
||
|
||
if (!dictStore.getIsSetDict) {
|
||
await dictStore.setDictMap();
|
||
}
|
||
|
||
// 转换菜单格式
|
||
|
||
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) {
|
||
await fetchUserInfo();
|
||
}
|
||
const menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
||
return {
|
||
fetchUserInfo,
|
||
currentUser,
|
||
menus,
|
||
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',
|
||
},
|
||
],
|
||
// 面包屑配置
|
||
breadcrumb: {
|
||
enable: true,
|
||
useRoutes: true,
|
||
},
|
||
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 支持异步的 patchClientRoutes
|
||
export async function patchClientRoutes({ routes }: any) {
|
||
const { wsCache } = useCache();
|
||
console.log('patchClientRoutes', patchClientRoutes);
|
||
// 先尝试从缓存获取
|
||
let menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
||
|
||
// 如果缓存没有,且有 token,则获取
|
||
if (!menus && getAccessToken()) {
|
||
try {
|
||
const data = await getInfo();
|
||
wsCache.set(CACHE_KEY.USER, data);
|
||
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
|
||
menus = data.menus;
|
||
} catch (error) {
|
||
console.error('获取菜单失败:', error);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!menus || menus.length === 0) {
|
||
return;
|
||
}
|
||
const routerIndex = routes.findIndex((item: any) => item.path === '/');
|
||
const parentId = routes[routerIndex].id;
|
||
|
||
if (menus) {
|
||
const r = loopMenuItem(menus, parentId);
|
||
console.log(r, routes);
|
||
routes[routerIndex].routes.push(...r);
|
||
}
|
||
}
|