2小时精通Qt搞企业项目,拆解软件必用7个核心模块
【技术交流群说明】
欢迎关注《Qt开发宝典》公众号
每日分享Linux C++/Qt实战干货与技术笔记
📌加入技术交流QQ群:895876809(纯技术,无广告)
📌进群获取C++全技术栈开发资料(含Qt/后端/嵌入式等)
一起学习,共同进步!
👇以下全文全是硬核干货。文章后面有Gitee仓库(工程项目源码)。
项目采用QWidget桌面界面、cmake工程管理和SQLite本地数据库,围绕汽配行业常见业务链路,提供从基础资料、库存、采购、销售、退货、价格、财务、报表,到用户权限、系统参数、CSV导入导出、数据库备份恢复等一整套模块化能力。
一、【工程项目运行】



























本项目定位为一个桌面端企业业务信息系统工程产品,聚焦汽配行业,覆盖如下典型业务域:
– 基础资料管理
– 采购与销售管理
– 库存与价格管理
– 财务协同
– 用户权限与系统治理
– 制造、CRM、协同、AI、移动扩展能力
该项目既可作为可运行的汽配行业管理系统原型,也可作为企业管理软件的二次开发。
2. 功能特性
【核心业务功能】
– 登录与账号校验
– 主界面导航与权限控制
– 配件、供应商、客户、仓库资料管理
– 库存管理、库存流水、库存预警
– 采购订单、销售订单、退货管理
– POS 零售
– 客户阶梯价 / 特价体系
– 财务应收应付、收付款、对账
– 报表与分析
– 用户、角色、权限、审计日志
– 系统参数、CSV 导入导出、数据库备份恢复
– 测试数据生成
【扩展功能】
– BOM、工单、质检、MRP 占位、追溯链
– CRM 跟进与售后工单
– 多公司
– 供应链协同伙伴信息
– AI 参数配置
– 移动设备登记
3. 技术栈
– 开发语言:C++
– UI 框架:Qt 6 Widgets
– 数据库:SQLite
– 构建系统:qmake
– 并发处理:QThread
– 资源管理:Qt Resource System
3. 工程文件中声明的Qt模块依赖
– core
– gui
– widgets
– sql
– concurrent
4. 主界面主要模块
– 财务管理
– 基础数据-配件
– 基础数据-供应商
– 基础数据-客户
– 基础数据-仓库
– 库存管理
– 高级库存
– 采购管理
– 销售管理
– 采购/销售退货
– POS 零售
– 客户价/阶梯价
– 报表与分析
– 生产制造
– CRM/售后
– 协同/多公司/AI/移动
– 系统管理-用户/角色
– 系统管理-操作日志
– 系统工具与参数
6. 工程目录结构
APIMS/├─ APIMS.pro├─ README.md├─ 需求分析.txt├─ src/│ ├─ main.cpp│ ├─ app.h│ ├─ app.cpp│ ├─ images.qrc│ ├─ images/│ │ └─ logo.ico│ ├─ db/│ │ ├─ db.h│ │ ├─ db.cpp│ │ ├─ schema.h│ │ ├─ schema.cpp│ │ ├─ migrations.h│ │ └─ migrations.cpp│ ├─ util/│ │ ├─ csv.h / csv.cpp│ │ ├─ fs.h / fs.cpp│ │ ├─ paging.h / paging.cpp│ │ ├─ permissions.h / .cpp│ │ ├─ strings.h / .cpp│ │ ├─ validators.h / .cpp│ │ └─ test_data_generator.*│ └─ ui/│ ├─ login_dialog.*│ ├─ main_window.*│ ├─ widgets/│ │ ├─ confirm_dialog.*│ │ ├─ paged_table.*│ │ └─ toast.*│ └─ pages/│ ├─ base_crud_page.*│ ├─ parts_page.*│ ├─ suppliers_page.*│ ├─ customers_page.*│ ├─ warehouses_page.*│ ├─ stock_page.*│ ├─ inventory_advanced_page.*│ ├─ purchase_orders_page.*│ ├─ sales_orders_page.*│ ├─ returns_page.*│ ├─ pos_page.*│ ├─ pricing_page.*│ ├─ reports_page.*│ ├─ finance_page.*│ ├─ manufacturing_page.*│ ├─ crm_page.*│ ├─ extensions_page.*│ ├─ users_roles_page.*│ ├─ audit_log_page.*│ └─ system_tools_page.*└─ .qtcreator/
1. db.h
#pragma once#include<QString>class QSqlDatabase;namespace Db {QString defaultDbPath();boolopenDefault();/// 关闭并移除默认连接(用于备份恢复前释放 SQLite 文件锁)voidcloseDefault();QSqlDatabase db();boolexec(const QString& sql, QString* err = nullptr);boolpragmaFastMode(QString* err = nullptr);boolaudit(qint64 userId, const QString& action, const QString& entity, qint64 entityId, const QString& detail, QString* err = nullptr);}
2. db.cpp
#include"db.h"#include<QDir>#include<QFileInfo>#include<QMetaType>#include<QSqlDatabase>#include<QSqlError>#include<QSqlQuery>#include<QStandardPaths>#include<QVariant>static const char* kConnName = "apims_default";QString Db::defaultDbPath(){const QString base = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);QDir().mkpath(base);return QDir(base).filePath("apims.sqlite3");}QSqlDatabase Db::db(){return QSqlDatabase::database(kConnName);}boolDb::exec(const QString& sql, QString* err){QSqlQuery q(Db::db());if (!q.exec(sql)) {if (err) *err = q.lastError().text();return false;}return true;}boolDb::pragmaFastMode(QString* err){const QStringList pragmas = {"PRAGMA foreign_keys = ON;","PRAGMA journal_mode = WAL;","PRAGMA synchronous = NORMAL;","PRAGMA temp_store = MEMORY;","PRAGMA mmap_size = 268435456;","PRAGMA cache_size = -200000;","PRAGMA busy_timeout = 5000;"};for (const auto& p : pragmas) {if (!Db::exec(p, err)) return false;}return true;}boolDb::openDefault(){if (QSqlDatabase::contains(kConnName)) {auto d = QSqlDatabase::database(kConnName);if (d.isOpen()) return true;}auto d = QSqlDatabase::addDatabase("QSQLITE", kConnName);d.setDatabaseName(defaultDbPath());if (!d.open()) {return false;}QString err;if (!pragmaFastMode(&err)) {return false;}return true;}voidDb::closeDefault(){if (!QSqlDatabase::contains(kConnName)) return;{QSqlDatabase d = QSqlDatabase::database(kConnName);if (d.isOpen()) d.close();}QSqlDatabase::removeDatabase(kConnName);}boolDb::audit(qint64 userId, const QString& action, const QString& entity, qint64 entityId, const QString& detail, QString* err){QSqlQuery q(Db::db());q.prepare("INSERT INTO audit_log(ts, user_id, action, entity, entity_id, detail) VALUES(datetime('now'), ?, ?, ?, ?, ?);");q.addBindValue(userId ? QVariant::fromValue<qint64>(userId) : QVariant(QMetaType::fromType<qlonglong>()));q.addBindValue(QVariant(action));q.addBindValue(QVariant(entity));q.addBindValue(entityId ? QVariant::fromValue<qint64>(entityId) : QVariant(QMetaType::fromType<qlonglong>()));q.addBindValue(QVariant(detail));if (!q.exec()) {if (err) *err = q.lastError().text();return false;}return true;}
3. main_window.h
#pragma once#include<QMainWindow>class QListWidget;class QStackedWidget;class QWidget;class MainWindow final : public QMainWindow {Q_OBJECTpublic:MainWindow(qint64 userId, const QString& displayName, QWidget* parent = nullptr);private:voidbuildUi();voidwire();voidbuildMenu();QWidget* createPage(int stackIndex);voidensurePageLoaded(int stackIndex);private slots:voidonGenerateDemoData();private:qint64 m_userId = 0;QString m_displayName;QListWidget* m_nav = nullptr;QStackedWidget* m_stack = nullptr;};
#include"main_window.h"#include<QGroupBox>#include<QHBoxLayout>#include<QInputDialog>#include<QListWidget>#include<QMenu>#include<QMenuBar>#include<QProgressDialog>#include<QStackedWidget>#include<QStatusBar>#include<QWidget>#include<QVBoxLayout>#include<QObject>#include<QThread>#include"db/db.h"#include"ui/pages/audit_log_page.h"#include"ui/pages/crm_page.h"#include"ui/pages/customers_page.h"#include"ui/pages/extensions_page.h"#include"ui/pages/finance_page.h"#include"ui/pages/inventory_advanced_page.h"#include"ui/pages/manufacturing_page.h"#include"ui/pages/parts_page.h"#include"ui/pages/pos_page.h"#include"ui/pages/pricing_page.h"#include"ui/pages/purchase_orders_page.h"#include"ui/pages/reports_page.h"#include"ui/pages/returns_page.h"#include"ui/pages/sales_orders_page.h"#include"ui/pages/stock_page.h"#include"ui/pages/suppliers_page.h"#include"ui/pages/system_tools_page.h"#include"ui/pages/users_roles_page.h"#include"ui/pages/warehouses_page.h"#include"ui/widgets/toast.h"#include"util/permissions.h"#include"util/test_data_generator.h"namespace {struct NavEntry {const char* label;const char* key;int stackIndex;};constexpr int kPageCount = 19;} // namespaceMainWindow::MainWindow(qint64 userId, const QString& displayName, QWidget* parent): QMainWindow(parent), m_userId(userId), m_displayName(displayName) {setWindowTitle(QString("APIMS - %1").arg(displayName));setWindowIcon(QIcon(":/new/prefix1/images/logo.ico"));resize(1280, 800);buildUi();buildMenu();wire();statusBar()->showMessage("就绪");}voidMainWindow::buildUi(){auto* central = new QWidget(this);auto* root = new QHBoxLayout;root->setContentsMargins(8, 8, 8, 8);m_nav = new QListWidget;m_nav->setFixedWidth(220);m_stack = new QStackedWidget;for (int i = 0; i < kPageCount; ++i) {m_stack->addWidget(new QWidget);}static const NavEntry kNav[] = {{ "财务管理", "nav/finance", 0 },{ "基础数据-配件", "nav/parts", 1 },{ "基础数据-供应商", "nav/suppliers", 2 },{ "基础数据-客户", "nav/customers", 3 },{ "基础数据-仓库", "nav/warehouses", 4 },{ "库存管理", "nav/stock", 5 },{ "高级库存", "nav/inventory_advanced", 6 },{ "采购管理", "nav/purchase", 7 },{ "销售管理", "nav/sales", 8 },{ "采购/销售退货", "nav/returns", 9 },{ "POS 零售", "nav/pos", 10 },{ "客户价/阶梯价", "nav/pricing", 11 },{ "报表与分析", "nav/reports", 12 },{ "生产制造", "nav/manufacturing", 13 },{ "CRM/售后", "nav/crm", 14 },{ "协同/多公司/AI/移动", "nav/extensions", 15 },{ "系统管理-用户/角色", "nav/users", 16 },{ "系统管理-操作日志", "nav/audit", 17 },{ "系统工具与参数", "nav/system_tools", 18 },};for (const auto& e : kNav) {if (!Perms::canAccessNav(m_userId, QString::fromUtf8(e.key))) continue;auto* item = new QListWidgetItem(QString::fromUtf8(e.label));item->setData(Qt::UserRole, e.stackIndex);m_nav->addItem(item);}if (m_nav->count() == 0) {auto* item = new QListWidgetItem("(无可用导航权限)");item->setFlags(item->flags() & ~Qt::ItemIsEnabled);m_nav->addItem(item);} else {m_nav->setCurrentRow(0);auto* it = m_nav->item(0);if (it) {const int idx = it->data(Qt::UserRole).toInt();ensurePageLoaded(idx);m_stack->setCurrentIndex(idx);}}auto* rightGroup = new QGroupBox;rightGroup->setTitle(QString());auto* rightLay = new QVBoxLayout(rightGroup);rightLay->setContentsMargins(8, 8, 8, 8);rightLay->addWidget(m_stack, 1);root->addWidget(m_nav);root->addWidget(rightGroup, 1);central->setLayout(root);setCentralWidget(central);}voidMainWindow::buildMenu(){auto* sys = menuBar()->addMenu("系统");connect(sys->addAction("生成测试数据"), &QAction::triggered, this, &MainWindow::onGenerateDemoData);}QWidget* MainWindow::createPage(int stackIndex){switch (stackIndex) {case 0: return new FinancePage(m_userId);case 1: return new PartsPage(m_userId);case 2: return new SuppliersPage(m_userId);case 3: return new CustomersPage(m_userId);case 4: return new WarehousesPage(m_userId);case 5: return new StockPage(m_userId);case 6: return new InventoryAdvancedPage(m_userId);case 7: return new PurchaseOrdersPage(m_userId);case 8: return new SalesOrdersPage(m_userId);case 9: return new ReturnsPage(m_userId);case 10: return new PosPage(m_userId);case 11: return new PricingPage(m_userId);case 12: return new ReportsPage(m_userId);case 13: return new ManufacturingPage(m_userId);case 14: return new CrmPage(m_userId);case 15: return new ExtensionsPage(m_userId);case 16: return new UsersRolesPage(m_userId);case 17: return new AuditLogPage(m_userId);case 18: return new SystemToolsPage(m_userId);default: return new QWidget;}}voidMainWindow::ensurePageLoaded(int stackIndex){if (!m_stack || stackIndex < 0 || stackIndex >= m_stack->count()) return;QWidget* current = m_stack->widget(stackIndex);if (!current || !current->objectName().isEmpty()) return;QWidget* page = createPage(stackIndex);page->setObjectName(QStringLiteral("loaded_page"));m_stack->removeWidget(current);current->deleteLater();m_stack->insertWidget(stackIndex, page);}voidMainWindow::wire(){connect(m_nav, QOverload<int>::of(&QListWidget::currentRowChanged), this, [this](int row) {if (row < 0) return;auto* it = m_nav->item(row);if (!it) return;const QVariant v = it->data(Qt::UserRole);if (!v.isValid()) return;const int idx = v.toInt();ensurePageLoaded(idx);m_stack->setCurrentIndex(idx);});}staticvoidwireGeneratorThread(QProgressDialog* dlg, QThread* workerThread, TestDataGenerator* gen, QWidget* toastParent){QObject::connect(dlg, &QProgressDialog::canceled, gen, [dlg, gen]() {dlg->setLabelText("正在取消,请稍候...");dlg->setCancelButton(nullptr);gen->requestCancel();});QObject::connect(gen, &TestDataGenerator::progress, toastParent, [dlg](quint64 done, quint64 total) {const int val = total ? static_cast<int>((done * 1000ULL) / total) : 0;dlg->setValue(val);dlg->setLabelText(QString("进度:%1 / %2").arg(done).arg(total));}, Qt::QueuedConnection);QObject::connect(gen, &TestDataGenerator::finished, toastParent, [dlg, workerThread, gen, toastParent](bool ok, const QString& msg) {dlg->setValue(1000);if (ok) Toast::info(toastParent, msg);else Toast::error(toastParent, msg);workerThread->quit();}, Qt::QueuedConnection);QObject::connect(workerThread, &QThread::finished, gen, &QObject::deleteLater);QObject::connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);QObject::connect(workerThread, &QThread::finished, dlg, &QObject::deleteLater);}voidMainWindow::onGenerateDemoData(){bool ok = false;const int rows = QInputDialog::getInt(this,"生成测试数据","请输入每个核心业务表要生成的记录条数:",500,1,1000000,50,&ok);if (!ok) return;auto* dlg = new QProgressDialog(QString("正在生成全系统测试数据。\n""目标每个核心业务表约 %1 条记录。\n""请稍候,可点击取消。").arg(rows),"取消", 0, 1000, this);dlg->setWindowTitle("生成测试数据");dlg->setWindowModality(Qt::WindowModal);dlg->setMinimumDuration(0);dlg->setAutoClose(false);dlg->setAutoReset(false);auto* workerThread = new QThread(this);auto* gen = new TestDataGenerator(Db::defaultDbPath(), m_userId);gen->moveToThread(workerThread);connect(workerThread, &QThread::started, gen, [gen, rows]() { gen->startFullSystem(static_cast<quint64>(rows)); }, Qt::QueuedConnection);wireGeneratorThread(dlg, workerThread, gen, this);workerThread->start();dlg->show();}
希望本文能为您的C++/Qt学习之旅提供有价值的参考!欢迎关注【Qt开发宝典】微信公众号,获取更多技术干货。
夜雨聆风