flower_linux 6 ay önce
ebeveyn
işleme
fb3f45090b

+ 5 - 0
CMakeLists.txt

@@ -62,6 +62,11 @@ qt_add_executable(soft_bus
     src/message_viewer/bus_data_table_widget.h src/message_viewer/bus_data_table_widget.cpp src/message_viewer/bus_data_table_widget.ui
     src/message_viewer/message_viewer_widget.h src/message_viewer/message_viewer_widget.cpp
     src/my_dock_manager/mdockmanager.cpp src/my_dock_manager/mdockmanager.h
+    src/database_manager/database_types.h
+    src/database_manager/databaseconnectiondock.h src/database_manager/databaseconnectiondock.cpp
+    src/database_manager/databasepage.h src/database_manager/databasepage.cpp
+    src/database_manager/databasemodule.h src/database_manager/databasemodule.cpp
+    src/database_manager/databasesearchdialog.h src/database_manager/databasesearchdialog.cpp
 
 )
 

+ 252 - 229
mainwindow.cpp

@@ -1,208 +1,234 @@
 #include "mainwindow.h"
-#include <QToolBar>
+#include "database_manager/databasemodule.h"
 #include <QAction>
-#include <QStackedWidget>
 #include <QHBoxLayout>
-#include <QStatusBar>
 #include <QLabel>
-#include <QSettings>
-#include <QMenuBar>
 #include <QMenu>
-#include <QShortcut>
+#include <QMenuBar>
 #include <QMessageBox>
+#include <QSettings>
+#include <QShortcut>
+#include <QStackedWidget>
+#include <QStatusBar>
+#include <QToolBar>
 
-MainWindow::MainWindow(QWidget *parent)
-    : QMainWindow(parent)
-{
-    createUI();
-}
-
-void MainWindow::createUI()
-{
-    // 创建菜单栏
-    createMenuBar();
-
-    // 创建主布局
-    QWidget *centralWidget = new QWidget(this);
-    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
-    // 界面设置
-    setWindowTitle("软件总线");
-    setWindowIcon(QIcon(":/qrc/icons/ads_icon.svg"));
-    setUnifiedTitleAndToolBarOnMac(true);
-    setDockNestingEnabled(true);
-    setAnimated(true);
-    setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
-    setDockOptions(QMainWindow::AllowTabbedDocks | QMainWindow::AnimatedDocks | QMainWindow::VerticalTabs);
-    setStatusBar(new QStatusBar(this));
-    setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
-    setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
-    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
-    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
-    setMinimumSize(800, 600);
-    setMaximumSize(1600, 1200);
-
-    // 创建页面堆栈
-    m_pageStack = new QStackedWidget(this);
-    mainLayout->addWidget(m_pageStack);
-    centralWidget->setLayout(mainLayout);
-    setCentralWidget(centralWidget);
-
-    // 创建工具栏和页面
-    createHomeToolbar();
-    createSerialToolbar();
-    createSettingsToolbar();
-    createHelpToolbar();
-
-    // 默认显示主界面
-    switchToPage(1);
-    // 设置快捷键
-    setupShortcuts();
-    // 链接信号槽
-    connectSignals();
-    // 创建状态栏
-    this->statusBar()->showMessage("就绪");
+MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { createUI(); }
+
+void MainWindow::createUI() {
+  // 创建菜单栏
+  createMenuBar();
+
+  // 创建主布局
+  QWidget *centralWidget = new QWidget(this);
+  QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
+  // 界面设置
+  setWindowTitle("软件总线");
+  setWindowIcon(QIcon(":/qrc/icons/ads_icon.svg"));
+  setUnifiedTitleAndToolBarOnMac(true);
+  setDockNestingEnabled(true);
+  setAnimated(true);
+  setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
+  setDockOptions(QMainWindow::AllowTabbedDocks | QMainWindow::AnimatedDocks |
+                 QMainWindow::VerticalTabs);
+  setStatusBar(new QStatusBar(this));
+  setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
+  setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
+  setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
+  setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
+  setMinimumSize(800, 600);
+  setMaximumSize(1600, 1200);
+
+  // 创建页面堆栈
+  m_pageStack = new QStackedWidget(this);
+  mainLayout->addWidget(m_pageStack);
+  centralWidget->setLayout(mainLayout);
+  setCentralWidget(centralWidget);
+
+  // 创建工具栏和页面
+  createHomeToolbar();
+  createSerialToolbar();
+  createDatabaseToolbar();
+  createSettingsToolbar();
+  createHelpToolbar();
+
+  // 默认显示主界面
+  switchToPage(1);
+  // 设置快捷键
+  setupShortcuts();
+  // 链接信号槽
+  connectSignals();
+  // 创建状态栏
+  this->statusBar()->showMessage("就绪");
 }
 
-void MainWindow::createMenuBar()
-{
-    // 创建菜单栏
-    QMenuBar *menuBar = new QMenuBar(this);
-    setMenuBar(menuBar);
-
-    // 文件菜单
-    QMenu *fileMenu = menuBar->addMenu("文件");
-    QAction *newAction = fileMenu->addAction(QIcon(":/qrc/icons/new.png"), "新建");
-    QAction *openAction = fileMenu->addAction(QIcon(":/qrc/icons/open.png"), "打开");
-    QAction *saveAction = fileMenu->addAction(QIcon(":/qrc/icons/save.png"), "保存");
-    fileMenu->addSeparator();
-    QAction *exitAction = fileMenu->addAction(QIcon(":/qrc/icons/exit.png"), "退出");
-
-    // 编辑菜单
-    QMenu *editMenu = menuBar->addMenu("编辑");
-    QAction *undoAction = editMenu->addAction(QIcon(":/qrc/icons/undo.png"), "撤销");
-    QAction *redoAction = editMenu->addAction(QIcon(":/qrc/icons/redo.png"), "重做");
-    editMenu->addSeparator();
-    QAction *cutAction = editMenu->addAction(QIcon(":/qrc/icons/cut.png"), "剪切");
-    QAction *copyAction = editMenu->addAction(QIcon(":/qrc/icons/copy.png"), "复制");
-    QAction *pasteAction = editMenu->addAction(QIcon(":/qrc/icons/paste.png"), "粘贴");
-
-    // 视图菜单
-    QMenu *viewMenu = menuBar->addMenu("视图");
-    QAction *homeAction = viewMenu->addAction(QIcon(":/qrc/icons/home.png"), "主界面");
-    QAction *serialAction = viewMenu->addAction(QIcon(":/qrc/icons/serial.png"), "串口");
-    QAction *settingsAction = viewMenu->addAction(QIcon(":/qrc/icons/settings.png"), "设置");
-    QAction *helpAction = viewMenu->addAction(QIcon(":/qrc/icons/help.png"), "帮助");
-
-    // 连接视图菜单的切换功能
-    connect(homeAction, &QAction::triggered, this, [this]()
-            { switchToPage(0); });
-    connect(serialAction, &QAction::triggered, this, [this]()
-            { switchToPage(1); });
-    connect(settingsAction, &QAction::triggered, this, [this]()
-            { switchToPage(2); });
-    connect(helpAction, &QAction::triggered, this, [this]()
-            { switchToPage(3); });
-
-    // 帮助菜单
-    QMenu *helpMenu = menuBar->addMenu("帮助");
-    QAction *helpDocAction = helpMenu->addAction(QIcon(":/qrc/icons/help.png"), "帮助文档");
-    QAction *aboutAction = helpMenu->addAction(QIcon(":/qrc/icons/about.png"), "关于");
-    QAction *updateAction = helpMenu->addAction(QIcon(":/qrc/icons/update.png"), "检查更新");
-
-    connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);
+void MainWindow::createMenuBar() {
+  // 视图菜单
+  QMenu *viewMenu = menuBar->addMenu("视图");
+  QAction *homeAction =
+      viewMenu->addAction(QIcon(":/qrc/icons/home.png"), "主界面");
+  QAction *serialAction =
+      viewMenu->addAction(QIcon(":/qrc/icons/serial.png"), "串口");
+  QAction *databaseAction =
+      viewMenu->addAction(QIcon(":/qrc/icons/grid_on.svg"), "数据库");
+  QAction *settingsAction =
+      viewMenu->addAction(QIcon(":/qrc/icons/settings.png"), "设置");
+  QAction *helpAction =
+      viewMenu->addAction(QIcon(":/qrc/icons/help.png"), "帮助");
+  // 连接视图菜单的切换功能
+  connect(homeAction, &QAction::triggered, this, [this]() { switchToPage(0); });
+  connect(serialAction, &QAction::triggered, this,
+          [this]() { switchToPage(1); });
+  connect(databaseAction, &QAction::triggered, this,
+          [this]() { switchToPage(2); });
+  connect(settingsAction, &QAction::triggered, this,
+          [this]() { switchToPage(3); });
+  connect(helpAction, &QAction::triggered, this, [this]() { switchToPage(4); });
+  
+  // 创建菜单栏
+  QMenuBar *menuBar = new QMenuBar(this);
+  setMenuBar(menuBar);
+
+  // 文件菜单
+  QMenu *fileMenu = menuBar->addMenu("文件");
+  QAction *newAction =
+      fileMenu->addAction(QIcon(":/qrc/icons/new.png"), "新建");
+  QAction *openAction =
+      fileMenu->addAction(QIcon(":/qrc/icons/open.png"), "打开");
+  QAction *saveAction =
+      fileMenu->addAction(QIcon(":/qrc/icons/save.png"), "保存");
+  fileMenu->addSeparator();
+  QAction *exitAction =
+      fileMenu->addAction(QIcon(":/qrc/icons/exit.png"), "退出");
+
+  // 编辑菜单
+  QMenu *editMenu = menuBar->addMenu("编辑");
+  QAction *undoAction =
+      editMenu->addAction(QIcon(":/qrc/icons/undo.png"), "撤销");
+  QAction *redoAction =
+      editMenu->addAction(QIcon(":/qrc/icons/redo.png"), "重做");
+  editMenu->addSeparator();
+  QAction *cutAction =
+      editMenu->addAction(QIcon(":/qrc/icons/cut.png"), "剪切");
+  QAction *copyAction =
+      editMenu->addAction(QIcon(":/qrc/icons/copy.png"), "复制");
+  QAction *pasteAction =
+      editMenu->addAction(QIcon(":/qrc/icons/paste.png"), "粘贴");
+
+  // 帮助菜单
+  QMenu *helpMenu = menuBar->addMenu("帮助");
+  QAction *helpDocAction =
+      helpMenu->addAction(QIcon(":/qrc/icons/help.png"), "帮助文档");
+  QAction *aboutAction =
+      helpMenu->addAction(QIcon(":/qrc/icons/about.png"), "关于");
+  QAction *updateAction =
+      helpMenu->addAction(QIcon(":/qrc/icons/update.png"), "检查更新");
+
+  connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);
 }
 
-void MainWindow::createHomeToolbar()
-{
-    // 创建主界面工具栏
-    Toolbar *homeToolbar = new Toolbar("主工具栏", this);
-    addToolBar(Qt::TopToolBarArea, homeToolbar);
-    homeToolbar->hide();
-
-    // 添加按钮
-    homeToolbar->addAction(QIcon(":/qrc/icons/new.png"), "新建");
-    homeToolbar->addAction(QIcon(":/qrc/icons/open.png"), "打开");
-    homeToolbar->addAction(QIcon(":/qrc/icons/save.png"), "保存");
-    homeToolbar->addAction(QIcon(":/qrc/icons/print.png"), "打印");
-
-    // 创建主界面页面
-    QLabel *homePage = new QLabel("<center><h1>主界面</h1></center>", this);
-    homePage->setAlignment(Qt::AlignCenter);
-
-    // 添加到堆栈
-    m_toolbars.append(homeToolbar);
-    m_pageStack->addWidget(homePage);
+void MainWindow::createHomeToolbar() {
+  // 创建主界面工具栏
+  Toolbar *homeToolbar = new Toolbar("主工具栏", this);
+  addToolBar(Qt::TopToolBarArea, homeToolbar);
+  homeToolbar->hide();
+
+  // 添加按钮
+  homeToolbar->addAction(QIcon(":/qrc/icons/new.png"), "新建");
+  homeToolbar->addAction(QIcon(":/qrc/icons/open.png"), "打开");
+  homeToolbar->addAction(QIcon(":/qrc/icons/save.png"), "保存");
+  homeToolbar->addAction(QIcon(":/qrc/icons/print.png"), "打印");
+
+  // 创建主界面页面
+  QLabel *homePage = new QLabel("<center><h1>主界面</h1></center>", this);
+  homePage->setAlignment(Qt::AlignCenter);
+
+  // 添加到堆栈
+  m_toolbars.append(homeToolbar);
+  m_pageStack->addWidget(homePage);
 }
 // 串口界面
-void MainWindow::createSerialToolbar()
-{
-    // 创建串口界面工具栏
-    Toolbar *serialToolbar = new Toolbar("主工具栏", this);
-    addToolBar(Qt::TopToolBarArea, serialToolbar);
-    serialToolbar->hide();
-
-    // 添加按钮
-    serialToolbar->addAction(QIcon(":/qrc/icons/new.png"), "新建");
-    serialToolbar->addAction(QIcon(":/qrc/icons/open.png"), "打开");
-    serialToolbar->addAction(QIcon(":/qrc/icons/save.png"), "保存");
-    serialToolbar->addAction(QIcon(":/qrc/icons/print.png"), "打印");
-
-    // 创建串口界面页面
-    QWidget *serialPage = new QWidget(this);
-    QVBoxLayout *serialLayout = new QVBoxLayout(serialPage);
-
-    // 创建 ViewSerial,不把 serialPage 当 parent(更安全)
-    m_viewSerial = new ViewSerial(nullptr);
-
-    // 将 m_viewSerial 添加到 serialPage 的布局中(关键)
-    serialLayout->addWidget(m_viewSerial);
-
-    // 添加到堆栈
-    m_toolbars.append(serialToolbar);
-    m_pageStack->addWidget(serialPage);
+void MainWindow::createSerialToolbar() {
+  // 创建串口界面工具栏
+  Toolbar *serialToolbar = new Toolbar("主工具栏", this);
+  addToolBar(Qt::TopToolBarArea, serialToolbar);
+  serialToolbar->hide();
+
+  // 添加按钮
+  serialToolbar->addAction(QIcon(":/qrc/icons/new.png"), "新建");
+  serialToolbar->addAction(QIcon(":/qrc/icons/open.png"), "打开");
+  serialToolbar->addAction(QIcon(":/qrc/icons/save.png"), "保存");
+  serialToolbar->addAction(QIcon(":/qrc/icons/print.png"), "打印");
+
+  // 创建串口界面页面
+  QWidget *serialPage = new QWidget(this);
+  QVBoxLayout *serialLayout = new QVBoxLayout(serialPage);
+
+  // 创建 ViewSerial,不把 serialPage 当 parent(更安全)
+  m_viewSerial = new ViewSerial(nullptr);
+
+  // 将 m_viewSerial 添加到 serialPage 的布局中(关键)
+  serialLayout->addWidget(m_viewSerial);
+
+  // 添加到堆栈
+  m_toolbars.append(serialToolbar);
+  m_pageStack->addWidget(serialPage);
+}
+
+void MainWindow::createDatabaseToolbar() {
+  if (!m_databaseModule) {
+    m_databaseModule = new DatabaseModule(this, this);
+  }
+
+  if (Toolbar *databaseToolbar = m_databaseModule->toolbar()) {
+    addToolBar(Qt::TopToolBarArea, databaseToolbar);
+    databaseToolbar->hide();
+    m_toolbars.append(databaseToolbar);
+  }
+
+  if (QWidget *databasePage = m_databaseModule->pageWidget()) {
+    m_databasePageIndex = m_pageStack->addWidget(databasePage);
+  }
 }
 
-void MainWindow::createSettingsToolbar()
-{
-    // 创建设置界面工具栏
-    Toolbar *settingsToolbar = new Toolbar("设置工具栏", this);
-    addToolBar(Qt::TopToolBarArea, settingsToolbar);
-    settingsToolbar->hide();
-
-    // 添加按钮
-    settingsToolbar->addAction(QIcon(":/qrc/icons/preferences.png"), "首选项");
-    settingsToolbar->addAction(QIcon(":/qrc/icons/theme.png"), "主题");
-    settingsToolbar->addAction(QIcon(":/qrc/icons/keyboard.png"), "快捷键");
-    settingsToolbar->addAction(QIcon(":/qrc/icons/plugins.png"), "插件");
-
-    // 创建设置界面页面
-    QLabel *settingsPage = new QLabel("<center><h1>设置界面</h1></center>", this);
-    settingsPage->setAlignment(Qt::AlignCenter);
-
-    // 添加到堆栈
-    m_toolbars.append(settingsToolbar);
-    m_pageStack->addWidget(settingsPage);
+void MainWindow::createSettingsToolbar() {
+  // 创建设置界面工具栏
+  Toolbar *settingsToolbar = new Toolbar("设置工具栏", this);
+  addToolBar(Qt::TopToolBarArea, settingsToolbar);
+  settingsToolbar->hide();
+
+  // 添加按钮
+  settingsToolbar->addAction(QIcon(":/qrc/icons/preferences.png"), "首选项");
+  settingsToolbar->addAction(QIcon(":/qrc/icons/theme.png"), "主题");
+  settingsToolbar->addAction(QIcon(":/qrc/icons/keyboard.png"), "快捷键");
+  settingsToolbar->addAction(QIcon(":/qrc/icons/plugins.png"), "插件");
+
+  // 创建设置界面页面
+  QLabel *settingsPage = new QLabel("<center><h1>设置界面</h1></center>", this);
+  settingsPage->setAlignment(Qt::AlignCenter);
+
+  // 添加到堆栈
+  m_toolbars.append(settingsToolbar);
+  m_pageStack->addWidget(settingsPage);
 }
 
-void MainWindow::createHelpToolbar()
-{
-    // 创建帮助界面工具栏
-    Toolbar *helpToolbar = new Toolbar("帮助工具栏", this);
-    addToolBar(Qt::TopToolBarArea, helpToolbar);
-    helpToolbar->hide();
-
-    // 添加按钮
-    helpToolbar->addAction(QIcon(":/qrc/icons/help.png"), "帮助文档");
-    helpToolbar->addAction(QIcon(":/qrc/icons/about.png"), "关于");
-    helpToolbar->addAction(QIcon(":/qrc/icons/update.png"), "检查更新");
-
-    // 创建帮助界面页面
-    QLabel *helpPage = new QLabel("<center><h1>帮助界面</h1></center>", this);
-    helpPage->setAlignment(Qt::AlignCenter);
-
-    // 添加到堆栈
-    m_toolbars.append(helpToolbar);
-    m_pageStack->addWidget(helpPage);
+void MainWindow::createHelpToolbar() {
+  // 创建帮助界面工具栏
+  Toolbar *helpToolbar = new Toolbar("帮助工具栏", this);
+  addToolBar(Qt::TopToolBarArea, helpToolbar);
+  helpToolbar->hide();
+
+  // 添加按钮
+  helpToolbar->addAction(QIcon(":/qrc/icons/help.png"), "帮助文档");
+  helpToolbar->addAction(QIcon(":/qrc/icons/about.png"), "关于");
+  helpToolbar->addAction(QIcon(":/qrc/icons/update.png"), "检查更新");
+
+  // 创建帮助界面页面
+  QLabel *helpPage = new QLabel("<center><h1>帮助界面</h1></center>", this);
+  helpPage->setAlignment(Qt::AlignCenter);
+
+  // 添加到堆栈
+  m_toolbars.append(helpToolbar);
+  m_pageStack->addWidget(helpPage);
 }
 
 /*
@@ -212,20 +238,17 @@ function:  返回指定索引的页面
     @param index 页面索引
     @return 无
 */
-void MainWindow::switchToPage(int index)
-{
-    // 隐藏所有工具栏
-    for (Toolbar *toolbar : m_toolbars)
-    {
-        toolbar->hide();
-    }
-
-    // 显示当前工具栏
-    if (index >= 0 && index < m_toolbars.size())
-    {
-        m_toolbars[index]->show();
-        m_pageStack->setCurrentIndex(index);
-    }
+void MainWindow::switchToPage(int index) {
+  // 隐藏所有工具栏
+  for (Toolbar *toolbar : m_toolbars) {
+    toolbar->hide();
+  }
+
+  // 显示当前工具栏
+  if (index >= 0 && index < m_toolbars.size()) {
+    m_toolbars[index]->show();
+    m_pageStack->setCurrentIndex(index);
+  }
 }
 
 /*
@@ -235,23 +258,25 @@ function:  设置快捷键
     @param
     @return 无
 */
-void MainWindow::setupShortcuts()
-{
-    // 创建快捷键
-    QShortcut *homeShortcut = new QShortcut(QKeySequence("Ctrl+1"), this);
-    QShortcut *serialShortcut = new QShortcut(QKeySequence("Ctrl+2"), this);
-    QShortcut *settingsShortcut = new QShortcut(QKeySequence("Ctrl+3"), this);
-    QShortcut *helpShortcut = new QShortcut(QKeySequence("Ctrl+4"), this);
-
-    // 连接快捷键信号
-    connect(homeShortcut, &QShortcut::activated, this, [this]()
-            { switchToPage(0); });
-    connect(serialShortcut, &QShortcut::activated, this, [this]()
-            { switchToPage(1); });
-    connect(settingsShortcut, &QShortcut::activated, this, [this]()
-            { switchToPage(2); });
-    connect(helpShortcut, &QShortcut::activated, this, [this]()
-            { switchToPage(3); });
+void MainWindow::setupShortcuts() {
+  // 创建快捷键
+  QShortcut *homeShortcut = new QShortcut(QKeySequence("Ctrl+1"), this);
+  QShortcut *serialShortcut = new QShortcut(QKeySequence("Ctrl+2"), this);
+  QShortcut *databaseShortcut = new QShortcut(QKeySequence("Ctrl+3"), this);
+  QShortcut *settingsShortcut = new QShortcut(QKeySequence("Ctrl+4"), this);
+  QShortcut *helpShortcut = new QShortcut(QKeySequence("Ctrl+5"), this);
+
+  // 连接快捷键信号
+  connect(homeShortcut, &QShortcut::activated, this,
+          [this]() { switchToPage(0); });
+  connect(serialShortcut, &QShortcut::activated, this,
+          [this]() { switchToPage(1); });
+  connect(databaseShortcut, &QShortcut::activated, this,
+          [this]() { switchToPage(2); });
+  connect(settingsShortcut, &QShortcut::activated, this,
+          [this]() { switchToPage(3); });
+  connect(helpShortcut, &QShortcut::activated, this,
+          [this]() { switchToPage(4); });
 }
 /*
 function:  链接信号槽
@@ -260,16 +285,14 @@ function:  链接信号槽
     @param
     @return 无
 */
-void MainWindow::connectSignals()
-{
-    // 连接信号槽
+void MainWindow::connectSignals() {
+  // 连接信号槽
 }
 
-void MainWindow::showAbout()
-{
-    QMessageBox::about(this, "关于",
-                       "<h2>基于DoDAF的多源数据融合系统总线</h2>"
-                       "<p> </p>"
-                       "<p>版本: 1.0.0</p>"
-                       "<p>© flower </p>");
+void MainWindow::showAbout() {
+  QMessageBox::about(this, "关于",
+                     "<h2>基于DoDAF的多源数据融合系统总线</h2>"
+                     "<p> </p>"
+                     "<p>版本: 1.0.0</p>"
+                     "<p>© flower </p>");
 }

+ 5 - 0
mainwindow.h

@@ -7,6 +7,8 @@
 #include "view_serial/viewserial.h"
 #include <QVector>
 
+class DatabaseModule;
+
 class MainWindow : public QMainWindow
 {
     Q_OBJECT
@@ -22,6 +24,7 @@ private:
     // toolbar-工具栏
     void createHomeToolbar();
     void createSerialToolbar();
+    void createDatabaseToolbar();
 
     void createSettingsToolbar();
     void createHelpToolbar();
@@ -36,6 +39,8 @@ private:
 
     // 子界面
     ViewSerial *m_viewSerial;
+    DatabaseModule *m_databaseModule = nullptr;
+    int m_databasePageIndex = -1;
 };
 
 #endif // MAINWINDOW_H

+ 17 - 0
src/database_manager/database_types.h

@@ -0,0 +1,17 @@
+#ifndef DATABASE_TYPES_H
+#define DATABASE_TYPES_H
+
+#include <QString>
+
+struct DatabaseConnectionParams
+{
+    QString driver;
+    QString host;
+    int port = 0;
+    QString database;
+    QString user;
+    QString password;
+};
+
+#endif // DATABASE_TYPES_H
+

+ 205 - 0
src/database_manager/databaseconnectiondock.cpp

@@ -0,0 +1,205 @@
+#include "databaseconnectiondock.h"
+
+#include <QComboBox>
+#include <QFormLayout>
+#include <QHBoxLayout>
+#include <QIntValidator>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QSqlDatabase>
+#include <QVBoxLayout>
+#include <QVariant>
+
+/*
+function:  构造函数
+@author:   flower
+@date:     2025-11-11
+    @param parent 父窗口
+    @return 无
+*/
+DatabaseConnectionDock::DatabaseConnectionDock(QWidget *parent)
+    : QWidget(parent) {
+  setObjectName(QStringLiteral("DatabaseConnectionDock"));
+  setWindowTitle(tr("服务器配置"));
+
+  auto *layout = new QVBoxLayout(this);
+  layout->setContentsMargins(0, 0, 0, 0);
+  layout->setSpacing(0);
+
+  QWidget *content = createContentWidget();
+  layout->addWidget(content);
+  setConnected(false);
+  setStatusMessage(tr("未连接"), false);
+}
+/*
+function:  创建内容窗口
+@author:   flower
+@date:     2025-11-11
+    @return 无
+*/
+
+QWidget *DatabaseConnectionDock::createContentWidget() {
+  QWidget *widget = new QWidget(this);
+  auto *layout = new QVBoxLayout(widget);
+  layout->setContentsMargins(8, 8, 8, 8);
+  layout->setSpacing(8);
+
+  m_statusLabel = new QLabel(widget);
+  m_statusLabel->setObjectName(QStringLiteral("DatabaseStatusLabel"));
+  layout->addWidget(m_statusLabel);
+
+  auto *formLayout = new QFormLayout();
+  formLayout->setLabelAlignment(Qt::AlignLeft);
+  formLayout->setFormAlignment(Qt::AlignTop);
+
+  m_driverCombo = new QComboBox(widget);
+  m_driverCombo->addItems(QSqlDatabase::drivers());
+  formLayout->addRow(tr("驱动"), m_driverCombo);
+  // 设置默认驱动为 QPSQL
+  int idx = m_driverCombo->findText(QStringLiteral("QPSQL"));
+  if (idx >= 0) {
+    m_driverCombo->setCurrentIndex(idx);
+  } else if (m_driverCombo->count() > 0) {
+    m_driverCombo->setCurrentIndex(0);
+  }
+
+  m_hostEdit = new QLineEdit(widget);
+  m_hostEdit->setPlaceholderText(QStringLiteral("127.0.0.1"));
+  formLayout->addRow(tr("主机"), m_hostEdit);
+
+  m_portEdit = new QLineEdit(widget);
+  m_portEdit->setPlaceholderText(QStringLiteral("5432"));
+  m_portEdit->setValidator(new QIntValidator(0, 65535, m_portEdit));
+  formLayout->addRow(tr("端口"), m_portEdit);
+
+  m_databaseEdit = new QLineEdit(widget);
+  m_databaseEdit->setPlaceholderText(QStringLiteral("soft_bus_db"));
+  formLayout->addRow(tr("数据库"), m_databaseEdit);
+
+  m_userEdit = new QLineEdit(widget);
+  m_userEdit->setPlaceholderText(QStringLiteral("postgres"));
+  formLayout->addRow(tr("用户名"), m_userEdit);
+
+  m_passwordEdit = new QLineEdit(widget);
+  m_passwordEdit->setEchoMode(QLineEdit::Password);
+  formLayout->addRow(tr("密码"), m_passwordEdit);
+
+  layout->addLayout(formLayout);
+
+  auto *buttonLayout = new QHBoxLayout();
+  buttonLayout->setSpacing(12);
+
+  m_connectButton = new QPushButton(tr("连接"), widget);
+  m_disconnectButton = new QPushButton(tr("断开"), widget);
+
+  buttonLayout->addWidget(m_connectButton);
+  buttonLayout->addWidget(m_disconnectButton);
+  buttonLayout->addStretch();
+
+  layout->addLayout(buttonLayout);
+  layout->addStretch();
+
+  connect(m_connectButton, &QPushButton::clicked, this,
+          &DatabaseConnectionDock::handleConnectClicked);
+  connect(m_disconnectButton, &QPushButton::clicked, this,
+          &DatabaseConnectionDock::handleDisconnectClicked);
+
+  return widget;
+}
+
+/*
+function:  处理连接按钮点击事件
+@author:   flower
+@date:     2025-11-11
+    @return 无
+*/
+// 根据提示,若用户未输入,则使用占位符默认值
+void DatabaseConnectionDock::handleConnectClicked() {
+  DatabaseConnectionParams params;
+  params.driver = m_driverCombo->currentText();
+  params.host = m_hostEdit->text().trimmed().isEmpty()
+                    ? m_hostEdit->placeholderText()
+                    : m_hostEdit->text().trimmed();
+  params.port = m_portEdit->text().trimmed().isEmpty()
+                    ? m_portEdit->placeholderText().toInt()
+                    : m_portEdit->text().toInt();
+  params.database = m_databaseEdit->text().trimmed().isEmpty()
+                        ? m_databaseEdit->placeholderText()
+                        : m_databaseEdit->text().trimmed();
+  params.user = m_userEdit->text().trimmed().isEmpty()
+                    ? m_userEdit->placeholderText()
+                    : m_userEdit->text().trimmed();
+  params.password =
+      m_passwordEdit->text(); // 密码不强制使用 placeholder,防止泄漏
+
+  if (params.driver.isEmpty()) {
+    setStatusMessage(tr("请选择数据库驱动"), false);
+    return;
+  }
+
+  emit connectRequested(params);
+};
+
+/*
+function:  处理断开按钮点击事件
+@author:   flower
+@date:     2025-11-11
+    @return 无
+*/
+void DatabaseConnectionDock::handleDisconnectClicked() {
+  emit disconnectRequested();
+}
+
+/*
+function:  设置状态消息
+@author:   flower
+@date:     2025-11-11
+    @param message 状态消息
+    @param success 是否成功
+    @return 无
+*/
+void DatabaseConnectionDock::setStatusMessage(const QString &message,
+                                              bool success) {
+  if (!m_statusLabel) {
+    return;
+  }
+  m_statusLabel->setText(message);
+  const QString color =
+      success ? QStringLiteral("#2f9e44") : QStringLiteral("#d9480f");
+  m_statusLabel->setStyleSheet(QStringLiteral("color:%1;").arg(color));
+}
+
+/*
+function:  设置忙碌状态
+@author:   flower
+@date:     2025-11-11
+    @param busy 忙碌状态
+    @return 无
+*/
+void DatabaseConnectionDock::setBusy(bool busy) {
+  const QList<QWidget *> inputs = {
+      m_driverCombo, m_hostEdit,     m_portEdit,      m_databaseEdit,
+      m_userEdit,    m_passwordEdit, m_connectButton, m_disconnectButton};
+  for (QWidget *widget : inputs) {
+    if (widget) {
+      widget->setEnabled(!busy);
+    }
+  }
+}
+
+/*
+function:  设置连接状态
+@author:   flower
+@date:     2025-11-11
+    @param connected 连接状态
+    @return 无
+*/
+void DatabaseConnectionDock::setConnected(bool connected) {
+  if (m_connectButton) {
+    m_connectButton->setEnabled(!connected);
+  }
+  if (m_disconnectButton) {
+    m_disconnectButton->setEnabled(connected);
+  }
+}

+ 48 - 0
src/database_manager/databaseconnectiondock.h

@@ -0,0 +1,48 @@
+#ifndef DATABASECONNECTIONDOCK_H
+#define DATABASECONNECTIONDOCK_H
+
+#include <QWidget>
+#include <QScopedPointer>
+#include "database_types.h"
+
+class QComboBox;
+class QLineEdit;
+class QLabel;
+class QPushButton;
+
+class DatabaseConnectionDock : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit DatabaseConnectionDock(QWidget *parent = nullptr);
+
+    void setStatusMessage(const QString &message, bool success);
+    void setBusy(bool busy);
+    void setConnected(bool connected);
+
+signals:
+    void connectRequested(const DatabaseConnectionParams &params);
+    void disconnectRequested();
+
+private slots:
+    void handleConnectClicked();
+    void handleDisconnectClicked();
+
+private:
+    QWidget *createContentWidget();
+
+private:
+    QComboBox *m_driverCombo = nullptr;
+    QLineEdit *m_hostEdit = nullptr;
+    QLineEdit *m_portEdit = nullptr;
+    QLineEdit *m_databaseEdit = nullptr;
+    QLineEdit *m_userEdit = nullptr;
+    QLineEdit *m_passwordEdit = nullptr;
+    QLabel *m_statusLabel = nullptr;
+    QPushButton *m_connectButton = nullptr;
+    QPushButton *m_disconnectButton = nullptr;
+};
+
+#endif // DATABASECONNECTIONDOCK_H
+

+ 895 - 0
src/database_manager/databasemodule.cpp

@@ -0,0 +1,895 @@
+#include "databasemodule.h"
+
+#include "databaseconnectiondock.h"
+#include "databasepage.h"
+#include "databasesearchdialog.h"
+#include "toolbar/toolbar.h"
+
+#include <QAction>
+#include <QIcon>
+#include <QMainWindow>
+#include <QSet>
+#include <QSqlDatabase>
+#include <QSqlDriver>
+#include <QSqlError>
+#include <QSqlQuery>
+#include <QSqlRecord>
+#include <QSqlTableModel>
+#include <QStatusBar>
+#include <QVariant>
+#include <QUuid>
+#include <algorithm>
+#include <utility>
+
+namespace {
+class PagedSqlTableModel : public QSqlTableModel
+{
+public:
+    PagedSqlTableModel(QObject *parent, const QSqlDatabase &db)
+        : QSqlTableModel(parent, db)
+    {
+        setEditStrategy(QSqlTableModel::OnManualSubmit);
+    }
+
+    void setPagination(int offset, int limit)
+    {
+        m_offset = std::max(0, offset);
+        m_limit = std::max(1, limit);
+    }
+
+protected:
+    QString selectStatement() const override
+    {
+        QString statement = QSqlTableModel::selectStatement();
+        if (m_limit > 0) {
+            statement += QStringLiteral(" LIMIT %1 OFFSET %2").arg(m_limit).arg(m_offset);
+        }
+        return statement;
+    }
+
+private:
+    int m_offset = 0;
+    int m_limit = 1;
+};
+
+constexpr int kDefaultPageSize = 20;
+
+} // namespace
+
+DatabaseModule::DatabaseModule(QMainWindow *owner, QObject *parent)
+    : QObject(parent)
+{
+    m_sortOrder = Qt::AscendingOrder;
+    m_pageSize = kDefaultPageSize;
+    setupUi(owner);
+}
+
+DatabaseModule::~DatabaseModule()
+{
+    disconnectFromServer();
+
+    if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
+        QSqlDatabase::removeDatabase(m_connectionName);
+    }
+}
+
+Toolbar *DatabaseModule::toolbar() const
+{
+    return m_toolbar;
+}
+
+QWidget *DatabaseModule::pageWidget() const
+{
+    return m_page;
+}
+
+void DatabaseModule::setupUi(QMainWindow *owner)
+{
+    Q_ASSERT(owner);
+
+    m_page = new DatabasePage(owner);
+
+    m_connectionDock = new DatabaseConnectionDock(m_page);
+    const QString connectionDockTitle = tr("服务器配置");
+    m_page->addDockWidget(m_connectionDock, connectionDockTitle, ads::RightDockWidgetArea);
+    m_connectionDockWidget = m_page->getDockWidget(connectionDockTitle);
+    if (m_connectionDockWidget) {
+        m_connectionDockWidget->closeDockWidget();
+    }
+
+    connect(m_connectionDock, &DatabaseConnectionDock::connectRequested,
+            this, &DatabaseModule::handleConnectRequested);
+    connect(m_connectionDock, &DatabaseConnectionDock::disconnectRequested,
+            this, &DatabaseModule::handleDisconnectRequested);
+
+    connect(m_page, &DatabasePage::tableActivated,
+            this, &DatabaseModule::handleTableActivated);
+    connect(m_page, &DatabasePage::refreshRequested,
+            this, &DatabaseModule::handleRefreshRequested);
+    connect(m_page, &DatabasePage::submitRequested,
+            this, &DatabaseModule::handleSubmitRequested);
+    connect(m_page, &DatabasePage::revertRequested,
+            this, &DatabaseModule::handleRevertRequested);
+    connect(m_page, &DatabasePage::deleteRowsRequested,
+            this, &DatabaseModule::handleDeleteRowsRequested);
+    connect(m_page, &DatabasePage::columnSearchRequested,
+            this, &DatabaseModule::handleColumnSearchRequested);
+
+    connect(m_page, &DatabasePage::pageChangeRequested,
+            this, &DatabaseModule::handlePageChangeRequested);
+    connect(m_page, &DatabasePage::pageSizeChangeRequested,
+            this, &DatabaseModule::handlePageSizeChanged);
+    connect(m_page, &DatabasePage::sortChangeRequested,
+            this, &DatabaseModule::handleSortChanged);
+
+    m_toolbar = new Toolbar(tr("数据库工具栏"), owner);
+    m_toolbar->hide();
+    QAction *configAction = m_toolbar->addAction(QIcon(":/qrc/icons/custom-menu-button.svg"),
+                                                 tr("服务器配置"));
+    QAction *refreshAction = m_toolbar->addAction(QIcon(":/qrc/icons/restore.svg"),
+                                                  tr("刷新"));
+    QAction *submitAction = m_toolbar->addAction(QIcon(":/qrc/icons/save.svg"),
+                                                 tr("提交"));
+    QAction *revertAction = m_toolbar->addAction(QIcon(":/qrc/icons/lock_outline.svg"),
+                                                 tr("撤销"));
+
+    connect(configAction, &QAction::triggered, this, [this]() {
+        if (m_connectionDockWidget) {
+            m_connectionDockWidget->toggleView(true);
+            if (m_connectionDock) {
+                m_connectionDock->setFocus();
+            } else if (QWidget *content = m_connectionDockWidget->widget()) {
+                content->setFocus();
+            }
+        }
+    });
+    connect(refreshAction, &QAction::triggered,
+            this, &DatabaseModule::handleRefreshRequested);
+    connect(submitAction, &QAction::triggered,
+            this, &DatabaseModule::handleSubmitRequested);
+    connect(revertAction, &QAction::triggered,
+            this, &DatabaseModule::handleRevertRequested);
+
+    if (owner && owner->statusBar()) {
+        connect(this, &DatabaseModule::statusMessage,
+                owner->statusBar(), qOverload<const QString &, int>(&QStatusBar::showMessage));
+    }
+}
+
+void DatabaseModule::handleConnectRequested(const DatabaseConnectionParams &params)
+{
+    if (!m_connectionDock) {
+        return;
+    }
+
+    m_connectionDock->setBusy(true);
+    m_connectionDock->setStatusMessage(tr("正在连接 %1...").arg(params.host), true);
+
+    disconnectFromServer();
+
+    m_connectionName = QStringLiteral("dbmodule_%1")
+                           .arg(QUuid::createUuid().toString(QUuid::Id128));
+
+    if (QSqlDatabase::contains(m_connectionName)) {
+        QSqlDatabase::removeDatabase(m_connectionName);
+    }
+
+    m_database = QSqlDatabase::addDatabase(params.driver, m_connectionName);
+    if (!params.host.isEmpty()) {
+        m_database.setHostName(params.host);
+    }
+    if (params.port > 0) {
+        m_database.setPort(params.port);
+    }
+    if (!params.database.isEmpty()) {
+        m_database.setDatabaseName(params.database);
+    }
+    if (!params.user.isEmpty()) {
+        m_database.setUserName(params.user);
+    }
+    if (!params.password.isEmpty()) {
+        m_database.setPassword(params.password);
+    }
+
+    if (!m_database.open()) {
+        const QString errorText = m_database.lastError().text();
+        updateStatus(tr("连接失败:%1").arg(errorText), false, 6000);
+        setConnected(false);
+        m_database = QSqlDatabase();
+        if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
+            QSqlDatabase::removeDatabase(m_connectionName);
+        }
+        m_connectionName.clear();
+        m_connectionDock->setBusy(false);
+        return;
+    }
+
+    setConnected(true);
+    updateStatus(tr("数据库连接成功"), true, 3000);
+    loadTables();
+    m_connectionDock->setBusy(false);
+}
+
+void DatabaseModule::handleDisconnectRequested()
+{
+    disconnectFromServer();
+}
+
+void DatabaseModule::disconnectFromServer()
+{
+    if (m_model) {
+        if (m_page) {
+            m_page->setTableModel(nullptr);
+        }
+        delete m_model;
+        m_model = nullptr;
+    }
+
+    m_currentTable.clear();
+    m_totalRows = 0;
+    m_currentPage = 1;
+    m_sortColumn = 0;
+    m_sortOrder = Qt::AscendingOrder;
+    m_pageSize = kDefaultPageSize;
+
+    if (m_database.isValid() && m_database.isOpen()) {
+        m_database.close();
+    }
+
+    if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
+        QSqlDatabase::removeDatabase(m_connectionName);
+    }
+    m_database = QSqlDatabase();
+    m_connectionName.clear();
+
+    if (m_page) {
+        m_page->clear();
+        m_page->setConnectionActive(false);
+    }
+    if (m_connectionDock) {
+        m_connectionDock->setConnected(false);
+        m_connectionDock->setBusy(false);
+        m_connectionDock->setStatusMessage(tr("未连接"), false);
+    }
+    emit statusMessage(tr("数据库连接已断开"), 4000);
+}
+
+void DatabaseModule::handleTableActivated(const QString &tableName)
+{
+    if (!m_database.isValid() || !m_database.isOpen()) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("尚未连接到数据库"), false);
+        }
+        return;
+    }
+    if (tableName.isEmpty()) {
+        if (m_page) {
+            m_page->setCurrentTableName(QString());
+            m_page->setTableModel(nullptr);
+        }
+        if (m_model) {
+            m_model->clear();
+        }
+        return;
+    }
+    loadTable(tableName);
+}
+
+void DatabaseModule::handleRefreshRequested()
+{
+    if (m_currentTable.isEmpty()) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("请先选择要刷新的表"), false);
+        }
+        return;
+    }
+    refreshCurrentPage(true);
+}
+
+void DatabaseModule::handleSubmitRequested()
+{
+    if (!m_model || m_currentTable.isEmpty()) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("没有可提交的修改"), false);
+        }
+        return;
+    }
+
+    if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
+        m_database.transaction();
+    }
+
+    if (!m_model->submitAll()) {
+        const QString errorText = m_model->lastError().text();
+        if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
+            m_database.rollback();
+        }
+        if (m_page) {
+            m_page->showInfoMessage(tr("提交失败:%1").arg(errorText), false);
+        }
+        return;
+    }
+
+    if (m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions)) {
+        m_database.commit();
+    }
+
+    refreshCurrentPage(true);
+
+    if (m_page) {
+        m_page->showInfoMessage(tr("已提交 %1 的修改").arg(m_currentTable), true);
+    }
+    emit statusMessage(tr("表 %1 的修改已保存").arg(m_currentTable), 3000);
+}
+
+void DatabaseModule::handleRevertRequested()
+{
+    if (!m_model) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("没有可以撤销的修改"), false);
+        }
+        return;
+    }
+    m_model->revertAll();
+    refreshCurrentPage(true);
+    if (m_page) {
+        m_page->showInfoMessage(tr("已撤销未提交的修改"), true);
+    }
+}
+
+void DatabaseModule::handleDeleteRowsRequested(const QModelIndexList &rows, bool batch)
+{
+    if (!m_model || m_currentTable.isEmpty()) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("当前没有可删除的数据"), false);
+        }
+        return;
+    }
+    if (rows.isEmpty()) {
+        return;
+    }
+
+    QSet<int> uniqueRows;
+    for (const QModelIndex &index : rows) {
+        if (index.isValid()) {
+            uniqueRows.insert(index.row());
+        }
+    }
+    if (uniqueRows.isEmpty()) {
+        return;
+    }
+
+    QList<int> rowNumbers = uniqueRows.values();
+    std::sort(rowNumbers.begin(), rowNumbers.end());
+
+    for (auto it = rowNumbers.crbegin(); it != rowNumbers.crend(); ++it) {
+        const int row = *it;
+        if (!m_model->removeRow(row)) {
+            if (m_page) {
+                m_page->showInfoMessage(tr("无法标记第 %1 行删除").arg(row + 1), false);
+            }
+            return;
+        }
+    }
+
+    const bool supportsTransactions = m_database.driver() && m_database.driver()->hasFeature(QSqlDriver::Transactions);
+    if (supportsTransactions) {
+        m_database.transaction();
+    }
+
+    if (!m_model->submitAll()) {
+        const QString errorText = m_model->lastError().text();
+        if (supportsTransactions) {
+            m_database.rollback();
+        }
+        m_model->revertAll();
+        if (m_page) {
+            m_page->showInfoMessage(tr("删除失败:%1").arg(errorText), false);
+        }
+        return;
+    }
+
+    if (supportsTransactions) {
+        m_database.commit();
+    }
+
+    refreshCurrentPage(true);
+
+    const int deletedCount = rowNumbers.size();
+    if (m_page) {
+        const QString message = batch && deletedCount > 1
+                                    ? tr("已批量删除 %1 行数据").arg(deletedCount)
+                                    : tr("已删除 1 行数据");
+        m_page->showInfoMessage(message, true);
+    }
+    emit statusMessage(tr("从表 %1 删除了 %2 行").arg(m_currentTable).arg(deletedCount), 3000);
+}
+
+void DatabaseModule::handleColumnSearchRequested(int column, const QString &header)
+{
+    if (!m_page) {
+        return;
+    }
+    if (!m_database.isValid() || !m_database.isOpen()) {
+        m_page->showInfoMessage(tr("尚未连接到数据库"), false);
+        return;
+    }
+    if (!m_model || m_currentTable.isEmpty()) {
+        m_page->showInfoMessage(tr("请先选择要搜索的数据表"), false);
+        return;
+    }
+    if (column < 0 || column >= m_model->columnCount()) {
+        return;
+    }
+
+    if (!m_searchDialog) {
+        m_searchDialog = new DatabaseSearchDialog(m_page);
+        connect(m_searchDialog, &DatabaseSearchDialog::requestSearch,
+                this, &DatabaseModule::handleSearchRequest);
+    }
+
+    const QStringList headers = currentHeaderLabels();
+    QString displayHeader = header;
+    if (displayHeader.isEmpty() && column >= 0 && column < headers.size()) {
+        displayHeader = headers.at(column);
+    }
+    if (displayHeader.isEmpty()) {
+        const QString fieldName = m_model->record().fieldName(column);
+        displayHeader = fieldName.isEmpty() ? tr("列 %1").arg(column + 1) : fieldName;
+    }
+
+    m_searchColumn = column;
+    m_searchDialog->setBusy(false);
+    m_searchDialog->setTableContext(m_currentTable, displayHeader);
+    m_searchDialog->setAvailableColumns(headers);
+    m_searchDialog->setInitialSortColumn(column);
+    m_searchDialog->resetSearch();
+    m_searchDialog->show();
+    m_searchDialog->raise();
+    m_searchDialog->activateWindow();
+}
+
+void DatabaseModule::handleSearchRequest(const QString &keyword,
+                                         int page,
+                                         int pageSize,
+                                         int sortColumn,
+                                         Qt::SortOrder order)
+{
+    if (!m_searchDialog) {
+        return;
+    }
+    if (!m_model || m_currentTable.isEmpty()) {
+        showSearchError(tr("当前没有可搜索的表"));
+        return;
+    }
+    if (!m_database.isValid() || !m_database.isOpen()) {
+        showSearchError(tr("尚未连接到数据库"));
+        return;
+    }
+    if (m_searchColumn < 0 || m_searchColumn >= m_model->columnCount()) {
+        showSearchError(tr("搜索列无效"));
+        return;
+    }
+
+    QStringList fieldNames = currentFieldNames();
+    if (fieldNames.isEmpty()) {
+        showSearchError(tr("无法获取列信息"));
+        return;
+    }
+    if (sortColumn < 0 || sortColumn >= fieldNames.size()) {
+        sortColumn = m_searchColumn;
+    }
+    const QString searchField = fieldNames.at(m_searchColumn);
+    const QString sortField = fieldNames.at(sortColumn);
+    if (searchField.isEmpty() || sortField.isEmpty()) {
+        showSearchError(tr("列名为空,无法执行搜索"));
+        return;
+    }
+
+    QSqlDriver *driver = m_database.driver();
+    if (!driver) {
+        showSearchError(tr("数据库驱动不可用"));
+        return;
+    }
+
+    const QString tableIdentifier = driver->escapeIdentifier(m_currentTable, QSqlDriver::TableName);
+    const QString searchIdentifier = driver->escapeIdentifier(searchField, QSqlDriver::FieldName);
+    const QString sortIdentifier = driver->escapeIdentifier(sortField, QSqlDriver::FieldName);
+
+    QStringList escapedFields;
+    escapedFields.reserve(fieldNames.size());
+    for (const QString &name : std::as_const(fieldNames)) {
+        escapedFields.append(driver->escapeIdentifier(name, QSqlDriver::FieldName));
+    }
+
+    const int safePageSize = pageSize > 0 ? pageSize : 1;
+    const QString likePattern = keyword.isEmpty()
+                                    ? QStringLiteral("%")
+                                    : QStringLiteral("%") + keyword + QStringLiteral("%");
+
+    const QString whereClause = QStringLiteral("%1 LIKE ?").arg(searchIdentifier);
+    const QString countSql = QStringLiteral("SELECT COUNT(*) FROM %1 WHERE %2")
+                                 .arg(tableIdentifier, whereClause);
+
+    QSqlQuery countQuery(m_database);
+    if (!countQuery.prepare(countSql)) {
+        showSearchError(tr("统计匹配数量失败:%1").arg(countQuery.lastError().text()));
+        return;
+    }
+    countQuery.addBindValue(likePattern);
+    if (!countQuery.exec() || !countQuery.next()) {
+        showSearchError(tr("统计匹配数量失败:%1").arg(countQuery.lastError().text()));
+        return;
+    }
+    const int totalRows = countQuery.value(0).toInt();
+    const int totalPages = (totalRows > 0 && safePageSize > 0)
+                               ? (totalRows + safePageSize - 1) / safePageSize
+                               : 0;
+
+    int safePage = page;
+    if (safePage < 1) {
+        safePage = 1;
+    }
+    if (totalPages > 0 && safePage > totalPages) {
+        safePage = totalPages;
+    }
+    if (totalPages == 0) {
+        safePage = 1;
+    }
+
+    const int offset = (safePage - 1) * safePageSize;
+    QString selectSql = QStringLiteral("SELECT %1 FROM %2 WHERE %3")
+                            .arg(escapedFields.join(QStringLiteral(", ")),
+                                 tableIdentifier,
+                                 whereClause);
+    const QString orderToken = order == Qt::AscendingOrder ? QStringLiteral("ASC") : QStringLiteral("DESC");
+    selectSql += QStringLiteral(" ORDER BY %1 %2").arg(sortIdentifier, orderToken);
+    selectSql += QStringLiteral(" LIMIT %1 OFFSET %2")
+                     .arg(QString::number(safePageSize), QString::number(offset));
+
+    QSqlQuery selectQuery(m_database);
+    if (!selectQuery.prepare(selectSql)) {
+        showSearchError(tr("执行查询失败:%1").arg(selectQuery.lastError().text()));
+        return;
+    }
+    selectQuery.addBindValue(likePattern);
+    if (!selectQuery.exec()) {
+        showSearchError(tr("执行查询失败:%1").arg(selectQuery.lastError().text()));
+        return;
+    }
+
+    QList<QVariantList> rows;
+    rows.reserve(safePageSize);
+
+    while (selectQuery.next()) {
+        QVariantList row;
+        row.reserve(fieldNames.size());
+        for (int columnIndex = 0; columnIndex < fieldNames.size(); ++columnIndex) {
+            row.append(selectQuery.value(columnIndex));
+        }
+        rows.append(row);
+    }
+
+    const QStringList headers = currentHeaderLabels();
+    m_searchDialog->setResults(rows, headers, totalRows, safePage, safePageSize);
+    if (totalRows > 0) {
+        m_searchDialog->setStatusMessage(tr("共找到 %1 条记录").arg(totalRows), false);
+    }
+    m_searchDialog->setBusy(false);
+}
+
+QStringList DatabaseModule::currentHeaderLabels() const
+{
+    QStringList headers;
+    if (!m_model) {
+        return headers;
+    }
+
+    const int columnCount = m_model->columnCount();
+    headers.reserve(columnCount);
+    for (int column = 0; column < columnCount; ++column) {
+        QString header = m_model->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString();
+        if (header.isEmpty()) {
+            header = tr("列 %1").arg(column + 1);
+        }
+        headers.append(header);
+    }
+    return headers;
+}
+
+QStringList DatabaseModule::currentFieldNames() const
+{
+    QStringList fieldNames;
+    if (!m_model) {
+        return fieldNames;
+    }
+    const QSqlRecord record = m_model->record();
+    const int columnCount = record.count();
+    fieldNames.reserve(columnCount);
+    for (int column = 0; column < columnCount; ++column) {
+        fieldNames.append(record.fieldName(column));
+    }
+    return fieldNames;
+}
+
+void DatabaseModule::showSearchError(const QString &message)
+{
+    if (m_searchDialog) {
+        m_searchDialog->setStatusMessage(message, true);
+        m_searchDialog->setBusy(false);
+    }
+    if (m_page) {
+        m_page->showInfoMessage(message, false);
+    }
+    emit statusMessage(message, 4000);
+}
+
+void DatabaseModule::resetModel()
+{
+    if (m_model) {
+        if (m_page) {
+            m_page->setTableModel(nullptr);
+        }
+        delete m_model;
+        m_model = nullptr;
+    }
+
+    if (m_database.isValid()) {
+        m_model = new PagedSqlTableModel(m_page, m_database);
+        if (m_page) {
+            m_page->setTableModel(m_model);
+        }
+    }
+}
+
+void DatabaseModule::loadTables()
+{
+    if (!m_database.isValid() || !m_database.isOpen() || !m_page) {
+        return;
+    }
+
+    const QStringList baseTables = m_database.tables(QSql::Tables);
+    QSet<QString> uniqueTables(baseTables.cbegin(), baseTables.cend());
+    const QStringList views = m_database.tables(QSql::Views);
+    for (const QString &view : views) {
+        uniqueTables.insert(view);
+    }
+
+    QStringList tableList = uniqueTables.values();
+    std::sort(tableList.begin(), tableList.end(), [](const QString &a, const QString &b) {
+        return QString::localeAwareCompare(a, b) < 0;
+    });
+
+    m_page->setTableNames(tableList);
+    m_page->showInfoMessage(tr("已加载 %1 张表").arg(tableList.size()), true);
+}
+
+void DatabaseModule::loadTable(const QString &tableName)
+{
+    if (!m_database.isValid() || !m_database.isOpen()) {
+        return;
+    }
+
+    if (!m_model) {
+        resetModel();
+    }
+
+    if (!m_model) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("无法创建数据模型"), false);
+        }
+        return;
+    }
+
+    if (tableName.isEmpty()) {
+        return;
+    }
+
+    m_currentTable = tableName;
+    m_currentPage = 1;
+    m_sortColumn = 0;
+    m_sortOrder = Qt::AscendingOrder;
+
+    m_model->setTable(tableName);
+
+    if (!calculateTotalRowCount()) {
+        updatePaginationUi();
+        return;
+    }
+
+    if (!refreshCurrentPage(false)) {
+        return;
+    }
+
+    if (m_page) {
+        m_page->setCurrentTableName(tableName);
+        m_page->showInfoMessage(tr("已加载表 %1").arg(tableName), true);
+    }
+}
+
+void DatabaseModule::setConnected(bool connected)
+{
+    if (m_connectionDock) {
+        m_connectionDock->setConnected(connected);
+    }
+    if (m_page) {
+        m_page->setConnectionActive(connected);
+    }
+}
+
+void DatabaseModule::updateStatus(const QString &message, bool success, int timeout)
+{
+    if (m_connectionDock) {
+        m_connectionDock->setStatusMessage(message, success);
+    }
+    if (m_page) {
+        m_page->showInfoMessage(message, success);
+    }
+    emit statusMessage(message, timeout);
+}
+
+
+void DatabaseModule::handlePageChangeRequested(int page)
+{
+    if (page <= 0 || !m_model || m_currentTable.isEmpty()) {
+        return;
+    }
+    int targetPage = page;
+    const int pages = totalPages();
+    if (pages > 0 && targetPage > pages) {
+        targetPage = pages;
+    }
+    if (targetPage == m_currentPage) {
+        return;
+    }
+    m_currentPage = targetPage;
+    refreshCurrentPage(false);
+}
+
+void DatabaseModule::handlePageSizeChanged(int pageSize)
+{
+    if (pageSize <= 0 || !m_model || m_currentTable.isEmpty()) {
+        return;
+    }
+    if (pageSize == m_pageSize) {
+        return;
+    }
+    m_pageSize = pageSize;
+    m_currentPage = 1;
+    refreshCurrentPage(true);
+}
+
+void DatabaseModule::handleSortChanged(int column, Qt::SortOrder order)
+{
+    if (!m_model || m_currentTable.isEmpty()) {
+        return;
+    }
+    int targetColumn = column;
+    const int columnCount = m_model->columnCount();
+    if (targetColumn < 0 || targetColumn >= columnCount) {
+        targetColumn = 0;
+    }
+    if (m_sortColumn == targetColumn && m_sortOrder == order) {
+        return;
+    }
+    m_sortColumn = targetColumn;
+    m_sortOrder = order;
+    m_currentPage = 1;
+    refreshCurrentPage(false);
+}
+
+bool DatabaseModule::refreshCurrentPage(bool recalcRowCount)
+{
+    if (!m_model || m_currentTable.isEmpty()) {
+        return false;
+    }
+
+    if (recalcRowCount && !calculateTotalRowCount()) {
+        updatePaginationUi();
+        return false;
+    }
+
+    if (m_pageSize <= 0) {
+        m_pageSize = kDefaultPageSize;
+    }
+
+    int pages = totalPages();
+    if (pages > 0) {
+        if (m_currentPage < 1) {
+            m_currentPage = 1;
+        } else if (m_currentPage > pages) {
+            m_currentPage = pages;
+        }
+    } else {
+        m_currentPage = 1;
+    }
+
+    auto *pagedModel = static_cast<PagedSqlTableModel *>(m_model);
+
+    const int columnCount = m_model->columnCount();
+    if (columnCount > 0) {
+        if (m_sortColumn < 0 || m_sortColumn >= columnCount) {
+            m_sortColumn = 0;
+        }
+        m_model->setSort(m_sortColumn, m_sortOrder);
+    }
+
+    const int offset = (m_currentPage - 1) * m_pageSize;
+    pagedModel->setPagination(offset, std::max(1, m_pageSize));
+
+    if (!pagedModel->select()) {
+        const QString errorText = pagedModel->lastError().text();
+        if (m_page) {
+            m_page->showInfoMessage(tr("加载表失败:%1").arg(errorText), false);
+            m_page->setPaginationEnabled(false);
+            m_page->updatePaginationInfo(0, 0, 0);
+        }
+        return false;
+    }
+
+    updatePaginationUi();
+    return true;
+}
+
+void DatabaseModule::updatePaginationUi()
+{
+    if (!m_page) {
+        return;
+    }
+
+    const QStringList headers = currentHeaderLabels();
+    if (!headers.isEmpty()) {
+        if (m_sortColumn < 0 || m_sortColumn >= headers.size()) {
+            m_sortColumn = 0;
+        }
+    }
+
+    m_page->setSortOptions(headers);
+    m_page->setCurrentSort(m_sortColumn, m_sortOrder);
+    m_page->setPageSizeValue(m_pageSize);
+
+    const int pages = totalPages();
+    const int displayPage = (m_totalRows > 0 && pages > 0) ? m_currentPage : 0;
+    m_page->setPaginationEnabled(!m_currentTable.isEmpty());
+    m_page->updatePaginationInfo(displayPage, pages, m_totalRows);
+}
+
+bool DatabaseModule::calculateTotalRowCount()
+{
+    if (!m_database.isValid() || !m_database.isOpen() || m_currentTable.isEmpty()) {
+        m_totalRows = 0;
+        return false;
+    }
+
+    QSqlDriver *driver = m_database.driver();
+    if (!driver) {
+        if (m_page) {
+            m_page->showInfoMessage(tr("数据库驱动不可用"), false);
+        }
+        m_totalRows = 0;
+        return false;
+    }
+
+    const QString tableIdentifier = driver->escapeIdentifier(m_currentTable, QSqlDriver::TableName);
+    QSqlQuery query(m_database);
+    const QString countSql = QStringLiteral("SELECT COUNT(*) FROM %1").arg(tableIdentifier);
+    if (!query.exec(countSql) || !query.next()) {
+        const QString errorText = query.lastError().text();
+        if (m_page) {
+            m_page->showInfoMessage(tr("统计表行数失败:%1").arg(errorText), false);
+        }
+        m_totalRows = 0;
+        return false;
+    }
+
+    m_totalRows = query.value(0).toInt();
+    return true;
+}
+
+int DatabaseModule::totalPages() const
+{
+    if (m_pageSize <= 0 || m_totalRows <= 0) {
+        return 0;
+    }
+    return (m_totalRows + m_pageSize - 1) / m_pageSize;
+}

+ 94 - 0
src/database_manager/databasemodule.h

@@ -0,0 +1,94 @@
+#ifndef DATABASEMODULE_H
+#define DATABASEMODULE_H
+
+#include <QObject>
+#include <QPointer>
+#include <QString>
+#include <QSqlDatabase>
+#include <QModelIndex>
+
+#include "database_types.h"
+
+class DatabaseConnectionDock;
+class DatabasePage;
+class Toolbar;
+class QSqlTableModel;
+class QMainWindow;
+class DatabaseSearchDialog;
+
+namespace ads {
+class CDockWidget;
+}
+
+namespace Qt {
+enum SortOrder;
+}
+
+class DatabaseModule : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit DatabaseModule(QMainWindow *owner, QObject *parent = nullptr);
+    ~DatabaseModule() override;
+
+    Toolbar *toolbar() const;
+    QWidget *pageWidget() const;
+
+signals:
+    void statusMessage(const QString &message, int timeout);
+
+public slots:
+    void disconnectFromServer();
+
+private slots:
+    void handleConnectRequested(const DatabaseConnectionParams &params);
+    void handleDisconnectRequested();
+    void handleTableActivated(const QString &tableName);
+    void handleRefreshRequested();
+    void handleSubmitRequested();
+    void handleRevertRequested();
+    void handleDeleteRowsRequested(const QModelIndexList &rows, bool batch);
+    void handleColumnSearchRequested(int column, const QString &header);
+    void handleSearchRequest(const QString &keyword,
+                             int page,
+                             int pageSize,
+                             int sortColumn,
+                             Qt::SortOrder order);
+    void handlePageChangeRequested(int page);
+    void handlePageSizeChanged(int pageSize);
+    void handleSortChanged(int column, Qt::SortOrder order);
+
+private:
+    void setupUi(QMainWindow *owner);
+    void resetModel();
+    void loadTables();
+    void loadTable(const QString &tableName);
+    void setConnected(bool connected);
+    void updateStatus(const QString &message, bool success, int timeout = 4000);
+    QStringList currentHeaderLabels() const;
+    QStringList currentFieldNames() const;
+    void showSearchError(const QString &message);
+    bool refreshCurrentPage(bool recalcRowCount);
+    void updatePaginationUi();
+    bool calculateTotalRowCount();
+    int totalPages() const;
+
+    QPointer<DatabaseConnectionDock> m_connectionDock;
+    QPointer<ads::CDockWidget> m_connectionDockWidget;
+    QPointer<DatabasePage> m_page;
+    QPointer<Toolbar> m_toolbar;
+    QPointer<DatabaseSearchDialog> m_searchDialog;
+    QString m_connectionName;
+    QSqlDatabase m_database;
+    QSqlTableModel *m_model = nullptr;
+    QString m_currentTable;
+    int m_searchColumn = -1;
+    int m_currentPage = 1;
+    int m_pageSize = 20;
+    int m_totalRows = 0;
+    int m_sortColumn = 0;
+    Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
+};
+
+#endif // DATABASEMODULE_H

+ 528 - 0
src/database_manager/databasepage.cpp

@@ -0,0 +1,528 @@
+#include "databasepage.h"
+
+#include <QAbstractItemModel>
+#include <QAbstractItemView>
+#include <QAction>
+#include <QComboBox>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QItemSelectionModel>
+#include <QLabel>
+#include <QListWidget>
+#include <QListWidgetItem>
+#include <QPushButton>
+#include <QSignalBlocker>
+#include <QSpinBox>
+#include <QSplitter>
+#include <QTableView>
+#include <QToolBar>
+#include <QTreeWidget>
+#include <QTreeWidgetItem>
+#include <QVBoxLayout>
+#include <QVariant>
+
+namespace {
+constexpr int kDefaultPageSize = 20;
+constexpr int kMinPageSize = 5;
+constexpr int kMaxPageSize = 500;
+}
+
+DatabasePage::DatabasePage(QWidget *parent)
+    : MDockManager(parent)
+{
+    m_currentSortOrder = Qt::AscendingOrder;
+
+    QWidget *tablePanel = new QWidget(this);
+    auto *tableLayout = new QVBoxLayout(tablePanel);
+    tableLayout->setContentsMargins(8, 8, 8, 8);
+    tableLayout->setSpacing(8);
+
+    auto *buttonLayout = new QHBoxLayout();
+    buttonLayout->setSpacing(12);
+
+    m_refreshButton = new QPushButton(tr("刷新"), tablePanel);
+    m_submitButton = new QPushButton(tr("提交修改"), tablePanel);
+    m_revertButton = new QPushButton(tr("撤销修改"), tablePanel);
+
+    buttonLayout->addWidget(m_refreshButton);
+    buttonLayout->addWidget(m_submitButton);
+    buttonLayout->addWidget(m_revertButton);
+    buttonLayout->addStretch();
+
+    tableLayout->addLayout(buttonLayout);
+
+    m_tableList = new QListWidget(tablePanel);
+    m_tableList->setSelectionMode(QAbstractItemView::SingleSelection);
+    tableLayout->addWidget(m_tableList, 1);
+
+    addDockWidget(tablePanel, tr("数据表"), ads::LeftDockWidgetArea);
+
+    QWidget *dataPanel = new QWidget(this);
+    auto *dataLayout = new QVBoxLayout(dataPanel);
+    dataLayout->setContentsMargins(8, 8, 8, 8);
+    dataLayout->setSpacing(6);
+
+    m_currentTableLabel = new QLabel(tr("当前未选择表"), dataPanel);
+    m_infoLabel = new QLabel(dataPanel);
+    m_infoLabel->setObjectName(QStringLiteral("DatabaseInfoLabel"));
+
+    m_tableToolbar = new QToolBar(dataPanel);
+    m_tableToolbar->setMovable(false);
+    m_tableToolbar->setFloatable(false);
+    m_tableToolbar->setToolButtonStyle(Qt::ToolButtonTextOnly);
+
+    m_deleteAction = m_tableToolbar->addAction(tr("删除"));
+    m_bulkDeleteAction = m_tableToolbar->addAction(tr("批量删除"));
+    m_deleteAction->setEnabled(false);
+    m_bulkDeleteAction->setEnabled(false);
+
+    auto *controlRow = new QHBoxLayout();
+    controlRow->setSpacing(8);
+
+    auto *sortLabel = new QLabel(tr("排序列"), dataPanel);
+    m_sortColumnCombo = new QComboBox(dataPanel);
+    m_sortColumnCombo->setEnabled(false);
+
+    auto *orderLabel = new QLabel(tr("排序方式"), dataPanel);
+    m_sortOrderCombo = new QComboBox(dataPanel);
+    m_sortOrderCombo->addItem(tr("升序"), static_cast<int>(Qt::AscendingOrder));
+    m_sortOrderCombo->addItem(tr("降序"), static_cast<int>(Qt::DescendingOrder));
+    m_sortOrderCombo->setEnabled(false);
+
+    auto *pageSizeLabel = new QLabel(tr("每页条数"), dataPanel);
+    m_pageSizeSpin = new QSpinBox(dataPanel);
+    m_pageSizeSpin->setRange(kMinPageSize, kMaxPageSize);
+    m_pageSizeSpin->setSingleStep(5);
+    m_pageSizeSpin->setValue(kDefaultPageSize);
+    m_pageSizeSpin->setEnabled(false);
+
+    controlRow->addWidget(sortLabel);
+    controlRow->addWidget(m_sortColumnCombo, 1);
+    controlRow->addWidget(orderLabel);
+    controlRow->addWidget(m_sortOrderCombo);
+    controlRow->addWidget(pageSizeLabel);
+    controlRow->addWidget(m_pageSizeSpin);
+    controlRow->addStretch();
+
+    m_prevPageButton = new QPushButton(tr("上一页"), dataPanel);
+    m_prevPageButton->setEnabled(false);
+    m_nextPageButton = new QPushButton(tr("下一页"), dataPanel);
+    m_nextPageButton->setEnabled(false);
+    controlRow->addWidget(m_prevPageButton);
+    controlRow->addWidget(m_nextPageButton);
+
+    m_pageInfoLabel = new QLabel(tr("未加载数据"), dataPanel);
+    controlRow->addWidget(m_pageInfoLabel);
+
+    m_tableView = new QTableView(dataPanel);
+    m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
+    m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
+    m_tableView->setAlternatingRowColors(true);
+    m_tableView->horizontalHeader()->setStretchLastSection(true);
+    connect(m_tableView->horizontalHeader(), &QHeaderView::sectionDoubleClicked,
+            this, &DatabasePage::handleHeaderDoubleClicked);
+    m_tableView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed | QAbstractItemView::AnyKeyPressed);
+
+    m_tableSplitter = new QSplitter(Qt::Vertical, dataPanel);
+    m_tableSplitter->addWidget(m_tableView);
+
+    m_detailView = new QTreeWidget(m_tableSplitter);
+    m_detailView->setColumnCount(2);
+    m_detailView->setHeaderLabels({tr("字段"), tr("值")});
+    m_detailView->setRootIsDecorated(false);
+    m_detailView->setUniformRowHeights(true);
+    m_detailView->setWordWrap(true);
+    m_detailView->setVisible(false);
+    m_tableSplitter->addWidget(m_detailView);
+    m_tableSplitter->setStretchFactor(0, 3);
+    m_tableSplitter->setStretchFactor(1, 2);
+    m_tableSplitter->setCollapsible(1, true);
+
+    dataLayout->addWidget(m_currentTableLabel);
+    dataLayout->addWidget(m_infoLabel);
+    dataLayout->addWidget(m_tableToolbar);
+    dataLayout->addLayout(controlRow);
+    dataLayout->addWidget(m_tableSplitter, 1);
+
+    addDockWidget(dataPanel, tr("表数据"), ads::CenterDockWidgetArea);
+
+    connect(m_tableList, &QListWidget::itemSelectionChanged,
+            this, &DatabasePage::handleTableSelectionChanged);
+    connect(m_refreshButton, &QPushButton::clicked,
+            this, &DatabasePage::refreshRequested);
+    connect(m_submitButton, &QPushButton::clicked,
+            this, &DatabasePage::submitRequested);
+    connect(m_revertButton, &QPushButton::clicked,
+            this, &DatabasePage::revertRequested);
+    connect(m_deleteAction, &QAction::triggered, this, [this]() {
+        QModelIndexList rows = selectedRowIndexes();
+        if (rows.isEmpty()) {
+            return;
+        }
+        if (rows.size() > 1) {
+            rows = {rows.first()};
+        }
+        emit deleteRowsRequested(rows, false);
+    });
+    connect(m_bulkDeleteAction, &QAction::triggered, this, [this]() {
+        const QModelIndexList rows = selectedRowIndexes();
+        if (rows.size() < 2) {
+            return;
+        }
+        emit deleteRowsRequested(rows, true);
+    });
+    connect(m_prevPageButton, &QPushButton::clicked,
+            this, &DatabasePage::handlePrevPageClicked);
+    connect(m_nextPageButton, &QPushButton::clicked,
+            this, &DatabasePage::handleNextPageClicked);
+    connect(m_pageSizeSpin, qOverload<int>(&QSpinBox::valueChanged),
+            this, &DatabasePage::handlePageSizeChanged);
+    connect(m_sortColumnCombo, qOverload<int>(&QComboBox::currentIndexChanged),
+            this, &DatabasePage::handleSortColumnChanged);
+    connect(m_sortOrderCombo, qOverload<int>(&QComboBox::currentIndexChanged),
+            this, &DatabasePage::handleSortOrderChanged);
+
+    setConnectionActive(false);
+    updatePaginationControls();
+}
+
+void DatabasePage::setConnectionActive(bool active)
+{
+    m_tableList->setEnabled(active);
+    m_refreshButton->setEnabled(active);
+    m_submitButton->setEnabled(active);
+    m_revertButton->setEnabled(active);
+    setTableActionsEnabled(active && m_tableView->model());
+    setPaginationEnabled(active && m_tableView->model());
+    if (!active) {
+        clear();
+        m_currentTableLabel->setText(tr("当前未连接到服务器"));
+    }
+}
+
+void DatabasePage::setTableNames(const QStringList &tables)
+{
+    const QString previous = m_currentTableLabel->text();
+    m_tableList->clear();
+    m_tableList->addItems(tables);
+    if (!tables.isEmpty()) {
+        m_tableList->setCurrentRow(0);
+    } else {
+        m_currentTableLabel->setText(tr("服务器中没有找到表"));
+    }
+    Q_UNUSED(previous);
+}
+
+void DatabasePage::setTableModel(QAbstractItemModel *model)
+{
+    if (QItemSelectionModel *selectionModel = m_tableView->selectionModel()) {
+        disconnect(selectionModel, &QItemSelectionModel::selectionChanged,
+                   this, &DatabasePage::handleRowSelectionChanged);
+    }
+    m_tableView->setModel(model);
+    if (model) {
+        if (QItemSelectionModel *selectionModel = m_tableView->selectionModel()) {
+            connect(selectionModel, &QItemSelectionModel::selectionChanged,
+                    this, &DatabasePage::handleRowSelectionChanged);
+        }
+        setTableActionsEnabled(true);
+    } else {
+        setTableActionsEnabled(false);
+        setSortOptions({});
+        setPaginationEnabled(false);
+        setPageSizeValue(kDefaultPageSize);
+    }
+    handleRowSelectionChanged();
+}
+
+void DatabasePage::setCurrentTableName(const QString &tableName)
+{
+    if (tableName.isEmpty()) {
+        m_currentTableLabel->setText(tr("当前未选择表"));
+    } else {
+        m_currentTableLabel->setText(tr("当前表:%1").arg(tableName));
+    }
+}
+
+void DatabasePage::showInfoMessage(const QString &message, bool success)
+{
+    const QString color = success ? QStringLiteral("#2b8a3e") : QStringLiteral("#c92a2a");
+    m_infoLabel->setStyleSheet(QStringLiteral("color:%1;").arg(color));
+    m_infoLabel->setText(message);
+}
+
+void DatabasePage::clear()
+{
+    m_tableList->clearSelection();
+    m_tableList->clear();
+    setTableModel(nullptr);
+    if (m_detailView) {
+        m_detailView->clear();
+        m_detailView->setVisible(false);
+    }
+    m_currentTableLabel->setText(tr("当前未选择表"));
+    m_infoLabel->clear();
+    setSortOptions({});
+    setPaginationEnabled(false);
+    setPageSizeValue(kDefaultPageSize);
+}
+
+void DatabasePage::setSortOptions(const QStringList &headers)
+{
+    if (!m_sortColumnCombo) {
+        return;
+    }
+    const QSignalBlocker blocker(m_sortColumnCombo);
+    m_sortColumnCombo->clear();
+    m_sortColumnCombo->addItems(headers);
+    if (headers.isEmpty()) {
+        m_sortColumnCombo->setCurrentIndex(-1);
+    } else {
+        int targetIndex = m_currentSortColumn;
+        if (targetIndex < 0 || targetIndex >= headers.size()) {
+            targetIndex = 0;
+        }
+        m_sortColumnCombo->setCurrentIndex(targetIndex);
+    }
+    updatePaginationControls();
+}
+
+void DatabasePage::setCurrentSort(int column, Qt::SortOrder order)
+{
+    m_currentSortColumn = column;
+    m_currentSortOrder = order;
+    if (m_sortColumnCombo) {
+        const QSignalBlocker blocker(m_sortColumnCombo);
+        if (column >= 0 && column < m_sortColumnCombo->count()) {
+            m_sortColumnCombo->setCurrentIndex(column);
+        } else if (m_sortColumnCombo->count() > 0) {
+            m_sortColumnCombo->setCurrentIndex(0);
+        } else {
+            m_sortColumnCombo->setCurrentIndex(-1);
+        }
+    }
+    if (m_sortOrderCombo) {
+        const QSignalBlocker blocker(m_sortOrderCombo);
+        int orderIndex = m_sortOrderCombo->findData(static_cast<int>(order));
+        if (orderIndex < 0) {
+            orderIndex = m_sortOrderCombo->findData(static_cast<int>(Qt::AscendingOrder));
+        }
+        if (orderIndex >= 0) {
+            m_sortOrderCombo->setCurrentIndex(orderIndex);
+        }
+    }
+}
+
+void DatabasePage::setPageSizeValue(int pageSize)
+{
+    if (!m_pageSizeSpin || pageSize <= 0) {
+        return;
+    }
+    const QSignalBlocker blocker(m_pageSizeSpin);
+    m_pageSizeSpin->setValue(pageSize);
+}
+
+void DatabasePage::setPaginationEnabled(bool enabled)
+{
+    if (m_paginationEnabled == enabled) {
+        updatePaginationControls();
+        return;
+    }
+    m_paginationEnabled = enabled;
+    if (!m_paginationEnabled) {
+        m_currentPage = 0;
+        m_totalPages = 0;
+        m_totalRows = 0;
+    }
+    updatePaginationControls();
+}
+
+void DatabasePage::updatePaginationInfo(int currentPage, int totalPages, int totalRows)
+{
+    m_currentPage = currentPage;
+    m_totalPages = totalPages;
+    m_totalRows = totalRows;
+    updatePaginationControls();
+}
+
+void DatabasePage::handleTableSelectionChanged()
+{
+    const QList<QListWidgetItem *> items = m_tableList->selectedItems();
+    if (items.isEmpty()) {
+        emit tableActivated(QString());
+        return;
+    }
+    emit tableActivated(items.first()->text());
+}
+
+QModelIndexList DatabasePage::selectedRowIndexes() const
+{
+    if (!m_tableView || !m_tableView->selectionModel()) {
+        return {};
+    }
+    return m_tableView->selectionModel()->selectedRows();
+}
+
+void DatabasePage::handleRowSelectionChanged()
+{
+    const QModelIndexList rows = selectedRowIndexes();
+    if (m_deleteAction) {
+        m_deleteAction->setEnabled(!rows.isEmpty());
+    }
+    if (m_bulkDeleteAction) {
+        m_bulkDeleteAction->setEnabled(rows.size() > 1);
+    }
+    updateDetailPanel();
+}
+
+void DatabasePage::handleHeaderDoubleClicked(int logicalIndex)
+{
+    if (!m_tableView) {
+        return;
+    }
+    QAbstractItemModel *model = m_tableView->model();
+    if (!model) {
+        return;
+    }
+    if (logicalIndex < 0 || logicalIndex >= model->columnCount()) {
+        return;
+    }
+
+    QString headerText = model->headerData(logicalIndex, Qt::Horizontal).toString();
+    if (headerText.isEmpty()) {
+        headerText = tr("列 %1").arg(logicalIndex + 1);
+    }
+    emit columnSearchRequested(logicalIndex, headerText);
+}
+
+void DatabasePage::handlePrevPageClicked()
+{
+    if (!m_paginationEnabled || m_currentPage <= 1) {
+        return;
+    }
+    emit pageChangeRequested(m_currentPage - 1);
+}
+
+void DatabasePage::handleNextPageClicked()
+{
+    if (!m_paginationEnabled || (m_totalPages > 0 && m_currentPage >= m_totalPages)) {
+        return;
+    }
+    emit pageChangeRequested(m_currentPage + 1);
+}
+
+void DatabasePage::handlePageSizeChanged(int value)
+{
+    if (!m_paginationEnabled || value <= 0) {
+        return;
+    }
+    emit pageSizeChangeRequested(value);
+}
+
+void DatabasePage::handleSortColumnChanged(int index)
+{
+    if (!m_paginationEnabled || index < 0) {
+        return;
+    }
+    m_currentSortColumn = index;
+    emit sortChangeRequested(index, m_currentSortOrder);
+}
+
+void DatabasePage::handleSortOrderChanged(int index)
+{
+    if (!m_paginationEnabled || !m_sortOrderCombo) {
+        return;
+    }
+    const QVariant data = m_sortOrderCombo->itemData(index);
+    if (!data.isValid()) {
+        return;
+    }
+    const auto order = static_cast<Qt::SortOrder>(data.toInt());
+    m_currentSortOrder = order;
+    emit sortChangeRequested(m_currentSortColumn, m_currentSortOrder);
+}
+
+void DatabasePage::updateDetailPanel()
+{
+    if (!m_detailView) {
+        return;
+    }
+    m_detailView->clear();
+
+    const QModelIndexList rows = selectedRowIndexes();
+    if (rows.size() != 1) {
+        m_detailView->setVisible(false);
+        return;
+    }
+
+    const QModelIndex index = rows.first();
+    QAbstractItemModel *model = m_tableView ? m_tableView->model() : nullptr;
+    if (!model) {
+        m_detailView->setVisible(false);
+        return;
+    }
+
+    const int columnCount = model->columnCount(index.parent());
+    for (int column = 0; column < columnCount; ++column) {
+        QString header = model->headerData(column, Qt::Horizontal).toString();
+        if (header.isEmpty()) {
+            header = tr("列 %1").arg(column + 1);
+        }
+        const QVariant value = model->index(index.row(), column).data(Qt::DisplayRole);
+        auto *item = new QTreeWidgetItem(m_detailView);
+        item->setText(0, header);
+        item->setText(1, value.toString());
+    }
+    m_detailView->setVisible(true);
+}
+
+void DatabasePage::setTableActionsEnabled(bool enabled)
+{
+    if (m_tableToolbar) {
+        m_tableToolbar->setEnabled(enabled);
+    }
+    if (!enabled) {
+        if (m_deleteAction) {
+            m_deleteAction->setEnabled(false);
+        }
+        if (m_bulkDeleteAction) {
+            m_bulkDeleteAction->setEnabled(false);
+        }
+        if (m_detailView) {
+            m_detailView->clear();
+            m_detailView->setVisible(false);
+        }
+        setPaginationEnabled(false);
+    }
+}
+
+void DatabasePage::updatePaginationControls()
+{
+    const bool hasPages = m_paginationEnabled && m_totalRows > 0 && m_totalPages > 0;
+    if (m_prevPageButton) {
+        m_prevPageButton->setEnabled(hasPages && m_currentPage > 1);
+    }
+    if (m_nextPageButton) {
+        m_nextPageButton->setEnabled(hasPages && m_totalPages > 0 && m_currentPage < m_totalPages);
+    }
+    if (m_sortColumnCombo) {
+        m_sortColumnCombo->setEnabled(m_paginationEnabled && m_sortColumnCombo->count() > 0);
+    }
+    if (m_sortOrderCombo) {
+        m_sortOrderCombo->setEnabled(m_paginationEnabled);
+    }
+    if (m_pageSizeSpin) {
+        m_pageSizeSpin->setEnabled(m_paginationEnabled);
+    }
+    if (m_pageInfoLabel) {
+        QString text;
+        if (!m_paginationEnabled) {
+            text = tr("未加载数据");
+        } else if (m_totalRows <= 0 || m_totalPages == 0) {
+            text = tr("暂无数据");
+        } else {
+            text = tr("第 %1 / %2 页,共 %3 条").arg(m_currentPage).arg(m_totalPages).arg(m_totalRows);
+        }
+        m_pageInfoLabel->setText(text);
+    }
+}

+ 97 - 0
src/database_manager/databasepage.h

@@ -0,0 +1,97 @@
+#ifndef DATABASEPAGE_H
+#define DATABASEPAGE_H
+
+#include "my_dock_manager/mdockmanager.h"
+
+#include <QModelIndex>
+#include <QStringList>
+
+class QListWidget;
+class QTableView;
+class QLabel;
+class QPushButton;
+class QAbstractItemModel;
+class QToolBar;
+class QAction;
+class QTreeWidget;
+class QSplitter;
+class QComboBox;
+class QSpinBox;
+
+namespace Qt {
+enum SortOrder;
+}
+
+class DatabasePage : public MDockManager
+{
+    Q_OBJECT
+
+public:
+    explicit DatabasePage(QWidget *parent = nullptr);
+
+    void setConnectionActive(bool active);
+    void setTableNames(const QStringList &tables);
+    void setTableModel(QAbstractItemModel *model);
+    void setCurrentTableName(const QString &tableName);
+    void showInfoMessage(const QString &message, bool success);
+    void clear();
+    QModelIndexList selectedRowIndexes() const;
+    void setSortOptions(const QStringList &headers);
+    void setCurrentSort(int column, Qt::SortOrder order);
+    void setPageSizeValue(int pageSize);
+    void setPaginationEnabled(bool enabled);
+    void updatePaginationInfo(int currentPage, int totalPages, int totalRows);
+
+signals:
+    void tableActivated(const QString &tableName);
+    void refreshRequested();
+    void submitRequested();
+    void revertRequested();
+    void deleteRowsRequested(const QModelIndexList &rows, bool batch);
+    void columnSearchRequested(int column, const QString &header);
+    void pageChangeRequested(int page);
+    void pageSizeChangeRequested(int pageSize);
+    void sortChangeRequested(int column, Qt::SortOrder order);
+
+private slots:
+    void handleTableSelectionChanged();
+    void handleRowSelectionChanged();
+    void handleHeaderDoubleClicked(int logicalIndex);
+    void handlePrevPageClicked();
+    void handleNextPageClicked();
+    void handlePageSizeChanged(int value);
+    void handleSortColumnChanged(int index);
+    void handleSortOrderChanged(int index);
+
+private:
+    QListWidget *m_tableList = nullptr;
+    QTableView *m_tableView = nullptr;
+    QLabel *m_currentTableLabel = nullptr;
+    QLabel *m_infoLabel = nullptr;
+    QPushButton *m_refreshButton = nullptr;
+    QPushButton *m_submitButton = nullptr;
+    QPushButton *m_revertButton = nullptr;
+    QToolBar *m_tableToolbar = nullptr;
+    QAction *m_deleteAction = nullptr;
+    QAction *m_bulkDeleteAction = nullptr;
+    QTreeWidget *m_detailView = nullptr;
+    QSplitter *m_tableSplitter = nullptr;
+    QComboBox *m_sortColumnCombo = nullptr;
+    QComboBox *m_sortOrderCombo = nullptr;
+    QSpinBox *m_pageSizeSpin = nullptr;
+    QPushButton *m_prevPageButton = nullptr;
+    QPushButton *m_nextPageButton = nullptr;
+    QLabel *m_pageInfoLabel = nullptr;
+    int m_currentPage = 1;
+    int m_totalPages = 0;
+    int m_totalRows = 0;
+    int m_currentSortColumn = 0;
+    Qt::SortOrder m_currentSortOrder;
+    bool m_paginationEnabled = false;
+
+    void updateDetailPanel();
+    void setTableActionsEnabled(bool enabled);
+    void updatePaginationControls();
+};
+
+#endif // DATABASEPAGE_H

+ 336 - 0
src/database_manager/databasesearchdialog.cpp

@@ -0,0 +1,336 @@
+#include "databasesearchdialog.h"
+
+#include <QAbstractItemView>
+#include <QComboBox>
+#include <QHeaderView>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QSignalBlocker>
+#include <QSpinBox>
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include <QTableView>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QVariant>
+#include <algorithm>
+
+namespace {
+constexpr int kDefaultPageSize = 20;
+constexpr int kMaxPageSize = 500;
+constexpr int kMinPageSize = 5;
+}
+
+DatabaseSearchDialog::DatabaseSearchDialog(QWidget *parent)
+    : QDialog(parent)
+{
+    setWindowTitle(tr("数据搜索"));
+    setModal(false);
+    resize(760, 520);
+
+    auto *mainLayout = new QVBoxLayout(this);
+    mainLayout->setContentsMargins(12, 12, 12, 12);
+    mainLayout->setSpacing(10);
+
+    m_contextLabel = new QLabel(this);
+    m_contextLabel->setObjectName(QStringLiteral("DatabaseSearchContextLabel"));
+    mainLayout->addWidget(m_contextLabel);
+
+    auto *searchRow = new QHBoxLayout();
+    searchRow->setSpacing(8);
+
+    m_keywordEdit = new QLineEdit(this);
+    m_keywordEdit->setPlaceholderText(tr("输入关键字,支持模糊匹配"));
+    searchRow->addWidget(m_keywordEdit, 1);
+
+    m_searchButton = new QPushButton(tr("搜索"), this);
+    searchRow->addWidget(m_searchButton);
+
+    searchRow->addSpacing(8);
+
+    auto *sortLabel = new QLabel(tr("排序列"), this);
+    searchRow->addWidget(sortLabel);
+
+    m_sortColumnCombo = new QComboBox(this);
+    searchRow->addWidget(m_sortColumnCombo);
+
+    auto *orderLabel = new QLabel(tr("排序方式"), this);
+    searchRow->addWidget(orderLabel);
+
+    m_sortOrderCombo = new QComboBox(this);
+    m_sortOrderCombo->addItem(tr("升序"), static_cast<int>(Qt::AscendingOrder));
+    m_sortOrderCombo->addItem(tr("降序"), static_cast<int>(Qt::DescendingOrder));
+    searchRow->addWidget(m_sortOrderCombo);
+
+    auto *pageSizeLabel = new QLabel(tr("每页条数"), this);
+    searchRow->addWidget(pageSizeLabel);
+
+    m_pageSizeSpin = new QSpinBox(this);
+    m_pageSizeSpin->setRange(kMinPageSize, kMaxPageSize);
+    m_pageSizeSpin->setValue(kDefaultPageSize);
+    m_pageSizeSpin->setSingleStep(5);
+    searchRow->addWidget(m_pageSizeSpin);
+
+    mainLayout->addLayout(searchRow);
+
+    m_statusLabel = new QLabel(this);
+    m_statusLabel->setWordWrap(true);
+    m_statusLabel->setObjectName(QStringLiteral("DatabaseSearchStatusLabel"));
+    mainLayout->addWidget(m_statusLabel);
+
+    m_resultsView = new QTableView(this);
+    m_resultsView->setSelectionBehavior(QAbstractItemView::SelectRows);
+    m_resultsView->setSelectionMode(QAbstractItemView::SingleSelection);
+    m_resultsView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+    m_resultsView->setAlternatingRowColors(true);
+    m_resultsView->horizontalHeader()->setStretchLastSection(true);
+    mainLayout->addWidget(m_resultsView, 1);
+
+    auto *paginationRow = new QHBoxLayout();
+    paginationRow->setSpacing(8);
+
+    m_prevButton = new QPushButton(tr("上一页"), this);
+    m_prevButton->setEnabled(false);
+    paginationRow->addWidget(m_prevButton);
+
+    m_nextButton = new QPushButton(tr("下一页"), this);
+    m_nextButton->setEnabled(false);
+    paginationRow->addWidget(m_nextButton);
+
+    m_pageInfoLabel = new QLabel(this);
+    paginationRow->addWidget(m_pageInfoLabel);
+    paginationRow->addStretch();
+
+    m_closeButton = new QPushButton(tr("关闭"), this);
+    paginationRow->addWidget(m_closeButton);
+
+    mainLayout->addLayout(paginationRow);
+
+    connect(m_closeButton, &QPushButton::clicked, this, &QDialog::reject);
+    connect(m_searchButton, &QPushButton::clicked, this, &DatabaseSearchDialog::handleSearchTrigger);
+    connect(m_keywordEdit, &QLineEdit::returnPressed, this, &DatabaseSearchDialog::handleSearchTrigger);
+    connect(m_prevButton, &QPushButton::clicked, this, &DatabaseSearchDialog::handlePrevPage);
+    connect(m_nextButton, &QPushButton::clicked, this, &DatabaseSearchDialog::handleNextPage);
+    connect(m_sortColumnCombo, qOverload<int>(&QComboBox::currentIndexChanged),
+            this, &DatabaseSearchDialog::handleSortColumnChanged);
+    connect(m_sortOrderCombo, qOverload<int>(&QComboBox::currentIndexChanged),
+            this, &DatabaseSearchDialog::handleSortOrderChanged);
+    connect(m_pageSizeSpin, qOverload<int>(&QSpinBox::valueChanged),
+            this, &DatabaseSearchDialog::handlePageSizeChanged);
+
+    updateControls();
+}
+
+void DatabaseSearchDialog::setTableContext(const QString &tableName, const QString &columnDisplayName)
+{
+    m_contextLabel->setText(tr("当前表:%1,模糊搜索列:%2").arg(tableName, columnDisplayName));
+}
+
+void DatabaseSearchDialog::setAvailableColumns(const QStringList &columns)
+{
+    m_availableColumns = columns;
+
+    const QSignalBlocker blocker(m_sortColumnCombo);
+    m_sortColumnCombo->clear();
+    m_sortColumnCombo->addItems(columns);
+}
+
+void DatabaseSearchDialog::setInitialSortColumn(int column)
+{
+    if (column < 0 || column >= m_sortColumnCombo->count()) {
+        column = 0;
+    }
+    const QSignalBlocker blocker(m_sortColumnCombo);
+    m_sortColumnCombo->setCurrentIndex(column);
+}
+
+void DatabaseSearchDialog::resetSearch()
+{
+    m_keywordEdit->clear();
+    m_keywordEdit->setFocus();
+    m_statusLabel->clear();
+    m_currentHeaders.clear();
+    m_totalRows = 0;
+    m_totalPages = 0;
+    m_currentPage = 1;
+    m_hasExecutedSearch = false;
+    if (m_resultsModel) {
+        m_resultsModel->clear();
+    }
+    updateControls();
+}
+
+void DatabaseSearchDialog::setBusy(bool busy)
+{
+    if (m_isBusy == busy) {
+        return;
+    }
+    m_isBusy = busy;
+    m_searchButton->setEnabled(!busy);
+    m_prevButton->setEnabled(!busy && m_hasExecutedSearch && m_currentPage > 1);
+    m_nextButton->setEnabled(!busy && m_hasExecutedSearch && m_currentPage < m_totalPages);
+    m_keywordEdit->setEnabled(!busy);
+    m_sortColumnCombo->setEnabled(!busy);
+    m_sortOrderCombo->setEnabled(!busy);
+    m_pageSizeSpin->setEnabled(!busy);
+    if (busy) {
+        setStatusMessage(tr("正在查询..."), false);
+    }
+}
+
+void DatabaseSearchDialog::setStatusMessage(const QString &message, bool error)
+{
+    QString color = error ? QStringLiteral("#c92a2a") : QStringLiteral("#2b8a3e");
+    if (message.isEmpty()) {
+        m_statusLabel->clear();
+    } else {
+        m_statusLabel->setStyleSheet(QStringLiteral("color:%1;").arg(color));
+        m_statusLabel->setText(message);
+    }
+}
+
+void DatabaseSearchDialog::setResults(const QList<QVariantList> &rows,
+                                      const QStringList &headers,
+                                      int totalRows,
+                                      int currentPage,
+                                      int pageSize)
+{
+    ensureModel();
+
+    m_resultsModel->clear();
+    m_resultsModel->setHorizontalHeaderLabels(headers);
+    for (const QVariantList &row : rows) {
+        QList<QStandardItem *> items;
+        items.reserve(row.size());
+        for (int column = 0; column < row.size(); ++column) {
+            auto *item = new QStandardItem(row.at(column).toString());
+            items.append(item);
+        }
+        m_resultsModel->appendRow(items);
+    }
+
+    m_currentHeaders = headers;
+    m_totalRows = totalRows;
+    m_pageSize = pageSize;
+    m_currentPage = currentPage;
+    m_hasExecutedSearch = true;
+    m_totalPages = (m_pageSize > 0) ? (m_totalRows + m_pageSize - 1) / m_pageSize : 0;
+    if (m_totalPages == 0 && m_totalRows > 0) {
+        m_totalPages = 1;
+    }
+
+    if (rows.isEmpty()) {
+        setStatusMessage(tr("没有匹配的记录"), false);
+    } else {
+        setStatusMessage(QStringLiteral(""), false);
+    }
+
+    updateControls();
+}
+
+void DatabaseSearchDialog::handleSearchTrigger()
+{
+    emitSearch(1);
+}
+
+void DatabaseSearchDialog::handlePrevPage()
+{
+    if (!m_hasExecutedSearch || m_currentPage <= 1) {
+        return;
+    }
+    emitSearch(m_currentPage - 1);
+}
+
+void DatabaseSearchDialog::handleNextPage()
+{
+    if (!m_hasExecutedSearch || m_currentPage >= m_totalPages) {
+        return;
+    }
+    emitSearch(m_currentPage + 1);
+}
+
+void DatabaseSearchDialog::handleSortColumnChanged(int /*index*/)
+{
+    if (!m_hasExecutedSearch) {
+        return;
+    }
+    emitSearch(1);
+}
+
+void DatabaseSearchDialog::handleSortOrderChanged()
+{
+    if (!m_hasExecutedSearch) {
+        return;
+    }
+    emitSearch(1);
+}
+
+void DatabaseSearchDialog::handlePageSizeChanged(int value)
+{
+    if (value <= 0) {
+        return;
+    }
+    if (m_pageSize == value && m_hasExecutedSearch) {
+        return;
+    }
+    m_pageSize = value;
+    if (m_hasExecutedSearch) {
+        emitSearch(1);
+    }
+}
+
+void DatabaseSearchDialog::emitSearch(int page)
+{
+    if (page < 1) {
+        page = 1;
+    }
+    const int sortColumn = m_sortColumnCombo->currentIndex();
+    const Qt::SortOrder order = static_cast<Qt::SortOrder>(m_sortOrderCombo->currentData().toInt());
+    m_currentPage = page;
+    setBusy(true);
+    emit requestSearch(m_keywordEdit->text().trimmed(),
+                       page,
+                       m_pageSizeSpin->value(),
+                       sortColumn,
+                       order);
+}
+
+void DatabaseSearchDialog::ensureModel()
+{
+    if (m_resultsModel) {
+        return;
+    }
+    m_resultsModel = new QStandardItemModel(this);
+    m_resultsView->setModel(m_resultsModel);
+    m_resultsView->horizontalHeader()->setStretchLastSection(true);
+    m_resultsView->verticalHeader()->setVisible(false);
+}
+
+void DatabaseSearchDialog::updateControls()
+{
+    const bool hasData = m_hasExecutedSearch && m_totalRows > 0;
+    const bool canPrev = hasData && m_currentPage > 1;
+    const bool canNext = hasData && m_currentPage < m_totalPages;
+
+    if (!m_isBusy) {
+        m_prevButton->setEnabled(canPrev);
+        m_nextButton->setEnabled(canNext);
+        m_searchButton->setEnabled(true);
+        m_keywordEdit->setEnabled(true);
+        m_sortColumnCombo->setEnabled(true);
+        m_sortOrderCombo->setEnabled(true);
+        m_pageSizeSpin->setEnabled(true);
+    }
+
+    if (m_totalPages <= 0) {
+        m_pageInfoLabel->setText(tr("无搜索结果"));
+    } else {
+        m_pageInfoLabel->setText(tr("第 %1 / %2 页,共 %3 条")
+                                     .arg(QString::number(std::max(1, m_currentPage)))
+                                     .arg(QString::number(std::max(1, m_totalPages)))
+                                     .arg(QString::number(m_totalRows)));
+    }
+}
+
+

+ 83 - 0
src/database_manager/databasesearchdialog.h

@@ -0,0 +1,83 @@
+#ifndef DATABASESEARCHDIALOG_H
+#define DATABASESEARCHDIALOG_H
+
+#include <QDialog>
+#include <QList>
+#include <QStringList>
+#include <QVariant>
+
+class QLabel;
+class QLineEdit;
+class QTableView;
+class QPushButton;
+class QComboBox;
+class QSpinBox;
+class QStandardItemModel;
+
+class DatabaseSearchDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit DatabaseSearchDialog(QWidget *parent = nullptr);
+
+    void setTableContext(const QString &tableName, const QString &columnDisplayName);
+    void setAvailableColumns(const QStringList &columns);
+    void setInitialSortColumn(int column);
+    void resetSearch();
+    void setBusy(bool busy);
+    void setStatusMessage(const QString &message, bool error);
+    void setResults(const QList<QVariantList> &rows,
+                    const QStringList &headers,
+                    int totalRows,
+                    int currentPage,
+                    int pageSize);
+
+signals:
+    void requestSearch(const QString &keyword,
+                       int page,
+                       int pageSize,
+                       int sortColumn,
+                       Qt::SortOrder sortOrder);
+
+private slots:
+    void handleSearchTrigger();
+    void handlePrevPage();
+    void handleNextPage();
+    void handleSortColumnChanged(int index);
+    void handleSortOrderChanged();
+    void handlePageSizeChanged(int value);
+
+private:
+    void emitSearch(int page);
+    void ensureModel();
+    void updateControls();
+
+private:
+    QLabel *m_contextLabel = nullptr;
+    QLabel *m_statusLabel = nullptr;
+    QLineEdit *m_keywordEdit = nullptr;
+    QTableView *m_resultsView = nullptr;
+    QPushButton *m_searchButton = nullptr;
+    QPushButton *m_prevButton = nullptr;
+    QPushButton *m_nextButton = nullptr;
+    QPushButton *m_closeButton = nullptr;
+    QComboBox *m_sortColumnCombo = nullptr;
+    QComboBox *m_sortOrderCombo = nullptr;
+    QSpinBox *m_pageSizeSpin = nullptr;
+    QLabel *m_pageInfoLabel = nullptr;
+    QStandardItemModel *m_resultsModel = nullptr;
+
+    QStringList m_availableColumns;
+    QStringList m_currentHeaders;
+    int m_currentPage = 1;
+    int m_totalRows = 0;
+    int m_totalPages = 0;
+    int m_pageSize = 20;
+    bool m_hasExecutedSearch = false;
+    bool m_isBusy = false;
+};
+
+#endif // DATABASESEARCHDIALOG_H
+
+

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1131 - 980
src/soft_bus_core/soft_bus_core.cpp


+ 1 - 1
src/view_serial/viewserial.cpp

@@ -74,7 +74,7 @@ void ViewSerial::initializeSystem()
     }
 }
 
-void ViewSerial::createUI()
+void ViewSerial:: createUI()
 {
     // 创建各个模块化组件
     m_deviceTreeWidget = new DeviceTreeWidget(this);