要实现该功能的核心点有两个,getBoundingClientRect() 和 ReactDOM.createPortal
父组件 Slide.jsx
import Drop from './Drop';
.....
const dropdownData = [
{
label: 'Menu 1',
children: [
{ label: 'Submenu 1.1' },
{ label: 'Submenu 1.2', children: [{ label: 'Submenu 1.2.1' }] }
]
},
{ label: 'Menu 2' }
];
<Drop data={dropdownData}>
<div className="mi-icon-box">
<Icon icon='jtm-add'></Icon>
</div>
</Drop>
子组件 Drop.jsx
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function CustomDropdown({ children, content }) {
const hasContent = content.props.items?.length !== 0;
console.log(children, content, hasContent, '数据');
const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef(null); // 触发元素的引用
const dropdownRef = useRef(null); // 子菜单容器的引用
useEffect(() => {
if (isOpen && triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect(); // 获取触发元素的位置信息
const dropdownEl = dropdownRef.current;
// 动态设置子菜单的位置
dropdownEl.style.position = 'absolute';
dropdownEl.style.top = `${rect.top + window.scrollY}px`; // 顶部对齐
dropdownEl.style.left = `${rect.right + window.scrollX}px`; // 右侧对齐
}
}, [isOpen]);
return (
<>
{/* 触发元素 */}
<div
ref={triggerRef}
onMouseEnter={() => setIsOpen(true)} // 鼠标进入时显示子菜单
onMouseLeave={() => setIsOpen(false)} // 鼠标离开时隐藏子菜单
style={{ cursor: 'pointer', display: 'inline-block' }}
>
{children} {/* 图标或触发内容 */}
</div>
{/* 子菜单 */}
{isOpen &&
ReactDOM.createPortal(
<div
ref={dropdownRef}
onMouseEnter={() => setIsOpen(true)} // 鼠标进入子菜单时不关闭
onMouseLeave={() => setIsOpen(false)} // 鼠标离开子菜单时关闭
style={
hasContent
? {
backgroundColor: '#fff',
border: '1px solid #ccc',
borderRadius: '4px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
zIndex: 1000,
padding: '8px'
}
: null
}
>
{content}
</div>,
document.body // 将子菜单挂载到 body 上
)}
</>
);
}
function NestedDropdown({ items }) {
// console.log(items, 'items');
return (
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{items.map((item, index) => (
<li key={index} style={{ padding: '8px 16px', cursor: 'pointer' }}>
<CustomDropdown content={<NestedDropdown items={item.children || []} />}>{item.label}</CustomDropdown>
</li>
))}
</ul>
);
}
export default function Drop(props) {
return (
<div>
{/* 图标作为触发元素 */}
<CustomDropdown content={<NestedDropdown items={props.data} />}>
<div>{props.children}</div>
</CustomDropdown>
</div>
);
}