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:
@@ -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)
|
||||
|
||||
16
Moncter/app/controller/UserController.php
Normal file
16
Moncter/app/controller/UserController.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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, // 将秒转换为毫秒
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
、
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -53,7 +53,7 @@ const messageHandlers: Record<string, MessageHandler> = {
|
||||
}
|
||||
},
|
||||
CmdSendMessageResult: message => {
|
||||
updateMessage(message.friendMessageId, {
|
||||
updateMessage(message.friendMessageId || message.chatroomMessageId, {
|
||||
sendStatus: 0,
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user