
简介压缩包提供一套基于Qt与数据库技术开发的学生管理系统完整源码面向正在做课程设计、毕业设计或入门Qt开发的在校生与自学开发者。项目涵盖学生信息管理、管理员后台等常见前后台模块并将界面文件与业务代码分离可帮助读者理解Qt窗体设计、信号槽机制以及SQLite数据库读写等核心实践。包内共22个文件主要由6个.cpp源码、5个.ui界面、5个.h头文件构成另含.pro工程文件、.db数据库文件与授权说明等整体约48KB结构紧凑、便于直接导入Qt工程查看运行。当前已有2219人学习浏览适合将其作为同类管理系统的开发框架参考或按需修改扩展为其他业务场景。1. 课设标配的 Qt 学生管理系统为什么到手跑不起来GitHub 上一搜Qt 基于数据库的学生管理系统源码 是课程设计和毕业设计里最容易撞衫的题目。功能基本都是登录、学生信息增删改查、成绩录入、列表展示听起来不难但真正把源码解压下来能一次跑起来的人十个里也就两三个。问题很少出在业务逻辑上多半是死在三个地方Qt 环境装错版本、数据库驱动没选对、插件目录打包时被漏掉。这篇文章按我做课设辅导时的习惯把从环境搭建到发布打包的完整链路拆开讲里面包含可直接抄的代码和参数。适合三类人准备交课程设计的在校生、给 Qt 项目补数据库模块的转行者、以及接手别人源码但还没搞清它为什么报错的初学者。2. 用 Qt 5.15 SQLite 搭最小骨架从 .pro 到第一个能连库的窗口2.1 选型先想清楚SQLite 还是 MySQLQt 5 还是 Qt 6学生管理系统最常见的数据库组合有两种SQLite 和 MySQL。标题只写了数据库没说具体是哪一种拿到源码后第一件事就是把这两个变量确认清楚否则后面所有代码都是空转。SQLite 是单文件嵌入式数据库Qt 自带的 QSQLITE 驱动是编译进 Qt 库的不需要额外安装服务、不需要配端口账号程序在哪跑数据库文件就跟着走。这对课程设计来说几乎是压倒性的优势答辩时换一台电脑把 exe 和 .db 文件一起拷过去就能演示不用现场装 MySQL。MySQL 的优势在于数据量稍大、多客户端并发写入时更稳而且很多老师看到QSqlDatabase::addDatabase(QMYSQL)会认为项目更有含金量。但代价是一串隐性成本Qt 自带的 MySQL 驱动需要单独编译或用官方安装包里的对应版本目标机器上得装 MySQL 客户端库libmysql.dll程序里要处理连接失败、字符集、端口占用。为几十条学生记录付出这些成本我个人认为不值。版本上除非源码明确用了 CMake 和 Qt 6否则优先用 Qt 5.15 系列。Qt 6 把 widgets 模块拆得更细部分源码里QRegExp和QTextCodec的用法在 Qt 6 里直接编不过课设项目去改这些兼容性问题的成本远大于收益。下面是两张常见组合的实际体验对比。组合环境搭建发布难度答辩加分踩坑概率Qt 5.15 SQLite低驱动内置低单文件一般低Qt 5.15 MySQL中要驱动库高服务客户端库较高高Qt 6 SQLite中模块拆分低一般中Qt 6 MySQL中高较高高我一般接到这种源码第一眼只干两件事打开.pro文件看QT 那行再搜addDatabase后面的驱动名。这两个信息决定了后面的所有操作路径。2.2 建 qmake 工程三行 QMake 配置把 sql 模块加进来如果手头的源码包缺了.pro文件或者你想把散落的.cpp文件重新组织成一个干净工程用下面这个 qmake 工程文件做起点。注意路径不要带中文MinGW 对中文路径的支持一直是玄学报错时你根本想不到是路径问题。QT core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET StudentMS TEMPLATE app CONFIG c11 SOURCES \ main.cpp \ mainwindow.cpp \ loginwidget.cpp \ dbmanager.cpp HEADERS \ mainwindow.h \ loginwidget.h \ dbmanager.h FORMS \ mainwindow.ui \ loginwidget.ui DESTDIR $$PWD/bin关键在QT core gui sql这一行。sql模块是 Qt 的数据库抽象层少了它代码里所有#include QtSql都会在编译阶段报不存在。DESTDIR $$PWD/bin是我一直保留的习惯它把生成的 exe 统一放到工程根目录下的 bin 文件夹里后续打包时直接从 bin 拿文件不用去 debug 或 release 目录里翻。greaterThan(QT_MAJOR_VERSION, 4): QT widgets是 Qt 5 的固定写法目的是把 widgets 模块加进来你不用去改它记着是模板就好。如果源码里用了QMessageBox、QTableWidget这些类widgets 必须在。工程文件写好之后在 Qt Creator 里直接打开.pro文件即可自动生成影子构建目录。如果源码里没有任何.pro文件你可以新建一个空工程再把这些SOURCES和HEADERS手动加进右键项目 - Add Existing Files。2.3 初始化数据库用一个 SQL 脚本一次性建出三张表学生管理系统一般至少需要三张表学生表、课程表、成绩表。很多源码把建表语句写在main.cpp里每次启动都CREATE TABLE IF NOT EXISTS一遍这不算错但把 SQL 独立成脚本文件更利于你排查问题和答辩讲解。下面这段 SQL 是我常用的结构覆盖了学生管理系统最常见的字段。-- create_tables.sql CREATE TABLE IF NOT EXISTS t_student ( stu_id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT NOT NULL UNIQUE, name TEXT NOT NULL, gender TEXT DEFAULT 男, class_name TEXT, enroll_date TEXT ); CREATE TABLE IF NOT EXISTS t_course ( course_id INTEGER PRIMARY KEY AUTOINCREMENT, course_no TEXT NOT NULL UNIQUE, course_name TEXT NOT NULL, credit REAL DEFAULT 2.0 ); CREATE TABLE IF NOT EXISTS t_score ( score_id INTEGER PRIMARY KEY AUTOINCREMENT, stu_no TEXT NOT NULL, course_no TEXT NOT NULL, score REAL CHECK(score 0 AND score 100), UNIQUE(stu_no, course_no) );三个设计细节值得说明。stu_id INTEGER PRIMARY KEY AUTOINCREMENT是 SQLite 的自增主键每插入一条记录自动加一删除记录不会复用旧编号这能避免课设演示时出现学号跳变被老师追问的尴尬。stu_no TEXT NOT NULL UNIQUE是逻辑上的业务主键学号本就不该重复。成绩表用UNIQUE(stu_no, course_no)做联合唯一约束保证同一学生对同一课程只能有一条成绩记录这比在代码里先查询再判断重复要可靠得多数据库层面就把边界兜住了。执行这个脚本有两种常见做法。一种是在 Qt 里逐行读取脚本并执行另一种是直接在执行入口处手动建表。我习惯用下面这种读取脚本的方式因为这样数据库结构改动时不用重新编译程序// initdb.cpp 中的关键代码 bool initDatabase(const QString dbPath, const QString scriptPath) { QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(dbPath); if (!db.open()) { qWarning() open db failed: db.lastError().text(); return false; } QFile script(scriptPath); if (!script.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() script not found; return false; } QStringList statements QString(script.readAll()) .split(;, Qt::SkipEmptyParts); QSqlQuery query(db); for (const QString stmt : statements) { QString s stmt.trimmed(); if (s.isEmpty()) continue; if (!query.exec(s)) { qWarning() exec failed: query.lastError().text(); qWarning() statement: s; return false; } } return true; }按分号切分语句的方式在处理简单建表脚本时够用但如果 SQL 里出现了触发器或包含分号的字符串这种切法就会翻车所以在项目早期把数据结构定简单一点后患少很多。要注意的是addDatabase如果反复调用且不传连接名Qt 会提示duplicate connection name所以这个函数在整个程序生命周期里只调用一次后面通过QSqlDatabase::database()获取连接。3. 把增删改查写进界面登录、表格与提交策略的信号槽链路3.1 登录窗口先做带参数绑定的账号校验别再拼 SQL 字符串学生管理系统的登录窗口通常就是一个QDialog上面两个输入框加一个登录按钮。很多初学者在登录按钮的槽函数里写query.exec(SELECT * FROM t_user WHERE account account )这种字符串拼接在课设里能跑但换任何字符都可能导致 SQL 失效属于典型的“能跑但不可复制”的写法。用prepare加bindValue才是通用做法。// loginwidget.cpp 登录校验核心 bool checkLogin(const QString account, const QString pwd) { QSqlDatabase db QSqlDatabase::database(); // 取默认连接 QSqlQuery query(db); query.prepare(SELECT 1 FROM t_user WHERE account ? AND password ?); query.addBindValue(account); query.addBindValue(pwd); if (!query.exec()) { qWarning() login query failed: query.lastError().text(); return false; } return query.next(); // 有记录即为登录成功 }prepare的作用是让数据库驱动先解析 SQL 结构再用addBindValue把变量安全地填充进去。这样输入框里即使有单引号、分号也不会破坏 SQL 本身的语义这是数据库交互的基本底线。query.next()在有结果时返回 true配合SELECT 1只查一列效率上也比SELECT *好一点虽然课设数据量根本体现不出来。这里有个容易被忽略的细节QSqlDatabase::database()如果不传参数取的是默认连接名。所以addDatabase被调用过之后所有模块里取连接都应该用这种方式而不是再新建一个连接。用 Qt Designer 拖出来的.ui文件里密码输入框记得勾选echoMode为Password这能避免答辩时旁边的人一眼看到明文密码。3.2 主窗表格QSqlTableModel 比 QTableWidget 省一半代码主窗口的学生信息列表代码包里常见两种实现QTableWidget手写填充或QSqlTableModel直接绑定数据库。后者是正确选择因为QSqlTableModel把查询、刷新、排序这些活都替你干了手写QTableWidget的代价是每次增删改都要手动同步界面和数据库。// mainwindow.cpp 初始化表格模型 model new QSqlTableModel(this); model-setTable(t_student); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-setSort(1, Qt::AscendingOrder); // 按学号升序 model-setHeaderData(1, Qt::Horizontal, tr(学号)); model-setHeaderData(2, Qt::Horizontal, tr(姓名)); model-setHeaderData(3, Qt::Horizontal, tr(性别)); model-setHeaderData(4, Qt::Horizontal, tr(班级)); model-select(); // 真正执行 SELECT ui-tableView-setModel(model); ui-tableView-hideColumn(0); // 隐藏 stu_id 主键列 ui-tableView-setSelectionBehavior(QAbstractItemView::SelectRows); ui-tableView-setSelectionMode(QAbstractItemView::SingleSelection);setEditStrategy三个取值需要分清。OnFieldChange是编辑完一个单元格立即写库简单但会让撤销变得困难OnRowChange是切行时提交OnManualSubmit是完全手动提交。我建议用OnManualSubmit原因放在下一节讲。setSort(1, Qt::AscendingOrder)的参数是列索引如果表结构变了列号也要跟着改。hideColumn(0)隐藏自增主键列避免用户在界面上看到一堆无意义的 ID。筛选功能可以用一句model-setFilter(class_name LIKE %软件%)加上model-select()实现这是表格模型最省事的地方不需要自己写 SQL 到QSqlQuery。3.3 增删改的统一入口OnManualSubmit 模式下的提交与后悔药为什么坚持用OnManualSubmit因为学生管理系统里最常见的操作是连续录入多个学生边写边提交会让界面和数据库状态频繁同步一旦写错就要去数据库里改。而手动模式下用户可以先在表格里修改任意多行最后点一次“保存”统一提交发现改错了还能点“撤销”全部回滚。// 保存按钮槽函数 void MainWindow::on_btnSave_clicked() { if (!model-submitAll()) { QMessageBox::warning(this, 保存失败, model-lastError().text()); model-revertAll(); // 回滚未成功的修改 return; } statusBar()-showMessage(保存成功, 3000); } // 新增按钮槽函数 void MainWindow::on_btnAdd_clicked() { int row model-rowCount(); if (!model-insertRow(row)) { qWarning() insert row failed; return; } // 先给新行写入占位值避免 NOT NULL 约束拦截整行提交 model-setData(model-index(row, 1), 新学号); model-setData(model-index(row, 2), 新姓名); model-setData(model-index(row, 3), 男); } // 删除按钮槽函数 void MainWindow::on_btnDelete_clicked() { QItemSelectionModel *sel ui-tableView-selectionModel(); QModelIndex index sel-currentIndex(); if (!index.isValid()) { QMessageBox::information(this, 提示, 请先选中一行); return; } model-removeRow(index.row()); }insertRow之后立刻setData占位值这步很多人会漏。原因是t_student表的stu_no和name都带NOT NULL约束如果新行这些字段是空的submitAll会一次性把所有未提交的修改全部回滚包括你之前改好的其他行。先塞占位值等用户编辑真实数据再统一提交就不会踩这个坑。revertAll是这节里最重要的“后悔药”。只要还没点保存任何误删、误改都能靠它恢复。这也正是OnManualSubmit的完整价值提交前的一切都是可逆的提交后只能靠数据库备份自救所以我还建议在主窗口的状态栏常驻一行“未保存修改”的提示用model-isDirty()判断界面体验会明显上一个档次。4. 数据库连接与字段细节单例、乱码、自增 ID、时间字段的四个约定4.1 数据库连接用单例管理重复 addDatabase 会报 duplicate connection学生管理系统模块一多登录窗口要连接、主窗口要连接、成绩录入窗口也要连接如果每个窗口都自己写一次QSqlDatabase::addDatabase程序运行到第二次调用时必然报duplicate connection name qt_sql_default_connection这一步直接卡住后续所有开发。正确的做法是写一个数据库管理类让连接在整个进程里只初始化一次。// dbmanager.h #ifndef DBMANAGER_H #define DBMANAGER_H #include QSqlDatabase #include QSqlQuery #include QVariant class DbManager { public: static DbManager instance(); // 单例入口 bool open(const QString dbPath); // 打开 SQLite 数据库 QSqlDatabase db() const; // 获取当前连接 void close(); private: DbManager() default; DbManager(const DbManager ) delete; DbManager operator(const DbManager ) delete; QSqlDatabase m_db; bool m_opened false; }; #endif // DBMANAGER_H// dbmanager.cpp #include dbmanager.h #include QDebug DbManager DbManager::instance() { static DbManager mgr; // C11 起静态局部变量线程安全 return mgr; } bool DbManager::open(const QString dbPath) { if (m_opened) return true; m_db QSqlDatabase::addDatabase(QSQLITE); m_db.setDatabaseName(dbPath); if (!m_db.open()) { qWarning() open error: m_db.lastError().text(); return false; } m_opened true; return true; } QSqlDatabase DbManager::db() const { return m_db; } void DbManager::close() { if (m_opened) { m_db.close(); m_opened false; } }static DbManager mgr这个写法是 Meyer 单例函数第一次被调用的那一刻才会构造对象之后每次调用都拿同一个实例比用全局指针更安全也更干净。addDatabase只在open里执行一次后续所有窗口只要DbManager::instance().db()拿到同一个连接做查询不会再出现连接名冲突。注意QSqlDatabase是值类型但内部引用同一个底层连接所以把它返回给别的模块用是安全的只要记住不要在那个模块里再次调用QSqlDatabase::addDatabase即可。4.2 QSqlQuery 和 QSqlTableModel 的分工边界这两者的使用边界是初学者最容易混的地方。QSqlTableModel适合单表维护它帮你管理 SELECT、排序、过滤、分页代码量最小但它难以表达多表联查、分组统计这类复杂查询。QSqlQuery则适合一切自定义 SQL灵活但需要自己管理结果集。我平时定的分工规则很直接登录校验、成绩统计、按班级汇总人数这些用QSqlQuery学生信息列表、课程列表这种“一张表平铺展示”用QSqlTableModel。下面是一个用QSqlQuery做统计的例子这种场景塞给QSqlTableModel反而绕// 统计每个班级的人数与平均分 QString sql SELECT s.class_name, COUNT(*) AS cnt, AVG(sc.score) AS avg_score FROM t_student s LEFT JOIN t_score sc ON s.stu_no sc.stu_no GROUP BY s.class_name; QSqlQuery query(DbManager::instance().db()); if (!query.exec(sql)) { qWarning() stat query failed: query.lastError().text(); return; } while (query.next()) { QString cls query.value(0).toString(); int cnt query.value(1).toInt(); double avg query.value(2).toDouble(); qDebug() cls cnt avg; }COUNT(*)和AVG(sc.score)的结果可以用列索引取也可以用query.record().indexOf(avg_score)按列名取。我习惯按列名取因为统计字段一多索引容易对错位置调试时又看不出问题。另外QSqlTableModel默认生成的SELECT语句会把整张表捞出来学生几千条时可以接受但超过几万行就该用setFilter限定范围。课设数据量通常不大但提前养成习惯没坏处。4.3 中文乱码、自增 ID 回填、时间字段格式的三条参数经验这三条是我每次处理源码都要检查一遍的固定动作。第一条中文乱码。SQLite 默认以 UTF-8 存储Qt 的QString也是 UTF-16 内部表示两者经驱动转换后一般不乱。真正容易出问题的是 MySQL 场景连接建立后要立刻执行一句SET NAMES utf8mb4否则插入中文后表单显示成问号。如果你的源码目标库是 MySQL在open成功后加这个操作// 仅 MySQL 连接需要SQLite 忽略即可 QSqlQuery q(m_db); q.exec(SET NAMES utf8mb4);第二条自增 ID 回填。往t_student插入记录后如果你马上要用这条记录的自增主键关联别的工作靠select max(stu_id)去查是不严谨的正确做法是用lastInsertIdQSqlQuery q(DbManager::instance().db()); q.prepare(INSERT INTO t_student(stu_no, name) VALUES (?, ?)); q.addBindValue(20250001); q.addBindValue(张三); if (q.exec()) { int newId q.lastInsertId().toInt(); // 就能拿到本次插入的自增 ID qDebug() new stu_id: newId; }第三条时间字段格式。学生管理系统里最常用的是入学日期我用TEXT存yyyy-MM-dd字符串比如2025-09-01。这种做法排序天然是按时间顺序的显示在QTableView里不用额外转换。有人喜欢用INTEGER存 Unix 时间戳但课设场景里可视化和导出都不方便多此一举。5. 高频坑与排查插件找不到、Qt 库版本冲突、database is locked、缺 DLL5.1 qt.qpa.plugin: could not find the qt platform plugin linuxfb / windows现象双击 exe 后程序没有窗口控制台输出qt.qpa.plugin: could not find the qt platform plugin linuxfb。在 Windows 上则常见could not find the qt platform plugin windows。原因Qt 的窗口系统抽象层QPA需要加载平台插件而插件文件默认放在 Qt 安装目录的plugins/platforms下。发布时只拷贝了 exe 而漏掉整个platforms目录Qt 找不到对应插件就会直接退出。在嵌入式 Linux 板子上出现 linuxfb 报错通常是交叉编译后没有把libqlinuxfb.so放进目标机的插件路径。解决不要手动到处找插件直接用 Qt 自带的windeployqt工具补齐。命令后面会专门说这里先说手工兜底方案在 exe 同目录下建platforms文件夹把 Qt 安装目录里plugins/platforms/qwindows.dll拷进去。如果你的程序是在嵌入式设备上跑就把libqlinuxfb.so拷到/opt/qt/plugins/platforms/这类目标路径下并用export QT_QPA_PLATFORM_PLUGIN_PATH指向它。5.2 fatal: cannot mix incompatible Qt library (version ex50601) with this library现象编译链接阶段弹出fatal: cannot mix incompatible Qt library (version ex50601) with this library链接直接失败代码一个没看懂就结束了。原因这个报错含义是 Qt 头文件与 Qt 库文件版本不一致。ex50601 是 Qt 5.6.1 的版本编码常见于两种情况一是源码工程原本用其他版本 Qt 生成过 Makefile换环境后没有清理二是电脑上装了两套 Qt例如 5.12 和 5.6系统 PATH 先找到了其中一个的头文件而链接时又用了另一个的库文件。本质上是编译环境“混搭”了。解决先清掉旧的构建产物再做一次完整重建。Qt Creator 里执行清除项目 - 删除 build 目录 - qmake - 构建。手动编译时在工程目录跑make distclean再重新qmake。如果机器上存在多套 Qt在构建时用qmake -v确认当前生效的 qmake 属于哪个版本必要时把旧版本的 bin 目录从 PATH 里摘掉。5.3 程序运行中提示 database is locked现象窗口能打开但做一次增删改时弹出database is locked随后整个功能失灵关闭程序再打开又恢复正常。原因SQLite 对同一数据库文件的写操作是全局互斥的。常见触发场景是程序里建了两个连接同时打开同一个.db文件或者前一个QSqlDatabase没有被正确关闭事务又没提交数据库文件被锁住。另一个高频场景是用QSqlTableModel的OnFieldChange模式在界面持续编辑时底层频繁写库偶发锁冲突。解决确保全程序只有一个连接所有操作共用DbManager::instance().db()。检查代码里所有transaction是否有配套的commit出现异常时有没有rollback。还有一个容易忽略的点SQLite 文件放在 U 盘或网络共享目录时锁行为更严格有条件就先把数据库拷到本地硬盘再运行。若只是偶发可以在打开数据库后执行一句PRAGMA busy_timeout 3000;让连接在遇到锁时等待而不是立刻报错。5.4 release 包在同学电脑上起不来缺动态库与插件目录现象在自己电脑上构建好 release 版本点击运行一切正常。文件拷到同学电脑上双击没反应或者弹出“找不到 libgcc_s_dw2-1.dll / Qt5Core.dll”。原因Qt 程序默认动态链接 Qt 的几个核心库Qt5Core、Qt5Gui、Qt5Widgets、Qt5Sql目标机器如果没有安装 Qt 环境或这些 dll 不在 exe 同目录系统就找不到依赖。另一个隐蔽原因是把 debug 构建的 dll 当成 release 发了出去debug 版 dll 还会额外依赖Qt5Cored.dll和调试版 C 运行库目标机器上通常没有。解决完整的解决方案下一节讲这里先给出一个快速验证手段。打开 Qt 安装目录下的bin/windeployqt.exe建议用命令行而不是直接双击执行windeployqt StudentMS.exe --release工具会自动把 exe 依赖的 Qt dll 和 platform 插件复制到 exe 旁边。复制完成后整个文件夹再拷给同学基本就不会抛缺 dll 的错了。6. 发布与验证让带数据库的 exe 在裸机一次跑通6.1 windeployqt 完整命令与 qt.conf 兜底发布这一步不少帖子建议手动拷贝十几个 dll我的建议是不要干那件事太容易漏而且 Qt 版本一变文件名就跟着变不可持续。统一走windeployqt加一个qt.conf的组合拳。以下命令在 Qt 命令行环境Qt 5.15 MinGW 版自带的“Qt 5.15.0 (MinGW 8.1.0 64-bit)”里执行# 先建好干净发布目录避免混入调试产物 mkdir dist cp bin/StudentMS.exe dist/ # 自动补齐 Qt 运行库、platforms 插件、sqldrivers 插件 windeployqt dist/StudentMS.exe --release --dir dist/ # 查看输出是否包含 sqldrivers/qsqlite.dll ls dist/sqldrivers/--release参数很重要如果构建时用的是 debug 模式这里要改传--debug否则工具会按错模式复制运行库。--dir指定输出目录不加这个参数时 dll 会散在 exe 旁边和手动拷贝没什么区别。运行完检查dist/sqldrivers/里有没有qsqlite.dll这是程序能连 SQLite 的关键很多人漏了这一步导致程序打开后窗口一片空白或提示数据库打开失败。如果目标是 MySQLwindeployqt不会自动完成所有工作你还需要额外把 Qt 编译好的qsqlmysql.dll复制到sqldrivers/并把 MySQL 客户端的libmysql.dll放在 exe 同目录。这属于高成本组合前面选型时已经说过理由这里不再展开。还有一个兜底配置叫qt.conf放在 exe 同目录作用是告诉程序去哪里找插件和库防止 Qt 往系统目录或其他路径乱找[Paths] Plugins ./plugins当与你解压的发布目录结构保持exe plugins/platforms plugins/sqldrivers时这个配置能避免 90% 的“本地好好的换台电脑就崩”问题。6.2 交付前按这份清单过一遍能少跑一趟答辩现场发布不是“能跑”就结束了学生管理系统的交付至少要验证以下几个场景每个都对应一个真实翻车点验证项操作通过标准全新环境启动复制整个 dist 文件夹到无 Qt 环境的新电脑双击 exe 正常出登录窗口数据库写入录入一个学生重启程序数据仍在界面正常显示重复学号输入已存在的学号并保存弹出错误提示程序不崩溃连续操作连续新增、修改、删除多次后统一保存列表数据与数据库一致文件锁释放程序退出后直接删除 .db 文件文件可正常删除无占用提示其中“重复学号”这个验证点很多人不做因为建表时已经加了UNIQUE约束数据库层面会拦截。但程序层面如果没捕获lastError()界面会静默失败或者弹出英文错误用户根本不知道发生了什么这种体验在答辩演示时非常扣分。建议在提交前先检查if (!model-submitAll()) { QSqlError err model-lastError(); if (err.text().contains(UNIQUE)) { QMessageBox::warning(this, 保存失败, 学号不能重复); } else { QMessageBox::warning(this, 保存失败, err.text()); } model-revertAll(); }6.3 给 MySQL 版留好移植口三个改动点如果你决定在答辩前把 SQLite 换成 MySQL 增加含金量代码层面的改动其实可以收敛到很小。前提是你一开始就按前面说的结构把连接封装在DbManager里并让界面层全部走QSqlQuery和QSqlTableModel不直接碰数据库细节。第一个改动点是DbManager::open里的驱动名和连接参数。QSQLITE换成QMYSQL参数从单个文件路径变成setHostName、setPort、setUserName、setPassword、setDatabaseName五个。第二个改动点是建表脚本MySQL 的AUTOINCREMENT要改成AUTO_INCREMENTTEXT改成VARCHAR类型差异集中在这个脚本里。第三个改动点是字符集连接成功之后执行SET NAMES utf8mb4否则中文会乱掉。就这几处其余包括QSqlTableModel的用法、提交策略和发布方式都不用动。这样做的价值在于你可以在开发阶段用 SQLite 快速迭代答辩前几天再切到 MySQL两边都被验证过风险就可控了。最后说一个我自己的习惯每次拿到别人的 Qt 源码我从不急着编译第一件事是看.pro文件里的模块和版本第二件事是看数据库脚本第三件事才是跑界面。这三个顺序倒过来大概率会陷入“环境问题无穷无尽”的循环。先把依赖讲清楚再动手你会少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取