antd pro v5 tab标签卡(多标签页)实现

本文档详细介绍了如何在Ant Design Pro项目中实现多标签页功能,包括核心原理、功能实现和代码示例。作者通过自定义Tabs组件和利用dva进行数据管理,解决了官方不支持多标签页的问题,实现了菜单标签与路由地址联动、内容缓存以及后台返回路由支持等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多标签页很多公司的后台管理系统都会有这个需求,之前用vue一般架子也是带的,现在公司用了antd pro ,看了下官方不支持,确实会影响性能,但是架不住需求。

补充: antdpro 已经内置了多页签,可以自己配置看看。这篇文章留着学习。

背景


先看下远古截图:

在这里插入图片描述

https://github.com/ant-design/ant-design-pro/issues/220

17年提出的需求现在还没有实现,看样官方也是铁了心了。在看看提供的其他解决方案:

另外我还找到了一个插件也可以实现:

https://github.com/fangzhengjin/umi-plugin-panel-tabs

这些不是因为这版本不对,就是细节不满意决定自己实现一个版本。

自己实现的好处:

  • UI自定义
  • 功能添加方便,知道核心原理,修改Bug也方便。

 

核心原理


先看最后实现的版本:

请添加图片描述

核心问题:

  1. 菜单标签路由地址联动
  2. 标签卡内容需要缓存,切换不丢失
  3. 后台返回路由也应该支持

 

功能实现


核心实现思路:

  1. 通过地址栏变化匹配路由变化标签栏
  2. 标签卡选用Tabs组件+ Route 标签加key缓存
  3. dva来实现数据管理,也可以选用别的,能全局操作即可。

具体逻辑就是,写一个TabsView 组件,在Layout chlidren的时候嵌套上Tabs 多页签卡这一层。

Layout 文件夹Index.ts 文件:

<TabsView
            activeKey={getActiveKey(props.tagsModel)}
            tags={props.tagsModel}
            route={props.route}
            dispatch={props.dispatch}
          />

1、 数据实现组织

利用dva 来实现tags 数组的增删改查,具体代码如下:

/*
 * @Author: ZY
 * @Date: 2021-10-25 13:42:43
 * @LastEditors: ZY
 * @LastEditTime: 2022-05-01 10:18:48
 * @Description: dva tags
 * tabs 整理设计思路:
 * 需求:两种逻辑,一种是菜单功能,功能、路由、tag是一对一的关系,另一种单据类可以开多个
 * 设计:
 * 利用tabs 进行页面布局,来实现缓存的目的。key是path和query的合集,这样能满足需求
 * 利用dva组织数据
 * 动态加载组件,组件利用key关联
 */
import type { Reducer } from 'umi';
import _remove from 'lodash/remove';
import _cloneDeep from 'lodash/cloneDeep';
import _findIndex from 'lodash/findIndex';

export interface Tag {
  key: string;
  title: string;
  path?: string;
  active: boolean;
  query?: any;
}

export type TagsStateType = Tag[];

export interface TagsModelType {
  namespace: 'tagsModel';
  state: TagsStateType;
  reducers: {
    addTag: Reducer<TagsStateType>;
    updateActive: Reducer<TagsStateType>;
    removeTag: Reducer<TagsStateType>;
    removeAllTags: Reducer<TagsStateType>;
  };
}

/**
 * @description: 初始化tab
 * @param {*}
 * @return {*}
 */
const homeTag: Tag = {
  key: `/dashboard`,
  title: '首页',
  active: true,
  path: '/dashboard',
};

/**
 * @description:
 * @param {*}
 * @return {*}
 */
const addNewTag = (tags: Tag[], newTag: Tag) => {
  if (_findIndex(tags, (t) => t.key === newTag.key) === -1) {
    // tags数组里面有没有新增的tag
    const cTags = tags.map((t) => {
      const ct = _cloneDeep(t);
      ct.active = false;
      return ct;
    });
    return [...cTags, newTag];
  }

  // 新增tag 在数组中,选中即可。
  const cTags = tags.map((t) => {
    const ct = _cloneDeep(t);
    ct.active = ct.key === newTag.key;
    return ct;
  });
  return cTags;
};

const TagsModel: TagsModelType = {
  namespace: 'tagsModel',
  state: [homeTag] as TagsStateType,
  reducers: {
    addTag: (state, action) => {
      if (state) {
        return addNewTag(state, action.payload);
      }
      return [];
    },
    updateActive: (state, action) => {
      if (!state) {
        return [];
      }
      const cTags = state.map((t) => {
        const ct = _cloneDeep(t);
        ct.active = ct.key === action.payload;
        return ct;
      });
      return [...cTags];
    },
    removeTag: (state, action) => {
      if (!state) {
        return [];
      }
      const ct = _cloneDeep(state);
      if (ct.filter((t) => t.active)[0].key === action.payload) {
        _remove(ct, (tag: Tag) => tag.key === action.payload);
        // 如果关闭的是当前选中的标签,默认选中最后一个的策略
        ct[ct.length - 1].active = true;
      } else {
        _remove(ct, (tag: Tag) => tag.key === action.payload);
      }
      return [...ct];
    },
    removeAllTags: () => {
      return [];
    },
  },
};

export default TagsModel;

2、 tabsView 组件核心代码

这里会遍历tags 数组,然后创建tab, 每一个tab 都用Route 标签缓存,通过路径匹配的组件。

 <Tabs
        activeKey={activeKey}
        type="editable-card"
        hideAdd={true}
        onEdit={tabOnEdit}
        onChange={tabOnChange}
        onTabClick={onTabClick}
      >
        {tags.length &&
          tags.map((tag) => {
            return (
              <TabPane tab={tag.title} key={tag.key} closable={tag.key !== '/dashboard'}>
                <Route key={tag.key} component={getPathComponent(tag.path!)} exact />
              </TabPane>
            );
          })}
      </Tabs>

通过路径找到组件

 const getPathComponent = (path: string) => {
    const r = route.routes?.filter((t) => {
      // 如果是动态路由,匹配非动态部分
      if (t.path?.includes(':')) {
        return path.includes(t.path.split(':')[0]);
      }
      return t.path === path;
    })[0];

    return (r as any).component;
  };

这样核心内容基本讲完,边缘代码就不多赘述了,代码再项目里还没来得及抽取,如有需要以后找时间发出。

应同学们要求匆忙抽取了这部分代码demo ,地址如下:

https://github.com/RainManGO/antd-pro-v5-tabs

Ant DesignTab组件中,切换标签页时遇到布局问题通常是由于内容管理策略的不同所引起的。当你提到之前的内容仍然占用文档流位置,新的内容会被推到下方,这是因为默认情况下,当使用`visibility`属性隐藏内容时,元素仍保留其原始大小占据空间。 要解决这个问题,你应该将隐藏内容的方式从`visibility`更改为`display`,这样可以移除元素而不影响文档流。下面是可能的解决方案: 1. 修改CSS样式[^1]: ```css .tab-content { display: flex; flex-direction: column; // 这里将方向设为垂直,以避免内容重叠 } .tab-hidden { display: none; // 替换为 display: none; } ``` 当你切换到一个新的tab时,只改变对应的`.tab-hidden`类的`display`属性,而不是整个页面。 2. 更新组件状态与生命周期方法[^2]: 在`Tabs`组件的`onChange`事件中,除了更新`tabActiveKey`,还要控制子组件的可见性。例如,你可以这样做: ```jsx <Tabs onChange={activeKey => this.setState({ tabActiveKey })}> {this.state.collectionType.map((item, index) => ( <TabPane key={index} tab={item.title} visible={activeKey === item.key}> {/* 子组件 */} </TabPane> ))} </Tabs> // 在组件内部,比如 componentDidUpdate 或者 useEffect 中 useEffect(() => { const currentKey = this.props.tabActiveKey || ''; this.updateChildVisibility(currentKey); }, [this.props.tabActiveKey]); updateChildVisibility(key) { this.setState(prevState => ({ collectionType: prevState.collectionType.map(item => item.key === key ? { ...item, visible: true } : { ...item, visible: false } ) })); } ``` 这将确保每个TabPane只在其对应的标签激活时显示,从而解决了布局问题。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星宇大前端

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值