定义 ContextMenu 组件
import React, { useRef, useEffect, FC } from "react"
import { getParentElement } from "@/util"
import { ActionItem } from './index'
import styles from "./index.less"
interface IProps {
actions: ActionItem[],
triggerClass?: string
}
const ContextMenu: FC<IProps> = (props) => {
const menuRef = useRef()
const componentId = useRef<string>('')
useEffect(() => {
document.addEventListener('contextmenu', triggerContextMenu);
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('contextmenu', triggerContextMenu);
document.removeEventListener('click', handleClick);
};
}, []);
const triggerContextMenu = (e: MouseEvent) => {
const menuContainer = menuRef.current as HTMLDivElement
const warpperElement = getParentElement(e.target as HTMLElement, props.triggerClass);
if (warpperElement) {
e.preventDefault();
menuContainer.style.display = 'block';
menuContainer.style.top = e.pageY + 'px';
menuContainer.style.left = e.pageX + 'px';
// 可以通过 warpperElement data-component-id 拿到对应的 id
componentId.current = warpperElement.getAttribute("data-component-id")
}
};
const handleClick = () => {
const menuContainer = menuRef.current as HTMLDivElement
menuContainer.style.display = "none"
}
return (
<ul
className={styles['menu-container']}
ref={menuRef}
id="menuContainer"
style={{ display: "none" }}
>
{props.actions.map((item, index) => {
return (
<li onClick={() => item.action(componentId.current)} key={index}>
{item.text} {item.shortcut}
</li>
);
})}
</ul>
);
};
ContextMenu.defaultProps = {
triggerClass: 'edit-wrapper-box'
}
export default ContextMenu
对 contextMenu 事件进行监听、然后利用 ReactDOM 添加到 body 上
export interface ActionItem {
action: (cid?: string) => void;
text: string;
shortcut: string;
}
export default function createContextMenu(actions: ActionItem[], triggerClass = "edit-wrapper-box") {
const container = document.createElement("div")
// ReactDOM.createPortal 也可以添加到外部
ReactDOM.render(<ContextMenu actions={actions} triggerClass={triggerClass} />, container)
document.body.append(container)
}
插件化 ContextMenu
我们可以对不同区域进行不同的右键菜单功能开发、如下
import { useEffect, useContext } from "react"
import { ActionItem } from "@/components/contextMenu"
import createContextMenu from '@/components/contextMenu'
import { AppContext, IContextProps } from '@/store/context';
import { SETACTIVE, DELETECOMPONENT, PASTECOPIEDCOMPONENT, COPYCOMPONENT } from '@/store/contant'
export default function InitContextMenu() {
const { dispatch } = useContext<IContextProps>(AppContext);
const editorActions: ActionItem[] = [
{
shortcut: '⌘C / Ctrl+C', text: "拷贝图层", action: (cid: string) => {
dispatch({
type: COPYCOMPONENT,
data: {
id: cid
}
});
}
},
{
shortcut: '⌘V / Ctrl+V', text: "粘贴图层", action: (cid: string) => {
dispatch({
type: PASTECOPIEDCOMPONENT,
});
}
},
{
shortcut: 'Backspace / Delete', text: "删除图层", action: (cid: string) => {
dispatch({
type: DELETECOMPONENT,
data: {
id: cid
}
});
}
},
{
shortcut: 'ESC', text: "取消选中", action: (cid: string) => {
dispatch({
type: SETACTIVE,
data: {
value: '',
},
});
}
},
{
shortcut: '⌘Z / Ctrl+Z', text: "撤销", action: (cid: string) => {
alert("待实现")
}
},
{
shortcut: ' ⌘⇧Z / Ctrl+Shift+Z', text: "重做", action: (cid: string) => {
alert("待实现")
}
},
]
const settingAction: ActionItem[] = [
{ shortcut: 'cv', text: "复制配置", action: () => { console.log("复制配置") } }
]
useEffect(() => {
// 画布右键操作
createContextMenu(editorActions, "edit-wrapper-box")
// 表单编辑右键操作
createContextMenu(settingAction, "pane-setting")
}, [dispatch])
}
最后我们只需要在 main 里面调用下 自定义的 Hooks 即可、但是我们实现了业务和逻辑的结偶、利用自定义的 hooks 我们可以随意自定义 右键菜单并进行不同的操作