新增右键菜单功能,支持会话置顶、删除和修改备注操作;更新样式以提升用户交互体验,优化会话信息展示逻辑。

This commit is contained in:
超级老白兔
2025-10-15 15:04:48 +08:00
parent edf5abcec9
commit a716315311
3 changed files with 247 additions and 5 deletions

View File

@@ -120,6 +120,43 @@
}
}
// 右键菜单样式
.contextMenu {
background: white;
border: 1px solid #d9d9d9;
border-radius: 6px;
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);
padding: 4px 0;
min-width: 120px;
z-index: 1000;
.menuItem {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
color: #262626;
transition: background-color 0.2s;
&:hover {
background-color: #f5f5f5;
}
&:active {
background-color: #e6f7ff;
}
.anticon {
font-size: 14px;
}
}
}
// 响应式设计
@media (max-width: 768px) {
.messageList {

View File

@@ -27,6 +27,7 @@ export interface ContractData {
lastMessageTime: number;
config: {
unreadCount: number;
top?: boolean;
};
duplicate: boolean;
[key: string]: any;
@@ -43,6 +44,7 @@ export interface ChatSession {
lastTime: string;
config: {
unreadCount: number;
top?: boolean;
};
online: boolean;
members?: string[];

View File

@@ -1,10 +1,16 @@
import React, { useEffect, useState } from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import React, { useEffect, useState, useRef } from "react";
import { List, Avatar, Badge, Modal, Input, message } from "antd";
import {
UserOutlined,
TeamOutlined,
PushpinOutlined,
DeleteOutlined,
EditOutlined,
} from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { updateConfig } from "@/pages/pc/ckbox/api";
import styles from "./MessageList.module.scss";
import { formatWechatTime } from "@/utils/common";
interface MessageListProps {}
@@ -22,6 +28,138 @@ const MessageList: React.FC<MessageListProps> = () => {
(ContractData | weChatGroup)[]
>([]);
const searchKeyword = useCkChatStore(state => state.searchKeyword);
// 右键菜单相关状态
const [contextMenu, setContextMenu] = useState<{
visible: boolean;
x: number;
y: number;
session: ContractData | weChatGroup | null;
}>({
visible: false,
x: 0,
y: 0,
session: null,
});
// 修改备注相关状态
const [editRemarkModal, setEditRemarkModal] = useState<{
visible: boolean;
session: ContractData | weChatGroup | null;
remark: string;
}>({
visible: false,
session: null,
remark: "",
});
const contextMenuRef = useRef<HTMLDivElement>(null);
// 右键菜单事件处理
const handleContextMenu = (
e: React.MouseEvent,
session: ContractData | weChatGroup,
) => {
e.preventDefault();
e.stopPropagation();
setContextMenu({
visible: true,
x: e.clientX,
y: e.clientY,
session,
});
};
// 隐藏右键菜单
const hideContextMenu = () => {
setContextMenu({
visible: false,
x: 0,
y: 0,
session: null,
});
};
// 置顶/取消置顶
const handleTogglePin = (session: ContractData | weChatGroup) => {
const isPinned = (session.config as any)?.top || true;
updateConfig({
id: session.id,
config: { top: isPinned, chat: true },
})
.then(() => {
message.success(`${isPinned ? "取消置顶" : "置顶"}成功`);
//更新当前这个item的config的top值
//并把当前的Item移动到聊天列表的最上方
})
.catch(() => {
message.error(`${isPinned ? "取消置顶" : "置顶"}失败`);
//更新当前这个item的config的top值
//先计算一下最后一个置顶的坐标并把当前的Item移动到最后一个置顶的 Item下边
});
hideContextMenu();
};
// 删除会话
const handleDelete = (session: ContractData | weChatGroup) => {
Modal.confirm({
title: "确认删除",
content: `确定要删除与 ${session.conRemark || session.nickname} 的会话吗?`,
onOk: () => {
// TODO: 调用API删除会话
console.log("删除会话", session);
message.success("删除成功");
hideContextMenu();
},
});
};
// 修改备注
const handleEditRemark = (session: ContractData | weChatGroup) => {
setEditRemarkModal({
visible: true,
session,
remark: session.conRemark || "",
});
hideContextMenu();
};
// 保存备注
const handleSaveRemark = () => {
if (!editRemarkModal.session) return;
// TODO: 调用API更新备注
console.log("更新备注", editRemarkModal.session, editRemarkModal.remark);
message.success("备注更新成功");
setEditRemarkModal({
visible: false,
session: null,
remark: "",
});
};
// 点击外部隐藏菜单
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
contextMenuRef.current &&
!contextMenuRef.current.contains(event.target as Node)
) {
hideContextMenu();
}
};
if (contextMenu.visible) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [contextMenu.visible]);
useEffect(() => {
let filteredSessions = getChatSessions;
@@ -55,6 +193,7 @@ const MessageList: React.FC<MessageListProps> = () => {
currentContract?.id === session.id ? styles.active : ""
}`}
onClick={() => onContactClick(session)}
onContextMenu={e => handleContextMenu(e, session)}
>
<div className={styles.messageInfo}>
<Badge count={session.config.unreadCount || 0} size="small">
@@ -73,7 +212,7 @@ const MessageList: React.FC<MessageListProps> = () => {
<div className={styles.messageDetails}>
<div className={styles.messageHeader}>
<div className={styles.messageName}>
{session.conRemark || session.nickname}
{session.conRemark || session.nickname || session.wechatId}
</div>
<div className={styles.messageTime}>
{formatWechatTime(session?.lastUpdateTime)}
@@ -84,6 +223,70 @@ const MessageList: React.FC<MessageListProps> = () => {
</List.Item>
)}
/>
{/* 右键菜单 */}
{contextMenu.visible && contextMenu.session && (
<div
ref={contextMenuRef}
className={styles.contextMenu}
style={{
position: "fixed",
left: contextMenu.x,
top: contextMenu.y,
zIndex: 1000,
}}
>
<div
className={styles.menuItem}
onClick={() => handleTogglePin(contextMenu.session!)}
>
<PushpinOutlined />
{(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"}
</div>
<div
className={styles.menuItem}
onClick={() => handleEditRemark(contextMenu.session!)}
>
<EditOutlined />
</div>
<div
className={styles.menuItem}
onClick={() => handleDelete(contextMenu.session!)}
>
<DeleteOutlined />
</div>
</div>
)}
{/* 修改备注Modal */}
<Modal
title="修改备注"
open={editRemarkModal.visible}
onOk={handleSaveRemark}
onCancel={() =>
setEditRemarkModal({
visible: false,
session: null,
remark: "",
})
}
okText="保存"
cancelText="取消"
>
<Input
value={editRemarkModal.remark}
onChange={e =>
setEditRemarkModal(prev => ({
...prev,
remark: e.target.value,
}))
}
placeholder="请输入备注"
maxLength={20}
/>
</Modal>
</div>
);
};