From f60bf294c1a4d4a6202d7e2e24895f96a4909ce3 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, 10 Sep 2025 10:02:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=A7=86=E9=A2=91=E6=B6=88=E6=81=AF):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E9=A2=91=E6=B6=88=E6=81=AF=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将视频消息处理逻辑从MessageRecord组件中抽离为独立组件VideoMessage 新增视频消息样式文件VideoMessage.module.scss 优化视频消息的加载状态处理和错误显示 --- Cunkebao/dist/.vite/manifest.json | 4 +- Cunkebao/dist/index.html | 4 +- .../VideoMessage/VideoMessage.module.scss | 153 +++++++++++++++ .../components/VideoMessage/index.tsx | 182 ++++++++++++++++++ .../components/MessageRecord/index.tsx | 175 +---------------- 5 files changed, 346 insertions(+), 172 deletions(-) create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/VideoMessage.module.scss create mode 100644 Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 07c2448c..0e0486f5 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -33,7 +33,7 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-CTEriEiT.js", + "file": "assets/index-PSLRJs-x.js", "name": "index", "src": "index.html", "isEntry": true, @@ -44,7 +44,7 @@ "_charts-ghR_XExL.js" ], "css": [ - "assets/index-ZHlr-6NP.css" + "assets/index-2A02LaoT.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index cc6295d6..9401fa4c 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -11,13 +11,13 @@ - + - +
diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/VideoMessage.module.scss b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/VideoMessage.module.scss new file mode 100644 index 00000000..a90b90a8 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/VideoMessage.module.scss @@ -0,0 +1,153 @@ +// 通用消息文本样式 +.messageText { + color: #666; + font-style: italic; + padding: 8px 12px; + background: #f5f5f5; + border-radius: 8px; + border: 1px solid #e0e0e0; +} + +// 消息气泡样式 +.messageBubble { + display: inline-block; + max-width: 70%; + padding: 8px 12px; + border-radius: 12px; + word-wrap: break-word; + position: relative; +} + +// 视频消息样式 +.videoMessage { + position: relative; + display: inline-block; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background: #000; + + .videoContainer { + position: relative; + display: inline-block; + cursor: pointer; + + video { + display: block; + max-width: 300px; + max-height: 400px; + border-radius: 8px; + } + } + + .videoThumbnail { + display: block; + max-width: 300px; + max-height: 400px; + border-radius: 8px; + cursor: pointer; + transition: opacity 0.3s ease; + } + + .videoPlayIcon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + z-index: 2; + + .loadingSpinner { + width: 48px; + height: 48px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top: 4px solid #fff; + border-radius: 50%; + animation: spin 1s linear infinite; + } + } + + .downloadButton { + position: absolute; + top: 8px; + right: 8px; + width: 32px; + height: 32px; + background: rgba(0, 0, 0, 0.6); + border-radius: 50%; + color: white; + border: none; + cursor: pointer; + transition: background 0.2s; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + z-index: 3; + + &:hover { + background: rgba(0, 0, 0, 0.8); + color: white; + } + } + + .playButton { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 48px; + height: 48px; + background: rgba(0, 0, 0, 0.6); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; + + svg { + margin-left: 2px; // 视觉居中调整 + } + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +// 响应式设计 +@media (max-width: 768px) { + .messageBubble { + padding: 6px 10px; + } + + .videoMessage .videoThumbnail, + .videoMessage .videoContainer video { + max-width: 200px; + max-height: 250px; + } + + .videoMessage .videoPlayIcon { + .loadingSpinner { + width: 36px; + height: 36px; + border-width: 3px; + } + } + + .videoMessage .downloadButton { + width: 28px; + height: 28px; + top: 6px; + right: 6px; + + svg { + font-size: 14px !important; + } + } +} \ No newline at end of file diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx new file mode 100644 index 00000000..1edbcd30 --- /dev/null +++ b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage/index.tsx @@ -0,0 +1,182 @@ +import React from "react"; +import { DownloadOutlined, PlayCircleFilled } from "@ant-design/icons"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import styles from "./VideoMessage.module.scss"; + +interface VideoMessageProps { + content: string; + msg: ChatRecord; + contract: ContractData | weChatGroup; +} + +const VideoMessage: React.FC = ({ + content, + msg, + contract, +}) => { + // 检测是否为直接视频链接的函数 + const isDirectVideoLink = (content: string): boolean => { + const trimmedContent = content.trim(); + return ( + trimmedContent.startsWith("http") && + (trimmedContent.includes(".mp4") || + trimmedContent.includes(".mov") || + trimmedContent.includes(".avi") || + trimmedContent.includes("video")) + ); + }; + + // 处理视频播放请求,发送socket请求获取真实视频地址 + const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => { + console.log("发送视频下载请求:", { messageId, tencentUrl }); + + // 先设置加载状态 + useWeChatStore.getState().setVideoLoading(messageId, true); + + // 构建socket请求数据 + useWebSocketStore.getState().sendCommand("CmdDownloadVideo", { + chatroomMessageId: contract.chatroomId ? messageId : 0, + friendMessageId: contract.chatroomId ? 0 : messageId, + seq: `${+new Date()}`, // 使用时间戳作为请求序列号 + tencentUrl: tencentUrl, + wechatAccountId: contract.wechatAccountId, + }); + }; + + // 渲染错误消息 + const renderErrorMessage = (message: string) => ( +
{message}
+ ); + + if (typeof content !== "string" || !content.trim()) { + return renderErrorMessage("[视频消息 - 无效内容]"); + } + + // 如果content是直接的视频链接(已预览过或下载好的视频) + if (isDirectVideoLink(content)) { + return ( +
+
+
+
+
+
+ ); + } + + try { + // 尝试解析JSON格式的视频数据 + if (content.startsWith("{") && content.endsWith("}")) { + const videoData = JSON.parse(content); + + // 验证必要的视频数据字段 + if ( + videoData && + typeof videoData === "object" && + videoData.previewImage && + videoData.tencentUrl + ) { + const previewImageUrl = String(videoData.previewImage).replace( + /[`"']/g, + "", + ); + + // 创建点击处理函数 + const handlePlayClick = (e: React.MouseEvent, msg: ChatRecord) => { + e.stopPropagation(); + // 如果没有视频URL且不在加载中,则发起下载请求 + if (!videoData.videoUrl && !videoData.isLoading) { + handleVideoPlayRequest(videoData.tencentUrl, msg.id); + } + }; + + // 如果已有视频URL,显示视频播放器 + if (videoData.videoUrl) { + return ( +
+
+
+
+
+
+ ); + } + + // 显示预览图,根据加载状态显示不同的图标 + return ( +
+
+
handlePlayClick(e, msg)} + > + 视频预览 { + const target = e.target as HTMLImageElement; + const parent = target.parentElement?.parentElement; + if (parent) { + parent.innerHTML = `
[视频预览加载失败]
`; + } + }} + /> +
+ {videoData.isLoading ? ( +
+ ) : ( + + )} +
+
+
+
+ ); + } + } + return renderErrorMessage("[视频消息]"); + } catch (e) { + console.warn("视频消息解析失败:", e); + return renderErrorMessage("[视频消息 - 解析失败]"); + } +}; + +export default VideoMessage; diff --git a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index dfebaab4..001c0954 100644 --- a/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -1,19 +1,14 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef } from "react"; import { Avatar, Divider } from "antd"; -import { - UserOutlined, - LoadingOutlined, - DownloadOutlined, - PlayCircleFilled, -} from "@ant-design/icons"; +import { UserOutlined, LoadingOutlined } from "@ant-design/icons"; import AudioMessage from "./components/AudioMessage/AudioMessage"; import SmallProgramMessage from "./components/SmallProgramMessage"; +import VideoMessage from "./components/VideoMessage"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; -import { formatWechatTime, parseWeappMsgStr } from "@/utils/common"; +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 { useWebSocketStore } from "@/store/module/websocket/websocket"; interface MessageRecordProps { contract: ContractData | weChatGroup; @@ -30,18 +25,6 @@ const MessageRecord: React.FC = ({ contract }) => { ); const prevMessagesRef = useRef(currentMessages); - // 检测是否为直接视频链接的函数 - const isDirectVideoLink = (content: string): boolean => { - const trimmedContent = content.trim(); - return ( - trimmedContent.startsWith("http") && - (trimmedContent.includes(".mp4") || - trimmedContent.includes(".mov") || - trimmedContent.includes(".avi") || - trimmedContent.includes("video")) - ); - }; - // 判断是否为表情包URL的工具函数 const isEmojiUrl = (content: string): boolean => { return ( @@ -156,23 +139,6 @@ const MessageRecord: React.FC = ({ contract }) => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; - // 处理视频播放请求,发送socket请求获取真实视频地址 - const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => { - console.log("发送视频下载请求:", { messageId, tencentUrl }); - - // 先设置加载状态 - useWeChatStore.getState().setVideoLoading(messageId, true); - - // 构建socket请求数据 - useWebSocketStore.getState().sendCommand("CmdDownloadVideo", { - chatroomMessageId: contract.chatroomId ? messageId : 0, - friendMessageId: contract.chatroomId ? 0 : messageId, - seq: `${+new Date()}`, // 使用时间戳作为请求序列号 - tencentUrl: tencentUrl, - wechatAccountId: contract.wechatAccountId, - }); - }; - // 解析消息内容,根据msgType判断消息类型并返回对应的渲染内容 const parseMessageContent = ( content: string | null | undefined, @@ -228,136 +194,9 @@ const MessageRecord: React.FC = ({ contract }) => { ); case 43: // 视频消息 - if (typeof content !== "string" || !content.trim()) { - return renderErrorMessage("[视频消息 - 无效内容]"); - } - - // 如果content是直接的视频链接(已预览过或下载好的视频) - if (isDirectVideoLink(content)) { - return ( - - ); - } - - try { - // 尝试解析JSON格式的视频数据 - if (content.startsWith("{") && content.endsWith("}")) { - const videoData = JSON.parse(content); - - // 验证必要的视频数据字段 - if ( - videoData && - typeof videoData === "object" && - videoData.previewImage && - videoData.tencentUrl - ) { - const previewImageUrl = String(videoData.previewImage).replace( - /[`"']/g, - "", - ); - - // 创建点击处理函数 - const handlePlayClick = ( - e: React.MouseEvent, - msg: ChatRecord, - ) => { - e.stopPropagation(); - // 如果没有视频URL且不在加载中,则发起下载请求 - if (!videoData.videoUrl && !videoData.isLoading) { - handleVideoPlayRequest(videoData.tencentUrl, msg.id); - } - }; - - // 如果已有视频URL,显示视频播放器 - if (videoData.videoUrl) { - return ( - - ); - } - - // 显示预览图,根据加载状态显示不同的图标 - return ( -
-
-
handlePlayClick(e, msg)} - > - 视频预览 { - const target = e.target as HTMLImageElement; - const parent = target.parentElement?.parentElement; - if (parent) { - parent.innerHTML = `
[视频预览加载失败]
`; - } - }} - /> -
- {videoData.isLoading ? ( -
- ) : ( - - )} -
-
-
-
- ); - } - } - return renderErrorMessage("[视频消息]"); - } catch (e) { - console.warn("视频消息解析失败:", e); - return renderErrorMessage("[视频消息 - 解析失败]"); - } + return ( + + ); case 47: // 动图表情包(gif、其他表情包) if (typeof content !== "string" || !content.trim()) {