增强GroupContextMenu和VirtualContactList组件:在GroupContextMenu中添加遮罩层右键事件回调以支持菜单在新位置打开,优化SidebarMenu中的右键菜单逻辑以避免闪烁,提升用户体验。
This commit is contained in:
@@ -32,6 +32,8 @@ export interface GroupContextMenuProps {
|
||||
onEditClick?: (group: ContactGroup) => void;
|
||||
/** 删除分组点击(由上层打开删除确认弹窗) */
|
||||
onDeleteClick?: (group: ContactGroup) => void;
|
||||
/** 遮罩层右键事件回调(用于在新位置打开菜单) */
|
||||
onOverlayContextMenu?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,7 @@ export const GroupContextMenu: React.FC<GroupContextMenuProps> = ({
|
||||
onAddClick,
|
||||
onEditClick,
|
||||
onDeleteClick,
|
||||
onOverlayContextMenu,
|
||||
}) => {
|
||||
// 默认群分组 / 未分组:id 为 0,且 groupType 为 1 或 2
|
||||
const isDefaultOrUngrouped =
|
||||
@@ -106,12 +109,27 @@ export const GroupContextMenu: React.FC<GroupContextMenuProps> = ({
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
// 处理遮罩层右键事件:关闭菜单并通知父组件处理新位置的右键事件
|
||||
const handleOverlayContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// 关闭当前菜单
|
||||
onClose();
|
||||
// 通知父组件处理新位置的右键事件
|
||||
if (onOverlayContextMenu) {
|
||||
// 使用 setTimeout 确保菜单先关闭,然后再处理新事件
|
||||
setTimeout(() => {
|
||||
onOverlayContextMenu(e);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.contextMenuOverlay}
|
||||
onClick={onClose}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
onContextMenu={handleOverlayContextMenu}
|
||||
/>
|
||||
<Menu
|
||||
className={styles.contextMenu}
|
||||
|
||||
@@ -287,6 +287,9 @@ export const VirtualContactList: React.FC<VirtualContactListProps> = ({
|
||||
<div
|
||||
style={style}
|
||||
className={styles.groupHeader}
|
||||
data-group-key={groupKey}
|
||||
data-group-id={group.id}
|
||||
data-group-type={group.groupType}
|
||||
onClick={() => onGroupToggle?.(group.id, group.groupType)}
|
||||
onContextMenu={e => onGroupContextMenu?.(e, group)}
|
||||
>
|
||||
|
||||
@@ -162,15 +162,120 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
(e: React.MouseEvent, group: ContactGroup) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setGroupContextMenu({
|
||||
visible: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
group,
|
||||
groupType: group.groupType,
|
||||
});
|
||||
|
||||
// 如果已经有菜单打开,先关闭它,然后在下一个渲染周期打开新菜单
|
||||
if (groupContextMenu.visible) {
|
||||
setGroupContextMenu({
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
// 使用 requestAnimationFrame 确保关闭操作先执行,然后再打开新菜单
|
||||
// 这样可以避免菜单闪烁,提供更流畅的体验
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setGroupContextMenu({
|
||||
visible: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
group,
|
||||
groupType: group.groupType,
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 如果没有菜单打开,直接打开新菜单
|
||||
setGroupContextMenu({
|
||||
visible: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
group,
|
||||
groupType: group.groupType,
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[groupContextMenu.visible],
|
||||
);
|
||||
|
||||
// 处理遮罩层右键事件:根据鼠标位置找到对应的群组并打开菜单
|
||||
const handleOverlayContextMenu = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
// 菜单已经关闭,现在需要找到鼠标位置下的群组元素
|
||||
// 使用 setTimeout 确保遮罩层已经移除,DOM 更新完成
|
||||
setTimeout(() => {
|
||||
// 获取当前要显示的群组列表(优先使用新架构的groups)
|
||||
const currentDisplayGroups =
|
||||
newGroups.length > 0
|
||||
? newGroups
|
||||
: contactGroups.map((g: ContactGroupByLabel) => ({
|
||||
id: g.id,
|
||||
groupName: g.groupName,
|
||||
groupType: g.groupType as 1 | 2,
|
||||
count: g.count,
|
||||
sort: g.sort,
|
||||
groupMemo: g.groupMemo,
|
||||
}));
|
||||
|
||||
// 方法1:尝试通过 data-group-key 属性直接找到群组元素
|
||||
const element = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (!element) return;
|
||||
|
||||
// 向上查找群组头部元素
|
||||
let groupElement: HTMLElement | null = element as HTMLElement;
|
||||
while (groupElement) {
|
||||
// 检查是否有 data-group-key 属性
|
||||
const groupKey = groupElement.getAttribute('data-group-key');
|
||||
if (groupKey) {
|
||||
// 解析 groupKey 获取群组信息
|
||||
const [groupId, groupType] = groupKey.split('_').map(Number);
|
||||
const group = currentDisplayGroups.find(
|
||||
g => g.id === groupId && g.groupType === groupType
|
||||
);
|
||||
if (group) {
|
||||
// 创建合成事件并触发群组右键菜单
|
||||
const syntheticEvent = {
|
||||
...e,
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {},
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
} as React.MouseEvent;
|
||||
handleGroupContextMenu(syntheticEvent, group);
|
||||
return;
|
||||
}
|
||||
}
|
||||
groupElement = groupElement.parentElement;
|
||||
}
|
||||
|
||||
// 方法2:如果方法1失败,遍历所有群组,检查鼠标位置是否在群组头部范围内
|
||||
for (const group of currentDisplayGroups) {
|
||||
const groupKey = getGroupKey(group.id, group.groupType, selectedAccountId);
|
||||
const groupHeaderElement = document.querySelector(
|
||||
`[data-group-key="${groupKey}"]`
|
||||
) as HTMLElement;
|
||||
if (groupHeaderElement) {
|
||||
const rect = groupHeaderElement.getBoundingClientRect();
|
||||
if (
|
||||
e.clientX >= rect.left &&
|
||||
e.clientX <= rect.right &&
|
||||
e.clientY >= rect.top &&
|
||||
e.clientY <= rect.bottom
|
||||
) {
|
||||
const syntheticEvent = {
|
||||
...e,
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {},
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
} as React.MouseEvent;
|
||||
handleGroupContextMenu(syntheticEvent, group);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 50); // 给足够的时间让遮罩层移除
|
||||
},
|
||||
[newGroups, contactGroups, selectedAccountId, getGroupKey, handleGroupContextMenu],
|
||||
);
|
||||
|
||||
// 打开新增分组弹窗
|
||||
@@ -666,6 +771,7 @@ const ContactListSimple: React.FC<WechatFriendsProps> = ({
|
||||
onAddClick={handleOpenAddGroupModal}
|
||||
onEditClick={handleOpenEditGroupModal}
|
||||
onDeleteClick={handleOpenDeleteGroupModal}
|
||||
onOverlayContextMenu={handleOverlayContextMenu}
|
||||
/>
|
||||
|
||||
{/* 联系人右键菜单 */}
|
||||
|
||||
Reference in New Issue
Block a user