目录
1、项目背景
2、核心功能
3、数据库设计
4、接口
5、项目中遇到的问题
6、扩展
众所周知,收银台即各个商铺结算付款的地方,客户在此处完成结款并获得购物票据,也可以通过这个系统快速的查出顾客结账情况、商品信息、售货情况等,这也就是传统收银台的最主要功能。但与此同时,传统收银台也存在一些缺陷,比如:收款结算速度慢、易出差错、不宜进行商品调价、商品盘点效率低、用户体验感不好。基于以上描述,所以模拟实现了一个带有一些扩展功能的小型收银台项目。
此次我做的小型收银台系统也结合传统收银台的功能,并在此基础上做了一定的扩展,主要功能包括:管理员可操作的员工管理与商品管理,以及售货员可操作的收银管理。整体功能框架如下:
1、登录模块
管理员与售货员输入自己的用户名与密码进行登录。根据用户身份不同,所具有的权限也不同,将会登录进不同的功能页面。
2、管理员界面
查询员工基本信息、添加新员工、删除离职员工信息、更新员工信息
按照条件查询商品信息、商品入库、过期商品的删除、商品信息变更、查看商品销售情况
3、售货员界面
查询商品库存信息、客户商品出售及取消出售、新增销售记录
结合本项目的功能,简单设计了以下几个表格
1、职工表
create table Employee(
id int, -- 员工编号
name varchar(20), -- 员工名字
gender varchar(3), -- 员工性别
birthday Date, -- 生日
password varchar(20), -- 员工密码
position varchar(10), -- 员工职位
telphone varchar(11), -- 联系方式
salary double(9,2) -- 联系方式
);
其中,将员工ID设置为主键,并添加非空、自增的属性
2、商品表
create table Goods(
GoodsID int, -- 商品编号
GoodsName varchar(20), -- 商品名称
GoodsType varchar(20), -- 商品类别:水果、烟酒、日常用品、副食等
ProductDate DATE, -- 商品生产日期
DeadDate DATE, -- 商品过期日期
Price double(9,2), -- 商品价格
Unit varchar(3), -- 计量单位
Inventory int, -- 库存量:商品剩余数量
AlarmValye int -- 报警值:低于该值时,应提醒管理员进货
);
3、售货记录表
create table SellRecord(
GoodsName varchar(20), -- 商品名称
GoodsPrice double(9, 2), -- 商品价格
Amount int, -- 售出数量
Unit varchar(3), -- 计量单位
SellTime Date, -- 售出时间
Operator varchar(20); -- 售货员
1、界面
使用Duilib界面库,首先我们看一下传统的MFC界面库与Duilib库的区别:
传统的MFC界面库存在以下缺陷:
而Duilib是一款强大轻量级的界面开发工具,可以将用户界面和处理逻辑彻底分离,极大地提高用户界面的开发效率。提供所见即所得的开发工具UIDesigner,并且使用XML来描述界面风格,界面布局,具有以下技术特点:
此处只罗列部分Duilib库的优势点,但是要注意的是Duilib仅仅是基于Win32的一套UI库,并不是使用了Duilib后就不是Win32程序了,Duilib并不像MFC一样将所有东西全包了,它仅仅包装了UI部分,其它内容还需要Win32知识。
2、数据库操作类封装
class MySQL
{
public:
MySQL();
bool ConnectMySQL(const char* host, const char* user, const char* password, const char* dbName);
~MySQL();
bool Insert(const string& strSQL);
bool Update(const string& strSQL);
bool Delete(const string& strSQL);
vector> Select(const string& strSQL);
private:
MYSQL* _mySQL;//mysql连接的实例对象
};
MySQL::MySQL()
{
_mySQL = mysql_init(nullptr);
}
bool MySQL::ConnectMySQL(const char* host, const char* user, const char* password, const char* dbName)
{
if (!mysql_real_connect(_mySQL, host, user, password, dbName, 3306, nullptr, 0))
{
cout << "数据库连接失败" << endl;
return false;
}
mysql_query(_mySQL, "set names 'gbk'");
return true;
}
vector> MySQL::Select(const string& strSQL)
{
vector> vvRet;
if (mysql_query(_mySQL, strSQL.c_str()))
{
//SQL命令响应失败
cout << mysql_error(_mySQL) << endl;
return vvRet;
}
//获取查询的记录集
MYSQL_RES * mySQLRes = mysql_store_result(_mySQL);
if (mySQLRes == nullptr)
{
cout << mysql_error(_mySQL) << endl;
return vvRet;
}
//获取记录集中有多少个字段
int itemCount = mysql_num_fields(mySQLRes);
MYSQL_ROW mysqlRow;
while (mysqlRow = mysql_fetch_row(mySQLRes))
{
//已经获取到一条记录
vector vItem;
for (size_t i = 0; i < itemCount; ++i)
{
vItem.push_back(mysqlRow[i]);
}
vvRet.push_back(vItem);
}
mysql_free_result(mySQLRes);
return vvRet;
}
bool MySQL::Insert(const string& strSQL)
{
if (mysql_query(_mySQL, strSQL.c_str()))
{
cout << mysql_error(_mySQL) << endl;
return false;
}
return true;
}
bool MySQL::Delete(const string& strSQL)
{
if (mysql_query(_mySQL, strSQL.c_str()))
{
cout << mysql_error(_mySQL) << endl;
return false;
}
return true;
}
bool MySQL::Update(const string& strSQL)
{
if (mysql_query(_mySQL, strSQL.c_str()))
{
cout << mysql_error(_mySQL) << endl;
return false;
}
return true;
}
MySQL::~MySQL()
{
mysql_close(_mySQL);
}
该项目中,主要是通过对数据库操作来达到项目需求,其他模块无非是构建相应的SQL语句,响应对应的控件完成各个界面具体的功能,这里就不过多的赘述了,下面简单的看一下如何借助Duilib库来创建一个窗口。
首先我们知道Duilib主打的界面制作方式是XML+UI引擎+win32框架,通过XML的方式来重写窗口,然后Duilib对XML进行解析,将窗口创建成功。
以员工操作窗口CCashierWnd为例,借助UIDesigner工具新建一个XML文件CCashierWnd.xml,具体内容如下所示,然后将该文件拷贝到exe所在目录下。
接下来让用户实现的窗口类继承自Duilib封装的:WindowImplBase类,该类是一个Duilib的基础框架类,封装了常用操作,方便使用。
class CCashierWnd :public WindowImplBase
{
public:
CCashierWnd(MySQL* mysql = nullptr)
:m_pMySQL(mysql)
{}
protected:
virtual void Notify(TNotifyUI& msg);
//WindowImplBase:提供的三个纯虚函数
//xml文件对应的目录
virtual CDuiString GetSkinFolder();
//xml文件的名字
virtual CDuiString GetSkinFile();
//窗口类的名字:在注册窗口时必须提供
virtual LPCTSTR GetWindowClassName(void) const;
void SelectGoods();
void AddGoodsCount();
void SubGoodsCount();
void InsertGoodsList();
void CancelOrder();
void CommitOrder();
//void DeleteEmployeeInfo();
private:
MySQL* m_pMySQL;
};
对于XML中给出的控件,如何进行响应?我们只需要重写NotifyUI类中的Notify纯虚函数即可。
void CCashierWnd::Notify(TNotifyUI& msg)
{
CDuiString strName = msg.pSender->GetName();
if (msg.sType == _T("click"))
{
if (strName == _T("BTN_CLOSE"))
Close();
else if (strName == _T("BTN_MIN"))
::SendMessage(m_hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
else if (strName == _T("BTN_SELECT"))
SelectGoods();
else if (strName == _T("BTN_ADD"))
AddGoodsCount();
else if (strName == _T("BTN_SUB"))
SubGoodsCount();
else if (strName == _T("BTN_OK"))
InsertGoodsList();
else if (strName == _T("BTN_COMMIT"))
CommitOrder();
else if (strName == _T("BTN_CANCLE"))
CancelOrder();
}
}
...
//下面处理具体的控件响应内容
...
整体的代码因为过多,就不在此处过多展示了,另外上传至github中,接下来再看看整个项目完成的过程中所遇到的问题。
1、配置环境
因为项目需要对数据库进行操作,还要使用Duilib库,因此需要配置好这两部分的环境,需要注意的是要看数据库是32位还是64位的,相应的编译器也要配置到相同的位,Duilib库也要按相同的位编译,不然就会遇到各种各样的错误,最终不能够正确执行代码。
2、编码格式
C++连接mysql时,比如查询语句中含有中文,或者得到结果中含有中文,经常出现编译出错或乱码的问题,VS默认使用gbk编码,所以在这里设置mysql以gbk格式编码
mysql_query(_mySql, "set names 'gbk'");
3、 UIDesigner工具使用
前面介绍了很多Duilib库的优势,它还提供了界面设计工具,避免了我们直接去写XML语句的问题,但是这个工具也有一些缺陷,比如说会修改已画好界面中的一些内容,不知道什么原因的崩溃掉,所以在使用的时候要一边画一边保存。
因为时间原因,以及自身水平的不足,项目现在就具有之前功能框架中介绍的这些模块,在以后的时间,还可以进一步完善这个项目,如下:
1、增加店铺会员用户管理的模块,对不同客户进行不同优惠售卖,会员积分等等功能;
2、数据安全问题。对于这样一个系统,实际使用过程中,可能会存在多人同时操作的情况存在,但是在项目中并没有考虑这个问题,在之后的扩展中可以考虑如何保证数据安全的问题。