新增右键菜单功能,支持会话置顶、删除和修改备注操作;更新样式以提升用户交互体验,优化会话信息展示逻辑。
This commit is contained in:
@@ -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) {
|
@media (max-width: 768px) {
|
||||||
.messageList {
|
.messageList {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface ContractData {
|
|||||||
lastMessageTime: number;
|
lastMessageTime: number;
|
||||||
config: {
|
config: {
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
|
top?: boolean;
|
||||||
};
|
};
|
||||||
duplicate: boolean;
|
duplicate: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -43,6 +44,7 @@ export interface ChatSession {
|
|||||||
lastTime: string;
|
lastTime: string;
|
||||||
config: {
|
config: {
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
|
top?: boolean;
|
||||||
};
|
};
|
||||||
online: boolean;
|
online: boolean;
|
||||||
members?: string[];
|
members?: string[];
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { List, Avatar, Badge } from "antd";
|
import { List, Avatar, Badge, Modal, Input, message } from "antd";
|
||||||
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
UserOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
PushpinOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||||
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
||||||
|
import { updateConfig } from "@/pages/pc/ckbox/api";
|
||||||
import styles from "./MessageList.module.scss";
|
import styles from "./MessageList.module.scss";
|
||||||
import { formatWechatTime } from "@/utils/common";
|
import { formatWechatTime } from "@/utils/common";
|
||||||
interface MessageListProps {}
|
interface MessageListProps {}
|
||||||
@@ -22,6 +28,138 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
(ContractData | weChatGroup)[]
|
(ContractData | weChatGroup)[]
|
||||||
>([]);
|
>([]);
|
||||||
const searchKeyword = useCkChatStore(state => state.searchKeyword);
|
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(() => {
|
useEffect(() => {
|
||||||
let filteredSessions = getChatSessions;
|
let filteredSessions = getChatSessions;
|
||||||
|
|
||||||
@@ -55,6 +193,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
currentContract?.id === session.id ? styles.active : ""
|
currentContract?.id === session.id ? styles.active : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => onContactClick(session)}
|
onClick={() => onContactClick(session)}
|
||||||
|
onContextMenu={e => handleContextMenu(e, session)}
|
||||||
>
|
>
|
||||||
<div className={styles.messageInfo}>
|
<div className={styles.messageInfo}>
|
||||||
<Badge count={session.config.unreadCount || 0} size="small">
|
<Badge count={session.config.unreadCount || 0} size="small">
|
||||||
@@ -73,7 +212,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
<div className={styles.messageDetails}>
|
<div className={styles.messageDetails}>
|
||||||
<div className={styles.messageHeader}>
|
<div className={styles.messageHeader}>
|
||||||
<div className={styles.messageName}>
|
<div className={styles.messageName}>
|
||||||
{session.conRemark || session.nickname}
|
{session.conRemark || session.nickname || session.wechatId}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.messageTime}>
|
<div className={styles.messageTime}>
|
||||||
{formatWechatTime(session?.lastUpdateTime)}
|
{formatWechatTime(session?.lastUpdateTime)}
|
||||||
@@ -84,6 +223,70 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
</List.Item>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user