Refactor IndexController to return a simple message instead of an iframe. Update AudioRecorder and SimpleFileUpload components to include file name in the upload callback. Modify MessageEnter component to handle new file structure and improve message handling logic. Clean up unused state and enhance video message rendering. Update websocket message management to handle message IDs more robustly.

This commit is contained in:
超级老白兔
2025-11-08 15:13:20 +08:00
parent f9c08b6091
commit 35c9369d3d
7 changed files with 162 additions and 186 deletions

View File

@@ -8,25 +8,7 @@ class IndexController
{
public function index(Request $request)
{
return <<<EOF
<style>
* {
padding: 0;
margin: 0;
}
iframe {
border: none;
overflow: scroll;
}
</style>
<iframe
src="https://www.workerman.net/wellcome"
width="100%"
height="100%"
allow="clipboard-write"
sandbox="allow-scripts allow-same-origin allow-popups"
></iframe>
EOF;
return "我是数据中心,有何贵干?";
}
public function view(Request $request)

View File

@@ -0,0 +1,16 @@
<?php
namespace app\controller;
use support\Request;
class UserController
{
public function hello(Request $request)
{
$default_name = 'webman';
// 从get请求里获得name参数如果没有传递name参数则返回$default_name
$name = $request->get('name', $default_name);
// 向浏览器返回字符串
return response('hello ' . $name);
}
}

View File

@@ -10,7 +10,11 @@ import {
import { uploadFile } from "@/api/common";
interface AudioRecorderProps {
onAudioUploaded: (audioData: { url: string; durationMs: number }) => void;
onAudioUploaded: (audioData: {
url: string;
name: string;
durationMs?: number;
}) => void;
className?: string;
disabled?: boolean;
maxDuration?: number; // 最大录音时长(秒)
@@ -206,6 +210,7 @@ const AudioRecorder: React.FC<AudioRecorderProps> = ({
// 调用回调函数传递音频URL和时长毫秒
onAudioUploaded({
url: filePath,
name: audioFile.name,
durationMs: recordingTime * 1000, // 将秒转换为毫秒
});

View File

@@ -3,7 +3,7 @@ import React, { useRef } from "react";
import { message } from "antd";
interface SimpleFileUploadProps {
onFileUploaded?: (filePath: string) => void;
onFileUploaded?: (filePath: { name: string; url: string }) => void;
maxSize?: number; // 最大文件大小(MB)
type?: number; // 1: 图片, 2: 视频, 3: 音频, 4: 文件
slot?: React.ReactNode;
@@ -51,7 +51,10 @@ const SimpleFileUpload: React.FC<SimpleFileUploadProps> = ({
try {
const fileUrl = await uploadFile(file);
onFileUploaded?.(fileUrl);
onFileUploaded?.({
name: file.name,
url: fileUrl,
});
message.success("文件上传成功");
} catch (error: any) {
console.error("文件上传失败:", error);

View File

@@ -34,7 +34,6 @@ const { sendCommand } = useWebSocketStore.getState();
const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
const [inputValue, setInputValue] = useState("");
const [showMaterialModal, setShowMaterialModal] = useState(false);
const EnterModule = useWeChatStore(state => state.EnterModule);
const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox);
const updateEnterModule = useWeChatStore(state => state.updateEnterModule);
@@ -254,20 +253,43 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
FILE: 5,
};
const handleFileUploaded = (
filePath: string | { url: string; durationMs: number },
filePath: { url: string; name: string; durationMs?: number },
fileType: number,
) => {
console.log("handleFileUploaded: ", fileType, filePath);
// msgType(1:文本 3:图片 43:视频 47:动图表情包gif、其他表情包 49:小程序/其他:图文、文件)
let msgType = 1;
let content: any = "";
if ([FileType.TEXT].includes(fileType)) {
msgType = getMsgTypeByFileFormat(filePath as string);
msgType = getMsgTypeByFileFormat(filePath.url);
} else if ([FileType.IMAGE].includes(fileType)) {
msgType = 3;
content = filePath.url;
} else if ([FileType.AUDIO].includes(fileType)) {
msgType = 34;
content = JSON.stringify({
url: filePath.url,
durationMs: filePath.durationMs,
});
} else if ([FileType.FILE].includes(fileType)) {
msgType = 49;
msgType = getMsgTypeByFileFormat(filePath.url);
if (msgType === 3) {
content = filePath.url;
}
if (msgType === 43) {
content = filePath.url;
}
if (msgType === 49) {
content = JSON.stringify({
type: "file",
title: filePath.name,
url: filePath.url,
});
}
}
const messageId = +Date.now();
const params = {
wechatAccountId: contract.wechatAccountId,
@@ -275,10 +297,37 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
msgSubType: 0,
msgType,
content: [FileType.AUDIO].includes(fileType)
? JSON.stringify(filePath)
: filePath,
content: content,
seq: messageId,
};
// 构造本地消息对象
const localMessage: ChatRecord = {
id: messageId, // 使用时间戳作为临时ID
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract?.chatroomId ? 0 : contract.id,
wechatChatroomId: contract?.chatroomId ? contract.id : 0,
tenantId: 0,
accountId: 0,
synergyAccountId: 0,
content: params.content,
msgType: msgType,
msgSubType: 0,
msgSvrId: "",
isSend: true, // 标记为发送中
createTime: new Date().toISOString(),
isDeleted: false,
deleteTime: "",
sendStatus: 1,
wechatTime: Date.now(),
origin: 0,
msgId: 0,
recalled: false,
seq: messageId,
};
// 先插入本地数据
addMessage(localMessage);
sendCommand("CmdSendMessage", params);
};
@@ -349,10 +398,10 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
<div className={styles.leftTool}>
<EmojiPicker onEmojiSelect={handleEmojiSelect} />
<SimpleFileUpload
onFileUploaded={filePath =>
handleFileUploaded(filePath, FileType.FILE)
onFileUploaded={fileInfo =>
handleFileUploaded(fileInfo, FileType.FILE)
}
maxSize={1}
maxSize={10}
type={4}
slot={
<Button
@@ -363,10 +412,10 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
}
/>
<SimpleFileUpload
onFileUploaded={filePath =>
handleFileUploaded(filePath, FileType.IMAGE)
onFileUploaded={fileInfo =>
handleFileUploaded(fileInfo, FileType.IMAGE)
}
maxSize={1}
maxSize={10}
type={1}
slot={
<Button
@@ -379,7 +428,14 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
<AudioRecorder
onAudioUploaded={audioData =>
handleFileUploaded(audioData, FileType.AUDIO)
handleFileUploaded(
{
name: audioData.name,
url: audioData.url,
durationMs: audioData.durationMs,
},
FileType.AUDIO,
)
}
className={styles.toolbarButton}
/>
@@ -462,87 +518,7 @@ const MessageEnter: React.FC<MessageEnterProps> = ({ contract }) => {
</>
)}
</Footer>
{/* 素材选择模态框 */}
<Modal
title="选择素材"
open={showMaterialModal}
onCancel={() => setShowMaterialModal(false)}
footer={[
<Button key="cancel" onClick={() => setShowMaterialModal(false)}>
</Button>,
<Button
key="confirm"
type="primary"
onClick={() => setShowMaterialModal(false)}
>
</Button>,
]}
width={800}
>
<div style={{ display: "flex", height: "400px" }}>
{/* 左侧素材分类 */}
<div
style={{
width: "200px",
background: "#f5f5f5",
borderRight: "1px solid #e8e8e8",
}}
>
<div style={{ padding: "16px", borderBottom: "1px solid #e8e8e8" }}>
<h4 style={{ margin: 0, color: "#262626" }}></h4>
</div>
<div style={{ padding: "8px 0" }}>
<div
style={{
padding: "8px 16px",
cursor: "pointer",
background: "#e6f7ff",
borderLeft: "3px solid #1890ff",
color: "#1890ff",
}}
>
4
</div>
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
...
</div>
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
D2辅助
</div>
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
ROS反馈演示...
</div>
<div style={{ padding: "8px 16px", cursor: "pointer" }}>
...
</div>
</div>
<div style={{ padding: "16px", borderTop: "1px solid #e8e8e8" }}>
<h4 style={{ margin: 0, color: "#262626" }}></h4>
</div>
</div>
{/* 右侧内容区域 */}
<div style={{ flex: 1, padding: "16px" }}>
<div style={{ marginBottom: "16px" }}>
<Input.Search placeholder="昵称" style={{ width: "100%" }} />
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "300px",
color: "#8c8c8c",
}}
>
</div>
</div>
</div>
</Modal>
</>
);
};

View File

@@ -57,24 +57,22 @@ const VideoMessage: React.FC<VideoMessageProps> = ({
// 如果content是直接的视频链接已预览过或下载好的视频
if (isDirectVideoLink(content)) {
return (
<div className={styles.messageBubble}>
<div className={styles.videoMessage}>
<div className={styles.videoContainer}>
<video
controls
src={content}
style={{ maxWidth: "100%", borderRadius: "8px" }}
/>
<a
href={content}
download
className={styles.downloadButton}
style={{ display: "flex" }}
onClick={e => e.stopPropagation()}
>
<DownloadOutlined style={{ fontSize: "18px" }} />
</a>
</div>
<div className={styles.videoMessage}>
<div className={styles.videoContainer}>
<video
controls
src={content}
style={{ maxWidth: "100%", borderRadius: "8px" }}
/>
<a
href={content}
download
className={styles.downloadButton}
style={{ display: "flex" }}
onClick={e => e.stopPropagation()}
>
<DownloadOutlined style={{ fontSize: "18px" }} />
</a>
</div>
</div>
);
@@ -109,24 +107,22 @@ const VideoMessage: React.FC<VideoMessageProps> = ({
// 如果已有视频URL显示视频播放器
if (videoData.videoUrl) {
return (
<div className={styles.messageBubble}>
<div className={styles.videoMessage}>
<div className={styles.videoContainer}>
<video
controls
src={videoData.videoUrl}
style={{ maxWidth: "100%", borderRadius: "8px" }}
/>
<a
href={videoData.videoUrl}
download
className={styles.downloadButton}
style={{ display: "flex" }}
onClick={e => e.stopPropagation()}
>
<DownloadOutlined style={{ fontSize: "18px" }} />
</a>
</div>
<div className={styles.videoMessage}>
<div className={styles.videoContainer}>
<video
controls
src={videoData.videoUrl}
style={{ maxWidth: "100%", borderRadius: "8px" }}
/>
<a
href={videoData.videoUrl}
download
className={styles.downloadButton}
style={{ display: "flex" }}
onClick={e => e.stopPropagation()}
>
<DownloadOutlined style={{ fontSize: "18px" }} />
</a>
</div>
</div>
);
@@ -134,38 +130,36 @@ const VideoMessage: React.FC<VideoMessageProps> = ({
// 显示预览图,根据加载状态显示不同的图标
return (
<div className={styles.messageBubble}>
<div className={styles.videoMessage}>
<div
className={styles.videoContainer}
onClick={e => handlePlayClick(e, msg)}
>
<img
src={previewImageUrl}
alt="视频预览"
className={styles.videoThumbnail}
style={{
maxWidth: "100%",
borderRadius: "8px",
opacity: videoData.isLoading ? "0.7" : "1",
}}
onError={e => {
const target = e.target as HTMLImageElement;
const parent = target.parentElement?.parentElement;
if (parent) {
parent.innerHTML = `<div class="${styles.messageText}">[视频预览加载失败]</div>`;
}
}}
/>
<div className={styles.videoPlayIcon}>
{videoData.isLoading ? (
<div className={styles.loadingSpinner}></div>
) : (
<PlayCircleFilled
style={{ fontSize: "48px", color: "#fff" }}
/>
)}
</div>
<div className={styles.videoMessage}>
<div
className={styles.videoContainer}
onClick={e => handlePlayClick(e, msg)}
>
<img
src={previewImageUrl}
alt="视频预览"
className={styles.videoThumbnail}
style={{
maxWidth: "100%",
borderRadius: "8px",
opacity: videoData.isLoading ? "0.7" : "1",
}}
onError={e => {
const target = e.target as HTMLImageElement;
const parent = target.parentElement?.parentElement;
if (parent) {
parent.innerHTML = `<div class="${styles.messageText}">[视频预览加载失败]</div>`;
}
}}
/>
<div className={styles.videoPlayIcon}>
{videoData.isLoading ? (
<div className={styles.loadingSpinner}></div>
) : (
<PlayCircleFilled
style={{ fontSize: "48px", color: "#fff" }}
/>
)}
</div>
</div>
</div>

View File

@@ -53,7 +53,7 @@ const messageHandlers: Record<string, MessageHandler> = {
}
},
CmdSendMessageResult: message => {
updateMessage(message.friendMessageId, {
updateMessage(message.friendMessageId || message.chatroomMessageId, {
sendStatus: 0,
});
},