C++/Qt停车场管理系统:从架构设计到动态UI与计费策略实现

C++/Qt停车场管理系统:从架构设计到动态UI与计费策略实现

1. 项目概述与核心价值

最近在整理过往项目时,翻到了一个用C++和Qt实现的停车场管理系统,感觉挺有代表性的。这不仅仅是一个简单的增删改查应用,它融合了UI设计、数据库操作、业务逻辑和状态管理,是检验一个C++开发者能否将面向对象思想、Qt框架特性与实际业务需求结合起来的绝佳练手项目。对于正在学习C++/Qt的朋友,或者想从控制台程序转向图形界面开发的同学来说,这个项目能帮你把书本上的类、继承、多态、信号槽这些概念,实实在在地落地成一个看得见、摸得着的软件。

这个系统要解决的核心问题很明确:模拟一个现代化停车场的日常运营。车主来了怎么快速找到空位并登记?停了多久该收多少钱?车位满了怎么办?管理员如何查看营收报表和车辆进出记录?这些看似简单的需求背后,涉及到数据模型的抽象、用户交互流程的设计以及前后端(这里指UI和逻辑)的解耦。用C++和Qt来做,既能保证核心业务逻辑的高效和稳定(C++的优势),又能借助Qt强大的控件库和信号槽机制,快速构建出专业、友好的桌面图形界面。下面,我就把这个项目的设计思路、关键实现细节以及我踩过的一些坑,系统地梳理一遍。

2. 整体架构设计与技术选型

2.1 为什么选择C++与Qt组合?

在项目启动前,技术栈的选择是首要问题。为什么不用更“时髦”的Python+PyQt或者Java Swing?又或者直接用C# WinForm?这里面的考量是多方面的。

首先,性能与资源控制是核心考量。停车场系统的数据操作(如频繁查询车位状态、计算停车费用)虽然不算极度密集,但要求响应及时、稳定。C++在内存管理和执行效率上的优势,可以确保在车辆进出高峰时段,计费和状态更新逻辑能迅速完成,避免界面卡顿。这对于需要7x24小时运行的商业系统来说,是一个重要的可靠性保障。

其次,Qt框架的成熟度与跨平台性。Qt不仅仅是一个UI库,它提供了一整套完整的应用程序开发框架,包括网络、数据库、XML、JSON解析等模块。对于停车场系统,我们大概率需要使用数据库(如SQLite或MySQL)来持久化数据,Qt的QtSql模块提供了数据库驱动的统一抽象,编写数据库操作代码非常方便。更重要的是,Qt的“一次编写,到处编译”特性,意味着你可以在Windows上开发,然后几乎不用修改代码就能在Linux或macOS上运行,这对于部署到不同环境的停车场管理终端非常有利。

最后,信号与槽(Signals & Slots)机制。这是Qt的灵魂,它提供了一种类型安全、松耦合的对象间通信方式。在停车场系统中,比如当一辆车出场结算完成后,我们需要同时更新UI上的车位状态、刷新计费列表和更新统计报表。如果使用传统的回调函数或全局变量,代码会变得混乱且难以维护。而通过信号槽,我们只需要在数据模型(如ParkingLot类)中发射一个vehicleExited信号,所有关心这个事件的UI组件(如车位图、表格、标签)都可以用自己的槽函数来响应,代码清晰,模块间依赖降到最低。

2.2 核心模块划分与类设计

基于上述考量,我将系统划分为几个核心模块,并设计了对应的类。清晰的模块划分是项目成功的基础。

1. 数据模型层(Model Layer)这是系统的“大脑”,负责所有业务逻辑和数据处理,完全独立于UI。

  • Vehicle(车辆类):抽象一辆车的信息。属性包括车牌号(licensePlate)、车型(type,如小车、大车)、入场时间(entryTime)、出场时间(exitTime)、状态(status,如停放中、已离场)等。它提供计算停车时长和费用的方法。
  • ParkingSpace(停车位类):代表一个具体的车位。属性包括车位编号(spaceId)、车位类型(与车型匹配)、当前状态(空闲、占用、预订)、以及当前停放的Vehicle对象指针(如果被占用)。它负责管理自身的状态转换。
  • ParkingLot(停车场类):这是核心的管理类。它包含一个ParkingSpace的集合(可以用QVectorQList),管理所有车位。提供的主要方法有:findAvailableSpace(VehicleType)(根据车型查找空位)、parkVehicle(Vehicle*)(停车)、removeVehicle(QString licensePlate)(取车并计费)、getOccupancyRate()(获取车位占用率)等。ParkingLot类也是主要信号的发射源。
  • DatabaseManager(数据库管理类):封装所有数据库操作。使用Qt的QSqlDatabaseQSqlQuery来连接数据库、创建表、执行插入、查询、更新操作。例如,车辆入场时,将记录插入parking_records表;出场时,更新该记录的出场时间和费用。

2. 用户界面层(UI Layer)使用Qt Designer进行可视化设计,生成.ui文件,再通过uic工具编译为C++头文件。

  • 主界面(MainWindow:系统的入口。通常包含菜单栏、工具栏、状态栏以及一个中心区域,中心区域可能采用QTabWidget来组织不同功能视图。
  • 车辆出入管理界面:包含车牌号输入框(QLineEdit)、车型选择下拉框(QComboBox)、入场/出场按钮等。这里需要处理用户输入,并调用数据模型层的方法。
  • 车位状态展示界面:这是UI的亮点。可以用QGridLayout配合自定义的QPushButtonQWidget来动态生成一个车位网格图。每个“车位”控件根据其对应的ParkingSpace状态(空闲-绿色、占用-红色、预订-黄色)来改变颜色和文本。
  • 记录查询与报表界面:使用QTableView来展示数据库中的停车记录。配合QSqlTableModelQSqlQueryModel,可以非常方便地将数据库表绑定到表格视图,并支持排序和过滤。

3. 控制层(Controller Layer)在Qt的Model/View架构中,这部分通常由主窗口类(如MainWindow的子类)承担。它负责:

  • 实例化数据模型层对象(ParkingLot,DatabaseManager)。
  • 连接信号与槽。例如,将ParkingLotspaceStatusChanged信号连接到负责更新车位图UI的槽函数;将“入场”按钮的clicked()信号连接到处理入场逻辑的槽函数。
  • 协调UI与数据模型之间的交互。当用户点击“入场”时,控制层从UI获取输入,校验后调用parkingLot->parkVehicle(...),然后根据操作结果更新UI或弹出提示。

注意:在实际项目中,为了更清晰地分离关注点,可以考虑引入一个单独的Controller类或使用QML(Qt Quick)来构建UI,但对于大多数桌面应用,上述基于QWidget和信号槽的模式已经足够清晰和高效。

3. 关键功能实现细节与难点解析

3.1 动态车位状态显示与交互

这是系统中最直观、最具交互性的部分。目标是在UI上用一个网格来实时反映所有车位的状态,并且点击车位可以查看详情或进行操作。

实现方案:

  1. 数据准备ParkingLot类中维护一个QVector<ParkingSpace*>列表。每个ParkingSpace对象都有唯一的ID和状态。
  2. UI构建:在MainWindow初始化时,根据停车场布局(比如10行10列),动态创建100个QPushButton或自定义的ParkingSpaceWidget(继承自QWidget),并放入一个QGridLayout中。
    // 伪代码示例 QGridLayout *gridLayout = new QGridLayout(this); for (int row = 0; row < ROWS; ++row) { for (int col = 0; col < COLS; ++col) { int spaceId = row * COLS + col; ParkingSpaceWidget *widget = new ParkingSpaceWidget(spaceId, this); gridLayout->addWidget(widget, row, col); connect(widget, &ParkingSpaceWidget::clicked, this, &MainWindow::onSpaceClicked); m_spaceWidgets[spaceId] = widget; // 保存到映射表中以便快速访问 } }
  3. 状态同步:这是核心。我们通过信号槽实现状态同步。
    • ParkingLot类中,定义一个信号:void spaceStatusChanged(int spaceId, ParkingSpace::Status newStatus);
    • parkVehicleremoveVehicle成功改变某个车位状态后,发射此信号。
    • MainWindow中,连接这个信号到一个更新UI的槽函数:
      connect(m_parkingLot, &ParkingLot::spaceStatusChanged, this, &MainWindow::updateSpaceWidgetStatus);
    • updateSpaceWidgetStatus槽函数根据spaceIdm_spaceWidgets映射表中找到对应的控件,然后根据newStatus改变其样式(颜色、文字)。
      void MainWindow::updateSpaceWidgetStatus(int spaceId, ParkingSpace::Status status) { ParkingSpaceWidget *widget = m_spaceWidgets.value(spaceId); if (!widget) return; switch(status) { case ParkingSpace::Free: widget->setStyleSheet("background-color: lightgreen;"); break; case ParkingSpace::Occupied: widget->setStyleSheet("background-color: salmon;"); break; // ... 其他状态 } widget->setText(QString("车位%1\n%2").arg(spaceId).arg(getStatusText(status))); }
  4. 交互处理:车位控件被点击时,发射自定义信号,MainWindowonSpaceClicked槽函数可以弹出对话框,显示该车位的详细信息(如停放车辆信息),或提供“手动释放”、“预订”等管理操作。

难点与技巧:

  • 性能:如果车位数量极大(如上千个),动态创建大量控件会消耗内存和初始化时间。可以考虑使用QTableView配合自定义委托(Delegate)来绘制车位,利用视图的滚动和复用机制提升性能。
  • 样式:直接使用setStyleSheet虽然方便,但样式复杂时难以维护。对于复杂的自定义控件,建议重写paintEvent方法进行绘制,这样更灵活高效。
  • 线程安全:如果数据库操作或某些计算放在子线程,那么更新UI(如改变控件颜色)必须在主线程进行。可以使用QMetaObject::invokeMethod或信号槽的Qt::QueuedConnection连接方式,确保跨线程安全地更新UI。

3.2 计费策略的灵活设计与实现

计费逻辑是停车场系统的核心业务规则。不同的停车场可能有不同的计费策略:按小时计费、分段计费、首小时免费、夜间费率不同、会员折扣等。我们需要设计一个灵活、可扩展的计费系统。

实现方案:采用策略模式(Strategy Pattern)

  1. 定义计费策略接口:创建一个抽象基类PricingStrategy
    class PricingStrategy { public: virtual ~PricingStrategy() = default; // 计算费用,参数:入场时间、出场时间、车辆类型 virtual double calculateFee(const QDateTime &entry, const QDateTime &exit, VehicleType type) const = 0; virtual QString getDescription() const = 0; };
  2. 实现具体策略:派生出不同的策略类。
    class HourlyPricing : public PricingStrategy { public: HourlyPricing(double ratePerHour) : m_ratePerHour(ratePerHour) {} double calculateFee(const QDateTime &entry, const QDateTime &exit, VehicleType type) const override { qint64 seconds = entry.secsTo(exit); double hours = std::ceil(seconds / 3600.0); // 不足一小时按一小时算 return hours * m_ratePerHour * getVehicleFactor(type); // 车型系数 } QString getDescription() const override { return QString("按时计费:%1元/小时").arg(m_ratePerHour); } private: double m_ratePerHour; }; class StepPricing : public PricingStrategy { public: StepPricing(double firstHourRate, double subsequentHourRate) {...} double calculateFee(...) const override { // 实现分段计费逻辑,例如首小时X元,之后每小时Y元 } };
  3. 在停车场类中集成ParkingLot类持有一个PricingStrategy的指针(或智能指针std::unique_ptr)。
    class ParkingLot { // ... double calculateFee(const QString &licensePlate) { Vehicle* vehicle = findVehicle(licensePlate); if (!vehicle || !m_pricingStrategy) return 0.0; return m_pricingStrategy->calculateFee(vehicle->entryTime(), QDateTime::currentDateTime(), vehicle->type()); } void setPricingStrategy(std::unique_ptr<PricingStrategy> strategy) { m_pricingStrategy = std::move(strategy); } private: std::unique_ptr<PricingStrategy> m_pricingStrategy; };
  4. 动态切换策略:可以在系统设置界面中,让管理员选择不同的计费策略,然后调用parkingLot->setPricingStrategy(...)进行切换,无需修改其他业务代码。

这样做的好处:计费逻辑被封装在独立的类中,新增一种计费方式(如“按天封顶”)只需要添加一个新的策略类,符合开闭原则。代码易于测试和维护。

3.3 数据库设计与持久化

数据需要持久化保存,以便重启后恢复状态和生成历史报表。SQLite是一个轻量级、无需单独服务器进程的嵌入式数据库,非常适合本项目。

数据库表设计:

-- 车辆信息表(静态信息,可扩展) CREATE TABLE IF NOT EXISTS vehicles ( id INTEGER PRIMARY KEY AUTOINCREMENT, license_plate TEXT UNIQUE NOT NULL, type INTEGER NOT NULL -- 0:小车,1:大车 ); -- 停车记录表(核心业务表) CREATE TABLE IF NOT EXISTS parking_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, vehicle_id INTEGER NOT NULL, space_id INTEGER NOT NULL, entry_time DATETIME NOT NULL, exit_time DATETIME, fee REAL DEFAULT 0.0, status INTEGER DEFAULT 1, -- 1:停放中,2:已结算离场 FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ); -- 可以添加其他表,如用户表、收费员表、财务报表等。

使用QtSql进行操作:

  1. 连接数据库:在DatabaseManager的构造函数或初始化函数中。
    bool DatabaseManager::connect() { m_db = QSqlDatabase::addDatabase("QSQLITE"); m_db.setDatabaseName("parking_system.db"); if (!m_db.open()) { qCritical() << "无法打开数据库:" << m_db.lastError().text(); return false; } // 创建表 QSqlQuery query; return query.exec(CREATE_TABLE_SQL); }
  2. 插入入场记录
    bool DatabaseManager::insertParkingRecord(const Vehicle &vehicle, int spaceId) { QSqlQuery query; query.prepare("INSERT INTO parking_records (vehicle_id, space_id, entry_time, status) " "VALUES ((SELECT id FROM vehicles WHERE license_plate = ?), ?, ?, ?)"); query.addBindValue(vehicle.licensePlate()); query.addBindValue(spaceId); query.addBindValue(vehicle.entryTime()); query.addBindValue(ParkingStatus::Parking); return query.exec(); }
  3. 查询与报表:使用QSqlQueryModelQSqlTableModel直接绑定到QTableView,可以极简地实现数据展示。
    // 在报表界面初始化时 QSqlTableModel *model = new QSqlTableModel(this, m_db); model->setTable("parking_records"); model->setFilter("status = 2"); // 只显示已离场记录 model->select(); ui->tableView->setModel(model); ui->tableView->horizontalHeader()->setStretchLastSection(true); // 自适应列宽

实操心得:务必注意数据库操作的错误处理。每次执行exec()后,检查query.lastError().isValid()。对于多线程环境,每个线程需要使用独立的数据库连接(QSqlDatabase::addDatabase时指定不同的连接名)。另外,对于频繁的查询(如根据车牌查车辆),记得在相关字段上创建索引以提升性能。

4. 项目构建、部署与常见问题排查

4.1 开发环境搭建与项目配置

一个清晰的开发环境是高效编码的基础。我推荐使用Qt Creator作为IDE,它专为Qt开发设计,对.pro文件管理、Qt库的包含、信号槽的自动连接等功能支持得最好。

1. 项目文件(.pro)配置要点:

QT += core gui sql # 添加sql模块以支持数据库 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets # Qt5需要widgets模块 CONFIG += c++17 # 使用现代C++标准 SOURCES += \ main.cpp \ mainwindow.cpp \ vehicle.cpp \ parkingspace.cpp \ parkinglot.cpp \ databasemanager.cpp \ pricingstrategy.cpp \ hourlypricing.cpp HEADERS += \ mainwindow.h \ vehicle.h \ parkingspace.h \ parkinglot.h \ databasemanager.h \ pricingstrategy.h \ hourlypricing.h FORMS += \ mainwindow.ui # 如果使用了资源文件(如图标、样式表) RESOURCES += resources.qrc # 发布时,指定程序图标(Windows) RC_ICONS = app.ico

关键点:一定要在.pro文件中正确添加所需的Qt模块,比如sql。忘记添加是导致编译时出现“undefined reference to ...”链接错误的常见原因。

2. 使用VSCode进行开发(可选):如果你更习惯VSCode,需要配置CMakeCMake Tools插件,并编写CMakeLists.txt文件来管理项目。同时,安装Qt VS ToolsC/C++插件来提供代码补全和调试支持。不过,对于Qt项目,特别是涉及UI设计(.ui文件)和资源文件(.qrc)时,Qt Creator的集成度更高,能减少很多配置上的麻烦。

4.2 程序发布与打包

开发完成后,如何将程序分发给没有安装Qt环境的用户?这就需要“打包发布”。

Windows平台发布步骤:

  1. 编译为Release版本:在Qt Creator中,将构建套件(Kit)切换到Release模式并编译。
  2. 找到可执行文件:在项目构建目录的release文件夹下找到.exe文件。
  3. 使用windeployqt工具:这是Qt自带的部署工具。在开始菜单找到“Qt 5.15.2 (MSVC 2019 64-bit)”之类的命令行,进入release文件夹,执行:
    windeployqt your_app_name.exe
    这个命令会自动扫描.exe文件依赖的Qt库(DLL文件)、插件(如图像格式插件qjpeg.dll、数据库驱动qsqlite.dll)等,并复制到当前目录。
  4. 手动补充必要文件
    • 确保数据库文件(如parking_system.db)在可执行文件同级目录或指定路径下。
    • 如果程序使用了Qt5Svg.dll等不常用模块,windeployqt可能不会自动包含,需要手动从Qt安装目录的bin文件夹复制。
    • 对于数据库驱动,windeployqt会复制qsqlite.dll,但你需要确保它在./sqldrivers子目录下,或者通过QCoreApplication::addLibraryPath在代码中指定路径。
  5. 测试:将整个文件夹(包含.exe和所有DLL)复制到一台没有安装Qt的电脑上,运行测试。
  6. 打包成安装包:可以使用Inno SetupNSIS等工具,将整个文件夹制作成安装程序。

Linux/macOS发布:原理类似,使用对应的部署工具(如linuxdeployqt)或手动将可执行文件和依赖的库打包。在Linux上,也可以考虑将程序打包成AppImageSnap等格式。

4.3 常见编译与运行问题排查

在开发过程中,你几乎一定会遇到下面这些问题。

问题1:编译时报错“:-1: error: unknown module(s) in qt: core5compat”

  • 原因:这个错误通常出现在.pro文件中包含了不存在的Qt模块。core5compat是Qt 6中为了兼容Qt 5某些API而引入的模块,在Qt 5中不存在。
  • 排查
    1. 检查你的.pro文件中的QT +=行。如果你是从网上拷贝的Qt 6项目配置,或者误写了core5compat,就会报错。
    2. 检查你的Qt版本。在Qt Creator中,点击“帮助”->“关于Qt Creator”,或在项目构建设置中查看。确保你使用的模块与你的Qt版本匹配。
  • 解决:从.pro文件的QT +=行中移除core5compat。对于Qt 5项目,通常不需要这个模块。常见的模块是core,gui,widgets,sql,network,charts等。

问题2:程序运行时崩溃,提示“无法加载数据库驱动”或“QSqlDatabase: QSQLITE driver not loaded”

  • 原因:程序运行时找不到SQLite数据库驱动(qsqlite.dlllibqsqlite.so)。
  • 排查
    1. 发布时,windeployqt是否成功复制了./sqldrivers目录及其中的qsqlite.dll
    2. 驱动文件的路径是否正确?Qt应用程序会在特定路径下查找插件。
  • 解决
    • 发布时:确保sqldrivers目录与可执行文件在同一个目录,或者在其子目录下。
    • 开发调试时:可以在main函数开头,添加以下代码来手动添加插件路径(适用于Windows):
      #include <QApplication> #include <QDir> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); // 添加应用程序自身目录下的plugins文件夹作为插件路径 a.addLibraryPath(a.applicationDirPath() + "/plugins"); // 或者,如果你知道Qt安装目录,也可以直接添加 // a.addLibraryPath("C:/Qt/5.15.2/msvc2019_64/plugins"); ... }

问题3:界面布局在放大/缩小窗口时变形

  • 原因:没有正确使用Qt的布局管理器(Layout),或者固定了某些控件的大小。
  • 解决
    • 坚持使用布局管理器(QHBoxLayout,QVBoxLayout,QGridLayout)来排列控件,而不是使用绝对坐标(setGeometry)。
    • 为控件设置合适的大小策略(sizePolicy),例如将QTextEditQTableView的水平和垂直策略都设置为Expanding,这样它们会随着布局拉伸。
    • 在顶层窗口(如MainWindow)的中心部件上设置一个总的布局,确保所有子控件都纳入布局管理。

问题4:信号槽连接失败,槽函数不执行

  • 原因:这是Qt新手最常见的问题之一。可能的原因有:对象生命周期问题、连接参数不匹配、线程问题等。
  • 排查与解决
    1. 检查对象是否存在:确保发送信号的对象和接收槽函数的对象在连接时都已被正确创建且未被销毁。如果对象是局部变量,可能在其作用域结束后就被销毁了。
    2. 使用新的连接语法(推荐):Qt5引入了基于函数指针的新语法,它能在编译时检查信号和槽的签名是否匹配,更安全。
      // 旧语法(字符串,运行时检查) connect(btn, SIGNAL(clicked()), this, SLOT(onBtnClicked())); // 新语法(函数指针,编译时检查) connect(btn, &QPushButton::clicked, this, &MainWindow::onBtnClicked);
      如果使用新语法时编译报错,很可能是签名不匹配或槽函数不是slots(在Qt5中,任何QObject的成员函数或lambda都可以作为槽)。
    3. 使用QDebug输出:在槽函数开头加一行qDebug() << "Slot called!";,看是否有输出,可以判断槽函数是否被触发。
    4. 检查线程:如果信号和槽对象处于不同线程,默认的连接类型(Qt::AutoConnection)会变为队列连接(Qt::QueuedConnection),这通常是正确的。但如果槽函数需要立即修改发送者线程的数据,就可能出问题。确保对共享数据的访问是线程安全的。

5. 功能扩展与优化思路

一个基础的停车场系统实现后,可以从以下几个方向进行扩展和深化,这能让你的项目更具竞争力和学习价值。

1. 引入数据可视化

  • 热力图展示车位使用频率:这就是热词中提到的“qt做的热力图”。你可以记录每个车位每天被占用的时长,然后使用QPainter在车位网格图上,根据使用频率绘制颜色深浅不同的热力图(高频-红色,低频-蓝色)。这能帮助管理员优化车位布局或定价策略。实现上,可以创建一个继承自QWidget的热力图控件,在其paintEvent中根据数据矩阵进行颜色填充。
  • 营收趋势图表:使用Qt Charts模块(QT += charts),将每日、每月的停车费收入绘制成折线图或柱状图,让数据更直观。

2. 增加网络通信模块

  • 实现一个简单的客户端/服务器架构:将核心的ParkingLot数据模型放在服务器端,多个管理终端(客户端)通过TCP/UDP或HTTP与服务器通信。这可以模拟大型连锁停车场的集中管理。Qt的QTcpSocketQTcpServerQNetworkAccessManager可以派上用场。
  • 数据同步:客户端只负责UI展示和操作指令发送,所有数据变更由服务器处理并广播给所有在线客户端,保证数据一致性。

3. 集成硬件或外部系统

  • 模拟车牌识别:这是停车场系统的前沿。虽然真实的车牌识别需要摄像头和AI模型,但你可以在软件层面模拟。设计一个“虚拟摄像头”线程,定期生成随机的车牌号字符串,并作为一个信号发射出来,UI层接收后自动填入车牌输入框,模拟自动识别入场。
  • 生成停车凭证:使用Qt的打印支持(QPrinter)或报告生成库,在车辆入场时打印带有二维码的停车小票,出场时扫码结算。

4. 应用设计模式进行重构

  • 观察者模式:你已经在用信号槽了,这本身就是观察者模式的实现。可以思考还有哪些状态变化需要被多个对象观察。
  • 工厂模式:如果需要支持多种类型的车辆(如普通车、电动车、VIP车),且每种车的计费规则不同,可以使用工厂模式来创建不同的Vehicle子类。
  • 单例模式:确保DatabaseManager在整个应用程序中只有一个实例,方便统一管理数据库连接。

5. 提升代码质量与可维护性

  • 单元测试:使用Qt Test框架为PricingStrategyParkingLot等核心业务类编写单元测试,确保计费逻辑、车位分配逻辑的正确性。
  • 日志系统:集成一个简单的日志库(如spdlog),将系统的关键操作、错误信息记录到文件,便于后期排查问题。
  • 国际化:使用Qt的国际化工具(lupdate,lrelease),将UI上的字符串用tr()包裹,方便后续支持多语言。

这个项目就像一棵树的主干,以上这些扩展点就是它的枝丫。每实现一个,你对C++、Qt乃至软件工程的理解就会更深一层。从实现基本功能,到优化用户体验,再到设计可扩展的架构,这个过程本身就是一个完整的、小型的软件开发周期体验。