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;