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 ? (<MenuclassName="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"><divref={(indexTabs) => (this.indexTabs = indexTabs)}className={`${this.props.isTenant? 'index-tabs-navigation-isTenant': 'index-tabs-navigation'}`}><TabshideAddtype="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: (<divclassName="customLabel"onContextMenu={this.onContextMenuFun.bind(this,index,)}><span className="customLabel-title">{item.title}</span>{String(this.props.openModuleId) === String(item.id) ? (<SyncOutlinedonClick={this.onReset.bind(this, item, index)}className="customLabel-reset"/>) : ('')}</div>),};})}/>{/* 自定義右擊菜單 */}{visiableContextMenu ? (<MenuclassName="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:
.contextMenuListposition: fixedz-index 1001border: solid 1px #e9ecf0padding: 5px 0.ant-menu-itemmargin-bottom: 0 !importantpadding: 5px 12px;line-height: 22px;height: 32px;margin-top: 0 !important.ant-menu-title-contentmargin-right: 5px !important;