增强GroupContextMenu和VirtualContactList组件:在GroupContextMenu中添加遮罩层右键事件回调以支持菜单在新位置打开,优化SidebarMenu中的右键菜单逻辑以避免闪烁,提升用户体验。

This commit is contained in:
乘风
2025-12-18 15:09:58 +08:00
parent 7724d60a05
commit 7f1fda40dc
3 changed files with 136 additions and 9 deletions

View File

@@ -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}

View File

@@ -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)}
>

View File

@@ -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}
/>
{/* 联系人右键菜单 */}