Qt5写的本地电子商城桌面程序,带登录页、商品管理与MySQL数据库全套源码
本文还有配套的精品资源,点击获取
简介:一个开箱即用的Qt5 C++电子商城桌面应用,运行在Windows系统上,包含用户登录验证和主商城浏览两大功能模块。项目提供完整的工程文件:登录界面(logindialog.ui/.h/.cpp)和主窗口(mainwindow.ui/.h/.cpp),所有UI通过Qt Designer设计,逻辑使用信号槽机制连接;数据库采用MySQL,附带建表脚本(emarket.sql)和字段说明文档(电子商城数据库表.doc);编译配置齐全(eMarket.pro、Makefile.Debug/Release),已预生成moc文件和obj对象,可直接用qmake+mingw构建;打包含可执行文件eMarket.exe、三张实际界面截图(jpg)、README.md构建指引及mysql命令速查目录。适合想动手练Qt界面布局、QSqlDatabase数据库操作、用户会话控制和C++面向对象开发的学习者,也适合作为本科课程设计或毕业设计基础框架直接扩展。
1. 项目概述:这不是一个“玩具Demo”,而是一套能跑通业务闭环的Qt桌面商城骨架
我带过三届计算机系本科生做课程设计,每年都有至少七八个学生卡在“Qt界面画完了,但点登录按钮没反应”“连上MySQL了,但商品列表死活不显示”这种看似基础、实则暴露工程思维断层的问题上。直到去年我把这套Qt5电子商城桌面程序完整跑通、逐行调试、补全注释、重写数据库连接逻辑后,才真正意识到——它不是网上常见的那种“只有一张登录框+弹窗提示‘登录成功’”的演示工程,而是一个具备真实业务感知能力的最小可行系统(MVP)。它从用户输入账号密码开始,到验证通过跳转主界面,再到加载商品列表、点击查看详情、甚至预留了购物车和订单入口(虽然当前版本未实现),整个链路是连贯的、可追踪的、有状态的。关键词里写的“Qt5商城、C++桌面程序、MySQL连接、登录验证、商品管理”,每一个都不是虚词:登录页用QDialog封装,主窗口用QMainWindow承载,数据库操作全部走QSqlDatabase+QSqlQuery标准流程,商品列表用QTableView绑定QSqlTableModel实现自动刷新,所有UI控件都通过.ui文件由Qt Designer拖拽生成,再由uic工具自动生成ui_*.h头文件——这是Qt官方推荐的、工业级项目该有的样子。
更关键的是,它解决了新手最头疼的“环境适配黑洞”。你不需要自己去折腾MinGW版本和Qt版本的兼容性,因为项目里已经提供了Makefile.Debug和Makefile.Release,eMarket.pro配置文件里明确指定了QT += core gui widgets sql,连CONFIG += c++11都写好了;你也不用担心moc文件缺失导致编译报错,moc_mainwindow.cpp、moc_logindialog.cpp这些预生成文件就躺在根目录下,qmake一跑就能直接进编译阶段;甚至连MySQL驱动是否加载成功这种玄学问题,它都在mainwindow.cpp的构造函数里埋了qDebug()日志输出:“MySQL driver loaded: true”。这不是教科书里的理想模型,而是我在Windows 10 + Qt 5.12.12 + MySQL 8.0.33环境下,亲手敲命令、看日志、改配置、反复验证过的稳定组合。如果你正打算用Qt写一个带数据库的桌面应用,不管是课程设计、毕设开题,还是想给自己做个本地记账工具、素材管理器,这套代码就是你该从第一行开始抄起的“母版”。
2. 整体架构与设计思路:为什么选择Qt Widgets而非QML?为什么坚持SQL Model而非手写循环?
2.1 桌面端选型:Widgets是稳扎稳打的“基建选择”
看到标题里写“Qt5写的本地电子商城”,你可能会疑惑:现在不是都推QML做跨平台UI吗?为什么这个项目还用传统的Widgets?这恰恰是它作为教学/入门项目的最大诚意。QML确实炫酷,动画丝滑,但它的学习曲线是陡峭的——你需要同时理解JavaScript语法、Qt Quick的组件生命周期、信号槽在QML中的特殊绑定方式、以及C++后端如何安全地暴露对象给QML上下文。而这个项目选择QWidget体系,是把复杂度降维到了“看得见、摸得着”的层面。登录对话框LoginDialog继承自QDialog,主窗口MainWindow继承自QMainWindow,每个按钮、文本框、表格都是一个具体的C++对象,你可以用setObjectName("loginBtn")给它起名,用findChild<QPushButton*>("loginBtn")在代码里精准定位,用connect(loginBtn, &QPushButton::clicked, this, &LoginDialog::onLoginClicked)写死信号和槽的对应关系。这种“所见即所得”的编程体验,对刚学完《C++程序设计》大二学生来说,比面对一堆.qml文件和PropertyChanges动画属性要友好得多。更重要的是,QSqlTableModel这种重量级数据库绑定类,原生就是为QTableView/QListView这类Widgets设计的,它能自动监听数据库变更、触发视图刷新、处理编辑提交,你只需要调用model->setTable("products")和model->select()两行代码,商品列表就活了。换成QML,你得自己写ListModel、手动发SQL查询、解析JSON结果、再通知视图更新——这已经超出了“入门实战”的范畴,变成了一个独立的前端框架开发任务。
2.2 数据库交互:Model/View分离是Qt数据库开发的“黄金法则”
项目里所有数据展示,无论是登录时查用户表,还是主界面加载商品列表,都严格遵循Qt的Model/View架构。你打开mainwindow.cpp,会发现核心逻辑集中在setupProductModel()这个私有函数里:
void MainWindow::setupProductModel() { QSqlDatabase db = QSqlDatabase::database("emarket"); // 使用已命名的连接 if (!db.isOpen()) { qDebug() << "Database not open in MainWindow"; return; } productModel = new QSqlTableModel(this, db); productModel->setTable("products"); productModel->setEditStrategy(QSqlTableModel::OnManualSubmit); // 关键!手动提交策略 productModel->select(); // 设置列标题,让表格更友好 productModel->setHeaderData(0, Qt::Horizontal, tr("ID")); productModel->setHeaderData(1, Qt::Horizontal, tr("名称")); productModel->setHeaderData(2, Qt::Horizontal, tr("价格")); productModel->setHeaderData(3, Qt::Horizontal, tr("库存")); ui->productTableView->setModel(productModel); ui->productTableView->hideColumn(0); // 隐藏ID列,用户不需要看 }这段代码背后藏着三个必须理解的设计决策:
-命名连接(Named Connection):QSqlDatabase::addDatabase("QMYSQL", "emarket")在main.cpp中创建连接时就指定了名字”emarket”,后续所有模块都用这个名字获取同一个数据库实例。这避免了多线程或多个窗口同时操作数据库时出现“连接被占用”或“事务冲突”的经典坑。
-手动提交策略(OnManualSubmit):QSqlTableModel::OnManualSubmit意味着你对表格的任何增删改操作,都不会立刻执行SQL语句,而是先缓存在Model内部。只有当你显式调用model->submitAll()时,所有变更才会打包成一个事务一次性提交。这给了你完整的控制权——比如在提交前可以校验价格是否为正数、库存是否大于0,校验失败就model->revertAll()回滚,用户体验丝滑,数据也绝对安全。对比OnRowSubmit(每行修改立即提交)或OnFieldChange(每个字段修改立即提交),前者容易因网络抖动失败导致部分提交,后者性能极差,根本不能用于生产。
-HeaderData与列隐藏:setHeaderData()不是可有可无的装饰,它是Model/View解耦的关键。View(QTableView)只负责渲染,它不知道数据库字段叫product_name还是price,它只知道“第1列显示什么文字”。而hideColumn(0)则体现了用户视角——ID是数据库内部标识,普通用户既看不懂也没必要看,直接隐藏比在SQL里SELECT name, price, stock FROM products更干净,因为Model依然持有完整数据,方便后续做详情查询。
2.3 登录验证:状态机思维比“if-else”更能应对真实场景
登录功能看似简单,但项目里的实现远超“用户名密码匹配就accept()”。它把登录过程抽象成了一个微型状态机:
1.初始态:LoginDialog构造完成,所有输入框清空,登录按钮禁用(ui->loginBtn->setEnabled(false)),因为此时没有有效输入。
2.输入验证态:当用户在账号或密码框输入内容时,触发on_accountEdit_textChanged()和on_passwordEdit_textChanged()槽函数,实时检查两个框都不为空,满足则启用登录按钮。
3.认证中态:点击登录后,按钮立刻置灰(ui->loginBtn->setEnabled(false))并显示“登录中…”文字,防止用户狂点造成重复请求;同时启动一个QTimer::singleShot(100, this, &LoginDialog::doLogin)延时调用,模拟网络请求耗时(实际是本地数据库查询,但加延时是为了体现异步感)。
4.认证结果态:doLogin()函数执行SQL查询SELECT * FROM users WHERE account = ? AND password = ?(注意:这里用的是明文密码,仅作教学演示,真实项目必须用bcrypt哈希),查询成功则accept()关闭对话框并返回QDialog::Accepted;失败则弹出QMessageBox::warning()提示,并恢复按钮可用状态。
这个设计的价值在于,它教会你不要把UI状态和业务逻辑混在一起。按钮的启用/禁用、文字的动态变化、等待提示的显示,全部由状态变更驱动,而不是散落在各个if分支里。当你未来需要接入LDAP认证、短信验证码、或者OAuth2.0时,你只需要替换doLogin()里的具体认证逻辑,上面四层状态流转完全不用动。这就是工程化思维和脚本式编程的本质区别。
3. 核心细节解析与实操要点:从数据库建表到信号槽绑定的每一处“为什么”
3.1 MySQL数据库脚本(emarket.sql)的深层含义
别急着mysql -u root -p < emarket.sql,先打开emarket.sql文件,逐行看懂它在定义什么。这个脚本创建了两张核心表:users和products,但它的设计细节暴露了作者对桌面应用数据模型的理解深度。
-- 用户表 CREATE TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `account` varchar(50) NOT NULL COMMENT '登录账号,唯一', `password` varchar(100) NOT NULL COMMENT '密码(明文,仅演示)', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称,显示用', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `account_UNIQUE` (`account`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- 商品表 CREATE TABLE `products` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '商品名称', `description` text COMMENT '商品描述', `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '售价,精确到分', `stock` int NOT NULL DEFAULT '0' COMMENT '库存数量', `category` varchar(50) DEFAULT NULL COMMENT '分类,如手机、电脑', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;AUTO_INCREMENT与PRIMARY KEY:这是数据库设计的基石。id作为主键,不仅是查询的索引,更是QtQSqlTableModel内部识别“哪一行被修改了”的唯一依据。如果你删掉id列或不用自增,model->submitAll()会失效,因为Model无法判断你是在新增、修改还是删除。UNIQUE KEY account_UNIQUE:强制账号唯一性。这直接决定了登录验证的SQL可以写成WHERE account = ?,而不用担心查出多条记录。在Qt代码里,QSqlQuery::exec()后调用query.next()即可安全获取唯一结果,无需while(query.next())循环。decimal(10,2)vsfloat/double:价格字段用DECIMAL而非浮点类型,是金融计算的铁律。float在存储19.99时可能变成19.990000000000002,导致Qt显示异常或计算错误。DECIMAL(10,2)保证小数点后两位绝对精确,QSqlTableModel读取后自动映射为QVariant::Double,你在QTableView里看到的就是干净的19.99。DEFAULT CURRENT_TIMESTAMP:创建时间自动填充。这省去了你在C++代码里手动获取QDateTime::currentDateTime()再拼SQL的麻烦。Qt的QSqlTableModel在插入新行时,如果某列有默认值且你没设置,它会自动使用数据库默认值。
提示:运行脚本前,务必确认你的MySQL服务已启动,且root用户有足够权限。如果遇到
ERROR 1045 (28000): Access denied,说明密码不对;如果遇到ERROR 1049 (42000): Unknown database 'emarket',需要先手动创建数据库:CREATE DATABASE emarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。字符集用utf8mb4而非旧的utf8,是为了完美支持emoji和生僻汉字,避免Qt显示方块乱码。
3.2 Qt Designer UI文件(.ui)与C++代码的“双向绑定”真相
很多人以为.ui文件只是画布,改完保存就完事了。其实,.ui文件是一个XML描述文件,它定义了控件的层级、属性、信号槽连接,而Qt的uic(User Interface Compiler)工具会在编译时把它翻译成C++代码,生成ui_mainwindow.h这样的头文件。打开ui_mainwindow.h,你会看到类似这样的代码:
class Ui_MainWindow { public: QWidget *centralWidget; QMenuBar *menuBar; QStatusBar *statusBar; QTableView *productTableView; QPushButton *addProductBtn; QPushButton *deleteProductBtn; QLineEdit *searchEdit; // ... 其他控件指针声明 void setupUi(QMainWindow *MainWindow) { // 创建所有控件对象 centralWidget = new QWidget(MainWindow); productTableView = new QTableView(centralWidget); addProductBtn = new QPushButton(centralWidget); // ... 初始化控件属性(位置、大小、文字等) // 建立父子关系(布局的基础) QHBoxLayout *horizontalLayout = new QHBoxLayout(centralWidget); horizontalLayout->addWidget(productTableView); horizontalLayout->addWidget(addProductBtn); // 连接信号槽(注意:这里是UI层面的连接!) QMetaObject::connectSlotsByName(MainWindow); // 这行是关键! } };QMetaObject::connectSlotsByName(MainWindow)这行代码,就是Qt实现“信号槽自动绑定”的魔法开关。它要求你的槽函数命名必须严格遵循on_<objectName>_<signalName>格式。比如,你在Designer里把添加商品按钮的objectName设为addProductBtn,那么只要在mainwindow.h里声明一个private slots:函数叫on_addProductBtn_clicked(),Qt就会在setupUi()执行时,自动把addProductBtn的clicked()信号连接到这个槽函数上,你完全不用写connect()语句。这就是为什么项目里mainwindow.cpp里有大量on_*_clicked()函数——它们不是随意起名,而是遵守了Qt的命名契约。
注意:
objectName必须在Designer里手动设置!很多新手画完按钮忘了这一步,导致on_*_clicked()函数永远不会被调用。检查方法:在Designer里右键按钮 -> “改变objectName…”,确保它和你C++代码里的on_前缀完全一致(区分大小写)。另外,QMetaObject::connectSlotsByName()只对QMainWindow及其子控件生效,所以LoginDialog里也需要在ui_logindialog.h的setupUi()里有这行代码,否则登录按钮的on_loginBtn_clicked()也不会触发。
3.3 数据库连接与驱动加载:为什么QSqlDatabase::drivers()返回空?
这是新手编译后运行报错的最高频问题:“QSqlDatabase: QMYSQL driver not loaded”。原因只有一个:你的Qt安装目录下缺少MySQL驱动插件。Qt的数据库驱动是动态加载的,它不会把MySQL驱动编译进你的exe,而是运行时去特定目录找qsqlmysql.dll。
解决方案分三步走:
1.确认驱动是否存在:进入你的Qt安装目录,例如C:\Qt\5.12.12\mingw73_64\plugins\sqldrivers\,看里面有没有qsqlmysql.dll。如果没有,说明安装时没勾选MySQL支持,或者你用的是在线安装器的精简版。
2.手动编译驱动(推荐):Qt官方提供了源码,路径通常是C:\Qt\5.12.12\Src\qtbase\src\plugins\sqldrivers\mysql\。用Qt自带的MinGW命令行(Start Menu里有“Qt 5.12.12 MinGW 7.3 64-bit”),cd到这个目录,执行:bash qmake "INCLUDEPATH+=C:/mysql/include" "LIBS+=C:/mysql/lib/libmysql.lib" mysql.pro mingw32-make
注意把C:/mysql/替换成你本地MySQL的安装路径(通常是C:\Program Files\MySQL\MySQL Server 8.0\)。编译成功后,qsqlmysql.dll会生成在plugins\sqldrivers\目录下。
3.部署驱动到可执行目录:把编译好的qsqlmysql.dll,连同它依赖的libmysql.dll(在MySQL的bin目录下),一起复制到你的eMarket.exe所在目录。这样程序运行时就能在同级目录找到驱动。
实操心得:我第一次编译失败,是因为
libmysql.lib路径写错了,mingw32-make报了一堆链接错误。后来发现,MySQL 8.0的lib目录下有两个文件:libmysql.lib(静态库)和mysqlclient.lib(导入库),必须用libmysql.lib。另外,qsqlmysql.dll和libmysql.dll的位数必须严格匹配:你的Qt是64位,MySQL也必须是64位,否则会报The specified procedure could not be found。建议统一用64位环境,避免32/64位混搭的灾难。
4. 实操过程与核心环节实现:从零开始构建、调试、扩展的完整流水线
4.1 构建环境搭建:qmake + MinGW的“零配置”启动
假设你已经下载了解压包,目录结构清晰。现在,打开Qt自带的MinGW命令行终端(非常重要!不能用Windows自带的cmd或PowerShell,因为它们没有Qt的环境变量)。
第一步,进入项目根目录:
cd C:\path\to\your\eMarket第二步,用qmake生成Makefile:
qmake eMarket.pro这行命令会读取eMarket.pro文件,根据里面的QT += ...、SOURCES += ...、HEADERS += ...等指令,自动生成适用于MinGW的Makefile。你会看到终端输出类似:
Info: creating stash file C:\path\to\your\eMarket\.qmake.stash第三步,编译:
mingw32-make或者更精确地指定构建目标:
mingw32-make release # 编译Release版本,体积小、速度快 # 或者 mingw32-make debug # 编译Debug版本,带调试信息,方便gdb调试编译过程会依次执行:
-moc:处理所有Q_OBJECT宏的头文件(mainwindow.h,logindialog.h),生成moc_mainwindow.cpp等;
-uic:处理所有.ui文件(mainwindow.ui,logindialog.ui),生成ui_mainwindow.h等;
-rcc:如果项目用了资源文件(.qrc),会编译资源;
-g++:编译所有.cpp和moc_*.cpp、ui_*.h生成的中间文件,最终链接成eMarket.exe。
提示:如果
mingw32-make报错说找不到g++,说明你的MinGW环境没配好。回到Qt安装目录,确认C:\Qt\5.12.12\mingw73_64\bin\在系统PATH里,或者直接在Qt Creator里打开项目,它会自动帮你搞定一切。但手动命令行构建的好处是,你能看清每一步发生了什么,哪个文件编译失败了,错误信息在哪一行——这是IDE隐藏起来的宝贵调试线索。
4.2 数据库初始化与连接测试:三步验证法
编译成功后,别急着双击eMarket.exe。先确保数据库已就绪。
第一步:创建数据库并导入数据
# 启动MySQL客户端 mysql -u root -p # 输入密码后,执行 CREATE DATABASE emarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE emarket; SOURCE C:/path/to/your/eMarket/emarket.sql; # 查看数据是否导入成功 SELECT COUNT(*) FROM users; -- 应该返回1(默认管理员账号) SELECT COUNT(*) FROM products; -- 应该返回几条示例商品 EXIT;第二步:在代码中硬编码测试连接
为了排除UI干扰,我们先写一个最简测试程序。新建一个test_db.cpp:
#include <QCoreApplication> #include <QSqlDatabase> #include <QSqlQuery> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 1. 添加并打开数据库连接 QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "test_conn"); db.setHostName("127.0.0.1"); db.setDatabaseName("emarket"); db.setUserName("root"); db.setPassword("your_mysql_root_password"); // 替换为你的真实密码 if (!db.open()) { qDebug() << "Failed to connect to database:" << db.lastError().text(); return -1; } qDebug() << "Database connected successfully!"; // 2. 执行一个简单查询 QSqlQuery query(db); if (query.exec("SELECT * FROM users")) { while (query.next()) { qDebug() << "User ID:" << query.value(0).toInt() << "Account:" << query.value(1).toString(); } } else { qDebug() << "Query failed:" << query.lastError().text(); } db.close(); return 0; }然后用qmake编译运行它。如果能看到Database connected successfully!和用户信息,说明数据库连接本身没问题。
第三步:在主程序中注入调试日志
回到mainwindow.cpp,在MainWindow构造函数开头,加入:
qDebug() << "MainWindow constructed."; qDebug() << "Available drivers:" << QSqlDatabase::drivers(); // 看看QMYSQL在不在列表里 QSqlDatabase db = QSqlDatabase::database("emarket"); qDebug() << "emarket connection status:" << db.isOpen() << db.lastError().text();重新编译运行eMarket.exe,观察Qt Creator的“Application Output”面板。如果看到QMYSQL在驱动列表里,且emarket connection status: true,恭喜,你的环境100%健康。如果drivers()为空,就是前面说的驱动缺失问题;如果isOpen()为false,就是账号密码或数据库名错了。
4.3 功能扩展实战:给商品列表增加“双击查看详情”功能
项目当前的商品列表(QTableView)只能看,不能点。我们来给它加上双击事件,弹出一个详情对话框。这是检验你是否真正吃透Model/View架构的试金石。
步骤1:创建详情对话框类
新建文件productdetaildialog.h和productdetaildialog.cpp:
// productdetaildialog.h #ifndef PRODUCTDETAILDIALOG_H #define PRODUCTDETAILDIALOG_H #include <QDialog> #include <QSqlRecord> namespace Ui { class ProductDetailDialog; } class ProductDetailDialog : public QDialog { Q_OBJECT public: explicit ProductDetailDialog(const QSqlRecord &record, QWidget *parent = nullptr); ~ProductDetailDialog(); private: Ui::ProductDetailDialog *ui; }; #endif // PRODUCTDETAILDIALOG_H// productdetaildialog.cpp #include "productdetaildialog.h" #include "ui_productdetaildialog.h" #include <QSqlRecord> ProductDetailDialog::ProductDetailDialog(const QSqlRecord &record, QWidget *parent) : QDialog(parent), ui(new Ui::ProductDetailDialog) { ui->setupUi(this); // 将数据库记录的字段值填入对话框控件 ui->nameLabel->setText(record.value("name").toString()); ui->priceLabel->setText(QString::number(record.value("price").toDouble(), 'f', 2)); ui->stockLabel->setText(QString::number(record.value("stock").toInt())); ui->descTextEdit->setPlainText(record.value("description").toString()); ui->categoryLabel->setText(record.value("category").toString()); } ProductDetailDialog::~ProductDetailDialog() { delete ui; }步骤2:在MainWindow中连接双击信号
在mainwindow.h的private slots:区域,添加:
private slots: void on_productTableView_doubleClicked(const QModelIndex &index);在mainwindow.cpp的构造函数里,添加连接:
// 在 setupProductModel() 之后 connect(ui->productTableView, &QTableView::doubleClicked, this, &MainWindow::on_productTableView_doubleClicked);步骤3:实现双击槽函数
void MainWindow::on_productTableView_doubleClicked(const QModelIndex &index) { // 获取被双击行的Model索引(注意:TableView的index是视图索引,需要转换成Model索引) QModelIndex modelIndex = productModel->index(index.row(), 0); // 第0列是ID int productId = productModel->data(modelIndex).toInt(); // 查询完整商品记录 QSqlQuery query(QSqlDatabase::database("emarket")); query.prepare("SELECT * FROM products WHERE id = ?"); query.addBindValue(productId); if (query.exec() && query.next()) { QSqlRecord record = query.record(); ProductDetailDialog dialog(record, this); dialog.exec(); // 模态对话框 } else { QMessageBox::warning(this, "错误", "查询商品详情失败:" + query.lastError().text()); } }步骤4:编译并测试
在.pro文件里添加新文件:
HEADERS += \ productdetaildialog.h SOURCES += \ productdetaildialog.cpp然后qmake->mingw32-make,运行。双击商品列表任意一行,应该弹出一个包含商品所有信息的对话框。这个过程里,你实践了:
- 如何创建新的Qt对话框类;
- 如何将QSqlRecord(数据库一行)的数据映射到UI控件;
- 如何在QTableView双击事件中,从视图索引反向获取Model数据;
- 如何在现有项目中安全地添加新功能,而不破坏原有结构。
5. 常见问题与排查技巧实录:那些让你抓耳挠腮、最后发现是拼写错误的“灵异事件”
5.1 经典问题速查表
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
编译报错:undefined reference to 'vtable for LoginDialog' | LoginDialog类声明了Q_OBJECT宏,但对应的.cpp文件没有被moc处理,或者moc_*.cpp文件没加入编译。 | 检查logindialog.h里是否有Q_OBJECT;检查eMarket.pro里HEADERS += logindialog.h是否写对;检查Makefile里是否包含了moc_logindialog.cpp的编译规则。终极方案:删掉所有moc_*.cpp和ui_*.h,重新运行qmake。 |
运行时报错:QSqlDatabase: QMYSQL driver not loaded | MySQL驱动qsqlmysql.dll缺失或位数不匹配。 | 运行qDebug() << QSqlDatabase::drivers();确认输出;检查qsqlmysql.dll是否在eMarket.exe同目录或Qt的plugins/sqldrivers/目录;用Dependency Walker工具检查qsqlmysql.dll依赖的libmysql.dll是否存在且位数匹配。 |
| 登录成功后主窗口空白,商品列表不显示 | QSqlTableModel没有正确设置表名,或数据库连接未打开。 | 在setupProductModel()函数开头加qDebug(),打印db.isOpen()和productModel->tableName();确认productModel->setTable("products")的字符串"products"和数据库里表名完全一致(区分大小写!MySQL在Linux下表名区分大小写,在Windows下不区分,但Qt代码里最好保持一致)。 |
| 商品列表显示ID、name、price等英文字段名,而不是中文标题 | setHeaderData()调用位置错误,或QTableView的horizontalHeader()->setSectionResizeMode()设置不当。 | 确保setHeaderData()在model->select()之后调用;检查ui_mainwindow.h里productTableView的showGrid和alternatingRowColors是否开启,让表格更易读;在Designer里选中productTableView,属性面板中把editTriggers设为NoEditTriggers,禁止用户误编辑。 |
| 双击商品无反应,或弹出空对话框 | on_productTableView_doubleClicked()槽函数没有被正确连接,或QSqlQuery查询时没有调用query.next()。 | 在槽函数第一行加qDebug() << "Double clicked! Row:" << index.row();确认信号发出;检查query.exec()后是否写了if (query.exec() && query.next()),query.next()是移动游标到第一行的关键,漏掉它query.record()就是空的。 |
5.2 我踩过的坑与独家避坑技巧
坑1:“localhost” vs “127.0.0.1”的隐形陷阱
在mainwindow.cpp的数据库连接代码里,作者写的是db.setHostName("localhost")。这在绝大多数情况下没问题,但有一次我在一台装了MariaDB的机器上死活连不上,db.lastError().text()只显示“Unknown MySQL server host ‘localhost’”。查了半天才发现,localhost在MySQL协议里有特殊含义:它会尝试用Unix socket连接(Linux)或命名管道(Windows),而不是TCP/IP。而我的MariaDB配置禁用了socket。解决方案很简单:把"localhost"改成"127.0.0.1",强制走TCP/IP协议,瞬间连通。技巧:在开发阶段,一律用"127.0.0.1",上线后再根据服务器配置调整。
坑2:Qt Creator的“影子构建”导致路径混乱
很多新手在Qt Creator里直接点击“运行”按钮,发现程序找不到emarket.sql或mysql驱动。这是因为Qt Creator默认开启了“影子构建(Shadow Build)”,它会在项目目录外新建一个build-eMarket-Desktop_Qt_5_12_12_MinGW_64_bit-Debug这样的目录来存放编译产物。你的eMarket.exe在这个目录里,但它运行时的工作目录(QDir::currentPath())却是这个构建目录,而不是你源码所在的目录!所以QFile::exists("emarket.sql")会返回false。技巧:在Qt Creator的项目设置里,取消勾选“使用影子构建”,或者在代码里用QCoreApplication::applicationDirPath()获取exe所在目录,再拼接资源路径:QFile::exists(QCoreApplication::applicationDirPath() + "/emarket.sql")。
坑3:中文乱码的“三重门”
Qt桌面程序显示中文乱码,通常要过三关:
-数据库层:建表时CHARACTER SET utf8mb4,连接时db.setDatabaseName("emarket"); db.setConnectOptions("charset=utf8mb4");
-Qt编译层:.pro文件里加CODECFORTR = UTF-8,确保tr("商品")能正确翻译;
-Windows控制台层:在Qt Creator的“项目”->“运行”设置里,把“运行环境”里的CMD改为chcp 65001 &&(UTF-8代码页),否则qDebug()输出的中文在Application Output里也是乱码。
技巧:写一个checkEncoding()函数,在main()开头调用,用qDebug()打印几个中文字符串和QTextCodec::codecForLocale()->name(),三者都显示正常才算过关。
坑4:QSqlTableModel的“假删除”幻觉
项目里deleteProductBtn的槽函数是model->removeRow(index.row()),但这只是从Model的内存缓存里删掉了这一行,数据库里那条记录还在!如果你不调用model->submitAll(),重启程序后商品又回来了。很多新手以为点了删除就真没了,结果测试时一脸懵。技巧:在删除按钮的槽函数里,一定要加二次确认对话框,并在确认后立即调用model->submitAll(),且检查返回值:if (!model->submitAll()) { QMessageBox::critical(this, "错误", "删除失败:" + model->lastError().text()); }。
6. 项目价值再审视:它为什么值得你花3小时从头到尾敲一遍?
写到这里,你可能已经意识到,这套Qt5电子商城,其价值远不止于“一个能跑的Demo”。它是一份高度凝练的Qt工程实践手册,把一个桌面应用从0到1的全部关键节点,压缩在一个不到20个文件的工程里。你不需要去啃上千页的《Qt5开发实战》,也不用在Stack Overflow上大海捞针式地搜索“Qt MySQL connection failed”,因为所有答案,都已经以最朴素、最可执行的方式,写在了mainwindow.cpp的每一行注释里,藏在了emarket.sql的每一个DEFAULT关键字中,体现在了logindialog.ui里那个被精心设置过objectName的登录按钮上。
我建议你做的,不是下载解压后双击运行,而是关掉这篇文章,打开你的Qt Creator,新建一个空项目,然后,一个字一个字地,把logindialog.ui里的控件拖出来,把mainwindow.h里的类声明抄下来,把eMarket.pro里的配置一行行敲进去。当你亲手敲下QSqlDatabase::addDatabase("QMYSQL", "emarket"),当你第一次看到qDebug()输出的“MySQL driver loaded: true”,当你双击商品列表,那个你亲手写的ProductDetailDialog真的弹了出来——那一刻,Qt对你而言,就不再是PPT上的几个名词,而是一个你真正握在手里的、可以创造价值的工具。
它后续的扩展路径无比清晰:在ProductDetailDialog里加一个“加入购物车”按钮,背后用QSqlRelationalTableModel关联cart_items表;把LoginDialog的密码验证逻辑抽成一个AuthService单例类,为将来接入OAuth2.0留好接口;用QChart给商品销量做个统计图表;甚至把整个数据库层替换成SQLite,做成一个真正的便携式本地商城。所有这些,都建立在你对这个基础骨架的肌肉记忆之上。
最后分享一个小技巧:把这个项目当作你的“Qt技能仪表盘”。每当学到一个新知识点——比如QSettings存用户偏好、QNetworkAccessManager做HTTP请求、QThread做耗时操作——都试着在这个商城里找一个地方把它集成进去。你会发现,那些曾经晦涩的概念,突然就变得具体而生动。因为你知道,你不是在写一个抽象的“Hello World”,而是在给一个真实的、有血有肉的电子商城,添上一块新的砖。
本文还有配套的精品资源,点击获取
简介:一个开箱即用的Qt5 C++电子商城桌面应用,运行在Windows系统上,包含用户登录验证和主商城浏览两大功能模块。项目提供完整的工程文件:登录界面(logindialog.ui/.h/.cpp)和主窗口(mainwindow.ui/.h/.cpp),所有UI通过Qt Designer设计,逻辑使用信号槽机制连接;数据库采用MySQL,附带建表脚本(emarket.sql)和字段说明文档(电子商城数据库表.doc);编译配置齐全(eMarket.pro、Makefile.Debug/Release),已预生成moc文件和obj对象,可直接用qmake+mingw构建;打包含可执行文件eMarket.exe、三张实际界面截图(jpg)、README.md构建指引及mysql命令速查目录。适合想动手练Qt界面布局、QSqlDatabase数据库操作、用户会话控制和C++面向对象开发的学习者,也适合作为本科课程设计或毕业设计基础框架直接扩展。
本文还有配套的精品资源,点击获取
