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 数据库功能
- 数据库中需存在四张表(入库表、出库表、库存表、收益表);
- 当入库/出库表数据发生变化时,同时修改入库表/收益表(触发器实现)。
; 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 创建触发器
本案例需要使用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 o
WHERE i.name = o.name
GROUP 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 earn
FROM out_prod AS o;
--按月统计收益表
DELETE FROM report;
REPLACE INTO report(month_earn,earn)
SELECT
STRFTIME( '%Y-%m', e.earn_date ) AS month,
SUM( e.earn )
FROM
earn AS e
--按earn_date中的年-月归组
GROUP BY
STRFTIME( '%Y-%m', e.earn_date );
--按月统计成本表
DELETE FROM cost;
REPLACE INTO cost(month_cost,cost)
SELECT
STRFTIME( '%Y-%m', i.prod_date ),
i.in_total
FROM
in_stock AS i
GROUP BY
STRFTIME( '%Y-%m', i.prod_date );
END
之前设计的时候提到,只要原始表发生任何变化,其他的表全部重新做,所以剩下的工作只是更改触发器触发条件,触发器内容全部一致。(这种设计固然是不好的。数据非常大的时候,会浪费很多资源。)
3.客户端
3.1 Ui界面
3.1.1 入库登记、出库登记、收益明细界面
客户端全局UI具体代码可见[hello_monster]的博客:点击《Qt实战笔记-从零开始搭建一套库存管理系统-(三)UI框架搭建-02》了解详情。
; 3.1.1 库存管理界面
库存管理界面设计到库存余量不足提醒功能。需要对QSqlTableModel类中的Data()方法进行重写,从而实现红色突出显示。
#include
class MySqlTableModel : public QSqlTableModel
{
public:
MySqlTableModel(QObject * parent = 0,QSqlDatabase db = QSqlDatabase());
~MySqlTableModel();
QVariant data(const QModelIndex &index,int role = Qt::DisplayRole)const;
}
#include "MySqlTableModel.h"
#include
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);
int r = index.row();
int c = 2;
QModelIndex index1 = this->index(r,c,QModelIndex());
QVariant value1 = QSqlTableModel::data(index1,Qt::DisplayRole);
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 收益报表
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);
bool bFlagEarn;
for(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();
axisX->append(categories);
chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0,100);
axisY->setTitleText("金额/千元");
axisY->setMinorTickCount(4);
axisY->setTickCount(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());
- 例子
QWidget *supplierPage = new QWidget;
mySqlite *mysql;
QSqlTableModel *supplierModel;
supplierModel = new QSqlTableModel(supplierPage,mysql->myDB);
supplierModel->setTable("in_stock");
supplierModel->select();
3.2.2.2 表示层显示Model数据
QTableView *tableView;
tableView = new QTableView(this);
tableView->setModel(outModel);
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);
supplierModel->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();
else
supplierModel->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 strFilter = "";
if(!nameEdit->text().isEmpty())
{
strFilter.append(name);
}
supplierModel->setFilter(strFilter);
supplierModel->select();
}
3.4 图表更新
实现效果:
struct stEarn
{
int month;
float earn;
};
struct stCost
{
int month;
float cost;
};
QBarSet *set0;
QBarSet *set1;
QVector<stEarn>vcearn;
QVector<stCost>vccost;
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工具即可完成打包。
总结:在windeployqt中先cd到exe执行文件目录下,随后执行windeployqt Manger.exe,等待完成即可。
Original: https://blog.csdn.net/weixin_45623223/article/details/115275328
Author: -zhaocan-
Title: 仓库管理系统————QT+SQLite实现
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/817033/
转载文章受原作者版权保护。转载请注明原作者出处!