乐于分享
好东西不私藏

2小时精通Qt搞企业项目,拆解软件必用7个核心模块

2小时精通Qt搞企业项目,拆解软件必用7个核心模块

欢迎关注《Qt开发宝典》公众号【知识宝库】!
定期持续分享Linux C++、Qt、AI等技术干货。

【技术交流群说明】

欢迎关注《Qt开发宝典》公众号

每日分享Linux C++/Qt实战干货与技术笔记

📌加入技术交流QQ群:895876809(纯技术,无广告)

📌进群获取C++全技术栈开发资料(含Qt/后端/嵌入式等)

一起学习,共同进步!

👇以下全文全是硬核干货。文章后面有Gitee仓库(工程项目源码)。

项目采用QWidget桌面界面cmake工程管理和SQLite本地数据库,围绕汽配行业常见业务链路,提供从基础资料、库存、采购、销售、退货、价格、财务、报表,到用户权限、系统参数、CSV导入导出、数据库备份恢复等一整套模块化能力。

一、【工程项目运行】

1. 系统登录(管理员权限)
2. 财务管理(应收AR)
3. 财务管理(应付AP)
4. 财务管理(回款)
5. 财务管理(付款)
6. 财务管理(对账)
7. 财务管理(币种)
8. 财务管理(汇率)
9. 财务管理(利润)
10. 基础数据–配件 
11. 基础数据–供应商
12. 基础数据–客户
13. 基础数据–仓库
14. 库存管理
15. 高级库存
16. 采购管理
17. 销售管理
18. 采购与销售退货
19. POS零售
20. 客户价与阶梯价
21. 报表与分析
22. 生产制造
23. CRM与售后
24. 协同/多公司/AI/移动
25. 系统管理-用户与角色
26. 系统管理–操作日志
27. 系统工具与参数
二、【工程项目简介】
1. 项目定位与目标

本项目定位为一个桌面端企业业务信息系统工程产品,聚焦汽配行业,覆盖如下典型业务域:

 基础资料管理

 采购与销售管理

 库存与价格管理

 财务协同

 用户权限与系统治理

 制造、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;};
4. main_window.cpp
#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(1280800);    buildUi();    buildMenu();    wire();    statusBar()->showMessage("就绪");}voidMainWindow::buildUi(){    auto* central = new QWidget(this);    auto* root = new QHBoxLayout;    root->setContentsMargins(8888);    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(8888);    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 0return new FinancePage(m_userId);    case 1return new PartsPage(m_userId);    case 2return new SuppliersPage(m_userId);    case 3return new CustomersPage(m_userId);    case 4return new WarehousesPage(m_userId);    case 5return new StockPage(m_userId);    case 6return new InventoryAdvancedPage(m_userId);    case 7return new PurchaseOrdersPage(m_userId);    case 8return new SalesOrdersPage(m_userId);    case 9return new ReturnsPage(m_userId);    case 10return new PosPage(m_userId);    case 11return new PricingPage(m_userId);    case 12return new ReportsPage(m_userId);    case 13return new ManufacturingPage(m_userId);    case 14return new CrmPage(m_userId);    case 15return new ExtensionsPage(m_userId);    case 16return new UsersRolesPage(m_userId);    case 17return new AuditLogPage(m_userId);    case 18return new SystemToolsPage(m_userId);    defaultreturn 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 < 0return;        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),        "取消"01000this);    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开发宝典】微信公众号,获取更多技术干货。