diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/ClickMenu.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/ClickMenu.module.scss new file mode 100644 index 00000000..115f51aa --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/ClickMenu.module.scss @@ -0,0 +1,103 @@ +.contextMenu { + background: #ffffff; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border: 1px solid #e8e8e8; + overflow: hidden; + min-width: 120px; + animation: fadeIn 0.15s ease-out; + + .menu { + border: none; + box-shadow: none; + background: transparent; + + :global(.ant-menu-item) { + padding: 8px 16px; + margin: 0; + height: auto; + line-height: 1.5; + border-radius: 0; + transition: all 0.2s ease; + + &:hover { + background-color: #f5f5f5; + } + + &:global(.ant-menu-item-danger) { + color: #ff4d4f; + + &:hover { + background-color: #fff2f0; + color: #ff4d4f; + } + } + + &:global(.ant-menu-item-disabled) { + color: #bfbfbf; + cursor: not-allowed; + + &:hover { + background-color: transparent; + } + } + + :global(.ant-menu-item-icon) { + margin-right: 8px; + font-size: 14px; + } + } + + :global(.ant-menu-item-divider) { + margin: 4px 0; + background-color: #f0f0f0; + } + } +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +// 暗色主题支持 +@media (prefers-color-scheme: dark) { + .contextMenu { + background: #2f2f2f; + border-color: #434343; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + + .menu { + :global(.ant-menu-item) { + color: #ffffff; + + &:hover { + background-color: #404040; + } + + &:global(.ant-menu-item-danger) { + color: #ff7875; + + &:hover { + background-color: #2a1215; + color: #ff7875; + } + } + + &:global(.ant-menu-item-disabled) { + color: #595959; + } + } + + :global(.ant-menu-item-divider) { + background-color: #434343; + } + } + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/index.tsx new file mode 100644 index 00000000..47e7f821 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/ClickMeau/index.tsx @@ -0,0 +1,196 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Menu, message } from "antd"; +import { + CopyOutlined, + DeleteOutlined, + ShareAltOutlined, + ReloadOutlined, +} from "@ant-design/icons"; +import { ChatRecord } from "@/pages/pc/ckbox/data"; +import styles from "./ClickMenu.module.scss"; + +interface ClickMenuProps { + visible: boolean; + x: number; + y: number; + messageData: ChatRecord | null; + onClose: () => void; + onCopy?: (content: string) => void; + onDelete?: (messageId: string) => void; + onForward?: (messageData: ChatRecord) => void; + onRetry?: (messageData: ChatRecord) => void; +} + +const ClickMenu: React.FC = ({ + visible, + x, + y, + messageData, + onClose, + onCopy, + onDelete, + onForward, + onRetry, +}) => { + const menuRef = useRef(null); + const [position, setPosition] = useState({ x, y }); + + useEffect(() => { + if (visible && menuRef.current) { + const menuRect = menuRef.current.getBoundingClientRect(); + const windowWidth = window.innerWidth; + const windowHeight = window.innerHeight; + + let adjustedX = x; + let adjustedY = y; + + // 防止菜单超出屏幕右边界 + if (x + menuRect.width > windowWidth) { + adjustedX = windowWidth - menuRect.width - 10; + } + + // 防止菜单超出屏幕下边界 + if (y + menuRect.height > windowHeight) { + adjustedY = windowHeight - menuRect.height - 10; + } + + setPosition({ x: adjustedX, y: adjustedY }); + } + }, [visible, x, y]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + const handleEscKey = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose(); + } + }; + + if (visible) { + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleEscKey); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleEscKey); + }; + }, [visible, onClose]); + + if (!visible || !messageData) { + return null; + } + + const handleCopy = () => { + if (messageData.content && onCopy) { + // 提取纯文本内容,去除HTML标签 + const textContent = messageData.content.replace(/<[^>]*>/g, ""); + onCopy(textContent); + navigator.clipboard + .writeText(textContent) + .then(() => { + message.success("已复制到剪贴板"); + }) + .catch(() => { + message.error("复制失败"); + }); + } + onClose(); + }; + + const handleDelete = () => { + if (messageData.id && onDelete) { + onDelete(messageData.id.toString()); + message.success("消息已删除"); + } + onClose(); + }; + + const handleForward = () => { + if (onForward) { + onForward(messageData); + message.info("转发功能待实现"); + } + onClose(); + }; + + const handleRetry = () => { + if (onRetry) { + onRetry(messageData); + message.info("重新发送"); + } + onClose(); + }; + + const menuItems = [ + { + key: "copy", + icon: , + label: "转发", + onClick: handleCopy, + disabled: !messageData.content || messageData.msgType === 3, // 图片消息不能复制文本 + }, + { + key: "forward", + icon: , + label: "复制", + onClick: handleForward, + }, + { + key: "copy", + icon: , + label: "多条转发", + onClick: handleCopy, + disabled: !messageData.content || messageData.msgType === 3, // 图片消息不能复制文本 + }, + { + key: "delete", + icon: , + label: "引用", + onClick: handleDelete, + }, + { + key: "delete", + icon: , + label: "撤回", + onClick: handleDelete, + }, + ]; + + // 如果是自己发送的消息且发送失败,显示重试选项 + if (messageData.isSend && messageData.status === "failed") { + menuItems.unshift({ + key: "retry", + icon: , + label: "重新发送", + onClick: handleRetry, + }); + } + + return ( +
+ +
+ ); +}; + +export default ClickMenu; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 86ef639f..72fca3a5 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -1,9 +1,10 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Avatar, Divider } from "antd"; import { UserOutlined, LoadingOutlined } from "@ant-design/icons"; import AudioMessage from "./components/AudioMessage/AudioMessage"; import SmallProgramMessage from "./components/SmallProgramMessage"; import VideoMessage from "./components/VideoMessage"; +import ClickMenu from "./components/ClickMeau"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { formatWechatTime } from "@/utils/common"; import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; @@ -16,6 +17,14 @@ interface MessageRecordProps { const MessageRecord: React.FC = ({ contract }) => { const messagesEndRef = useRef(null); + // 右键菜单状态 + const [contextMenu, setContextMenu] = useState({ + visible: false, + x: 0, + y: 0, + messageData: null as ChatRecord | null, + }); + const currentMessages = useWeChatStore(state => state.currentMessages); const loadChatMessages = useWeChatStore(state => state.loadChatMessages); const messagesLoading = useWeChatStore(state => state.messagesLoading); @@ -454,6 +463,46 @@ const MessageRecord: React.FC = ({ contract }) => { } }; + // 右键菜单事件处理 + const handleContextMenu = (e: React.MouseEvent, msg: ChatRecord) => { + e.preventDefault(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + messageData: msg, + }); + }; + + const handleCloseContextMenu = () => { + setContextMenu({ + visible: false, + x: 0, + y: 0, + messageData: null, + }); + }; + + const handleCopyMessage = (content: string) => { + // 复制消息内容的处理逻辑 + console.log("复制消息:", content); + }; + + const handleDeleteMessage = (messageId: string) => { + // 删除消息的处理逻辑 + console.log("删除消息:", messageId); + }; + + const handleForwardMessage = (messageData: ChatRecord) => { + // 转发消息的处理逻辑 + console.log("转发消息:", messageData); + }; + + const handleRetryMessage = (messageData: ChatRecord) => { + // 重试发送消息的处理逻辑 + console.log("重试发送:", messageData); + }; + // 用于分组消息并添加时间戳的辅助函数 const groupMessagesByTime = (messages: ChatRecord[]) => { return messages @@ -477,6 +526,7 @@ const MessageRecord: React.FC = ({ contract }) => { className={`${styles.messageItem} ${ isOwn ? styles.ownMessage : styles.otherMessage }`} + onContextMenu={e => handleContextMenu(e, msg)} >
{/* 如果不是群聊 */} @@ -587,6 +637,19 @@ const MessageRecord: React.FC = ({ contract }) => { ))}
+ + {/* 右键菜单组件 */} +
); };