feat(weChat): 添加视频请求处理和WebSocket监听功能

- 在weChat store中新增pendingVideoRequests状态和相关操作方法
- 实现WebSocket监听处理视频下载响应
- 重构ChatWindow组件,将视频处理逻辑移至store
- 优化消息分组和渲染逻辑
This commit is contained in:
超级老白兔
2025-09-03 15:08:29 +08:00
parent 647e2a5f7e
commit 8b1387a8ed
3 changed files with 155 additions and 127 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useEffect, useRef } from "react";
import { Layout, Button, Avatar, Space, Dropdown, Menu, Tooltip } from "antd";
import {
PhoneOutlined,
@@ -18,10 +18,7 @@ import {
} from "@ant-design/icons";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import styles from "./ChatWindow.module.scss";
import {
useWebSocketStore,
WebSocketMessage,
} from "@/store/module/websocket/websocket";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { formatWechatTime } from "@/utils/common";
import Person from "./components/Person";
import MessageEnter from "./components/MessageEnter";
@@ -39,18 +36,13 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
showProfile = true,
onToggleProfile,
}) => {
const [messages, setMessages] = useState<ChatRecord[]>([]);
const [loading, setLoading] = useState(false);
const [pendingVideoRequests, setPendingVideoRequests] = useState<
Record<string, string>
>({});
const messagesEndRef = useRef<HTMLDivElement>(null);
const currentMessages = useWeChatStore(state => state.currentMessages);
useEffect(() => {
// 只有在非视频加载操作时才自动滚动到底部
// 检查是否有视频正在加载中
const hasLoadingVideo = messages.some(msg => {
const hasLoadingVideo = currentMessages.some(msg => {
try {
const content =
typeof msg.content === "string"
@@ -65,78 +57,13 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
if (!hasLoadingVideo) {
scrollToBottom();
}
}, [messages]);
}, [currentMessages]);
// 添加 WebSocket 消息订阅 - 监听视频下载响应消息
// 初始化WebSocket监听
useEffect(() => {
// 只有当有待处理的视频请求时才订阅WebSocket消息
if (Object.keys(pendingVideoRequests).length === 0) {
return;
}
console.log("开始监听视频下载响应,当前待处理请求:", pendingVideoRequests);
// 订阅 WebSocket 消息变化
const unsubscribe = useWebSocketStore.subscribe(state => {
// 只处理新增的消息
const messages = state.messages as WebSocketMessage[];
// 筛选出视频下载响应消息
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);
// 从待处理队列中移除
setPendingVideoRequests(prev => {
const newRequests = { ...prev };
delete newRequests[messageId];
return newRequests;
});
// 更新消息内容将视频URL添加到对应的消息中
setMessages(prevMessages => {
return prevMessages.map(msg => {
if (msg.id === Number(messageId)) {
try {
const msgContent =
typeof msg.content === "string"
? JSON.parse(msg.content)
: msg.content;
// 更新消息内容添加视频URL并移除加载状态
return {
...msg,
content: JSON.stringify({
...msgContent,
videoUrl: message.content.url,
isLoading: false,
}),
};
} catch (e) {
console.error("解析消息内容失败:", e);
}
}
return msg;
});
});
}
}
});
});
// 组件卸载时取消订阅
return () => {
unsubscribe();
};
}, [pendingVideoRequests]); // 依赖于pendingVideoRequests当队列变化时重新设置订阅
const unsubscribe = useWeChatStore.getState().initWebSocketListener();
return unsubscribe;
}, []);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -157,29 +84,13 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
wechatAccountId: contract.wechatAccountId,
});
// 将消息ID和请求序列号添加到待处理队列
setPendingVideoRequests(prev => ({
...prev,
[messageId]: messageId,
}));
// 将消息ID添加到待处理队列
useWeChatStore
.getState()
.addPendingVideoRequest(messageId.toString(), messageId.toString());
// 更新消息状态为加载中
setMessages(prevMessages => {
return prevMessages.map(msg => {
if (msg.id === messageId) {
// 保存原始内容添加loading状态
const originalContent = msg.content;
return {
...msg,
content: JSON.stringify({
...JSON.parse(originalContent),
isLoading: true,
}),
};
}
return msg;
});
});
useWeChatStore.getState().setMessageLoading(messageId.toString(), true);
};
// 解析消息内容,判断消息类型并返回对应的渲染内容
@@ -613,14 +524,10 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
// 用于分组消息并添加时间戳的辅助函数
const groupMessagesByTime = (messages: ChatRecord[]) => {
const groups: { time: string; messages: ChatRecord[] }[] = [];
messages.forEach(msg => {
// 使用 formatWechatTime 函数格式化时间戳
const formattedTime = formatWechatTime(msg.wechatTime);
groups.push({ time: formattedTime, messages: [msg] });
});
return groups;
return messages.map(msg => ({
time: formatWechatTime(msg.wechatTime),
messages: [msg],
}));
};
const renderMessage = (msg: ChatRecord) => {
@@ -721,21 +628,13 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
{/* 聊天内容 */}
<Content className={styles.chatContent}>
<div className={styles.messagesContainer}>
{loading ? (
<div className={styles.loadingContainer}>
<div>...</div>
</div>
) : (
<>
{groupMessagesByTime(messages).map((group, groupIndex) => (
<React.Fragment key={`group-${groupIndex}`}>
<div className={styles.messageTime}>{group.time}</div>
{group.messages.map(renderMessage)}
</React.Fragment>
))}
<div ref={messagesEndRef} />
</>
)}
{groupMessagesByTime(currentMessages).map((group, groupIndex) => (
<React.Fragment key={`group-${groupIndex}`}>
<div className={styles.messageTime}>{group.time}</div>
{group.messages.map(renderMessage)}
</React.Fragment>
))}
<div ref={messagesEndRef} />
</div>
</Content>

View File

@@ -7,8 +7,22 @@ export interface WeChatState {
// 当前聊天用户的消息列表(只存储当前聊天用户的消息)
currentMessages: ChatRecord[];
setCurrentContact: (contract: ContractData | weChatGroup) => void;
// 消息加载状态
messagesLoading: boolean;
// 待处理的视频请求队列
pendingVideoRequests: Record<string, string>;
// Actions
setCurrentContact: (contract: ContractData | weChatGroup) => void;
loadChatMessages: (contact: ContractData | weChatGroup) => Promise<void>;
// 视频请求相关方法
addPendingVideoRequest: (messageId: string, requestId: string) => void;
removePendingVideoRequest: (messageId: string) => void;
updateMessageWithVideo: (messageId: string, videoUrl: string) => void;
setMessageLoading: (messageId: string, isLoading: boolean) => void;
// WebSocket监听初始化
initWebSocketListener: () => void;
}

View File

@@ -5,6 +5,10 @@ import { WeChatState } from "./weChat.data";
import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { addChatSession } from "@/store/module/ckchat/ckchat";
import {
useWebSocketStore,
WebSocketMessage,
} from "@/store/module/websocket/websocket";
export const useWeChatStore = create<WeChatState>()(
persist(
(set, get) => ({
@@ -12,6 +16,7 @@ export const useWeChatStore = create<WeChatState>()(
currentContract: null,
currentMessages: [],
messagesLoading: false,
pendingVideoRequests: {},
// Actions
setCurrentContact: (contract: ContractData | weChatGroup) => {
@@ -58,7 +63,7 @@ export const useWeChatStore = create<WeChatState>()(
},
setMessageLoading: loading => {
set({ messagesLoading: loading });
set({ messagesLoading: Boolean(loading) });
},
addMessage: message => {
@@ -80,11 +85,121 @@ export const useWeChatStore = create<WeChatState>()(
getCurrentMessages: () => get().currentMessages,
getMessagesLoading: () => get().messagesLoading,
// 视频请求相关方法
addPendingVideoRequest: (messageId: string, requestId: string) => {
set(state => ({
pendingVideoRequests: {
...state.pendingVideoRequests,
[messageId]: requestId,
},
}));
},
removePendingVideoRequest: (messageId: string) => {
set(state => {
const newRequests = { ...state.pendingVideoRequests };
delete newRequests[messageId];
return { pendingVideoRequests: newRequests };
});
},
updateMessageWithVideo: (messageId: string, videoUrl: string) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
if (msg.id === Number(messageId)) {
try {
const msgContent =
typeof msg.content === "string"
? JSON.parse(msg.content)
: msg.content;
return {
...msg,
content: JSON.stringify({
...msgContent,
videoUrl: videoUrl,
isLoading: false,
}),
};
} catch (e) {
console.error("解析消息内容失败:", e);
}
}
return msg;
}),
}));
},
setMessageLoadingStatus: (messageId: string, isLoading: boolean) => {
set(state => ({
currentMessages: state.currentMessages.map(msg => {
if (msg.id === Number(messageId)) {
try {
const originalContent = msg.content;
return {
...msg,
content: JSON.stringify({
...JSON.parse(originalContent),
isLoading: isLoading,
}),
};
} catch (e) {
console.error("解析消息内容失败:", e);
}
}
return msg;
}),
}));
},
// WebSocket监听初始化
initWebSocketListener: () => {
const unsubscribe = useWebSocketStore.subscribe(state => {
const messages = state.messages as WebSocketMessage[];
const currentState = get();
// 只有当有待处理的视频请求时才处理
if (Object.keys(currentState.pendingVideoRequests).length === 0) {
return;
}
messages.forEach(message => {
if (message?.content?.cmdType === "CmdDownloadVideoResult") {
console.log("收到视频下载响应:", message.content);
// 检查是否是我们正在等待的视频响应
const messageId = Object.keys(
currentState.pendingVideoRequests,
).find(
id =>
currentState.pendingVideoRequests[id] ===
message.content.friendMessageId,
);
if (messageId) {
console.log("找到对应的消息ID:", messageId);
// 从待处理队列中移除
currentState.removePendingVideoRequest(messageId);
// 更新消息内容添加视频URL
currentState.updateMessageWithVideo(
messageId,
message.content.url,
);
}
}
});
});
return unsubscribe;
},
clearAllData: () => {
set({
currentContract: null,
currentMessages: [],
messagesLoading: false,
pendingVideoRequests: {},
});
},
}),