ant design pro多页签功能

效果:

原理:
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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

【JavaEE】网络编程——UDP

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 1.数据报套接字(UDP)1.1特点1.2编码1.2.1DatagramSocket1.2.2DatagramPacket…

汽车预约维修小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;技师管理&#xff0c;技师信息管理&#xff0c;用户预约管理&#xff0c;取消预约管理&#xff0c;订单信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;技师信息&a…

接上一回C++:补继承漏洞+多态原理(带图详解)

引子&#xff1a;接上一回我们讲了继承的分类与六大默认函数&#xff0c;其实继承中的菱形继承是有一个大坑的&#xff0c;我们也要进入多态的学习了。 注意&#xff1a;我学会了&#xff0c;但是讲述上可能有一些不足&#xff0c;希望大家多多包涵 继承复习&#xff1a; 1&…

并查集+链表,CF 1131F - Asya And Kittens

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1131F - Asya And Kittens 二、解题报告 1、思路分析 本质是拼积木游戏 初始有n块积木&#xff0c;每次两块首尾拼成一块就行&#xff0c;拼接n - 1 次最后会得到一个大积木&#xff0c;我们从左往右输出组…

Nuxt3封装网络请求 useFetch $fetch

前言&#xff1a; 刚接触、搭建Nuxt3项目的过程还是有点懵的&#xff0c;有种摸石头过河的感觉&#xff0c;对于网络请求这块&#xff0c;与之前的Vue3项目有所区别&#xff0c;在Vue项目通常使用axios这个库进行网络请求&#xff0c;但在Nuxt项目并不推荐&#xff0c;因为有内…

【PostgreSQL】Spring boot + Mybatis-plus + PostgreSQL 处理json类型情况

Spring boot Mybatis-plus PostgreSQL 处理json类型情况 一、前言二、技术栈三、背景分析四、方案分析4.1 在PostgreSQL 数据库中直接存储 json 对象4.2 在PostgreSQL 数据库中存储 json 字符串 五、自定义类型处理器5.1 定义类型处理器5.2 使用自定义类型处理器 一、前言 在…

【PowerShell】-1-快速熟悉并使用PowerShell

目录 PowerShell是什么&#xff1f;和CMD的区别&#xff1f; PowerShell的演变 自动化IT管理任务 一些名词 详尽的PowerShell开始之路 1.打开PowerShell&#xff1a; 2.基本命令&#xff1a; &#xff08;1&#xff09;Get-Process &#xff08;2&#xff09;变量赋值…

React Hooks学习笔记

一、usestate的使用方法-初始化state函数 import React, { useState } from "react"; function App() {const [count, setCount] useState(0);return (<div><p>点击{count}次</p><button onClick{() > setCount(count 1)}>点击</bu…

【Linux系统】信号量(初次理解)

五个概念 多个执行流&#xff08;进程&#xff09;&#xff0c;能看到的一份资源&#xff1a;共享资源被保护起来的资源可以叫临界资源&#xff08;同步和互斥&#xff09; --- 用互斥的方式保护共享资源就叫临界资源互斥&#xff1a;任何时刻只能有一个进程在访问共享资源资源…

就业平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;企业管理&#xff0c;企业类型管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;招聘信息&#xff0c;简历&#xff0c;我的 …

SpringCloud--Eureka集群

Eureka注册中心集群 为什么要集群 如果只有一个注册中心服务器&#xff0c;会存在单点故障&#xff0c;不可以高并发处理所以要集群。 如何集群 准备三个EurekaServer 相互注册&#xff0c;也就是说每个EurekaServer都需要向所有的EureakServer注册&#xff0c;包括自己 &a…

游戏如何应对黑灰产工作室

游戏黑灰产工作室&#xff0c;是指以非法渠道、非法手段通过游戏进行牟利的团伙。使用脚本、外挂是黑灰产工作室的显著特征&#xff0c;其常见的牟利方式有&#xff1a;打金工作室、资源囤积号、初始号、自抽号、代练工作室以及营销欺诈等。 ▲ 常见的游戏黑灰产工作室牟利路径…

PMP证书 怎么报名?

首先&#xff0c;PMP证书相当于某些行业的敲门砖&#xff0c;身处项目管理相关行业的人应该清楚&#xff0c;这个证书&#xff0c;可能是你升职的不可或缺的一把钥匙。首先&#xff0c;我们先来了解一下什么是pmp。 1充分了解PMP PMP&#xff08;项目管理专业人士资格认证&am…

idm站点抓取可以用来做什么 idm站点抓取能抓取本地网页吗 idm站点抓取怎么用 网络下载加速器

在下载工具众多且竞争激烈的市场中&#xff0c;Internet Download Manager&#xff08;简称IDM&#xff09;作为一款专业的下载加速软件&#xff0c;仍然能够赢得众多用户的青睐&#xff0c;这都要得益于它的强大的下载功能。我们在开始使用IDM的时候总是有很多疑问&#xff0c…

「iOS」暑假第一周 —— ZARA的仿写

暑假第一周 ZARA的仿写 文章目录 暑假第一周 ZARA的仿写写在前面viewDidLoad 之中的优先级添加自定义字体下载想要的字体添加至info之中找到字体名字并应用 添加应用图标和启动页面 写在前面 暑假第一周留校学习&#xff0c;对于ZARA进行了仿写&#xff0c;在仿写的过程之中&a…

探索创意无限:独特的平面设计趋势与案例分享

随着平面设计领域的不断发展&#xff0c;平面设计趋势也在不断变化。在一个信息爆炸的时代&#xff0c;设计不仅仅是视觉的表达&#xff0c;更是思想和情感的交流。到了 2024 年&#xff0c;一些新的平面设计趋势已经开始显现&#xff0c;同时一些旧的趋势得到了新的发展和再度…

Jenkins安装部署与配置

目录 前言 Jenkins 的主要功能 Jenkins 的工作流程 一. 环境准备 二. 安装JDK 三. 安装Tomcat 四. 部署Jenkins 五. 浏览器访问 六. 修改超级管理员默认密码 七. 系统配置 八. 安装插件 九. 手动部署插件 前言 Jenkins 是一个开源的自动化服务器&#xff0c;用于…

C# 串口数据转网口实现空气风速风向检测

1.窗体搭建 添加time(定时器) 因为需要风速和风向自动刷新 2.进行网口空气检测 ①服务器连接按钮 // 连接按钮private void button1_Click(object sender, EventArgs e){if (button1.Text "连接"){ConnectSocke();// 连接服务器}else{CloseSocket(); // 关闭服务器…

苹果提出RLAIF:轻量级语言模型编写代码

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读 代码生成一直是一个充满挑战的领域。随着大型语言模型&#xff08;LLM&#xff09;的出现&#xff0c;我们见证了在自然语言理解和生成方面的显著进步。然而&#xff0c;当涉及到代码生成&a…