当前位置: 首页 > news >正文

前端自定义右键菜单

  • 废话少说,先上效果

自定义右键菜单

菜单容器

容器主要代码

class App extends Component<appProps, appState> {constructor(props: appProps) {super(props)this.state = {position: { top: 0, left: 0 },menuVisiable: false,menuWidth: 0,menuHeight: 0,menuDatas: [{id: "A000001",value: "apple",children: []},{id: "A000003",value: "AAAAAAAAAAAAAAAAAAAA",children: [{id: "A000031",value: "BBBBBBBBBBBBBBB",children: [{id: "A000311",value: "ddddddddddddd",children: []}]},{id: "A000032",value: "CCCCCCCCCCCCCCCCCCC",children: []}]}, {id: "A000002",value: "banana",children: []}]}}contextMenuHandle = (e: React.MouseEvent<HTMLDivElement>) => {this.setState({ menuVisiable: true, position: { top: top, left: left } })}handleClick = (e: React.MouseEvent<HTMLDivElement>) => {e.preventDefault()this.setState({ menuVisiable: false })}onMenuClick = (data: menu) => {console.log(data)this.setState({ menuVisiable: false })}syncParams = (width: number, height: number) => {this.setState({ menuHeight: height, menuWidth: width })}showMenus = () => {if (!this.state.menuVisiable) {return null;}return <Content containerId='content-container' syncParams={this.syncParams} menuItams={this.state.menuDatas} position={this.state.position} onMenuClick={this.onMenuClick} />}render(): ReactNode {return (<section id="content-container" onClick={this.handleClick} onContextMenu={this.contextMenuHandle}>{this.showMenus()}</section>)}
}export default App

容器样式

#content-container {height: 80vh;width: 90vw;background-color: rgb(255, 255, 255);border-radius: 8px;
}

菜单内容

菜单主要代码

export interface menu {id: stringvalue: stringchildren: menu[]
}export interface position {top: numberleft: number
}interface contentProps {menuItams: menu[]onMenuClick: (data: menu) => voidposition: positionsyncParams: FunctioncontainerId: string
}interface contentState {selfHeight: number
}class Content extends Component<contentProps, contentState> {innerRef = createRef<HTMLDivElement>();observer: ResizeObserver | null = null;state: contentState = {selfHeight: 0}componentDidMount = () => {if (this.innerRef.current) {this.setState({ selfHeight: this.innerRef.current.clientHeight })// 实例化 ResizeObserver,回调函数会在尺寸变化时触发this.observer = new ResizeObserver(([entries]) => {// 获取最新的 contentRect 尺寸(不包含 padding 和 border)const { width, height } = entries.contentRect;this.props.syncParams(width, height)});// 开始监听绑定的 DOM 元素this.observer.observe(this.innerRef.current);}}componentWillUnmount(): void {if (this.observer) {this.observer.disconnect();}}handleClick = (data: menu): void => {this.props.onMenuClick(data)}getTop = () => {return this.props.position.top}getLeft = () => {return this.props.position.left}generateItems = (menuItams: menu[]): React.ReactNode => {let menuList = menuItams.map((el, i) => {let children: React.ReactNodeif (el.children.length > 0) {children = this.generateItems(el.children)}return <MenuItem containerId={this.props.containerId} level={i} originPosition={this.props.position} onChildClick={this.handleClick} key={el.id} item={el} children={children} />})return menuList;}render(): ReactNode {let menuList = this.generateItems(this.props.menuItams);return (<><div ref={this.innerRef} id="content-menu" style={{ left: this.getLeft() + "px", top: this.getTop() + "px" }} onContextMenu={(e) => { e.stopPropagation(); e.preventDefault() }}>{menuList}</div></>)}}interface menuItemProps {item: menuchildren: React.ReactNodelevel: numberbrother: numberoriginPosition: positioncontainerId: stringonChildClick: (data: menu) => void
}interface menuItemState {top: numberleft: numbershowChild: booleanhoverMenu: boolean
}class MenuItem extends Component<menuItemProps, menuItemState> {innerRef = createRef<HTMLDivElement>();constructor(props: menuItemProps) {super(props)this.state = {top: 0,left: 0,showChild: false,hoverMenu: false}}handleClick = (e: React.MouseEvent<HTMLDivElement>) => {e.stopPropagation()e.preventDefault()this.props.onChildClick(this.props.item)}getTop = () => {return this.state.top}getLeft = () => {return this.state.left}handleMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {e.preventDefault()e.stopPropagation()this.setState({ hoverMenu: true })if (this.props.children) {this.setState({ showChild: true })}}handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {e.preventDefault()e.stopPropagation()this.setState({ hoverMenu: false, showChild: false })}render(): ReactNode {return (<><div key={this.props.item.id} className={this.state.hoverMenu ? "hightlight" : ""} ref={this.innerRef} id="menu-item" onClick={this.handleClick} onMouseEnter={this.handleMouseOver} onMouseLeave={() => this.setState({ hoverMenu: false, showChild: false })}><QuestionCircleOutlined /><div id="item-context">{this.props.item.value}</div>{this.props.children ? <RightOutlined /> : null}{this.state.showChild ?<div id="children-menu" style={{ left: this.getLeft() + "px", top: this.getTop() + "px" }}>{this.props.children}</div>: null}</div></>)}
}export default Content

菜单样式

#content-menu {box-shadow: 0 6px 16px 0 rgba(0,0,0,0.08), 0 3px 6px -4px rgba(0,0,0,0.12), 0 9px 28px 8px rgba(0,0,0,0.05);border-radius: 4px;position: relative;width: 200px;background-color: rgb(255, 255, 255);
}#menu-item{display: flex;justify-content: space-between;align-items: center;padding: 3px 10px;height: 26px;#item-context{padding: 0 10px;max-width: 100px;margin-right: auto;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}
}.hightlight{color: #1677ff; cursor: pointer;
}#children-menu{color: initial; box-shadow: 0 6px 16px 0 rgba(0,0,0,0.08), 0 3px 6px -4px rgba(0,0,0,0.12), 0 9px 28px 8px rgba(0,0,0,0.05);position: absolute;width: 200px;background-color: rgb(255, 255, 255);
}

感谢交流、留言!!!

http://www.zskr.cn/news/1371549.html

相关文章:

  • XCOM 2模组管理终极方案:AML启动器完全手册
  • 华为OD机试真题 新系统 Java实现 【数据包优先级窗口查找】
  • 机器学习泛化理论:从AIC/BIC到集中不等式的模型选择与误差分析
  • 从岭回归到Lasso:正则化原理、稀疏性与ADMM算法实践
  • 量化精度损失超8.7%?DeepSeek-VL多模态模型INT4部署避坑指南,含Per-Tensor校准实操清单
  • 数据决定上限,准备决定成败:DeepSeek同源训练数据预处理全链路拆解,错过这3个关键阈值=白训2000卡时
  • 紧急通告:Gemini当前版本对非RGB图像(CMYK/灰度/16bit TIFF)存在系统性解析缺陷!已确认影响金融票据识别与工业质检部署,补丁预计Q3上线
  • WorkshopDL终极指南:跨平台Steam创意工坊模组自由下载神器
  • PolyPyGY二维碳材料:计算设计的高性能锂电阳极新星
  • 告别重复造轮子:用ArcGIS脚本工具封装你的Python代码,效率提升不止一点点
  • 从0到1构建企业级脑筋急转弯生成系统:融合知识图谱校验+幽默度评分模型+人工审核SOP(GitHub开源代码已获1.2k Star)
  • Windows Defender移除工具终极指南:3步彻底禁用安全组件,性能飙升30%
  • 从被动应答到自我进化,深度拆解Agent核心技术范式的四年演进之路
  • 拓扑数据分析与机器学习预测燃料电池电极性能
  • 拓扑数据分析实战:从点云到机器学习特征提取
  • 别再只用OTSU了!OpenCV实战:用Triangle算法搞定单峰图像的二值化(附Python代码)
  • 2026年在湖南选智能家居,有线和无线究竟该怎么选?
  • 摒弃地毯式盲搜,智能定位指引科学救援方向 ——视频孪生无感定位驱动煤矿智能化抢险救援技术方案
  • 2026年湖南旧房改造,原来老房升级智能家居有这些攻略?
  • 全域轨迹可回溯,高效破解煤矿灾害搜救难题 ——基于视频孪生无感定位的矿山轨迹溯源搜救技术解析方案
  • 凯莱德门业怎么样?3万平方生产基地、200名员工,专注铸铝门与高端大门定制 - Amonic
  • 如何用1分钟语音数据训练高质量AI语音克隆?GPT-SoVITS完整指南揭秘
  • 基于EMOS与DRN的WRF太阳辐照度集合预报后处理技术详解
  • 从传统到智能:3步解锁Audacity的AI音频处理革命
  • 抖音批量下载器:5分钟掌握高效音乐视频下载技巧,提升创作效率95%
  • 市面上可靠的石牌坊厂商推荐,单门石牌坊/花岗岩石牌坊/复式石牌坊/石雕石牌坊/石牌坊,石牌坊品牌哪家专业 - 品牌推荐师
  • 司替戊醇Stiripentol常见副作用为食欲下降共济失调及嗜睡表现【海得康】
  • 摄像头协议体系研究:从技术架构到应用实践
  • 告别手动创建!Windows 11右键菜单一键添加Markdown文件(以MarkText为例)
  • 企业ESG披露合规危机应对指南(2024欧盟CSRD强制落地倒计时)