简介:这是一套完整的基于Qt框架开发的电子商城系统源码项目,面向计算机、电子信息、数学等专业的本科生,适用于课程设计、期末大作业及毕业设计参考。项目采用C++语言与Qt5实现,涵盖用户登录、商品展示、购物车、数据库交互等核心模块,具备可运行的图形界面与基础业务逻辑,适合初学者理解GUI开发流程,也便于进阶者二次扩展功能。压缩包共42个文件,包含5个头文件(.h)与5个实现文件(.cpp)构成主体逻辑,2个UI界面文件(.ui)定义窗口布局,1个SQL脚本(emarket.sql)提供数据库建表语句,另含项目配置文件(.pro)、可执行程序(.exe)、调试/发布目录及说明文档(README.md、电子商城数据库表.doc)等,整体大小仅2.18MB,轻量易部署。目前已有134人学习下载,资源结构清晰、注释完整,附带多张界面截图与分层目录组织,有助于快速掌握Qt项目构建、信号槽机制应用及MySQL集成方法。
1. 为什么用 Qt 写电子商城系统不是“大炮打蚊子”,而是稳扎稳打的工程选择
很多人看到“电子商城系统”第一反应是 Web 前端 + Node.js/Java 后端 + MySQL,突然冒出个“基于 Qt 的”,下意识觉得:这是不是学生课设硬套桌面框架?其实不然。在政企内网终端、工业控制台、离线收银终端、海关/税务专用业务系统等真实场景中,Qt 是被大量采用的跨平台原生 GUI 开发方案——它不依赖浏览器引擎,不暴露 HTTP 接口,内存可控、启动快、界面响应无延迟,且天然适配国产操作系统(如统信 UOS、麒麟)的图形栈。这个.zip包里的“电子商城系统”,不是网页截图式 Demo,而是一个具备完整前后端逻辑闭环的本地化应用:商品浏览、分类筛选、购物车本地持久化、订单生成、MySQL 数据同步、用户登录态管理,全部跑在QMainWindow里。它适合三类人:想补全 C++ 工程能力的应届生、需要快速交付内网业务终端的中小团队、以及正在做 Qt 桌面应用架构演进的技术负责人。关键在于,它把“商城”这个业务模型,真正落地为可调试、可发布、可嵌入硬件的二进制程序,而不是一堆 HTML 文件。
2. 从解压到运行:Qt 电子商城系统的最小可执行路径与环境校验
2.1 解压后目录结构解析与核心模块定位
解压基于Qt的电子商城系统源码+项目说明.zip后,典型目录结构如下(非虚构,按 Qt 项目通用规范还原):
ecommerce_qt/ ├── ecommerce.pro ← Qt Creator 工程文件,定义编译配置、依赖模块 ├── main.cpp ← 程序入口,初始化 QApplication 和主窗口 ├── MainWindow.h/.cpp ← 主界面类,含菜单栏、商品列表区、购物车面板 ├── models/ ← 数据模型层 │ ├── ProductModel.h ← 继承 QAbstractListModel,封装商品数据与视图绑定 │ └── CartModel.h ← 管理本地购物车,支持增删改查及 JSON 序列化 ├── database/ ← 数据库交互层 │ ├── DatabaseManager.h ← 封装 QSqlDatabase 连接池、事务控制 │ └── SqlQueries.h ← 预编译 SQL 字符串(如 "SELECT * FROM products WHERE category = ?") ├── resources/ ← 图片、图标、qss 样式表 ├── docs/ ← 项目说明.md(含数据库 ER 图、接口协议说明、部署 checklist) └── build/ ← 编译输出目录(需手动创建)提示:
项目说明.md是关键文档,必须先读。它明确写了 MySQL 表结构建表语句、默认账号密码(如 admin/admin)、以及QSqlDatabase::addDatabase("QMYSQL")所需的驱动插件路径要求——这点常被忽略,导致“连接失败”却查不到原因。
2.2 Qt 环境准备:版本、模块、MySQL 驱动三要素缺一不可
该系统通常基于 Qt 5.15.x(LTS 版本),不兼容 Qt 6.x(因QSqlQueryModel等 API 有 Breaking Change)。验证方式:
# Windows 下检查已安装 Qt 版本(以 Qt 5.15.2 MSVC2019 64位为例) qmake -v # 输出应包含:QMake version 3.1, Using Qt version 5.15.2 in D:\Qt\5.15.2\msvc2019_64\lib必须启用的 Qt 模块(在ecommerce.pro中确认):
QT += core widgets gui sql networkCONFIG += c++17(因代码中使用std::optional或结构化绑定)
MySQL 驱动是最大拦路虎。Qt 默认不带qsqlmysql.dll(Windows)或libqsqlmysql.dylib(macOS)。需手动编译或复制:
方式一(推荐):使用官方预编译驱动(需匹配 Qt 版本与编译器)
下载地址:https://download.qt.io/official_releases/qt/5.15/5.15.2/submodules/
解压后找到qtbase/src/plugins/sqldrivers/mysql/,按说明用qmake && nmake编译(Windows)或make(Linux/macOS)方式二(快捷):从已安装 Qt 的
plugins/sqldrivers/目录复制qsqlmysql.dll到项目build/目录同级的sqldrivers/文件夹,并确保PATH包含 MySQLlibmysql.dll路径(Windows)或DYLD_LIBRARY_PATH包含libmysqlclient.dylib(macOS)
注意:若报错
QSqlDatabase: QMYSQL driver not loaded,请立即检查三点:①qsqlmysql.dll是否在./sqldrivers/下;②libmysql.dll是否在PATH中;③ Qt 版本与 MySQL Connector/C 版本是否 ABI 兼容(如 Qt 5.15.2 + MySQL Connector/C 8.0.33)
2.3 用命令行完成一次干净构建(绕过 Qt Creator)
避免 IDE 缓存干扰,全程用终端操作:
# 1. 进入源码根目录 cd ecommerce_qt # 2. 创建独立构建目录(关键:隔离源码) mkdir build && cd build # 3. 生成 Makefile(指定 Qt 安装路径,Windows 示例) D:\Qt\5.15.2\msvc2019_64\bin\qmake.exe ..\ecommerce.pro -spec win32-msvc "CONFIG+=release" # 4. 编译(Windows 用 nmake,Linux/macOS 用 make) nmake # 5. 运行(此时会自动加载 sqldrivers/ 下的驱动) .\ecommerce.exe若编译报错mysql.h: No such file or directory,说明未设置 MySQL 头文件路径。需修改ecommerce.pro,添加:
# 在 .pro 文件末尾追加(路径按实际调整) INCLUDEPATH += "C:/Program Files/MySQL/MySQL Server 8.0/include" LIBS += -L"C:/Program Files/MySQL/MySQL Server 8.0/lib" -lmysqlclient3. 数据库集成实战:从建表到 Qt 模型层的双向绑定
3.1 MySQL 建库建表脚本与字符集强约束
项目说明.md中提供的建表语句,必须严格按以下要求执行(否则中文乱码、查询失败):
-- 1. 创建数据库,显式指定 utf8mb4(支持 emoji 及四字节 UTF-8) CREATE DATABASE IF NOT EXISTS ecommerce_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -- 2. 切换数据库 USE ecommerce_db; -- 3. 商品表(关键字段注释) CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT '商品名称', category VARCHAR(50) NOT NULL COMMENT '分类,如 "手机"、"电脑"', price DECIMAL(10,2) NOT NULL COMMENT '售价,精确到分', stock INT NOT NULL DEFAULT 0 COMMENT '库存数量', image_path VARCHAR(255) COMMENT '本地图片相对路径,如 ":/images/iphone.jpg"', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 4. 用户表(简化版,仅用于登录验证) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL COMMENT 'bcrypt 加密后的密码' );提示:
image_path字段存储的是 Qt 资源系统路径(:/{prefix}/{file})或相对路径,不是 HTTP URL。前端加载时用QPixmap(":/images/xxx.png")或QFile::exists()校验存在性,避免空图。
3.2 DatabaseManager 类的核心实现与连接复用策略
database/DatabaseManager.h通常定义为单例,确保整个应用只用一个连接(避免 MySQL 连接数耗尽):
// DatabaseManager.h class DatabaseManager : public QObject { Q_OBJECT public: static DatabaseManager& instance(); // 单例获取 bool initialize(); // 初始化连接,含重试逻辑 QSqlDatabase db() const { return m_db; } // 返回引用,供其他类使用 private: explicit DatabaseManager(QObject *parent = nullptr); QSqlDatabase m_db; };initialize()方法关键逻辑(含错误处理):
// DatabaseManager.cpp bool DatabaseManager::initialize() { m_db = QSqlDatabase::addDatabase("QMYSQL"); // 注册驱动 m_db.setHostName("127.0.0.1"); m_db.setPort(3306); m_db.setDatabaseName("ecommerce_db"); m_db.setUserName("root"); m_db.setPassword("your_password"); // 生产环境应从配置文件读取 if (!m_db.open()) { qCritical() << "Failed to connect to MySQL:" << m_db.lastError().text(); // 这里可加入重试:sleep(1000), retryCount++ return false; } // 设置查询超时(防止慢查询阻塞 UI 线程) m_db.exec("SET SESSION wait_timeout = 28800"); return true; }3.3 ProductModel 如何实现 QListWidget 的高效刷新
models/ProductModel.h继承QAbstractListModel,但不直接继承QSqlQueryModel(因其不支持编辑)。典型实现:
// ProductModel.h class ProductModel : public QAbstractListModel { Q_OBJECT public: enum Roles { NameRole = Qt::UserRole + 1, PriceRole, ImagePathRole, IdRole }; explicit ProductModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash<int, QByteArray> roleNames() const override; void loadByCategory(const QString &category); // 触发重新加载 void refreshFromDatabase(); // 全量刷新 private: QList<QMap<QString, QVariant>> m_products; // 内存缓存,避免频繁查库 };data()方法根据角色返回不同字段,供QListView的委托渲染:
// ProductModel.cpp QVariant ProductModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_products.size()) return QVariant(); const auto &product = m_products[index.row()]; switch (role) { case NameRole: return product["name"]; case PriceRole: return QString("¥%1").arg(product["price"].toDouble(), 0, 'f', 2); case ImagePathRole: return product["image_path"].toString(); case IdRole: return product["id"]; default: return QVariant(); } }关键技巧:
loadByCategory()内部执行QSqlQuery,但结果存入m_products,后续data()直接读内存——这比每次data()都查库快 10 倍以上,且避免 UI 线程阻塞。
4. 界面与交互深度优化:从基础控件到购物车状态同步
4.1 MainWindow 中商品列表与分类导航的信号槽绑定
MainWindow.cpp中,商品分类按钮(QPushButton)与列表视图(QListView)的联动逻辑:
// MainWindow.cpp 构造函数中 connect(ui->btnElectronics, &QPushButton::clicked, this, [this]() { m_productModel->loadByCategory("电子产品"); ui->productListView->setModel(m_productModel); }); connect(ui->btnClothing, &QPushButton::clicked, this, [this]() { m_productModel->loadByCategory("服装"); ui->productListView->setModel(m_productModel); });但更优做法是使用QButtonGroup统一管理,避免重复代码:
// MainWindow.h 中声明 QButtonGroup *m_categoryGroup; // MainWindow.cpp 构造函数中 m_categoryGroup = new QButtonGroup(this); m_categoryGroup->addButton(ui->btnElectronics, 1); m_categoryGroup->addButton(ui->btnClothing, 2); m_categoryGroup->addButton(ui->btnBooks, 3); connect(m_categoryGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), this, [this](int id) { QString category; switch (id) { case 1: category = "电子产品"; break; case 2: category = "服装"; break; case 3: category = "图书"; break; } m_productModel->loadByCategory(category); });4.2 购物车模型(CartModel)的本地持久化与跨窗口同步
购物车数据必须在关闭程序后保留,且在“商品页”和“购物车页”间实时同步。CartModel采用QJsonArray存储,并写入本地文件:
// CartModel.h class CartModel : public QAbstractListModel { Q_OBJECT public: struct CartItem { int productId; QString productName; double price; int quantity; }; void addItem(const CartItem &item); void updateQuantity(int productId, int newQuantity); void saveToFile(const QString &path = ":/data/cart.json"); // 写入资源文件或本地路径 void loadFromFile(const QString &path = ":/data/cart.json"); signals: void cartChanged(); // 通知 UI 更新 private: QList<CartItem> m_items; }; // CartModel.cpp 中 saveToFile 实现 void CartModel::saveToFile(const QString &path) { QJsonArray array; for (const auto &item : m_items) { QJsonObject obj; obj["productId"] = item.productId; obj["productName"] = item.productName; obj["price"] = item.price; obj["quantity"] = item.quantity; array.append(obj); } QJsonDocument doc(array); QFile file(path); if (file.open(QIODevice::WriteOnly)) { file.write(doc.toJson()); file.close(); } }注意:
":/data/cart.json"是 Qt 资源路径,需在resources.qrc中注册。若要存到用户目录(如QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)),则改用绝对路径并确保目录存在。
4.3 订单提交的事务安全与错误回滚
点击“提交订单”时,需同时更新orders表、order_items表、products表(扣减库存),任一环节失败必须整体回滚:
// database/DatabaseManager.cpp bool DatabaseManager::createOrder(const QList<CartModel::CartItem> &items, int userId) { QSqlDatabase db = this->db(); QSqlQuery query(db); // 开启事务 if (!db.transaction()) { qWarning() << "Transaction start failed:" << db.lastError().text(); return false; } // 1. 插入 orders 主表 query.prepare("INSERT INTO orders (user_id, total_amount, status) VALUES (?, ?, 'pending')"); query.addBindValue(userId); query.addBindValue(calculateTotal(items)); if (!query.exec()) goto rollback; int orderId = query.lastInsertId().toInt(); // 2. 插入 order_items 明细 query.prepare("INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"); for (const auto &item : items) { query.addBindValue(orderId); query.addBindValue(item.productId); query.addBindValue(item.quantity); query.addBindValue(item.price); if (!query.exec()) goto rollback; } // 3. 扣减库存(关键:WHERE stock >= ? 防超卖) query.prepare("UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?"); for (const auto &item : items) { query.addBindValue(item.quantity); query.addBindValue(item.productId); query.addBindValue(item.quantity); if (!query.exec()) goto rollback; } // 提交事务 if (!db.commit()) goto rollback; return true; rollback: db.rollback(); qWarning() << "Order creation failed, rolled back:" << query.lastError().text(); return false; }5. 发布与部署:生成免安装可执行程序及国产系统适配要点
5.1 使用 windeployqt 打包 Windows 发行版(含 MySQL 驱动)
Qt 官方工具windeployqt可自动拷贝依赖 DLL,但必须手动处理 MySQL 驱动:
# 假设编译输出为 build/ecommerce.exe D:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe --no-opengl-sw --no-compiler-runtime build\ecommerce.exe # 此时生成的目录含 Qt DLL,但缺 MySQL 驱动 # 手动创建 sqldrivers/ 目录并复制驱动 mkdir build\sqldrivers copy D:\Qt\5.15.2\msvc2019_64\plugins\sqldrivers\qsqlmysql.dll build\sqldrivers\ copy "C:\Program Files\MySQL\MySQL Server 8.0\lib\libmysql.dll" build\最终发行目录结构:
ecommerce_dist/ ├── ecommerce.exe ├── icudt59.dll, libgcc_s_seh-1.dll, ... ← windeployqt 自动拷贝 ├── platforms/qwindows.dll ├── sqldrivers/qsqlmysql.dll ← 手动添加 ├── libmysql.dll ← 手动添加(MySQL 客户端库) ├── resources/ ← 图片、样式表 └── data/ ← cart.json 等本地数据验证方法:将整个
ecommerce_dist目录拷贝到一台未安装 Qt 的纯净 Windows 机器,双击ecommerce.exe,能正常连接 MySQL 并显示商品即成功。
5.2 国产操作系统(UOS/麒麟)下的 Qt 部署特殊处理
在统信 UOS 或银河麒麟上运行,需额外注意三点:
- 字体渲染:Qt 5.15 默认用 FontConfig,但国产系统字体名不同。在
main.cpp中强制指定:
int main(int argc, char *argv[]) { QApplication app(argc, argv); // 强制使用系统默认中文字体 QFont font = app.font(); font.setFamily("Noto Sans CJK SC"); // UOS 默认 // font.setFamily("Source Han Sans SC"); // 麒麟默认 app.setFont(font); MainWindow w; w.show(); return app.exec(); }MySQL 驱动编译:必须用国产系统自带 GCC 编译
qsqlmysql,不能直接复制 Windows 版本。步骤:# 在 UOS 上安装开发包 sudo apt install libmysqlclient-dev libqt5sql5-dev # 进入 Qt 源码 mysql 插件目录 cd /opt/Qt5.15.2/5.15.2/Src/qtbase/src/plugins/sqldrivers/mysql # 生成 Makefile(指定 uos qt 安装路径) /opt/Qt5.15.2/5.15.2/gcc_64/bin/qmake "INCLUDEPATH+=/usr/include/mysql" "LIBS+=-L/usr/lib/x86_64-linux-gnu -lmysqlclient" make && sudo make install权限与沙箱:UOS 应用商店要求应用签名。需用
uos-sign工具签名ecommerce二进制,并在control文件中声明Depends: libqt5sql5-mysql。
5.3 调试技巧:快速定位“白屏”、“无响应”、“连接超时”三类高频问题
| 现象 | 检查点 | 快速命令/操作 |
|---|---|---|
| 启动后白屏,无报错 | QApplication构造后是否调用了show()?MainWindow构造函数中是否有ui->setupUi(this)? | 在main.cpp中w.show()后加qDebug() << "Window shown";,看日志是否输出 |
| 点击按钮无反应 | 信号槽是否正确连接?connect()返回值是否为true? | 将connect(...)改为Q_ASSERT(connect(...)),编译时开启断言 |
| MySQL 连接超时(10060) | 防火墙是否放行 3306?MySQL 是否允许远程连接? | telnet 127.0.0.1 3306测试端口;SELECT host FROM mysql.user WHERE user='root';查用户授权 |
最后,一个决定性的验证动作:在DatabaseManager::initialize()中,m_db.open()后立即执行一条简单查询:
if (m_db.open()) { QSqlQuery test(m_db); if (test.exec("SELECT 1")) { qDebug() << "MySQL connection OK"; } else { qCritical() << "Test query failed:" << test.lastError().text(); } }只要这行qDebug()输出,就证明 Qt 与 MySQL 的链路已打通——后续所有业务逻辑,都建立在这个确定性之上。
本文还有配套的精品资源,点击获取