antd-pro v5版本的tab页签最简实践

本文介绍了一种基于Ant Design Pro V5版本的简易Tab页签实现方法,仅需少量代码即可完成配置。通过自定义组件`TagView`和调整`layout`配置来实现页签功能,适用于对性能要求不高的场景。

antd-pro v5版本的tab页签最简实践

前言

网上找了很多资料,也看了好些个代码,总觉得他们写的太累赘了。其实官方自己也说了,antdpro做页签和他们的产品理念不符合才不做,但是不代表他们没给我们预留修改空间,但是找了好多代码都没找到一个合适的插件。经过对layout组件的一番研究,但其实官方团队还是给我们预留了空间的,所以我也就是依据官方的预留空间做了一个最简实践,只需要写一个组件,加几行layout配置代码即可实现页签功能。

提醒

tab页签肯定是会存在性能问题的,就如同iframe一样,但是一般有这个需求的应该都不会太在乎性能问题。页签功能自行定义,我这里只是提供一个实现方案。还没有做细化功能的。并且该方案十分轻量,无任何累赘。感谢博主的文章antd pro(V5) 实现多tab,的代码给了我灵感,用了他仿element-admin的样式。

效果

在这里插入图片描述

核心的api

  • childrenRende
import { join } from 'node:path'; import { defineConfig } from '@umijs/max'; import defaultSettings from './defaultSettings'; import proxy from './proxy'; import routes from './routes'; const { REACT_APP_ENV = 'dev' } = process.env; const PUBLIC_PATH: string = '/'; export default defineConfig({ hash: true, publicPath: PUBLIC_PATH, routes, ignoreMomentLocale: true, proxy: proxy[REACT_APP_ENV as keyof typeof proxy], fastRefresh: true, model: {}, initialState: {}, title: 'Ant Design Pro', layout: { locale: false, ...defaultSettings, // ==================== 在这里添加多标配置 ==================== multiTabs: { // 这里修改为复数形式 enabled: true, // 启用多标 keepAlive: true, // 启用面缓存 multiTabMode: 'chrome', // 标样式 fixedMultiTab: true, // 固定标头 }, }, moment2dayjs: { preset: 'antd', plugins: ['duration'], }, locale: { default: 'zh-CN', antd: true, baseNavigator: true, }, antd: { appConfig: {}, configProvider: { theme: { cssVar: true, token: { fontFamily: 'AlibabaSans, sans-serif', }, }, }, }, request: {}, access: {}, headScripts: [ { src: join(PUBLIC_PATH, 'scripts/loading.js'), async: true }, ], presets: ['umi-presets-pro'], openAPI: [ { requestLibPath: "import { request } from '@umijs/max'", schemaPath: join(__dirname, 'oneapi.json'), mock: false, }, { requestLibPath: "import { request } from '@umijs/max'", schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', projectName: 'swagger', }, ], mock: { include: ['mock/**/*', 'src/pages/**/_mock.ts'], }, mako: {}, esbuildMinifyIIFE: true, requestRecord: {}, exportStatic: {}, history: { type: 'hash' }, }); import type { ProLayoutProps } from '@ant-design/pro-components'; /** * @name */ const Settings: ProLayoutProps & { pwa?: boolean; logo?: string; } = { navTheme: 'light', // 拂晓蓝 colorPrimary: '#1890ff', layout: 'mix', contentWidth: 'Fluid', fixedHeader: false, fixSiderbar: true, colorWeak: false, title: '框架', pwa: true, logo: './logo.svg', // 项目logo iconfontUrl: '', token: { // 参见ts声明,demo 见文档,通过token 修改样式 //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F }, // 侧边栏宽度 siderWidth: 220, }; export default Settings; /** * @name umi 的路由配置 * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的后。 * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 * @param redirect 配置路由跳转 * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User * @doc https://umijs.org/docs/guides/routes */ export default [ { path: '/user', layout: false, routes: [ { name: 'login', path: '/user/login', component: './user/login', }, ], }, { path: '/company', name: '组织管理', icon: 'smile', component: './company', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: '/permissions', name: '权限管理', icon: 'lock', access: 'hasRoute', // 移除冗余的 multiTab 配置 routes: [ { path: 'admin', name: '账户管理', component: './permissions/account', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: 'menu', name: '菜单管理', component: './permissions/menu', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: 'role', name: '角色管理', component: './permissions/role', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, ], }, { path: '/staff', name: '员工管理', icon: 'smile', component: './staff', access: 'hasRoute', // 移除冗余的 multiTab 配置 }, { path: '/', redirect: '/staff', }, { path: '*', layout: false, component: './404', }, ]; import { LinkOutlined, FullscreenOutlined, FullscreenExitOutlined, RedoOutlined, LoadingOutlined } from '@ant-design/icons'; 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, useLocation } from '@umijs/max'; import React, { useState, useEffect, useCallback } from 'react'; import { AvatarDropdown, AvatarName, Footer, Question, SelectLang, } from '@/components'; import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; import '@ant-design/v5-patch-for-react-19'; const isDev = process.env.NODE_ENV === 'development'; const loginPath = '/user/login'; // 全屏钩子保持不变 const useFullscreen = () => { const [isFullscreen, setIsFullscreen] = useState(false); const checkFullscreen = useCallback(() => { setIsFullscreen( !!(document as any).fullscreenElement || !!(document as any).webkitFullscreenElement || !!(document as any).mozFullScreenElement || !!(document as any).msFullscreenElement ); }, []); const toggleFullscreen = useCallback(() => { if (isFullscreen) { if ((document as any).exitFullscreen) (document as any).exitFullscreen(); else if ((document as any).webkitExitFullscreen) (document as any).webkitExitFullscreen(); else if ((document as any).mozCancelFullScreen) (document as any).mozCancelFullScreen(); else if ((document as any).msExitFullscreen) (document as any).msExitFullscreen(); } else { const docEl = document.documentElement; if ((docEl as any).requestFullscreen) (docEl as any).requestFullscreen(); else if ((docEl as any).webkitRequestFullscreen) (docEl as any).webkitRequestFullscreen(); else if ((docEl as any).mozRequestFullScreen) (docEl as any).mozRequestFullScreen(); else if ((docEl as any).msRequestFullscreen) (docEl as any).msRequestFullscreen(); } }, [isFullscreen]); useEffect(() => { const fullscreenChangeHandler = () => checkFullscreen(); document.addEventListener('fullscreenchange', fullscreenChangeHandler); document.addEventListener('webkitfullscreenchange', fullscreenChangeHandler); document.addEventListener('mozfullscreenchange', fullscreenChangeHandler); document.addEventListener('MSFullscreenChange', fullscreenChangeHandler); return () => { document.removeEventListener('fullscreenchange', fullscreenChangeHandler); document.removeEventListener('webkitfullscreenchange', fullscreenChangeHandler); document.removeEventListener('mozfullscreenchange', fullscreenChangeHandler); document.removeEventListener('MSFullscreenChange', fullscreenChangeHandler); }; }, [checkFullscreen]); return { isFullscreen, toggleFullscreen }; }; export async function getInitialState(): Promise<{ settings?: Partial<LayoutSettings>; currentUser?: API.CurrentUser; loading?: boolean; fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; }> { const fetchUserInfo = async () => { try { const msg = await queryCurrentUser({ skipErrorHandler: true, }); return msg.data; } catch (_error) { history.push(loginPath); } return undefined; }; const { location } = history; if ( ![loginPath, '/user/register', '/user/register-result'].includes( location.pathname, ) ) { const currentUser = await fetchUserInfo(); return { fetchUserInfo, currentUser, settings: defaultSettings as Partial<LayoutSettings>, }; } return { fetchUserInfo, settings: defaultSettings as Partial<LayoutSettings>, }; } export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState, }) => { const { isFullscreen, toggleFullscreen } = useFullscreen(); const [routeKey, setRouteKey] = useState(Date.now()); const [isRefreshing, setIsRefreshing] = useState(false); const location = useLocation(); // 刷新方法 const refreshPage = useCallback(() => { setIsRefreshing(true); setRouteKey(Date.now()); setTimeout(() => setIsRefreshing(false), 300); }, []); return { // 全局开启多标功能(复数形式,与ProLayout属性一致) multiTabs: true, actionsRender: () => [ // 全屏按钮 isFullscreen ? ( <FullscreenExitOutlined key="exit-fullscreen" onClick={toggleFullscreen} title="退出全屏" style={{ cursor: 'pointer', marginRight: 8 }} /> ) : ( <FullscreenOutlined key="fullscreen" onClick={toggleFullscreen} title="全屏显示" style={{ cursor: 'pointer', marginRight: 8 }} /> ), // 刷新按钮 isRefreshing ? ( <LoadingOutlined key="refresh-loading" spin style={{ fontSize: '16px', cursor: 'pointer', marginRight: 8 }} title="刷新中..." onClick={refreshPage} /> ) : ( <RedoOutlined key="refresh" style={{ fontSize: '16px', cursor: 'pointer', marginRight: 8 }} title="刷新面" onClick={refreshPage} /> ), ], avatarProps: { src: initialState?.currentUser?.avatar, title: <AvatarName />, render: (_, avatarChildren) => { return <AvatarDropdown>{avatarChildren}</AvatarDropdown>; }, }, footerRender: () => <Footer />, onPageChange: () => { const { location } = history; if (!initialState?.currentUser && location.pathname !== loginPath) { history.push(loginPath); } }, menuHeaderRender: undefined, childrenRender: (children) => { return ( <div key={routeKey}> {children} </div> ); }, ...initialState?.settings, }; }; export const request: RequestConfig = { ...errorConfig };怎么修复多标
最新发布
11-06
评论 23
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值