C++/Qt/SQLite实战:甜品店ERP系统设计与开发全解析
2026/7/17 2:08:48 网站建设 项目流程

1. 项目概述与核心价值

最近在整理过往的项目资料,翻到了一个几年前为朋友甜品店做的管理系统。当时朋友刚创业,店里生意不错,但管理全靠手写单子和Excel表格,一到高峰期就手忙脚乱,库存也经常对不上。他找到我,希望能用技术手段把日常运营管起来。我评估了一下需求,觉得用C++来开发一个桌面应用是个不错的选择。这个项目虽然规模不大,但麻雀虽小五脏俱全,从需求分析、数据库设计、界面开发到最后的打包部署,完整地走了一遍软件工程的生命周期。今天我就把这个“基于C++的甜品店管理系统”的设计和实现过程,作为一个详细的实例拆解出来,希望能给想用C++做点实际小项目、练练手的朋友们一些参考。

这个系统本质上是一个单店版的ERP(企业资源计划)系统,核心目标就三个:管钱、管货、管人。具体来说,它要能处理顾客点单收银、实时更新库存、管理会员信息、统计员工业绩,并生成各类经营报表。选择C++,一方面是考虑到系统对响应速度和稳定性有要求(比如收银时不能卡顿),另一方面也是我个人对C++比较熟悉,想挑战一下用它在非游戏、非底层开发的领域做点东西。整个项目采用经典的MVC(模型-视图-控制器)架构,数据库用的是轻量级的SQLite,界面则用Qt框架来开发。下面,我就从设计思路开始,一步步带你还原这个项目的构建过程。

2. 系统整体设计与架构选型

2.1 需求分析与功能模块划分

接手项目后,第一步不是直接写代码,而是和店主朋友泡在店里待了两天,观察实际的运营流程。我发现,一个甜品店的日常运营主要围绕以下几个核心场景展开:

  1. 前台收银:顾客点单,选择甜品、饮品,可能使用会员折扣或优惠券,最后结算支付(支持现金、扫码)。
  2. 库存管理:每卖出一份产品,对应的原料(如面粉、奶油、水果)库存就要减少;当原料低于安全库存时,需要提醒补货。
  3. 会员管理:记录顾客信息,管理会员积分、储值余额,以及生日优惠等。
  4. 员工管理:记录员工上下班考勤,并根据销售记录计算提成或绩效。
  5. 经营分析:店主需要查看日/月销售报表、热销产品排行、利润分析等。

基于这些观察,我将系统划分为五大核心模块:

  • 销售管理模块:负责订单的创建、修改、结算与打印小票。
  • 库存管理模块:管理所有原材料和成品的入库、出库、盘点及库存预警。
  • 会员管理模块:实现会员的注册、充值、积分兑换与消费记录查询。
  • 员工管理模块:管理员工信息、排班考勤与销售业绩关联。
  • 报表统计模块:提供销售、库存、会员等多维度数据查询与图表展示。

注意:在小型项目中,切忌一开始就设计过于复杂的功能。我的原则是“核心流程闭环优先”。第一期只实现最基本的点单收银、库存扣减和销售日报,让系统先跑起来。会员、复杂报表等都是后续迭代中加入的。这能有效控制项目风险,快速验证核心逻辑。

2.2 技术栈选型与理由

确定了做什么,接下来就要决定用什么技术来做。

  1. 核心语言:C++

    • 理由:性能是首要考虑。收银系统要求响应迅速,尤其是在促销折扣计算、库存实时更新时,C++的零成本抽象和高效率是关键。其次,项目后期可能需要连接一些硬件设备(如扫码枪、小票打印机),C++的底层控制能力更强。当然,这也是我个人技术栈的偏好。
  2. 图形界面框架:Qt

    • 理由:C++领域成熟的GUI框架不多,Qt是社区最活跃、文档最全的一个。它信号与槽的机制非常适合事件驱动的桌面应用开发,自带的控件库丰富,能快速构建出美观、专业的界面。更重要的是,Qt提供了对SQLite、图表等功能的良好支持,能极大减少我们集成第三方库的工作量。
  3. 数据库:SQLite

    • 理由:甜品店是单店运营,数据量不会特别大(预计年订单量在10万级),并发也很低(最多同时2-3个收银终端)。SQLite作为一个进程内的数据库,无需安装和配置独立的数据库服务,部署极其简单,一个.db文件就搞定了,非常适合这种轻量级桌面应用场景。
  4. 开发环境:Visual Studio + Qt VS Tools

    • 理由:在Windows平台下,Visual Studio的调试体验是最好的。配合官方的Qt Visual Studio Tools插件,可以无缝进行Qt应用的开发和调试。当然,你也可以选择Qt Creator,它更轻量,对Qt的支持更原生。
  5. 版本控制:Git

    • 理由:毋庸置疑,即使是个人小项目,使用Git进行版本管理也是最佳实践。它能让你放心地尝试重构,并且方便回溯到任何一个历史版本。

这个技术栈组合(C++/Qt/SQLite)在Windows桌面应用开发中非常经典,工具链成熟,社区资源丰富,能确保我们把主要精力集中在业务逻辑的实现上,而不是折腾开发环境。

2.3 系统架构设计:MVC模式的应用

为了保持代码的清晰和可维护性,我采用了改良版的MVC架构。

  • Model(模型):对应每个业务实体,如Product(产品)、Order(订单)、Member(会员)等类。这些类不仅是数据的容器,也封装了与自身相关的核心业务逻辑,并通过DatabaseManager单例类与SQLite数据库交互。
  • View(视图):即Qt创建的各个UI界面,如主窗口、收银界面、库存管理界面等。它们只负责数据的展示和用户输入的捕获,不包含业务逻辑。
  • Controller(控制器):这里的“控制器”角色主要由Qt的“信号与槽”机制和部分中介类来承担。例如,收银界面(View)上的“结算”按钮被点击,会发出一个信号,这个信号连接到某个业务处理类(可视为Controller)的槽函数上,该槽函数会调用相应的Order模型的方法完成结算,并更新数据库和界面。

这种分离使得界面改动不会影响业务逻辑,数据库 schema 变更也只需调整Model层的代码,大大提升了代码的可维护性。

3. 数据库设计与核心表结构

数据库是系统的基石,设计得好,后期开发事半功倍;设计得不好,到处都是坑。我遵循数据库设计范式的基本要求,并结合实际业务场景做了适当优化。

3.1 核心表结构详解

以下是几个最核心的表的设计:

1. 产品表 (products)这张表存储所有可供销售的商品,包括甜品、饮品等。

CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER NOT NULL, -- 关联分类 name TEXT NOT NULL UNIQUE, -- 产品名称 barcode TEXT, -- 条形码,方便扫码 price REAL NOT NULL, -- 售价 cost REAL, -- 成本价,用于计算毛利 unit TEXT NOT NULL, -- 单位,如'份'、'杯' recipe TEXT, -- 配方(JSON格式,关联原料和用量) is_active INTEGER DEFAULT 1, -- 是否在售 FOREIGN KEY (category_id) REFERENCES categories(id) );

实操心得recipe字段我用了TEXT类型存储JSON字符串。为什么不拆分成另一张“配方明细表”?因为对于小店来说,产品配方相对固定且变更不频繁,用JSON存储更简单,查询时直接解析即可。这是一种典型的“空间换时间”和“复杂度换灵活性”的权衡,在简单场景下是可行的。

2. 订单与订单明细表 (orders & order_items)这是核心中的核心,采用主表-明细表的结构。

-- 订单主表 CREATE TABLE orders ( id TEXT PRIMARY KEY, -- 订单号,规则如'20240517001' member_id INTEGER, -- 会员ID,非会员则为NULL total_amount REAL NOT NULL, -- 订单总金额 discount_amount REAL DEFAULT 0.0, -- 折扣金额 final_amount REAL NOT NULL, -- 实收金额 payment_method TEXT NOT NULL, -- 支付方式:cash, wechat, alipay status TEXT DEFAULT 'completed', -- 状态: completed, refunded created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (member_id) REFERENCES members(id) ); -- 订单明细表 CREATE TABLE order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id TEXT NOT NULL, product_id INTEGER NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL, -- 成交单价(可能因会员价而不同) subtotal REAL NOT NULL, -- 小计 = quantity * unit_price FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) );

注意orders.id没有使用自增整数,而是用了自定义规则的字符串。这是因为在实际经营中,店主和顾客更习惯“20240517-001”这样的订单号,一眼就能看出日期。我通过程序在插入订单时生成这个ID。

3. 库存变动记录表 (inventory_transactions)任何库存的增减都需要有记录,这是保证库存准确性的关键。

CREATE TABLE inventory_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, ingredient_id INTEGER NOT NULL, -- 原料ID change_type TEXT NOT NULL, -- 类型: purchase_in(采购入库), sale_out(销售出库), adjust(盘点调整) related_id TEXT, -- 关联单据ID,如订单号或采购单号 quantity_change REAL NOT NULL, -- 变动数量(正为增,负为减) unit_cost REAL, -- 本次变动的单位成本(主要用于采购入库) note TEXT, -- 备注 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) );

这个表的设计体现了“流水账”思想。任何时候查询某个原料的当前库存,只需要计算SUM(quantity_change)即可。虽然每次查询都要做聚合计算,但得益于数据量不大和SQLite的索引优化,性能完全可接受,而且保证了数据的历史可追溯性。

3.2 数据库连接与封装

在C++中操作SQLite,我直接使用了Qt自带的QSqlDatabaseQSqlQuery类,并封装了一个DatabaseManager单例类。

// DatabaseManager 简化示例 class DatabaseManager { public: static DatabaseManager& instance(); bool openDatabase(const QString& path); QSqlDatabase database() const; // 封装一些常用操作 bool addProduct(const Product& product); QVector<Product> getAllProducts(); // ... 其他方法 private: DatabaseManager() = default; // 私有构造函数 QSqlDatabase m_db; };

openDatabase函数中,我不仅创建连接,还通过执行一个初始化SQL脚本(包含上述所有CREATE TABLE语句)来确保数据库表结构的存在。这样,第一次运行程序时就会自动完成建表。

踩坑记录:多线程访问数据库是一个大坑。Qt的数据库连接默认不能在多线程间共享。我的解决方案是,在程序启动时(主线程)创建并打开数据库连接。任何需要访问数据库的后台线程(比如一个专门生成报表的线程),都通过信号槽将请求发送到主线程的一个专门对象中,由该对象在主线程中执行数据库操作,再将结果传回。虽然效率不是最高,但保证了线程安全,对于本应用足够了。

4. 核心功能模块的C++实现

4.1 模型层(Model)类的设计

模型类是业务逻辑的载体。以Product类为例:

class Product { public: Product() = default; Product(int id, const QString& name, double price, double cost, const QString& unit); int id() const { return m_id; } QString name() const { return m_name; } double price() const { return m_price; } // ... 其他getter/setter // 业务逻辑方法 bool isProfitable() const { return m_price > m_cost; } QJsonObject parseRecipe() const; // 解析配方JSON // 静态方法,用于与数据库交互 static bool createTable(); static QVector<Product> getAll(); static Product getById(int id); static bool add(const Product& product); static bool update(const Product& product); static bool remove(int id); private: int m_id = -1; QString m_name; double m_price = 0.0; double m_cost = 0.0; QString m_unit; QString m_recipeJson; // ... };

这里我采用了“活动记录(Active Record)”模式的变体。每个对象知道自己对应的数据库记录,并且类提供了静态方法来进行增删改查。这样做的好处是业务逻辑和数据紧密结合,代码直观。缺点是如果业务复杂,Product类可能会变得臃肿。对于这个小系统来说,清晰度优先。

4.2 销售管理模块的实现

销售收银是系统的门面,界面要清晰,逻辑要严谨。

界面布局:使用Qt Designer设计,主要分为产品分类导航区、产品展示区、购物车清单区和结算信息区。

核心流程代码

// 在收银界面控制器中 void PosController::onCheckoutButtonClicked() { // 1. 验证购物车 if (m_cart.isEmpty()) { QMessageBox::warning(this, tr("Warning"), tr("Cart is empty!")); return; } // 2. 计算总价、折扣等 double total = calculateTotal(m_cart); double discount = calculateDiscount(m_currentMember, total); double finalAmount = total - discount; // 3. 创建订单对象 Order order; order.setOrderId(generateOrderId()); // 生成唯一订单号 order.setMemberId(m_currentMember.id()); order.setTotalAmount(total); order.setDiscountAmount(discount); order.setFinalAmount(finalAmount); order.setPaymentMethod(m_selectedPaymentMethod); order.setStatus("completed"); // 4. 创建订单项 QVector<OrderItem> items; for (const auto& cartItem : m_cart) { OrderItem item; item.setProductId(cartItem.product.id()); item.setQuantity(cartItem.quantity); item.setUnitPrice(cartItem.product.price()); // 这里可能根据会员等级调整 item.setSubtotal(cartItem.quantity * item.unitPrice()); items.append(item); } // 5. 数据库事务:保存订单和订单项,并更新库存 QSqlDatabase::database().transaction(); // 开始事务 try { if (!Order::add(order, items)) { throw std::runtime_error("Failed to save order."); } if (!updateInventoryForOrder(items)) { // 遍历items,扣减库存 throw std::runtime_error("Failed to update inventory."); } if (m_currentMember.isValid()) { updateMemberPoints(m_currentMember, finalAmount); // 更新会员积分 } QSqlDatabase::database().commit(); // 提交事务 // 6. 成功后,打印小票、清空购物车、更新界面 printReceipt(order, items); clearCart(); updateUI(); } catch (const std::exception& e) { QSqlDatabase::database().rollback(); // 回滚事务 QMessageBox::critical(this, tr("Error"), tr("Checkout failed: %1").arg(e.what())); } }

关键点解析:整个结算过程被包裹在一个数据库事务中。这是至关重要的!它保证了订单数据、库存扣减、积分更新这三个操作要么全部成功,要么全部失败。想象一下,如果订单保存成功但库存扣减失败,就会导致“卖了东西但库存没少”的数据不一致灾难。事务是确保数据完整性的基石。

4.3 库存管理模块的实现

库存管理的核心是“实时”和“准确”。我在Product类中增加了recipe属性(存储JSON),并在updateInventoryForOrder函数中实现库存扣减逻辑。

bool PosController::updateInventoryForOrder(const QVector<OrderItem>& items) { for (const auto& item : items) { Product product = Product::getById(item.productId()); QJsonObject recipe = product.parseRecipe(); // 解析配方JSON for (auto it = recipe.begin(); it != recipe.end(); ++it) { QString ingredientName = it.key(); double amountPerUnit = it.value().toDouble(); // 每份产品消耗的原料量 double totalConsumed = amountPerUnit * item.quantity(); // 查询原料当前库存 Ingredient ing = Ingredient::getByName(ingredientName); if (ing.id() == -1) { qWarning() << "Ingredient not found:" << ingredientName; return false; } if (ing.currentStock() < totalConsumed) { qWarning() << "Insufficient stock for" << ingredientName; return false; // 库存不足,订单失败 } // 创建出库记录 InventoryTransaction trans; trans.setIngredientId(ing.id()); trans.setChangeType("sale_out"); trans.setRelatedId(order.orderId()); trans.setQuantityChange(-totalConsumed); // 负数表示出库 trans.setNote(QString("For order %1, product %2").arg(order.orderId()).arg(product.name())); if (!InventoryTransaction::add(trans)) { return false; } // 注意:这里没有直接更新ingredient表的库存字段,当前库存通过SUM(transactions)实时计算。 } } return true; }

这种基于配方的库存扣减,实现了销售与原料库存的联动。每当有新品上市或配方调整,只需修改对应产品的recipeJSON即可,非常灵活。

4.4 报表统计模块的实现

店主最关心的就是数据。我使用Qt的QSqlQueryModel或自定义的TableModel来为QTableView提供数据,并利用QChart来绘制简单的图表。

销售日报表查询示例

QVector<SalesData> ReportManager::getDailySales(const QDate& date) { QVector<SalesData> result; QSqlQuery query; query.prepare("SELECT strftime('%H', created_at) as hour, " "COUNT(*) as order_count, " "SUM(final_amount) as total_amount " "FROM orders " "WHERE DATE(created_at) = DATE(:date) AND status = 'completed' " "GROUP BY strftime('%H', created_at) " "ORDER BY hour"); query.bindValue(":date", date.toString("yyyy-MM-dd")); if (query.exec()) { while (query.next()) { SalesData data; data.timePeriod = query.value("hour").toString() + ":00"; data.orderCount = query.value("order_count").toInt(); data.totalSales = query.value("total_amount").toDouble(); result.append(data); } } else { qCritical() << "Failed to query daily sales:" << query.lastError(); } return result; }

这个查询按小时聚合了指定日期的订单数和销售额,可以很直观地看出一天中的销售高峰时段。将result绑定到Qt的QChart视图上,就能生成柱状图或折线图。

5. 项目构建、部署与优化心得

5.1 使用CMake管理项目

为了让项目结构清晰且易于移植,我放弃了在Visual Studio里直接配置,转而使用CMake来管理构建过程。

cmake_minimum_required(VERSION 3.16) project(DessertShopManager VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找Qt库 find_package(Qt6 COMPONENTS Core Widgets Sql Charts REQUIRED) # 添加可执行文件 add_executable(DessertShopManager src/main.cpp src/database/DatabaseManager.cpp src/models/Product.cpp src/models/Order.cpp # ... 所有源文件 ) # 链接Qt库 target_link_libraries(DessertShopManager PRIVATE Qt6::Core Qt6::Widgets Qt6::Sql Qt6::Charts ) # 包含头文件目录 target_include_directories(DessertShopManager PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ) # 自动处理Qt的元对象系统(moc) set_target_properties(DessertShopManager PROPERTIES AUTOMOC ON AUTORCC ON AUTOUIC ON )

使用CMake后,无论是在Windows上用VS编译,还是在Linux上用GCC编译,都只需要简单的几条命令,极大地提升了项目的可维护性。

5.2 打包与部署

开发完成后的打包部署是让软件真正能用的最后一步。在Windows上,我使用windeployqt工具来自动收集Qt运行时库。

  1. 在Release模式下编译项目。
  2. 将生成的DessertShopManager.exe复制到一个干净的文件夹(如package)。
  3. 打开Qt命令行,导航到该文件夹,执行:
    windeployqt DessertShopManager.exe
    这个命令会自动将程序运行所需的Qt DLL、插件等文件都拷贝到当前目录。
  4. 将程序目录(包含exe和所有依赖文件)压缩,就可以分发给用户了。用户解压后直接双击exe即可运行,无需安装Qt环境。

避坑技巧windeployqt有时会漏掉一些特定的插件,比如数据库驱动。如果用户运行程序提示找不到qsqlite驱动,你需要手动从Qt安装目录/plugins/sqldrivers下将qsqlite.dll(以及可能需要的icu相关dll)拷贝到程序目录下的sqldrivers文件夹中。

5.3 性能优化与代码质量

  1. 数据库查询优化

    • 索引是关键:在经常用于查询条件的字段上创建索引,如orders.created_at,products.name,能极大提升查询速度。
    • 避免N+1查询:在需要加载订单及其明细时,使用JOIN一次查询出来,而不是先查订单列表,再循环查询每个订单的明细。
    • 善用事务:批量插入数据(如初始化数据、导入历史订单)时,一定要放在一个事务中,这比单条插入快几个数量级。
  2. 界面响应优化

    • 耗时操作放后台:像生成月度销售报表这种可能涉及数万条数据统计的操作,一定要放在单独的线程(QThread)或使用QtConcurrent运行,避免阻塞主界面导致“卡死”。
    • 列表虚拟化:如果产品列表或订单历史记录非常多,考虑使用QListViewQTableView的视图模式,并自定义模型,只渲染可视区域内的数据。
  3. 代码结构优化

    • 依赖注入:像DatabaseManager这样的全局单例,虽然方便,但也增加了模块间的耦合。在更严谨的设计中,可以考虑通过构造函数将依赖传递给需要它的类,这样更利于单元测试。
    • 错误处理:不要忽略SQL查询的返回值。每一个query.exec()后都应该检查query.lastError(),并在界面上给用户友好的提示,而不是让程序默默崩溃。

6. 开发中遇到的典型问题与解决方案

在开发这个系统的过程中,我踩了不少坑,也总结出一些实用的排查技巧。

6.1 数据库并发访问冲突

问题描述:在早期版本,当收银前台正在结算(写入订单),同时后台在生成报表(复杂查询)时,偶尔会出现数据库被锁定的错误。

原因分析:SQLite默认的锁机制是,一个写操作会锁定整个数据库文件,期间其他读写操作都需要等待。虽然本系统并发很低,但报表查询如果涉及大量数据扫描,耗时较长,就可能与快速的写操作冲突。

解决方案

  1. 优化查询:为报表查询的WHERE条件和JOIN字段建立索引,大幅缩短查询时间,减少锁持有时间。
  2. 读写分离思想:对于报表这类只读查询,使用QSqlDatabase::cloneDatabase创建一个新的数据库连接,并在这个连接上设置QSqlQuery::setForwardOnly(true)QSqlQuery::setNumericalPrecisionPolicy,告诉SQLite这是只读操作,它可以进行一些优化。
  3. 重试机制:在捕获到“database is locked”错误时,进行短暂休眠后重试操作(例如最多重试3次,每次间隔100毫秒)。对于收银这种关键操作,简单的重试往往能解决问题。

6.2 Qt界面中文乱码

问题描述:在界面或数据库中显示的中文变成了乱码。

原因分析:这是C++/Qt跨平台开发中的经典问题。源文件的编码、编译器的执行字符集、Qt内部字符串的转换(QStringstd::string/char*的互转)都可能出问题。

解决方案(Windows+MSVC环境下):

  1. 源文件保存为UTF-8 with BOM:在Visual Studio中,通过“文件 -> 高级保存选项”确保所有.cpp.h文件编码是“Unicode (UTF-8 带签名) - 代码页 65001”。
  2. main.cpp中设置编码
    #include <QApplication> #include <QTextCodec> int main(int argc, char *argv[]) { QApplication a(argc, argv); // 以下两行对于旧版Qt(如Qt5)可能是必要的 // QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // QTextCodec::setCodecForLocale(codec); // 对于Qt6,默认就是UTF-8,通常不需要设置 // ... return a.exec(); }
  3. 数据库连接字符串指定编码
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("shop.db"); // 执行一个设置编码的pragma(如果数据库是新创建的) if (db.open()) { QSqlQuery query; query.exec("PRAGMA encoding = 'UTF-8';"); }

6.3 内存泄漏排查

问题描述:程序运行一段时间后,内存占用缓慢增长。

原因分析:C++需要手动管理内存,常见的泄漏点有:new了对象没有delete、Qt对象父子关系未正确设置导致无法自动销毁、容器中存放了指针未清理。

排查与解决

  1. 使用Qt的对象树机制:在堆上创建的QObject派生类对象(如自定义的Widget),如果指定了父对象,父对象销毁时会自动销毁所有子对象。这是避免内存泄漏的第一道防线。
  2. 遵循RAII原则:对于非QObject的资源,如文件句柄、网络连接,使用智能指针(std::unique_ptr,std::shared_ptr)进行管理。
  3. 使用工具辅助:在Windows上,Visual Studio的诊断工具(Debug -> Performance Profiler -> Memory Usage)非常强大。在Linux下,可以使用valgrind。通过工具可以精确定位到泄漏发生的位置和分配时的调用栈。

6.4 数据备份与恢复功能

这是一个后期应店主要求增加的功能,但非常重要。

实现方案

  1. 备份:非常简单,因为数据库就是一个单独的.db文件。我写了一个BackupService类,定时(比如每天营业结束后)调用QFile::copy,将shop.db复制到指定备份目录,并按日期重命名(如shop_backup_20240517.db)。
  2. 恢复
    • 在系统设置界面提供一个“恢复数据库”按钮。
    • 点击后,通过QFileDialog让用户选择之前备份的.db文件。
    • 关键步骤:在覆盖当前数据库文件前,必须先关闭所有现有的数据库连接QSqlDatabase::removeDatabase),否则文件会被占用导致覆盖失败。
    • 覆盖完成后,重新初始化DatabaseManager,并提示用户重启应用。

血泪教训:一定要在恢复功能里做验证!比如检查备份文件是否是一个有效的SQLite数据库(可以尝试打开并执行一个简单查询)。我曾经因为一个错误的文件覆盖了数据库,差点丢失一天的数据。从此以后,恢复前先验证,并且永远保留至少三个不同日期的备份成了我的铁律。

这个基于C++和Qt的甜品店管理系统项目,从一行代码到最终交付使用,前后历时约两个月。它让我深刻体会到,用C++做应用层开发,虽然入门门槛比一些现代语言高,但在性能控制和系统理解上带来的收益是巨大的。整个项目代码(不含Qt库)大约8000行,对于想要深入理解面向对象设计、数据库应用和桌面GUI开发的朋友来说,是一个非常不错的练手项目。你可以从我设计的基础功能出发,尝试添加更多模块,比如供应链管理、线上订单对接,甚至引入简单的机器学习算法来预测畅销品,这都会是极具挑战和成就感的学习过程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询