diff --git a/src/Client/BattlefieldExplorationSystem b/src/Client/BattlefieldExplorationSystem index e20da90c..dc88f611 100755 Binary files a/src/Client/BattlefieldExplorationSystem and b/src/Client/BattlefieldExplorationSystem differ diff --git a/src/Client/BattlefieldExplorationSystem.pro b/src/Client/BattlefieldExplorationSystem.pro index 29f16305..cc5584a4 100644 --- a/src/Client/BattlefieldExplorationSystem.pro +++ b/src/Client/BattlefieldExplorationSystem.pro @@ -27,7 +27,9 @@ SOURCES += \ src/ui/main/MainWindow.cpp \ src/ui/dialogs/DeviceDialog.cpp \ src/ui/components/DeviceCard.cpp \ - src/ui/components/DeviceListPanel.cpp + src/ui/components/DeviceListPanel.cpp \ + src/ui/components/SystemLogPanel.cpp \ + src/utils/SystemLogger.cpp # Header files - 按模块组织 HEADERS += \ @@ -36,7 +38,9 @@ HEADERS += \ include/ui/main/MainWindow.h \ include/ui/dialogs/DeviceDialog.h \ include/ui/components/DeviceCard.h \ - include/ui/components/DeviceListPanel.h + include/ui/components/DeviceListPanel.h \ + include/ui/components/SystemLogPanel.h \ + include/utils/SystemLogger.h # UI forms - 按模块组织 FORMS += \ diff --git a/src/Client/doc/README.md b/src/Client/doc/README.md index 7d645405..394197f0 100644 --- a/src/Client/doc/README.md +++ b/src/Client/doc/README.md @@ -24,6 +24,7 @@ API文档、组件设计、技术实现细节等 **当前文档**: - `phase3_ui_refactor_plan.md` - Phase 3界面重构技术设计文档 +- `system_log_design.md` - 系统日志功能设计文档 (Phase 5) **后续文档**: - `api_documentation.md` - API接口文档 @@ -52,8 +53,8 @@ API文档、组件设计、技术实现细节等 - `code_refactor_summary.md` - 代码重构和模块化改造总结 - `phase3_completion_report.md` - Phase 3界面重构完成报告 -**后续文档**: -- `phase2_completion_report.md` - Phase 2完成报告 +**计划文档**: +- `phase5_completion_report.md` - Phase 5功能增强完成报告 (进行中) - `testing_report.md` - 测试报告 - `performance_analysis.md` - 性能分析报告 - `final_project_report.md` - 最终项目报告 @@ -108,6 +109,9 @@ Git工作流程、分支策略、代码审查等 | 日期 | 文档 | 变更描述 | 作者 | |------|------|----------|------| | 2025-06-18 | 全部 | 初始化文档目录结构,迁移Phase 1文档 | Claude | +| 2024-12-21 | task.md | 更新Phase 5系统日志功能开发进展 | Qt UI Developer | +| 2024-12-21 | system_log_design.md | 创建系统日志功能技术设计文档 | Qt UI Developer | +| 2024-12-21 | README.md | 更新文档索引,添加新的技术文档链接 | Qt UI Developer | --- diff --git a/src/Client/doc/planning/task.md b/src/Client/doc/planning/task.md index 26101a87..2dd08f63 100644 --- a/src/Client/doc/planning/task.md +++ b/src/Client/doc/planning/task.md @@ -102,18 +102,25 @@ - [x] 添加数据统计图表显示 - [x] 添加系统运行状态指示器 -### Phase 5: 功能模块重构和增强 (优先级: 中) +### Phase 5: 功能模块重构和增强 (优先级: 中) 🚧 **进行中** -#### 5.1 设备管理功能增强 +#### 5.1 系统日志功能开发 🚧 **当前任务** +- [x] **UI重构**: 删除重复的设备管理按钮,为系统日志让出空间 +- [ ] **组件开发**: 创建SystemLogPanel组件和SystemLogger单例管理器 +- [ ] **界面集成**: 使用QSplitter实现日志与设备管理的35%:65%空间分配 +- [ ] **日志功能**: 实现多级别日志记录、格式化显示、过滤搜索功能 +- [ ] **系统集成**: 在关键操作点集成日志记录(设备操作、数据库、地图交互) + +#### 5.2 设备管理功能增强 - [ ] 重构添加设备对话框,提升用户体验 - [ ] 实现设备批量管理功能 - [ ] 添加设备配置导入/导出功能 - [ ] 实现设备连接测试功能 -#### 5.2 系统功能模块 +#### 5.3 系统功能模块 - [ ] 实现系统设置管理界面 - [ ] 添加用户权限管理功能 -- [ ] 实现操作日志记录功能 +- [x] **已集成**: 操作日志记录功能(通过系统日志实现) - [ ] 添加系统备份和恢复功能 ### Phase 6: 样式和主题系统 (优先级: 中) @@ -295,11 +302,11 @@ CREATE TABLE dog_devices ( - 完整的状态指示系统和交互反馈机制 ### 当前状态 🚧 -**当前阶段**: Phase 4 - 地图和可视化组件优化 ✅ **已完成** -**完成日期**: 2024年12月19日 -**进展情况**: 设备卡片界面已成功集成到主窗口,地图集成完全实现,设备-地图交互功能正常 +**当前阶段**: Phase 5 - 功能模块重构和增强 🚧 **进行中** +**开始日期**: 2024年12月21日 +**进展情况**: Phase 4已完全完成,开始Phase 5系统日志功能开发 -**最新进展** (2024年12月19日): +**Phase 4 最终成果** (2024年12月19日): ✅ **组件集成完成**: DeviceListPanel已成功集成到MainWindow左侧面板 ✅ **设备卡片显示**: 4个测试设备(2个无人机+2个机器狗)正常显示 ✅ **过滤功能**: 设备搜索和分类过滤功能正常工作 @@ -310,6 +317,13 @@ CREATE TABLE dog_devices ( ✅ **设备图标系统**: 不同设备类型显示不同图标(无人机/机器狗) ✅ **状态色彩显示**: 根据设备状态显示不同颜色(在线/警告/离线) +**Phase 5 最新进展** (2024年12月21日): +🚧 **UI重构开始**: 开始系统日志功能开发,移除重复的设备管理按钮 +✅ **按钮清理完成**: 成功删除MainWindow中4个重复的设备管理按钮 +✅ **空间预留**: 在左侧面板为系统日志预留350px垂直空间 +🚧 **组件开发中**: 准备创建SystemLogPanel和SystemLogger组件 +🎯 **目标架构**: 35%系统日志 + 65%设备管理的垂直分割布局 + **Phase 4 重大里程碑达成** ✅: - 🎯 **设备-地图集成**: 成功实现设备列表与地图的双向交互 - 🎯 **可视化增强**: 地图标记系统支持实时设备状态显示 @@ -324,14 +338,26 @@ CREATE TABLE dog_devices ( - **设备类型图标**: 无人机和机器狗不同图标显示系统 - **交互体验**: 点击设备卡片自动聚焦到地图位置 -### 下一步计划 -1. ✅ ~~**组件集成**: 将新的设备卡片界面集成到主窗口~~ **已完成** -2. ✅ ~~**数据库连接**: 连接真实的设备数据库,实现数据同步(当前使用测试数据)~~ **已完成** -3. ✅ ~~**地图集成优化**: 优化现有地图显示和交互功能~~ **已完成** -4. ✅ ~~**设备位置显示**: 在地图上实时显示设备位置标记~~ **已完成** -5. ✅ ~~**设备-地图交互**: 实现点击设备定位功能~~ **已完成** -6. 📋 **硬件集成准备**: 为后续硬件设备接入做接口准备 -7. 📋 **Phase 5功能增强**: 准备进入下一阶段的功能模块开发 +### Phase 5 系统日志功能开发计划 + +#### 当前进展 🚧 +1. ✅ **UI空间预留**: 删除重复按钮,为系统日志预留350px空间 +2. 🚧 **组件架构设计**: SystemLogPanel + SystemLogger单例设计 +3. 📋 **界面开发**: 实现紧凑军用风格的日志显示界面 +4. 📋 **功能实现**: 多级别日志、格式化、过滤、搜索功能 +5. 📋 **布局集成**: QSplitter实现35%:65%垂直分割 +6. 📋 **系统集成**: 在关键操作点添加日志记录 + +#### 系统日志功能规格 +- **日志级别**: Info, Warning, Error, Success, Debug +- **显示特性**: 时间戳、颜色编码、图标标识 +- **交互功能**: 清空、暂停/恢复、级别过滤 +- **性能优化**: 行数限制、内存管理、异步更新 +- **集成点**: 设备操作、数据库、地图交互、错误处理 + +### 长期规划 +6. 📋 **硬件集成准备**: 为后续硬件设备接入做接口准备 +7. 📋 **Phase 6样式优化**: 主题系统和视觉规范完善 --- diff --git a/src/Client/doc/technical/system_log_design.md b/src/Client/doc/technical/system_log_design.md new file mode 100644 index 00000000..6ac8acb5 --- /dev/null +++ b/src/Client/doc/technical/system_log_design.md @@ -0,0 +1,322 @@ +# 系统日志功能设计文档 + +## 文档信息 +- **文档版本**: v1.0 +- **创建日期**: 2024年12月21日 +- **作者**: Qt UI Developer Expert +- **项目阶段**: Phase 5 - 功能模块重构和增强 + +## 概述 + +系统日志功能是BattlefieldExplorationSystem项目Phase 5的核心特性,旨在替换左侧面板重复的设备管理按钮,提供系统操作的实时监控和记录功能。 + +### 设计目标 + +1. **消除功能重复**: 移除与设备管理面板重复的按钮 +2. **增强系统透明度**: 实时显示关键操作信息 +3. **提升调试能力**: 便于开发和维护阶段的问题追踪 +4. **专业化界面**: 符合军用系统的专业要求 + +## 架构设计 + +### 整体架构 + +``` +左侧面板布局 (使用QSplitter): +├── 系统日志面板 (SystemLogPanel) - 35% 空间 +│ ├── 头部控制区 (40px) +│ ├── 日志显示区 (260-310px, 可滚动) +│ └── 状态信息区 (20px, 可选) +├── 分隔器 (QSplitter::handle) - 8px +└── 设备管理面板 (DeviceListPanel) - 65% 空间 + └── 现有的设备管理功能 +``` + +### 核心组件 + +#### 1. SystemLogger (单例管理器) +```cpp +class SystemLogger : public QObject +{ + Q_OBJECT + +public: + enum LogLevel { + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + Success = 4 + }; + + static SystemLogger* getInstance(); + + void logInfo(const QString &message); + void logWarning(const QString &message); + void logError(const QString &message); + void logSuccess(const QString &message); + void logDebug(const QString &message); + +signals: + void logAdded(LogLevel level, const QString &message); + +private: + static SystemLogger *s_instance; + SystemLogger(QObject *parent = nullptr); +}; +``` + +#### 2. SystemLogPanel (界面组件) +```cpp +class SystemLogPanel : public QWidget +{ + Q_OBJECT + +public: + explicit SystemLogPanel(QWidget *parent = nullptr); + +public slots: + void addLog(SystemLogger::LogLevel level, const QString &message); + void clearLogs(); + void pauseLogging(); + void resumeLogging(); + void setLogLevelFilter(SystemLogger::LogLevel minLevel); + +private: + QTextEdit *m_logTextEdit; + QPushButton *m_clearButton; + QPushButton *m_pauseButton; + QComboBox *m_levelFilter; + QLabel *m_statusLabel; + + bool m_isPaused; + SystemLogger::LogLevel m_minLevel; + int m_logCounts[5]; // 各级别日志计数 +}; +``` + +## 界面设计 + +### 视觉风格 + +**军用深蓝风格**, 与现有界面保持一致: +- **主色调**: 深蓝灰色背景 `rgb(15, 22, 32)` → `rgb(25, 35, 45)` +- **强调色**: 青蓝色边框 `rgba(82, 194, 242, 0.3)` +- **文字色**: 浅色文字 `rgb(220, 230, 242)` + +### 组件布局 + +#### 头部控制区 (40px) +``` +[🖥️ 系统日志] [清空] [暂停] [级别过滤▼] +``` + +#### 日志显示区 (主要区域) +``` +HH:MM:SS.mmm 🔵 设备连接成功: UAV001 +HH:MM:SS.mmm 🟡 网络延迟较高: 150ms +HH:MM:SS.mmm 🔴 数据库连接失败: 超时 +HH:MM:SS.mmm 🟢 设备定位完成: DOG001 +``` + +#### 状态信息区 (20px, 可选) +``` +总计: 156 | 错误: 3 | 警告: 12 | 就绪 +``` + +### 日志格式设计 + +#### 格式规范 +``` +[时间戳] [级别图标] [消息内容] +``` + +#### 级别定义 +- **🔍 Debug**: 开发调试信息 (灰色 `#9E9E9E`) +- **🔵 Info**: 一般操作信息 (蓝色 `#52C2F2`) +- **🟡 Warning**: 警告信息 (黄色 `#FFD700`) +- **🔴 Error**: 错误信息 (红色 `#FF4444`) +- **🟢 Success**: 成功操作 (绿色 `#00FF7F`) + +#### 格式化函数 +```cpp +QString formatLogEntry(SystemLogger::LogLevel level, const QString &message) +{ + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); + QString levelIcon, levelColor; + + switch(level) { + case Info: levelIcon = "🔵"; levelColor = "#52C2F2"; break; + case Warning: levelIcon = "🟡"; levelColor = "#FFD700"; break; + case Error: levelIcon = "🔴"; levelColor = "#FF4444"; break; + case Success: levelIcon = "🟢"; levelColor = "#00FF7F"; break; + case Debug: levelIcon = "🔍"; levelColor = "#9E9E9E"; break; + } + + return QString("[%2] %3 %4") + .arg(levelColor) + .arg(timestamp) + .arg(levelIcon) + .arg(message); +} +``` + +## 功能规格 + +### 核心功能 + +#### 1. 日志记录 +- **多级别支持**: Debug, Info, Warning, Error, Success +- **时间戳**: 精确到毫秒的时间记录 +- **颜色编码**: 不同级别使用不同颜色显示 +- **图标标识**: 直观的视觉识别 + +#### 2. 日志管理 +- **清空日志**: 一键清除所有日志记录 +- **暂停/恢复**: 暂停日志更新,便于查看 +- **级别过滤**: 只显示指定级别以上的日志 +- **行数限制**: 自动管理内存,限制最大行数 + +#### 3. 高级功能 +- **自动滚动**: 新日志自动滚动到底部 +- **搜索功能**: 在日志中搜索关键词 (可选) +- **导出功能**: 导出日志到文件 (可选) + +### 集成点 + +系统日志将集成到以下关键操作点: + +#### 1. 设备管理操作 +```cpp +// 设备添加 +SystemLogger::getInstance()->logInfo("设备添加成功: " + deviceName); + +// 设备连接 +SystemLogger::getInstance()->logSuccess("设备连接成功: " + deviceId); +SystemLogger::getInstance()->logError("设备连接失败: " + deviceId + " - " + errorMsg); + +// 设备删除 +SystemLogger::getInstance()->logInfo("设备删除: " + deviceName); +``` + +#### 2. 数据库操作 +```cpp +// 数据库连接 +SystemLogger::getInstance()->logSuccess("数据库连接成功"); +SystemLogger::getInstance()->logError("数据库连接失败: " + error); + +// 数据操作 +SystemLogger::getInstance()->logInfo("加载了 " + QString::number(count) + " 个设备"); +``` + +#### 3. 地图交互 +```cpp +// 地图操作 +SystemLogger::getInstance()->logInfo("地图初始化完成"); +SystemLogger::getInstance()->logSuccess("设备定位完成: " + deviceId); +``` + +#### 4. 系统事件 +```cpp +// 系统启动 +SystemLogger::getInstance()->logInfo("系统启动完成"); + +// 错误处理 +SystemLogger::getInstance()->logWarning("网络延迟较高: " + QString::number(latency) + "ms"); +``` + +## 性能考虑 + +### 内存管理 +- **行数限制**: 最大500行,超出自动删除最旧记录 +- **异步更新**: 日志记录不阻塞主线程 +- **智能刷新**: 避免频繁UI更新造成性能问题 + +### 用户体验 +- **响应速度**: 日志添加不影响界面响应 +- **视觉效果**: 平滑的滚动和更新动画 +- **可用性**: 直观的控制按钮和快捷操作 + +## 空间分配方案 + +### QSplitter布局 +```cpp +// 创建垂直分割器 +QSplitter *leftSplitter = new QSplitter(Qt::Vertical, this); + +// 系统日志面板 +SystemLogPanel *logPanel = new SystemLogPanel(this); +logPanel->setMinimumHeight(200); // 最小高度 +logPanel->setMaximumHeight(400); // 最大高度 + +// 设备管理面板 +DeviceListPanel *devicePanel = new DeviceListPanel(this); +devicePanel->setMinimumHeight(300); // 最小高度 + +// 设置分割比例 (35% : 65%) +leftSplitter->addWidget(logPanel); +leftSplitter->addWidget(devicePanel); +leftSplitter->setSizes(QList() << 350 << 650); +``` + +### 响应式调整 +- **小屏幕**: 自动调整比例,保证最小可用空间 +- **大屏幕**: 维持设计比例,提供最佳用户体验 +- **用户可调**: 支持用户拖拽分割线调整比例 + +## 实施计划 + +### Phase 1: 核心组件开发 +1. 创建 SystemLogger 单例管理器 +2. 创建 SystemLogPanel 界面组件 +3. 实现基础的日志记录和显示功能 + +### Phase 2: 界面集成 +1. 修改 MainWindow 布局,移除重复按钮 +2. 使用 QSplitter 实现分割布局 +3. 集成系统日志面板到左侧面板 + +### Phase 3: 功能完善 +1. 实现日志过滤和搜索功能 +2. 添加高级控制功能 (清空、暂停等) +3. 优化性能和用户体验 + +### Phase 4: 系统集成 +1. 在关键操作点添加日志记录 +2. 完善错误处理和异常记录 +3. 测试和调试整体功能 + +## 技术风险与应对 + +### 主要风险 +1. **性能影响**: 频繁的日志更新可能影响界面性能 +2. **内存占用**: 大量日志可能导致内存占用过高 +3. **用户体验**: 日志信息过多可能干扰主要功能 + +### 应对策略 +1. **异步处理**: 使用异步机制避免阻塞主线程 +2. **智能限制**: 设置合理的行数和更新频率限制 +3. **用户控制**: 提供暂停、过滤等用户控制选项 + +## 测试策略 + +### 功能测试 +- 各级别日志记录和显示正确性 +- 过滤和搜索功能准确性 +- 清空和暂停功能可靠性 + +### 性能测试 +- 大量日志的性能表现 +- 内存使用情况监控 +- 界面响应速度测试 + +### 集成测试 +- 与现有功能的兼容性 +- 空间分配的正确性 +- 用户交互的流畅性 + +--- + +**文档状态**: 🟢 **已完成** +**下次更新**: 根据实施进展进行更新 +**相关文档**: task.md, phase5_completion_report.md \ No newline at end of file diff --git a/src/Client/forms/main/MainWindow.ui b/src/Client/forms/main/MainWindow.ui index 29a1d0e5..bb34b376 100644 --- a/src/Client/forms/main/MainWindow.ui +++ b/src/Client/forms/main/MainWindow.ui @@ -262,198 +262,37 @@ border-radius: 1px; - - - 6 + + + + + 0 + 350 + - - - - - - - 0 - 70 - - - - - 18 - 75 - true - - - - 机器人列表 - - - - - - - - 40 - 40 - - - - - 40 - 40 - - - - border-image: url(:/image/res/image/tab.svg); - - - - - - - - - - - - - - - 0 - 85 - - - - - 20 - 75 - true - - - - 添加机器人 - - - - - - - - 40 - 40 - - - - - 40 - 40 - - - - border-image: url(:/image/res/image/robotbtn.svg); - - - - - - - - - - - - - - - 0 - 85 - - - - - 20 - 75 - true - - - - 添加无人机 - - - - - - - - 40 - 40 - - - - - 40 - 40 - - - - border-image: url(:/image/res/image/UAV.svg); - - - - - - - - - - - - - - - 0 - 70 - - - - - 18 - 75 - true - - - - 无人机列表 - - - - - - - - 40 - 40 - - - - - 40 - 40 - - - - false - - - border-image: url(:/image/res/image/tab.svg); - - - - - - - - - + + color: rgb(82, 194, 242); +font-size: 16px; +font-weight: bold; +padding: 20px; +margin: 10px; +background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 rgba(82, 194, 242, 0.1), + stop:1 rgba(45, 120, 180, 0.1)); +border: 2px dashed rgba(82, 194, 242, 0.5); +border-radius: 8px; + + + 🖥️ 系统日志面板 + +此区域将显示系统日志信息 +包括设备操作、连接状态、 +地图交互等关键信息 + + + Qt::AlignCenter + + diff --git a/src/Client/include/ui/components/SystemLogPanel.h b/src/Client/include/ui/components/SystemLogPanel.h new file mode 100644 index 00000000..04a6a201 --- /dev/null +++ b/src/Client/include/ui/components/SystemLogPanel.h @@ -0,0 +1,202 @@ +/** + * @file SystemLogPanel.h + * @brief 系统日志面板界面组件头文件 + * @author Qt UI Developer Expert + * @date 2024-12-21 + * @version 1.0 + */ + +#ifndef SYSTEM_LOG_PANEL_H +#define SYSTEM_LOG_PANEL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 前向声明 +class SystemLogger; + +/** + * @class SystemLogPanel + * @brief 系统日志显示面板组件 + * + * 系统日志面板是BattlefieldExplorationSystem中的核心UI组件, + * 用于实时显示系统操作日志、设备状态变化、错误信息等关键信息。 + * + * 主要功能: + * - 多级别日志显示(Debug, Info, Warning, Error, Success) + * - 实时日志更新和格式化显示 + * - 日志过滤和搜索功能 + * - 日志清空、暂停/恢复控制 + * - 军用风格的界面设计 + */ +class SystemLogPanel : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief 构造函数 + * @param parent 父控件指针 + */ + explicit SystemLogPanel(QWidget *parent = nullptr); + + /** + * @brief 析构函数 + */ + ~SystemLogPanel(); + + /** + * @brief 日志级别枚举(与SystemLogger保持一致) + */ + enum LogLevel { + Debug = 0, ///< 调试信息 + Info = 1, ///< 一般信息 + Warning = 2, ///< 警告信息 + Error = 3, ///< 错误信息 + Success = 4 ///< 成功信息 + }; + +public slots: + /** + * @brief 添加日志条目 + * @param level 日志级别 + * @param message 日志消息内容 + */ + void addLog(LogLevel level, const QString &message); + + /** + * @brief 清空所有日志 + */ + void clearLogs(); + + /** + * @brief 暂停日志更新 + */ + void pauseLogging(); + + /** + * @brief 恢复日志更新 + */ + void resumeLogging(); + + /** + * @brief 设置日志级别过滤 + * @param minLevel 最小显示级别 + */ + void setLogLevelFilter(LogLevel minLevel); + +private slots: + /** + * @brief 处理清空按钮点击 + */ + void onClearButtonClicked(); + + /** + * @brief 处理暂停/恢复按钮点击 + */ + void onPauseButtonClicked(); + + /** + * @brief 处理级别过滤器变化 + * @param index 选中的过滤级别索引 + */ + void onLevelFilterChanged(int index); + + /** + * @brief 更新状态信息显示 + */ + void updateStatusInfo(); + +private: + /** + * @brief 初始化UI界面 + */ + void setupUI(); + + /** + * @brief 设置界面样式 + */ + void setupStyle(); + + /** + * @brief 连接信号和槽 + */ + void connectSignals(); + + /** + * @brief 格式化日志条目 + * @param level 日志级别 + * @param message 消息内容 + * @return 格式化后的HTML字符串 + */ + QString formatLogEntry(LogLevel level, const QString &message); + + /** + * @brief 获取级别图标 + * @param level 日志级别 + * @return 对应的图标字符串 + */ + QString getLevelIcon(LogLevel level); + + /** + * @brief 获取级别颜色 + * @param level 日志级别 + * @return 对应的颜色字符串 + */ + QString getLevelColor(LogLevel level); + + /** + * @brief 获取级别名称 + * @param level 日志级别 + * @return 对应的级别名称 + */ + QString getLevelName(LogLevel level); + + /** + * @brief 限制日志行数,防止内存占用过高 + */ + void limitLogLines(); + + /** + * @brief 自动滚动到底部 + */ + void scrollToBottom(); + +private: + // UI组件 + QTextEdit *m_logTextEdit; ///< 日志显示文本框 + QPushButton *m_clearButton; ///< 清空按钮 + QPushButton *m_pauseButton; ///< 暂停/恢复按钮 + QComboBox *m_levelFilter; ///< 级别过滤下拉框 + QLabel *m_statusLabel; ///< 状态信息标签 + QLabel *m_titleLabel; ///< 标题标签 + + // 布局管理 + QVBoxLayout *m_mainLayout; ///< 主布局 + QHBoxLayout *m_controlLayout; ///< 控制按钮布局 + QHBoxLayout *m_statusLayout; ///< 状态信息布局 + + // 状态变量 + bool m_isPaused; ///< 是否暂停日志更新 + LogLevel m_minLevel; ///< 最小显示级别 + int m_logCounts[5]; ///< 各级别日志计数 + int m_totalLogCount; ///< 总日志数量 + int m_maxLogLines; ///< 最大日志行数限制 + + // 定时器 + QTimer *m_statusUpdateTimer; ///< 状态更新定时器 + + // 样式常量 + static const int MAX_LOG_LINES; ///< 最大日志行数 + static const int STATUS_UPDATE_INTERVAL; ///< 状态更新间隔(ms) +}; + +#endif // SYSTEM_LOG_PANEL_H \ No newline at end of file diff --git a/src/Client/include/ui/main/MainWindow.h b/src/Client/include/ui/main/MainWindow.h index 41e15fe8..f226443a 100644 --- a/src/Client/include/ui/main/MainWindow.h +++ b/src/Client/include/ui/main/MainWindow.h @@ -24,6 +24,7 @@ #include #include #include +#include // Qt控件头文件 #include @@ -40,6 +41,7 @@ // 自定义模块头文件 // #include "AudioModule/IntelligenceUI.h" // 暂时注释掉,待实现 #include "ui/components/DeviceListPanel.h" +#include "ui/components/SystemLogPanel.h" // 标准库头文件 #include @@ -230,6 +232,11 @@ private: */ void setupDeviceListPanel(); + /** + * @brief 设置系统日志面板和左侧面板分割器 + */ + void setupSystemLogPanel(); + /** * @brief 连接信号和槽 */ @@ -249,6 +256,8 @@ private: Ui::MainWindow *m_ui; ///< UI界面指针 // IntelligenceUI *m_intelligenceUI; ///< 情报传达界面指针(暂时注释掉) DeviceListPanel *m_deviceListPanel; ///< 设备列表面板组件 + SystemLogPanel *m_systemLogPanel; ///< 系统日志面板组件 + QSplitter *m_leftPanelSplitter; ///< 左侧面板分割器 QVector> m_robotList; ///< 机器人列表(名称-IP地址对) QVector> m_uavList; ///< 无人机列表(名称-IP地址对) // 人脸识别相关成员变量已移除(功能暂未实现) diff --git a/src/Client/include/utils/SystemLogger.h b/src/Client/include/utils/SystemLogger.h new file mode 100644 index 00000000..716f4e40 --- /dev/null +++ b/src/Client/include/utils/SystemLogger.h @@ -0,0 +1,178 @@ +/** + * @file SystemLogger.h + * @brief 系统日志管理器单例类头文件 + * @author Qt UI Developer Expert + * @date 2024-12-21 + * @version 1.0 + */ + +#ifndef SYSTEM_LOGGER_H +#define SYSTEM_LOGGER_H + +#include +#include +#include +#include +#include + +/** + * @class SystemLogger + * @brief 系统日志管理器单例类 + * + * SystemLogger是战场探索系统的核心日志管理组件,采用单例模式设计, + * 负责统一管理和分发系统中的各种日志信息。 + * + * 主要功能: + * - 多级别日志记录(Debug, Info, Warning, Error, Success) + * - 线程安全的日志记录 + * - 信号机制实时通知UI组件 + * - 统一的日志接口,便于系统各模块调用 + * + * 使用方式: + * @code + * SystemLogger::getInstance()->logInfo("设备连接成功"); + * SystemLogger::getInstance()->logError("数据库连接失败"); + * @endcode + */ +class SystemLogger : public QObject +{ + Q_OBJECT + +public: + /** + * @brief 日志级别枚举 + */ + enum LogLevel { + Debug = 0, ///< 调试信息 - 开发阶段使用 + Info = 1, ///< 一般信息 - 正常操作记录 + Warning = 2, ///< 警告信息 - 需要注意的情况 + Error = 3, ///< 错误信息 - 系统错误和异常 + Success = 4 ///< 成功信息 - 重要操作成功完成 + }; + + /** + * @brief 获取单例实例 + * @return SystemLogger单例指针 + */ + static SystemLogger* getInstance(); + + /** + * @brief 销毁单例实例 + * 通常在应用程序退出时调用 + */ + static void destroyInstance(); + + /** + * @brief 记录调试信息 + * @param message 日志消息内容 + */ + void logDebug(const QString &message); + + /** + * @brief 记录一般信息 + * @param message 日志消息内容 + */ + void logInfo(const QString &message); + + /** + * @brief 记录警告信息 + * @param message 日志消息内容 + */ + void logWarning(const QString &message); + + /** + * @brief 记录错误信息 + * @param message 日志消息内容 + */ + void logError(const QString &message); + + /** + * @brief 记录成功信息 + * @param message 日志消息内容 + */ + void logSuccess(const QString &message); + + /** + * @brief 通用日志记录方法 + * @param level 日志级别 + * @param message 日志消息内容 + */ + void log(LogLevel level, const QString &message); + + /** + * @brief 设置是否启用控制台输出 + * @param enabled true为启用,false为禁用 + */ + void setConsoleOutputEnabled(bool enabled); + + /** + * @brief 获取级别名称字符串 + * @param level 日志级别 + * @return 级别名称 + */ + static QString getLevelString(LogLevel level); + +signals: + /** + * @brief 日志添加信号 + * @param level 日志级别 + * @param message 日志消息内容 + * + * 当有新日志记录时发出此信号,UI组件可以连接此信号来实时更新显示 + */ + void logAdded(LogLevel level, const QString &message); + +private: + /** + * @brief 私有构造函数(单例模式) + * @param parent 父对象指针 + */ + explicit SystemLogger(QObject *parent = nullptr); + + /** + * @brief 私有析构函数(单例模式) + */ + ~SystemLogger(); + + /** + * @brief 禁用拷贝构造函数 + */ + SystemLogger(const SystemLogger&) = delete; + + /** + * @brief 禁用赋值运算符 + */ + SystemLogger& operator=(const SystemLogger&) = delete; + + /** + * @brief 内部日志记录实现 + * @param level 日志级别 + * @param message 日志消息内容 + */ + void logInternal(LogLevel level, const QString &message); + + /** + * @brief 输出到控制台 + * @param level 日志级别 + * @param message 日志消息内容 + */ + void outputToConsole(LogLevel level, const QString &message); + +private: + static SystemLogger *s_instance; ///< 单例实例指针 + static QMutex s_mutex; ///< 线程安全互斥锁 + + bool m_consoleOutputEnabled; ///< 是否启用控制台输出 + QMutex m_logMutex; ///< 日志记录互斥锁 +}; + +/** + * @brief 便捷宏定义,简化日志调用 + */ +#define LOG_DEBUG(msg) SystemLogger::getInstance()->logDebug(msg) +#define LOG_INFO(msg) SystemLogger::getInstance()->logInfo(msg) +#define LOG_WARNING(msg) SystemLogger::getInstance()->logWarning(msg) +#define LOG_ERROR(msg) SystemLogger::getInstance()->logError(msg) +#define LOG_SUCCESS(msg) SystemLogger::getInstance()->logSuccess(msg) + +#endif // SYSTEM_LOGGER_H \ 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 5107bc8a..8b4e53a0 100644 --- a/src/Client/src/ui/components/DeviceCard.cpp +++ b/src/Client/src/ui/components/DeviceCard.cpp @@ -7,6 +7,7 @@ */ #include "ui/components/DeviceCard.h" +#include "utils/SystemLogger.h" // Qt GUI头文件 #include @@ -60,6 +61,7 @@ DeviceCard::DeviceCard(const DeviceInfo &device, QWidget *parent) setAttribute(Qt::WA_Hover, true); qDebug() << "DeviceCard created for device:" << device.name; + SystemLogger::getInstance()->logDebug(QString("创建设备卡片: %1").arg(device.name)); } DeviceCard::~DeviceCard() @@ -436,7 +438,11 @@ void DeviceCard::refreshStatus() void DeviceCard::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - setSelected(!m_isSelected); + bool newSelected = !m_isSelected; + setSelected(newSelected); + if (newSelected) { + SystemLogger::getInstance()->logInfo(QString("选中设备: %1").arg(m_deviceInfo.name)); + } event->accept(); } QWidget::mousePressEvent(event); @@ -523,6 +529,7 @@ void DeviceCard::paintEvent(QPaintEvent *event) void DeviceCard::onDetailsClicked() { qDebug() << "Details clicked for device:" << m_deviceInfo.name; + SystemLogger::getInstance()->logInfo(QString("查看设备详情: %1").arg(m_deviceInfo.name)); emit deviceDetailsRequested(m_deviceInfo.id); } @@ -536,10 +543,12 @@ void DeviceCard::onControlClicked() // 当前在线,切换为离线 newStatus = DeviceStatus::Offline; qDebug() << "Disconnecting device:" << m_deviceInfo.name; + SystemLogger::getInstance()->logInfo(QString("正在断开连接: %1").arg(m_deviceInfo.name)); } else { // 当前离线,切换为在线 newStatus = DeviceStatus::Online; qDebug() << "Connecting device:" << m_deviceInfo.name; + SystemLogger::getInstance()->logInfo(QString("正在连接设备: %1").arg(m_deviceInfo.name)); } // 更新数据库中的状态 @@ -557,8 +566,13 @@ void DeviceCard::onControlClicked() qDebug() << "Device status successfully updated:" << m_deviceInfo.name << "to" << (newStatus == DeviceStatus::Online ? "Online" : "Offline"); + + // 记录连接状态变更成功 + QString statusText = (newStatus == DeviceStatus::Online) ? "上线" : "离线"; + SystemLogger::getInstance()->logSuccess(QString("设备 %1 已%2").arg(m_deviceInfo.name).arg(statusText)); } else { qWarning() << "Failed to update device status in database for:" << m_deviceInfo.name; + SystemLogger::getInstance()->logError(QString("设备状态更新失败: %1").arg(m_deviceInfo.name)); // 可以显示错误提示给用户 } } @@ -566,6 +580,7 @@ void DeviceCard::onControlClicked() void DeviceCard::onLocationClicked() { qDebug() << "Location clicked for device:" << m_deviceInfo.name; + SystemLogger::getInstance()->logInfo(QString("请求设备定位: %1").arg(m_deviceInfo.name)); emit deviceLocationRequested(m_deviceInfo.id); } diff --git a/src/Client/src/ui/components/DeviceListPanel.cpp b/src/Client/src/ui/components/DeviceListPanel.cpp index c447b0e5..7a1d36f7 100644 --- a/src/Client/src/ui/components/DeviceListPanel.cpp +++ b/src/Client/src/ui/components/DeviceListPanel.cpp @@ -7,6 +7,7 @@ */ #include "ui/components/DeviceListPanel.h" +#include "utils/SystemLogger.h" // Qt GUI头文件 #include @@ -290,6 +291,7 @@ int DeviceListPanel::getOnlineDeviceCount() const void DeviceListPanel::refreshDeviceList() { qDebug() << "Refreshing device list..."; + SystemLogger::getInstance()->logInfo("正在刷新设备列表..."); // 清除现有设备卡片 clearAllDeviceCards(); @@ -312,6 +314,7 @@ void DeviceListPanel::refreshDeviceList() updateDeviceCountStats(); qDebug() << "Device list refreshed. Total devices:" << m_allDevices.size(); + SystemLogger::getInstance()->logSuccess(QString("设备列表刷新完成,共加载 %1 个设备").arg(m_allDevices.size())); } void DeviceListPanel::addDevice(const DeviceInfo &device) @@ -335,6 +338,7 @@ void DeviceListPanel::addDevice(const DeviceInfo &device) updateDeviceCountStats(); qDebug() << "Device added:" << device.name; + SystemLogger::getInstance()->logSuccess(QString("设备已添加: %1").arg(device.name)); } } @@ -345,6 +349,15 @@ void DeviceListPanel::removeDevice(const QString &deviceId) return; } + // 获取设备名称用于日志 + QString deviceName = "未知设备"; + for (const auto &device : m_allDevices) { + if (device.id == deviceId) { + deviceName = device.name; + break; + } + } + // 移除设备卡片 DeviceCard *card = m_deviceCards.take(deviceId); card->deleteLater(); @@ -365,6 +378,7 @@ void DeviceListPanel::removeDevice(const QString &deviceId) updateDeviceCountStats(); qDebug() << "Device removed:" << deviceId; + SystemLogger::getInstance()->logWarning(QString("设备已移除: %1").arg(deviceName)); } void DeviceListPanel::updateDevice(const DeviceInfo &device) @@ -469,6 +483,7 @@ QList DeviceListPanel::loadDevicesFromDatabase() if (db.open()) { qDebug() << "Successfully connected to Client database"; + SystemLogger::getInstance()->logSuccess("成功连接到数据库"); QSqlQuery query(db); QString sql = "SELECT id, name, device_type, state, ip, port, longitude, latitude, signal_strength, battery_level FROM devices"; @@ -513,6 +528,7 @@ QList DeviceListPanel::loadDevicesFromDatabase() db.close(); } else { qWarning() << "Failed to connect to Client database:" << db.lastError().text(); + SystemLogger::getInstance()->logError("数据库连接失败"); } } catch (const std::exception& e) { qWarning() << "Database connection exception:" << e.what(); @@ -557,6 +573,7 @@ bool DeviceListPanel::deleteDeviceFromDatabase(const QString &deviceId) if (query.exec()) { if (query.numRowsAffected() > 0) { qDebug() << "Successfully deleted device from database:" << deviceId; + SystemLogger::getInstance()->logSuccess("设备从数据库中删除成功"); success = true; // 从内存中移除设备 @@ -588,11 +605,13 @@ bool DeviceListPanel::deleteDeviceFromDatabase(const QString &deviceId) } } else { qWarning() << "Failed to execute delete query:" << query.lastError().text(); + SystemLogger::getInstance()->logError("数据库删除操作失败"); } db.close(); } else { qWarning() << "Failed to connect to database for deletion:" << db.lastError().text(); + SystemLogger::getInstance()->logError("删除操作数据库连接失败"); } // 清理数据库连接 diff --git a/src/Client/src/ui/components/SystemLogPanel.cpp b/src/Client/src/ui/components/SystemLogPanel.cpp new file mode 100644 index 00000000..cde94c22 --- /dev/null +++ b/src/Client/src/ui/components/SystemLogPanel.cpp @@ -0,0 +1,476 @@ +/** + * @file SystemLogPanel.cpp + * @brief 系统日志面板界面组件实现 + * @author Qt UI Developer Expert + * @date 2024-12-21 + * @version 1.0 + */ + +#include "ui/components/SystemLogPanel.h" +#include "utils/SystemLogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 静态常量定义 +const int SystemLogPanel::MAX_LOG_LINES = 500; +const int SystemLogPanel::STATUS_UPDATE_INTERVAL = 2000; // 2秒 + +SystemLogPanel::SystemLogPanel(QWidget *parent) + : QWidget(parent) + , m_logTextEdit(nullptr) + , m_clearButton(nullptr) + , m_pauseButton(nullptr) + , m_levelFilter(nullptr) + , m_statusLabel(nullptr) + , m_titleLabel(nullptr) + , m_mainLayout(nullptr) + , m_controlLayout(nullptr) + , m_statusLayout(nullptr) + , m_isPaused(false) + , m_minLevel(Debug) + , m_totalLogCount(0) + , m_maxLogLines(MAX_LOG_LINES) + , m_statusUpdateTimer(nullptr) +{ + // 初始化日志计数器 + for (int i = 0; i < 5; ++i) { + m_logCounts[i] = 0; + } + + setupUI(); + setupStyle(); + connectSignals(); + + // 连接到SystemLogger的信号 + connect(SystemLogger::getInstance(), &SystemLogger::logAdded, + this, [this](SystemLogger::LogLevel level, const QString &message) { + addLog(static_cast(level), message); + }); + + qDebug() << "SystemLogPanel initialized successfully"; +} + +SystemLogPanel::~SystemLogPanel() +{ + if (m_statusUpdateTimer) { + m_statusUpdateTimer->stop(); + } + qDebug() << "SystemLogPanel destroyed"; +} + +void SystemLogPanel::setupUI() +{ + // 创建主布局 + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setSpacing(4); + m_mainLayout->setContentsMargins(8, 8, 8, 10); + + // 创建标题标签 + m_titleLabel = new QLabel("🖥️ 系统日志", this); + m_titleLabel->setMinimumHeight(25); + m_titleLabel->setAlignment(Qt::AlignCenter); + + // 创建控制按钮布局 + m_controlLayout = new QHBoxLayout(); + m_controlLayout->setSpacing(4); + + // 创建控制按钮 + m_clearButton = new QPushButton("清空", this); + m_clearButton->setMinimumSize(68, 30); + m_clearButton->setMaximumSize(68, 30); + + m_pauseButton = new QPushButton("暂停", this); + m_pauseButton->setMinimumSize(68, 30); + m_pauseButton->setMaximumSize(68, 30); + + // 创建级别过滤器 + m_levelFilter = new QComboBox(this); + m_levelFilter->addItem("全部", static_cast(Debug)); + m_levelFilter->addItem("信息+", static_cast(Info)); + m_levelFilter->addItem("警告+", static_cast(Warning)); + m_levelFilter->addItem("错误+", static_cast(Error)); + m_levelFilter->addItem("成功", static_cast(Success)); + m_levelFilter->setMinimumSize(88, 30); + m_levelFilter->setMaximumSize(88, 30); + + // 添加控制组件到布局 + m_controlLayout->addWidget(m_clearButton); + m_controlLayout->addWidget(m_pauseButton); + m_controlLayout->addStretch(); + m_controlLayout->addWidget(m_levelFilter); + + // 创建日志显示文本框 + m_logTextEdit = new QTextEdit(this); + m_logTextEdit->setReadOnly(true); + m_logTextEdit->setMinimumHeight(250); + m_logTextEdit->setMaximumHeight(350); + m_logTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_logTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + // 创建状态信息标签 + m_statusLabel = new QLabel("就绪", this); + m_statusLabel->setMinimumHeight(26); + m_statusLabel->setMaximumHeight(28); + m_statusLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + // 创建状态布局 + m_statusLayout = new QHBoxLayout(); + m_statusLayout->setContentsMargins(0, 0, 0, 0); + m_statusLayout->addWidget(m_statusLabel); + m_statusLayout->addStretch(); + + // 添加所有组件到主布局 + m_mainLayout->addWidget(m_titleLabel); + m_mainLayout->addLayout(m_controlLayout); + m_mainLayout->addWidget(m_logTextEdit); + m_mainLayout->addLayout(m_statusLayout); + + // 创建状态更新定时器 + m_statusUpdateTimer = new QTimer(this); + m_statusUpdateTimer->setInterval(STATUS_UPDATE_INTERVAL); + m_statusUpdateTimer->start(); + + qDebug() << "SystemLogPanel UI setup completed"; +} + +void SystemLogPanel::setupStyle() +{ + // 主面板样式 + setStyleSheet( + "SystemLogPanel {" + " background-color: rgba(25, 35, 45, 0.95);" + " border: 2px solid rgba(82, 194, 242, 0.4);" + " border-radius: 8px;" + " padding: 4px;" + "}" + ); + + // 标题样式 + m_titleLabel->setStyleSheet( + "QLabel {" + " color: rgb(82, 194, 242);" + " font-size: 14px;" + " font-weight: bold;" + " padding: 4px 8px;" + " background: qlineargradient(x1:0, y1:0, x2:1, y2:1," + " stop:0 rgba(82, 194, 242, 0.2)," + " stop:1 rgba(45, 120, 180, 0.2));" + " border: 1px solid rgba(82, 194, 242, 0.5);" + " border-radius: 4px;" + "}" + ); + + // 按钮通用样式 + QString buttonStyle = + "QPushButton {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(45, 65, 95, 0.8)," + " stop:1 rgba(25, 40, 65, 0.8));" + " color: rgb(220, 230, 242);" + " border: 1px solid rgba(82, 194, 242, 0.5);" + " border-radius: 4px;" + " font-size: 12px;" + " font-weight: bold;" + " padding: 3px 8px;" + "}" + "QPushButton:hover {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(82, 194, 242, 0.6)," + " stop:1 rgba(45, 120, 180, 0.6));" + " border: 1px solid rgba(82, 194, 242, 0.8);" + "}" + "QPushButton:pressed {" + " background: qlineargradient(x1:0, y1:0, x2:0, y2:1," + " stop:0 rgba(82, 194, 242, 0.8)," + " stop:1 rgba(45, 120, 180, 0.8));" + "}"; + + m_clearButton->setStyleSheet(buttonStyle); + m_pauseButton->setStyleSheet(buttonStyle); + + // 下拉框样式 + m_levelFilter->setStyleSheet( + "QComboBox {" + " background: rgba(25, 40, 65, 0.8);" + " color: rgb(220, 230, 242);" + " border: 1px solid rgba(82, 194, 242, 0.5);" + " border-radius: 4px;" + " padding: 3px 8px;" + " font-size: 12px;" + " font-weight: bold;" + "}" + "QComboBox::drop-down {" + " subcontrol-origin: padding;" + " subcontrol-position: top right;" + " width: 15px;" + " border-left: 1px solid rgba(82, 194, 242, 0.5);" + "}" + "QComboBox::down-arrow {" + " width: 8px;" + " height: 8px;" + "}" + "QComboBox QAbstractItemView {" + " background-color: rgba(25, 40, 65, 0.95);" + " color: rgb(220, 230, 242);" + " border: 1px solid rgba(82, 194, 242, 0.5);" + " border-radius: 4px;" + " selection-background-color: rgba(82, 194, 242, 0.3);" + "}" + ); + + // 文本框样式 + m_logTextEdit->setStyleSheet( + "QTextEdit {" + " background-color: rgba(15, 22, 32, 0.9);" + " color: rgb(220, 230, 242);" + " border: 1px solid rgba(82, 194, 242, 0.4);" + " border-radius: 6px;" + " padding: 6px;" + " font-family: 'Consolas', 'Monaco', monospace;" + " font-size: 13px;" + " line-height: 1.4;" + " selection-background-color: rgba(82, 194, 242, 0.3);" + "}" + "QScrollBar:vertical {" + " background-color: rgba(30, 44, 62, 0.8);" + " width: 10px;" + " border-radius: 5px;" + "}" + "QScrollBar::handle:vertical {" + " background-color: rgba(82, 194, 242, 0.6);" + " border-radius: 5px;" + " min-height: 20px;" + "}" + "QScrollBar::handle:vertical:hover {" + " background-color: rgba(82, 194, 242, 0.8);" + "}" + ); + + // 状态标签样式 + m_statusLabel->setStyleSheet( + "QLabel {" + " color: rgb(150, 180, 210);" + " font-size: 12px;" + " font-weight: normal;" + " padding: 3px 6px;" + " background: transparent;" + "}" + ); + + qDebug() << "SystemLogPanel styles applied"; +} + +void SystemLogPanel::connectSignals() +{ + // 连接按钮信号 + connect(m_clearButton, &QPushButton::clicked, + this, &SystemLogPanel::onClearButtonClicked); + connect(m_pauseButton, &QPushButton::clicked, + this, &SystemLogPanel::onPauseButtonClicked); + + // 连接下拉框信号 + connect(m_levelFilter, QOverload::of(&QComboBox::currentIndexChanged), + this, &SystemLogPanel::onLevelFilterChanged); + + // 连接定时器信号 + connect(m_statusUpdateTimer, &QTimer::timeout, + this, &SystemLogPanel::updateStatusInfo); + + qDebug() << "SystemLogPanel signals connected"; +} + +void SystemLogPanel::addLog(LogLevel level, const QString &message) +{ + // 如果暂停则不添加日志 + if (m_isPaused) { + return; + } + + // 检查级别过滤 + if (level < m_minLevel) { + return; + } + + // 更新计数器 + if (level >= 0 && level < 5) { + m_logCounts[level]++; + } + m_totalLogCount++; + + // 格式化并添加日志条目 + QString formattedEntry = formatLogEntry(level, message); + m_logTextEdit->append(formattedEntry); + + // 限制日志行数 + limitLogLines(); + + // 自动滚动到底部 + scrollToBottom(); + + qDebug() << QString("Log added: [%1] %2").arg(getLevelName(level)).arg(message); +} + +void SystemLogPanel::clearLogs() +{ + m_logTextEdit->clear(); + + // 重置计数器 + for (int i = 0; i < 5; ++i) { + m_logCounts[i] = 0; + } + m_totalLogCount = 0; + + // 更新状态信息 + updateStatusInfo(); + + qDebug() << "System logs cleared"; +} + +void SystemLogPanel::pauseLogging() +{ + m_isPaused = true; + m_pauseButton->setText("恢复"); + updateStatusInfo(); + qDebug() << "Logging paused"; +} + +void SystemLogPanel::resumeLogging() +{ + m_isPaused = false; + m_pauseButton->setText("暂停"); + updateStatusInfo(); + qDebug() << "Logging resumed"; +} + +void SystemLogPanel::setLogLevelFilter(LogLevel minLevel) +{ + m_minLevel = minLevel; + // 更新下拉框选择 + for (int i = 0; i < m_levelFilter->count(); ++i) { + if (m_levelFilter->itemData(i).toInt() == static_cast(minLevel)) { + m_levelFilter->setCurrentIndex(i); + break; + } + } + qDebug() << "Log level filter set to:" << getLevelName(minLevel); +} + +void SystemLogPanel::onClearButtonClicked() +{ + clearLogs(); +} + +void SystemLogPanel::onPauseButtonClicked() +{ + if (m_isPaused) { + resumeLogging(); + } else { + pauseLogging(); + } +} + +void SystemLogPanel::onLevelFilterChanged(int index) +{ + if (index >= 0 && index < m_levelFilter->count()) { + LogLevel newLevel = static_cast(m_levelFilter->itemData(index).toInt()); + m_minLevel = newLevel; + qDebug() << "Log level filter changed to:" << getLevelName(newLevel); + } +} + +void SystemLogPanel::updateStatusInfo() +{ + QString statusText = QString("总计: %1 | 错误: %2 | 警告: %3") + .arg(m_totalLogCount) + .arg(m_logCounts[Error]) + .arg(m_logCounts[Warning]); + + if (m_isPaused) { + statusText += " | 已暂停"; + } else { + statusText += " | 运行中"; + } + + m_statusLabel->setText(statusText); +} + +QString SystemLogPanel::formatLogEntry(LogLevel level, const QString &message) +{ + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); + QString levelIcon = getLevelIcon(level); + QString levelColor = getLevelColor(level); + + return QString("[%2] %3 %4") + .arg(levelColor) + .arg(timestamp) + .arg(levelIcon) + .arg(message); +} + +QString SystemLogPanel::getLevelIcon(LogLevel level) +{ + switch (level) { + case Debug: return "🔍"; + case Info: return "🔵"; + case Warning: return "🟡"; + case Error: return "🔴"; + case Success: return "🟢"; + default: return "⚪"; + } +} + +QString SystemLogPanel::getLevelColor(LogLevel level) +{ + switch (level) { + case Debug: return "#9E9E9E"; + case Info: return "#52C2F2"; + case Warning: return "#FFD700"; + case Error: return "#FF4444"; + case Success: return "#00FF7F"; + default: return "#FFFFFF"; + } +} + +QString SystemLogPanel::getLevelName(LogLevel level) +{ + switch (level) { + case Debug: return "Debug"; + case Info: return "Info"; + case Warning: return "Warning"; + case Error: return "Error"; + case Success: return "Success"; + default: return "Unknown"; + } +} + +void SystemLogPanel::limitLogLines() +{ + // 获取文档和光标 + QTextDocument *document = m_logTextEdit->document(); + int blockCount = document->blockCount(); + + // 如果超过最大行数,删除最旧的行 + if (blockCount > m_maxLogLines) { + QTextCursor cursor(document); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, + blockCount - m_maxLogLines); + cursor.removeSelectedText(); + } +} + +void SystemLogPanel::scrollToBottom() +{ + QScrollBar *scrollBar = m_logTextEdit->verticalScrollBar(); + scrollBar->setValue(scrollBar->maximum()); +} \ No newline at end of file diff --git a/src/Client/src/ui/dialogs/DeviceDialog.cpp b/src/Client/src/ui/dialogs/DeviceDialog.cpp index b30563ad..c7bd330e 100644 --- a/src/Client/src/ui/dialogs/DeviceDialog.cpp +++ b/src/Client/src/ui/dialogs/DeviceDialog.cpp @@ -8,6 +8,7 @@ #include "ui/dialogs/DeviceDialog.h" #include "build/ui_DeviceDialog.h" +#include "utils/SystemLogger.h" // Qt headers #include @@ -61,6 +62,7 @@ void DeviceDialog::setDeviceInfo(const QString &deviceId, const QString &name, c const QString &createdAt, const QString &updatedAt) { m_currentDeviceId = deviceId; + SystemLogger::getInstance()->logInfo(QString("打开设备详情对话框: %1").arg(name)); // 设置设备图标 setDeviceIcon(type); @@ -284,6 +286,7 @@ void DeviceDialog::refreshDeviceInfo() void DeviceDialog::onConnectClicked() { if (!m_currentDeviceId.isEmpty()) { + SystemLogger::getInstance()->logInfo("从详情对话框请求连接设备"); emit deviceConnectRequested(m_currentDeviceId); // 记录操作日志 @@ -294,6 +297,7 @@ void DeviceDialog::onConnectClicked() void DeviceDialog::onDisconnectClicked() { if (!m_currentDeviceId.isEmpty()) { + SystemLogger::getInstance()->logInfo("从详情对话框请求断开设备"); emit deviceDisconnectRequested(m_currentDeviceId); // 记录操作日志 @@ -304,6 +308,7 @@ void DeviceDialog::onDisconnectClicked() void DeviceDialog::onLocateClicked() { if (!m_currentDeviceId.isEmpty()) { + SystemLogger::getInstance()->logInfo("从详情对话框请求设备定位"); emit deviceLocationRequested(m_currentDeviceId); // 记录操作日志 @@ -313,6 +318,7 @@ void DeviceDialog::onLocateClicked() void DeviceDialog::onRefreshClicked() { + SystemLogger::getInstance()->logInfo("刷新设备信息"); refreshDeviceInfo(); } diff --git a/src/Client/src/ui/main/MainWindow.cpp b/src/Client/src/ui/main/MainWindow.cpp index 234832ea..b541d81f 100644 --- a/src/Client/src/ui/main/MainWindow.cpp +++ b/src/Client/src/ui/main/MainWindow.cpp @@ -9,6 +9,7 @@ #include "ui/main/MainWindow.h" #include "build/ui_MainWindow.h" #include "ui/dialogs/DeviceDialog.h" +#include "utils/SystemLogger.h" // Qt GUI头文件 #include @@ -42,11 +43,14 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_ui(new Ui::MainWindow) , m_deviceListPanel(nullptr) + , m_systemLogPanel(nullptr) + , m_leftPanelSplitter(nullptr) // , m_intelligenceUI(nullptr) // 暂时注释掉 { m_ui->setupUi(this); @@ -84,22 +88,60 @@ void MainWindow::setupUI() // 初始化随机数生成器 qsrand(QTime::currentTime().msec()); - // 创建并集成DeviceListPanel到左侧面板 - setupDeviceListPanel(); + // 创建并集成SystemLogPanel和DeviceListPanel到左侧面板 + setupSystemLogPanel(); // 恢复地图显示控制 setupMapDisplay(); - // 控制添加机器人 - addRobotControl(m_ui->addrobot); - // 控制机器人列表 - robotsInfosControl(m_ui->robottab); + // 注意:原有的重复设备管理按钮已被移除,功能集成在DeviceListPanel中 + + // 记录系统启动日志 + SystemLogger::getInstance()->logInfo("系统启动完成"); } -void MainWindow::setupDeviceListPanel() +void MainWindow::setupSystemLogPanel() { + // 创建系统日志面板 + m_systemLogPanel = new SystemLogPanel(this); + // 创建设备列表面板 m_deviceListPanel = new DeviceListPanel(this); + // 创建垂直分割器 + m_leftPanelSplitter = new QSplitter(Qt::Vertical, this); + + // 设置系统日志面板的高度限制 + m_systemLogPanel->setMinimumHeight(200); + m_systemLogPanel->setMaximumHeight(400); + + // 设置设备管理面板的高度限制 + m_deviceListPanel->setMinimumHeight(300); + + // 添加面板到分割器 + m_leftPanelSplitter->addWidget(m_systemLogPanel); + m_leftPanelSplitter->addWidget(m_deviceListPanel); + + // 设置分割比例 (35% : 65%) + m_leftPanelSplitter->setSizes(QList() << 350 << 650); + + // 设置分割器样式 + m_leftPanelSplitter->setStyleSheet( + "QSplitter::handle {" + " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," + " stop:0 rgba(82, 194, 242, 0.3)," + " stop:0.5 rgba(82, 194, 242, 0.8)," + " stop:1 rgba(82, 194, 242, 0.3));" + " border-radius: 2px;" + " height: 8px;" + "}" + "QSplitter::handle:hover {" + " background: qlineargradient(x1:0, y1:0, x2:1, y2:0," + " stop:0 rgba(82, 194, 242, 0.5)," + " stop:0.5 rgba(82, 194, 242, 1.0)," + " stop:1 rgba(82, 194, 242, 0.5));" + "}" + ); + // 获取左侧面板的布局 QVBoxLayout *leftLayout = qobject_cast(m_ui->leftPanel->layout()); if (leftLayout) { @@ -111,8 +153,8 @@ void MainWindow::setupDeviceListPanel() } } - // 将DeviceListPanel添加到左侧面板 - leftLayout->addWidget(m_deviceListPanel); + // 将分割器添加到左侧面板 + leftLayout->addWidget(m_leftPanelSplitter); // 连接DeviceListPanel信号 connect(m_deviceListPanel, &DeviceListPanel::deviceSelected, @@ -126,12 +168,20 @@ void MainWindow::setupDeviceListPanel() connect(m_deviceListPanel, &DeviceListPanel::addDeviceRequested, this, &MainWindow::onAddDeviceRequested); - qDebug() << "DeviceListPanel integrated into left panel - original content hidden"; + qDebug() << "SystemLogPanel and DeviceListPanel integrated with QSplitter (35%:65%)"; + SystemLogger::getInstance()->logInfo("系统日志面板初始化完成"); } else { qWarning() << "Failed to get left panel layout"; + SystemLogger::getInstance()->logError("左侧面板布局获取失败"); } } +void MainWindow::setupDeviceListPanel() +{ + // 此方法已被setupSystemLogPanel()替代,保留以兼容可能的调用 + qDebug() << "setupDeviceListPanel() is deprecated, use setupSystemLogPanel() instead"; +} + void MainWindow::setupStyle() { // 设置按钮样式 - 现代化军用风格 @@ -162,10 +212,7 @@ void MainWindow::setupStyle() "}"; // 应用样式到所有按钮 - m_ui->robottab->setStyleSheet(buttonStyle); - m_ui->addrobot->setStyleSheet(buttonStyle); - m_ui->addUAV->setStyleSheet(buttonStyle); - m_ui->UAVtab->setStyleSheet(buttonStyle); + // 注意:原有的重复设备管理按钮已被移除 m_ui->UAVview->setStyleSheet(buttonStyle); m_ui->robotView->setStyleSheet(buttonStyle); m_ui->robotMapping->setStyleSheet(buttonStyle); @@ -178,8 +225,7 @@ void MainWindow::setupStyle() void MainWindow::connectSignals() { // 连接按钮信号 - connect(m_ui->addUAV, &QPushButton::clicked, this, &MainWindow::onAddUAVClicked); - connect(m_ui->UAVtab, &QPushButton::clicked, this, &MainWindow::onUAVTabClicked); + // 注意:原有的重复设备管理按钮信号已被移除 connect(m_ui->UAVview, &QPushButton::clicked, this, &MainWindow::onUAVViewClicked); connect(m_ui->robotView, &QPushButton::clicked, this, &MainWindow::onRobotViewClicked); connect(m_ui->robotMapping, &QPushButton::clicked, this, &MainWindow::onRobotMappingClicked); @@ -424,6 +470,9 @@ void MainWindow::onAddRobotClicked() m_robotList.append(qMakePair(name, ip)); QMessageBox::information(this, "成功", "机器人添加成功!"); + // 记录成功日志 + SystemLogger::getInstance()->logSuccess(QString("机器人添加成功: %1 (%2)").arg(name).arg(deviceId)); + // 刷新设备列表 if (m_deviceListPanel) { m_deviceListPanel->refreshDeviceList(); @@ -432,6 +481,7 @@ void MainWindow::onAddRobotClicked() dialog->accept(); } else { QMessageBox::warning(this, "错误", "保存到数据库失败!"); + SystemLogger::getInstance()->logError(QString("机器人添加失败: %1 - 数据库保存失败").arg(name)); } } else { QMessageBox::warning(this, "错误", "请填写完整信息!"); @@ -611,6 +661,9 @@ void MainWindow::onAddUAVClicked() m_uavList.append(qMakePair(name, ip)); QMessageBox::information(this, "成功", "无人机添加成功!"); + // 记录成功日志 + SystemLogger::getInstance()->logSuccess(QString("无人机添加成功: %1 (%2)").arg(name).arg(deviceId)); + // 刷新设备列表 if (m_deviceListPanel) { m_deviceListPanel->refreshDeviceList(); @@ -619,6 +672,7 @@ void MainWindow::onAddUAVClicked() dialog->accept(); } else { QMessageBox::warning(this, "错误", "保存到数据库失败!"); + SystemLogger::getInstance()->logError(QString("无人机添加失败: %1 - 数据库保存失败").arg(name)); } } else { QMessageBox::warning(this, "错误", "请填写完整信息!"); @@ -724,6 +778,7 @@ void MainWindow::onDeviceControlRequested(const QString &deviceId) void MainWindow::onDeviceLocationRequested(const QString &deviceId) { qDebug() << "Device location requested for:" << deviceId; + SystemLogger::getInstance()->logInfo(QString("请求设备定位: %1").arg(deviceId)); // 从设备列表面板获取设备信息 if (m_deviceListPanel) { @@ -785,6 +840,7 @@ void MainWindow::onDeviceLocationRequested(const QString &deviceId) } qDebug() << QString("设备 %1 定位到位置: (%2, %3)").arg(deviceName).arg(latitude).arg(longitude); + SystemLogger::getInstance()->logSuccess(QString("设备定位完成: %1 -> (%2, %3)").arg(deviceName).arg(latitude).arg(longitude)); } else { QMessageBox::warning(this, "设备定位", QString("无法获取设备位置信息\n设备ID: %1").arg(deviceId)); @@ -806,6 +862,7 @@ void MainWindow::onDeviceDetailsRequested(const QString &deviceId) if (!db.open()) { qWarning() << "Failed to connect to database for device details:" << db.lastError().text(); + SystemLogger::getInstance()->logError("数据库连接失败 - 无法查询设备详情"); QMessageBox::warning(this, "错误", "无法连接到数据库"); return; } @@ -888,6 +945,7 @@ void MainWindow::onAddDeviceRequested(const QString &deviceType) void MainWindow::setupMapDisplay() { qDebug() << "Setting up map display..."; + SystemLogger::getInstance()->logInfo("开始设置地图显示"); // 创建WebEngineView来显示地图 QWebEngineView* webView = new QWebEngineView(m_ui->MapDisplayer); @@ -911,9 +969,11 @@ void MainWindow::setupMapDisplay() connect(webView, &QWebEngineView::loadFinished, this, [this](bool success) { if (success) { qDebug() << "Map loaded successfully, initializing device markers..."; + SystemLogger::getInstance()->logSuccess("地图加载完成"); QTimer::singleShot(1000, this, &MainWindow::initializeDeviceMarkersOnMap); } else { qDebug() << "Map loading failed!"; + SystemLogger::getInstance()->logError("地图加载失败"); } }); } @@ -976,6 +1036,7 @@ bool MainWindow::addDeviceToDatabase(const QString &deviceId, const QString &nam if (!db.open()) { qWarning() << "Failed to connect to database for adding device:" << db.lastError().text(); + SystemLogger::getInstance()->logError("数据库连接失败 - 无法添加设备"); return false; } diff --git a/src/Client/src/utils/SystemLogger.cpp b/src/Client/src/utils/SystemLogger.cpp new file mode 100644 index 00000000..7cb5bd89 --- /dev/null +++ b/src/Client/src/utils/SystemLogger.cpp @@ -0,0 +1,158 @@ +/** + * @file SystemLogger.cpp + * @brief 系统日志管理器单例类实现 + * @author Qt UI Developer Expert + * @date 2024-12-21 + * @version 1.0 + */ + +#include "utils/SystemLogger.h" +#include +#include +#include +#include +#include + +// 静态成员初始化 +SystemLogger* SystemLogger::s_instance = nullptr; +QMutex SystemLogger::s_mutex; + +SystemLogger::SystemLogger(QObject *parent) + : QObject(parent) + , m_consoleOutputEnabled(true) +{ + qDebug() << "SystemLogger instance created"; +} + +SystemLogger::~SystemLogger() +{ + qDebug() << "SystemLogger instance destroyed"; +} + +SystemLogger* SystemLogger::getInstance() +{ + // 双重检查锁定模式确保线程安全 + if (s_instance == nullptr) { + QMutexLocker locker(&s_mutex); + if (s_instance == nullptr) { + s_instance = new SystemLogger(); + } + } + return s_instance; +} + +void SystemLogger::destroyInstance() +{ + QMutexLocker locker(&s_mutex); + if (s_instance != nullptr) { + delete s_instance; + s_instance = nullptr; + } +} + +void SystemLogger::logDebug(const QString &message) +{ + log(Debug, message); +} + +void SystemLogger::logInfo(const QString &message) +{ + log(Info, message); +} + +void SystemLogger::logWarning(const QString &message) +{ + log(Warning, message); +} + +void SystemLogger::logError(const QString &message) +{ + log(Error, message); +} + +void SystemLogger::logSuccess(const QString &message) +{ + log(Success, message); +} + +void SystemLogger::log(LogLevel level, const QString &message) +{ + logInternal(level, message); +} + +void SystemLogger::setConsoleOutputEnabled(bool enabled) +{ + QMutexLocker locker(&m_logMutex); + m_consoleOutputEnabled = enabled; + + QString statusMsg = enabled ? "Console output enabled" : "Console output disabled"; + qDebug() << "SystemLogger:" << statusMsg; +} + +QString SystemLogger::getLevelString(LogLevel level) +{ + switch (level) { + case Debug: return "DEBUG"; + case Info: return "INFO"; + case Warning: return "WARNING"; + case Error: return "ERROR"; + case Success: return "SUCCESS"; + default: return "UNKNOWN"; + } +} + +void SystemLogger::logInternal(LogLevel level, const QString &message) +{ + // 线程安全保护 + QMutexLocker locker(&m_logMutex); + + // 输出到控制台(如果启用) + if (m_consoleOutputEnabled) { + outputToConsole(level, message); + } + + // 发出信号通知UI组件 + emit logAdded(level, message); +} + +void SystemLogger::outputToConsole(LogLevel level, const QString &message) +{ + QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); + QString levelStr = getLevelString(level); + QString logLine = QString("[%1] [%2] %3").arg(timestamp, levelStr, message); + + // 根据日志级别选择输出流 + switch (level) { + case Error: + // 错误输出到标准错误流 + std::cerr << logLine.toStdString() << std::endl; + break; + case Warning: + // 警告也输出到标准错误流 + std::cerr << logLine.toStdString() << std::endl; + break; + default: + // 其他级别输出到标准输出流 + std::cout << logLine.toStdString() << std::endl; + break; + } + + // 同时也输出到Qt的调试系统 + switch (level) { + case Debug: + qDebug().noquote() << logLine; + break; + case Info: + qInfo().noquote() << logLine; + break; + case Warning: + qWarning().noquote() << logLine; + break; + case Error: + qCritical().noquote() << logLine; + break; + case Success: + qInfo().noquote() << logLine; // 成功信息使用qInfo输出 + break; + } +} \ No newline at end of file