定义 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 HTMLDivElementconst 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 拿到对应的 idcomponentId.current = warpperElement.getAttribute("data-component-id")}};const handleClick = () => {const menuContainer = menuRef.current as HTMLDivElementmenuContainer.style.display = "none"}return (<ulclassName={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 我们可以随意自定义 右键菜单并进行不同的操作
