React:tabs或标签页自定义右击菜单内容,支持内嵌iframe关闭菜单方案

React:tabs或标签页自定义右击菜单内容,支持内嵌iframe关闭菜单方案

不管是react、vue还是原生js,原理是一样的。
注意如果内嵌iframe情况下,iframe无法使用事件监听,但是可以使用iframe的任何点击行为都会往父级window通信,使用window的message事件监听即可。

场景

前端自定义标签页,一个标签对应一个路由页面,通过切换标签快速切换不同应用或者页面

代码

变量
state = {
    contextMenuIndex: '', // 右击菜单索引
    contextMenuPosition: { // 右击菜单定位信息
      clientX: '',
      clientY: '',
    },
    visiableContextMenu: false, // 右击菜单是否显示
  };
事件加载
componentDidMount() {
	// 监听当前document的鼠标右击事件
    document.addEventListener('contextmenu', (event) => {
      event.preventDefault();
      if (this.state.visiableContextMenu === -1) {
        return;
      }
      this.setState({
        contextMenuPosition: {
          clientX: `${event.clientX}px`,
          clientY: `${event.clientY}px`,
        },
      });
    });
    
	// 监听当前document的鼠标点击事件,用于关闭自定义菜单
    document.addEventListener('click', () => {
      this.setState({
        visiableContextMenu: false,
      });
    });

	// 监听当前window的messag事件(有内嵌iframe时使用,若无可不使用)
	// 无法使用iframe监听,可以通过和父级window的消息通信达到目的。
    window.addEventListener('message', () => {
      this.setState({
        visiableContextMenu: false,
      });
    });
  }
标签
		<div>
		  {/* ... */}

		  {/* 自定义右击菜单 */}
          {visiableContextMenu ? (
            <Menu
              className="contextMenuList"
              style={{ left: clientX, top: clientY }}
            >
              <Menu.Item onClick={() => this.hadleCloseByIndex([contextMenuIndex])}>
                关闭当前
              </Menu.Item>
              <Menu.Item onClick={() => this.closeLeft()}>关闭左侧</Menu.Item>
              <Menu.Item onClick={() => this.closeRight()}>关闭右侧</Menu.Item>
              <Menu.Item onClick={() => this.closeAll()}>关闭全部</Menu.Item>
            </Menu>
          ) : (
            ''
          )}
        </div>
完整案例代码
import React, { Component } from 'react';
import { SyncOutlined } from '@ant-design/icons';
import { Tabs, Menu } from 'antd';
import store from 'store';

// styl
import './IndexTabsNavigation.styl';

class IndexTabsNavigation extends Component {
  state = {
    contextMenuIndex: '',
    contextMenuPosition: {
      clientX: '',
      clientY: '',
    },
    visiableContextMenu: false,
  };

  onClick(id) {
    this.props.updateOpenModuleId(id);
  }

  onEdit(targetKey, action) {
    // e.stopPropagation();
    if (action === 'remove') {
      // 多租户首页最后一个数据不能删除
      if (this.props.isTenant && this.props.openModule.length === 1) return;
      const index = this.props.openModule.findIndex(
        (item) => String(item.id) === String(targetKey),
      );
      this.props.removeModule(targetKey, index);
    }
  }

  onReset(item, index, e) {
    e.stopPropagation();
    const getIframe = document.querySelectorAll('.inner-iframe')[index];
    if (getIframe) {
      getIframe.setAttribute('src', `${item.path}&_t=${Math.random() * 1e18}`);
    }
  }

  componentDidMount() {
    document.addEventListener('contextmenu', (event) => {
      event.preventDefault();
      if (this.state.visiableContextMenu === -1) {
        return;
      }
      this.setState({
        contextMenuPosition: {
          clientX: `${event.clientX}px`,
          clientY: `${event.clientY}px`,
        },
      });
    });

    document.addEventListener('click', () => {
      this.setState({
        visiableContextMenu: false,
      });
    });

    window.addEventListener('message', () => {
      this.setState({
        visiableContextMenu: false,
      });
    });
  }

  // 设置右击菜单
  onContextMenuFun(contextMenuIndex) {
    this.setState({
      contextMenuIndex,
      visiableContextMenu: true,
    });
  }

  hadleCloseByIndex(indexList) {
    if (this.props.isTenant && this.props.openModule.length === 1) return;
    indexList.map((index, idx) => {
      const item = this.props.openModule[index];
      setTimeout(() => {
        this.props.removeModule(item.id, index);
      }, 100 * idx)
    })
  }

  // 关闭左侧
  closeLeft() {
    const { contextMenuIndex } = this.state;
    if (contextMenuIndex <= 0) return;
    const closeList = Array.from({length: contextMenuIndex}).map((item, index) => index)
    this.props.removeModuleListByIndex(closeList);
  }

  // 关闭右侧
  closeRight() {
    const { contextMenuIndex } = this.state;
    const openModule = this.props.openModule;
    const delLength = openModule.length - 1 - contextMenuIndex;
    if (delLength <= 0) return;
    const closeList = Array.from({length: delLength}).map((item, index) => item = contextMenuIndex + index + 1)
    this.props.removeModuleListByIndex(closeList);

    setTimeout(() => {
      // 判断当前tabs是否有高亮
      const newOpenModule = [...this.props.openModule];
      const openModuleOpenInfo = store.get('openModuleOpenInfo') || {};
      const openObj = newOpenModule.find(
        (item) => String(item.id) === String(openModuleOpenInfo.id),
      );
      if (!openObj) {
        this.props.updateOpenModuleId(newOpenModule[newOpenModule.length - 1].id);
      }
    }, 300)
  }

  // 关闭全部
  closeAll() {
    const openModule = this.props.openModule;
    if (openModule.length - 1 <= 0) return;
    const openModuleOpenInfo = store.get('openModuleOpenInfo') || {};
    const openIndex = openModule.findIndex(
      (item) => String(item.id) === String(openModuleOpenInfo.id),
    );
    const closeList = openModule.map((item, index) => index).filter((item, index) => index !== openIndex)
    this.props.removeModuleListByIndex(closeList);
  }

  render() {
    const {
      contextMenuIndex,
      visiableContextMenu,
      contextMenuPosition: { clientX, clientY },
    } = this.state;
    
    return (
      <div className="index-tabs-navigation-box">
        <div
          ref={(indexTabs) => (this.indexTabs = indexTabs)}
          className={`${
            this.props.isTenant
              ? 'index-tabs-navigation-isTenant'
              : 'index-tabs-navigation'
          }`}
        >
          <Tabs
            hideAdd
            type="editable-card"
            activeKey={String(this.props.openModuleId)}
            onChange={this.onClick.bind(this)}
            onEdit={this.onEdit.bind(this)}
            items={this.props.openModule.map((item, index) => {
              return {
                key: String(item.id),
                label: (
                  <div
                    className="customLabel"
                    onContextMenu={this.onContextMenuFun.bind(
                      this,
                      index,
                    )}
                  >
                    <span className="customLabel-title">{item.title}</span>
                    {String(this.props.openModuleId) === String(item.id) ? (
                      <SyncOutlined
                        onClick={this.onReset.bind(this, item, index)}
                        className="customLabel-reset"
                      />
                    ) : (
                      ''
                    )}
                  </div>
                ),
              };
            })}
          />

          {/* 自定义右击菜单 */}
          {visiableContextMenu ? (
            <Menu
              className="contextMenuList"
              style={{ left: clientX, top: clientY }}
            >
              <Menu.Item onClick={() => this.hadleCloseByIndex([contextMenuIndex])}>
                关闭当前
              </Menu.Item>
              <Menu.Item onClick={() => this.closeLeft()}>关闭左侧</Menu.Item>
              <Menu.Item onClick={() => this.closeRight()}>关闭右侧</Menu.Item>
              <Menu.Item onClick={() => this.closeAll()}>关闭全部</Menu.Item>
            </Menu>
          ) : (
            ''
          )}
        </div>
      </div>
    );
  }
}

export default IndexTabsNavigation;

样式代码styl:

.contextMenuList
    position: fixed
    z-index 1001
    border: solid 1px #e9ecf0
    padding: 5px 0

    .ant-menu-item
      margin-bottom: 0 !important
      padding: 5px 12px;
      line-height: 22px;
      height: 32px;
      margin-top: 0 !important

      .ant-menu-title-content
        margin-right: 5px !important;
案例效果图

在这里插入图片描述

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

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

相关文章

【等保】网络安全等级保护(等保2.0PPT)

等保2.0&#xff08;网络安全等级保护基本要求的第二代标准&#xff09;的推出和实施&#xff0c;是基于多方面的考虑和需求。以下是实施等保2.0的主要原因&#xff1a; 加强网络安全保护&#xff1a; 随着网络技术的不断发展和网络威胁的不断增加&#xff0c;传统的网络安全保…

BGP中的TCP连接源地址问题

3.TCP连接源地址&#xff08;用loop back地址是最优选择&#xff09; 应用场景与理论&#xff1a; 由于BGP应用于大型网络中&#xff0c;为了避免单点失败&#xff0c;往往需要通过多条链路连接&#xff0c;当一条链路故障时候就用另一条链路继续工作&#xff0c;但是BGP又无法…

Swift 6:导入语句上的访问级别

文章目录 前言示例启用 AccessLevelOnImport破坏性变更采用这些更改总结前言 SE-0409 提案引入了一项新功能,即允许使用 Swift 的任何可用访问级别标记导入声明,以限制导入的符号可以在哪些类型或接口中使用。由于这些变化,现在可以将依赖项标记为对当前源文件(private 或…

IO-Link软件开发流程

目录 了解IO-Link协议&#xff1a; 确定物理连接方式&#xff1a; 编写驱动程序&#xff1a; 测试通信&#xff1a; 集成与应用&#xff1a; 优化与迭代&#xff1a; 文档编写与用户支持&#xff1a; IO-Link产品的开发流程主要包括以下几个步骤 了解IO-Link协议&#x…

【java实习评审】 项目详情模块,如何设计关联表,提高查询性能

大家好&#xff0c;本篇文章分享一下【校招VIP】免费商业项目“推评分16”第一期电影详情模块 java同学的文档周最佳作品。 1、本项目是基于年轻人的喜好&#xff0c;更个性的电影推荐网站。筛选各分类的知名电影&#xff0c;并给出推荐理由和下载链接。另外&#xff0c;通过…

泰迪智能科技实验室产品-云计算资源管理平台介绍

云计算资源管理平台是一款集群应用程序管理平台&#xff0c;以Docker、Kubernetes为核心引擎的容器化应用部署、运行环境&#xff0c;对数据中心的物理服务器、网络、存储、虚拟服务器等基础架构资源进行集中统一的管理、分配、监控等。平台旨在围绕行业应用逐步由“虚拟化”向…

Docker部署前端,动态配置后端地址

本文介绍了使用Docker环境变量动态配置nginx。采用的是通过docker run -e xxxxxxx先往容器注入环境变量&#xff0c;然后进一步通过envsubst指令将环境变量写入到conf文件中&#xff0c;实现动态配置文件内容。 背景 前后端分离的架构下&#xff0c;经常会用到nginx反向代理来…

深度学习 --- stanford cs231学习笔记七(训练神经网络之梯度下降优化器)

5&#xff0c;梯度下降优化器 5&#xff0c;1 梯度下降在深度学习中的作用 在深度学习中&#xff0c;权重W的值是否合理是由损失函数L来判断的。L越小&#xff0c;表示W的设置越happy。L越大&#xff0c;表示W的值越unhappy。 为了让L越来越小&#xff0c;常用的方法是梯度下降…

自主可控的芯片设计供应链软件:保障芯片产业安全的关键

在当前的科技浪潮中&#xff0c;芯片作为信息技术的核心&#xff0c;其设计、制造和供应链的安全性和自主可控性显得尤为重要。而自主可控的芯片设计供应链软件&#xff0c;正是保障这一产业链安全的关键环节。 首先&#xff0c;我们要明确自主可控芯片设计供应链软件的核心价值…

【强化学习】第02期:动态规划方法

笔者近期上了国科大周晓飞老师《强化学习及其应用》课程&#xff0c;计划整理一个强化学习系列笔记。笔记中所引用的内容部分出自周老师的课程PPT。笔记中如有不到之处&#xff0c;敬请批评指正。 文章目录 2.1 动态规划&#xff1a;策略收敛法/策略迭代法2.2 动态规划&#xf…

聚星文社AI工具

聚星文社AI工具是一种基于人工智能技术开发的工具&#xff0c;旨在辅助作者和写作人员提升创作效率和质量。 点击下载 该工具可以提供多项功能&#xff0c;包括语法纠错、智能推荐、文章自动摘要等。 通过使用聚星文社AI工具&#xff0c;用户可以在写作过程中得到即时的纠错建…

数据库使用笔记

1.mysql数据库频繁访问导致连接超时 解决办法一&#xff1a; 优化查询&#xff1a;检查并优化SQL查询语句&#xff0c;减少不必要的数据库调用。增加连接池大小&#xff1a;如果应用程序使用连接池&#xff0c;可以考虑增加连接池的最大连接数。&#xff08;注&#xff1a;不能…

权限维持-域环境单机版---自启动

免责声明:本文仅做技术交流与学习... 目录 1.windows自启动路径加载 2.自启动服务加载 3.自启动注册表加载 所在regedit目录: -添加启动项 --重启生效 4.计划计时任务 windows软件或程序服务开机自启动的四种方式-CSDN博客 1.windows自启动路径加载 --当windows注销…

LabVIEW在机器人研究所中的应用

机器人研究所致力于机器人技术的研究与开发&#xff0c;涵盖工业机器人、服务机器人、医疗机器人等多个领域。研究所需要一个高效、灵活的实验控制和数据采集系统&#xff0c;以进行复杂的机器人实验&#xff0c;并对实验数据进行实时处理和分析。 项目需求 实时控制与监控&am…

ai轨迹过京东m端

声明(a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本…

什么是WABF验证?

今年的618电商购物节已经落下帷幕&#xff0c;在此期间&#xff0c;各大电商平台都普遍迎来了用户访问量、优惠券领取量和交易量的显著增长。在这一时期&#xff0c;业务安全成为电商平台关注的焦点。验证码作为一种常见的业务安全工具&#xff0c;能够有效应对业务安全问题。然…

探究Qt5【元对象编译器,moc】的 设计原理和技术细节

Qt5是一个跨平台C框架&#xff0c;它有个突出的特点就是其元对象系统&#xff0c;该系统通过扩展C的能力&#xff0c;为事件处理提供了信号与槽机制、为对象内省提供了属性系统。为了支持这些特性&#xff0c;Qt引入了元对象编译器&#xff08;Meta-Object Compiler, MOC&#…

单源最短路径问题(Dijstra)

#include<iostream> using namespace std; #define MAX 500 #define INT 999 typedef struct {char vex[MAX];int Edge[MAX][MAX];int vexnum,arcnum; }MGraph; void InitMG(MGraph &MG) {cout<<"输入顶点数和边数&#xff1a;";cin>>MG.vexnu…

探索区块链:颠覆性技术的崛起

目录 一、引言 二、区块链技术概述 三、区块链应用场景 四、区块链面临的挑战 五、区块链的未来展望 六、结语 一、引言 在数字化浪潮的推动下&#xff0c;区块链技术以其独特的去中心化、透明性和不可篡改性等特性&#xff0c;正在逐步改变我们的生活。从金融领域到供应…

树莓派4B_OpenCv学习笔记15:OpenCv定位物体实时坐标

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 今日学习 OpenCv定位物体实时位置&#xff0c;代码来源是…