基于Qt与C++的校园商铺进销存系统开发实战
2026/9/3 4:56:07 网站建设 项目流程

简介:这是一套面向计算机专业本科生的毕业设计级Qt+C++校园商铺系统实战源码,适用于课程实践、毕设开发与GUI应用能力提升。资源包含140个文件,涵盖11个核心CPP功能模块(如登录、购物车、管理员管理、充值、消息处理等)、10个UI界面文件、23个备份文件及41张PNG界面截图,辅以H头文件、QRC资源文件与PRO工程配置,整体压缩包仅15.66MB,结构清晰、模块解耦度高,便于理解MVC架构在Qt中的落地实现。已有30人学习下载,读者可直接编译运行,深入掌握Qt信号槽机制、数据库交互、多窗口协同及C++面向对象设计实践;源码注释完整,各模块职责明确,为二次开发提供坚实基础,特别适合从零构建跨平台桌面端商铺管理系统的进阶学习者。

1. 项目概述:一个校园商铺系统的诞生

最近在整理硬盘,翻出来一个几年前用Qt和C++写的校园商铺系统,从界面到数据库,从前端到后端,都是自己一手撸出来的。当时做这个项目,一方面是给学校里的一个小超市做信息化升级,另一方面也是想系统地练练手,把Qt的GUI、网络通信、数据库操作这些知识点串起来。现在回头看,这个项目虽然不大,但“麻雀虽小,五脏俱全”,涵盖了桌面应用开发的很多核心环节,对于想深入学习Qt和C++实战的朋友来说,是个不错的练手材料。

这个系统本质上是一个为校园内小型商铺(比如便利店、打印店、水果摊)定制的单机版进销存管理软件。它要解决的核心问题很简单:老板需要一个直观的工具来管理商品(录入、查询、修改、删除)、记录每一笔销售、统计每日/每月的营收,最好还能在库存快见底时给个提醒。市面上成熟的ERP系统对于这种小场景来说太臃肿,而Excel记账又太容易出错且不方便。所以,一个轻量、快速、界面友好的专用软件就成了刚需。

整个项目采用经典的MVC(模型-视图-控制器)架构,用Qt Creator作为IDE,数据库选择了轻量级的SQLite,这样最终打包成一个独立的exe,拿到任何一台Windows电脑上都能直接运行,不需要额外安装数据库服务,对商铺老板来说非常友好。接下来,我就把这个项目的核心设计思路、关键代码实现,以及开发过程中踩过的那些“坑”,毫无保留地分享出来。

2. 系统整体设计与技术选型考量

2.1 为什么选择Qt + C++ + SQLite这个技术栈?

做桌面应用,技术栈的选择很多。Java有Swing/JavaFX,C#有WinForms/WPF,Python有PyQt/PySide,那为什么偏偏选Qt+C++呢?这背后有几个很实际的考量。

首先,性能与资源占用是首要因素。校园商铺的电脑配置通常不会太高,可能还是用了好几年的老机器。C++编译出的原生代码,在运行效率上有着天然优势,启动快、响应迅速,长时间运行也不会因为垃圾回收导致卡顿。Qt框架本身虽然庞大,但通过静态编译或者只链接必要的模块,可以生成一个体积适中、依赖极少的可执行文件。相比之下,带JVM的Java或带解释器的Python应用,在低配环境下的启动速度和内存占用就不那么理想了。

其次,跨平台潜力与原生体验。虽然当前需求是Windows单机版,但Qt的跨平台特性为未来可能的扩展(比如在校园网内做个简单的Linux服务器版)留了后路。更重要的是,Qt的控件能很好地适配不同操作系统的原生风格,做出来的程序看起来不“山寨”,用户体验更专业。对于商铺老板来说,一个看起来和系统其他软件风格一致的程序,学习成本会更低。

最后,SQLite数据库的零配置优势。这是本项目选型中的点睛之笔。SQLite是一个进程内的库,数据库就是一个单独的.db文件。这意味着:

  1. 无需安装与配置:不需要像MySQL或PostgreSQL那样单独安装、配置服务、设置用户权限。对于最终用户来说完全透明。
  2. 部署极其简单:只需要把.exe和.db文件放在一起,或者程序第一次运行时自动在同目录创建.db文件即可。
  3. 足够胜任:校园商铺的数据量(商品数千种,交易记录数万条)对于SQLite来说游刃有余,其ACID事务特性也能保证销售数据不会错乱。

这个技术栈组合,完美契合了“轻量、独立、高效”的项目目标。

2.2 核心功能模块拆解与数据流设计

系统主要围绕“商品”和“交易”两个核心实体展开。我将其拆解为以下几个功能模块,并设计了相应的数据表结构。

1. 商品管理模块这是系统的基础。核心操作是CRUD(增删改查)。需要存储的商品信息包括:商品编号(唯一主键)、商品名称、分类(如饮料、零食、文具)、规格、单位、进货价、零售价、当前库存量以及最低库存预警线。

注意:商品编号不建议使用数据库自增ID,而应采用有业务意义的编码,如“SP-20240001”。这样在盘点、打印小票时更直观。我在设计时使用了“分类缩写+序列号”的方式,由程序在添加商品时自动生成。

2. 销售收银模块这是系统的核心业务流程。界面需要模拟一个简单的收银台:通过扫描枪输入或手动选择商品,实时计算总价,完成收款后打印小票(或模拟打印),并更新库存。 这里的关键是事务处理。一次销售可能包含多个商品,必须在同一个数据库事务中完成:1)在销售主表插入一条记录(包含销售单号、时间、总金额);2)在销售明细表插入多条商品记录;3)更新商品表中对应商品的库存数量。这三步必须全部成功或全部失败,否则会导致库存和账目对不上。Qt的SQL模块提供了事务支持,一定要用起来。

3. 库存查询与预警模块提供一个表格视图,实时展示所有商品的库存情况。当某个商品的库存量低于其预设的“最低库存预警线”时,在表格中用醒目的颜色(如红色背景)高亮显示该行,提醒老板该进货了。 这个功能实现起来不难,但体验很好。关键在于QTableViewQSqlTableModel的配合,以及自定义delegate来实现条件化的单元格渲染。

4. 统计报表模块提供按日、按周、按月查看销售总额、毛利、最畅销商品等统计信息。这里涉及到对销售数据的聚合查询。SQLite的日期时间函数和聚合函数(SUM,GROUP BY)完全可以满足需求。 例如,查询今日销售额的SQL大致是:SELECT SUM(总金额) FROM 销售主表 WHERE date(销售时间) = date('now')。在Qt中,可以将这些查询结果用QChart模块绘制成柱状图或折线图,让数据可视化,更直观。

数据表结构设计示例:

-- 商品表 CREATE TABLE product ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增ID,仅内部使用 code VARCHAR(20) UNIQUE NOT NULL, -- 商品编码,业务主键 name VARCHAR(100) NOT NULL, category VARCHAR(50), unit_price REAL NOT NULL, -- 零售价 cost_price REAL, -- 进货价 stock INTEGER DEFAULT 0, alert_stock INTEGER DEFAULT 10 -- 库存预警线 ); -- 销售主表 CREATE TABLE sale_order ( order_id VARCHAR(30) PRIMARY KEY, -- 销售单号,如'SALE-20240527-001' sale_time DATETIME DEFAULT CURRENT_TIMESTAMP, total_amount REAL NOT NULL, cash_received REAL NOT NULL, change REAL NOT NULL ); -- 销售明细表 CREATE TABLE sale_detail ( detail_id INTEGER PRIMARY KEY AUTOINCREMENT, order_id VARCHAR(30), product_code VARCHAR(20), quantity INTEGER NOT NULL, unit_price REAL NOT NULL, subtotal REAL NOT NULL, FOREIGN KEY (order_id) REFERENCES sale_order(order_id), FOREIGN KEY (product_code) REFERENCES product(code) );

实操心得:在销售明细表中,除了关联商品编码,我还冗余存储了成交时的unit_price。这是因为商品的零售价可能会变动,我们必须记录每一笔交易发生时实际的价格,保证历史数据的准确性,不能只关联商品表去查当前价格。

3. 开发环境搭建与项目框架构建

3.1 Qt与C++环境配置避坑指南

工欲善其事,必先利其器。虽然Qt安装看起来简单,但新手很容易在环境配置上浪费大量时间。这里分享我最顺滑的一套配置方案。

第一步:安装Qt不建议在线安装,网络不稳定且慢。去Qt官网的存档站点,直接下载离线安装包。对于这个项目,选择Qt 5.15.2MSVC 2019 64-bit版本是一个稳定且兼容性好的选择。安装时,在组件选择页面,务必勾选:

  • Qt 5.15.2->MSVC 2019 64-bit
  • Qt 5.15.2->Source Components(可选,但方便查看源码)
  • Developer and Designer Tools->Qt Creator 10.0.1 CDB Debugger Support
  • Developer and Designer Tools->MinGW 8.1.0 64-bit(作为备用编译套件)

安装路径不要有中文和空格,比如D:\Qt\5.15.2\

第二步:配置IDE - 强推Qt CreatorQt Creator是Qt的“亲儿子”,对Qt项目的支持是最好的,代码补全、UI设计器集成、调试都非常方便。安装完Qt后,Qt Creator通常已经一并装好了。

打开Qt Creator,首先进入工具->选项->Kits

  1. 检查编译器:你应该能看到自动检测到的Desktop Qt 5.15.2 MSVC2019 64bit套件。确保其编译器指向正确的MSVC路径(通常是Visual Studio安装目录下的VC\Tools\MSVC\...\bin\Hostx64\x64里的cl.exe)。
  2. 检查Qt版本:确保套件使用的Qt版本是你刚安装的5.15.2 MSVC2019 64bit
  3. 调试器:如果调试器没有自动配置,需要手动指定。对于MSVC,调试器是CDB,路径在Qt安装目录\Tools\QtCreator\bin\cdb\cdb.exe。对于MinGW,调试器是GDB

踩坑实录:最常见的问题是编译时报错“找不到Windows SDK”或“MSVC编译器不可用”。这通常是因为你的电脑没有安装Visual Studio或者相应的构建工具。解决方案是去微软官网下载并安装“Build Tools for Visual Studio 2022”,安装时只需勾选“C++ 生成工具”即可,不需要安装完整的VS IDE。安装后,重启Qt Creator,它一般就能自动找到编译器了。

第三步:创建项目打开Qt Creator,新建项目,选择Qt Widgets Application。在类信息页面,基类选择QMainWindow,这样会生成一个带菜单栏、工具栏和状态栏的主窗口框架,非常适合我们这个管理系统。创建完成后,你会得到一个包含.pro项目文件、main.cpp、主窗口头文件和源文件以及一个.ui表单文件的项目结构。

3.2 项目工程文件(.pro)的关键配置

Qt的项目配置都在.pro文件里,它决定了哪些模块被引入、如何编译。对于我们的商铺系统,.pro文件需要精心配置。

QT += core gui sql charts printsupport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 # 如果你想发布时静态编译,取消下面两行的注释(但需要静态编译的Qt库) # CONFIG += static # QTPLUGIN += qsqlite SOURCES += \ main.cpp \ mainwindow.cpp \ productdialog.cpp \ saledialog.cpp \ databasemanager.cpp HEADERS += \ mainwindow.h \ productdialog.h \ saledialog.h \ databasemanager.h FORMS += \ mainwindow.ui \ productdialog.ui \ saledialog.ui # 发布版本关闭调试信息,开启优化 CONFIG(release, debug|release): { DEFINES += QT_NO_DEBUG_OUTPUT QMAKE_CXXFLAGS_RELEASE -= -O2 QMAKE_CXXFLAGS_RELEASE *= -O3 } # 指定生成的可执行文件输出目录 DESTDIR = $$PWD/bin MOC_DIR = $$PWD/build/moc RCC_DIR = $$PWD/build/rcc UI_DIR = $$PWD/build/ui OBJECTS_DIR = $$PWD/build/obj

关键配置解读:

  1. QT += core gui sql charts printsupport:这是本项目的核心。
    • core,gui,widgets是基础。
    • sql:数据库模块,必须。
    • charts:用于绘制统计报表的图表模块(Qt Charts)。
    • printsupport:支持打印销售小票。
  2. CONFIG += c++17:使用现代C++标准,方便使用foreachauto等特性,提升开发效率。
  3. 输出目录管理:通过DESTDIRMOC_DIR等变量,将生成的文件归类到binbuild目录下,保持项目根目录的整洁,这在团队协作或版本管理时非常有用。
  4. 发布配置:在发布版本(release)中,定义QT_NO_DEBUG_OUTPUT可以关闭Qt内部的一些调试日志,减小体积。将优化等级从-O2提升到-O3可以让程序运行得更快。

4. 核心模块的详细实现与代码剖析

4.1 数据库连接与管理的单例模式封装

数据库操作是系统的基石,必须保证在整个应用生命周期内,数据库连接是唯一且稳定的。我采用单例模式封装了一个DatabaseManager类。

databasemanager.h

#ifndef DATABASEMANAGER_H #define DATABASEMANAGER_H #include <QObject> #include <QSqlDatabase> #include <QString> class DatabaseManager : public QObject { Q_OBJECT public: static DatabaseManager& instance(); bool openDatabase(const QString& path = QString()); // 打开/创建数据库 QSqlDatabase& database() { return mDatabase; } void closeDatabase(); // 封装常用操作 bool initDatabase(); // 初始化数据表 bool executeTransaction(const std::function<bool()>& operation); // 事务执行封装 private: explicit DatabaseManager(QObject *parent = nullptr); ~DatabaseManager(); DatabaseManager(const DatabaseManager&) = delete; DatabaseManager& operator=(const DatabaseManager&) = delete; QSqlDatabase mDatabase; QString mDatabasePath; }; #endif // DATABASEMANAGER_H

databasemanager.cpp关键部分

#include "databasemanager.h" #include <QSqlQuery> #include <QSqlError> #include <QDebug> #include <QDir> #include <QStandardPaths> DatabaseManager& DatabaseManager::instance() { static DatabaseManager singleton; return singleton; } DatabaseManager::DatabaseManager(QObject *parent) : QObject(parent) { // 默认数据库路径:程序所在目录下的 shop_data.db mDatabasePath = QCoreApplication::applicationDirPath() + "/shop_data.db"; } bool DatabaseManager::openDatabase(const QString& path) { if (!path.isEmpty()) { mDatabasePath = path; } // 如果数据库文件不存在,SQLite会自动创建 mDatabase = QSqlDatabase::addDatabase("QSQLITE", "shop_connection"); // 指定连接名 mDatabase.setDatabaseName(mDatabasePath); if (!mDatabase.open()) { qCritical() << "Failed to open database:" << mDatabase.lastError().text(); return false; } qDebug() << "Database opened successfully at:" << mDatabasePath; // 开启外键约束(SQLite默认关闭) QSqlQuery query(mDatabase); if (!query.exec("PRAGMA foreign_keys = ON;")) { qWarning() << "Failed to enable foreign keys:" << query.lastError().text(); } // 初始化表结构 return initDatabase(); } bool DatabaseManager::initDatabase() { QSqlQuery query(mDatabase); QStringList tableCreationQueries; tableCreationQueries << QStringLiteral( "CREATE TABLE IF NOT EXISTS product (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "code VARCHAR(20) UNIQUE NOT NULL," "name VARCHAR(100) NOT NULL," "category VARCHAR(50)," "unit_price REAL NOT NULL CHECK(unit_price >= 0)," "cost_price REAL CHECK(cost_price >= 0)," "stock INTEGER DEFAULT 0 CHECK(stock >= 0)," "alert_stock INTEGER DEFAULT 10 CHECK(alert_stock >= 0)" ");" ); // ... 类似地添加 sale_order 和 sale_detail 表的创建语句 for (const QString& sql : tableCreationQueries) { if (!query.exec(sql)) { qCritical() << "Failed to create table:" << query.lastError().text() << "\nSQL:" << sql; return false; } } return true; } bool DatabaseManager::executeTransaction(const std::function<bool()>& operation) { mDatabase.transaction(); if (operation()) { if (mDatabase.commit()) { return true; } else { qCritical() << "Transaction commit failed:" << mDatabase.lastError().text(); } } else { qCritical() << "Operation within transaction failed."; } mDatabase.rollback(); // 回滚 return false; }

核心技巧executeTransaction函数是一个事务执行的模板。它将一段需要原子性执行的代码(通过std::function传入)包裹在transaction()commit()/rollback()之间。这样,在任何需要事务的地方,比如完成一次销售,你可以这样写:

bool success = DatabaseManager::instance().executeTransaction([&](){ // 1. 插入销售主表 // 2. 插入销售明细表 // 3. 更新商品库存 // 如果任何一步失败,返回false return true; // 所有步骤成功则返回true });

这种写法让事务逻辑非常清晰,避免了在每个业务函数里重复写begin()commit()和错误处理。

4.2 商品管理模块:Model/View架构实战

Qt的Model/View架构是处理表格数据的利器。对于商品列表,我使用QSqlTableModel直接映射数据库表到UI,再配合QTableView进行展示和编辑。

MainWindow中初始化商品视图:

// mainwindow.cpp void MainWindow::initProductView() { // 创建模型 QSqlTableModel *model = new QSqlTableModel(this, DatabaseManager::instance().database()); model->setTable("product"); model->setEditStrategy(QSqlTableModel::OnManualSubmit); // 手动提交修改 model->setHeaderData(model->fieldIndex("code"), Qt::Horizontal, tr("商品编码")); model->setHeaderData(model->fieldIndex("name"), Qt::Horizontal, tr("商品名称")); model->setHeaderData(model->fieldIndex("category"), Qt::Horizontal, tr("分类")); model->setHeaderData(model->fieldIndex("unit_price"), Qt::Horizontal, tr("零售价")); model->setHeaderData(model->fieldIndex("stock"), Qt::Horizontal, tr("库存")); model->setHeaderData(model->fieldIndex("alert_stock"), Qt::Horizontal, tr("预警库存")); model->select(); // 从数据库加载数据 // 设置视图 ui->tableViewProduct->setModel(model); ui->tableViewProduct->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tableViewProduct->setSelectionMode(QAbstractItemView::SingleSelection); ui->tableViewProduct->horizontalHeader()->setStretchLastSection(true); ui->tableViewProduct->setAlternatingRowColors(true); // 隔行变色,提升可读性 ui->tableViewProduct->setSortingEnabled(true); // 启用点击表头排序 // 隐藏不需要的列(如内部id) ui->tableViewProduct->hideColumn(model->fieldIndex("id")); ui->tableViewProduct->hideColumn(model->fieldIndex("cost_price")); // 连接信号槽:双击编辑 connect(ui->tableViewProduct, &QTableView::doubleClicked, this, &MainWindow::onProductDoubleClicked); }

实现库存预警高亮:这里需要用到QStyledItemDelegate来自定义单元格的绘制。

// alertdelegate.h class AlertDelegate : public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; // alertdelegate.cpp void AlertDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // 假设库存列是第4列(从0开始),预警库存列是第5列 if (index.column() == 4) { // stock列 QModelIndex alertIndex = index.sibling(index.row(), 5); // 同行的alert_stock列 int stock = index.data().toInt(); int alertStock = alertIndex.data().toInt(); if (stock <= alertStock) { // 库存低于预警线,设置红色背景 opt.backgroundBrush = QBrush(QColor(255, 200, 200)); // 浅红色 } } QStyledItemDelegate::paint(painter, opt, index); } // 在MainWindow中应用这个Delegate ui->tableViewProduct->setItemDelegateForColumn(stockColumnIndex, new AlertDelegate(this));

注意事项:使用QSqlTableModel时,setEditStrategy非常重要。OnManualSubmit策略意味着用户在表格里的修改不会立即写入数据库,必须调用model->submitAll()才会提交。这给了我们一个缓冲,可以在提交前进行数据校验,或者提供一个“保存”按钮让用户确认修改。如果选择OnFieldChange,则每次编辑单元格失去焦点都会立即提交,风险较高。

4.3 销售收银模块:事务处理与UI交互

销售界面(SaleDialog)是交互最复杂的地方。它需要快速响应用户输入(扫码或键盘),动态更新清单和总价。

UI布局设计要点:

  1. 顶部:一个QLineEdit作为输入框,用于接收扫码枪输入(扫码枪模拟键盘输入)或手动输入商品编码。
  2. 中部:一个QTableWidgetQTableView,用于显示当前销售单中的商品列表(商品名、单价、数量、小计)。
  3. 右侧:一个QLabel动态显示“总计:XX元”,以及“收款”、“取消”等按钮。
  4. 底部:数字键盘(可以用QPushButton模拟),方便触摸屏操作。

核心逻辑流程:

  1. 添加商品:在输入框监听returnPressed信号。当用户输入编码并回车后,在product表中查询该商品。如果找到,检查库存是否充足。如果充足,则将商品信息(编码、名称、单价)添加到中间的临时销售清单中(用一个QList<SaleItem>结构体列表在内存中维护),并清空输入框。同时,实时计算并更新总价。
  2. 修改数量:允许用户在中间的清单表格中直接修改商品数量。修改时,需要再次校验库存(原数量+新数量 <= 当前库存?)。
  3. 完成交易:点击“收款”按钮。
    • 弹出对话框,让用户输入实收金额,并自动计算找零。
    • 用户确认后,调用DatabaseManager::executeTransaction执行一个包含以下步骤的事务: a. 生成一个唯一的销售单号(规则如SALE-年月日-序号)。 b. 向sale_order表插入主记录。 c. 遍历内存中的销售清单,向sale_detail表插入明细记录。 d. 更新product表中每个售出商品的stock字段(原子性减库存)。
    • 如果事务成功,则清空临时销售清单,提示“收款成功”,并可选择是否打印小票。
    • 如果事务失败(如并发操作导致库存不足),则提示错误,交易取消。

关键代码片段(添加商品到清单):

void SaleDialog::onAddProduct(const QString& productCode) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare("SELECT name, unit_price, stock FROM product WHERE code = ?"); query.addBindValue(productCode); if (!query.exec() || !query.next()) { QMessageBox::warning(this, tr("错误"), tr("未找到商品编码:%1").arg(productCode)); return; } QString name = query.value(0).toString(); double price = query.value(1).toDouble(); int stock = query.value(2).toInt(); // 检查库存 int existingQty = findQuantityInCart(productCode); // 查找购物车中已有数量 if (existingQty + 1 > stock) { QMessageBox::warning(this, tr("库存不足"), tr("商品【%1】库存仅剩%2件").arg(name).arg(stock)); return; } // 添加到内存中的购物车列表 mSaleCart SaleItem item; item.productCode = productCode; item.productName = name; item.unitPrice = price; item.quantity = existingQty + 1; item.subtotal = item.unitPrice * item.quantity; // 如果已存在,则更新数量和小计;否则追加新项 updateOrAddToCart(item); // 刷新UI表格和总计 refreshCartTable(); calculateTotal(); }

5. 进阶功能实现与界面美化

5.1 使用Qt Charts实现销售数据可视化

统计报表不能只是枯燥的数字,图表能让趋势一目了然。Qt Charts模块可以轻松创建柱状图、折线图、饼图。

实现一个简单的日销售额柱状图:

  1. 在.pro文件中已添加QT += charts
  2. 在统计对话框的头文件中包含头文件并前向声明
    #include <QtCharts/QChartView> #include <QtCharts/QBarSeries> #include <QtCharts/QBarSet> #include <QtCharts/QBarCategoryAxis> #include <QtCharts/QValueAxis> QT_CHARTS_USE_NAMESPACE
  3. 在对话框初始化函数中创建图表
    void StatsDialog::initSalesChart() { // 1. 从数据库查询最近7天的销售额 QMap<QString, double> dailySales; // key: 日期, value: 销售额 QSqlQuery query; query.prepare("SELECT date(sale_time) as day, SUM(total_amount) " "FROM sale_order WHERE sale_time >= date('now', '-6 days') " "GROUP BY day ORDER BY day"); if (query.exec()) { while (query.next()) { dailySales[query.value(0).toString()] = query.value(1).toDouble(); } } // 2. 创建数据集 QBarSet *set = new QBarSet(tr("日销售额")); QStringList categories; for (int i = 6; i >= 0; --i) { QDate date = QDate::currentDate().addDays(-i); QString dateStr = date.toString("MM-dd"); categories << dateStr; *set << dailySales.value(date.toString("yyyy-MM-dd"), 0.0); } // 3. 创建系列和图表 QBarSeries *series = new QBarSeries(); series->append(set); series->setLabelsVisible(true); // 显示柱子上方的数值标签 QChart *chart = new QChart(); chart->addSeries(series); chart->setTitle(tr("近七日销售额趋势")); chart->setAnimationOptions(QChart::SeriesAnimations); // 添加动画效果 // 4. 创建坐标轴 QBarCategoryAxis *axisX = new QBarCategoryAxis(); axisX->append(categories); chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); QValueAxis *axisY = new QValueAxis(); axisY->setLabelFormat("%.0f 元"); // Y轴标签格式 chart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisY); // 5. 将图表放入视图 QChartView *chartView = new QChartView(chart); chartView->setRenderHint(QPainter::Antialiasing); // 抗锯齿,让图表更平滑 // 将chartView添加到对话框的布局中 ui->verticalLayoutChart->addWidget(chartView); }

美化技巧:可以给QBarSet设置颜色setColor(QColor(70, 130, 180)),给图表设置主题chart->setTheme(QChart::ChartThemeBlueNcs),让图表看起来更专业。

5.2 使用Qt StyleSheet进行界面美化

Qt的样式表(QSS)类似于CSS,可以极大地美化原生控件的外观。对于商铺系统,一个简洁、清晰、重点突出的界面很重要。

为整个应用设置一个统一的样式:可以在main.cpp或主窗口的构造函数中加载QSS文件,或者直接设置。

// 在主窗口构造函数中 this->setStyleSheet(R"( QMainWindow { background-color: #f5f5f5; } QTableView { alternate-background-color: #f0f8ff; selection-background-color: #4682b4; selection-color: white; gridline-color: #d3d3d3; } QHeaderView::section { background-color: #e6e6e6; padding: 5px; border: 1px solid #d3d3d3; font-weight: bold; } QPushButton { background-color: #5cacee; border: none; color: white; padding: 8px 16px; border-radius: 4px; font-size: 14px; } QPushButton:hover { background-color: #4a9fe0; } QPushButton:pressed { background-color: #3a8ed0; } QLineEdit, QComboBox { padding: 5px; border: 1px solid #ccc; border-radius: 3px; } QLabel#titleLabel { font-size: 18px; font-weight: bold; color: #333; } )"); // 特别高亮“收款”按钮 ui->btnCheckout->setStyleSheet("QPushButton {" "background-color: #32cd32;" // 绿色 "font-size: 16px;" "font-weight: bold;" "padding: 10px 20px;" "}" "QPushButton:hover { background-color: #28a428; }");

心得:样式表功能强大,但不要过度使用,以免影响性能。对于需要频繁刷新的控件(如表格),复杂的样式可能导致卡顿。保持风格统一、色彩协调、重点操作突出即可。

6. 项目打包、部署与实战问题排查

6.1 使用windeployqt进行发布打包

开发完成后,我们需要将程序打包成一个可以独立分发的文件夹。Qt提供了windeployqt工具来自动收集所有依赖的DLL。

  1. 编译Release版本:在Qt Creator中,将构建套件切换到Release模式,然后编译项目。
  2. 找到可执行文件:编译完成后,在项目构建目录的release文件夹(或你在.pro中指定的DESTDIR)下找到.exe文件。
  3. 打开Qt命令行:在开始菜单找到Qt 5.15.2 (MSVC 2019 64-bit)下的Qt 5.15.2 64-bit for Desktop (MSVC 2019),这是一个已经配置好环境变量的命令行。
  4. 运行部署工具:在命令行中,切换到你的.exe文件所在目录,执行:
    windeployqt your_app_name.exe
    这个命令会自动扫描.exe文件,将其依赖的Qt核心库、插件(如图像格式支持、数据库驱动等)复制到当前目录。
  5. 手动补充依赖
    • 数据库驱动windeployqt通常会复制qsqlite.dll。检查生成的sqldrivers文件夹是否存在该文件。
    • VC++运行时库:程序依赖MSVC的运行库。你需要将vc_redist.x64.exe(对于64位程序)打包给用户,或者指导用户从微软官网下载安装。更简单的办法是,在打包文件夹里放入以下DLL(从系统目录C:\Windows\System32复制,但需注意版权和分发许可):
      • msvcp140.dll
      • vcruntime140.dll
      • vcruntime140_1.dll(VS 2017及以上版本需要)
    • 自定义文件:将你的数据库文件shop_data.db(或一个空的模板数据库)也放入文件夹。

现在,这个文件夹就可以压缩后分发给用户了。用户只需解压,双击.exe即可运行。

6.2 开发与部署中的常见问题与解决方案

在实际开发和用户使用中,总会遇到一些意想不到的问题。这里记录了几个最具代表性的“坑”及其解决方法。

问题1:程序在开发机运行正常,拷贝到别的电脑上启动崩溃或提示缺少DLL。

  • 排查:这几乎肯定是依赖库缺失。首先检查打包文件夹是否包含了所有必要的Qt DLL(windeployqt已解决)。如果还报错,使用Dependency WalkerVisual Studio自带的dumpbin /dependents your_app.exe工具查看具体缺少哪个DLL。
  • 解决:最常见的是缺少MSVC运行时库特定插件的依赖。确保platforms/qwindows.dllimageformats/*.dll等插件文件夹完整。对于运行时库,要么要求用户安装,要么将对应的msvcpxxx.dll一并打包(需谨慎评估许可)。

问题2:销售时,偶尔会出现“库存扣减不正确”,比如卖出一件商品,库存却减了多件。

  • 排查:这是典型的并发问题。虽然我们是单机程序,但如果用户快速连续点击“收款”按钮,或者程序有多个地方同时修改库存(比如后台盘点线程),就可能发生。
  • 解决
    1. 事务隔离:确保所有的库存更新操作都在数据库事务内完成,并且使用SELECT ... FOR UPDATE(SQLite中可用BEGIN IMMEDIATE TRANSACTION)来锁定行,防止脏读。在我们的executeTransaction封装中,已经使用了事务。
    2. 业务逻辑加锁:在应用层,对于关键操作(如销售),可以使用QMutex进行加锁,确保同一时间只有一个销售流程在执行。
    class SaleManager : public QObject { Q_OBJECT public: bool checkout(const QList<SaleItem>& cart) { QMutexLocker locker(&mMutex); // 加锁 return DatabaseManager::instance().executeTransaction([&](){ // 检查并扣减库存 for (const auto& item : cart) { QSqlQuery q; q.prepare("SELECT stock FROM product WHERE code=?"); q.addBindValue(item.productCode); if (!q.exec() || !q.next()) return false; int currentStock = q.value(0).toInt(); if (currentStock < item.quantity) return false; // 库存不足 // 扣减库存 q.prepare("UPDATE product SET stock = stock - ? WHERE code=?"); q.addBindValue(item.quantity); q.addBindValue(item.productCode); if (!q.exec()) return false; } // ... 插入订单记录 return true; }); } private: QMutex mMutex; };

问题3:商品或销售记录越来越多,查询和表格加载变慢。

  • 排查:当数据量达到数万条时,SELECT * FROM product这样的查询和QSqlTableModel全量加载确实会变慢。
  • 解决
    1. 数据库索引:为经常用于查询和连接的字段建立索引,能极大提升速度。
      CREATE INDEX idx_product_code ON product(code); CREATE INDEX idx_sale_order_time ON sale_order(sale_time); CREATE INDEX idx_sale_detail_order_id ON sale_detail(order_id);
    2. 分页加载:对于商品列表,不要一次性加载所有数据。实现分页功能,每次只查询和显示一页(如100条)数据。这需要修改模型,使用QSqlQueryModel并配合LIMITOFFSET子句手动实现。
    3. 增量加载:对于销售记录查询,可以默认只加载最近一个月的数据,并提供日期筛选器让用户按需查询。

问题4:用户误操作删除了重要商品或销售数据。

  • 解决
    1. 软删除:重要的业务数据(如商品)不进行物理删除,而是在表中增加一个is_deleted标志位。删除操作只是更新这个标志位。查询时默认过滤掉已删除的数据。这为数据恢复提供了可能。
    2. 操作日志:建立一个operation_log表,记录关键操作(增删改)的用户、时间、操作类型和详情。当出现问题时,可以追溯。
    3. 定期备份:最简单的,可以提示用户定期手动备份.db文件。也可以程序实现每周自动将数据库文件复制到备份目录。

开发这样一个完整的系统,从设计到实现再到打包部署,每一步都需要仔细考量。Qt和C++的组合给了我们强大的控制力和性能,但也要面对更多的细节。希望这个详细的指南和源码思路,能帮你少走弯路,更快地构建出自己的应用。

本文还有配套的精品资源,点击获取

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

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

立即咨询