Browse Source

支持图片

zhuizhu 6 months ago
parent
commit
f97a7fa0ec

+ 2 - 1
config/networkconfig.cpp

@@ -86,8 +86,9 @@ bool NetworkConfig::isValid() const
 
 void NetworkConfig::initDefaults()
 {
+    // 106.55.186.74
     // 服务器配置默认值
-    m_serverConfig.host = "106.55.186.74";  // 使用项目中的生产服务器
+    m_serverConfig.host = "127.0.0.1"; // 使用项目中的生产服务器
     m_serverConfig.port = 8200;
     m_serverConfig.useHttps = false;
     m_serverConfig.apiPath = "/api";

+ 129 - 8
network/websocketclient.cpp

@@ -5,6 +5,12 @@
 #include <QJsonParseError>
 #include <QUrl>
 #include <QUrlQuery>
+#include <QFileInfo>
+#include <QBuffer>
+#include <QImageReader>
+#include <QStandardPaths>
+#include <QDir>
+#include <QFile>
 
 #include "networkaccessmanager.h"
 
@@ -101,6 +107,75 @@ void WebSocketClient::sendMessage(const QString& content, const QString& type)
     m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact));
 }
 
+void WebSocketClient::sendImageMessage(const QString& imagePath, const QString& text)
+{
+    if (!m_connected) {
+        emit errorOccurred("未连接到聊天室,无法发送图片消息");
+        return;
+    }
+
+    // 检查文件是否存在
+    QFileInfo fileInfo(imagePath);
+    if (!fileInfo.exists() || !fileInfo.isFile()) {
+        emit errorOccurred("图片文件不存在: " + imagePath);
+        return;
+    }
+
+    // 读取图片文件并转换为Base64
+    QFile file(imagePath);
+    if (!file.open(QIODevice::ReadOnly)) {
+        emit errorOccurred("无法读取图片文件: " + imagePath);
+        return;
+    }
+
+    QByteArray imageData = file.readAll();
+    file.close();
+
+    // 检查文件大小限制(例如5MB)
+    const qint64 maxFileSize = 5 * 1024 * 1024; // 5MB
+    if (imageData.size() > maxFileSize) {
+        emit errorOccurred("图片文件过大,请选择小于5MB的图片");
+        return;
+    }
+
+    // 获取文件扩展名来确定MIME类型
+    QString mimeType = "image/jpeg"; // 默认
+    QString suffix = fileInfo.suffix().toLower();
+    if (suffix == "png") {
+        mimeType = "image/png";
+    } else if (suffix == "gif") {
+        mimeType = "image/gif";
+    } else if (suffix == "bmp") {
+        mimeType = "image/bmp";
+    } else if (suffix == "webp") {
+        mimeType = "image/webp";
+    }
+
+    // 构建符合WSMessage结构的消息
+    QJsonObject message;
+    message["type"] = "room";  // 使用room类型
+    message["roomId"] = m_roomId;
+    message["time"] = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
+    
+    // 如果有附加文本消息,放在content字段
+    if (!text.isEmpty()) {
+        message["content"] = text;
+    }
+    
+    // 图片数据放在data字段中
+    QJsonObject imageData_obj;
+    imageData_obj["messageType"] = "image";  // 标识这是图片消息
+    imageData_obj["imageData"] = QString::fromLatin1(imageData.toBase64());
+    imageData_obj["mimeType"] = mimeType;
+    imageData_obj["fileName"] = fileInfo.fileName();
+    imageData_obj["fileSize"] = imageData.size();
+    
+    message["data"] = imageData_obj;
+
+    QJsonDocument doc(message);
+    m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact));
+}
+
 bool WebSocketClient::isConnected() const
 {
     return m_connected;
@@ -154,14 +229,8 @@ void WebSocketClient::onTextMessageReceived(const QString& message)
         const QString type = obj["type"].toString();
         const QString content = obj["content"].toString();
 
-        // 设置消息类型
-        if (type == "room") {
-            chatMessage.type = MessageType::Left;
-        } else if (type == "system") {
-            chatMessage.type = MessageType::System;
-        } else if (type == "private") {
-            chatMessage.type = MessageType::Right;
-        } else if (type == "ping") {
+        // 处理特殊消息类型
+        if (type == "ping") {
             return;
         } else if (type == "room_live_status") {
             emit liveStatus(content);
@@ -171,6 +240,58 @@ void WebSocketClient::onTextMessageReceived(const QString& message)
             return;
         }
 
+        // 设置消息类型
+        if (type == "room") {
+            chatMessage.type = MessageType::Left;
+        } else if (type == "system") {
+            chatMessage.type = MessageType::System;
+        } else if (type == "private") {
+            chatMessage.type = MessageType::Right;
+        }
+
+        // 检查是否包含扩展数据(图片等)
+        if (obj.contains("data") && !obj["data"].isNull()) {
+            QJsonObject dataObj = obj["data"].toObject();
+            QString messageType = dataObj["messageType"].toString();
+            
+            if (messageType == "image") {
+                // 处理图片消息
+                QString imageData = dataObj["imageData"].toString();
+                QString mimeType = dataObj["mimeType"].toString();
+                QString fileName = dataObj["fileName"].toString();
+                
+                // 解码Base64图片数据
+                QByteArray decodedData = QByteArray::fromBase64(imageData.toLatin1());
+                
+                // 创建临时文件保存图片
+                QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
+                QString tempFileName = QString("chat_image_%1_%2").arg(QDateTime::currentMSecsSinceEpoch()).arg(fileName);
+                QString tempFilePath = QDir(tempDir).absoluteFilePath(tempFileName);
+                
+                QFile tempFile(tempFilePath);
+                if (tempFile.open(QIODevice::WriteOnly)) {
+                    tempFile.write(decodedData);
+                    tempFile.close();
+                    
+                    // 设置图片消息属性
+                    chatMessage.imagePath = tempFilePath;
+                    chatMessage.contentType = content.isEmpty() ? ContentType::Image : ContentType::Mixed;
+                    
+                    // 获取图片尺寸
+                    QImageReader reader(tempFilePath);
+                    if (reader.canRead()) {
+                        chatMessage.imageSize = reader.size();
+                    }
+                } else {
+                    qDebug() << "Failed to save image to temp file:" << tempFilePath;
+                    return;
+                }
+            }
+        } else {
+            // 纯文本消息
+            chatMessage.contentType = ContentType::Text;
+        }
+
         // 设置消息内容
         chatMessage.text = content;
         chatMessage.senderName = obj["fromUserName"].toString();

+ 3 - 0
network/websocketclient.h

@@ -24,6 +24,9 @@ public:
     
     // 发送消息
     void sendMessage(const QString& content, const QString& type = "room");
+    
+    // 发送图片消息
+    void sendImageMessage(const QString& imagePath, const QString& text = QString());
 
     // 获取连接状态
     bool isConnected() const;

+ 446 - 115
widgets/chatView/chatwindow.cpp

@@ -2,159 +2,490 @@
 
 #include "appevent.h"
 #include "widgets/chatView/chat1/chatview.h"
+#include <QTableWidget>
+#include <QHeaderView>
+#include <QApplication>
+#include <QClipboard>
+#include <QMimeData>
+#include <QImageReader>
+#include <QStandardPaths>
+#include <QDir>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QScrollBar>
+#include <QSplitter>
+#include <QTextDocumentFragment>
+#include <QTextImageFormat>
+#include <QMenu>
+#include <QAction>
+#include <QUuid>
 
-ChatWindow::ChatWindow(WebSocketClient *webSocketClient, QWidget *parent)
-    : QWidget(parent)
-    , m_webSocketClient(webSocketClient)
+// MultiFormatInputEdit 实现
+MultiFormatInputEdit::MultiFormatInputEdit(QWidget *parent)
+    : QTextEdit(parent)
 {
-    setWindowTitle("气泡消息示例");
-    resize(800, 600);
+    setAcceptDrops(true);
+    setPlaceholderText("输入消息... (支持粘贴图片)");
+    setMaximumHeight(120);
+    setMinimumHeight(60);
+    
+    // 连接文本变化信号
+    connect(this, &QTextEdit::textChanged, this, &MultiFormatInputEdit::onTextChanged);
+}
 
-    QVBoxLayout *mainLayout = new QVBoxLayout(this);
+QString MultiFormatInputEdit::getPlainText() const
+{
+    // 获取纯文本,但保留图片占位符信息
+    QString text = toPlainText().trimmed();
+    return text;
+}
 
-    // 创建消息视图
-    m_messageView = new ChatView(this);
-    mainLayout->addWidget(m_messageView);
+QStringList MultiFormatInputEdit::getImagePaths() const
+{
+    return m_imagePaths;
+}
 
-    // 创建输入区域
-    QHBoxLayout *inputLayout = new QHBoxLayout();
-    m_inputEdit = new QLineEdit(this);
-    m_inputEdit->setPlaceholderText("输入消息...");
-    inputLayout->addWidget(m_inputEdit);
+void MultiFormatInputEdit::insertImage(const QString &imagePath)
+{
+    if (imagePath.isEmpty() || m_imagePaths.contains(imagePath))
+        return;
+        
+    m_imagePaths.append(imagePath);
+    insertImageIntoDocument(imagePath);
+    
+    emit imageInserted(imagePath);
+    emit contentChanged();
+}
 
-    QPushButton *sendButton = new QPushButton("发送", this);
-    inputLayout->addWidget(sendButton);
-
-    QPushButton *imageButton = new QPushButton("图片", this);
-    // inputLayout->addWidget(imageButton);
-    imageButton->setHidden(true);
-
-    QPushButton *fileButton = new QPushButton("文件", this);
-    //  inputLayout->addWidget(fileButton);
-    fileButton->setHidden(true);
-
-    QPushButton *recallButton = new QPushButton("撤回", this);
-    //  inputLayout->addWidget(recallButton);
-    recallButton->setHidden(true);
-
-    mainLayout->addLayout(inputLayout);
-
-    // 连接信号和槽
-    connect(sendButton, &QPushButton::clicked, this, &ChatWindow::onSendClicked);
-    connect(m_inputEdit, &QLineEdit::returnPressed, this, &ChatWindow::onSendClicked);
-    connect(imageButton, &QPushButton::clicked, this, &ChatWindow::onImageClicked);
-    connect(fileButton, &QPushButton::clicked, this, &ChatWindow::onFileClicked);
-    connect(recallButton, &QPushButton::clicked, this, &ChatWindow::onRecallClicked);
-
-    // // 添加一些示例消息
-    // m_messageView->addMessage(QDateTime::currentDateTime().toString(), "", true);
-    // m_messageView->addMessage("你好,这是一条测试文本消息!", "", true);
-    // m_messageView->addMessage(
-    //     "你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,"
-    //     "这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一"
-    //     "条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!",
-    //     "",
-    //     true);
-
-    // initWebsocket();
-    // m_messageView->addSuccessMessage("操作成功完成", BubbleMessage::Sent);
-    // m_messageView->addFailureMessage("操作失败", BubbleMessage::Received);
-
-    connect(m_webSocketClient,
-            &WebSocketClient::messageReceived,
-            this,
-            [this](const ChatMessage &message) {
-                qDebug() << message.text;
-                if (message.isLeft()) {
-                    m_messageView->addMessage(message.text, "", message.senderName, true);
-                } else if (message.isRight()) {
-                    m_messageView->addMessage(message.text, "", message.senderName, false);
-                } else {
-                    m_messageView->addSystemMessage(message.text);
-                }
-            }); // 回复
+void MultiFormatInputEdit::clearContent()
+{
+    clear();
+    m_imagePaths.clear();
+    m_imageResourceMap.clear();
+    document()->clear();
+    emit contentChanged();
+}
 
-    // connect(m_webSocketClient,
-    //         &WebSocketClient::connectionStateChanged,
-    //         this,
-    //         &ChatRoomWidget::onConnectionStateChanged); //连接状态
-    connect(m_webSocketClient,
-            &WebSocketClient::errorOccurred,
-            this,
-            [](const QString &errorMessage) { qDebug() << errorMessage; }); //错误反馈
+bool MultiFormatInputEdit::validateContent() const
+{
+    QString text = getPlainText();
+    
+    // 检查是否有内容
+    if (text.isEmpty() && m_imagePaths.isEmpty()) {
+        return false;
+    }
+    
+    // 检查文本长度
+    if (text.length() > 5000) {
+        return false;
+    }
+    
+    // 检查图片文件是否存在
+    for (const QString &imagePath : m_imagePaths) {
+        if (!QFileInfo::exists(imagePath)) {
+            return false;
+        }
+    }
+    
+    return true;
+}
 
-    connect(m_webSocketClient, &WebSocketClient::connectionStateChanged, this, [](bool connected) {
-        emit AppEvent::instance()->connectionStateChanged(connected);
-    });
+void MultiFormatInputEdit::removeImage(const QString &imagePath)
+{
+    if (!m_imagePaths.contains(imagePath))
+        return;
+        
+    m_imagePaths.removeAll(imagePath);
+    
+    // 从文档中移除图片资源
+    if (m_imageResourceMap.contains(imagePath)) {
+        QString resourceName = m_imageResourceMap[imagePath];
+        document()->addResource(QTextDocument::ImageResource, QUrl(resourceName), QVariant());
+        m_imageResourceMap.remove(imagePath);
+    }
+    
+    emit imageRemoved(imagePath);
+    emit contentChanged();
 }
-void ChatWindow::initWebsocket(const QString &roomId)
+
+void MultiFormatInputEdit::dragEnterEvent(QDragEnterEvent *event)
 {
-    // 首先断开链接
-    m_webSocketClient->disconnect();
+    if (event->mimeData()->hasImage() || event->mimeData()->hasUrls()) {
+        event->acceptProposedAction();
+    } else {
+        QTextEdit::dragEnterEvent(event);
+    }
+}
 
-    if (roomId.isEmpty()) {
-        m_webSocketClient->connectToRoom("416a1990-85ae-48af-aef6-5168230b999f");
+void MultiFormatInputEdit::dropEvent(QDropEvent *event)
+{
+    const QMimeData *mimeData = event->mimeData();
+    
+    if (mimeData->hasUrls()) {
+        const QList<QUrl> urls = mimeData->urls();
+        for (const QUrl &url : urls) {
+            if (url.isLocalFile()) {
+                QString filePath = url.toLocalFile();
+                if (isImageFormat(filePath)) {
+                    insertImage(filePath);
+                }
+            }
+        }
+        event->acceptProposedAction();
+    } else if (mimeData->hasImage()) {
+        QPixmap pixmap = qvariant_cast<QPixmap>(mimeData->imageData());
+        if (!pixmap.isNull()) {
+            QString tempPath = saveImageToTemp(pixmap);
+            if (!tempPath.isEmpty()) {
+                insertImage(tempPath);
+            }
+        }
+        event->acceptProposedAction();
     } else {
-        m_webSocketClient->connectToRoom(roomId);
+        QTextEdit::dropEvent(event);
     }
 }
 
+void MultiFormatInputEdit::keyPressEvent(QKeyEvent *event)
+{
+    if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
+        if (event->modifiers() & Qt::ControlModifier) {
+            // Ctrl+Enter 发送消息
+            emit sendRequested();
+            return;
+        } else {
+            // 普通回车换行
+            QTextEdit::keyPressEvent(event);
+        }
+    } else {
+        QTextEdit::keyPressEvent(event);
+    }
+}
 
-void ChatWindow::onSendClicked()
+void MultiFormatInputEdit::insertFromMimeData(const QMimeData *source)
 {
-    QString text = m_inputEdit->text().trimmed();
-    if (text.isEmpty())
-        return;
+    if (source->hasImage()) {
+        QPixmap pixmap = qvariant_cast<QPixmap>(source->imageData());
+        if (!pixmap.isNull()) {
+            QString tempPath = saveImageToTemp(pixmap);
+            if (!tempPath.isEmpty()) {
+                insertImage(tempPath);
+            }
+        }
+    } else {
+        QTextEdit::insertFromMimeData(source);
+    }
+}
 
-    m_webSocketClient->sendMessage(text, "room");
+void MultiFormatInputEdit::contextMenuEvent(QContextMenuEvent *event)
+{
+    QMenu *menu = createStandardContextMenu();
+    
+    // 检查是否点击在图片上
+    QTextCursor cursor = cursorForPosition(event->pos());
+    QTextCharFormat format = cursor.charFormat();
+    
+    if (format.isImageFormat()) {
+        QTextImageFormat imageFormat = format.toImageFormat();
+        QString imageName = imageFormat.name();
+        
+        // 查找对应的图片路径
+        QString imagePath;
+        for (auto it = m_imageResourceMap.begin(); it != m_imageResourceMap.end(); ++it) {
+            if (it.value() == imageName) {
+                imagePath = it.key();
+                break;
+            }
+        }
+        
+        if (!imagePath.isEmpty()) {
+            menu->addSeparator();
+            QAction *removeAction = menu->addAction("删除图片");
+            connect(removeAction, &QAction::triggered, [this, imagePath]() {
+                removeImage(imagePath);
+            });
+        }
+    }
+    
+    menu->exec(event->globalPos());
+    delete menu;
+}
 
-    m_messageView->addMessage(text, "", AppEvent::instance()->userName(), false);
+void MultiFormatInputEdit::onTextChanged()
+{
+    emit contentChanged();
+}
 
-    m_inputEdit->clear();
+void MultiFormatInputEdit::processImageData(const QByteArray &imageData, const QString &format)
+{
+    QPixmap pixmap;
+    if (pixmap.loadFromData(imageData, format.toUtf8().constData())) {
+        QString tempPath = saveImageToTemp(pixmap, format);
+        if (!tempPath.isEmpty()) {
+            insertImage(tempPath);
+        }
+    }
 }
 
-void ChatWindow::onImageClicked()
+bool MultiFormatInputEdit::isImageFormat(const QString &fileName) const
 {
-    QString fileName = QFileDialog::getOpenFileName(this,
-                                                    "选择图片",
-                                                    QString(),
-                                                    "图片文件 (*.png *.jpg *.jpeg *.bmp)");
-    if (fileName.isEmpty())
-        return;
+    QStringList imageFormats = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp"};
+    QString suffix = QFileInfo(fileName).suffix().toLower();
+    return imageFormats.contains(suffix);
+}
 
-    QPixmap image(fileName);
-    if (image.isNull())
-        return;
+QString MultiFormatInputEdit::saveImageToTemp(const QPixmap &pixmap, const QString &format)
+{
+    QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
+    QDir().mkpath(tempDir);
+    
+    QString fileName = QString("chat_image_%1.%2")
+                      .arg(QDateTime::currentMSecsSinceEpoch())
+                      .arg(format.toLower());
+    QString filePath = QDir(tempDir).absoluteFilePath(fileName);
+    
+    if (pixmap.save(filePath, format.toUtf8().constData())) {
+        return filePath;
+    }
+    
+    return QString();
 }
 
-void ChatWindow::onFileClicked()
+void MultiFormatInputEdit::insertImageIntoDocument(const QString &imagePath)
 {
-    QString fileName = QFileDialog::getOpenFileName(this, "选择文件");
-    if (fileName.isEmpty())
+    QPixmap pixmap(imagePath);
+    if (pixmap.isNull())
         return;
+    
+    // 缩放图片以适应输入框
+    int maxWidth = width() - 20;  // 留出边距
+    int maxHeight = 80;           // 限制高度
+    
+    if (pixmap.width() > maxWidth || pixmap.height() > maxHeight) {
+        pixmap = pixmap.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+    }
+    
+    // 生成唯一的资源名称
+    QString resourceName = generateResourceName(imagePath);
+    m_imageResourceMap[imagePath] = resourceName;
+    
+    // 将图片添加到文档资源
+    document()->addResource(QTextDocument::ImageResource, QUrl(resourceName), pixmap);
+    
+    // 在光标位置插入图片
+    QTextCursor cursor = textCursor();
+    QTextImageFormat imageFormat;
+    imageFormat.setName(resourceName);
+    imageFormat.setWidth(pixmap.width());
+    imageFormat.setHeight(pixmap.height());
+    
+    cursor.insertImage(imageFormat);
+    cursor.insertText(" "); // 在图片后添加空格,方便编辑
+    
+    setTextCursor(cursor);
+}
+
+QString MultiFormatInputEdit::generateResourceName(const QString &imagePath)
+{
+    return QString("image_%1_%2").arg(QUuid::createUuid().toString(QUuid::WithoutBraces))
+                                 .arg(QFileInfo(imagePath).baseName());
+}
 
-    // QFileInfo fileInfo(fileName);
+// ChatWindow 实现
+
+ChatWindow::ChatWindow(WebSocketClient *webSocketClient, QWidget *parent)
+    : QWidget(parent)
+    , m_webSocketClient(webSocketClient)
+{
+    setupUI();
+    connectSignals();
+}
 
-    // // 随机发送或接收文件
-    // BubbleMessage::MessageDirection direction = (qrand() % 2 == 0) ? BubbleMessage::Sent
-    //                                                                : BubbleMessage::Received;
-    // m_messageView->addFileMessage(fileInfo.fileName(), fileName, fileInfo.size(), direction);
+void ChatWindow::setupUI()
+{
+    setWindowTitle("聊天窗口");
+    resize(800, 600);
+    
+    QVBoxLayout *mainLayout = new QVBoxLayout(this);
+    
+    // 消息显示区域
+    m_messageView = new ChatView(this);
+    mainLayout->addWidget(m_messageView, 1);
+    
+    setupInputArea();
+}
+
+void ChatWindow::setupInputArea()
+{
+    // 输入区域容器
+    QWidget *inputContainer = new QWidget;
+    QVBoxLayout *inputLayout = new QVBoxLayout(inputContainer);
+    inputLayout->setContentsMargins(5, 5, 5, 5);
+    inputLayout->setSpacing(5);
+    
+    // 输入框
+    m_inputEdit = new MultiFormatInputEdit(this);
+    inputLayout->addWidget(m_inputEdit);
+    
+    // 按钮区域
+    QHBoxLayout *buttonLayout = new QHBoxLayout;
+    buttonLayout->addStretch();
+    
+    m_imageButton = new QPushButton("图片", this);
+    m_fileButton = new QPushButton("文件", this);
+    m_sendButton = new QPushButton("发送", this);
+    
+    buttonLayout->addWidget(m_imageButton);
+    buttonLayout->addWidget(m_fileButton);
+    buttonLayout->addWidget(m_sendButton);
+    
+    inputLayout->addLayout(buttonLayout);
+    
+    // 添加到主布局
+    layout()->addWidget(inputContainer);
+    
+    // 隐藏文件按钮(暂未实现)
+    m_fileButton->hide();
+}
+
+void ChatWindow::connectSignals()
+{
+    // 输入框信号
+    connect(m_inputEdit, &MultiFormatInputEdit::sendRequested, 
+            this, &ChatWindow::onSendClicked);
+    connect(m_inputEdit, &MultiFormatInputEdit::contentChanged, 
+            this, &ChatWindow::onInputContentChanged);
+    connect(m_inputEdit, &MultiFormatInputEdit::imageInserted, 
+            this, &ChatWindow::onImageInserted);
+    
+    // 按钮信号
+    connect(m_sendButton, &QPushButton::clicked, this, &ChatWindow::onSendClicked);
+    connect(m_imageButton, &QPushButton::clicked, this, &ChatWindow::onImageClicked);
+    connect(m_fileButton, &QPushButton::clicked, this, &ChatWindow::onFileClicked);
+    
+    // WebSocket信号
+    if (m_webSocketClient) {
+        connect(m_webSocketClient, &WebSocketClient::messageReceived, 
+                [this](const ChatMessage &message) {
+                    if (message.hasImage()) {
+                        // 处理图片消息
+                        if (message.isLeft()) {
+                            m_messageView->addImageMessage(message.imagePath, message.avatar, message.senderName, true, message.text);
+                        } else {
+                            m_messageView->addImageMessage(message.imagePath, message.avatar, message.senderName, false, message.text);
+                        }
+                    } else {
+                        // 处理纯文本消息
+                        if (message.isLeft()) {
+                            m_messageView->addMessage(message.text, message.avatar, message.senderName, true);
+                        } else {
+                            m_messageView->addMessage(message.text, message.avatar, message.senderName, false);
+                        }
+                    }
+                });
+        
+        connect(m_webSocketClient, &WebSocketClient::connectionStateChanged, 
+                [this](bool connected) {
+                    if (connected) {
+                        m_messageView->addSystemMessage("已连接到服务器");
+                    } else {
+                        m_messageView->addSystemMessage("与服务器断开连接");
+                    }
+                });
+    }
+}
+
+void ChatWindow::initWebsocket(const QString &roomId)
+{
+    m_roomId = roomId;
+    if (m_webSocketClient) {
+        m_webSocketClient->connectToRoom(roomId.isEmpty() ? "default_room" : roomId);
+    }
+}
+
+void ChatWindow::onSendClicked()
+{
+    if (validateInput()) {
+        sendMessage();
+    }
+}
+
+void ChatWindow::onImageClicked()
+{
+    QString fileName = QFileDialog::getOpenFileName(
+        this,
+        "选择图片",
+        "",
+        "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp)"
+    );
+    
+    if (!fileName.isEmpty()) {
+        m_inputEdit->insertImage(fileName);
+    }
+}
+
+void ChatWindow::onFileClicked()
+{
+    // 文件发送功能待实现
+    QMessageBox::information(this, "提示", "文件发送功能暂未实现");
 }
 
 void ChatWindow::onRecallClicked()
 {
-    // 随机发送或接收撤回消息
-    // BubbleMessage::MessageDirection direction = (qrand() % 2 == 0) ? BubbleMessage::Sent
-    //                                                                : BubbleMessage::Received;
-    // QString name = (direction == BubbleMessage::Sent) ? "你" : "对方";
-    // m_messageView->addRecallMessage(name, direction);
+    // 撤回消息功能待实现
+}
+
+void ChatWindow::onInputContentChanged()
+{
+    // 更新发送按钮状态
+    m_sendButton->setEnabled(m_inputEdit->validateContent());
+}
+
+void ChatWindow::onImageInserted(const QString &imagePath)
+{
+    Q_UNUSED(imagePath)
+    // 图片已直接显示在输入框中,无需额外处理
+}
+
+void ChatWindow::sendMessage()
+{
+    QString text = m_inputEdit->getPlainText();
+    QStringList imagePaths = m_inputEdit->getImagePaths();
+    
+    // 发送文本消息
+    if (!text.isEmpty()) {
+        m_messageView->addMessage(text, "", "我", false);
+        if (m_webSocketClient) {
+            m_webSocketClient->sendMessage(text);
+        }
+    }
+    
+    // 发送图片消息
+    for (const QString &imagePath : imagePaths) {
+        sendImageMessage(imagePath, text);
+    }
+    
+    // 清空输入框
+    m_inputEdit->clearContent();
+}
+
+void ChatWindow::sendImageMessage(const QString &imagePath, const QString &text)
+{
+    if (QFileInfo::exists(imagePath)) {
+        m_messageView->addImageMessage(imagePath, "", "我", false, text);
+        if (m_webSocketClient) {
+            m_webSocketClient->sendImageMessage(imagePath, text);
+        }
+    }
+}
+
+bool ChatWindow::validateInput() const
+{
+    return m_inputEdit->validateContent();
 }
 
 void ChatWindow::closeEvent(QCloseEvent *event)
 {
-    // 阻止默认关闭行为
     event->ignore();
-    
-    // 发出关闭请求信号,让MainPanel处理
     emit windowCloseRequested();
 }

+ 75 - 7
widgets/chatView/chatwindow.h

@@ -9,10 +9,71 @@
 #include <QPushButton>
 #include <QVBoxLayout>
 #include <QCloseEvent>
+#include <QTextEdit>
+#include <QLabel>
+#include <QScrollArea>
+#include <QMimeData>
+#include <QDragEnterEvent>
+#include <QDropEvent>
+#include <QPixmap>
+#include <QContextMenuEvent>
+#include <QMap>
 #include "network/websocketclient.h"
 
 class ChatView;
 
+// 多格式输入框类
+class MultiFormatInputEdit : public QTextEdit
+{
+    Q_OBJECT
+public:
+    explicit MultiFormatInputEdit(QWidget *parent = nullptr);
+    
+    // 获取纯文本内容
+    QString getPlainText() const;
+    
+    // 获取所有图片路径
+    QStringList getImagePaths() const;
+    
+    // 添加图片到输入框
+    void insertImage(const QString &imagePath);
+    
+    // 清空内容
+    void clearContent();
+    
+    // 验证输入内容
+    bool validateContent() const;
+    
+    // 移除指定图片
+    void removeImage(const QString &imagePath);
+
+signals:
+    void contentChanged();
+    void imageInserted(const QString &imagePath);
+    void imageRemoved(const QString &imagePath);
+    void sendRequested();
+
+protected:
+    void dragEnterEvent(QDragEnterEvent *event) override;
+    void dropEvent(QDropEvent *event) override;
+    void keyPressEvent(QKeyEvent *event) override;
+    void insertFromMimeData(const QMimeData *source) override;
+    void contextMenuEvent(QContextMenuEvent *event) override;
+
+private slots:
+    void onTextChanged();
+
+private:
+    QStringList m_imagePaths;
+    QMap<QString, QString> m_imageResourceMap; // 图片路径到资源名称的映射
+    
+    void processImageData(const QByteArray &imageData, const QString &format = "PNG");
+    bool isImageFormat(const QString &fileName) const;
+    QString saveImageToTemp(const QPixmap &pixmap, const QString &format = "PNG");
+    void insertImageIntoDocument(const QString &imagePath);
+    QString generateResourceName(const QString &imagePath);
+};
+
 class ChatWindow : public QWidget
 {
     Q_OBJECT
@@ -29,21 +90,28 @@ signals:
 
 private slots:
     void onSendClicked();
-
     void onImageClicked();
-
     void onFileClicked();
-
     void onRecallClicked();
-
-public slots:
+    void onInputContentChanged();
+    void onImageInserted(const QString &imagePath);
 
 private:
     ChatView *m_messageView;
-    QLineEdit *m_inputEdit;
+    MultiFormatInputEdit *m_inputEdit;
+    QPushButton *m_sendButton;
+    QPushButton *m_imageButton;
+    QPushButton *m_fileButton;
 
-    // WebSocket客户端
+    QString m_roomId;
     WebSocketClient *m_webSocketClient;
+
+    void setupUI();
+    void setupInputArea();
+    void connectSignals();
+    void sendMessage();
+    void sendImageMessage(const QString &imagePath, const QString &text = QString());
+    bool validateInput() const;
 };
 
 #endif // CHATWINDOW_H