From 48880bed0d8eecf0b9ce1b09ff626a8abe416fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 3 Sep 2025 11:25:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(wechat):=20=E5=AE=9E=E7=8E=B0=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E8=81=8A=E5=A4=A9=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8F=8A=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加微信聊天状态管理store,包含联系人管理、消息发送/接收、未读消息计数等功能 修复聊天窗口参数传递问题,统一使用contract.id作为标识 调整消息加载数量从10条减少到5条 --- .../components/MessageEnter/index.tsx | 2 +- .../pc/ckbox/components/ChatWindow/index.tsx | 4 +- Cunkebao/src/store/module/weChat.ts | 601 ++++++++++++++++++ .../src/store/module/websocket/msgManage.ts | 3 +- 4 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 Cunkebao/src/store/module/weChat.ts diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx index e869a45d..0f4650d2 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx @@ -38,7 +38,7 @@ const MessageEnter: React.FC = ({ 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, diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 565881be..22e96c0f 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -64,11 +64,11 @@ const ChatWindow: React.FC = ({ 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; } diff --git a/Cunkebao/src/store/module/weChat.ts b/Cunkebao/src/store/module/weChat.ts new file mode 100644 index 00000000..e1cf17a4 --- /dev/null +++ b/Cunkebao/src/store/module/weChat.ts @@ -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; // key为消息临时ID,value为发送状态 + + // 消息加载状态 + loadingMessages: boolean; + + // 待处理的视频请求 + pendingVideoRequests: Record; // messageId -> requestId + + // 输入框内容状态 - 按联系人/群组ID分组存储 + inputValues: Record; + + // 素材模态框状态 + showMaterialModal: boolean; + selectedMaterialType: string; +} + +export interface WeChatActions { + // 设置当前选中的联系人/群组 + setCurrentContract: (contract: ContractData | weChatGroup | null) => void; + + // 获取聊天消息 + fetchChatMessages: (contract: ContractData | weChatGroup) => Promise; + + // 设置当前消息列表 + setCurrentMessages: (messages: ChatRecord[]) => void; + + // 添加消息到当前聊天 + addMessageToCurrentChat: (message: ChatRecord) => void; + + // 更新消息内容(用于视频加载状态更新等) + updateMessage: (messageId: number, updates: Partial) => void; + + // 发送消息 + sendMessage: ( + contract: ContractData | weChatGroup, + content: string, + msgType?: number, + ) => Promise; + + // 处理视频播放请求 + 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, + ) => 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()( + 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); + } + } + }); + }); +} diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts index 2883be13..484058af 100644 --- a/Cunkebao/src/store/module/websocket/msgManage.ts +++ b/Cunkebao/src/store/module/websocket/msgManage.ts @@ -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 = { // 在这里添加具体的处理逻辑 }, //收到消息 - CmdNewMessage: message => { + CmdNewMessage: (message: Messages) => { console.log("收到消息", message.friendMessage); // 在这里添加具体的处理逻辑 },