QT练习项目:智能门店
·
前言
此文档仅做记录并且分享自己学习的过程,学艺不精,code新人,如有错误欢迎在评论区讨论、指正! 若文章内容对你有帮助,希望可以给我点个赞!
1.项目背景介绍
- 为什么想做这个项目: 出于对现实生活中“扫码点单”这一行为背后的实现感到好奇,尝试独立完成的练手项目。
- 想要做到什么程度: 基本贴合现实生活,不过暂时只能用于小店,因为数据库在本地;包含必要的功能——订单查询,历史查询,门店流水统计,订单跟踪。
- 项目结果: 项目满分100分的话,大概能给自己打个70分吧,基础功能有一些,但和实际生活中的还差的远;像是历史记录查询、门店营销额度查询没来得及做,界面比较直观了,但还是不太方便。
- 项目展示:QT练手项目:智能门店
2. 项目讲解
2.1 项目架构
- 服务器:店主使用;用于查看订单信息和客户需求。
- 客户端:客户使用;用于扫码点单,提出需求。
- 消息类型:发送订单、呼叫服务员、结账
- 消息体:
- 订单:OREDR 1号 XXX(菜目数量,价格) 总计 XXX 元 END
- 呼叫:CALL 1号 END
- 结账:PAY 1号 END
2.2 项目组件及相关代码
- 服务端:
开始界面
相关代码:widget.cpp
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
slog = new serverLogin();//登录界面
/*----------------设置背景图片------------------*/
QPixmap pixmap(":/icon/jiemian.jpg");
QPixmap pix = pixmap.scaled(this->size());//图片自适应窗口
// 创建调色板
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
}
Widget::~Widget()
{
delete ui;
}
// 此处点击开始按钮后,会唤起第二个界面,此处代码处理唤起界面,还支持界面跟随
void Widget::on_pushButton_clicked()
{
// 获取当前主窗体的位置和大小
QRect geom = this->geometry();
// 计算新窗体的位置,使其居中于主窗体
int x = geom.x() + (geom.width() - slog->width()) / 2;
int y = geom.y() + (geom.height() - slog->height()) / 2;
slog->move(x, y); // 移动到计算出的中心位置
slog->show();//打开登录界面
this->close();
}
登录界面
相关代码: serverlogin.cpp涉及到 Sqlite3 数据库,所以代码会稍长一点,代码大致就是从界面上的两个lineEdit 分别的获取账号和密码,注册检查数据库是否存在该用户,若不存在则添加;登录查看数据库是否存在该用户,存在则唤起后续界面。
#include "serverlogin.h"
#include "ui_serverlogin.h"
serverLogin::serverLogin(QWidget *parent) :
QWidget(parent),
ui(new Ui::serverLogin)
{
ui->setupUi(this);
QPixmap pixmap(":/icon/jiemian.jpg");
QPixmap pix = pixmap.scaled(this->size());
// 创建调色板
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
menu = new MenuServer();
msgBox = new QMessageBox(this);
/*--------------------SQL start-------------------*/
//创建并打开数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); //添加数据库驱动
db.setDatabaseName("my.db"); //设置数据库名
if (!db.open()) { //打开数据库
qDebug() << "Database open failed!";
}
query = new QSqlQuery(db);
QString sql = "create table if not exists server(uid text primary key, passwd text)";
if (!query->exec(sql)) { //执行sql语句
qDebug() << query->lastError().text(); //获取最后一次错误信息
}
/*--------------------SQL end-------------------*/
}
serverLogin::~serverLogin()
{
delete ui;
}
//注册
void serverLogin::on_pushButton_2_clicked()
{
QString name = ui->lineEdit->text(); //账号
QString pswd = ui->lineEdit_2->text(); // 密码
if (name.isEmpty() || pswd.isEmpty()) {
//QMessageBox::warning(this, "注册用户", "账号与密码不能为空!");
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText("注册用户");
msgBox->setInformativeText("账号与密码不能为空!");
msgBox->show();
return;
}
QString sql = QString("insert into server values('%1', '%2')").arg(name).arg(pswd);
if (!query->exec(sql)) { //执行sql语句
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText("注册用户");
msgBox->setInformativeText(QString("注册失败: %1").arg(query->lastError().text()));
msgBox->show();
} else{
msgBox->setText("注册用户");
msgBox->setInformativeText("注册成功!");
msgBox->show();
}
}
//登录
void serverLogin::on_pushButton_clicked()
{
QString name = ui->lineEdit->text(); //账号
QString pswd = ui->lineEdit_2->text(); // 密码
if (name.isEmpty() || pswd.isEmpty()) {
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText("用户登录");
msgBox->setInformativeText("账号与密码不能为空!");
msgBox->show();
return;
}
//搜寻用户信息
QString sql = QString("select * from server where uid='%1'and passwd='%2'").arg(name).arg(pswd);
if (query->exec(sql)) {
// 检查是否有匹配的记录
if (query->next()) {
/* -------------------------登录成功,进入管理界面-------------------------*/
msgBox->setText("登录成功");
msgBox->setInformativeText("欢迎使用管理员:" + name + "!");
msgBox->show();
// 获取当前主窗体的位置和大小
QRect geom = this->geometry();
// 计算新窗体的位置,使其居中于主窗体
int x = geom.x() + (geom.width() - menu->width()) / 2;
int y = geom.y() + (geom.height() - menu->height()) / 2;
this->close();
menu->move(x, y);
menu->show();//打开管理界面
} else {
// 登录失败,显示失败消息框
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText("登录失败");
msgBox->setInformativeText("用户名或密码错误,请重试");
msgBox->show();
}
} else {
// 查询执行失败,显示错误信息
msgBox->setIcon(QMessageBox::Warning);
msgBox->setText("查询错误");
msgBox->setInformativeText("数据库查询失败: " + query->lastError().text());
msgBox->show();
}
}
服务器订单界面
相关代码:menuserver.cpp涉及到 TCP 和消息的解析,是服务器最核心的代码部分。
#include "menuserver.h"
#include "ui_menuserver.h"
MenuServer::MenuServer(QWidget *parent) :
QWidget(parent),
ui(new Ui::MenuServer)
{
ui->setupUi(this);
QPixmap pixmap(":/icon/jiemian.jpg");
QPixmap pix = pixmap.scaled(this->size());
// 创建调色板
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
msgBox = new QMessageBox(this);
ui->tabWidget->setCurrentIndex(0);
this->setWindowTitle("餐厅管理系统");
/*--------------------------网络相关-----------------------------*/
server = new QTcpServer; //1. 创建套接字
if (!server->listen(QHostAddress::AnyIPv4, 9999)) { //2. bind+listen
qDebug() << server->errorString();
}
//当有新的连接进来时触发
connect(server, &QTcpServer::newConnection, this, &MenuServer::accept_client);
}
MenuServer::~MenuServer()
{
delete ui;
}
//接收客户端连接,识别消息类型,根据消息类型做不同的事情
void MenuServer::accept_client(){
if (server->hasPendingConnections()) { //判断是否有等待的连接
socket = server->nextPendingConnection(); //接收连接
msgBox->setText("提示");
msgBox->setInformativeText("准备迎客!");
msgBox->show();
connect(socket, &QTcpSocket::disconnected, [&](){//客户端断开
msgBox->setText("提示");
msgBox->setInformativeText("用户离开");
msgBox->show();
socket->close();
});
/*--------识别消息类型,根据消息类型做不同的事情--------*/
connect(socket, &QTcpSocket::readyRead, [&](){
static QByteArray buffer;
buffer.append(socket->readAll());
// 检查是否包含 'END' 分隔符以处理完整消息
int endIndex = buffer.indexOf("END");
QByteArray messageBytes = buffer.left(endIndex); // 获取完整消息(不包括 'END')
// 移除已处理的部分(包括 'END' 的长度)
buffer.remove(0, endIndex + 3);
QString message = QString::fromUtf8(messageBytes).trimmed(); // 转换为 QString,并去除多余空格
// 分析消息内容
QStringList parts = message.split(" ", QString::SkipEmptyParts);
if (parts.size() > 2 && parts[0] == "ORDER") { // 确保有足够的部分并且是 ORDER 消息
QString orderType = parts[0]; // "ORDER"
QString tablenum = parts[1]; // 桌号
// 剩余部分为订单内容,合并成一个字符串(去掉第一部分)
QString orderContent = parts.mid(2).join(" ");
qDebug() << "桌号:" << tablenum;
qDebug() << "订单内容:" << orderContent;
// 判断桌号,并将对应订单信息放到对应框内
if ("1号" == tablenum) {
ui->textEdit->setText(orderContent);
}else if("2号" == tablenum){
ui->textEdit_6->setText(orderContent);
}else if("3号" == tablenum){
ui->textEdit_7->setText(orderContent);
}else if("4号" == tablenum){
ui->textEdit_8->setText(orderContent);
}else if("5号" == tablenum){
ui->textEdit_9->setText(orderContent);
}else if("6号" == tablenum){
ui->textEdit_10->setText(orderContent);
}
}else if(parts[0] == "CALL"){
msgBox->setText("提示");
msgBox->setInformativeText(QString(" %1 号桌顾客呼叫服务员").arg(parts[1]));
msgBox->show();
}else if(parts[0] == "PAY"){
//QMessageBox::information(nullptr, "提示", QString(" %1桌顾客要结账").arg(parts[1]));
msgBox->setText("提示");
msgBox->setInformativeText(QString(" %1 桌顾客要结账").arg(parts[1]));
msgBox->show();
}
});
}
}
//清空
void MenuServer::on_pushButton_clicked()
{
ui->textEdit->clear();
}
void MenuServer::on_pushButton_6_clicked()
{
ui->textEdit_6->clear();
}
void MenuServer::on_pushButton_7_clicked()
{
ui->textEdit_7->clear();
}
void MenuServer::on_pushButton_8_clicked()
{
ui->textEdit_8->clear();
}
void MenuServer::on_pushButton_9_clicked()
{
ui->textEdit_9->clear();
}
void MenuServer::on_pushButton_10_clicked()
{
ui->textEdit_10->clear();
}
- 客户端
初始界面
相关代码:widget.cpp 要进行二维码的识别,链接了百度人工智能的云端对二维码进行解析,二维码由草料二维码生成。
二维码背后蕴藏的实际上就是个字符串信息,此处二维码信息为“点菜”二字,若信息不对,是无法唤起后续界面的。
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowTitle("点单");
QPixmap pixmap(":/icon/1.jpg");
QPixmap pix = pixmap.scaled(this->size());//解决图片与窗口大小不匹配的问题
// 创建调色板
QPalette palette;
//palette.setBrush(QPalette::Background,QBrush(QPixmap(":/icon/jiemian.jpg")));
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
log = new login;
/*--------------------QRcode start-------------------*/
filename = "D:/Qt/Project/Final/qrcode_baiduAI/pic/order.png";//文本信息:“点菜”
ui->label_2->setPixmap(QPixmap("../orderClient/qrcode/order.png"));//二维码
ui->label_2->setScaledContents(true);
manager = new QNetworkAccessManager(this);
//获取access_token值
QString url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=srWCVQuRjrRh68R88XMQR0nV&client_secret=0eO0z57vBvXA7FKyTPQpeGFzsY47vwk8";
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); //设置请求的头
req.setUrl(QUrl(url)); //设置url
manager->post(req, "");
connect(manager, &QNetworkAccessManager::finished, this, &Widget::get_reply);
/*--------------------QRcode end-------------------*/
}
Widget::~Widget()
{
delete ui;
}
/*--------------------QRcode-------------------*/
void Widget::get_reply(QNetworkReply *reply) //接收响应并处理
{
QByteArray data = reply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isEmpty())
return;
QJsonObject obj = doc.object();
if (obj.contains("access_token")) {
access_token = obj.value("access_token").toString();
} else if (obj.contains("codes_result")) {
QJsonArray arr = obj.value("codes_result").toArray();
obj = arr.at(0).toObject();
if (obj.contains("text")) {
/*-----------------------获得了“点菜”的信息------------------------*/
QString text = obj.value("text").toArray().at(0).toString();
qDebug() << text;
/*-----------------------唤起登录界面------------------------*/
if("点菜" == text){
// 获取当前主窗体的位置和大小
QRect geom = this->geometry();
// 计算新窗体的位置,使其居中于主窗体
int x = geom.x() + (geom.width() - log->width()) / 2;
int y = geom.y() + (geom.height() - log->height()) / 2;
this->close();
log->move(x, y);
log->show();
}
}
}
}
//识别二维码,得到“点菜”信息
void Widget::on_pushButton_clicked()
{
QString url = "https://aip.baidubce.com/rest/2.0/ocr/v1/qrcode?access_token=" + access_token;
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); //设置请求的头
req.setUrl(QUrl(url)); //设置url
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "error!";
return;
}
QByteArray data = file.readAll();
file.close();
QByteArray body = "image=" + data.toBase64().toPercentEncoding();
manager->post(req, body);
}
客户端点菜界面 为客户端的核心部分,用于构筑订单,提交需求,组件类别可以在ordermenu.ui文件里看到,这里不过多说明。
相关代码:ordermenu.cpp水平有限,实际上重复内容比较多,还望海涵
#include "ordermenu.h"
#include "ui_ordermenu.h"
OrderMenu::OrderMenu(QWidget *parent) :
QWidget(parent),
ui(new Ui::OrderMenu)
{
ui->setupUi(this);
this->setWindowTitle("点单系统");
//设置背景图片
QPixmap pixmap(":/icon/3.jpg");
QPixmap pix = pixmap.scaled(this->size());
// 创建调色板
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
/*---------------价格相关-----------------*/
total = 0; //总价
msgBox = new QMessageBox(this);
menuPrices = { //价目表
{"葱姜炒肉", 15},
{"油炸排骨", 20},
{"泡鲁达", 8},
{"永昌火瓢牛肉", 80},
{"滇西牛扒烀", 40},
{"下村豆粉", 8},
{"保山火烧肉", 32},
{"傣族手抓饭", 150},
{"云南凉虾", 5},
{"云南甜米酒", 8}
};
ui->tabWidget->setCurrentIndex(0);
/*---------------家常 pic-----------------*/
ui->label_2->setPixmap(QPixmap(":/deshes/congjiang").scaled(ui->label_2->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_10->setPixmap(QPixmap(":/deshes/youzhapaigu").scaled(ui->label_10->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
/*---------------云南特色 pic-----------------*/
ui->label_22->setPixmap(QPixmap(":/deshes/huopiaoniurou").scaled(ui->label_22->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_32->setPixmap(QPixmap(":/deshes/niupahu").scaled(ui->label_32->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_35->setPixmap(QPixmap(":/deshes/xiacundoufen").scaled(ui->label_35->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_39->setPixmap(QPixmap(":/deshes/huoshaorou").scaled(ui->label_39->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_43->setPixmap(QPixmap(":/deshes/daizushuozhuafan").scaled(ui->label_43->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
/*---------------饮品 pic-----------------*/
ui->label_45->setPixmap(QPixmap(":/deshes/paoluda").scaled(ui->label_45->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_51->setPixmap(QPixmap(":/deshes/liangxia").scaled(ui->label_51->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->label_61->setPixmap(QPixmap(":/deshes/mijiu").scaled(ui->label_61->size(), \
Qt::KeepAspectRatio, Qt::SmoothTransformation));
/*---------------家常 slots----------------------*/
connect(ui->spinBox, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //葱姜炒肉
connect(ui->spinBox_5, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //油炸排骨
/*---------------饮品 slots----------------------*/
connect(ui->spinBox_32, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //泡鲁达
connect(ui->spinBox_40, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //云南凉虾
connect(ui->spinBox_41, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //云南甜米酒
/*---------------云南特色 slots----------------------*/
connect(ui->spinBox_20, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //火瓢牛肉
connect(ui->spinBox_23, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //扒烀
connect(ui->spinBox_24, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //下村
connect(ui->spinBox_26, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //火烧
connect(ui->spinBox_29, SIGNAL(valueChanged(int)), this, \
SLOT(on_spinBox_valueChanged(int))); //手抓
/*---------------------关于网络--------------------------*/
socket = new QTcpSocket;
host = "192.168.20.1";
port = 9999;
socket->connectToHost(host, port);//连接
}
OrderMenu::~OrderMenu()
{
delete ui;
}
//构建订单
void OrderMenu::on_spinBox_valueChanged(int arg1)
{
// 获取发送信号的对象(即哪个 QSpinBox 发生了变化)
QSpinBox *spinbox = qobject_cast<QSpinBox *>(sender());
if (!spinbox) return; // 确保转换成功
QString itemName;
total = 0;
// 根据不同的 QSpinBox 设置对应的 item 名称
// 可以继续添加更多的 QSpinBox 和对应项...
if (spinbox == ui->spinBox) {
itemName = "葱姜炒肉";
}else if(spinbox == ui->spinBox_5){
itemName = "油炸排骨";
}else if(spinbox == ui->spinBox_32){
itemName = "泡鲁达";
}else if(spinbox == ui->spinBox_20){
itemName = "永昌火瓢牛肉";
}else if(spinbox == ui->spinBox_23){
itemName = "滇西牛扒烀";
}else if(spinbox == ui->spinBox_24){
itemName = "下村豆粉";
}else if(spinbox == ui->spinBox_26){
itemName = "保山火烧肉";
}else if(spinbox == ui->spinBox_29){
itemName = "傣族手抓饭";
}else if(spinbox == ui->spinBox_40){
itemName = "云南凉虾";
}else if(spinbox == ui->spinBox_41){
itemName = "云南甜米酒";
}
//看listWidget内是否已经有该菜
auto tmp = ui->listWidget->findItems(itemName, Qt::MatchContains);
//若有
if (arg1 == 0) {
// 当值为 0 时,删除对应项
if (!tmp.isEmpty()) {
QListWidgetItem *item = tmp.first();
delete item; // 删除找到的第一项
}
} else {
// 当值不为 0 时,增加或更新对应项
if (!tmp.isEmpty()) {
QListWidgetItem *item = tmp.first();
item->setText(QString("%1 %2 份").arg(itemName).arg(arg1)); // 更新数量
} else {
ui->listWidget->addItem(QString("%1 %2 份").arg(itemName).arg(arg1)); // 添加新项
}
}
// 更新 total 显示到 lineEdit 上
total = (ui->spinBox->value() * menuPrices["葱姜炒肉"]) +
(ui->spinBox_5->value() * menuPrices["油炸排骨"]) +
(ui->spinBox_32->value() * menuPrices["泡鲁达"]) +
(ui->spinBox_20->value() * menuPrices["永昌火瓢牛肉"]) +
(ui->spinBox_23->value() * menuPrices["滇西牛扒烀"]) +
(ui->spinBox_24->value() * menuPrices["下村豆粉"]) +
(ui->spinBox_26->value() * menuPrices["保山火烧肉"]) +
(ui->spinBox_29->value() * menuPrices["傣族手抓饭"]) +
(ui->spinBox_40->value() * menuPrices["云南凉虾"]) +
(ui->spinBox_41->value() * menuPrices["云南甜米酒"]);
ui->lineEdit->setText(QString::number(total));
}
//提交订单给服务器
void OrderMenu::on_pushButton_clicked()
{
/*--------------构筑订单内容-------------*/
result.clear();
//添加发送内容类型
result += "ORDER ";
//添加桌号
result += ui->spinBox_2->text() + " ";
//添加菜和价格
for(int i=0; i<ui->listWidget->count(); i++){ //遍历订单内容
QListWidgetItem *tmp = ui->listWidget->item(i);
if(tmp){
result += tmp->text() + "\n";
// result += tmp->text();
}
}
//添加价格信息,与结束标志
result += QString("总计 %1 元 END").arg(ui->lineEdit->text());
qDebug() << result << endl;
//发送订单
socket->write(result.toUtf8());
QThread::usleep(100);
}
//结账
void OrderMenu::on_pushButton_2_clicked()
{
//QMessageBox::information(nullptr, "尊敬的顾客", "服务员正在前往,请稍后...");
msgBox->setText("尊敬的顾客");
msgBox->setInformativeText("服务员正在前往,请稍后...");
msgBox->show();
result.clear();
//添加发送内容类型
result += "PAY ";
//添加桌号和结束标志
result += ui->spinBox_2->text() + " END";
socket->write(result.toUtf8());
QThread::usleep(100);
}
// 呼叫服务员
void OrderMenu::on_pushButton_3_clicked()
{
//QMessageBox::information(nullptr, "尊敬的顾客", "服务员正在前往,请稍后...");
msgBox->setText("尊敬的顾客");
msgBox->setInformativeText("服务员正在前往,请稍后...");
msgBox->show();
result.clear();
//添加发送内容类型
result += "CALL ";
//添加桌号和结束标志
result += ui->spinBox_2->text() + " END";
socket->write(result.toUtf8());
QThread::usleep(100);
}
3. 问题与解决
3.1 界面背景图片设置: 如图所示,这是其中一个界面的样式,可以看到该界面由一个 QWidget、一个QLabel和一个 QPushButton组成;
单纯使用“编辑样式表”,并不能完成以上“仅设置背景的需求”,label 和 button 同样会被设置上图片
- 解决: 使用
QPalette的setBrush方法,即可完成需求
QPixmap pixmap(":/icon/jiemian.jpg");
QPixmap pix = pixmap.scaled(this->size()); //设置背景图片适应窗口尺寸
// 创建调色板
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(pix));
// 应用调色板到窗口
this->setPalette(palette);
3.2 数据交互逻辑有误: 实际上是对 QT 的信号与槽机制的认知有误
- 错误设计:
简单的TCP通信流程:客户端先发消息类型,再发消息体;客户端先接收消息类型,再接收消息体,并做不同的处理,在 C语言网络编程时这种设计是没有问题的;
但在QT中Socket通信是通过信号与槽机制来完成数据交互的:也就是说,“客户端先发消息类型,再发消息体”,触发的都是同一个信号,对于服务器而言两次都是接收消息类型,并执行对消息类型的处理。
connect(socket, &QTcpSocket::readyRead, [&](){
static QByteArray buffer;
buffer.append(socket->readAll());
- 改进设计:模仿
HTTP网络传输的协议,客户端将消息类型和消息体用字符串拼接操作为简单数据体,最后再加上一个结束标志;服务器接受完整消息,并逐步解析解析消息。- 客户端发送订单消息代码
QString result;用于存储消息体
- 客户端发送订单消息代码
//提交订单给服务器
void OrderMenu::on_pushButton_clicked()
{
/*--------------构筑订单内容-------------*/
result.clear();
//添加发送内容类型
result += "ORDER ";//消息头
//添加桌号
result += ui->spinBox_2->text() + " ";
//添加菜和价格
for(int i=0; i<ui->listWidget->count(); i++){ //遍历订单内容
QListWidgetItem *tmp = ui->listWidget->item(i);
if(tmp){
result += tmp->text() + "\n";
// result += tmp->text();
}
}
//添加价格信息,与结束标志
result += QString("总计 %1 元 END").arg(ui->lineEdit->text());
qDebug() << result << endl;
//发送订单
socket->write(result.toUtf8());
QThread::usleep(100);
}
服务器解析消息体
//接收客户端连接,识别消息类型,根据消息类型做不同的事情
void MenuServer::accept_client(){
if (server->hasPendingConnections()) { //判断是否有等待的连接
socket = server->nextPendingConnection(); //接收连接
/*--------识别消息类型,根据消息类型做不同的事情--------*/
connect(socket, &QTcpSocket::readyRead, [&](){
static QByteArray buffer;
buffer.append(socket->readAll());
// 检查是否包含 'END' 分隔符以处理完整消息
int endIndex = buffer.indexOf("END");
QByteArray messageBytes = buffer.left(endIndex); // 获取完整消息(不包括 'END')
// 移除已处理的部分(包括 'END' 的长度)
buffer.remove(0, endIndex + 3);
QString message = QString::fromUtf8(messageBytes).trimmed(); // 转换为 QString,并去除多余空格
// 分析消息内容
QStringList parts = message.split(" ", QString::SkipEmptyParts);
if (parts.size() > 2 && parts[0] == "ORDER") { // 确保有足够的部分并且是 ORDER 消息
QString orderType = parts[0]; // "ORDER"
QString tablenum = parts[1]; // 桌号
// 剩余部分为订单内容,合并成一个字符串(去掉第一部分)
QString orderContent = parts.mid(2).join(" ");
qDebug() << "桌号:" << tablenum;
qDebug() << "订单内容:" << orderContent;
// 判断桌号,并将对应订单信息放到对应框内
if ("1号" == tablenum) {
ui->textEdit->setText(orderContent);
}else if("2号" == tablenum){
ui->textEdit_6->setText(orderContent);
}else if("3号" == tablenum){
ui->textEdit_7->setText(orderContent);
}else if("4号" == tablenum){
ui->textEdit_8->setText(orderContent);
}else if("5号" == tablenum){
ui->textEdit_9->setText(orderContent);
}else if("6号" == tablenum){
ui->textEdit_10->setText(orderContent);
}
}else if(parts[0] == "CALL"){
msgBox->setText("提示");
msgBox->setInformativeText(QString(" %1 号桌顾客呼叫服务员").arg(parts[1]));
msgBox->show();
}else if(parts[0] == "PAY"){
//QMessageBox::information(nullptr, "提示", QString(" %1桌顾客要结账").arg(parts[1]));
msgBox->setText("提示");
msgBox->setInformativeText(QString(" %1 桌顾客要结账").arg(parts[1]));
msgBox->show();
}
});
}
}
特别鸣谢
- 文案:張嘉鑫
- 逻辑视图:張嘉鑫
- 代码:張嘉鑫
- 其余图片:来自网络
更多推荐



所有评论(0)