想要源码的可以直接去github拿额:地址
项目模块主要是以下三个模块
现在的开发进度完成了 登录、系统基础数据维护模块,开发完房态图,下面还有一些就不准备做了,因为去工作了,哈哈哈。
目前我已经把该项目的数据库发布到了网上,可以通过网络进行访问数据库。
UI接近网页的风格,走简约风格,主要就是灰色调,使用蓝色作为交互颜色,还有蓝色的对比色”黄色“,为选中表格的颜色。
还有一些模块的技术带你没来得及记录或者自己难以用文字描述出来…
DB文件夹:放置有关数据库的类
Dao文件夹:BaseDao 、SpeedyDao类接收Service 层传递过来的字段和值,进行拼接SQL语句,调用SqlInterface中的函数进行操作数据库。
Service文件夹:每一个Service类都有之对应的界面/交互功能类,接收来自界面/交互功能类的参数,把参数封装成一个适合Dao层参数,调用Dao层操作数据库。
Widgets 文件夹:界面类存放的位置
登陆中使用到了一个查询函数(SqlInterface层),它的声明如下:
QList<QVariantMap> queryList(QString sql,QVariantList params = QVariantList());
实现逻辑:
输入用户名和密码后,点击登录;这时候进入数据库查询用户信息,匹配则导出一个文本,里面写入用户名称,后打开主页面。若不匹配,则提示,如下图。
主页面由三个子widget组成
通过中间件Widget使用信号和槽进行跨页面交互,实现的功能:
它的中心控件为TreeWidget,用于显示从数据库中查询到的系统模块。
进入到登录页面后HeaderWidget部分的右上角,将显示当前登录者,显示登录者右边是注销按钮,点击它将会弹出提示是否注销登录,是,就注销账号后重启软件,否,不进行操作。
在左边的树形列表中点击一项,将在右边的窗口中添加该项对应的窗体(图片看不见建议放大看…)。
房态:空闲(绿色)、预约(蓝色)、入住(灰色)、清洁(黄色)、红色(维修)
鼠标滚轮向下滚动到一定的位置就加载剩下的,鼠标放在房间块上显示房间的具体信息,右键可以直接预定啥的,功能没写,但是样式写上去了。这里有点小bug啦…
这里显示数据的是 视图类(QGraphicsView)视图项也是自己定义的。
上方知识点中的第二点,便不会重复添加或修改数据是通过SQL语句的NOT EXISTS关键字完成任务的。
实现逻辑:
bool upDataByInfoRepeat(QString tableName,QStringList key = QStringList(), QVariantList value = QVariantList(), QHash>hash = QHash>());
关于CustomTableModel,重写了以下虚函数
在这个model中,用于保存数据的容器是:
初始化数据时候,从QList中取出数据。调用的方法是:SpeedyDao::selectSqlRecord
由于这个自定义model做的逻辑有些混乱,所以做完这个model后准备弃用。
一开始我的想法是,model与service两个层进行交互查询SQL拼接和查询都交给service层来做,但是没有实现两者之间的分层。为了完成这个model,两者之间的关系发生了一些混乱的交互,逻辑也开始混乱。
现在就开始捋一捋model、service、ui这三个层之间的运作逻辑(转至3.3.3员工部分观看)。
在3.4.2.2员工中使用了自定义委托控件(Delegate文件夹下),它是继承自QItemDelegate的一个委托类,重写了以下函数:
基础数据的增删查改,但是在新增和修改的过程中数据库中出现相同的数据,那么本次新增或修改将不会执行。
该部门展示数据的控件是QTableView,同时还使用了QSqlTableModel,调用QSqlTableModel的setTable函数指定查询表。
QSqlTableModel是一个可以直接在表格中双击进行编辑的Model,这个功能实在是太强大了。所以我给他双击编辑这个功能需要使用提交按钮才能提交,当觉得修改不好或发生一些无法预知的问题,可以使用“回退”进行数据的恢复。(没有使用“提交”的情况下才可以使用“回退”功能)。
基础数据的增删查改。在新增和修改的过程中数据库中出现相同的数据,那么本次新增或修改将不会执行。
该页面展示数据的控件是 QTableWidget,项 QTableWidgetItem 。使用手动添加的方法进行添加。
在这里我用于存储数据的容器是实体类Entitys/SystemSimpleModelEntity.h文件中 StaffPosition
这个页面是员工管理的精髓页面,同时也是最难的一个。在这里没有使用到新增和修改重复验证这个功能。
该页面中展示数据的控件是QTableView,使用了一个自定义的Model(Custom/CustomTableModel.h)。用到了分页功能,还有tableView直接编辑功能,自定义委托等。
该页面实现了的功能点:
model、service、ui三层运作逻辑:
由于为了实现上面介绍的功能,为此修改了modle、service、paging分页三者。它们之间的关联性实在是太过于强内聚,没有很好进行解耦。而且用起来还特别麻烦,需要定义多个方法与之呼应,这个写法显然不是我所需要的。所以CustomTableModel(model)、PagingClass(分页)两个类归属为staff这个类独有的。在往后的表格编辑中会新建一个全新的,更易于扩展的model和分页。
上面修改(直接编辑QTableView)model往后的解析太牵强,直接上代码:
/**
* @brief StaffService::onModelUpdateAppendSql
* @param id 员工id
* @param field 修改列
* @param value 修改值
* 接收来自 CustomTableModel 修改信号
*/
void StaffService::onModelUpdateAppendSql(int id, QString field, QVariant value)
{
//判断map中是否存在该id
if(m_updateMap.contains(id))
{
//存在追加
int index = m_updateMap.value(id);
QList<QString> fields = m_updateField.at(index);
QString fieldMyName = fieldName(field);
if(!fields.contains(fieldMyName))
{
m_updateField[index].append(fieldMyName);
m_updateValues[index].append(value);
}
else
{
int repIndex = fields.indexOf(fieldMyName);
(m_updateField[index])[repIndex] = fieldMyName;
(m_updateValues[index])[repIndex] = value;
}
}
else
{
//不存在,添加
m_updateMap.insert(id,m_index);
QList<QString> fields;
fields.append(fieldName(field));
m_updateField.append(fields);
QVariantList values;
values.append(value);
m_updateValues.append(values);
m_index++;
}
}
操作员这个界面用到了4张表:1.操作员表2.员工表3.部门表4.职业表.
操作员左连接员工,员工左连接部门和职业表。
在这里新增的时候遇到了一个有趣的查询,查询员工表,查询不是操作员的员工。这里我封装的拼接SQL语句方法弄得我够呛…用到了模糊查询和Not Exists(不存在),然后发现给Select加上Not Exists 写法太过于臃肿,然后我把它改成了 Not IN
图片中左边的带有查询的表格,那就是上方说到的“查询员工表,查询不是操作员的员工”,它是通过右边的对话框点击“选择员工”按钮弹出的。
关于QSqlQuery的模糊查询产生一个BUG。我们拼接好SQL语句后给到QSqlQuery进行绑定参数如:
SELECT room.room_num FROM room WHERE room.room_num LIKE ‘%?%’
上面这句假设就是我们拼接的SQL语句,我们直接使用QSqlQuery进行绑定参数,这个时候进行exec()查询后,发现一条数据都莫得。这就是我们在绑定参数的时候产生的一个bug,最神奇的我们使用boundValues()函数获取绑定的参数值,它居然绑定上去了…在这里我卡了半天。
问题产生的原因:在网上大佬是这样说的,绑定string时会自动加上单引号所以%必须在bindValue()时候接上去。
解决方案:bindValue()函数中添加%符号。
m_query->bindValue(i,QString("%%1%").arg(p.toString()));
关于查询:(臃肿)
NOT EXISTS (SELECT 1 FROM ( SELECT 1 FROM operator AS d1 WHERE d1.staff_id = staff.staff_id) AS a)
良好的:
staff.staff_id NOT IN (SELECT operator.staff_id FROM operator )
房间管理有:楼区、楼座、房间类型、房间状态、房间属性、房间、房间拥有的属性,总共7个小模块。
它们之间有许多相同的代码,我就不一一介绍了,下方把逻辑差不多的页面进行整理。
它们是:楼区、楼座、房间类型、房间状态、房间属性,这5个小模块。就是基础数据部分,没有太多的难点,并且为了加快开发速度,统一使用了QTableWidget这个容器,然后手动添加item(QTableWidgetItem)。下面一个一个截图看一下吧。
在这个模块中着重讲解 房间 、房间拥有的属性这两个页面。
在房间管理这一部分,我们使用了新的自定义model和分页。
查询部分:查询部分使用了当窗体大小改变,表格显示的条数会根据tableView的高度而改变。当然我们可以手动设置显示条数。首页、上一页、下一页、尾页和跳转这些基础的分页功能我们都有,当然不仅如此,我们提供了更加精确的条件查询。下面一起看一下我们的UI界面吧。
查询:
一开始我们初始化查询,是不带任何筛选条件的,显示条数根据页面的大小而改变。
条件查询对话框,是我们用来具体筛选那些数据是需要查询的,筛选数据一般离不开like模糊查询,在操作员那一部分讲解了like的使用,可以回头看一下。我将对话框中得到的数据它放到QList容器进行一些处理:将数据为空的数据剔除,然后通过发送信号传输到我们的房间主页面中。
然后通过房间主页面创建一个槽函数接收该信号传输过来的信号,放入service层中进行处理拼接SQL,在条件中like用到的情况下我们在service发送一个like出现的索引位置,传输到dao层绑定参数会用到这个索引。
我们的房间主页面、service、条件对话框,只是用来处理查询数据拼接SQL语句。真正的查询是在我们最新创建的model中:CustomTableViewModel
CustomTableViewModel 是一个集成新增、修改、分页的model,这些实现操作都是与该类有关。相对于之前我们自定义的 CustomTableModel 它逻辑更加的独立、清晰,不需要在service中创建一大堆的槽函数,来实现model与分页的交互,更加轻便。
setSql:设置sql语句
select:查询
setShowCount:设置显示条数
setThisPage:设置当前页,跳转
新增:
新增之前我们需要给model设置一个新增的SQL语句,通过setInsertSql方法。我们通过insertRows方法在model中创建一行全新的空白行,这时为新增状态(普通按钮将隐藏,显示提交/回滚按钮),在setData中实现新增的真正操作,这一步实在是太复杂了,想简化。
直接贴代码:
if(!index.isValid())
{
return false;
}
int row = index.row();
int col = index.column();
if(value == m_data[row][col])
{
return true;
}
if(role == Qt::EditRole)
{
m_data[row][col] = value;
}
else if(role == Qt::UserRole)
{
//获取当前列名
QString field = m_headerData.at(col);
//新增
if(m_isInsert)
{
if(m_updateData.size() > 0)
{
//添加至容器
for(int i = 0; i < m_updateData.size(); i++)
{
QVariantMap map = m_updateData[i];
//存在
QMap<QString,QVariant>::iterator ite = map.find(field);
if(ite != map.end())
{
ite->setValue(value);
break;
}
else
{
QVariantMap map1;
//不存在
map1.insert(field,value);
m_updateData.append(map1);
break;
}
}
//排序,新增
if(m_headerData.size()-1 == m_updateData.size())
{
QVariantList myvalue;
//排序
for(int i = 0; i < m_headerData.size(); i++)
{
for(int j = 0; j < m_headerData.size()-1; j++)
{
QMap<QString,QVariant>::iterator ite = m_updateData[j].find(m_headerData[i]);
if(ite != m_updateData[j].end())
{
myvalue.append(ite.value());
break;
}
}
}
//新增
if(!m_speedDao->insertSql(m_insertSql,myvalue))
{
emit sqlError("新增发生错误!" + m_speedDao->getLastError());
backAll();
return false;
}
else
{
m_isFirstSelect = true;
m_updateData.clear();
}
}
}
else
{
//不存在
QVariantMap map;
map.insert(field,value);
m_updateData.append(map);
}
}
//下面还有个else,在下方...
修改:
修改需要使用到m_headerData中的字段进行拼接SQL和绑定参数,实现也是在setData中,但是比新增代码少很多,它跟在新增的后面就是它的else部分
贴代码:
else
{
//修改 1.修改的表 2.修改的字段 3.表主键
QString updateSql = QString(" UPDATE %1 SET %2 = ? WHERE %3 = ? ").arg(m_tableName)
.arg(field).arg(m_headerData.at(0));
QVariantList values ;
values << value << index.sibling(index.row(),0).data();
if(!m_speedDao->updateToModel(updateSql,values))
{
qDebug() << m_speedDao->getLastError();
emit sqlError("修改发生错误!");
backAll();
return false;
}
}
m_userData[row][col] = value;
}
删除就是在房间主页面和service中实现的,没有把删除括入model的范围。
在这里我们使用到了model、service、筛选对话框,因为这里我们需要查询房间信息,那么直接把之前的东西拿来用。当然还有我们房间属性表中,我们也是拿之前的东西来用这样一来就可以减少好多的工作量。
新增部分就有一个新的东西,在新增前查询一下有没有重复。(根据条数>0,说明重复)
没有什么新的东西
都快成说明书了,哈哈哈…