diff --git a/src/Client/include/ui/components/DeviceCard.h b/src/Client/include/ui/components/DeviceCard.h index 087ebea..1182704 100644 --- a/src/Client/include/ui/components/DeviceCard.h +++ b/src/Client/include/ui/components/DeviceCard.h @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include /** * @brief 设备状态枚举 @@ -212,6 +215,13 @@ signals: * @param deviceId 设备ID */ void deviceDetailsRequested(const QString &deviceId); + + /** + * @brief 设备状态变化信号 + * @param deviceId 设备ID + * @param status 新状态 + */ + void deviceStatusChanged(const QString &deviceId, DeviceStatus status); public slots: /** @@ -278,6 +288,18 @@ private: */ void setupStyle(); + /** + * @brief 设置动画效果 + */ + void setupAnimations(); + + /** + * @brief 状态变化动画 + * @param oldStatus 旧状态 + * @param newStatus 新状态 + */ + void animateStatusChange(DeviceStatus oldStatus, DeviceStatus newStatus); + /** * @brief 更新状态颜色 */ @@ -309,6 +331,25 @@ private: * @return 格式化的坐标字符串 */ QString formatCoordinates(double longitude, double latitude) const; + + /** + * @brief 获取连接按钮文本 + * @return 按钮文本("连接"或"断开") + */ + QString getConnectionButtonText() const; + + /** + * @brief 获取连接按钮提示文本 + * @return 按钮提示文本 + */ + QString getConnectionButtonTooltip() const; + + /** + * @brief 更新数据库中的设备状态 + * @param status 新的设备状态 + * @return 是否更新成功 + */ + bool updateDeviceStatusInDatabase(DeviceStatus status); private: // 设备信息 @@ -338,6 +379,11 @@ private: QGridLayout *m_infoLayout; ///< 信息区域布局 QVBoxLayout *m_buttonLayout; ///< 按钮区域布局(垂直排列) + // 动画效果组件 + QPropertyAnimation *m_hoverAnimation; ///< 悬停动画 + QPropertyAnimation *m_scaleAnimation; ///< 缩放动画 + QGraphicsDropShadowEffect *m_shadowEffect; ///< 阴影效果 + // 样式和配置 - 优化的卡片尺寸 static const int CARD_WIDTH = 320; ///< 卡片宽度 (再次增加) static const int CARD_HEIGHT = 240; ///< 卡片高度 (增加以显示完整信息) diff --git a/src/Client/include/ui/components/DeviceListPanel.h b/src/Client/include/ui/components/DeviceListPanel.h index f1655d6..4b4eeb2 100644 --- a/src/Client/include/ui/components/DeviceListPanel.h +++ b/src/Client/include/ui/components/DeviceListPanel.h @@ -265,6 +265,13 @@ private slots: */ void onDeviceCardDetailsRequested(const QString &deviceId); + /** + * @brief 设备状态变化槽函数 + * @param deviceId 设备ID + * @param status 新状态 + */ + void onDeviceStatusChanged(const QString &deviceId, DeviceStatus status); + /** * @brief 状态监控定时器槽函数 */ diff --git a/src/Client/include/ui/main/MainWindow.h b/src/Client/include/ui/main/MainWindow.h index 583feb1..9c40e66 100644 --- a/src/Client/include/ui/main/MainWindow.h +++ b/src/Client/include/ui/main/MainWindow.h @@ -98,6 +98,17 @@ public: */ void setupMapDisplay(); + /** + * @brief 添加设备到数据库 + * @param name 设备名称 + * @param type 设备类型 ("uav" 或 "dog") + * @param ip IP地址 + * @param port 端口号 + * @param state 状态 (默认为0) + * @return 是否成功 + */ + bool addDeviceToDatabase(const QString &name, const QString &type, const QString &ip, int port, int state = 0); + /** * @brief 地图显示控制 * @param button 控制按钮指针 diff --git a/src/Client/run_app.sh b/src/Client/run_app.sh new file mode 100644 index 0000000..9b4ce91 --- /dev/null +++ b/src/Client/run_app.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# 战场探索系统启动脚本 +# 清理snap环境变量,避免库冲突 + +# 清理可能导致冲突的snap环境变量 +unset GTK_PATH +unset LOCPATH +unset GIO_MODULE_DIR +unset GSETTINGS_SCHEMA_DIR + +# 启动程序 +exec ./BattlefieldExplorationSystem "$@" diff --git a/src/Client/setup_database.sql b/src/Client/setup_database.sql new file mode 100644 index 0000000..b672806 --- /dev/null +++ b/src/Client/setup_database.sql @@ -0,0 +1,55 @@ +-- BattlefieldExplorationSystem 数据库设置脚本 + +-- 创建数据库 +CREATE DATABASE IF NOT EXISTS fly_land_database; +USE fly_land_database; + +-- 创建无人机表 +CREATE TABLE IF NOT EXISTS uavdatabase ( + id VARCHAR(50) PRIMARY KEY, + state INT DEFAULT 0, + ip VARCHAR(15) NOT NULL, + port INT DEFAULT 8080, + lon DOUBLE DEFAULT 0.0, + lat DOUBLE DEFAULT 0.0 +); + +-- 创建机器狗表 +CREATE TABLE IF NOT EXISTS dogdatabase ( + id VARCHAR(50) PRIMARY KEY, + state INT DEFAULT 0, + ip VARCHAR(15) NOT NULL, + port INT DEFAULT 9090, + lon DOUBLE DEFAULT 0.0, + lat DOUBLE DEFAULT 0.0 +); + +-- 插入测试数据 - 无人机 +INSERT INTO uavdatabase (id, state, ip, port, lon, lat) VALUES +('UAV001', 1, '192.168.1.101', 8080, 116.40, 39.90), +('UAV002', 2, '192.168.1.102', 8080, 116.42, 39.92), +('UAV003', 0, '192.168.1.103', 8080, 116.44, 39.94) +ON DUPLICATE KEY UPDATE + state = VALUES(state), + ip = VALUES(ip), + port = VALUES(port), + lon = VALUES(lon), + lat = VALUES(lat); + +-- 插入测试数据 - 机器狗 +INSERT INTO dogdatabase (id, state, ip, port, lon, lat) VALUES +('DOG001', 1, '192.168.1.201', 9090, 116.38, 39.88), +('DOG002', 0, '192.168.1.202', 9090, 116.46, 39.86) +ON DUPLICATE KEY UPDATE + state = VALUES(state), + ip = VALUES(ip), + port = VALUES(port), + lon = VALUES(lon), + lat = VALUES(lat); + +-- 验证数据 +SELECT 'UAV Devices:' AS info; +SELECT * FROM uavdatabase; + +SELECT 'Dog Devices:' AS info; +SELECT * FROM dogdatabase; \ No newline at end of file diff --git a/src/Client/src/ui/components/DeviceCard.cpp b/src/Client/src/ui/components/DeviceCard.cpp index fafb68b..5107bc8 100644 --- a/src/Client/src/ui/components/DeviceCard.cpp +++ b/src/Client/src/ui/components/DeviceCard.cpp @@ -18,6 +18,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include DeviceCard::DeviceCard(const DeviceInfo &device, QWidget *parent) : QWidget(parent) @@ -38,9 +45,13 @@ DeviceCard::DeviceCard(const DeviceInfo &device, QWidget *parent) , m_headerLayout(nullptr) , m_infoLayout(nullptr) , m_buttonLayout(nullptr) + , m_hoverAnimation(nullptr) + , m_scaleAnimation(nullptr) + , m_shadowEffect(nullptr) { setupUI(); setupStyle(); + setupAnimations(); // 添加动画初始化 connectSignals(); // 设置卡片基本属性 @@ -68,10 +79,11 @@ void DeviceCard::setupUI() m_headerLayout->setSpacing(8); // 减少间距,让名称更靠左 m_headerLayout->setContentsMargins(0, 0, 0, 0); // 移除边距 - // 设备图标 - 适当调整尺寸 + // 设备图标 - 增大尺寸提高区分度 m_deviceIconLabel = new QLabel(); - m_deviceIconLabel->setFixedSize(32, 32); // 稍微减小图标 + m_deviceIconLabel->setFixedSize(40, 40); // 增大图标尺寸 m_deviceIconLabel->setScaledContents(true); + m_deviceIconLabel->setAlignment(Qt::AlignCenter); updateDeviceIcon(); // 设备名称 - 调整字体和位置 @@ -84,11 +96,11 @@ void DeviceCard::setupUI() m_deviceNameLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 左对齐,垂直居中 m_deviceNameLabel->setContentsMargins(0, 0, 0, 0); // 恢复正常边距 - // 状态指示器(圆点)- 适当调整尺寸 + // 状态指示器(圆点)- 增大尺寸提高可见性 m_statusIndicator = new QLabel("●"); - m_statusIndicator->setFixedSize(20, 20); + m_statusIndicator->setFixedSize(28, 28); // 增大状态指示器 m_statusIndicator->setAlignment(Qt::AlignCenter); - m_statusIndicator->setFont(QFont("Arial", 16)); + m_statusIndicator->setFont(QFont("Arial", 22)); // 增大字体 // 状态文本 - 适当调整字体 m_statusLabel = new QLabel(getStatusText(m_currentStatus)); @@ -143,9 +155,9 @@ void DeviceCard::setupUI() m_detailsButton->setToolTip("设备详情"); m_detailsButton->setFont(QFont("Arial", 12, QFont::Bold)); - m_controlButton = new QPushButton("控制"); + m_controlButton = new QPushButton(getConnectionButtonText()); m_controlButton->setFixedSize(60, 32); - m_controlButton->setToolTip("设备控制"); + m_controlButton->setToolTip(getConnectionButtonTooltip()); m_controlButton->setFont(QFont("Arial", 12, QFont::Bold)); m_locationButton = new QPushButton("定位"); @@ -181,7 +193,7 @@ void DeviceCard::setupUI() void DeviceCard::setupStyle() { - // 基础卡片样式 + // 基础卡片样式(移除CSS悬停样式,使用动画实现) QString cardStyle = QString( "DeviceCard {" " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," @@ -190,12 +202,6 @@ void DeviceCard::setupStyle() " border: 2px solid rgba(82, 194, 242, 0.4);" " border-radius: %1px;" "}" - "DeviceCard:hover {" - " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," - " stop:0 rgba(82, 194, 242, 0.3)," - " stop:1 rgba(45, 120, 180, 0.3));" - " border-color: rgba(82, 194, 242, 0.8);" - "}" ).arg(BORDER_RADIUS); setStyleSheet(cardStyle); @@ -266,6 +272,95 @@ void DeviceCard::setupStyle() updateStatusColor(); } +void DeviceCard::setupAnimations() +{ + // 创建阴影效果 + m_shadowEffect = new QGraphicsDropShadowEffect(this); + m_shadowEffect->setBlurRadius(15); + m_shadowEffect->setColor(QColor(82, 194, 242, 100)); + m_shadowEffect->setOffset(0, 3); + setGraphicsEffect(m_shadowEffect); + + // 创建悬停动画(阴影模糊半径) + m_hoverAnimation = new QPropertyAnimation(m_shadowEffect, "blurRadius", this); + m_hoverAnimation->setDuration(200); + m_hoverAnimation->setEasingCurve(QEasingCurve::OutCubic); + + // 创建缩放动画(会在需要时手动创建) + m_scaleAnimation = nullptr; +} + +void DeviceCard::animateStatusChange(DeviceStatus oldStatus, DeviceStatus newStatus) +{ + // 状态指示器闪烁动画 + QPropertyAnimation *flashAnimation = new QPropertyAnimation(m_statusIndicator, "windowOpacity", this); + flashAnimation->setDuration(300); + flashAnimation->setKeyValueAt(0, 1.0); + flashAnimation->setKeyValueAt(0.5, 0.3); + flashAnimation->setKeyValueAt(1, 1.0); + flashAnimation->setEasingCurve(QEasingCurve::InOutQuad); + + // 卡片边框闪烁效果 + QPropertyAnimation *borderAnimation = new QPropertyAnimation(this); + borderAnimation->setDuration(500); + + // 根据状态变化类型选择不同的动画效果 + QString notificationColor; + if (newStatus == DeviceStatus::Online && oldStatus != DeviceStatus::Online) { + // 设备上线 - 绿色闪烁 + notificationColor = "rgba(0, 255, 127, 0.8)"; + } else if (newStatus == DeviceStatus::Offline) { + // 设备离线 - 红色闪烁 + notificationColor = "rgba(255, 68, 68, 0.8)"; + } else if (newStatus == DeviceStatus::Warning) { + // 设备警告 - 黄色闪烁 + notificationColor = "rgba(255, 215, 0, 0.8)"; + } else { + // 其他状态变化 - 蓝色闪烁 + notificationColor = "rgba(82, 194, 242, 0.8)"; + } + + // 设置临时的闪烁样式 + connect(flashAnimation, &QPropertyAnimation::valueChanged, this, [this, notificationColor](const QVariant &value) { + QString flashStyle = QString( + "DeviceCard {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(45, 65, 95, 0.9)," + " stop:1 rgba(25, 40, 65, 0.9));" + " border: 3px solid %1;" + " border-radius: %2px;" + "}" + ).arg(notificationColor).arg(BORDER_RADIUS); + + setStyleSheet(flashStyle); + }); + + // 动画结束后恢复正常样式并更新状态颜色 + connect(flashAnimation, &QPropertyAnimation::finished, this, [this, flashAnimation]() { + // 恢复默认样式 + QString defaultStyle = QString( + "DeviceCard {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(45, 65, 95, 0.9)," + " stop:1 rgba(25, 40, 65, 0.9));" + " border: 2px solid rgba(82, 194, 242, 0.4);" + " border-radius: %1px;" + "}" + ).arg(BORDER_RADIUS); + + setStyleSheet(defaultStyle); + + // 更新状态颜色 + updateStatusColor(); + + // 清理动画对象 + flashAnimation->deleteLater(); + }); + + // 启动动画 + flashAnimation->start(); +} + void DeviceCard::connectSignals() { connect(m_detailsButton, &QPushButton::clicked, this, &DeviceCard::onDetailsClicked); @@ -290,11 +385,21 @@ void DeviceCard::updateDeviceInfo(const DeviceInfo &device) void DeviceCard::updateDeviceStatus(DeviceStatus status) { - m_currentStatus = status; - m_statusLabel->setText(getStatusText(status)); - updateStatusColor(); - - qDebug() << "Device status updated:" << m_deviceInfo.name << "status:" << static_cast(status); + if (m_currentStatus != status) { + DeviceStatus oldStatus = m_currentStatus; + m_currentStatus = status; + m_statusLabel->setText(getStatusText(status)); + + // 更新连接按钮文本和提示 + m_controlButton->setText(getConnectionButtonText()); + m_controlButton->setToolTip(getConnectionButtonTooltip()); + + // 添加状态变化动画效果 + animateStatusChange(oldStatus, status); + + qDebug() << "Device status updated with animation:" << m_deviceInfo.name + << "from" << static_cast(oldStatus) << "to" << static_cast(status); + } } @@ -340,6 +445,30 @@ void DeviceCard::mousePressEvent(QMouseEvent *event) void DeviceCard::enterEvent(QEvent *event) { m_isHovered = true; + + // 启动悬停动画效果 + if (m_hoverAnimation && m_shadowEffect) { + m_hoverAnimation->setStartValue(15); + m_hoverAnimation->setEndValue(25); + m_hoverAnimation->start(); + + // 更新阴影颜色为更亮的蓝色 + m_shadowEffect->setColor(QColor(82, 194, 242, 150)); + } + + // 更新卡片背景以实现更明显的悬停效果 + QString enhancedHoverStyle = QString( + "DeviceCard {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(82, 194, 242, 0.4)," + " stop:1 rgba(45, 120, 180, 0.4));" + " border: 2px solid rgba(82, 194, 242, 1.0);" + " border-radius: %1px;" + "}" + ).arg(BORDER_RADIUS); + + setStyleSheet(enhancedHoverStyle); + update(); QWidget::enterEvent(event); } @@ -347,6 +476,30 @@ void DeviceCard::enterEvent(QEvent *event) void DeviceCard::leaveEvent(QEvent *event) { m_isHovered = false; + + // 停止悬停动画效果 + if (m_hoverAnimation && m_shadowEffect) { + m_hoverAnimation->setStartValue(25); + m_hoverAnimation->setEndValue(15); + m_hoverAnimation->start(); + + // 恢复阴影颜色 + m_shadowEffect->setColor(QColor(82, 194, 242, 100)); + } + + // 恢复卡片默认背景 + QString defaultStyle = QString( + "DeviceCard {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(45, 65, 95, 0.9)," + " stop:1 rgba(25, 40, 65, 0.9));" + " border: 2px solid rgba(82, 194, 242, 0.4);" + " border-radius: %1px;" + "}" + ).arg(BORDER_RADIUS); + + setStyleSheet(defaultStyle); + update(); QWidget::leaveEvent(event); } @@ -375,8 +528,39 @@ void DeviceCard::onDetailsClicked() void DeviceCard::onControlClicked() { - qDebug() << "Control clicked for device:" << m_deviceInfo.name; - emit deviceControlRequested(m_deviceInfo.id); + qDebug() << "Connection toggle clicked for device:" << m_deviceInfo.name; + + // 切换设备连接状态 + DeviceStatus newStatus; + if (m_currentStatus == DeviceStatus::Online) { + // 当前在线,切换为离线 + newStatus = DeviceStatus::Offline; + qDebug() << "Disconnecting device:" << m_deviceInfo.name; + } else { + // 当前离线,切换为在线 + newStatus = DeviceStatus::Online; + qDebug() << "Connecting device:" << m_deviceInfo.name; + } + + // 更新数据库中的状态 + if (updateDeviceStatusInDatabase(newStatus)) { + // 数据库更新成功,更新UI + updateDeviceStatus(newStatus); + m_deviceInfo.status = newStatus; + + // 更新按钮文本和提示 + m_controlButton->setText(getConnectionButtonText()); + m_controlButton->setToolTip(getConnectionButtonTooltip()); + + // 发送状态变化信号,通知父组件更新统计信息 + emit deviceStatusChanged(m_deviceInfo.id, newStatus); + + qDebug() << "Device status successfully updated:" << m_deviceInfo.name + << "to" << (newStatus == DeviceStatus::Online ? "Online" : "Offline"); + } else { + qWarning() << "Failed to update device status in database for:" << m_deviceInfo.name; + // 可以显示错误提示给用户 + } } void DeviceCard::onLocationClicked() @@ -388,7 +572,34 @@ void DeviceCard::onLocationClicked() void DeviceCard::updateStatusColor() { QString color = getStatusColor(m_currentStatus); - m_statusIndicator->setStyleSheet(QString("QLabel { color: %1; background: transparent; border: none; }").arg(color)); + + // 为状态指示器添加发光效果和阴影 + QString shadowColor; + switch (m_currentStatus) { + case DeviceStatus::Online: + shadowColor = "rgba(0, 255, 127, 0.6)"; // 绿色发光 + break; + case DeviceStatus::Warning: + shadowColor = "rgba(255, 215, 0, 0.6)"; // 黄色发光 + break; + case DeviceStatus::Offline: + shadowColor = "rgba(255, 68, 68, 0.6)"; // 红色发光 + break; + default: + shadowColor = "rgba(136, 136, 136, 0.6)"; // 灰色发光 + break; + } + + m_statusIndicator->setStyleSheet(QString( + "QLabel {" + " color: %1;" + " background: qradialgradient(cx:0.5, cy:0.5, radius:0.8," + " stop:0 %2, stop:1 transparent);" + " border-radius: 14px;" + " border: 2px solid %1;" + " font-weight: bold;" + "}" + ).arg(color).arg(shadowColor)); // 更新卡片边框颜色 QString borderColor; @@ -415,10 +626,42 @@ void DeviceCard::updateDeviceIcon() QString iconPath = m_deviceInfo.getTypeIconPath(); QPixmap pixmap(iconPath); if (!pixmap.isNull()) { - m_deviceIconLabel->setPixmap(pixmap.scaled(24, 24, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + m_deviceIconLabel->setPixmap(pixmap.scaled(36, 36, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + // 添加图标背景效果 + m_deviceIconLabel->setStyleSheet( + "QLabel {" + " background: qlineargradient(x1:0, y1:0, x2:1, y2:1," + " stop:0 rgba(82, 194, 242, 0.15)," + " stop:1 rgba(45, 120, 180, 0.15));" + " border: 2px solid rgba(82, 194, 242, 0.3);" + " border-radius: 20px;" + " padding: 2px;" + "}" + ); } else { qWarning() << "Failed to load device icon:" << iconPath; - m_deviceIconLabel->setText("📱"); // 备用图标 + // 设置默认设备图标,根据设备类型选择 + if (m_deviceInfo.type == "uav") { + m_deviceIconLabel->setText("🚁"); // 无人机图标 + m_deviceIconLabel->setFont(QFont("Arial", 20)); + } else if (m_deviceInfo.type == "dog") { + m_deviceIconLabel->setText("🐕"); // 机器狗图标 + m_deviceIconLabel->setFont(QFont("Arial", 20)); + } else { + m_deviceIconLabel->setText("📱"); // 备用图标 + m_deviceIconLabel->setFont(QFont("Arial", 18)); + } + // 添加图标背景效果 + m_deviceIconLabel->setStyleSheet( + "QLabel {" + " background: qlineargradient(x1:0, y1:0, x2:1, y2:1," + " stop:0 rgba(82, 194, 242, 0.15)," + " stop:1 rgba(45, 120, 180, 0.15));" + " border: 2px solid rgba(82, 194, 242, 0.3);" + " border-radius: 20px;" + " padding: 2px;" + "}" + ); } } @@ -435,10 +678,10 @@ QString DeviceCard::getStatusText(DeviceStatus status) const QString DeviceCard::getStatusColor(DeviceStatus status) const { switch (status) { - case DeviceStatus::Online: return "#00FF7F"; // 绿色 - case DeviceStatus::Warning: return "#FFD700"; // 黄色 - case DeviceStatus::Offline: return "#FF4444"; // 红色 - default: return "#888888"; // 灰色 + case DeviceStatus::Online: return "#00FF7F"; // 明亮绿色 + case DeviceStatus::Warning: return "#FFD700"; // 明亮黄色 + case DeviceStatus::Offline: return "#FF4444"; // 明亮红色 + default: return "#A0A0A0"; // 明亮灰色 } } @@ -447,4 +690,61 @@ QString DeviceCard::formatCoordinates(double longitude, double latitude) const return QString("N%1, E%2") .arg(latitude, 0, 'f', 2) .arg(longitude, 0, 'f', 2); +} + +QString DeviceCard::getConnectionButtonText() const +{ + if (m_currentStatus == DeviceStatus::Online) { + return "断开"; + } else { + return "连接"; + } +} + +QString DeviceCard::getConnectionButtonTooltip() const +{ + if (m_currentStatus == DeviceStatus::Online) { + return "断开设备连接"; + } else { + return "连接到设备"; + } +} + +bool DeviceCard::updateDeviceStatusInDatabase(DeviceStatus status) +{ + // 创建唯一的数据库连接名称(包含时间戳避免重复) + QString connectionName = QString("DeviceCard_%1_%2").arg(m_deviceInfo.id).arg(QDateTime::currentMSecsSinceEpoch()); + QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connectionName); + db.setHostName("localhost"); + db.setPort(3306); + db.setDatabaseName("Client"); + db.setUserName("root"); + db.setPassword("hzk200407140238"); + + if (!db.open()) { + qWarning() << "Failed to connect to database for device status update:" << db.lastError().text(); + return false; + } + + QSqlQuery query(db); + + // 将DeviceStatus转换为数据库中的state值 (0=离线, 1=在线) + int stateValue = (status == DeviceStatus::Online) ? 1 : 0; + + // 更新设备状态 + QString sql = "UPDATE devices SET state = ? WHERE id = ?"; + query.prepare(sql); + query.addBindValue(stateValue); + query.addBindValue(m_deviceInfo.id); + + bool success = query.exec(); + if (!success) { + qWarning() << "Failed to update device status in database:" << query.lastError().text(); + } else { + qDebug() << "Successfully updated device status in database:" << m_deviceInfo.id + << "state:" << stateValue; + } + + db.close(); + return success; } \ No newline at end of file diff --git a/src/Client/src/ui/components/DeviceListPanel.cpp b/src/Client/src/ui/components/DeviceListPanel.cpp index d4e772c..34035b5 100644 --- a/src/Client/src/ui/components/DeviceListPanel.cpp +++ b/src/Client/src/ui/components/DeviceListPanel.cpp @@ -14,6 +14,11 @@ #include #include +// Qt SQL头文件 +#include +#include +#include + DeviceListPanel::DeviceListPanel(QWidget *parent) : QWidget(parent) , m_titleLabel(nullptr) @@ -72,7 +77,7 @@ void DeviceListPanel::setupUI() m_headerLayout = new QHBoxLayout(); // 面板标题 - m_titleLabel = new QLabel("🤖 设备管理"); + m_titleLabel = new QLabel("🎯 设备管理中心"); m_titleLabel->setFont(QFont("Arial", 18, QFont::Bold)); // 设备数量标签 @@ -88,11 +93,11 @@ void DeviceListPanel::setupUI() // === 操作按钮区域 === m_buttonLayout = new QHBoxLayout(); - m_addUAVButton = new QPushButton("+ 无人机"); + m_addUAVButton = new QPushButton("🚁 + 无人机"); m_addUAVButton->setMaximumHeight(50); m_addUAVButton->setMaximumWidth(120); - m_addDogButton = new QPushButton("+ 机器狗"); + m_addDogButton = new QPushButton("🐕 + 机器狗"); m_addDogButton->setMaximumHeight(50); m_addDogButton->setMaximumWidth(120); @@ -413,6 +418,7 @@ DeviceCard* DeviceListPanel::createDeviceCard(const DeviceInfo &device) connect(card, &DeviceCard::deviceControlRequested, this, &DeviceListPanel::onDeviceCardControlRequested); connect(card, &DeviceCard::deviceLocationRequested, this, &DeviceListPanel::onDeviceCardLocationRequested); connect(card, &DeviceCard::deviceDetailsRequested, this, &DeviceListPanel::onDeviceCardDetailsRequested); + connect(card, &DeviceCard::deviceStatusChanged, this, &DeviceListPanel::onDeviceStatusChanged); return card; } @@ -423,95 +429,82 @@ QList DeviceListPanel::loadDevicesFromDatabase() qDebug() << "Loading devices from database..."; - // TODO: 尝试从真实数据库加载数据 + // 尝试从真实数据库加载数据 bool databaseAvailable = false; try { - // 检查数据库连接 - if (m_uavDatabase && m_dogDatabase) { - qDebug() << "Database connections available, attempting to load real data..."; - // 这里将来会实现真实的数据库查询 - // auto uavList = m_uavDatabase->getAllDevices(); - // auto dogList = m_dogDatabase->getAllDevices(); - databaseAvailable = false; // 暂时设为false,直到实现查询方法 + qDebug() << "Attempting to connect to unified devices database..."; + + // 创建数据库连接 + QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "DeviceListPanel_Connection"); + db.setHostName("localhost"); + db.setPort(3306); + db.setDatabaseName("Client"); + db.setUserName("root"); + db.setPassword("hzk200407140238"); + + if (db.open()) { + qDebug() << "Successfully connected to Client database"; + + QSqlQuery query(db); + QString sql = "SELECT id, name, device_type, state, ip, port, longitude, latitude, signal_strength, battery_level FROM devices"; + + if (query.exec(sql)) { + qDebug() << "Successfully executed query on devices table"; + + while (query.next()) { + DeviceInfo device; + device.id = query.value("id").toString(); + device.name = query.value("name").toString(); + device.type = query.value("device_type").toString(); + device.ipAddress = query.value("ip").toString(); + device.port = query.value("port").toInt(); + device.longitude = query.value("longitude").toDouble(); + device.latitude = query.value("latitude").toDouble(); + device.signalStrength = query.value("signal_strength").toInt(); + device.batteryLevel = query.value("battery_level").toInt(); + + // 状态转换:0=离线, 1=在线 + int state = query.value("state").toInt(); + switch (state) { + case 1: device.status = DeviceStatus::Online; break; + case 0: + default: device.status = DeviceStatus::Offline; break; + } + + device.lastHeartbeat = QDateTime::currentDateTime(); + device.createdAt = QDateTime::currentDateTime(); + device.updatedAt = QDateTime::currentDateTime(); + + devices.append(device); + qDebug() << "Loaded device from database:" << device.name << "(" << device.type << ")"; + } + + databaseAvailable = true; + qDebug() << "Successfully loaded" << devices.size() << "devices from unified database"; + } else { + qWarning() << "Failed to execute query:" << query.lastError().text(); + } + + db.close(); + } else { + qWarning() << "Failed to connect to Client database:" << db.lastError().text(); } + } catch (const std::exception& e) { + qWarning() << "Database connection exception:" << e.what(); + databaseAvailable = false; } catch (...) { - qWarning() << "Database connection failed, using test data"; + qWarning() << "Unknown database connection error, using test data"; databaseAvailable = false; } if (!databaseAvailable) { - qDebug() << "Using test data for demonstration..."; - } + qWarning() << "Database not available, no devices loaded."; + qDebug() << "Please ensure MySQL is running and Client database exists with devices table."; + + // 数据库不可用时不加载任何设备 - // 添加测试数据以便查看效果(当真实数据库不可用时) - DeviceInfo uav1; - uav1.id = "UAV001"; - uav1.name = "侦察机-01"; - uav1.type = "uav"; - uav1.ipAddress = "192.168.1.101"; - uav1.port = 8080; - uav1.longitude = 116.40; - uav1.latitude = 39.90; - uav1.signalStrength = 85; - uav1.batteryLevel = 90; - uav1.status = DeviceStatus::Online; - uav1.lastHeartbeat = QDateTime::currentDateTime(); - uav1.createdAt = QDateTime::currentDateTime().addDays(-7); - uav1.updatedAt = QDateTime::currentDateTime(); - devices.append(uav1); - - DeviceInfo uav2; - uav2.id = "UAV002"; - uav2.name = "侦察机-02"; - uav2.type = "uav"; - uav2.ipAddress = "192.168.1.102"; - uav2.port = 8080; - uav2.longitude = 116.42; - uav2.latitude = 39.92; - uav2.signalStrength = 72; - uav2.batteryLevel = 65; - uav2.status = DeviceStatus::Warning; - uav2.lastHeartbeat = QDateTime::currentDateTime().addSecs(-30); - uav2.createdAt = QDateTime::currentDateTime().addDays(-5); - uav2.updatedAt = QDateTime::currentDateTime(); - devices.append(uav2); - - DeviceInfo dog1; - dog1.id = "DOG001"; - dog1.name = "巡逻犬-Alpha"; - dog1.type = "dog"; - dog1.ipAddress = "192.168.1.201"; - dog1.port = 9090; - dog1.longitude = 116.38; - dog1.latitude = 39.88; - dog1.signalStrength = 95; - dog1.batteryLevel = 80; - dog1.status = DeviceStatus::Online; - dog1.lastHeartbeat = QDateTime::currentDateTime(); - dog1.createdAt = QDateTime::currentDateTime().addDays(-3); - dog1.updatedAt = QDateTime::currentDateTime(); - devices.append(dog1); - - DeviceInfo dog2; - dog2.id = "DOG002"; - dog2.name = "巡逻犬-Beta"; - dog2.type = "dog"; - dog2.ipAddress = "192.168.1.202"; - dog2.port = 9090; - dog2.longitude = 116.44; - dog2.latitude = 39.86; - dog2.signalStrength = 0; - dog2.batteryLevel = 25; - dog2.status = DeviceStatus::Offline; - dog2.lastHeartbeat = QDateTime::currentDateTime().addSecs(-300); - dog2.createdAt = QDateTime::currentDateTime().addDays(-1); - dog2.updatedAt = QDateTime::currentDateTime().addSecs(-120); - devices.append(dog2); - - // TODO: 实现真实的数据库查询 - // auto uavList = m_uavDatabase->getAllDevices(); - // auto dogList = m_dogDatabase->getAllDevices(); + } return devices; } @@ -640,6 +633,23 @@ void DeviceListPanel::onDeviceCardDetailsRequested(const QString &deviceId) emit deviceDetailsRequested(deviceId); } +void DeviceListPanel::onDeviceStatusChanged(const QString &deviceId, DeviceStatus status) +{ + qDebug() << "Device status changed signal received:" << deviceId << "status:" << static_cast(status); + + // 更新内部设备列表中的状态 + for (auto &device : m_allDevices) { + if (device.id == deviceId) { + device.status = status; + qDebug() << "Updated device status in internal list:" << device.name; + break; + } + } + + // 更新统计信息显示 + updateDeviceCountStats(); +} + void DeviceListPanel::setSearchKeyword(const QString &keyword) { m_currentSearchKeyword = keyword.trimmed(); diff --git a/src/Client/src/ui/main/MainWindow.cpp b/src/Client/src/ui/main/MainWindow.cpp index 75cb695..38078ed 100644 --- a/src/Client/src/ui/main/MainWindow.cpp +++ b/src/Client/src/ui/main/MainWindow.cpp @@ -28,6 +28,11 @@ #include #include #include + +// Qt SQL头文件 +#include +#include +#include #include #include #include @@ -321,9 +326,20 @@ void MainWindow::onAddRobotClicked() QString ip = ipEdit->text().trimmed(); if (!name.isEmpty() && !ip.isEmpty()) { - m_robotList.append(qMakePair(name, ip)); - QMessageBox::information(this, "成功", "机器人添加成功!"); - dialog->accept(); + // 保存到数据库 + if (addDeviceToDatabase(name, "dog", ip, 9090, 0)) { + m_robotList.append(qMakePair(name, ip)); + QMessageBox::information(this, "成功", "机器人添加成功!"); + + // 刷新设备列表 + if (m_deviceListPanel) { + m_deviceListPanel->refreshDeviceList(); + } + + dialog->accept(); + } else { + QMessageBox::warning(this, "错误", "保存到数据库失败!"); + } } else { QMessageBox::warning(this, "错误", "请填写完整信息!"); } @@ -407,9 +423,20 @@ void MainWindow::onAddUAVClicked() QString ip = ipEdit->text().trimmed(); if (!name.isEmpty() && !ip.isEmpty()) { - m_uavList.append(qMakePair(name, ip)); - QMessageBox::information(this, "成功", "无人机添加成功!"); - dialog->accept(); + // 保存到数据库 + if (addDeviceToDatabase(name, "uav", ip, 8080, 0)) { + m_uavList.append(qMakePair(name, ip)); + QMessageBox::information(this, "成功", "无人机添加成功!"); + + // 刷新设备列表 + if (m_deviceListPanel) { + m_deviceListPanel->refreshDeviceList(); + } + + dialog->accept(); + } else { + QMessageBox::warning(this, "错误", "保存到数据库失败!"); + } } else { QMessageBox::warning(this, "错误", "请填写完整信息!"); } @@ -684,4 +711,63 @@ void MainWindow::initializeDeviceMarkersOnMap() break; } } +} + +bool MainWindow::addDeviceToDatabase(const QString &name, const QString &type, const QString &ip, int port, int state) +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "MainWindow_AddDevice_Connection"); + db.setHostName("localhost"); + db.setPort(3306); + db.setDatabaseName("Client"); + db.setUserName("root"); + db.setPassword("hzk200407140238"); + + if (!db.open()) { + qWarning() << "Failed to connect to database for adding device:" << db.lastError().text(); + return false; + } + + QSqlQuery query(db); + + // 生成唯一ID + QString deviceId; + if (type == "uav") { + // 查找现有UAV数量来生成ID + query.exec("SELECT COUNT(*) FROM devices WHERE device_type = 'uav'"); + int count = 0; + if (query.next()) { + count = query.value(0).toInt(); + } + deviceId = QString("UAV%1").arg(count + 1, 3, 10, QChar('0')); // UAV001, UAV002... + } else if (type == "dog") { + // 查找现有DOG数量来生成ID + query.exec("SELECT COUNT(*) FROM devices WHERE device_type = 'dog'"); + int count = 0; + if (query.next()) { + count = query.value(0).toInt(); + } + deviceId = QString("DOG%1").arg(count + 1, 3, 10, QChar('0')); // DOG001, DOG002... + } + + // 插入新设备 + QString sql = "INSERT INTO devices (id, name, device_type, state, ip, port, longitude, latitude, signal_strength, battery_level) " + "VALUES (?, ?, ?, ?, ?, ?, 0.0, 0.0, 0, 100)"; + + query.prepare(sql); + query.addBindValue(deviceId); + query.addBindValue(name); + query.addBindValue(type); + query.addBindValue(state); // 默认状态为0(离线) + query.addBindValue(ip); + query.addBindValue(port); + + bool success = query.exec(); + if (!success) { + qWarning() << "Failed to insert device into database:" << query.lastError().text(); + } else { + qDebug() << "Successfully added device to database:" << deviceId << name; + } + + db.close(); + return success; } \ No newline at end of file