feat(weChat): 添加消息多选转发功能
- 在微信聊天记录组件中添加复选框支持多选消息 - 新增showCheckbox状态和updateShowCheckbox方法控制复选框显示 - 重构右键菜单处理逻辑,统一使用onCommad回调 - 添加EnterModule状态管理不同功能模块 - 调整消息项布局样式以适配复选框
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user