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

news2024/11/16 1:26:56

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;
案例效果图

在这里插入图片描述

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

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

相关文章

Python | Leetcode Python题解之第169题多数元素

题目&#xff1a; 题解&#xff1a; class Solution:def majorityElement(self, nums: List[int]) -> int:count 0candidate Nonefor num in nums:if count 0:candidate numcount (1 if num candidate else -1)return candidate

查看es p12证书文件过期方法

查看证书过期时间: openssl pkcs12 -in elastic-certificates.p12 -nokeys -out elastic-certificates.crt (需要输入证书生成时配置密码) openssl x509 -enddate -noout -in elastic-certificates.crt

Elasticsearch 使用误区之一——将 Elasticsearch 视为关系数据库!

Elasticsearch 是一个强大的工具&#xff0c;尤其在全文检索、实时分析、机器学习、地理数据应用、日志和事件数据分析、安全信息和事件管理等场景有大量的应用。 然而&#xff0c;Elastic Stack 技术栈的选型及应用效能取决于正确的使用方式。选型错误或者误用 Elasticsearch …

如何在Windows系统部署Terraria私服并配置公网地址实现远程联机

文章目录 前言1. 下载Terraria私服2. 本地运行Terraria 私服3. 本地Terraria私服连接4. Windwos安装Cpolar 工具5. 配置Terraria远程联机地址6. Terraria私服远程联机7. 固定远程联机地址8. 固定的联机地址测试 前言 本文将为你详细介绍在本地如何运行泰拉瑞亚本地私服和结合C…

Recovery

Steal&#xff1a;允许未提交的事务写到磁盘上 Force&#xff1a;在事务提交之前该事务所有更新必须被写到磁盘上 No-StealForce 性能差&#xff0c;需要等待修改被写到磁盘上才能顺利commit 不需要undo&#xff0c;因为aborted事务不会被写到磁盘上 不需要redo&#xff0…

Ubuntu Apache2 搭建Gerrit 环境

一、前言 时隔多年&#xff0c;好久没有更新CSDN 博客了&#xff0c;主要原因有如下两点&#xff1a; 1、平时工作繁忙&#xff0c;无暇更新。 2、工作内容涉及信息安全&#xff0c;一些工作经验积累不便更新到互联网上。 最近一直在折腾搭建Gerrit 环境&#xff0c;最开始…

红酒邂逅时尚,品味生活的双重魅力,引领潮流新风尚

在繁华的都市中&#xff0c;红酒与时尚如同一对孪生姐妹&#xff0c;共同诠释着品味生活的双重魅力。红酒&#xff0c;那深邃的色泽中蕴藏着千年的历史与文化&#xff1b;时尚&#xff0c;那流转的光影中凝聚着时代的潮流与个性。当两者相遇&#xff0c;便碰撞出了特别的火花&a…

BEVDistill

摘要 将激光雷达检测器纳入多视图 3D 物体检测&#xff0c;在 BEV 空间中统一图像和激光雷达特征&#xff0c;让图像BEV特征自适应学习点云BEV特征。 背景 LiDAR 点可捕获精确的 3D 空间信息&#xff0c;为基于相机的目标检测提供自然指导。鉴于此&#xff0c;最近的相关工作…

Handling `nil` Values in `NSDictionary` in Objective-C

Handling nil Values in NSDictionary in Objective-C When working with Objective-C, particularly when dealing with data returned from a server, it’s crucial (至关重要的) to handle nil values appropriately (适当地) to prevent unexpected crashes. Here, we ex…

ModbusRTU协议报文解析

ModbusRTU协议报文解析 报文格式&#xff1a; 设备地址/从站地址&#xff1a; 1个字节 指定目标设备地址&#xff08;从站地址&#xff09; 功能码&#xff1a;1个字节 功能码在modbus协议用于表示信息帧的功能&#xff0c;例如读取线圈状态、读取寄存器等。 数据&#xff…

SSRF漏洞原理与案例分析

一、什么是SSRF漏洞 SSRF (Server-Side Request Forgery&#xff1a;服务器端请求伪造)是一种由攻击者构造请求&#xff0c;由服务端发起请求的安全漏洞。一般情况下&#xff0c;SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的&#xff0c;所以服务端能请…

论文速递 | Management Science 4月文章合集(下)

编者按 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Management Science在2024年4月份发布有关OR/OM以及相关应用的13篇文章的基本信息&#xff0c;旨在帮助读者快速洞察领域新动态。本文为第二部分&#xff08;2/2&#xff09;。 推荐文章1 ● 题目&#xff1a;Social Le…

HarmonyOS应用开发——Hello World

下载 HUAWEI DevEco Studio: https://developer.harmonyos.com/cn/develop/deveco-studio/#download 同意&#xff0c;进入配置页面&#xff1a; 配置下载源以及本地存放路径&#xff0c;包括nodejs和ohpm: 配置鸿蒙SDK路径&#xff1a; 接受协议&#xff1a; 确认无误后&#…

面试-细聊synchronized

1.线程安全问题的主要诱因&#xff1a; 存在多条共享数据(临界资源) 存在多条线程共同操作这些共享数据 解决问题的根本方法&#xff1a; 同一时刻有且仅有一个线程在操作共享数据&#xff0c;其他线程必须等到该线程处理完数据后在对共享数据进行操作。 2.synchroized锁 分…

边缘计算为企业解决数据问题,提升业务效率和竞争力-天拓四方

企业在当前数字化时代面临着一系列具体的问题和挑战&#xff0c;这些问题往往与数据处理、实时响应、安全性以及运营成本等方面密切相关。边缘计算作为一种新兴的计算模型&#xff0c;能够有效地帮助企业解决这些问题&#xff0c;提升业务效率和竞争力。 首先&#xff0c;企业…

清华、北大与微软推出Glyph-ByT5-v2,精准生成文字海报,支持10种语言,效果炸裂

前言 在 AI 领域&#xff0c;文生图技术已经取得了令人惊叹的进展&#xff0c;但如何将文字精准地融入图像&#xff0c;并支持多种语言&#xff0c;一直是研究人员面临的挑战。为了解决这一难题&#xff0c;清华大学、北京大学和微软亚洲研究院的研究人员合作推出了 Glyph-ByT…

网络安全等级保护测评

网络安全等级保护 《GB17859 计算机信息系统安全保护等级划分准则》 规定计算机信息系统安全保护等级共分五级 《中华人民共和国网络安全法》 “国家实行网络安全等级保护制度。 等级测评 测评机构依据国家网络安全等级保护制度规定&#xff0c;按照有关 管理规范和…

泰迪智能科技与成都文理学院人工智能与大数据学院开展校企合作交流

近日&#xff0c;在推动高等教育与产业深度融合的背景下&#xff0c;成都文理学院人工智能与大数据学院携手广东泰迪智能科技股份有限公司开展“专业建设交流会”。人工智能与大数据学院院长胡念青、院长助理陈坚、骨干教师刘超超、孙沛、赵杰、文运、胡斌、邹杰出席本次交流会…

二级web基础操作题练习

---------要求--------- 利用HTML和CSS实现如图所示页面&#xff1a; ---------代码示例--------- 分析&#xff1a;该页面包含一个标题、一个副标题、“姓名信息”的表格&#xff0c;并且有一段文字提示用户仔细填写&#xff0c;使用内联CSS来控制HTML页面的视觉外观&…

TiDB 资源管控的对撞测试以及最佳实践架构

作者&#xff1a; GreenGuan 原文来源&#xff1a; https://tidb.net/blog/bc405c21 引言 TiDB 是一个存算分离的架构&#xff0c;资源管控对这种分离的架构来说实现确实有非常大的难度&#xff0c;TiDB 从 7.1 版本开始引入资源管控的概念&#xff0c;在社区也有不少伙伴测…