1.概述

非常感谢hello_monster博主的分享,在他的基础上完善和新增的一些功能,使功能相对更加完整。

本文较为基础,本人也是小白,文中有不合适和不对的地方欢迎各位留言指正。

希望能对你有所帮助。

1.1 开发环境及开发工具

  • SQLite3 + QT 5.12.10

    SQLite是一个轻量级的数据库,不需要部署即可使用。

    qt绘制图表比较简单,并且Qt5可以直接使用SQLite;同时QT支持夸平台,这样不同的操作系统也不需要修改源码,只需要重新编译即可。

  • QT Creator + Navicat

    QT使用官方的开发工具QT Creator;

    Navicat Premium是一款数据库可视化工具。

1.2 功能描述及实现方式

1.2.1 数据库功能

  • 数据库中需存在四张表(入库表、出库表、库存表、收益表);
    仓库管理系统————QT+SQLite实现-编程知识网

  • 当入库/出库表数据发生变化时,同时修改入库表/收益表(触发器实现)。

1.2.2 软件功能

  • 界面实现数据的增、删、改、查;
  • 库存余量不足提示(重写QSqlTableModel的data函数);
  • 绘制一年中各个月份的成本和利润的柱状图(QtCharts)。

2.数据库功能的实现

2.1 设计表

2.1.1 SQLite3中的数据类型

SQLite3不同于MySql等数据库,他的数据类型只有4种。本次主要用到的类型如下:

  • TEXT(字符型)
  • Date(日期型)
  • INTEGER(整型)
  • REAL(浮点型)

参考链接:https://www.runoob.com/sqlite/sqlite-data-types.html

2.1.2 创建表

创建表可以选择Sql语言或者图形界面操作。本文采用Sql语言来创建。

--入库表
CREATE TABLE in_stock
(name TEXT(30),prod_date date,in_price real,in_num integer,in_total real
);
--出库表
CREATE TABLE out_prod
(name TEXT(30),out_date date,client TEXT(10),sale_price real,out_num integer,out_total real,finalpay TEXT(2)
);
--库存表
CREATE TABLE stock
(name TEXT(30),remain integer,alarm integer
);
--收益表
CREATE TABLE earn
(name TEXT(30),earn_date date,earn real
);

2.1.3 每月的成本和利润表

​ 为了界面中统计表的数据获取更加简单,可以多创建两张表,随后直接从这两张表中获取数据。

--每月的成本表
CREATE TABLE cost
(month_cost TEXT,cost real
);
--每月的利润表
CREATE TABLE report
(month_earn TEXT,earn real
);

2.2 触发器

2.2.1 触发器设计

​ 数据主要产生变化的表就是入库和出库两张表,可能新增、修改、删除等操作,首先想到用触发器实现。

​ 设想一下,当入库表有新增操作时,库存表和成本表都需要跟着变化;有删除操作时,库存表和成本表也都需要变化;但是有修改操作时,可能只有库存表变化,也可能只有成本表变化,也可能都有变化。出库表也是同样。

​ 在产生修改操作的时候合理的触发器实现较为复杂,本案例采用了较为简单易懂的实现方式:无论你的数据如何变化,只要产生了变化,我们就把库存表、成本表、收益表,重新填入一遍数据。覆盖掉之前的数据。这样无论你做出什么样的修改,其他的所有的表只需要做两件事:

​ 1.删除原来的数据;2.求出最新的库存、收益、成本等数据。

2.2.2 创建触发器

仓库管理系统————QT+SQLite实现-编程知识网

本案例需要使用6个触发器,由上图蓝色标记。

--创建触发器
--(创建在in_stock表产生insert操作after的触发器,名为tr_replace_stock)
CREATE TRIGGER tr_replace_stock
AFTER INSERT
ON in_stock
--触发成功开始执行
BEGIN
--结束
END--in_stock删除之后触发器
CREATE TRIGGER tr_del_in_stock
AFTER DELETE
ON in_stock
BEGIN
--触发器功能代码
END--in_stock更新之后触发器
CREATE TRIGGER tr_update_stock
AFTER UPDATE OF name,
in_price,
in_num,
in_total,
prod_date
ON in_stock
BEGIN
--
END--剩下三个改变表名和字段名即可

2.2.3 触发器功能

--in_stock插入触发器--
CREATE TRIGGER tr_replace_stock
AFTER INSERT
ON in_stock
--触发成功开始执行
BEGIN
--删除stock表中所有的数据
DELETE FROM stock;
--REPLACE INTO (如果新表中存在就更新如果不存在就插入)
--分别将in_stock、out_prod表中以name分类求in_num、out——num的和;
--当in_stock和out_prod表中的name相同时,计算in_num - out_prod;如果out_num为空时,以0带入计算。
REPLACE INTO stock(name,remain)SELECT i.name,SUM(i.in_num) - IFNULL((SELECT SUM(o.out_num) FROM out_prod AS oWHERE i.name = o.nameGROUP BY o.name) ,0)FROM in_stock AS i GROUP BY i.name;
--结束
END

这样入库表有数据插入,就可以自动完成库存表的数据更新。

随后当入库表插入时还需要更新收益表、按月统计的成本、利润表。这时只需要在BEGIN和END之间添加代码即可。

BEIGIN
--每日售出利润表
DELETE FROM earn;REPLACE INTO earn(name,earn_date,earn)SELECT o.name,o.out_date,o.out_num * (o.sale_price- (SELECT i.in_price FROM in_stock AS i WHERE i.name = o.name)) 	 AS earnFROM out_prod AS o;
--按月统计收益表
DELETE FROM report;REPLACE INTO report(month_earn,earn)SELECTSTRFTIME( '%Y-%m', e.earn_date ) AS month,SUM( e.earn ) FROMearn AS e --按earn_date中的年-月归组GROUP BYSTRFTIME( '%Y-%m', e.earn_date );
--按月统计成本表
DELETE FROM cost;REPLACE INTO cost(month_cost,cost)SELECTSTRFTIME( '%Y-%m', i.prod_date ),i.in_total FROMin_stock AS i GROUP BYSTRFTIME( '%Y-%m', i.prod_date );
END

之前设计的时候提到,只要原始表发生任何变化,其他的表全部重新做,所以剩下的工作只是更改触发器触发条件,触发器内容全部一致。(这种设计固然是不好的。数据非常大的时候,会浪费很多资源。)

3.客户端

3.1 Ui界面

3.1.1 入库登记、出库登记、收益明细界面

​ 客户端全局UI具体代码可见[hello_monster]的博客:点击《Qt实战笔记-从零开始搭建一套库存管理系统-(三)UI框架搭建-02》了解详情。

仓库管理系统————QT+SQLite实现-编程知识网
仓库管理系统————QT+SQLite实现-编程知识网
仓库管理系统————QT+SQLite实现-编程知识网

3.1.1 库存管理界面

​ 库存管理界面设计到库存余量不足提醒功能。需要对QSqlTableModel类中的Data()方法进行重写,从而实现红色突出显示。

仓库管理系统————QT+SQLite实现-编程知识网

//头文件MySqlTableModel.h
#include <QSqlTableModel>
class MySqlTableModel : public QSqlTableModel
{public:MySqlTableModel(QObject * parent = 0,QSqlDatabase db = QSqlDatabase());~MySqlTableModel();QVariant data(const QModelIndex &index,int role = Qt::DisplayRole)const;
}

//实现文件MySqlTableModel.cpp
#include "MySqlTableModel.h"
#include <QColor>
MySqlTableModel::MySqlTableModel(QObject * parent, QSqlDatabase db) : QSqlTableModel(parent,db)
{}MySqlTableModel::~MySqlTableModel()
{}
QVariant MySqlTableModel::data(const QModelIndex &index, int role) const
{bool flag =false;if(index.column() == 1)//第二列{QVariant value = QSqlTableModel::data(index,Qt::DisplayRole);//当前行的stock值int r = index.row();//行int c = 2;//列QModelIndex index1 = this->index(r,c,QModelIndex());QVariant value1 = QSqlTableModel::data(index1,Qt::DisplayRole);//当前行的alarm值int stock = value.toInt();int alarm = value1.toInt();if(stock<alarm)//判断库存量<报警值{flag = true;}}if(role == Qt::BackgroundColorRole && flag){return QVariant(QColor(255,60,0));}return QSqlTableModel::data(index,role);;
}

3.1.3 收益报表

仓库管理系统————QT+SQLite实现-编程知识网

//mainwindow.cpp
QWidget* MainWindow::creatReport()
{QWidget *reportPage = new QWidget;QLabel *titleLable = new QLabel("选择年份:");reportEdit = new QLineEdit;QHBoxLayout *titleLayout = new QHBoxLayout;titleLayout->addWidget(titleLable);titleLayout->addWidget(reportEdit);titleLayout->addWidget(reportBtn);titleLayout->addStretch();QBarSet *set0 = new QBarSet("成本");QBarSet *set1 = new QBarSet("利润");set0 = new QBarSet("成本");set1 = new QBarSet("利润");int year = getYear();//获取输入框中年份SelectYearData(year);//搜索year年的收益和成本数据bool bFlagEarn;//将有没有数据的月份处理为0for(int i =1;i<13;i++){bFlagEarn = false;for(int j = 0;j<vcearn.size();j++){if(vcearn.at(j).month == i){*set1<<vcearn.at(j).earn;bFlagEarn = true;break;}}if(!bFlagEarn){*set1<<0;}}bool bFlagCost;for(int i =1;i<13;i++){bFlagCost = false;for(int j = 0;j<vccost.size();j++){if(vccost.at(j).month == i){*set0<<vccost.at(j).cost;bFlagCost = true;break;}}if(!bFlagCost){*set0<<0;}}QBarSeries *series = new QBarSeries();series->append(set0);series->append(set1);QChart *chart = new QChart();chart->addSeries(series);chart->setTitle("全年进销表");chart->setAnimationOptions(QChart::SeriesAnimations);QStringList categories;categories << "一月" << "二月" << "三月" << "四月" << "五月" << "六月"<<"七月"<<"八月"<<"九月"<<"十月"<<"十一月"<<"十二月";QBarCategoryAxis *axisX = new QBarCategoryAxis();//初始化x轴axisX->append(categories);chart->addAxis(axisX, Qt::AlignBottom);series->attachAxis(axisX);QValueAxis *axisY = new QValueAxis();//初始化y轴axisY->setRange(0,100);//设置y轴范围为0~100axisY->setTitleText("金额/千元");//y轴标题axisY->setMinorTickCount(4);//y轴等分为4份axisY->setTickCount(5);//y轴每份再等分5小格chart->addAxis(axisY, Qt::AlignLeft);series->attachAxis(axisY);chart->legend()->setVisible(true);chart->legend()->setAlignment(Qt::AlignBottom);QChartView *chartView = new QChartView(chart);chartView->setRenderHint(QPainter::Antialiasing);QVBoxLayout *layout = new QVBoxLayout;layout->addLayout(titleLayout);layout->addWidget(chartView);reportPage->setLayout(layout);return reportPage;
}

3.2 显示数据库中数据

3.2.1 数据库的连接

bool mySqlite::connectDB()
{QSqlDatabase myDB = QSqlDatabase::addDatabase("QSQLITE");myDB.setDatabaseName(QApplication::applicationDirPath() + "/Database/"+"myWMS.db");if(!myDB.open())return false;return true;
}

3.2.2 将表中数据显示到界面(Model于View)

QT中将数据层(Model)和表示层(View)进行了分离,这里我们使用QSqlModel类中的方法与数据源通信,用QTableView将Model中数据以图表的形式显示出来。

3.2.2.1Model关联原始数据
  • 函数原型
explicit QSqlTableModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase());
  • 例子
//.h中声明
QWidget *supplierPage = new QWidget;
mySqlite *mysql;
QSqlTableModel *supplierModel; 
//.cpp中实现
supplierModel = new QSqlTableModel(supplierPage,mysql->myDB);//关联 数据库
supplierModel->setTable("in_stock");//关联数据库表
supplierModel->select();//选取表中所有的行
3.2.2.2 表示层显示Model数据
//.h中声明
QTableView *tableView;
//.cpp中实现
tableView = new QTableView(this);
tableView->setModel(outModel);//关联model
tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);//使其不可编辑

3.3 操作数据库

  QSqlRecord record = supplierModel->record();record.setValue(0,goodsName);//商品名称record.setValue(1,goodsDate);//添加日期record.setValue(2,goodsPrice);//商品价格record.setValue(3,goodsNum);//商品数量record.setValue(4,tatal);//总价supplierModel->insertRecord(supplierModel->rowCount(), record);//添加至ModelsupplierModel->submitAll();//提交
 int curRow = tableView->currentIndex().row();supplierModel->removeRow(curRow);int ok = QMessageBox::warning(this,tr("删除当前行!"),tr("确定删除当前行吗"),QMessageBox::Yes,QMessageBox::No);if(ok == QMessageBox::No)supplierModel->revertAll(); //如果不删除,则撤销elsesupplierModel->submitAll(); //否则提交,在数据库中删除改行
void MainWindow::modifySupplierData()
{QString goodsName = nameEdit->text();QDate currentDate = QDateTime::currentDateTime().date();QString goodsDate = currentDate.toString("yyyy-MM-dd");float goodsPrice = (priceEdit->text()).toFloat();int goodsNum = (numEdit->text()).toInt();float tatal = goodsPrice * goodsNum;QString goodsTotal = QString::number(tatal, 'f', 2);if(goodsName.isEmpty() || tatal <= 0){QMessageBox::information(this,"提示","修改失败,数据为空");return;}int curRow = tableView->currentIndex().row();QSqlRecord record = supplierModel->record(curRow);record.setValue(0,goodsName);record.setValue(1,goodsDate);record.setValue(2,goodsPrice);record.setValue(3,goodsNum);record.setValue(4,tatal);if(supplierModel->setRecord(curRow, record)){supplierModel->submitAll();}
}
void MainWindow::searchSupplierData()
{QString name =  QString("name = '%1'").arg(nameEdit->text());// QString date = QString("date = '%1'").arg(dateEdit->date().toString("yyyy-MM-dd"));QString strFilter = "";if(!nameEdit->text().isEmpty()){strFilter.append(name);}//如需要日期查询请取消注释
//    if(!dateEdit->text().isEmpty())
//    {
//        if(!strFilter.isEmpty())
//        {
//            strFilter.append(" and ");
//        }
//        strFilter.append(date);
//    }supplierModel->setFilter(strFilter);supplierModel->select();
}

3.4 图表更新

实现效果:

仓库管理系统————QT+SQLite实现-编程知识网

//.h中声明
struct stEarn
{int month;float earn;
};
struct stCost
{int month;float cost;
};//mainwindow类中声明QBarSet *set0;QBarSet *set1;QVector<stEarn>vcearn; QVector<stCost>vccost;
//.cpp实现
void MainWindow::searchYearBtn()
{int year = getYear();//获取输入框中的年份SelectYearData(year);//搜索该年份各个月的成本和收益数据bool bCost,bEarn;for(int i = 0;i<12;i++){bCost = false;bEarn = false;set0->replace(i,0);set1->replace(i,0);for(int j = 0;j<vccost.size();j++){if(vccost.at(j).month == i+1){set0->replace(i,vccost.at(j).cost);//更新数据break;}}for(int j = 0;j<vcearn.size();j++){if(vcearn.at(j).month == i+1){set1->replace(i,vcearn.at(j).earn);//更新数据break;}}}
}
//获取输入框中年份
int MainWindow::getYear()
{int year = reportEdit->text().toInt();if(year == 0){QDate date = QDate::currentDate();year = date.year();}return year;
}//搜索该年份各个月的成本和收益数据
void MainWindow::SelectYearData(int year)
{vcearn.clear();vccost.clear();QSqlQuery query;QString str;str.sprintf("SELECT * FROM cost AS c WHERE SUBSTR(c.month_cost,0,5) = '%d'",year);query.prepare(str);query.exec();while(query.next()){stCost costTemp;QString month = query.value("month_cost").toString();costTemp.month = month.right(2).toInt();QString earn = query.value("cost").toString();costTemp.cost = earn.toFloat() / 1000;vccost.push_back(costTemp);}str.sprintf("SELECT * FROM report AS e WHERE SUBSTR(e.month_earn,0,5) = '%d'",year);query.prepare(str);query.exec();while(query.next()){stEarn earnTemp;QString month = query.value("month_earn").toString();earnTemp.month = month.right(2).toInt();QString earn = query.value("earn").toString();earnTemp.earn = earn.toFloat() / 1000;vcearn.push_back(earnTemp);}
}

4.发布

在一台没有装过QT开发环境的电脑上运行本程序,可能会出现各种各样的错误。我们只要使用Q T自带的windeployqt工具即可完成打包。

参考:《Qt程序打包发布方法(使用官方提供的windeployqt工具)》——lxj434368832

总结:在windeployqt中先cd到exe执行文件目录下,随后执行windeployqt Manger.exe,等待完成即可。