• qt历史数据管理模块(模块化程序)功能块复制直接使用不冲突


    一、前言

    qt对历史数据管理部分个人总结的模块化程序,直接按照步骤复制粘贴程序,直接实现历史数据管理功能,无需花费脑筋在理清各个思路,适合快速编写组装程序

    二、环境

    windows

    qt5.7

    sqlite3

    三、正文

    建议参照前文,首先把前期准备环境做好,或者直接下载前文程序作为基础模板。icon-default.png?t=M85Bhttps://blog.csdn.net/qq_37603131/article/details/128178726

    这里就不放前文的前期环境准备搭建工作了,直接放内容

    使用图标

     

    1.UI界面

    首先在ui界面中放入10个控件,分别是历史数据表格、数据查询按键、起始时间搜索选择框、截止时间搜索选择框、起始时间设置框、截止时间设置框、设备号选择框、操作人员选择框、设备号输入框、操作员列表选择框,如下图所示布局

    时间设置框的样式表这样好用,使用鼠标点点点就可以修改时间了

    1. QDateTimeEdit{
    2. color: rgb(55, 156, 212);
    3. }
    4. QDateTimeEdit::up-button,QTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button {subcontrol-origin:border;
    5. subcontrol-position:right;
    6. border-image: url(:/PIC/add.png);
    7. width: 30px;
    8. height: 30px;
    9. }
    10. QDateTimeEdit::down-button,QTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button {subcontrol-origin:border;
    11. subcontrol-position:left;
    12. border-image: url(:/PIC/reduce.png);
    13. width: 30px;
    14. height: 30px;
    15. }
    16. QDateTimeEdit::up-button:pressed,QTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{subcontrol-origin:border;
    17. subcontrol-position:right;
    18. border-image: url(:/PIC/add-click.png);
    19. width: 30px;
    20. height: 30px;
    21. }
    22. QDateTimeEdit::down-button:pressed,QTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed{
    23. subcontrol-position:left;
    24. border-image: url(:/PIC/reduce-click.png);
    25. width: 30px;
    26. height: 30px;
    27. }

    2.头文件

    1. #ifndef MAINWINDOW_H
    2. #define MAINWINDOW_H
    3. #include "common.h"
    4. namespace Ui {
    5. class MainWindow;
    6. }
    7. class MainWindow : public QMainWindow
    8. {
    9. Q_OBJECT
    10. public:
    11. explicit MainWindow(QWidget *parent = 0);
    12. ~MainWindow();
    13. signals:
    14. //人员管理
    15. void sendtext(QString,QString,bool,QString,QStringList);//messagebox_user
    16. //历史数据查看
    17. void sendhistorydata(QStringList);//messagebox_history
    18. protected:
    19. //表格滑动刷新
    20. bool eventFilter(QObject *obj, QEvent *event);
    21. private slots:
    22. //人员管理
    23. void on_btn_user_add_clicked();//用户增加
    24. void on_btn_user_change_clicked();//用户修改
    25. void on_btn_user_delete_clicked();//用户删除
    26. void tableWidget_user_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push);
    27. void tableWidget_user_refuse(QTableWidget *TableWidget);//更新用户信息
    28. //历史数据查询模块
    29. void on_btn_datasearch_clicked();//数据查询
    30. void tableWidget_datasearch_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push);
    31. void tableWidget_datasearch_refuse(QTableWidget *TableWidget);//更新历史数据信息
    32. private:
    33. Ui::MainWindow *ui;
    34. QScrollBar *m_scrollBarV;//表格滚动实现
    35. };
    36. #endif // MAINWINDOW_H

    看似这么多,实际就增加3个函数,和一个信号函数

    1. void sendhistorydata(QStringList);//messagebox_history
    2. //历史数据查询模块
    3. void on_btn_datasearch_clicked();//数据查询
    4. void tableWidget_datasearch_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push);
    5. void tableWidget_datasearch_refuse(QTableWidget *TableWidget);//更新历史数据信息

    3.源文件

    1. #include "mainwindow.h"
    2. #include "ui_mainwindow.h"
    3. #include "login.h"
    4. #include "messagebox/messagebox_user.h"
    5. #include "messagebox/messagebox_history.h"
    6. //风格:文字白色,图片选择样式切换
    7. QString QCheckBoxstyle=
    8. "QCheckBox{color: rgb(255, 255, 255);background-color: rgb(0, 0, 0);}"
    9. "QCheckBox::indicator{background-color: rgba(255, 255, 255, 0);border: 0px solid #b1b1b1;width: 30px;height: 30px;}"
    10. "QCheckBox::indicator:unchecked {image:url(:/PIC/未选择.png);}"
    11. "QCheckBox::indicator:unchecked:hover {image:url(:/PIC/未选择.png);}"
    12. "QCheckBox::indicator:unchecked:pressed {image:url(:/PIC/未选择.png);}"
    13. "QCheckBox::indicator:checked {image:url(:/PIC/选择.png);}"
    14. "QCheckBox::indicator:checked:hover {image:url(:/PIC/选择.png);}"
    15. "QCheckBox::indicator:checked:pressed {image:url(:/PIC/选择.png);}";
    16. QString btnwhite="color: rgb(255, 255, 255);";
    17. QString btngreen="color: rgb(0, 255, 0);";
    18. QString btnblue="color: rgb(20, 50, 255);";
    19. QString btngray="color: rgb(160, 160, 160);";
    20. QString btnstyle=
    21. "background-color: rgba(255, 255, 255, 0);"
    22. "border-right:0px solid #9eca56; "
    23. "border-bottom:0px solid #9eca56;"
    24. "border-left:0px solid #9eca56;"
    25. "border-top:0px solid #9eca56; "
    26. "font: 75 italic 12pt '宋体';"
    27. "text-decoration: underline;";
    28. MainWindow::MainWindow(QWidget *parent) :
    29. QMainWindow(parent),
    30. ui(new Ui::MainWindow)
    31. {
    32. ui->setupUi(this);
    33. this->setWindowModality(Qt::ApplicationModal);//设置一直保持在顶端,不可切换其他界面,除非被新界面带此属性覆盖
    34. this->setWindowFlags(Qt::Dialog);//取消窗口最大化最小化按钮
    35. //用户管理初始化
    36. QVector<int> table_line;
    37. table_line.append({140,600,400,100,300});//配置表格宽度5列
    38. QStringList table_title;
    39. table_title.append({"序号","账号(操作人员)","密码","管理员","备注"});//配置表格表头名称5列
    40. m_scrollBarV = ui->tableWidget_user->verticalScrollBar();//绑定表格滑动效//在表格第一次初始化前必须初始化一次,否则程序崩溃
    41. tableWidget_user_init(ui->tableWidget_user,table_line,table_title,true);//初始化表格表头
    42. tableWidget_user_refuse(ui->tableWidget_user);//更新全部用户信息
    43. connect(ui->tableWidget_user,&QTableWidget::cellPressed,[=](){//点击表格某行生效,当需要滑动时必定点击到表格某行。
    44. m_scrollBarV = ui->tableWidget_user->verticalScrollBar();//绑定表格滑动效
    45. });
    46. //历史数据初始化
    47. QVector<int> historytable_line;
    48. historytable_line.append({150,400,200,200,200,300});//配置表格宽度6列
    49. QStringList historytable_title;
    50. historytable_title.append({"序号","日期","操作员","设备号","健康状态","功能"});//配置表格表头名称6列
    51. m_scrollBarV = ui->tableWidget_history->verticalScrollBar();//绑定表格滑动效//在表格第一次初始化前必须初始化一次,否则程序崩溃
    52. tableWidget_datasearch_init(ui->tableWidget_history,historytable_line,historytable_title,true);//初始化表格表头
    53. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    54. connect(ui->tableWidget_history,&QTableWidget::cellPressed,[=](){//点击表格某行生效,当需要滑动时必定点击到表格某行。
    55. m_scrollBarV = ui->tableWidget_history->verticalScrollBar();//绑定表格滑动效
    56. });
    57. }
    58. MainWindow::~MainWindow()
    59. {
    60. delete ui;
    61. }
    62. /*****************************人员管理处理函数**********************************/
    63. //用户增加
    64. void MainWindow::on_btn_user_add_clicked()
    65. {
    66. MessageBox_user * box = new MessageBox_user();
    67. box->show();
    68. box->setAttribute(Qt::WA_DeleteOnClose);//若是关闭界面,则彻底释放资源
    69. connect(this,SIGNAL(sendtext(QString,QString,bool,QString,QStringList)),box,SLOT(receivetext(QString,QString,bool,QString,QStringList)));
    70. emit sendtext("","",false,"",Data_user.usernamelist);
    71. connect(box,&MessageBox_user::sendtext,[=](QString res1,QString res2,bool res3,QString res4){
    72. qDebug()<
    73. QSqlQuery qry(db);//数据库保存
    74. qry.exec(QString("INSERT INTO Sys_user(name,password,root,note) VALUES ('%1','%2','%3','%4');").arg(res1).arg(res2).arg(res3).arg(res4));
    75. tableWidget_user_refuse(ui->tableWidget_user);//更新全部用户信息
    76. });
    77. }
    78. //用户修改
    79. void MainWindow::on_btn_user_change_clicked()
    80. {
    81. int checkid=0;
    82. QList checkList = ui->tableWidget_user->findChildren();
    83. for(int i=0;isize();i++){//遍历寻找是否有选中
    84. if(checkList.at(i)->isChecked()){//如果找到选中,则跳出循环
    85. checkid=checkList.at(i)->objectName().replace("QCheckBox_user_","").toInt();//替换控件名称转换为表格所在的行
    86. break;//跳出寻找
    87. }
    88. }
    89. if(checkid>0){//有选中的行
    90. MessageBox_user * box = new MessageBox_user();
    91. box->show();
    92. box->setAttribute(Qt::WA_DeleteOnClose);//若是关闭界面,则彻底释放资源
    93. connect(this,SIGNAL(sendtext(QString,QString,bool,QString,QStringList)),box,SLOT(receivetext(QString,QString,bool,QString,QStringList)));
    94. QString change_username=Data_user.usernamelist.at(checkid-1);//赋值选中用户名称
    95. QString change_userpassword=Data_user.userpswdlist.at(checkid-1);//赋值选中用户密码
    96. bool change_userroot=Data_user.userroot.at(checkid-1);//赋值选中用户权限
    97. QString change_usernote=Data_user.usernote.at(checkid-1);//赋值选中用户备注
    98. emit sendtext(change_username,change_userpassword,change_userroot,change_usernote,Data_user.usernamelist);
    99. connect(box,&MessageBox_user::sendtext,[=](QString res1,QString res2,bool res3,QString res4){
    100. // qDebug()<
    101. //判断修改的用户是否为当前用户
    102. int err_flag=0;//错误编号
    103. if(change_username==Data_user.now_username){
    104. if(res3==0){
    105. res3=1;
    106. err_flag=1;//降低本身权限,可能导致没有账号能进入管理员系统
    107. }
    108. //赋值登录信息到当前用户信息缓存
    109. Data_user.now_username=res1;//当前用户
    110. Data_user.now_password=res2;//当前密码
    111. Data_user.now_root=res3;//赋值权限
    112. Data_user.now_noted=res4;//赋值备注
    113. //存储当前登录界面配置信息到数据库
    114. QSqlQuery qry(db);//数据库保存
    115. qry.exec(QString("UPDATE Sys_userlast set username='%1',userpassword='%2' where id = 1;").arg(res1).arg(res2));
    116. //刷新当前用户名称
    117. // ui->label_name->setText(QString("当前用户:%1").arg(Data_user.now_username));
    118. }
    119. QSqlQuery qry(db);//数据库保存
    120. qry.exec(QString("UPDATE Sys_user set name='%1',password='%2',root='%3' ,note='%4'where name = '%5';").arg(res1).arg(res2).arg(res3).arg(res4).arg(change_username));
    121. tableWidget_user_refuse(ui->tableWidget_user);//更新全部用户信息
    122. if(err_flag==1)
    123. massage_dialog(1,"提示","用户信息修改成功!\r\n(不可修改自身权限,已自动恢复)",1);
    124. else
    125. massage_dialog(1,"提示","用户信息修改成功!",1);
    126. });
    127. }
    128. else{
    129. massage_dialog(1,"提示","请选择要修改的用户列表序号!",1);
    130. }
    131. }
    132. //用户删除
    133. void MainWindow::on_btn_user_delete_clicked()
    134. {
    135. int checkid=0;
    136. QList checkList = ui->tableWidget_user->findChildren();
    137. for(int i=0;isize();i++){//遍历寻找是否有选中
    138. if(checkList.at(i)->isChecked()){//如果找到选中,则跳出循环
    139. checkid=checkList.at(i)->objectName().replace("QCheckBox_user_","").toInt();//替换控件名称转换为表格所在的行
    140. break;//跳出寻找
    141. }
    142. }
    143. if(checkid>0){//有选中的行
    144. if(Data_user.usernamelist.at(checkid-1)==Data_user.now_username){
    145. massage_dialog(1,"提示","不可以删除当前登录用户!",1);
    146. }
    147. else{
    148. int res=massage_dialog(2,"提示",QString("是否确认删除用户%1").arg(Data_user.usernamelist.at(checkid-1)),4);
    149. if(res==0){//点击确认返回0,点击取消或关闭返回1
    150. QSqlQuery qry(db);
    151. qry.exec(QString("delete from Sys_user where name = '%1'").arg(Data_user.usernamelist.at(checkid-1)));
    152. qry.prepare("vacuum");//删除成功之后释放内存碎片
    153. qry.exec();
    154. tableWidget_user_refuse(ui->tableWidget_user);//更新全部用户信息
    155. massage_dialog(1,"提示","删除用户成功!",1);
    156. }
    157. }
    158. }
    159. else{
    160. massage_dialog(1,"提示","请选择要删除的用户列表序号!",1);
    161. }
    162. }
    163. //通用表格初始化函数,建立表格列,刷新表头和列宽
    164. void MainWindow::tableWidget_user_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push)
    165. {
    166. tab->clearContents();//清空内容
    167. tab->verticalHeader()->setVisible(false);//去掉行序号
    168. tab->horizontalHeader()->setFixedHeight(40); //设置表头的高度
    169. tab->horizontalHeader()->setStretchLastSection(true);//设置表格是否充满,即行末不留空
    170. //tab->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); //禁止鼠标拖放列宽度
    171. tab->horizontalHeader()->setFocusPolicy(Qt::NoFocus); //设置表头不可选
    172. tab->horizontalHeader()->setHighlightSections(false); //设置表头不可选//QTableWidget表头塌陷问题解决
    173. tab->setEditTriggers(QAbstractItemView::NoEditTriggers);//设置表格内容不可修改
    174. tab->setSelectionBehavior(QAbstractItemView::SelectRows);//设置选中就是一行选中
    175. tab->setSelectionMode(QAbstractItemView::SingleSelection);//设置只能选中一行
    176. tab->setFocusPolicy(Qt::NoFocus);//设置去掉选中虚线框
    177. //tab->setAlternatingRowColors(true);//设置表格颜色交替
    178. //表格滚动部分实现函数:初始化函数+以下俩函数+ (QObject *obj, QEvent *event)
    179. if(push)tab->viewport()->installEventFilter(this);//对此对象安装事件过滤器
    180. if(push)tab->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);//设置滚动模式按照像素滑动,做表格滑动时使用
    181. tab->setColumnCount(line.size());//设置列数量
    182. for(int i=0;isize();i++)
    183. tab->setColumnWidth(i,line.at(i));
    184. tab->setHorizontalHeaderLabels(name);//设置table列表各项标题
    185. tab->setRowCount(0);
    186. }
    187. //更新用户信息
    188. void MainWindow::tableWidget_user_refuse(QTableWidget *TableWidget)
    189. {
    190. Data_user.usernamelist.clear();//清空用户账号
    191. Data_user.userpswdlist.clear();//清空用户密码
    192. Data_user.userroot.clear();//清空用户权限
    193. Data_user.usernote.clear();//清空用户备注
    194. QSqlQuery qry(db);
    195. if(qry.exec("select * from Sys_user")){
    196. for(int i=0;qry.next()&&i<1000;i++){
    197. Data_user.usernamelist.append(qry.value(0).toString());//赋值用户账号
    198. Data_user.userpswdlist.append(qry.value(1).toString());//赋值用户密码
    199. Data_user.userroot.append(qry.value(2).toBool());//赋值用户权限
    200. Data_user.usernote.append(qry.value(3).toString());//赋值用户备注
    201. }
    202. }
    203. TableWidget->clearContents();//清空内容
    204. TableWidget->setRowCount(Data_user.usernamelist.size());//设置表格行数
    205. //删除上次表格的复选框控件
    206. QList checkList = TableWidget->findChildren();
    207. for(int i=0;isize();i++)delete checkList[i];
    208. //删除上次表格的按键控件
    209. QList btnList = TableWidget->findChildren();
    210. for(int i=0;isize();i++)delete btnList[i];
    211. for(int i=0;isize();i++){ //刷新信号列表
    212. //表格插入复选框
    213. QCheckBox *lineid=new QCheckBox;
    214. lineid->setObjectName(tr("QCheckBox_user_%1").arg(i+1));
    215. TableWidget->setCellWidget(i,0,lineid);//插入按键到表格指定行列
    216. lineid->setText(QString("%1").arg(i+1,2,10,QChar('0')));//设置文本
    217. lineid->setStyleSheet(QCheckBoxstyle);//设置风格
    218. lineid->setFont(QFont("隶书", 12, QFont::Bold));//设置文字
    219. lineid->setAutoExclusive(true);//设置只有一个复选框可以选择,其他的自动失效
    220. TableWidget->setItem(i,1,new QTableWidgetItem(Data_user.usernamelist.at(i)));//更新账号
    221. TableWidget->item(i,1)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    222. TableWidget->setItem(i,2,new QTableWidgetItem(Data_user.userpswdlist.at(i)));//更新密码
    223. TableWidget->item(i,2)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    224. if(Data_user.userroot.at(i))
    225. TableWidget->setItem(i,3,new QTableWidgetItem("是"));//更新管理员
    226. else
    227. TableWidget->setItem(i,3,new QTableWidgetItem("否"));//更新管理员
    228. TableWidget->item(i,3)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    229. QPushButton *aaa=new QPushButton;
    230. aaa->setObjectName(tr("btn_page5_usertable_1_%1").arg(i+1));
    231. TableWidget->setCellWidget(i,4,aaa);//插入按键到表格指定行列
    232. aaa->setText("备注信息");//设置文本
    233. QString styles=btnwhite+btnstyle;styles.replace("12pt","16pt");
    234. aaa->setStyleSheet(styles);//设置风格,加大字号
    235. connect(aaa,&QPushButton::clicked,[=](){//绑定按键点击回调槽函数
    236. massage_dialog(1,"备注信息",Data_user.usernote.at(i),1);
    237. });
    238. TableWidget->setRowHeight(i,50);//设置行高
    239. }
    240. // TableWidget->resizeRowsToContents();//根据单元格内容自动换行
    241. }
    242. /*****************************历史管理处理函数**********************************/
    243. //数据查询
    244. void MainWindow::on_btn_datasearch_clicked()
    245. {
    246. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    247. }
    248. //通用表格初始化函数,建立表格列,刷新表头和列宽
    249. void MainWindow::tableWidget_datasearch_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push)
    250. {
    251. tab->clearContents();//清空内容
    252. tab->verticalHeader()->setVisible(false);//去掉行序号
    253. tab->horizontalHeader()->setFixedHeight(40); //设置表头的高度
    254. tab->horizontalHeader()->setStretchLastSection(true);//设置表格是否充满,即行末不留空
    255. //tab->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); //禁止鼠标拖放列宽度
    256. tab->horizontalHeader()->setFocusPolicy(Qt::NoFocus); //设置表头不可选
    257. tab->horizontalHeader()->setHighlightSections(false); //设置表头不可选//QTableWidget表头塌陷问题解决
    258. tab->setEditTriggers(QAbstractItemView::NoEditTriggers);//设置表格内容不可修改
    259. tab->setSelectionBehavior(QAbstractItemView::SelectRows);//设置选中就是一行选中
    260. tab->setSelectionMode(QAbstractItemView::SingleSelection);//设置只能选中一行
    261. tab->setFocusPolicy(Qt::NoFocus);//设置去掉选中虚线框
    262. //tab->setAlternatingRowColors(true);//设置表格颜色交替
    263. //表格滚动部分实现函数:初始化函数+以下俩函数+ (QObject *obj, QEvent *event)
    264. if(push)tab->viewport()->installEventFilter(this);//对此对象安装事件过滤器
    265. if(push)tab->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);//设置滚动模式按照像素滑动,做表格滑动时使用
    266. tab->setColumnCount(line.size());//设置列数量
    267. for(int i=0;isize();i++)
    268. tab->setColumnWidth(i,line.at(i));
    269. tab->setHorizontalHeaderLabels(name);//设置table列表各项标题
    270. tab->setRowCount(0);
    271. }
    272. //更新历史数据信息,默认包含日期筛选,人员筛选,编码筛选,如有不同功能更改相应部分
    273. void MainWindow::tableWidget_datasearch_refuse(QTableWidget *TableWidget)
    274. {
    275. //更新页面控件操作员列表,每次都更新,以防人员管理改变
    276. QString lastname=ui->comboBox_user->currentText();
    277. ui->comboBox_user->clear();//清空操作员列表
    278. ui->comboBox_user->addItems(Data_user.usernamelist);//放入最新操作员列表
    279. ui->comboBox_user->setCurrentText(lastname);
    280. //筛选搜索数据库数据
    281. QStringList searchterm;//筛选条件,默认包含日期筛选,人员筛选,编码筛选,如有不同功能更改相应部分
    282. if(ui->checkBox_starttime->isChecked())//判断起始日期是否包含
    283. searchterm.append(QString("(time>'%1')").arg(ui->dateTimeEdit_start->dateTime().toString("yyyy-MM-dd")));//time是数据库表头
    284. if(ui->checkBox_endtime->isChecked())//判断截止日期是否包含
    285. searchterm.append(QString("(time<'%1')").arg(ui->dateTimeEdit_end->dateTime().toString("yyyy-MM-dd")));//time是数据库表头
    286. if(ui->checkBox_code->isChecked())//判断设备号是否包含
    287. searchterm.append(QString("code is '%1'").arg(ui->lineEdit_code->text()));//code是数据库表头
    288. if(ui->checkBox_user->isChecked())//判断操作员是否包含
    289. searchterm.append(QString("name is '%1'").arg(ui->comboBox_user->currentText()));//name是数据库表头
    290. QSqlQuery qry(db);
    291. QString sql=QString("select * from Sys_history");
    292. if(searchterm.size()>0){//判断是否有筛选条件
    293. sql.append(" where ");
    294. for(int i=0;isize();i++){
    295. sql.append(searchterm.at(i));//添加筛选内容
    296. if(i<(searchterm.size()-1))
    297. sql.append(" and ");//添加连接符,最后一个不添加
    298. }
    299. }
    300. ui->btn_datasearch->setToolTip(sql);
    301. // qDebug()<
    302. QStringList searchdata[7];//搜索数据,本程序数据库包含7列,如更换不同程序数据列不同更改相应部分
    303. if(qry.exec(sql)){
    304. for(int i=0;qry.next()&&i<10000;i++){
    305. searchdata[0].append(qry.value(0).toString());//赋值日期时间
    306. searchdata[1].append(qry.value(1).toString());//赋值操作员
    307. searchdata[2].append(qry.value(2).toString());//赋值设备编码
    308. searchdata[3].append(qry.value(3).toString());//赋值数据组合体1
    309. searchdata[4].append(qry.value(4).toString());//赋值数据组合体2
    310. searchdata[5].append(qry.value(5).toString());//赋值数据组合体3
    311. searchdata[6].append(qry.value(6).toString());//赋值设备状况分数
    312. }
    313. }
    314. //更新表格显示
    315. TableWidget->clearContents();//清空内容
    316. TableWidget->setRowCount(searchdata[0].size());//设置表格行数
    317. //删除上次表格的控件,这里必须先删除widget里面的控件,后删除widget,否则程序运行崩溃
    318. QList btnList = TableWidget->findChildren();
    319. for(int i=0;isize();i++)delete btnList[i];//这里仍然要删除按键,没有因为按键在widget里先删除widget而被删除,还得先删除按键,否则崩溃
    320. QList widgetList = TableWidget->findChildren();
    321. for(int i=0;isize();i++){
    322. if(widgetList.at(i)->objectName().contains("widget_historytable_"))//判断是表格创建控件,才刷新
    323. delete widgetList[i];//删除表格中新建的widget,根据object命名,避免错误删除其他widget
    324. }
    325. for(int i=0;i0].size();i++){ //刷新信号列表
    326. TableWidget->setItem(i,0,new QTableWidgetItem(QString("%1").arg(i+1,2,10,QChar('0'))));//更新序号
    327. TableWidget->item(i,0)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    328. TableWidget->setItem(i,1,new QTableWidgetItem(searchdata[0].at(i)));//更新历史数据存储日期时间
    329. TableWidget->item(i,1)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    330. TableWidget->setItem(i,2,new QTableWidgetItem(searchdata[1].at(i)));//更新历史数据操作员
    331. TableWidget->item(i,2)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    332. TableWidget->setItem(i,3,new QTableWidgetItem(searchdata[2].at(i)));//更新历史数据设备编码
    333. TableWidget->item(i,3)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    334. TableWidget->setItem(i,4,new QTableWidgetItem(searchdata[6].at(i)+"%"));//更新历史数据设备状态分数
    335. TableWidget->item(i,4)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    336. //创建水平布局
    337. QHBoxLayout *vLayout = new QHBoxLayout(this);//创建布局,布局不需要单独删除,会随着widget删除
    338. vLayout->setContentsMargins(10, 2, 10, 2);// 设置外间距left,top,right,bottom
    339. vLayout->setSpacing(10);//设置内间距
    340. //创建widget放入单元格中,在widget里放入其他控件
    341. QWidget *widget = new QWidget(this);
    342. widget->setObjectName(tr("widget_historytable_%1").arg(i+1));
    343. widget->setStyleSheet("background-color: #000000;");
    344. //创建按键1,根据需求更改创建的控件和数量
    345. QPushButton *aaa=new QPushButton(this);//创建按键控件,按键控件需要单独删除,不会随着widget删除
    346. aaa->setObjectName(tr("btn_table_look%1").arg(i+1));
    347. aaa->setText("数据查看");//设置文本
    348. aaa->setStyleSheet("font: 16pt ""黑体"";background-color: #000000;color: rgb(202, 234, 206);border-radius:10px;");//设置风格
    349. aaa->setMinimumSize(140,45);//设置按键控件最小尺寸
    350. aaa->setIcon(QIcon(":/PIC/查看1.png"));//设置图标
    351. aaa->setIconSize(QSize(40,40));//设置图标尺寸
    352. //创建按键2,根据需求更改创建的控件和数量
    353. QPushButton *bbb=new QPushButton(this);//创建按键控件,按键控件需要单独删除,不会随着widget删除
    354. bbb->setObjectName(tr("btn_table_delete%1").arg(i+1));
    355. bbb->setText("数据删除");//设置文本
    356. bbb->setStyleSheet("font: 16pt ""黑体"";background-color: #000000;color: rgb(202, 234, 206);border-radius:10px;");//设置风格
    357. bbb->setMinimumSize(140,45);//设置按键控件最小尺寸
    358. bbb->setIcon(QIcon(":/PIC/删除.png"));//设置图标
    359. bbb->setIconSize(QSize(40,40));//设置图标尺寸
    360. //将widget放入表格单元格中
    361. vLayout->addWidget(aaa);//将按键放入布局中
    362. vLayout->addWidget(bbb);//将按键放入布局中
    363. widget->setLayout(vLayout);//将布局放入widget中
    364. TableWidget->setCellWidget(i,5,widget);//插入widget到表格指定行列
    365. widget->show();//显示widget
    366. TableWidget->setRowHeight(i,50);//设置行高
    367. //绑定自定义控件按键点击槽函数,根据需求更改创建的控件和数量
    368. connect(aaa,&QPushButton::clicked,[=](){//绑定按键点击回调槽函数
    369. messagebox_history * box = new messagebox_history();
    370. box->show();
    371. box->setAttribute(Qt::WA_DeleteOnClose);//若是关闭界面,则彻底释放资源
    372. connect(this,SIGNAL(sendhistorydata(QStringList)),box,SLOT(receivedata(QStringList)));
    373. QStringList choosehistory;//容器 存放选择的信号所有列内容
    374. for(int j=0;j<7;j++)choosehistory.append(searchdata[j].at(i));
    375. emit sendhistorydata(choosehistory);//发送选择的历史数据信号内容
    376. });
    377. connect(bbb,&QPushButton::clicked,[=](){//绑定按键点击回调槽函数
    378. int res=massage_dialog(2,"提示",QString("是否确认删除序号%1数据?\r\n日期:%2\r\n操作员:%3\r\n设备号:%4")
    379. .arg(QString("%1").arg(i+1,2,10,QChar('0')))
    380. .arg(searchdata[0].at(i)).arg(searchdata[1].at(i)).arg(searchdata[2].at(i)),4);
    381. if(res==0){//点击确认返回0,点击取消或关闭返回1
    382. QSqlQuery qry(db);
    383. qry.exec(QString("delete from Sys_history where time = '%1'").arg(searchdata[0].at(i)));
    384. qry.prepare("vacuum");//删除成功之后释放内存碎片
    385. qry.exec();
    386. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    387. massage_dialog(1,"提示","删除数据成功!",1);
    388. }
    389. });
    390. }
    391. // TableWidget->resizeRowsToContents();//根据单元格内容自动换行
    392. }
    393. /*****************************通用处理函数**********************************/
    394. //滑动表格实现函数
    395. bool MainWindow::eventFilter(QObject *obj, QEvent *event)
    396. {
    397. static signed int press_y = 0;
    398. static signed int move_y = -1;
    399. static signed int release_y = 0;
    400. static QDateTime pressDateTime;
    401. static QPropertyAnimation *animation = new QPropertyAnimation();
    402. // qDebug()<objectName()<type();
    403. if("qt_scrollarea_viewport" != obj->objectName())
    404. return false;
    405. int scrollV_max = m_scrollBarV->maximum();//获取当前滚动控件的最大进度条值
    406. int scrollV_min = m_scrollBarV->minimum();//获取当前滚动控件的最小进度条值,基本是0
    407. #ifdef WINDOWS
    408. int global_y=QCursor::pos().y();//相对于正屏幕的y坐标,window下此方法即可获取
    409. #else
    410. QMouseEvent *event1 = (QMouseEvent *)event;
    411. int global_y=event1->globalPos().y();//相对于正屏幕的y坐标,linux下用此方法获取
    412. #endif
    413. //根据鼠标的动作——按下、放开、拖动,执行相应的操作
    414. if(event->type() == QEvent::MouseButtonPress){//记录按下的时间、坐标
    415. pressDateTime = QDateTime::currentDateTime();
    416. move_y = global_y;
    417. press_y = move_y;
    418. animation->stop();
    419. }
    420. else if(event->type() == QEvent::MouseButtonRelease){//鼠标放开,根据鼠标拖动的垂直距离和持续时间,设置窗口滚动快慢程度和距离
    421. if(animation->targetObject() != m_scrollBarV){
    422. animation->setTargetObject(m_scrollBarV);
    423. animation->setPropertyName("value");
    424. }
    425. move_y = -1;
    426. release_y = global_y;
    427. QObject *parent_obj = obj->parent();
    428. if(parent_obj != 0 || parent_obj->inherits("QAbstractItemView"))
    429. QTimer::singleShot(150, (QAbstractItemView *)parent_obj, SLOT(clearSelection()));
    430. int endValue;
    431. int pageStep;
    432. if(release_y - press_y != 0 && qAbs(release_y - press_y) > 45){
    433. int mseconds = pressDateTime.msecsTo(QDateTime::currentDateTime());
    434. int limit = 440;
    435. pageStep = 240;//scrollBarV->pageStep();
    436. if(mseconds > limit)//滑动的时间大于某个值的时候,不再滚动(通过增加分母)
    437. mseconds = mseconds + (mseconds - limit) * 20;
    438. if(release_y - press_y > 0){
    439. endValue = m_scrollBarV->value()- pageStep * (200.0 / mseconds);//.0避免避免强制转换为整形
    440. if(scrollV_min > endValue)endValue = scrollV_min;
    441. }
    442. else if(release_y - press_y < 0){
    443. endValue = m_scrollBarV->value() + pageStep * (200.0 / mseconds);
    444. if(endValue > scrollV_max)
    445. endValue = scrollV_max;
    446. }
    447. if(mseconds > limit)mseconds = 0;//滑动的时间大于某个值的时候,滚动距离变小,减小滑动的时间
    448. animation->setDuration(mseconds+550);
    449. animation->setEndValue(endValue);
    450. animation->setEasingCurve(QEasingCurve::OutQuad);
    451. animation->start();
    452. return true;
    453. }
    454. }
    455. else if(event->type() == QEvent::MouseMove && move_y >= 0){//窗口跟着鼠标移动
    456. int move_distance = global_y - move_y;
    457. int endValue = m_scrollBarV->value() - move_distance;
    458. if(scrollV_min > endValue)endValue = scrollV_min;
    459. if(endValue > scrollV_max)endValue = scrollV_max;
    460. m_scrollBarV->setValue(endValue);
    461. move_y = global_y;
    462. }
    463. return false;
    464. }

    这里也是,看似很多,实际包含了上篇文章的人员管理部分模板, 实际数据管理内容增加如下

    1. //历史数据初始化
    2. QVector<int> historytable_line;
    3. historytable_line.append({150,400,200,200,200,300});//配置表格宽度6列
    4. QStringList historytable_title;
    5. historytable_title.append({"序号","日期","操作员","设备号","健康状态","功能"});//配置表格表头名称6列
    6. m_scrollBarV = ui->tableWidget_history->verticalScrollBar();//绑定表格滑动效//在表格第一次初始化前必须初始化一次,否则程序崩溃
    7. tableWidget_datasearch_init(ui->tableWidget_history,historytable_line,historytable_title,true);//初始化表格表头
    8. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    9. connect(ui->tableWidget_history,&QTableWidget::cellPressed,[=](){//点击表格某行生效,当需要滑动时必定点击到表格某行。
    10. m_scrollBarV = ui->tableWidget_history->verticalScrollBar();//绑定表格滑动效
    11. });
    12. /*****************************历史管理处理函数**********************************/
    13. //数据查询
    14. void MainWindow::on_btn_datasearch_clicked()
    15. {
    16. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    17. }
    18. //通用表格初始化函数,建立表格列,刷新表头和列宽
    19. void MainWindow::tableWidget_datasearch_init(QTableWidget *tab,QVector<int> line,QStringList name,bool push)
    20. {
    21. tab->clearContents();//清空内容
    22. tab->verticalHeader()->setVisible(false);//去掉行序号
    23. tab->horizontalHeader()->setFixedHeight(40); //设置表头的高度
    24. tab->horizontalHeader()->setStretchLastSection(true);//设置表格是否充满,即行末不留空
    25. //tab->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); //禁止鼠标拖放列宽度
    26. tab->horizontalHeader()->setFocusPolicy(Qt::NoFocus); //设置表头不可选
    27. tab->horizontalHeader()->setHighlightSections(false); //设置表头不可选//QTableWidget表头塌陷问题解决
    28. tab->setEditTriggers(QAbstractItemView::NoEditTriggers);//设置表格内容不可修改
    29. tab->setSelectionBehavior(QAbstractItemView::SelectRows);//设置选中就是一行选中
    30. tab->setSelectionMode(QAbstractItemView::SingleSelection);//设置只能选中一行
    31. tab->setFocusPolicy(Qt::NoFocus);//设置去掉选中虚线框
    32. //tab->setAlternatingRowColors(true);//设置表格颜色交替
    33. //表格滚动部分实现函数:初始化函数+以下俩函数+ (QObject *obj, QEvent *event)
    34. if(push)tab->viewport()->installEventFilter(this);//对此对象安装事件过滤器
    35. if(push)tab->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);//设置滚动模式按照像素滑动,做表格滑动时使用
    36. tab->setColumnCount(line.size());//设置列数量
    37. for(int i=0;isize();i++)
    38. tab->setColumnWidth(i,line.at(i));
    39. tab->setHorizontalHeaderLabels(name);//设置table列表各项标题
    40. tab->setRowCount(0);
    41. }
    42. //更新历史数据信息,默认包含日期筛选,人员筛选,编码筛选,如有不同功能更改相应部分
    43. void MainWindow::tableWidget_datasearch_refuse(QTableWidget *TableWidget)
    44. {
    45. //更新页面控件操作员列表,每次都更新,以防人员管理改变
    46. QString lastname=ui->comboBox_user->currentText();
    47. ui->comboBox_user->clear();//清空操作员列表
    48. ui->comboBox_user->addItems(Data_user.usernamelist);//放入最新操作员列表
    49. ui->comboBox_user->setCurrentText(lastname);
    50. //筛选搜索数据库数据
    51. QStringList searchterm;//筛选条件,默认包含日期筛选,人员筛选,编码筛选,如有不同功能更改相应部分
    52. if(ui->checkBox_starttime->isChecked())//判断起始日期是否包含
    53. searchterm.append(QString("(time>'%1')").arg(ui->dateTimeEdit_start->dateTime().toString("yyyy-MM-dd")));//time是数据库表头
    54. if(ui->checkBox_endtime->isChecked())//判断截止日期是否包含
    55. searchterm.append(QString("(time<'%1')").arg(ui->dateTimeEdit_end->dateTime().toString("yyyy-MM-dd")));//time是数据库表头
    56. if(ui->checkBox_code->isChecked())//判断设备号是否包含
    57. searchterm.append(QString("code is '%1'").arg(ui->lineEdit_code->text()));//code是数据库表头
    58. if(ui->checkBox_user->isChecked())//判断操作员是否包含
    59. searchterm.append(QString("name is '%1'").arg(ui->comboBox_user->currentText()));//name是数据库表头
    60. QSqlQuery qry(db);
    61. QString sql=QString("select * from Sys_history");
    62. if(searchterm.size()>0){//判断是否有筛选条件
    63. sql.append(" where ");
    64. for(int i=0;isize();i++){
    65. sql.append(searchterm.at(i));//添加筛选内容
    66. if(i<(searchterm.size()-1))
    67. sql.append(" and ");//添加连接符,最后一个不添加
    68. }
    69. }
    70. ui->btn_datasearch->setToolTip(sql);
    71. // qDebug()<
    72. QStringList searchdata[7];//搜索数据,本程序数据库包含7列,如更换不同程序数据列不同更改相应部分
    73. if(qry.exec(sql)){
    74. for(int i=0;qry.next()&&i<10000;i++){
    75. searchdata[0].append(qry.value(0).toString());//赋值日期时间
    76. searchdata[1].append(qry.value(1).toString());//赋值操作员
    77. searchdata[2].append(qry.value(2).toString());//赋值设备编码
    78. searchdata[3].append(qry.value(3).toString());//赋值数据组合体1
    79. searchdata[4].append(qry.value(4).toString());//赋值数据组合体2
    80. searchdata[5].append(qry.value(5).toString());//赋值数据组合体3
    81. searchdata[6].append(qry.value(6).toString());//赋值设备状况分数
    82. }
    83. }
    84. //更新表格显示
    85. TableWidget->clearContents();//清空内容
    86. TableWidget->setRowCount(searchdata[0].size());//设置表格行数
    87. //删除上次表格的控件,这里必须先删除widget里面的控件,后删除widget,否则程序运行崩溃
    88. QList btnList = TableWidget->findChildren();
    89. for(int i=0;isize();i++)delete btnList[i];//这里仍然要删除按键,没有因为按键在widget里先删除widget而被删除,还得先删除按键,否则崩溃
    90. QList widgetList = TableWidget->findChildren();
    91. for(int i=0;isize();i++){
    92. if(widgetList.at(i)->objectName().contains("widget_historytable_"))//判断是表格创建控件,才刷新
    93. delete widgetList[i];//删除表格中新建的widget,根据object命名,避免错误删除其他widget
    94. }
    95. for(int i=0;i0].size();i++){ //刷新信号列表
    96. TableWidget->setItem(i,0,new QTableWidgetItem(QString("%1").arg(i+1,2,10,QChar('0'))));//更新序号
    97. TableWidget->item(i,0)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    98. TableWidget->setItem(i,1,new QTableWidgetItem(searchdata[0].at(i)));//更新历史数据存储日期时间
    99. TableWidget->item(i,1)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    100. TableWidget->setItem(i,2,new QTableWidgetItem(searchdata[1].at(i)));//更新历史数据操作员
    101. TableWidget->item(i,2)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    102. TableWidget->setItem(i,3,new QTableWidgetItem(searchdata[2].at(i)));//更新历史数据设备编码
    103. TableWidget->item(i,3)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    104. TableWidget->setItem(i,4,new QTableWidgetItem(searchdata[6].at(i)+"%"));//更新历史数据设备状态分数
    105. TableWidget->item(i,4)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);//数据居中
    106. //创建水平布局
    107. QHBoxLayout *vLayout = new QHBoxLayout(this);//创建布局,布局不需要单独删除,会随着widget删除
    108. vLayout->setContentsMargins(10, 2, 10, 2);// 设置外间距left,top,right,bottom
    109. vLayout->setSpacing(10);//设置内间距
    110. //创建widget放入单元格中,在widget里放入其他控件
    111. QWidget *widget = new QWidget(this);
    112. widget->setObjectName(tr("widget_historytable_%1").arg(i+1));
    113. widget->setStyleSheet("background-color: #000000;");
    114. //创建按键1,根据需求更改创建的控件和数量
    115. QPushButton *aaa=new QPushButton(this);//创建按键控件,按键控件需要单独删除,不会随着widget删除
    116. aaa->setObjectName(tr("btn_table_look%1").arg(i+1));
    117. aaa->setText("数据查看");//设置文本
    118. aaa->setStyleSheet("font: 16pt ""黑体"";background-color: #000000;color: rgb(202, 234, 206);border-radius:10px;");//设置风格
    119. aaa->setMinimumSize(140,45);//设置按键控件最小尺寸
    120. aaa->setIcon(QIcon(":/PIC/查看1.png"));//设置图标
    121. aaa->setIconSize(QSize(40,40));//设置图标尺寸
    122. //创建按键2,根据需求更改创建的控件和数量
    123. QPushButton *bbb=new QPushButton(this);//创建按键控件,按键控件需要单独删除,不会随着widget删除
    124. bbb->setObjectName(tr("btn_table_delete%1").arg(i+1));
    125. bbb->setText("数据删除");//设置文本
    126. bbb->setStyleSheet("font: 16pt ""黑体"";background-color: #000000;color: rgb(202, 234, 206);border-radius:10px;");//设置风格
    127. bbb->setMinimumSize(140,45);//设置按键控件最小尺寸
    128. bbb->setIcon(QIcon(":/PIC/删除.png"));//设置图标
    129. bbb->setIconSize(QSize(40,40));//设置图标尺寸
    130. //将widget放入表格单元格中
    131. vLayout->addWidget(aaa);//将按键放入布局中
    132. vLayout->addWidget(bbb);//将按键放入布局中
    133. widget->setLayout(vLayout);//将布局放入widget中
    134. TableWidget->setCellWidget(i,5,widget);//插入widget到表格指定行列
    135. widget->show();//显示widget
    136. TableWidget->setRowHeight(i,50);//设置行高
    137. //绑定自定义控件按键点击槽函数,根据需求更改创建的控件和数量
    138. connect(aaa,&QPushButton::clicked,[=](){//绑定按键点击回调槽函数
    139. messagebox_history * box = new messagebox_history();
    140. box->show();
    141. box->setAttribute(Qt::WA_DeleteOnClose);//若是关闭界面,则彻底释放资源
    142. connect(this,SIGNAL(sendhistorydata(QStringList)),box,SLOT(receivedata(QStringList)));
    143. QStringList choosehistory;//容器 存放选择的信号所有列内容
    144. for(int j=0;j<7;j++)choosehistory.append(searchdata[j].at(i));
    145. emit sendhistorydata(choosehistory);//发送选择的历史数据信号内容
    146. });
    147. connect(bbb,&QPushButton::clicked,[=](){//绑定按键点击回调槽函数
    148. int res=massage_dialog(2,"提示",QString("是否确认删除序号%1数据?\r\n日期:%2\r\n操作员:%3\r\n设备号:%4")
    149. .arg(QString("%1").arg(i+1,2,10,QChar('0')))
    150. .arg(searchdata[0].at(i)).arg(searchdata[1].at(i)).arg(searchdata[2].at(i)),4);
    151. if(res==0){//点击确认返回0,点击取消或关闭返回1
    152. QSqlQuery qry(db);
    153. qry.exec(QString("delete from Sys_history where time = '%1'").arg(searchdata[0].at(i)));
    154. qry.prepare("vacuum");//删除成功之后释放内存碎片
    155. qry.exec();
    156. tableWidget_datasearch_refuse(ui->tableWidget_history);//更新历史信息
    157. massage_dialog(1,"提示","删除数据成功!",1);
    158. }
    159. });
    160. }
    161. // TableWidget->resizeRowsToContents();//根据单元格内容自动换行
    162. }

    没了,就这点,但是实现了数据管理的大功能,有表格初始化函数,根据使用需求设置不同方式的初始化函数,有表格刷新函数,和搜索按键,搜索按键也是调用搜索刷新函数,搜索刷新会根据界面控件的选择清空,输入不同的数据库指令去查询数据库内容,然后再把内容显示出来

    4.数据库表

    这里我使用了7个表头,有3个基本是固定的,4个是数据,前三个是时间,人员和编号,基本都应该包含,后面数据根据不同情况适当调整。

    搜索出来界面是这样的

     可以看见有功能列表,有数据查看和数据删除按键,这里是将单元格放入 了一个widget,在widget上放了按键,实际使用可以根据不同的使用情况更换不同的控件,第一个按键是将点击行的详细数据通过信号槽方式传递给一个新的界面程序messagebox_history,在那里面做历史数据处理显示,第二个按键设置的是删除按键,通过点击按键自动识别当前的选择内容对应数据库内容,之后执行删除就会删除数据库相应内容,并重新刷新表格,更新数据。

     
    

     动态使用效果如下: 

    本文程序不包含数据存储,数据存储按照表格存储数据协议格式存储即可

    四、结语

    本文例程下载链接

  • 相关阅读:
    java读取pdf数据
    k8s pod理论
    【代数学习题4.2】从零理解范数与迹 —— 求数域元素的范数与迹
    JavaScript防抖和节流(从认识到理解到手写)
    flume数据无法发送
    Java中如何清空一个HashSet对象呢?
    矩阵分析与计算学习记录-广义逆矩阵
    [UE]常见C++类继承关系
    【Linux】—— 在Linux上进行读写文件操作
    mysql第十六章 变量,流程控制触发器课后练习
  • 原文地址:https://blog.csdn.net/qq_37603131/article/details/128191851