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

在这里插入图片描述

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值