ant design pro多页签功能

news2024/9/17 3:41:04

效果:

原理:
1、所有需要页签页面,都需要一个共同父组件

2、如何缓存,用的是ant的Tabs组件,在共同父组件中,实际是展示的Tabs组件

3、右键,用的是ant的Dropdown组件,当点击时,记录所对应的key值及坐标,做后续操作

代码:

1、路由处理,需要用一个共同的父组件

在ant design pro4中,不支持隐藏父路由,展示子路由的功能,需要在app.tsx文件中,对路由做额外处理

共同父组件,BaseLayout.tsx文件,记录所有路由组件,在tab中展示。在不同版本的ant design pro中,props提供的路由组件的值会有差别

import {useEffect, useRef, useState} from "react";
import {Tabs} from "antd";
import { history } from 'umi';
import RightMenu from "@/components/RightMenu";
import './baseLayout.less'

const BaseLayout = (props: any) => {
  const [activeTab, setActiveTab] = useState<any>('');
  const [nodeKey, setNodeKey] = useState('')
  const [tabItems, setTabItems] = useState<{
    name: string,
    pathname: string
  }[]>([]);

  const pathname = props.location.pathname
  const refPathObj = useRef<any>({})
  const refRightMenu = useRef<any>(null)
  const refLastPath = useRef<any>('')

  const setPathObj = (list: any[]) => {
    list.forEach((v: any) => {
      if (v.routes) {
        setPathObj(v.routes)
      } else if (!v.redirect) {
        const C = v.component
        refPathObj.current[v.path] = {
          name: v.name,
          component: <C />
        }
      }
    })
  }

  // 获取默认路由
  useEffect(() => {
    const routes = props.route.routes || []
    setPathObj(routes)
  }, []);

  // 更新tab列表
  useEffect(() => {
    const currtabItem = {
      name: refPathObj.current?.[pathname]?.['name'],
      pathname,
    };
    const isReplace = props.history.action === "REPLACE"
    const lastPath = refLastPath.current
    if (pathname !== '/') {
      setTabItems((prev) => {
        let next = prev.find((item) => item.pathname === pathname)
          ? prev
          : [...prev, currtabItem];
        // 如果是replace,则隐藏上一个
        if (isReplace) {
          next = next.filter((v) => v.pathname !== lastPath)
        }
        return next.slice(-5)
      });
      setActiveTab(pathname)
    } else {
      history.push('/orderManage')
    }
    refLastPath.current = pathname
  }, [pathname]);

  const clickMouseRight = (e: any) => {
    const $target = e.target
    const left = e.clientX
    const top = e.clientY
    const getNodeKey: any = (el: any) => {
      const nKey = el.getAttribute('data-node-key')
      if (nKey) {
        return nKey
      }
      return getNodeKey(el.parentNode)
    }
    const nKey = getNodeKey($target)
    setNodeKey(nKey)
    refRightMenu.current.setShow(true)
    refRightMenu.current.setStyle({left, top})
  }

  // 右击事件
  useEffect(() => {
    const right: any = document.querySelector('.base-layout-tab-menu');
    if (!right) return () => {}
    const $tabBox = right.children[0].children[0].children[0]
    $tabBox!.oncontextmenu = function(e: any){
      e.preventDefault();
      clickMouseRight(e)
    };
    return () => {
      $tabBox!.oncontextmenu = null
    }
  }, []);

  const removeTab = (targetKey: any) => {
    const next = tabItems.filter((v) => {
      return v.pathname !== targetKey
    })
    setTabItems(next)
    if (activeTab === targetKey) {
      history.push(next[next.length-1].pathname)
    }
  };

  const clickRightMenu: any = (p: any) => {
    const key = p.key
    switch (key) {
      case 'current':
        removeTab(nodeKey)
        break
      case 'other':
        setTabItems(prev => {
          const next = prev.filter((v) => {
            return v.pathname === nodeKey
          })
          history.push(nodeKey)
          return next
        })
        break
    }
    refRightMenu.current.setShow(false)
  }

  const rightMenuItem = [
    {
      key: 'current',
      label: '关闭',
      disabled: tabItems.length <= 1,
      onClick: clickRightMenu
    },
    {
      key: 'other',
      label: '关闭其它',
      disabled: tabItems.length <= 1,
      onClick: clickRightMenu
    }
  ]

  return <>
    <Tabs
      className={'base-layout-tab-menu'}
      type="editable-card"
      hideAdd
      onChange={(activeKey) => {
        history.push(activeKey)
        setActiveTab(activeKey)
      }}
      activeKey={activeTab}
      onEdit={removeTab}
    >
      {tabItems.length > 0 &&
        tabItems.map((tabItem) => {
          return (
            <Tabs.TabPane
              tab={tabItem.name}
              key={tabItem.pathname}
              closable={tabItems.length > 1}
            >
              {/* 替换原来直接输出的 children */}
              {refPathObj.current[tabItem.pathname]['component']}
            </Tabs.TabPane>
          );
        })}
    </Tabs>
    {/*{ props.children }*/}
    <RightMenu
      ref={refRightMenu}
      items={rightMenuItem}
    />
  </>
}

BaseLayout.displayName = 'BaseLayout'
export default BaseLayout

自定义右键操作,RightMenu.tsx文件

import React, {FC, useEffect, useImperativeHandle, useState} from "react";
import {Dropdown} from "antd";
import './index.less'

interface IProps {
  ref: any
  items: {
    key: string,
    label: string,
    onClick: any
  }[]
}

const RightMenu: FC<IProps> = React.forwardRef((props, ref) => {
  const [visible, setVisible] = useState(false)
  const [sty, setSty] = useState<{
    left: number,
    top: number
  }>({left: 0, top: 0})

  const {items} = props

  useEffect(() => {
    const fn = function () {
      setVisible(false)
    }
    document.addEventListener('click', fn)
    return () => {
      document.removeEventListener('click', fn)
    }
  }, []);

  const setShow = (b: boolean) => {
    setVisible(b)
  }

  const setStyle = (s: any) => {
    setSty(s)
  }

  useImperativeHandle(ref, () => ({
    setShow,
    setStyle
  }))

  return <>
    <Dropdown
      menu={{ items }}
      open={visible}
    >
      <span
        className={'right-menu-holder'}
        style={{
          ...sty,
          display: visible ? 'block' : 'none',
        }}
      >&nbsp;</span>
    </Dropdown>
  </>
})

RightMenu.displayName = 'RightMenu'
export default RightMenu

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1919069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot新手快速入门系列教程十:基于docker容器,部署一个简单的项目

前述&#xff1a; 本篇教程将略过很多docker下载环境配置的基础步骤&#xff0c;如果您对docker不太熟悉请参考我的上一个教程&#xff1a;SpringBoot新手快速入门系列教程九&#xff1a;基于docker容器&#xff0c;部署一个简单的项目 使用 Docker Compose 支持部署 Docker 项…

MySQL某个字段按指定值排序,其他值按创建时间排序

项目场景&#xff1a; MySQL某个字段按指定值排序&#xff0c;其他值按创建时间排序&#xff0c;我们需要用到FIELD() 函数&#xff0c;它是一种对查询结果排序的方法&#xff0c;可以根据指定的字段值顺序进行排序。 order by FIELD() 函数的语法如下&#xff1a; ORDER BY …

[GHCTF 2024 新生赛]ezzz_unserialize

源码&#xff1a; <?php /*** Author: hey* message: Patience is the key in life,I think youll be able to find vulnerabilities in code audits.* Have fun and Good luck!!!*/ error_reporting(0); class Sakura{public $apple;public $strawberry;public function …

LiteOS增加执行自定义源码

开发过程注意事项&#xff1a; 源码工程路径不能太长 源码工程路径不能有中文 一定要关闭360等杀毒软件&#xff0c;否则编译的打包阶段会出错 增加自定义源码的步骤: 1.创建源码目录 2. 创建源文件 新建myhello目录后&#xff0c;再此目录下再新建源文件myhello_demo.c 3. 编…

GitHub Codespace从入门到放弃

洞悉技术的本质&#xff0c;享受科技的乐趣 背景 我在使用腾讯云主机打开使用github使用免费云空间编译代码。 遇到问题 在打开过程中 提升如下错误 你遇到的问题 别人也可能遇到 https://github.com/orgs/community/discussions/109419 我做了什么 阅读官方文档 https://docs.…

集群管理脚本

虚拟机集群管理脚本 文章目录 虚拟机集群管理脚本一、远程调用脚本(remote_call.sh)二、远程复制目录脚本(remote_copy.sh) 一、远程调用脚本(remote_call.sh) 如果有传命令参数&#xff0c;则执行该命令&#xff1b;如果没有传命令参数&#xff0c;则不执行。 #!/bin/bashcm…

用python识别二维码(python实例二十三)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.识别二维码 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&…

获取时间的函数

#include <func.h>int main(){time_t mainNow;time(&mainNow);printf("main time %s\n",ctime(&mainNow));return 0;} 结果如下&#xff1a; #include <stdio.h> #include <time.h> int main() {time_t mainNow;struct tm *tm_info;ch…

Windows环境+C#实现显示接口测试

代码如下&#xff1a; using Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Design; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; …

ThreadLocal与ThreadLocalMap

参考&#xff1a;理清ThreadLocal、ThreadLocalMap、Thread之间的关系 - 翎野君 - 博客园 (cnblogs.com) ThreadLocalMap 是 ThreadLocal 类中的一个静态内部类&#xff0c;但它存在于每个线程的 Thread 对象内部&#xff0c;而不是 ThreadLocal 实例本身。 ThreadLocal 类&am…

怎么压缩视频?推荐7款必备视频压缩软件免费版(强烈建议收藏)

如今&#xff0c;视频内容日益丰富&#xff0c;并占据了许多人的日常娱乐和工作生活。然而&#xff0c;随着高清和超高清视频的普及&#xff0c;视频文件的体积也越来越大&#xff0c;给存储和传输带来了挑战。因此&#xff0c;学会如何压缩视频文件成为了许多人的需求之一。本…

Spring Web MVC入门(1)(建立连接)

一.什么是Spring Web MVC? Spring Web MVC是基于ServletAPI构建的原始Web框架,从一开始就包含在Spring框架中.它的正式名称"Spring Web MVC"来自其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC". 二.MVC的定义 MVC是Model View Controller的缩…

[终端安全]-7 后量子密码算法

本文参考资料来源&#xff1a;NSA Releases Future Quantum-Resistant (QR) Algorithm Requirements for National Security Systems > National Security Agency/Central Security Service > Article Commercial National Security Algorithm Suite 2.0” (CNSA 2.0) C…

高性价比之战,希喂、霍尼韦尔、安德迈宠物空气净化器真实PK

在拥有孕妇与小孩的家庭中饲养宠物&#xff0c;营造一个既温馨又健康的居家环境显得尤为重要。尽管日常的打扫与清洁工作已做得相当到位&#xff0c;但空气中仍难免悬浮着细微的宠物浮毛与不易察觉的异味&#xff0c;这些长期累积下来&#xff0c;极易成为细菌滋生的温床&#…

双一流高校某教学系统存在多个高危漏洞

脆弱资产搜集 信息搜集过程中&#xff0c;除了用常见子域名扫一遍&#xff0c;还可以通过空间搜索引擎手动搜索。我用的就是把学校名称或者缩写作为关键字&#xff0c;利用语法: web.body“关键字”&&web.body“系统” web.body“关键字”&&web.body“登录”…

05:定时器中断

中断 1、定时器T0中断2、案例&#xff1a;通过定时器T0中断来实现灯间隔1s亮灭 1、当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求&#xff0c;要求CPU暂停当前的工作&#xff0c;转而去处理这个紧急事件&#xff0c;处理完以后&#xff0c;再回到原来被中断的地方…

基于红黑树对map和set的封装

前言 前面我们已经对红黑树做了介绍和实现&#xff0c;本期我们来对红黑树进一步改造&#xff0c;然后基于改造后的红黑树封装出map和set&#xff01; 本期内容介绍 • 红黑树的改造 • 红黑树的迭代器实现 • map的封装 • set的封装 • 全部源码 ● 红黑树的改造 我们目前…

小程序项目记录

写小程序遇到的问题&#xff1a; 1、如何发行小程序 第一步点击“发行” 然后选择“小程序-微信(仅适用于uni-app)” 然后会弹出一个这样的框 微信小程序名称和AppId会自动带入 然后控制台会出现这些信息 注意&#xff1a;生成的这个路径build为线上环境部署所使用路径&…

Hydra-MDP: 端到端多模态规划与多目标 Hydra 蒸馏

Hydra-MDP: End-to-end Multimodal Planning with Multi-target Hydra-Distillation Hydra-MDP: 端到端多模态规划与多目标 Hydra 蒸馏 Abstract We propose Hydra-MDP, a novel paradigm employing multiple teachers in a teacher-student model. This approach uses know…

音质强者悠律Ringbuds pro,时尚与音质并存

首次将手机使用的“素皮”材料用在充电仓的设计上&#xff0c;不仅手感好&#xff0c;质感直接拉满&#xff0c;而这样的工业设计不仅获得了很多消费者的喜爱&#xff0c;同时也荣获了世界知名的红点设计大奖&#xff0c;此奖项并一直被冠以“国际工业设计的奥斯卡”之称。可见…