feat(wechat): 实现微信聊天状态管理及消息处理功能

添加微信聊天状态管理store,包含联系人管理、消息发送/接收、未读消息计数等功能
修复聊天窗口参数传递问题,统一使用contract.id作为标识
调整消息加载数量从10条减少到5条
This commit is contained in:
超级老白兔
2025-09-03 11:25:10 +08:00
parent c6da2062f2
commit 48880bed0d
4 changed files with 606 additions and 4 deletions

View File

@@ -38,7 +38,7 @@ const MessageEnter: React.FC<MessageEnterProps> = ({
console.log("发送消息", contract);
const params = {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract?.chatroomId || 0,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
msgSubType: 0,
msgType: 1,

View File

@@ -64,11 +64,11 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
wechatAccountId: contract.wechatAccountId,
From: 1,
To: 4704624000000,
Count: 10,
Count: 5,
olderData: true,
};
if (contract.chatroomId) {
params.wechatChatroomId = contract.chatroomId;
params.wechatChatroomId = contract.id;
} else {
params.wechatFriendId = contract.id;
}

View File

@@ -0,0 +1,601 @@
import { create } from "zustand";
import { createPersistStore } from "../createPersistStore";
import { getChatMessages } from "@/pages/pc/ckbox/api";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useWebSocketStore } from "./websocket/websocket";
// 聊天列表项类型定义
export interface ChatListItem {
id: string;
name: string;
avatar?: string;
type: "friend" | "group";
lastMessage?: ChatRecord;
unreadCount: number;
isOnline?: boolean;
lastActiveTime: number; // 最后活跃时间,用于排序
contractData: ContractData | weChatGroup; // 保存完整的联系人数据
}
// 微信聊天相关的类型定义
export interface WeChatState {
// 当前选中的联系人/群组
currentContract: ContractData | weChatGroup | null;
// 当前聊天用户的消息列表(只存储当前聊天用户的消息)
currentMessages: ChatRecord[];
// 联系人列表(所有好友和群组)
contacts: (ContractData | weChatGroup)[];
// 聊天列表(有聊天记录的联系人/群组,按最后活跃时间排序)
chatList: ChatListItem[];
// 消息发送状态
sendingStatus: Record<string, boolean>; // key为消息临时IDvalue为发送状态
// 消息加载状态
loadingMessages: boolean;
// 待处理的视频请求
pendingVideoRequests: Record<string, string>; // messageId -> requestId
// 输入框内容状态 - 按联系人/群组ID分组存储
inputValues: Record<string, string>;
// 素材模态框状态
showMaterialModal: boolean;
selectedMaterialType: string;
}
export interface WeChatActions {
// 设置当前选中的联系人/群组
setCurrentContract: (contract: ContractData | weChatGroup | null) => void;
// 获取聊天消息
fetchChatMessages: (contract: ContractData | weChatGroup) => Promise<void>;
// 设置当前消息列表
setCurrentMessages: (messages: ChatRecord[]) => void;
// 添加消息到当前聊天
addMessageToCurrentChat: (message: ChatRecord) => void;
// 更新消息内容(用于视频加载状态更新等)
updateMessage: (messageId: number, updates: Partial<ChatRecord>) => void;
// 发送消息
sendMessage: (
contract: ContractData | weChatGroup,
content: string,
msgType?: number,
) => Promise<void>;
// 处理视频播放请求
handleVideoPlayRequest: (
tencentUrl: string,
messageId: number,
contract: ContractData | weChatGroup,
) => void;
// 设置待处理的视频请求
setPendingVideoRequest: (messageId: string, requestId: string) => void;
// 移除待处理的视频请求
removePendingVideoRequest: (messageId: string) => void;
// 处理视频下载响应
handleVideoDownloadResponse: (messageId: string, videoUrl: string) => void;
// 设置输入框内容
setInputValue: (contractId: string, value: string) => void;
// 获取输入框内容
getInputValue: (contractId: string) => string;
// 清空输入框内容
clearInputValue: (contractId: string) => void;
// 素材模态框相关操作
setShowMaterialModal: (show: boolean) => void;
setSelectedMaterialType: (type: string) => void;
// 新消息处理
handleNewMessage: (
message: ChatRecord,
fromContract: ContractData | weChatGroup,
) => void;
// 聊天列表管理
setContacts: (contacts: (ContractData | weChatGroup)[]) => void;
setChatList: (chatList: ChatListItem[]) => void;
updateChatListItem: (
contractId: string,
updates: Partial<ChatListItem>,
) => void;
moveToTopOfChatList: (contractId: string) => void;
addToChatList: (contract: ContractData | weChatGroup) => void;
// 未读消息管理
incrementUnreadCount: (contractId: string) => void;
clearUnreadCount: (contractId: string) => void;
// 便捷选择器
getUnreadTotal: () => number;
getChatListSorted: () => ChatListItem[];
// 清理指定联系人的数据
clearContractData: (contractId: string) => void;
// 清理所有数据
clearAllData: () => void;
}
type WeChatStore = WeChatState & WeChatActions;
// 生成联系人/群组的唯一ID
const getContractId = (contract: ContractData | weChatGroup): string => {
if ("chatroomId" in contract && contract.chatroomId) {
return `group_${contract.chatroomId}`;
}
return `friend_${contract.id}`;
};
export const useWeChatStore = create<WeChatStore>()(
createPersistStore(
(set, get) => ({
// 初始状态
currentContract: null,
currentMessages: [],
contacts: [],
chatList: [],
sendingStatus: {},
loadingMessages: false,
pendingVideoRequests: {},
inputValues: {},
showMaterialModal: false,
selectedMaterialType: "",
// Actions
setCurrentContract: contract => {
set({ currentContract: contract });
// 切换联系人时清空当前消息,等待重新加载
set({ currentMessages: [] });
// 清除该联系人的未读数
if (contract) {
const contractId = getContractId(contract);
get().clearUnreadCount(contractId);
}
},
fetchChatMessages: async contract => {
const { currentMessages } = get();
// 如果已经有消息数据,不重复加载
if (currentMessages.length > 0) {
return;
}
set({ loadingMessages: true });
try {
const params: any = {
wechatAccountId: contract.wechatAccountId,
From: 1,
To: 4704624000000,
Count: 10,
olderData: true,
};
if ("chatroomId" in contract && contract.chatroomId) {
params.wechatChatroomId = contract.chatroomId;
} else {
params.wechatFriendId = contract.id;
}
const messages = await getChatMessages(params);
set({ currentMessages: messages });
} catch (error) {
console.error("获取聊天消息失败:", error);
} finally {
set({ loadingMessages: false });
}
},
setCurrentMessages: messages => {
set({ currentMessages: messages });
},
addMessageToCurrentChat: message => {
set(state => ({
currentMessages: [...state.currentMessages, message],
}));
},
updateMessage: (messageId, updates) => {
set(state => ({
currentMessages: state.currentMessages.map(msg =>
msg.id === messageId ? { ...msg, ...updates } : msg,
),
}));
},
sendMessage: async (contract, content, msgType = 1) => {
const contractId = getContractId(contract);
const tempId = `temp_${Date.now()}`;
// 设置发送状态
set(state => ({
sendingStatus: {
...state.sendingStatus,
[tempId]: true,
},
}));
try {
const params = {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId:
"chatroomId" in contract && contract.chatroomId
? contract.chatroomId
: 0,
wechatFriendId:
"chatroomId" in contract && contract.chatroomId ? 0 : contract.id,
msgSubType: 0,
msgType,
content,
};
// 通过WebSocket发送消息
const { sendCommand } = useWebSocketStore.getState();
await sendCommand("CmdSendMessage", params);
// 清空对应的输入框内容
get().clearInputValue(contractId);
} catch (error) {
console.error("发送消息失败:", error);
} finally {
// 移除发送状态
set(state => {
const newSendingStatus = { ...state.sendingStatus };
delete newSendingStatus[tempId];
return { sendingStatus: newSendingStatus };
});
}
},
handleVideoPlayRequest: (tencentUrl, messageId, contract) => {
const requestSeq = `${Date.now()}`;
console.log("发送视频下载请求:", { messageId, requestSeq });
// 构建socket请求数据
const { sendCommand } = useWebSocketStore.getState();
sendCommand("CmdDownloadVideo", {
chatroomMessageId:
"chatroomId" in contract && contract.chatroomId ? messageId : 0,
friendMessageId:
"chatroomId" in contract && contract.chatroomId ? 0 : messageId,
seq: requestSeq,
tencentUrl: tencentUrl,
wechatAccountId: contract.wechatAccountId,
});
// 添加到待处理队列
get().setPendingVideoRequest(
messageId.toString(),
messageId.toString(),
);
// 更新消息状态为加载中
const messages = get().currentMessages;
const targetMessage = messages.find(msg => msg.id === messageId);
if (targetMessage) {
try {
const originalContent =
typeof targetMessage.content === "string"
? JSON.parse(targetMessage.content)
: targetMessage.content;
get().updateMessage(messageId, {
content: JSON.stringify({
...originalContent,
isLoading: true,
}),
});
} catch (e) {
console.error("解析消息内容失败:", e);
}
}
},
setPendingVideoRequest: (messageId, requestId) => {
set(state => ({
pendingVideoRequests: {
...state.pendingVideoRequests,
[messageId]: requestId,
},
}));
},
removePendingVideoRequest: messageId => {
set(state => {
const newRequests = { ...state.pendingVideoRequests };
delete newRequests[messageId];
return { pendingVideoRequests: newRequests };
});
},
handleVideoDownloadResponse: (messageId, videoUrl) => {
const { currentMessages } = get();
const targetMessage = currentMessages.find(
msg => msg.id === Number(messageId),
);
if (targetMessage) {
try {
const msgContent =
typeof targetMessage.content === "string"
? JSON.parse(targetMessage.content)
: targetMessage.content;
// 更新消息内容添加视频URL并移除加载状态
get().updateMessage(Number(messageId), {
content: JSON.stringify({
...msgContent,
videoUrl: videoUrl,
isLoading: false,
}),
});
// 从待处理队列中移除
get().removePendingVideoRequest(messageId);
} catch (e) {
console.error("解析消息内容失败:", e);
}
}
},
setInputValue: (contractId, value) => {
set(state => ({
inputValues: {
...state.inputValues,
[contractId]: value,
},
}));
},
getInputValue: contractId => {
return get().inputValues[contractId] || "";
},
clearInputValue: contractId => {
set(state => {
const newInputValues = { ...state.inputValues };
delete newInputValues[contractId];
return { inputValues: newInputValues };
});
},
setShowMaterialModal: show => {
set({ showMaterialModal: show });
},
setSelectedMaterialType: type => {
set({ selectedMaterialType: type });
},
// 新消息处理
handleNewMessage: (message, fromContract) => {
const { currentContract } = get();
const contractId = getContractId(fromContract);
// 如果是当前聊天用户的消息,直接添加到当前消息列表
if (currentContract && getContractId(currentContract) === contractId) {
get().addMessageToCurrentChat(message);
} else {
// 如果不是当前聊天用户,更新聊天列表
get().incrementUnreadCount(contractId);
get().moveToTopOfChatList(contractId);
// 更新聊天列表项的最后消息
get().updateChatListItem(contractId, {
lastMessage: message,
lastActiveTime: message.createTime || Date.now(),
});
}
},
// 聊天列表管理
setContacts: contacts => {
set({ contacts });
},
setChatList: chatList => {
set({ chatList });
},
updateChatListItem: (contractId, updates) => {
set(state => ({
chatList: state.chatList.map(item =>
getContractId(item.contractData) === contractId
? { ...item, ...updates }
: item,
),
}));
},
moveToTopOfChatList: contractId => {
set(state => {
const chatList = [...state.chatList];
const itemIndex = chatList.findIndex(
item => getContractId(item.contractData) === contractId,
);
if (itemIndex > 0) {
// 移动到顶部
const item = chatList.splice(itemIndex, 1)[0];
chatList.unshift({ ...item, lastActiveTime: Date.now() });
} else if (itemIndex === -1) {
// 如果不在聊天列表中,从联系人列表找到并添加
const contract = state.contacts.find(
c => getContractId(c) === contractId,
);
if (contract) {
get().addToChatList(contract);
}
}
return { chatList };
});
},
addToChatList: contract => {
set(state => {
const contractId = getContractId(contract);
// 检查是否已存在
const exists = state.chatList.some(
item => getContractId(item.contractData) === contractId,
);
if (exists) return state;
const newChatItem: ChatListItem = {
id: contractId,
name: contract.nickName || contract.name || "",
avatar: contract.avatar,
type: "chatroomId" in contract ? "group" : "friend",
unreadCount: 0,
lastActiveTime: Date.now(),
contractData: contract,
};
return {
chatList: [newChatItem, ...state.chatList],
};
});
},
// 未读消息管理
incrementUnreadCount: contractId => {
// 更新聊天列表中的未读数
set(state => ({
chatList: state.chatList.map(item =>
getContractId(item.contractData) === contractId
? { ...item, unreadCount: item.unreadCount + 1 }
: item,
),
}));
},
clearUnreadCount: contractId => {
// 清除聊天列表中的未读数
set(state => ({
chatList: state.chatList.map(item =>
getContractId(item.contractData) === contractId
? { ...item, unreadCount: 0 }
: item,
),
}));
},
// 便捷选择器
getUnreadTotal: () => {
const { chatList } = get();
return chatList.reduce((total, item) => total + item.unreadCount, 0);
},
getChatListSorted: () => {
const { chatList } = get();
return [...chatList].sort(
(a, b) => b.lastActiveTime - a.lastActiveTime,
);
},
clearContractData: contractId => {
set(state => {
const newInputValues = { ...state.inputValues };
delete newInputValues[contractId];
return {
inputValues: newInputValues,
chatList: state.chatList.filter(
item => getContractId(item.contractData) !== contractId,
),
};
});
},
clearAllData: () => {
set({
currentContract: null,
currentMessages: [],
contacts: [],
chatList: [],
sendingStatus: {},
loadingMessages: false,
pendingVideoRequests: {},
inputValues: {},
showMaterialModal: false,
selectedMaterialType: "",
});
},
}),
{
name: "wechat-store",
// 只持久化部分状态,排除临时状态
partialize: state => ({
contacts: state.contacts,
chatList: state.chatList,
inputValues: state.inputValues,
}),
},
),
);
// 导出便捷的选择器函数
export const useCurrentContract = () =>
useWeChatStore(state => state.currentContract);
export const useCurrentMessages = () =>
useWeChatStore(state => state.currentMessages);
export const useCurrentInputValue = () => {
const { currentContract, getInputValue } = useWeChatStore();
if (!currentContract) return "";
const contractId = getContractId(currentContract);
return getInputValue(contractId);
};
export const useChatList = () =>
useWeChatStore(state => state.getChatListSorted());
export const useUnreadTotal = () =>
useWeChatStore(state => state.getUnreadTotal());
// 初始化WebSocket消息监听
if (typeof window !== "undefined") {
// 监听WebSocket消息处理视频下载响应
useWebSocketStore.subscribe(state => {
const messages = state.messages as any[];
const { pendingVideoRequests, handleVideoDownloadResponse } =
useWeChatStore.getState();
// 只有当有待处理的视频请求时才处理消息
if (Object.keys(pendingVideoRequests).length === 0) {
return;
}
messages.forEach(message => {
if (message?.content?.cmdType === "CmdDownloadVideoResult") {
console.log("收到视频下载响应:", message.content);
// 检查是否是我们正在等待的视频响应
const messageId = Object.keys(pendingVideoRequests).find(
id => pendingVideoRequests[id] === message.content.friendMessageId,
);
if (messageId) {
console.log("找到对应的消息ID:", messageId);
handleVideoDownloadResponse(messageId, message.content.url);
}
}
});
});
}

View File

@@ -2,6 +2,7 @@
import { deepCopy } from "@/utils/common";
import { WebSocketMessage } from "./websocket";
import { getkfUserList, asyncKfUserList } from "@/store/module/ckchat/ckchat";
import { Messages } from "./msg.data";
// 消息处理器类型定义
type MessageHandler = (message: WebSocketMessage) => void;
@@ -30,7 +31,7 @@ const messageHandlers: Record<string, MessageHandler> = {
// 在这里添加具体的处理逻辑
},
//收到消息
CmdNewMessage: message => {
CmdNewMessage: (message: Messages) => {
console.log("收到消息", message.friendMessage);
// 在这里添加具体的处理逻辑
},