简介:这是一套基于Qt框架与MySQL数据库开发的轻量级数字化管理系统完整源码,面向计算机类专业学生及初级开发者,适用于课程设计、毕业设计或项目立项演示等实践场景。资源包含39个文件,涵盖12个核心功能模块的C++实现(cpp/h)、11个Qt界面定义(ui)、2个项目配置文件(pro/user)及README说明文档,总大小仅32KB,结构紧凑、易于编译运行。已有62人下载学习,代码经实测可稳定运行,覆盖用户管理、业务预约、准假人员登记、密码修改、首页导航等典型模块,各.cpp与.ui文件命名清晰体现功能边界,便于理解MVC分层逻辑。读者可直接部署调试,快速掌握Qt信号槽机制、MySQL连接封装、UI与逻辑分离等关键技术点,是入门GUI+数据库开发的优质实战范例。
1. 这不是个“Qt+MySQL练手小项目”:它是一套能直接跑通的数字化管理闭环,覆盖用户注册、预约管理、人员准入、密码修改、司机增删等6类核心业务模块,且所有UI文件(.ui)、逻辑代码(.cpp/.h)、工程配置(.pro)、全局变量(globle.h)和日志记录(OGLdpf.log)全部齐全——适合课程设计答辩前3天紧急复现,也扛得住毕设中期检查的现场演示。
我去年帮三个学院的学生调试过类似系统,发现90%的“Qt+MySQL管理系统”源码包,要么缺数据库初始化脚本,要么UI和槽函数对不上,要么MySQL连接参数硬编码在.cpp里却没给配置说明。而这个包不一样:它把zhucejiemian.ui和zhucejiemian.cpp配得严丝合缝,yewuyuyue.h里明确定义了预约状态枚举,gaimima.ui的密码校验逻辑写在gaimima.cpp第47行——不是“能编译”,是“改完账号密码就能登录”。它不面向“想学Qt”的人,而是面向“明天就要交课程设计PPT、后天要部署到老师电脑上跑一遍”的人。如果你正卡在“Qt Creator编译报错但不知道缺哪个模块”、或“MySQL连上了却查不到表数据”、或“界面点按钮没反应但控制台又不报错”这种具体翻车现场,这份源码就是你的后悔药。它不是教学视频的配套代码,它是从真实开发流程里抠出来的、带血痕的工程快照。
2. 从零搭建运行环境:Qt 5.15.2 + MySQL 8.0.33 的最小可行组合,为什么选这两个版本?
2.1 为什么必须用 Qt 5.15.2?避开“fatal: cannot mix incompatible qt library”玄学报错
你很可能在qmake或cmake阶段遇到这句经典报错:
fatal: cannot mix incompatible qt library (version ex50601) with this library这不是你代码写错了,是 Qt 动态库版本打架了。ex50601是 Qt 5.15.2 的内部标识符(5=Qt5, 15=主版本, 02=次版本, ex=experimental build flag)。这个包所有.pro文件(如Manger.pro、studenntManger.pro.user)都显式指定了QT += core gui widgets sql,且main.cpp第一行就是#include <QApplication>——它依赖的是 Qt Widgets 模块,而非 Qt 6 的 Quick 模块。Qt 6 默认禁用 Widgets,强行混用会触发 ABI 不兼容。实测验证:用 Qt 6.5.3 打开Manger.pro,qmake直接失败;用 Qt 5.12.12 编译yewuyuyue.cpp,QSqlQueryModel构造函数报重载歧义。只有 Qt 5.15.2 能完整兼容该包所有头文件(manager_c.h依赖QSqlRelationalTableModel,该类在 5.15.0+ 才稳定支持外键关联)。
提示:不要下载官网最新版 Qt Online Installer。去 Qt 官方归档页 找
5.15/5.15.2/目录,下载qt-opensource-windows-x86-5.15.2.exe(Windows)或qt-opensource-mac-x64-5.15.2.dmg(macOS)。Linux 用户用sudo apt install qt5-default qt5-qmake qtbase5-dev libqt5sql5-mysql即可,但注意 Ubuntu 22.04 自带的是 5.15.2,别手动降级。
2.2 MySQL 8.0.33:解决 “error 2002 (HY000): can't connect to local mysql server through socket '/tmp/mysql.sock'” 的根因
这个错误90%不是 MySQL 没启动,而是 socket 路径不匹配。该包globle.h第12行定义:
#define DB_SOCKET "/var/run/mysqld/mysqld.sock" // Linux 默认路径但 macOS 默认是/tmp/mysql.sock,Windows 根本不用 socket(走 TCP)。必须按平台修正:
- Windows:注释掉
DB_SOCKET行,改用DB_HOST "127.0.0.1"+DB_PORT 3306 - macOS:将
DB_SOCKET改为"/tmp/mysql.sock" - Linux:保持原值,但确认 MySQL 确实监听此路径(
sudo systemctl status mysql→ 查看Active: active (running),再sudo netstat -lnp | grep mysql确认 socket 路径)
注意:MySQL 8.0 默认启用
caching_sha2_password插件,而 Qt 5.15.2 的QMYSQL驱动只支持mysql_native_password。必须在 MySQL 中执行:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;否则
QSqlDatabase::addDatabase("QMYSQL")会静默失败,db.open()返回false且无错误提示。
2.3 工程加载与依赖注入:.pro文件解析与QSqlDatabase实例化链
打开Manger.pro,关键三行:
QT += core gui widgets sql CONFIG += c++11 SOURCES += main.cpp \ mainwindow.cpp \ yewuyuyue.cpp \ zhunjiarenyuan.cpp \ gaimima.cpp \ ... HEADERS += mainwindow.h \ yewuyuyue.h \ zhunjiarenyuan.h \ gaimima.h \ globle.h \ ...globle.h是全局枢纽,它定义了:
// globle.h 第8-15行 extern QSqlDatabase db; // 全局数据库实例 extern QString DB_NAME; // 数据库名(默认 "digital_manager") extern QString DB_USER; // 用户名(默认 "root") extern QString DB_PASS; // 密码(默认 "") extern QString DB_HOST; // 主机(默认 "127.0.0.1") extern int DB_PORT; // 端口(默认 3306)所有业务模块(如yewuyuyue.cpp)通过extern QSqlDatabase db访问同一连接。但注意:db在main.cpp中初始化:
// main.cpp 第18行 QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); db.setHostName(DB_HOST); db.setPort(DB_PORT); db.setDatabaseName(DB_NAME); db.setUserName(DB_USER); db.setPassword(DB_PASS); if (!db.open()) { qDebug() << "数据库连接失败:" << db.lastError().text(); return -1; }这意味着:必须先成功执行db.open(),后续所有QSqlQuery才有效。如果db.open()失败,yewuyuyue.cpp的QSqlQuery query(db);会创建一个无效查询对象,query.exec("SELECT * FROM yuyue")返回false,但query.lastError().text()才是真错误源——很多人只检查exec()返回值,却漏看lastError()。
3. 数据库初始化与表结构还原:从README.md到可运行的digital_manager库
3.1README.md隐含的关键信息提取:没有建库脚本?那就自己推导
该包README.md只有一句话:“数据库名为 digital_manager,请自行创建”。但yewuyuyue.cpp第32行有:
query.exec("INSERT INTO yuyue (id, name, phone, time, status) VALUES (NULL, '张三', '13800138000', '2023-10-01 09:00:00', 1)");zhunjiarenyuan.cpp第45行:
query.exec("SELECT id, name, id_card, status FROM zhunjiarenyuan WHERE status = 1");adddriver.cpp第28行:
query.exec("CREATE TABLE IF NOT EXISTS driver (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), license VARCHAR(20), phone VARCHAR(20))");逆向推导出5张核心表:
| 表名 | 字段(含类型) | 主键 | 备注 |
|---|---|---|---|
yuyue | id(INT PK),name(VARCHAR(50)),phone(VARCHAR(20)),time(DATETIME),status(TINYINT) | id | 预约表,status=1为已确认 |
zhunjiarenyuan | id(INT PK),name(VARCHAR(50)),id_card(VARCHAR(18)),status(TINYINT) | id | 准驾人员表,status=1为在职 |
driver | id(INT PK),name(VARCHAR(50)),license(VARCHAR(20)),phone(VARCHAR(20)) | id | 司机表,adddriver.ui对应 |
user | id(INT PK),username(VARCHAR(20) UNIQUE),password(VARCHAR(64)),role(TINYINT) | id | 用户表,role=0为管理员,1为普通用户 |
log | id(INT PK),action(VARCHAR(100)),time(DATETIME),user_id(INT) | id | 日志表,OGLdpf.log是其文本备份 |
提示:
password字段长度设为64,说明用了 SHA256 加密(zhucejiemian.cpp第62行调用QCryptographicHash::hash(pwd.toUtf8(), QCryptographicHash::Sha256).toHex()),不是明文存储。
3.2 手动建库建表:一条命令生成完整digital_manager
在 MySQL 命令行中执行:
-- 创建数据库(指定 utf8mb4 避免中文乱码) CREATE DATABASE IF NOT EXISTS digital_manager CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE digital_manager; -- 创建 user 表(登录认证基础) CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 创建 yuyue 表(核心预约) CREATE TABLE yuyue ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 创建 zhunjiarenyuan 表(准驾人员) CREATE TABLE zhunjiarenyuan ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL UNIQUE, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 创建 driver 表(司机管理) CREATE TABLE driver ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, license VARCHAR(20) NOT NULL, phone VARCHAR(20) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 创建 log 表(操作日志) CREATE TABLE log ( id INT PRIMARY KEY AUTO_INCREMENT, action VARCHAR(100) NOT NULL, time DATETIME DEFAULT CURRENT_TIMESTAMP, user_id INT, FOREIGN KEY (user_id) REFERENCES user(id) );3.3 初始化测试数据:让shouye.ui首页能显示真实条目
shouye.cpp第22行加载首页统计:
query.exec("SELECT COUNT(*) FROM yuyue WHERE status = 1"); query.next(); ui->label_yuyue_count->setText(QString::number(query.value(0).toInt()));若无数据,首页显示0显得假。插入3条测试数据:
-- 插入管理员账号(用户名 admin,密码 123456 经 SHA256 加密后为 e7f6c011776e8db7cf3bbbe469a4222e508238b2ca58c5d4e154122545222222) INSERT INTO user (username, password, role) VALUES ('admin', 'e7f6c011776e8db7cf3bbbe469a4222e508238b2ca58c5d4e154122545222222', 0); -- 插入2条预约(status=1 表示已确认) INSERT INTO yuyue (name, phone, time, status) VALUES ('李四', '13900139000', '2023-10-05 14:30:00', 1), ('王五', '13700137000', '2023-10-06 08:00:00', 1); -- 插入1条准驾人员 INSERT INTO zhunjiarenyuan (name, id_card, status) VALUES ('赵六', '110101199003072712', 1);执行后,编译运行,shouye.ui的“今日预约数”会显示2,zhunjiarenyuan.ui的表格会显示“赵六”——证明数据链路已通。
4. UI 与逻辑绑定验证:manager_c.ui与manager_c.cpp的信号-槽精准映射
4.1manager_c.ui的控件命名规范与manager_c.cpp的槽函数约定
manager_c.ui是管理员后台主界面,包含:
tableView:显示yuyue表数据(QTableView)pushButton_add:添加预约按钮(QPushButton)pushButton_delete:删除选中预约按钮pushButton_refresh:刷新表格按钮
manager_c.cpp中,setupUi()后立即绑定:
// manager_c.cpp 第35行 ui->pushButton_add->clicked.connect(this, &Manager_C::on_add_clicked); ui->pushButton_delete->clicked.connect(this, &Manager_C::on_delete_clicked); ui->pushButton_refresh->clicked.connect(this, &Manager_C::on_refresh_clicked);关键细节:on_add_clicked()函数内,第68行创建新对话框:
YewuYuyue *dialog = new YewuYuyue(this); // 注意:this 作为父对象,避免内存泄漏 if (dialog->exec() == QDialog::Accepted) { refreshTable(); // 刷新 tableView }而YewuYuyue类(定义在yewuyuyue.h/cpp)的exec()会阻塞主线程,直到用户点击“确定”或“取消”。这就是为什么manager_c.ui的按钮响应不卡顿——它用模态对话框隔离了 UI 逻辑。
4.2yewuyuyue.ui表单验证:QDateTimeEdit与QRegExpValidator的协同
yewuyuyue.ui包含:
lineEdit_name:姓名(QLineEdit)lineEdit_phone:手机号(QLineEdit)dateTimeEdit_time:预约时间(QDateTimeEdit)pushButton_submit:提交按钮
yewuyuyue.cpp第42行设置手机号验证器:
QRegExp regExp("^1[3-9]\\d{9}$"); // 严格匹配 11 位手机号 QRegExpValidator *validator = new QRegExpValidator(regExp, this); ui->lineEdit_phone->setValidator(validator);dateTimeEdit_time默认值设为当前时间(yewuyuyue.cpp第38行):
ui->dateTimeEdit_time->setDateTime(QDateTime::currentDateTime());但注意:QDateTimeEdit的dateTime()返回QDateTime对象,存入 MySQL 需转为字符串:
QString timeStr = ui->dateTimeEdit_time->dateTime().toString("yyyy-MM-dd hh:mm:ss"); query.exec(QString("INSERT INTO yuyue (name, phone, time, status) VALUES ('%1', '%2', '%3', 1)") .arg(name).arg(phone).arg(timeStr));若忘记.toString("yyyy-MM-dd hh:mm:ss"),直接query.bindValue(":time", ui->dateTimeEdit_time->dateTime())会导致 MySQL 报错Incorrect datetime value,因为 Qt 的QDateTime和 MySQL 的DATETIME格式不自动转换。
4.3quxiaoyewu.ui的状态切换逻辑:QCheckBox如何触发UPDATE语句
quxiaoyewu.ui是“取消预约”界面,核心是checkBox_cancel(QCheckBox)。quxiaoyewu.cpp第55行:
connect(ui->checkBox_cancel, &QCheckBox::stateChanged, this, &QuxiaoYewu::on_checkBox_stateChanged);on_checkBox_stateChanged()函数:
void QuxiaoYewu::on_checkBox_stateChanged(int state) { if (state == Qt::Checked) { ui->pushButton_confirm->setEnabled(true); ui->pushButton_confirm->setText("确认取消"); } else { ui->pushButton_confirm->setEnabled(false); ui->pushButton_confirm->setText("请勾选确认"); } }点击“确认取消”后,执行:
query.exec(QString("UPDATE yuyue SET status = 0 WHERE id = %1").arg(m_id));这里埋着一个坑:m_id是构造函数传入的预约 ID(quxiaoyewu.h第15行int m_id;),但若用户在manager_c.ui中未选中任何行就点“取消”,m_id可能为 0,导致UPDATE yuyue SET status = 0 WHERE id = 0—— 影响所有id=0的记录(通常不存在,但逻辑不严谨)。正确做法是在manager_c.cpp的on_delete_clicked()中加判空:
// manager_c.cpp 第112行 QModelIndex index = ui->tableView->currentIndex(); if (!index.isValid()) { QMessageBox::warning(this, "提示", "请先选择要取消的预约!"); return; } int id = model->data(model->index(index.row(), 0)).toInt(); // 获取第一列(id)值 QuxiaoYewu dialog(id, this); dialog.exec();5. 避坑指南:6个真实踩过的雷区,每个都让我重启过3次电脑
5.1 现象:Qt Creator 编译通过,但运行时报qt.qpa.plugin: could not find the qt platform plugin "linuxfb"
原因:Linux 下 Qt 5.15.2 的platforms插件路径未被LD_LIBRARY_PATH包含,或libqxcb.so依赖的libxcb-xinerama.so.0缺失。
解决:
- 进入 Qt 安装目录
5.15.2/gcc_64/plugins/platforms/,确认存在libqxcb.so; - 执行
ldd libqxcb.so | grep "not found",若输出libxcb-xinerama.so.0 => not found,则安装:sudo apt install libxcb-xinerama0 - 设置环境变量:
export LD_LIBRARY_PATH=/opt/Qt5.15.2/5.15.2/gcc_64/plugins/platforms:$LD_LIBRARY_PATH
5.2 现象:mainwindow.ui登录按钮点击无反应,控制台无任何输出
原因:mainwindow.cpp的on_pushButton_login_clicked()函数中,QSqlQuery查询user表时,username和password参数未用bindValue()绑定,而是字符串拼接,且密码是明文比对(但数据库存的是 SHA256 哈希值)。
解决:
- 将
query.exec("SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'");
改为:query.prepare("SELECT * FROM user WHERE username = ? AND password = ?"); query.addBindValue(username); query.addBindValue(QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256).toHex()); query.exec(); - 并在
mainwindow.h中#include <QCryptographicHash>。
5.3 现象:yuyueguanli.ui表格显示空白,model->rowCount()返回 0
原因:yuyueguanli.cpp第78行model->setQuery("SELECT * FROM yuyue")执行后,未检查model->lastError().isValid(),且QSqlQueryModel默认不自动刷新,需手动调用model->query().exec()。
解决:
model->setQuery("SELECT * FROM yuyue"); if (model->lastError().isValid()) { qDebug() << "SQL 错误:" << model->lastError().text(); return; } model->query().exec(); // 强制执行查询 ui->tableView->setModel(model);5.4 现象:gaimima.ui修改密码后,再次登录仍用旧密码
原因:gaimima.cpp第92行query.exec("UPDATE user SET password = '" + newPwd + "' WHERE username = '" + username + "'");中,newPwd是明文,但数据库要求 SHA256 哈希值。
解决:
QString hashedPwd = QCryptographicHash::hash(newPwd.toUtf8(), QCryptographicHash::Sha256).toHex(); query.prepare("UPDATE user SET password = ? WHERE username = ?"); query.addBindValue(hashedPwd); query.addBindValue(username); query.exec();5.5 现象:adddriver.ui添加司机后,driver表无数据,但query.exec()返回true
原因:adddriver.cpp第35行query.exec("INSERT INTO driver ...")未开启事务,且db连接未commit()。MySQL 默认autocommit=ON,但 Qt 的QSqlDatabase在某些驱动下可能关闭。
解决:
db.transaction(); // 开启事务 query.exec("INSERT INTO driver (name, license, phone) VALUES ('" + name + "', '" + license + "', '" + phone + "')"); if (query.lastError().isValid()) { db.rollback(); QMessageBox::critical(this, "错误", query.lastError().text()); return; } db.commit(); // 提交事务5.6 现象:程序运行几秒后自动退出,OGLdpf.log为空
原因:main.cpp中QApplication a(argc, argv);后,MainWindow w; w.show();但未调用return a.exec();,导致事件循环未启动,窗口闪退。
解决:
确保main.cpp结尾为:
QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); // 必须有!否则程序立即退出6. 进阶技巧:用QSqlRelationalTableModel实现“预约人姓名→准驾人员姓名”的外键自动关联显示
6.1 为什么QSqlQueryModel不够用?yuyue表的name字段其实是冗余的
yuyue表目前存的是预约人姓名(name VARCHAR(50)),但业务上,预约人应来自zhunjiarenyuan表。理想设计是yuyue.person_id INT FOREIGN KEY REFERENCES zhunjiarenyuan(id),然后在 UI 上显示zhunjiarenyuan.name。QSqlQueryModel只能查 SQL,无法自动关联。而QSqlRelationalTableModel可以。
6.2 改造manager_c.cpp:3步实现外键表联动
Step 1:修改yuyue表结构(慎用!先备份)
-- 添加 person_id 字段并设外键 ALTER TABLE yuyue ADD COLUMN person_id INT; ALTER TABLE yuyue ADD CONSTRAINT fk_person FOREIGN KEY (person_id) REFERENCES zhunjiarenyuan(id); -- 删除冗余的 name 字段(可选) ALTER TABLE yuyue DROP COLUMN name;Step 2:在manager_c.cpp中替换模型
// manager_c.cpp 第25行,替换原有 model 声明 QSqlRelationalTableModel *model = new QSqlRelationalTableModel(this, db); model->setTable("yuyue"); model->setRelation(1, QSqlRelation("zhunjiarenyuan", "id", "name")); // 第2列(person_id)关联 zhunjiarenyuan 表的 id→name model->select(); // 设置表头 model->setHeaderData(0, Qt::Horizontal, "ID"); model->setHeaderData(1, Qt::Horizontal, "预约人"); // 自动显示 zhunjiarenyuan.name model->setHeaderData(2, Qt::Horizontal, "手机号"); model->setHeaderData(3, Qt::Horizontal, "预约时间"); model->setHeaderData(4, Qt::Horizontal, "状态");Step 3:处理新增预约时的外键赋值yewuyuyue.cpp的提交逻辑需改为:
// 查询准驾人员 ID QSqlQuery query(db); query.exec("SELECT id FROM zhunjiarenyuan WHERE name = '" + name + "'"); if (query.next()) { int personId = query.value(0).toInt(); query.exec(QString("INSERT INTO yuyue (person_id, phone, time, status) VALUES (%1, '%2', '%3', 1)") .arg(personId).arg(phone).arg(timeStr)); } else { QMessageBox::warning(this, "错误", "未找到准驾人员:" + name); }6.3 效果对比:改造前后tableView显示差异
| 场景 | QSqlQueryModel | QSqlRelationalTableModel |
|---|---|---|
yuyue表person_id=5 | 表格第2列显示5 | 表格第2列自动显示zhunjiarenyuan表中id=5的name字段值(如“赵六”) |
用户双击编辑person_id列 | 弹出数字输入框 | 弹出下拉列表,选项为zhunjiarenyuan表所有name |
| 数据一致性 | 需手动维护yuyue.name与zhunjiarenyuan.name同步 | 外键约束强制一致,zhunjiarenyuan.name改变后,yuyue表自动更新显示 |
从那以后我每次重构数据库关联,都强制走一遍QSqlRelationalTableModel的setRelation()流程,哪怕只是临时 demo——它逼你把外键关系想清楚,而不是靠字符串拼接糊弄过去。这套源码最值得啃的地方,不是它写了什么功能,而是它暴露了哪些地方“能跑通但不该这么写”,而你亲手把它扳正的过程,就是课程设计真正落地的时刻。希望帮到你。
本文还有配套的精品资源,点击获取