赞
踩
非常感谢hello_monster博主的分享,在他的基础上完善和新增的一些功能,使功能相对更加完整。
本文较为基础,本人也是小白,文中有不合适和不对的地方欢迎各位留言指正。
希望能对你有所帮助。
SQLite3 + QT 5.12.10
SQLite是一个轻量级的数据库,不需要部署即可使用。
qt绘制图表比较简单,并且Qt5可以直接使用SQLite;同时QT支持夸平台,这样不同的操作系统也不需要修改源码,只需要重新编译即可。
QT Creator + Navicat
QT使用官方的开发工具QT Creator;
Navicat Premium是一款数据库可视化工具。
数据库中需存在四张表(入库表、出库表、库存表、收益表);
当入库/出库表数据发生变化时,同时修改入库表/收益表(触发器实现)。
SQLite3不同于MySql等数据库,他的数据类型只有4种。本次主要用到的类型如下:
参考链接:https://www.runoob.com/sqlite/sqlite-data-types.html
创建表可以选择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 );
为了界面中统计表的数据获取更加简单,可以多创建两张表,随后直接从这两张表中获取数据。
--每月的成本表
CREATE TABLE cost
(
month_cost TEXT,
cost real
);
--每月的利润表
CREATE TABLE report
(
month_earn TEXT,
earn real
);
数据主要产生变化的表就是入库和出库两张表,可能新增、修改、删除等操作,首先想到用触发器实现。
设想一下,当入库表有新增操作时,库存表和成本表都需要跟着变化;有删除操作时,库存表和成本表也都需要变化;但是有修改操作时,可能只有库存表变化,也可能只有成本表变化,也可能都有变化。出库表也是同样。
在产生修改操作的时候合理的触发器实现较为复杂,本案例采用了较为简单易懂的实现方式:无论你的数据如何变化,只要产生了变化,我们就把库存表、成本表、收益表,重新填入一遍数据。覆盖掉之前的数据。这样无论你做出什么样的修改,其他的所有的表只需要做两件事:
1.删除原来的数据;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 --剩下三个改变表名和字段名即可
--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
之前设计的时候提到,只要原始表发生任何变化,其他的表全部重新做,所以剩下的工作只是更改触发器触发条件,触发器内容全部一致。(这种设计固然是不好的。数据非常大的时候,会浪费很多资源。)
客户端全局UI具体代码可见[hello_monster]的博客:点击《Qt实战笔记-从零开始搭建一套库存管理系统-(三)UI框架搭建-02》了解详情。
库存管理界面设计到库存余量不足提醒功能。需要对QSqlTableModel类中的Data()方法进行重写,从而实现红色突出显示。
//头文件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);; }
//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; //将有没有数据的月份处理为0 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();//初始化x轴 axisX->append(categories); chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); QValueAxis *axisY = new QValueAxis();//初始化y轴 axisY->setRange(0,100);//设置y轴范围为0~100 axisY->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; }
bool mySqlite::connectDB()
{
QSqlDatabase myDB = QSqlDatabase::addDatabase("QSQLITE");
myDB.setDatabaseName(QApplication::applicationDirPath() + "/Database/"+"myWMS.db");
if(!myDB.open())
return false;
return true;
}
QT中将数据层(Model)和表示层(View)进行了分离,这里我们使用QSqlModel类中的方法与数据源通信,用QTableView将Model中数据以图表的形式显示出来。
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();//选取表中所有的行
//.h中声明
QTableView *tableView;
//.cpp中实现
tableView = new QTableView(this);
tableView->setModel(outModel);//关联model
tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);//使其不可编辑
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);//添加至Model
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 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(); }
实现效果:
//.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); } }
在一台没有装过QT开发环境的电脑上运行本程序,可能会出现各种各样的错误。我们只要使用Q T自带的windeployqt工具即可完成打包。
总结:在windeployqt中先cd到exe执行文件目录下,随后执行windeployqt Manger.exe,等待完成即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。