feat(weChat): 添加消息多选转发功能

- 在微信聊天记录组件中添加复选框支持多选消息
- 新增showCheckbox状态和updateShowCheckbox方法控制复选框显示
- 重构右键菜单处理逻辑,统一使用onCommad回调
- 添加EnterModule状态管理不同功能模块
- 调整消息项布局样式以适配复选框
This commit is contained in:
超级老白兔
2025-09-18 16:38:18 +08:00
parent ac53861a85
commit f0f64dd118
5 changed files with 122 additions and 72 deletions

View File

@@ -49,6 +49,7 @@
.messageItem {
display: flex;
margin-bottom: 12px;
align-items: flex-start;
&.ownMessage {
justify-content: flex-end;
@@ -77,6 +78,15 @@
}
}
// Checkbox 容器
.checkboxContainer {
display: flex;
align-items: flex-start;
margin-right: 8px;
margin-top: 4px;
flex-shrink: 0;
}
// 消息内容容器
.messageContent {
display: flex;

View File

@@ -2,10 +2,8 @@ import React, { useState, useEffect, useRef } from "react";
import { Menu, message } from "antd";
import {
CopyOutlined,
DeleteOutlined,
CheckSquareOutlined,
RollbackOutlined,
ReloadOutlined,
ExportOutlined,
LinkOutlined,
} from "@ant-design/icons";
@@ -18,10 +16,7 @@ interface ClickMenuProps {
y: number;
messageData: ChatRecord | null;
onClose: () => void;
onCopy?: (content: string) => void;
onDelete?: (messageId: string) => void;
onForward?: (messageData: ChatRecord) => void;
onRetry?: (messageData: ChatRecord) => void;
onCommad: (action: string) => void;
}
const ClickMenu: React.FC<ClickMenuProps> = ({
@@ -30,10 +25,7 @@ const ClickMenu: React.FC<ClickMenuProps> = ({
y,
messageData,
onClose,
onCopy,
onDelete,
onForward,
onRetry,
onCommad,
}) => {
const menuRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ x, y });
@@ -90,10 +82,9 @@ const ClickMenu: React.FC<ClickMenuProps> = ({
}
const handleCopy = () => {
if (messageData.content && onCopy) {
if (messageData.content) {
// 提取纯文本内容去除HTML标签
const textContent = messageData.content.replace(/<[^>]*>/g, "");
onCopy(textContent);
navigator.clipboard
.writeText(textContent)
.then(() => {
@@ -106,75 +97,34 @@ const ClickMenu: React.FC<ClickMenuProps> = ({
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",
key: "transmit",
icon: <ExportOutlined />,
label: "转发",
onClick: handleCopy,
disabled: !messageData.content || messageData.msgType === 3, // 图片消息不能复制文本
},
{
key: "forward",
icon: <CopyOutlined />,
label: "复制",
onClick: handleForward,
},
{
key: "copy",
icon: <CopyOutlined />,
label: "复制",
},
{
key: "multipleForwarding",
icon: <CheckSquareOutlined />,
label: "多条转发",
onClick: handleCopy,
disabled: !messageData.content || messageData.msgType === 3, // 图片消息不能复制文本
},
{
key: "delete",
key: "quote",
icon: <LinkOutlined />,
label: "引用",
onClick: handleDelete,
},
{
key: "delete",
key: "recall",
icon: <RollbackOutlined />,
label: "撤回",
onClick: handleDelete,
},
];
// 如果是自己发送的消息且发送失败,显示重试选项
if (messageData.isSend && messageData.status === "failed") {
menuItems.unshift({
key: "retry",
icon: <ReloadOutlined />,
label: "重新发送",
onClick: handleRetry,
});
}
return (
<div
ref={menuRef}
@@ -187,7 +137,16 @@ const ClickMenu: React.FC<ClickMenuProps> = ({
}}
>
<Menu
items={menuItems}
items={menuItems.map(item => ({
...item,
onClick: value => {
if (value.key === "copy") {
handleCopy();
} else {
onCommad(value.key);
}
},
}))}
mode="vertical"
selectable={false}
className={styles.menu}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from "react";
import { Avatar } from "antd";
import { Avatar, Checkbox } from "antd";
import { UserOutlined, LoadingOutlined } from "@ant-design/icons";
import AudioMessage from "./components/AudioMessage/AudioMessage";
import SmallProgramMessage from "./components/SmallProgramMessage";
@@ -10,13 +10,12 @@ import { formatWechatTime } from "@/utils/common";
import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji";
import styles from "./MessageRecord.module.scss";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
interface MessageRecordProps {
contract: ContractData | weChatGroup;
}
const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
const messagesEndRef = useRef<HTMLDivElement>(null);
// 右键菜单状态
const [contextMenu, setContextMenu] = useState({
visible: false,
@@ -25,6 +24,9 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
messageData: null as ChatRecord | null,
});
// 选中的聊天记录状态
const [selectedRecords, setSelectedRecords] = useState<ChatRecord[]>([]);
const currentMessages = useWeChatStore(state => state.currentMessages);
const loadChatMessages = useWeChatStore(state => state.loadChatMessages);
const messagesLoading = useWeChatStore(state => state.messagesLoading);
@@ -32,8 +34,13 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
const currentGroupMembers = useWeChatStore(
state => state.currentGroupMembers,
);
const showCheckbox = useWeChatStore(state => state.showCheckbox);
const prevMessagesRef = useRef(currentMessages);
const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox);
const updateEnterModule = useWeChatStore(state => state.updateEnterModule);
const currentKf = useCkChatStore(state =>
state.kfUserList.find(kf => kf.id === state.kfSelected),
);
// 判断是否为表情包URL的工具函数
const isEmojiUrl = (content: string): boolean => {
return (
@@ -503,6 +510,22 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
console.log("重试发送:", messageData);
};
// 处理checkbox选中状态变化
const handleCheckboxChange = (checked: boolean, msg: ChatRecord) => {
if (checked) {
// 添加到选中记录
setSelectedRecords(prev => [...prev, msg]);
} else {
// 从选中记录中移除
setSelectedRecords(prev => prev.filter(record => record.id !== msg.id));
}
};
// 检查消息是否被选中
const isMessageSelected = (msg: ChatRecord) => {
return selectedRecords.some(record => record.id === msg.id);
};
// 用于分组消息并添加时间戳的辅助函数
const groupMessagesByTime = (messages: ChatRecord[]) => {
return messages
@@ -532,6 +555,15 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
{/* 如果不是群聊 */}
{!isGroup && !isOwn && (
<>
{/* Checkbox 显示控制 */}
{showCheckbox && (
<div className={styles.checkboxContainer}>
<Checkbox
checked={isMessageSelected(msg)}
onChange={e => handleCheckboxChange(e.target.checked, msg)}
/>
</div>
)}
<Avatar
size={32}
src={contract.avatar}
@@ -551,6 +583,15 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
{/* 如果是群聊 */}
{isGroup && !isOwn && (
<>
{/* Checkbox 显示控制 */}
{showCheckbox && (
<div className={styles.checkboxContainer}>
<Checkbox
checked={isMessageSelected(msg)}
onChange={e => handleCheckboxChange(e.target.checked, msg)}
/>
</div>
)}
<Avatar
size={32}
src={groupMemberAvatar(msg)}
@@ -575,9 +616,18 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
)}
{isOwn && (
<>
{/* Checkbox 显示控制 */}
{showCheckbox && (
<div className={styles.checkboxContainer}>
<Checkbox
checked={isMessageSelected(msg)}
onChange={e => handleCheckboxChange(e.target.checked, msg)}
/>
</div>
)}
<Avatar
size={32}
src={groupMemberAvatar(msg)}
src={currentKf.avatar}
icon={<UserOutlined />}
className={styles.messageAvatar}
/>
@@ -617,6 +667,26 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
loadChatMessages(false, timestamp);
};
const handCommad = (action: string) => {
switch (action) {
case "transmit":
handleForwardMessage(contextMenu.messageData);
break;
case "multipleForwarding":
// 多条转发逻辑
updateShowCheckbox(!showCheckbox);
updateEnterModule(!showCheckbox ? "common" : "multipleForwarding");
break;
case "quote":
// 引用逻辑
break;
case "recall":
// 撤回逻辑
break;
default:
break;
}
};
return (
<div className={styles.messagesContainer}>
@@ -638,7 +708,6 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
{group.messages
.filter(v => ![10000].includes(v.msgType))
.map(msg => {
console.log("Rendering message with msgType:", msg.msgType);
return renderMessage(msg);
})}
</React.Fragment>
@@ -652,10 +721,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
y={contextMenu.y}
messageData={contextMenu.messageData}
onClose={handleCloseContextMenu}
onCopy={handleCopyMessage}
onDelete={handleDeleteMessage}
onForward={handleForwardMessage}
onRetry={handleRetryMessage}
onCommad={handCommad}
/>
</div>
);

View File

@@ -21,6 +21,11 @@ export interface WeChatState {
messagesLoading: boolean;
isLoadingData: boolean;
currentGroupMembers: any[];
showCheckbox: boolean;
updateShowCheckbox: (show: boolean) => void;
EnterModule: string;
// EnterModule 相关方法
updateEnterModule: (module: string) => void;
MomentCommon: FriendsCircleItem[];
// MomentCommon 相关方法
clearMomentCommon: () => void;

View File

@@ -28,6 +28,14 @@ export const useWeChatStore = create<WeChatState>()(
messagesLoading: false,
isLoadingData: false,
currentGroupMembers: [],
showCheckbox: false,
updateShowCheckbox: (show: boolean) => {
set({ showCheckbox: show });
},
EnterModule: "common",
updateEnterModule: (module: string) => {
set({ EnterModule: module });
},
MomentCommon: [], //朋友圈数据
MomentCommonLoading: false, //朋友圈数据是否正在加载
@@ -329,3 +337,5 @@ export const useCurrentMessages = () =>
useWeChatStore(state => state.currentMessages);
export const useMessagesLoading = () =>
useWeChatStore(state => state.messagesLoading);
export const useShowCheckbox = () =>
useWeChatStore(state => state.showCheckbox);