/** * KRA - App Configuration * 应用运行时配置 * 参考 GVA 的 permission.js 和 router store 实现动态路由 */ import { AvatarDropdown, AvatarName, Footer } from '@/components'; import CommandMenu from '@/components/CommandMenu'; import { getUserInfo } from '@/services/kratos/user'; import { asyncMenu } from '@/services/kratos/menu'; import { getToken } from '@/utils/auth'; import { asyncRouterHandle, BackendMenuItem } from '@/utils/dynamicRouter'; import { BulbOutlined, BulbFilled, ReloadOutlined, SettingOutlined, UserOutlined, QuestionCircleOutlined, SearchOutlined, } from '@ant-design/icons'; import * as Icons from '@ant-design/icons'; import type { Settings as LayoutSettings, MenuDataItem } 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 { Tooltip } from 'antd'; import React, { useState } from 'react'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; const isDev = process.env.NODE_ENV === 'development'; const loginPath = '/user/login'; const initPath = '/init'; const whiteList = [loginPath, initPath, '/error/403', '/error/404', '/error/500', '/scanUpload']; // 图标映射函数 const iconMap: Record = {}; Object.keys(Icons).forEach((key) => { if (key.endsWith('Outlined') || key.endsWith('Filled') || key.endsWith('TwoTone')) { const IconComponent = (Icons as any)[key]; iconMap[key.toLowerCase().replace(/(outlined|filled|twotone)$/, '')] = ; } }); // 自定义图标映射(GVA 使用的图标名称) const customIconMap: Record = { 'odometer': , 'user': , 'avatar': , 'tickets': , 'platform': , 'coordinate': , 'notebook': , 'pie-chart': , 'compass': , 'tools': , 'cpu': , 'magic-stick': , 'operation': , 'folder': , 'reading': , 'picture-filled': , 'magnet': , 'partly-cloudy': , 'server': , 'warn': , 'cherry': , 'shop': , 'box': , 'files': , 'message': , 'scaleToOriginal': , 'upload': , 'upload-filled': , 'management': , 'info-filled': , 'cloudy': , 'setting': , 'customer-gva': , }; function getIcon(iconName?: string): React.ReactNode { if (!iconName) return undefined; // 先查自定义映射 if (customIconMap[iconName]) return customIconMap[iconName]; // 再查 antd 图标 const key = iconName.toLowerCase().replace(/-/g, ''); if (iconMap[key]) return iconMap[key]; return undefined; } /** * 将后端菜单数据转换为 ProLayout 菜单格式 */ function convertToMenuData(menus: BackendMenuItem[], parentPath: string = ''): MenuDataItem[] { return menus.map((menu) => { const path = menu.path.startsWith('/') || menu.path.startsWith('http') ? menu.path : `${parentPath}/${menu.path}`.replace(/\/+/g, '/'); const menuItem: MenuDataItem = { path, name: menu.meta?.title || menu.name, icon: getIcon(menu.meta?.icon), hideInMenu: menu.hidden, }; if (menu.children && menu.children.length > 0) { menuItem.children = convertToMenuData(menu.children, path); } return menuItem; }); } /** * 获取初始状态 * @see https://umijs.org/docs/api/runtime-config#getinitialstate */ export async function getInitialState(): Promise<{ settings?: Partial; currentUser?: API.CurrentUser; loading?: boolean; menus?: BackendMenuItem[]; menuData?: MenuDataItem[]; fetchUserInfo?: () => Promise; fetchMenus?: () => Promise; }> { // 获取用户信息 const fetchUserInfo = async (): Promise => { try { const token = getToken(); if (!token) { return undefined; } const res = await getUserInfo(); if (res.code === 0 && res.data?.userInfo) { const userInfo = res.data.userInfo; return { uuid: userInfo.uuid, userName: userInfo.userName, nickName: userInfo.nickName, name: userInfo.nickName || userInfo.userName, avatar: userInfo.headerImg, headerImg: userInfo.headerImg, authority: userInfo.authority, authorities: userInfo.authorities, buttons: res.data.buttons || [], }; } return undefined; } catch (error) { console.error('获取用户信息失败:', error); return undefined; } }; // 获取动态菜单 - 对应 GVA 的 SetAsyncRouter const fetchMenus = async (): Promise => { try { const token = getToken(); if (!token) { return []; } const res = await asyncMenu(); if (res.code === 0 && res.data?.menus) { return res.data.menus; } return []; } catch (error) { console.error('获取菜单失败:', error); return []; } }; // 如果是白名单页面,不获取用户信息和菜单 const { location } = history; if (whiteList.some((path) => location.pathname.startsWith(path))) { return { fetchUserInfo, fetchMenus, settings: defaultSettings as Partial, }; } // 获取用户信息和菜单 const currentUser = await fetchUserInfo(); if (!currentUser) { history.push(loginPath); return { fetchUserInfo, fetchMenus, settings: defaultSettings as Partial, }; } // 获取动态菜单 const menus = await fetchMenus(); const menuData = convertToMenuData(menus); return { fetchUserInfo, fetchMenus, currentUser, menus, menuData, settings: defaultSettings as Partial, }; } /** * ProLayout 配置 * @see https://procomponents.ant.design/components/layout */ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => { const handleRefresh = () => { window.location.reload(); }; const handleToggleTheme = () => { const newNavTheme = initialState?.settings?.navTheme === 'realDark' ? 'light' : 'realDark'; setInitialState((preInitialState) => ({ ...preInitialState, settings: { ...preInitialState?.settings, navTheme: newNavTheme, }, })); }; const handleOpenSetting = () => { const settingBtn = document.querySelector('.ant-pro-setting-drawer-handle'); if (settingBtn) { (settingBtn as HTMLElement).click(); } }; const iconStyle: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: '50%', border: '1px solid #d9d9d9', cursor: 'pointer', }; return { ...initialState?.settings, // 使用后端返回的菜单数据 menu: { locale: false, // 禁用国际化 request: async () => { // 返回已获取的菜单数据 return initialState?.menuData || []; }, }, actionsRender: () => { const isDarkTheme = initialState?.settings?.navTheme === 'realDark'; return [ , window.dispatchEvent(new CustomEvent('openCommandMenu'))} style={iconStyle} > , , , {isDarkTheme ? ( ) : ( )} , ]; }, avatarProps: { src: initialState?.currentUser?.avatar || initialState?.currentUser?.headerImg || '/default-avatar.svg', title: , icon: , render: (_, avatarChildren) => {avatarChildren}, }, waterMarkProps: { content: initialState?.currentUser?.nickName || initialState?.currentUser?.userName, }, footerRender: () =>