基于脚手架微服务的视频点播系统-界面布局部分(二):用户界面及系统管理界面布局
基于脚手架微服务的视频点播系统-界面布局部分:二.首页及播放界面布局
- 一.用户界面布局
- 1.1用户界面布局分析与实现
- 1.2更新用户图像按钮及逻辑
- 1.3修改按钮及逻辑
- 1.4上传视频对话框实现逻辑
- 1.4.1页面跳转逻辑处理
- 1.4.2页面控件响应处理
- 二.系统界面布局
- 2.1系统管理页框架
- 2.2审核管理页
- 2.3角色管理页
- 2.4审核和角色管理页面切换
- 2.5视频审核表项
- 2.6角色管理表项
- 2.7编辑用户信息页
- 2.8分页器实现
- 三.登录界面布局
- 四.toast窗口
我们先来看下本文结束后需要完成的界面:
1.用户界面:

2.修改个人信息界面

3.上传视频界面

4.系统管理界面-视频审核与角色管理


5.登录注册界面

一.用户界面布局
1.1用户界面布局分析与实现
当用户点击"我的"页面切换按钮时,就会显示我的页面。仔细观察发现,我的页面整体属于上下结构布局,从上往下依次为:基本信息区、我的视频区、视频信息显⽰区,每个视频信息框可以复用VideoBox。
所以我们可以新增一个ui设计师类,类名为ModifyMyselfDialog然后在其ui界面进行布局如下:
界面控件嵌套关系如下:
和前文一样,详细的布局信息以及qss样式代码可以在本项目更新完毕之后的最后一篇博客置顶获取源码进行参考,这里我们就不罗列了为了避免文章长度冗余。
1.2更新用户图像按钮及逻辑
首先我的界面不会出现关注按钮,所以这里我们需要先隐藏掉:
/////myselfwidget.cpp-在myselfwidget类中添加一个ui初始化私有成员函数initUi(),在构造函数中调用
void MyselfWidget::initUi()
{ui->attentionBtn->hide();//测试添加视频for(int i = 0;i < 16;i++){VideoBox* box = new VideoBox();ui->layout->addWidget(box,i /4,i % 4);}
}
然后当用户鼠标放到头像位置时会有如下效果,点击之后会弹出对话框让用户选择图片去设置头像:
显然QPushButton无法解决此问题,此时我们就需要自定义一个类继承自QPushButton来实现此效果。我们新建一个普通类名为AvatarButton,函数头文件与cpp文件详情实现如下:
/////////////avatarbutton.h
#ifndef AVATARBUTTON_H
#define AVATARBUTTON_H#include <QPushButton>
#include <QLabel>class AvatarButton : public QPushButton
{Q_OBJECT
public:explicit AvatarButton(QWidget *parent = nullptr);//重写鼠标进入与离开事件以显示或隐藏黑色遮罩void enterEvent(QEnterEvent* event) override;void leaveEvent(QEvent* event) override;//设置是否拿掉遮罩void isHideMask(bool ishide);
private://设置点击之后的槽函数void onAvatarBtnClicked();
private:QLabel* mask;//黑色遮罩bool maskHide;//设置是否拿掉遮罩默认不拿掉
};#endif // AVATARBUTTON_H////////////avatarbutton.cpp
#include "avatarbutton.h"
#include "util.h"
#include <QFileDialog>AvatarButton::AvatarButton(QWidget *parent): QPushButton{parent},maskHide(true)
{//初始化遮罩mask = new QLabel(this);//黑色遮罩字体颜色为白色mask->setStyleSheet("background-color : rgba(0,0,0,0.5);""color : #FFFFFF;""border-radius : 30px;");mask->setText("修改头像");mask->setGeometry(0,0,60,60);//因为我们头像编辑框设置的是60*60mask->setAlignment(Qt::AlignCenter);//设置文本居中mask->hide();//连接信号槽connect(this,&QPushButton::clicked,this,&AvatarButton::onAvatarBtnClicked);
}void AvatarButton::enterEvent(QEnterEvent *event)
{if(maskHide){mask->show();}
}void AvatarButton::leaveEvent(QEvent *event)
{mask->hide();
}void AvatarButton::isHideMask(bool ishide)
{//设置mask遮罩的可见性maskHide = ishide;
}void AvatarButton::onAvatarBtnClicked()
{//参数含义:设置父元素|设置窗口标题|设置起始目录|设置目标类型文件QString filePath = QFileDialog::getOpenFileName(nullptr,"选择图片","C:\\","Image Files (*.jpg *.png)");if(filePath.isEmpty()){LOG() << "取消选择头像";return;}//这里因为之后我们需要将数据传到服务端,所以需要先将该数据转化为二进制流-新增工具函数QByteArray byteArray = loadFileToByteArray(filePath);if(byteArray.isEmpty()){//说明用户上传的文件无法打开或有误-不再设置头像return;}//裁剪并设置用户上传的头像this->setIcon(makeCircleIcon(byteArray,30));//上传图片数据到服务端//...
}
hover效果的显示就是一个简单的widget遮罩很简单上面代码已经很详细了。但是注意到我们这里有loadFileToByteArray与makeCircleIcon函数,为什么呢,因为用户在上传头像时,我们不仅仅需要将头像文件上传至服务器,而且它选择的图片不一定是60*60大小的,同时我们的图片还是圆形,所以这里我们就需要对用户上传的头像文件先转化为二进制流同时对该图像进行裁剪:
//////////util.h-新增
//将上传的文件转化为二进制流
static inline QByteArray loadFileToByteArray(const QString& filePath)
{QFile file(filePath);//以只读方式打开if(!file.open(QIODevice::ReadOnly)){LOG() << "文件打开失败";return QByteArray();}QByteArray res = file.readAll();file.close();return res;
}
//将二进制流转化为图片并裁剪为圆形作为头像
static inline QIcon makeCircleIcon(const QByteArray& byteArray, int radius){QPixmap pixmap;pixmap.loadFromData(byteArray);if (pixmap.isNull()) {//说明用户上传的不是图片文件return QIcon();}// 把 pixmap 缩放到指定的 2*radius ⼤⼩. IgnoreAspectRatio 忽略⻓宽⽐;// SmoothTransformation 平滑缩放, 获得更⾼的图⽚质量, 但是会牺牲⼀定速度.pixmap = pixmap.scaled(2*radius, 2*radius, Qt::IgnoreAspectRatio,Qt::SmoothTransformation);// 构造绘图设置,可以理解成画图的画布QPixmap output = QPixmap(pixmap.size());output.fill(Qt::transparent); // 设置透明背景QPainter painter(&output);// 设置抗锯齿 | 缩放图片时使用双线性或更好的滤波算法,产生平滑效果painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);// 创建圆形路径QPainterPath path;path.addEllipse(0, 0, 2*radius, 2*radius);// 设置裁剪路径,裁剪路径的作⽤是限制绘图操作的范围,只有在裁剪路径内的区域才会被裁剪painter.setClipPath(path);// 绘制圆形图⽚painter.drawPixmap(0, 0, pixmap);// 结束绘制:end()内部会释放与绘图设备相关的资源,确保绘图命令都能够被正确执⾏painter.end();QIcon icon(output);return icon;
}
打开myselfwidget.ui,选中avatarBtn按钮,将类型提升为AvatarButton,运⾏程序就能看到更新图像按钮的效果。那么此时就可以在我的界面设置头像了。
1.3修改按钮及逻辑
当用户点击修改按钮后,能够弹出修改对话框,用户可以对自己的昵称、密码信息进⾏修改。添加⼀个设计师界⾯,类名称设置为ModifyMyselfDialog然后我们在ui界面布局如下:
控件嵌套关系如下:
alt+shift+r即可看到如下预览界面:
给我的⻚⾯中的settingBtn按钮绑定槽函数,然后在槽函数中显⽰ModifyMyselfDialog对话框。注意ModifyMyselfDialog需要继承⾃QDialog,并设置去掉边框。
//////////////////////////////// myselfwidget.h
///////////////////////////////////
class MyselfWidget : public QWidget
{Q_OBJECT
private://个人信息修改按钮被点击void onModifyMyselfClicked();
private:Ui::MyselfWidget *ui;
};
///////myselfwidget.cpp
void MyselfWidget::initUi()
{connect(ui->settingBtn,&QPushButton::clicked,this,&MyselfWidget::onModifyMyselfClicked);
}void MyselfWidget::onModifyMyselfClicked()
{ModifyMyselfDialog* modify = new ModifyMyselfDialog();modify->exec();//模态显示delete modify;
}
/////////////////////////modifymyselfdialog.h
#ifndef MODIFYMYSELFDIALOG_H
#define MODIFYMYSELFDIALOG_H#include <QDialog>namespace Ui {
class ModifyMyselfDialog;
}class ModifyMyselfDialog : public QDialog
{Q_OBJECTpublic:explicit ModifyMyselfDialog(QWidget *parent = nullptr);~ModifyMyselfDialog();
private:void onSubmitBtnClicked();void showPasswordDlg();
private:Ui::ModifyMyselfDialog *ui;
};#endif // MODIFYMYSELFDIALOG_H///////////////modifymyselfdialog.cpp
#include "modifymyselfdialog.h"
#include "ui_modifymyselfdialog.h"
#include "util.h"
#include "newpassworddialog.h"ModifyMyselfDialog::ModifyMyselfDialog(QWidget *parent): QDialog(parent), ui(new Ui::ModifyMyselfDialog)
{ui->setupUi(this);//默认隐藏修改密码后的widgetui->passwordWidget->hide();//设置背景透明 | 无边框setWindowFlag(Qt::FramelessWindowHint);setAttribute(Qt::WA_TranslucentBackground);//连接信号槽connect(ui->cancelBtn,&QPushButton::clicked,this,&ModifyMyselfDialog::close);connect(ui->submitBtn,&QPushButton::clicked,this,&ModifyMyselfDialog::onSubmitBtnClicked);connect(ui->passwordBtn, &QPushButton::clicked, this,&ModifyMyselfDialog::showPasswordDlg);connect(ui->changePasswordBtn, &QPushButton::clicked, this,&ModifyMyselfDialog::showPasswordDlg);
}ModifyMyselfDialog::~ModifyMyselfDialog()
{delete ui;
}void ModifyMyselfDialog::onSubmitBtnClicked()
{LOG() << "用户信息已修改";//上传修改信息到服务端close();
}void ModifyMyselfDialog::showPasswordDlg()
{NewPasswordDialog* newPassWordDialog = new NewPasswordDialog();newPassWordDialog->exec();const QString currentPassword = newPassWordDialog->getNewPassWord();if(currentPassword.isEmpty()){LOG() << "密码修改已取消";}else{LOG() << "新密码已设置" << currentPassword;ui->passwordWidget->show();ui->passwordBtn->hide();}delete newPassWordDialog;
}
因为弹出修改个⼈资料对话框之后,点击设置密码按钮,会弹出设计密码对话框,让⽤⼾完成密码修改。这里我们需要新建一个ui设计师类名为NewPasswordDialog,在其ui文件中布局如下:
控件的嵌套关系如下:
当用户点击修改密码时,就能显⽰出修改密码对话框。上面的showPasswordDlg就是用来控制其显示的函数。但是⽤⼾在修改密码时,密码是有限制的:密码由数字、⼤写英⽂字⺟、⼩写英⽂字⺟、特殊字符组成,至少包含两种类型,⻓度要求8-16位字符。因此当用户前后两次密码输⼊完成之后,需要验证是否满足密码限制,以及前后两次输⼊的密码是否相等。所以我们可以这样设计NewPasswordDialog类:
///////////////newpassworddialog.h
#ifndef NEWPASSWORDDIALOG_H
#define NEWPASSWORDDIALOG_H#include <QDialog>namespace Ui {
class NewPasswordDialog;
}class NewPasswordDialog : public QDialog
{Q_OBJECTpublic:explicit NewPasswordDialog(QWidget *parent = nullptr);const QString& getNewPassWord();~NewPasswordDialog();private:void onSubmitBtnClicked();void onEditingFinished();bool checkPasswordEdit();
private:Ui::NewPasswordDialog *ui;QString newPassWord;
};#endif // NEWPASSWORDDIALOG_H
///////////////newpassworddialog.cpp
#include "newpassworddialog.h"
#include "ui_newpassworddialog.h"NewPasswordDialog::NewPasswordDialog(QWidget *parent): QDialog(parent), ui(new Ui::NewPasswordDialog)
{ui->setupUi(this);//设置背景透明 | 无边框setWindowFlag(Qt::FramelessWindowHint);setAttribute(Qt::WA_TranslucentBackground);//连接信号槽connect(ui->cancelBtn,&QPushButton::clicked,this,&NewPasswordDialog::close);connect(ui->submitBtn,&QPushButton::clicked,this,&NewPasswordDialog::onSubmitBtnClicked);connect(ui->passwordEdit1,&QLineEdit::editingFinished,this,&NewPasswordDialog::onEditingFinished);connect(ui->passwordEdit2,&QLineEdit::editingFinished,this,&NewPasswordDialog::onEditingFinished);
}const QString &NewPasswordDialog::getNewPassWord()
{return newPassWord;
}NewPasswordDialog::~NewPasswordDialog()
{delete ui;
}void NewPasswordDialog::onSubmitBtnClicked()
{if(!checkPasswordEdit())return;newPassWord = ui->passwordEdit1->text();close();
}bool NewPasswordDialog::checkPasswordEdit()
{//核验密码的正确性//1.检验输入密码是否为空if(ui->passwordEdit1->text().isEmpty()){ui->messageLabel->setText("设置的新密码不能为空!");return false;}if(ui->passwordEdit2->text().isEmpty()){ui->messageLabel->setText("两次输入的密码不一致!");return false;}//2.检验输入的密码长度以及是否至少包含两种字符if(ui->passwordEdit1->text().size() < 8 || ui->passwordEdit2->text().size() > 16){ui->messageLabel->setText("密码长度不合法!");return false;}QVector<bool> kinds(4,false);int kindsNum = 0;for(auto& c : ui->passwordEdit1->text()){if(QChar(c).isDigit()){kindsNum += kinds[0] ? 0 : 1;kinds[0] = true;}else if(QChar(c).isUpper()){kindsNum += kinds[1] ? 0 : 1;kinds[1] = true;}else if(QChar(c).isLower()){kindsNum += kinds[2] ? 0 : 1;kinds[2] = true;}else if(QChar(c).isPunct()){kindsNum += kinds[3] ? 0 : 1;kinds[3] = true;}else{ui->messageLabel->setText("密码中含有非法字符!");return false;}}if(kindsNum < 2){ui->messageLabel->setText("密码中必须包含两种及以上字符!");return false;}//3.检验两次输入密码是否一致if(ui->passwordEdit1->text() != ui->passwordEdit2->text()){ui->messageLabel->setText("两次输入的密码不一致!");return false;}//到这里说明密码合法,清空错误信息ui->messageLabel->setText("");return true;
}void NewPasswordDialog::onEditingFinished()
{checkPasswordEdit();
}
1.4上传视频对话框实现逻辑
视频信息对话框中元素整体为上下结构,因为界⾯中元素较多,页面中显示不下,因此界⾯中所有元
素都处于QScrollArea中,界⾯结构⼤致如下:
那么接下来我们就新建一个ui设计师类名为UploadVideoPage,然后在其ui界面中进行布局:
控件嵌套关系如下:
布局完毕并设置完样式后,预览下就能看到如下画面:
1.4.1页面跳转逻辑处理
由于上传视频页面和首页、我的页面在相同位置显示,因此需要再LimePlayer的stackedWidget中添加⼀个新页面,objectName修改为uploadVideo,然后将其类型提升为UploadVideoPage。 当用户点击我的页面中上传视频按钮,并且选择了需要上传的视频时,我的页面会发送⼀个jumpToMyPage(ButtonType buttontype)信号,由LimePlayer处理该信号将页面切换到上传视频页面,当用户填好视频信息点击提交按钮时再跳回我的页面:
///////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H#include <QWidget>
#include "pageswitchbutton.h"namespace Ui {
class UploadVideoPage;
}class UploadVideoPage : public QWidget
{Q_OBJECTpublic:explicit UploadVideoPage(QWidget *parent = nullptr);//视频信息提交完毕后跳转到我的页面void onCommitBtnClicked();~UploadVideoPage();signals:void jumpToMyPage(ButtonType buttontype);
};#endif // UPLOADVIDEOPAGE_H
///////////////////uploadvideopage.cpp
UploadVideoPage::UploadVideoPage(QWidget *parent): QWidget(parent), ui(new Ui::UploadVideoPage)
{ui->setupUi(this);//当用户提交完之后跳转到我的页面connect(ui->commitBtn,&QPushButton::clicked,this,&UploadVideoPage::onCommitBtnClicked);
}void UploadVideoPage::onCommitBtnClicked()
{if(ui->videoTitle->text().isEmpty()){//如果标题和简介为空不允许提交//Toast::showToast("标题不可以为空T_T~~~");-后面设置return;}LOG() << "视频详细信息已填好,准备上传服务端";emit jumpToMyPage(MyPageBtn);
}//记得在构造函数处连接信号槽/////////////////limeplayer.cpp
void MyselfWidget::initUi()
{connect(ui->uploadVideoBtn,&QPushButton::clicked,this,&MyselfWidget::onUpLoadVideoBtnClicked);
}void LimePlayer::connectSignalAndSlot()
{//当视频上传完毕之后跳转到我的页面connect(ui->uploadVideo,&UploadVideoPage::jumpToMyPage,this,&LimePlayer::switchPage);//连接上传视频页面的信号connect(ui->myPage,&MyselfWidget::switchUploadVideoPage,this,&LimePlayer::switchPage);
}
////////////////////////pageswitchbutton.h
//标记按钮类型
enum ButtonType{HomePageBtn,MyPageBtn,SysPageBtn,UpLoadPageBtn//-新增类型
};
////////////////////////myselfwidget.h
private://上传视频按钮被点击void onUpLoadVideoBtnClicked();
signals:void switchUploadVideoPage(ButtonType pageIndex);
/////////////////////////myselfwidget.cpp
void MyselfWidget::onUpLoadVideoBtnClicked()
{//弹出打开⽂件对话框,让⽤⼾选择要上传的视频⽂件QString videoFilePath = QFileDialog::getOpenFileName(nullptr, "上传视频","","Videos(*.mp4 *.rmvb *.avi *.mov)");if(!videoFilePath.isEmpty()){// 视频⼤小限制,上限为4GQFileInfo fileInfo(videoFilePath);qint64 fileSize = fileInfo.size();qlonglong maxVideoSize = 4294967296;if(fileSize > maxVideoSize){LOG()<<"视频文件必须小于4G";return;}emit switchUploadVideoPage(UpLoadPageBtn);}
}
1.4.2页面控件响应处理
标题框和简介框的⽂本有字数限制,随着用户不断输⼊,要能让用户看到还剩余多少字可以输⼊。因此需要捕获标题QLineEdit和简介QPlainTextEdit的QLineEdit::textChanged信号,在绑定的槽函数中实现剩余字数提示功能。
//////////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H#include <QWidget>
#include "pageswitchbutton.h"namespace Ui {
class UploadVideoPage;
}class UploadVideoPage : public QWidget
{Q_OBJECTpublic:void onVideoTitleChanged(const QString& text);void onPlainTextEditChanged();
private:const int maxTitleTextSize = 80;const int maxPlainTextSize = 1000;
};#endif // UPLOADVIDEOPAGE_H/////////////////////////uploadvideopage.cpp
#include "uploadvideopage.h"
#include "ui_uploadvideopage.h"
#include "util.h"
#include "toast.h"
#include <QMessageBox>UploadVideoPage::UploadVideoPage(QWidget *parent): QWidget(parent), ui(new Ui::UploadVideoPage)
{//限制二者的输入字数ui->videoTitle->setMaxLength(maxTitleTextSize);connect(ui->videoTitle,&QLineEdit::textChanged,this,&UploadVideoPage::onVideoTitleChanged);connect(ui->plainTextEdit,&QPlainTextEdit::textChanged,this,&UploadVideoPage::onPlainTextEditChanged);
}void UploadVideoPage::onVideoTitleChanged(const QString& text)
{//同步改变后缀文本int Size = text.size();ui->leftWord->setText(QString::number(Size) + "/" + QString::number(maxTitleTextSize));
}void UploadVideoPage::onPlainTextEditChanged()
{//同步改变后缀文本QString currentText = ui->plainTextEdit->toPlainText();int currentLength = currentText.size();// 如果当前字符数超过限制if (currentLength > maxPlainTextSize) {// 阻塞信号,防止在setPlainText时再次触发textChanged信号导致递归ui->plainTextEdit->blockSignals(true);// 截取前maxPlainTextSize个字符QString newText = currentText.left(maxPlainTextSize);ui->plainTextEdit->setPlainText(newText);//设置光标位置到文本末尾QTextCursor cursor = ui->plainTextEdit->textCursor();cursor.movePosition(QTextCursor::End);ui->plainTextEdit->setTextCursor(cursor);// 解阻塞信号ui->plainTextEdit->blockSignals(false);}//同步改变后缀文本currentLength = std::min(currentLength,maxPlainTextSize);ui->briefLeftWord->setText(QString::number(currentLength) + "/" + QString::number(maxPlainTextSize));
}
视频封⾯图默认使用视频首帧,如果用户想要更换视频⾸⻚封⾯图时,可以点击更改封⾯图按钮,从磁盘选择本地图⽚作为视频封⾯图,注意图⽚宽⾼⽐为4:3:
//////////////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H#include <QWidget>
#include "pageswitchbutton.h"namespace Ui {
class UploadVideoPage;
}class UploadVideoPage : public QWidget
{Q_OBJECTpublic:explicit UploadVideoPage(QWidget *parent = nullptr);
private://这里我们将所有的槽函数设置为私有//当标题与简介文本改变时同步字数显示void onVideoTitleChanged(const QString& text);void onPlainTextEditChanged();//视频信息提交完毕后跳转到我的页面void onCommitBtnClicked();//当用户想要自己设置视频封面时选取视频封面并设置void onChangeBtnClicked();
};#endif // UPLOADVIDEOPAGE_H////////////////uploadvideopage.cpp
#include "uploadvideopage.h"
#include "ui_uploadvideopage.h"
#include "util.h"
#include "toast.h"
#include <QMessageBox>UploadVideoPage::UploadVideoPage(QWidget *parent): QWidget(parent), ui(new Ui::UploadVideoPage)
{ui->setupUi(this);//相应用户修改视频封面信息的操作connect(ui->changeButton,&QPushButton::clicked,this,&UploadVideoPage::onChangeBtnClicked);
}void UploadVideoPage::onChangeBtnClicked()
{QString imagePath = QFileDialog::getOpenFileName(nullptr,"选取视频封面","","Images (*.png *.xpm *.jpg)");if(!imagePath.isEmpty()){//设置视频封面图片并对图片进行裁剪QPixmap image(imagePath);//忽略原图像宽高比 | 设置为平滑缩放-scaled返回的是一个新的pixmap对象,而不会修改原图像image = image.scaled(ui->imageLabel->size(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation);ui->imageLabel->setPixmap(image);//如果不立即重绘会导致图片显示是没有缩放的状态repaint();}
}
标签和分类后序在进行处理。
二.系统界面布局
当点击主页中系统管理页面切换按钮时,应切换到系统管理⻚⾯。系统管理⻚⽀持两个页面:审核管理和⻆⾊管理。审核管理页面中系统管理员对⽤⼾上传的视频进⾏审核、上架和下架处理,⻆⾊管理页面主要是新增、编辑管理员信息,启⽤和禁⽌管理员账号等。
仔细观察我们刚开始给出的系统管理页面的显示,页面中仅仅的数据展示操作区不同外,其余基本是⼀样的,因此在界⾯设计时可以考虑将公共部分提取出来作为基类,然后让审核管理和⻆⾊管理⻚⾯去继承该基类可以重复代码冗余。但是qt designer设计的ui界⾯本⾝不⽀持继承,想要达到继承复用减少代码冗余,审核管理和⻆⾊管理操作和展⽰部分可以⽤纯代码方式实现。
2.1系统管理页框架
新建⼀个qt设计师界面,命名为AdminWidget,然后我们在其ui设计师界面进行布局如下:
控件嵌套关系如下:
审核管理和⻆⾊管理页面实现完后,将container中checkTable和roleTable的类型替换掉,系统管理页面就完成了。(他俩初始是个QWidget)。
2.2审核管理页
审核页面整体为上下结构,从上往下依次为编辑选择按钮区,视频信息显示区和分页器区域,编辑选择按钮区域中用户可以进⾏条件查询等,视频信息展示区主要展⽰各视频信息,每条展示视频信息的控件需要用户⾃定义;如果⼀个视频比较多页面展示不下时,可以通过分页器向前向后翻页或快速定位指定区域,因此分页器也需要用户⾃定义。
那我们先来实现审核管理页吧,添加⼀个设计师界⾯,命名为CheckTable,然后在ui界面进行布局如下:
其控件嵌套关系如下:
设置完成后点击adminwidget.ui,将checkTable的类型提升为CheckTable。运行程序之后就有最开始展示的效果了(当然此时界面中没有任何条目)。
在审核管理页面,管理员通过用户id查看用户上传的视频,也可以根据视频状态查看该状态下的视频。用户id只能是大于0的整数,因此需要对用户ID编辑框进行条件限制。当然当⽤⼾点击重置和查看按钮后,按钮也需要响应对应的操作。
/////////////////checktable.h
#ifndef CHECKTABLE_H
#define CHECKTABLE_H#include <QWidget>namespace Ui {
class CheckTable;
}class CheckTable : public QWidget
{Q_OBJECTpublic:explicit CheckTable(QWidget *parent = nullptr);~CheckTable();private:void onResetBtnClicked();void onQueryBtnClicked();private:Ui::CheckTable *ui;
};#endif // CHECKTABLE_H
//////////////////////////checktable.cpp
#include "checktable.h"
#include "ui_checktable.h"
#include "util.h"
#include "checktableitem.h"
#include "paginator.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>CheckTable::CheckTable(QWidget *parent): QWidget(parent), ui(new Ui::CheckTable)
{ui->setupUi(this);ui->videoStatus->addItem("全部分类");ui->videoStatus->addItem("待审核");ui->videoStatus->addItem("审核通过");ui->videoStatus->addItem("审核驳回");ui->videoStatus->addItem("已下架");ui->videoStatus->addItem("转码中");ui->videoStatus->setCurrentIndex(0);//设置正则表达式以限制用户Id的输入QRegularExpression regExp("^[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{4}$");QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp,this);//设置验证器ui->userIdEdit->setValidator(validator);//连接重置按钮与查询按钮的信号槽connect(ui->resetBtn,&QPushButton::clicked,this,&CheckTable::onResetBtnClicked);connect(ui->queryBtn,&QPushButton::clicked,this,&CheckTable::onQueryBtnClicked);
}CheckTable::~CheckTable()
{delete ui;
}void CheckTable::onResetBtnClicked()
{ui->userIdEdit->setText("");ui->videoStatus->setCurrentIndex(0);LOG() << "重置按钮被点击";
}void CheckTable::onQueryBtnClicked()
{LOG() << "查询按钮被点击";
}
2.3角色管理页
⻆⾊管理页面和审核页面布局形式几乎相同,直接参考审核⻚⾯布局基本可以完成角色页面布局,添加⼀个设计师界⾯,命名为RoleTable然后在其ui界面中进行布局:
其控件嵌套关系如下:
设置完成之后,打开adminWidget.ui,将roleTable的类型提升为RoleTable。在⻆⾊管理页面,管理员通过用户⼿机号查看用户信息,因此⼿机号编辑框需要进行输入限制。此外管理员也可以根据用户状态查看该状态下的所有用户信息,因此用户状态的初始数据需要提前设置好。此外重置和审核按钮点击时,也需要处理相应的逻辑:
///////////////////////roletable.h
#ifndef ROLETABLE_H
#define ROLETABLE_H#include <QWidget>namespace Ui {
class RoleTable;
}class RoleTable : public QWidget
{Q_OBJECTpublic:explicit RoleTable(QWidget *parent = nullptr);void updateRoleTable();~RoleTable();private:void onResetBtnClicked();void onQueryBtnClicked();void onInsertBtnClicked();private:Ui::RoleTable *ui;
};#endif // ROLETABLE_H
/////////////////////////roletable.cpp
#include "roletable.h"
#include "ui_roletable.h"
#include "util.h"
#include "roletableitem.h"
#include "edituserdialog.h"
#include "paginator.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>RoleTable::RoleTable(QWidget *parent): QWidget(parent), ui(new Ui::RoleTable)
{ui->setupUi(this);ui->userStatus->addItem("全部分类");ui->userStatus->addItem("启⽤");ui->userStatus->addItem("停⽤");ui->userStatus->setCurrentIndex(0);//设置正则表达式以限制手机号的输入QRegularExpression regExp("^1\\d{10}$");QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp,this);//设置验证器ui->phone->setValidator(validator);//连接重置按钮与查询按钮与新增按钮的信号槽connect(ui->resetBtn,&QPushButton::clicked,this,&RoleTable::onResetBtnClicked);connect(ui->queryBtn,&QPushButton::clicked,this,&RoleTable::onQueryBtnClicked);
}RoleTable::~RoleTable()
{delete ui;
}void RoleTable::onResetBtnClicked()
{ui->phone->setText("");ui->userStatus->setCurrentIndex(0);LOG() << "审核界面重置按钮被按下";
}void RoleTable::onQueryBtnClicked()
{LOG() << "审核界面查询按钮被按下";
}
2.4审核和角色管理页面切换
当用户点击审核管理和角色管理按钮时,页面需要在审核管理页和角色管理页之间切换。实现原理⾮常简单,就直接切换stackedWidget界面即可。
//////////adminwidget.h
#ifndef ADMINWIDGET_H
#define ADMINWIDGET_H#include <QWidget>namespace Ui {
class AdminWidget;
}class AdminWidget : public QWidget
{Q_OBJECTpublic:explicit AdminWidget(QWidget *parent = nullptr);~AdminWidget();
private:void onCheckBtnClicked();void onRoleBtnClicked();
private:Ui::AdminWidget *ui;const QString selectedStyle = "background-color: #FFFFFF;""font-size: 14px;""color: #3ECEFF;""border:none;""border-bottom: 2px solid #3ECEFF;""font-weight:bold;";const QString unSelectedStyle = "background-color: #FFFFFF;""font-size: 14px;""color: #666666;""border:none;""border-bottom: 2px solid #F5F6F8;";
};#endif // ADMINWIDGET_H/////////////////adminwidget.cpp
#include "adminwidget.h"
#include "ui_adminwidget.h"AdminWidget::AdminWidget(QWidget *parent): QWidget(parent), ui(new Ui::AdminWidget)
{ui->setupUi(this);//连接checkBtn与roleBtn的点击信号槽函数connect(ui->checkBtn,&QPushButton::clicked,this,&AdminWidget::onCheckBtnClicked);connect(ui->roleBtn,&QPushButton::clicked,this,&AdminWidget::onRoleBtnClicked);
}AdminWidget::~AdminWidget()
{delete ui;
}void AdminWidget::onCheckBtnClicked()
{//切换页面ui->stackedWidget->setCurrentIndex(0);//更改按钮样式ui->checkBtn->setStyleSheet(selectedStyle);ui->roleBtn->setStyleSheet(unSelectedStyle);
}void AdminWidget::onRoleBtnClicked()
{//切换页面ui->stackedWidget->setCurrentIndex(1);//更改按钮样式ui->roleBtn->setStyleSheet(selectedStyle);ui->checkBtn->setStyleSheet(unSelectedStyle);
}
2.5视频审核表项
添加⼀个Qt设计师界⾯,命名为CheckTableItem然后在其ui界面中进行布局:
控件嵌套关系如下:
此时我们在checktable中添加更新函数就能有文章刚开始展示的效果了:
////////checktable.h
#ifndef CHECKTABLE_H
#define CHECKTABLE_H#include <QWidget>namespace Ui {
class CheckTable;
}class CheckTable : public QWidget
{void updateCheckTable();
};#endif // CHECKTABLE_H///////////////////checktable.cpp
#include "checktable.h"
#include "ui_checktable.h"
#include "util.h"
#include "checktableitem.h"
#include "paginator.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>CheckTable::CheckTable(QWidget *parent): QWidget(parent), ui(new Ui::CheckTable)
{//更新checkTable页面updateCheckTable();
}void CheckTable::updateCheckTable()
{//测试代码for(int i = 0;i < 20;i++){CheckTableItem* item = new CheckTableItem();ui->layout->addWidget(item);}
}
2.6角色管理表项
添加⼀个Qt设计师界面,命名为RoleTableItem,然后在其ui文件中进行布局如下:
控件嵌套关系如下:
然后我们像checkTable那样给他设置几个到界面中就能有开头展示的效果了:
/////roletableitem.h
#ifndef ROLETABLEITEM_H
#define ROLETABLEITEM_H#include <QWidget>namespace Ui {
class RoleTableItem;
}class RoleTableItem : public QWidget
{Q_OBJECTpublic:explicit RoleTableItem(QWidget *parent = nullptr,int serialNumber = 0);void updateSerialNumber(int serialNumber);~RoleTableItem();
private:Ui::RoleTableItem *ui;
};#endif // ROLETABLEITEM_H///////roletableitem.cpp
#include "roletableitem.h"
#include "ui_roletableitem.h"
#include "edituserdialog.h"RoleTableItem::RoleTableItem(QWidget *parent,int serialNumber): QWidget(parent), ui(new Ui::RoleTableItem)
{ui->setupUi(this);//初始化序号updateSerialNumber(serialNumber);
}void RoleTableItem::updateSerialNumber(int serialNumber)
{ui->idLabel->setText(QString::number(serialNumber));
}RoleTableItem::~RoleTableItem()
{delete ui;
}
/////////////////roletable.h
#ifndef ROLETABLE_H
#define ROLETABLE_H#include <QWidget>class RoleTable : public QWidget
{void updateRoleTable();
};#endif // ROLETABLE_H/////////////////roletable.cpp
#include "roletable.h"
#include "ui_roletable.h"
#include "util.h"
#include "roletableitem.h"
#include "edituserdialog.h"
#include "paginator.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>RoleTable::RoleTable(QWidget *parent): QWidget(parent), ui(new Ui::RoleTable)
{ui->setupUi(this);//初始化管理员界面updateRoleTable();
}void RoleTable::updateRoleTable()
{//测试for(int i = 0;i < 10;i++){RoleTableItem* item = new RoleTableItem(nullptr,i + 1);ui->layout->addWidget(item);}
}
2.7编辑用户信息页
在角色管理界⾯中,当管理员点击新增按钮时,需要弹出新增管理员信息窗口,完成新增加管理员信息的输入;当点击编辑按钮时,需要弹出编辑管理员信息窗口,完成对所选管理员信息的编辑。但仔细观察,这两个页面实际是完全相同的。
注意,新增和编辑用户信息窗口并不是上面白色窗口区域,而是整个窗口区域,窗口上覆盖⼀个遮罩层,用户编辑信息区域在遮罩层上,这样编辑信息区域能更清晰突显出来。该窗口也采用绝对定位的⽅式进行布局。这里我们添加⼀个Qt设计师界⾯,命名为EditUserDialog,然后在其ui文件中进行布局:
EditUserDialog创建是⼀个模态对话框,并且能够在外部设置创建新增对话框,还是编辑对话框。同时我们设置正则表达式限制输入框中的输入
///////////////////edituserdialog.h
#ifndef EDITUSERDIALOG_H
#define EDITUSERDIALOG_H#include <QDialog>namespace Ui {
class EditUserDialog;
}class EditUserDialog : public QDialog
{Q_OBJECTpublic:explicit EditUserDialog(QWidget *parent = nullptr);void setTitle(const QString& title);~EditUserDialog();private:void onSubmitBtnClicked();void onCancelBtnClicked();private:Ui::EditUserDialog *ui;
};#endif // EDITUSERDIALOG_H///////////////edituserdialog.cpp
#include "edituserdialog.h"
#include "ui_edituserdialog.h"
#include "util.h"
#include "limeplayer.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>EditUserDialog::EditUserDialog(QWidget *parent): QDialog(parent), ui(new Ui::EditUserDialog)
{ui->setupUi(this);//移除标题栏并设置背景透明setWindowFlag(Qt::FramelessWindowHint);setAttribute(Qt::WA_TranslucentBackground);//绑定提交与取消的槽函数connect(ui->submitBtn,&QPushButton::clicked,this,&EditUserDialog::onSubmitBtnClicked);connect(ui->cancelBtn,&QPushButton::clicked,this,&EditUserDialog::onCancelBtnClicked);//设置正则表达式以限制手机号的输入QRegularExpression regExp("^1\\d{10}$");QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp,this);//为手机号位置设置正则表达式加以输入限制ui->phoneEdit->setValidator(validator);//为选择框添加成员ui->roleComboBox->addItem("管理员");ui->roleComboBox->setCurrentIndex(0);//当简介中输入文字时同步文字消息框connect(ui->commentTextEdit,&QPlainTextEdit::textChanged,this,[=](){QString text = ui->commentTextEdit->toPlainText();if(text.size() > 10){//防止setText时再触发textChanged信号导致递归调用ui->commentTextEdit->blockSignals(true);//截取前10个字符text = text.left(10);ui->commentTextEdit->setPlainText(text);// 设置光标位置到末尾QTextCursor cursor = ui->commentTextEdit->textCursor();cursor.movePosition(QTextCursor::End);ui->commentTextEdit->setTextCursor(cursor);ui->commentTextEdit->blockSignals(false);}ui->wordContent->setText(QString::number(text.size()) + "/10");});
}void EditUserDialog::setTitle(const QString &title)
{ui->titleLabel->setText(title);
}EditUserDialog::~EditUserDialog()
{delete ui;
}void EditUserDialog::onSubmitBtnClicked()
{LOG() << "角色管理页面用户信息已提交";close();
}void EditUserDialog::onCancelBtnClicked()
{LOG() << "角色管理页面用户信息取消提交";close();
}
在RoleTable中给新增按钮添加槽函数,在槽函数中显示EditUserDialog窗口;同理,在RoleTableItem中给编辑按钮添加槽函数,当编辑按钮点击的时候显⽰EditUserDialog窗口。
////////roletable.h
#ifndef ROLETABLE_H
#define ROLETABLE_H#include <QWidget>namespace Ui {
class RoleTable;
}class RoleTable : public QWidget
{
private:void onInsertBtnClicked();
};#endif // ROLETABLE_H
////////roletable.cpp
#include "roletable.h"
#include "ui_roletable.h"
#include "util.h"
#include "roletableitem.h"
#include "edituserdialog.h"
#include "paginator.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>RoleTable::RoleTable(QWidget *parent): QWidget(parent), ui(new Ui::RoleTable)
{ui->setupUi(this);connect(ui->insertBtn,&QPushButton::clicked,this,&RoleTable::onInsertBtnClicked);
}void RoleTable::onInsertBtnClicked()
{EditUserDialog* dialog = new EditUserDialog();dialog->setTitle("新增后台用户");dialog->exec();delete dialog;
}
/////roletableitem与上面设置基本一致这里不再给出代码
但是此时有一个问题虽然窗口能够显示出来了,但是位置不对,为了更方便之后的获取主窗口的位置,同时确保同一个程序中只有一个limePlayer实例,这里我们将limePlayer设置为单例模式:
/////limeplayer.h
#ifndef LIMEPLAYER_H
#define LIMEPLAYER_H#include <QWidget>
#include "pageswitchbutton.h"QT_BEGIN_NAMESPACE
namespace Ui {
class LimePlayer;
}
QT_END_NAMESPACEclass LimePlayer : public QWidget
{Q_OBJECTpublic:static LimePlayer* getInstance();~LimePlayer();
private://将函数设置为单例模式LimePlayer(QWidget *parent = nullptr);
private:static LimePlayer* limePlayer;
};
#endif // LIMEPLAYER_H//////////limeplayer.cpp
LimePlayer* LimePlayer::limePlayer = nullptr;LimePlayer* LimePlayer::getInstance()
{if(limePlayer == nullptr){limePlayer = new LimePlayer();}return limePlayer;
}/////main.cpp
#include "limeplayer.h"
#include "startpage.h"#include <QApplication>
#include <QStyleFactory>int main(int argc, char *argv[])
{// 禁⽌窗⼝按照分辨率百分⽐缩放,必须套放在程序第⼀句QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);//让它不跟随系统设置的深色模式qputenv("QT_QPA_PLATFORM","windows:darkmode=0");QApplication a(argc, argv);// 设置应用程序图标a.setWindowIcon(QIcon(":/images/limePlayer.ico"));//显示启动窗口StartPage startPage;startPage.startUp();startPage.exec();LimePlayer* w = LimePlayer::getInstance();w->show();return a.exec();
}
此时我们在edituserdialog.cpp设置其初始位置即可:
/////edituserdialog.cpp
EditUserDialog::EditUserDialog(QWidget *parent): QDialog(parent), ui(new Ui::EditUserDialog)
{ui->setupUi(this);//设置初始位置this->move(LimePlayer::getInstance()->mapToGlobal(QPoint(25,25)));
}
这时侯位置显示就没问题了。
2.8分页器实现
当数据量比较⼤时,如果将数据⼀次性加载出来会非常耗时,而且也不⽅便用户查看,因此都是分页显示,用户通过分页器可以快速定位到指定页面查看数据:
假设:page为当前显示页,pageCount为总页数,而默认显示的pageButton数为7个,要实现上述分页器需求,则:
① 当点击"<“按钮时,显示前一页,当点击”>“按钮时显示最后一页
② 当在跳转⾄框中输入数字后按回⻋键可直接跳转⾄指定页,数字在1至最大页数之间;若输入超过最大页数则跳转到最后页
③ 如果总页数小于7⻚,中间部分按钮上显示1~7页数,不出现折叠按钮,page按钮为激活按钮
④ 如果总页数⼤于7⻚,当前显显示页为1-5时,前5个按钮展示1~5页,第6按钮为折叠按钮显示
为”…",最后⼀个按纽显示总页数,page按钮为激活按钮
⑤ 如果总页数⼤于7页,当前显⽰页为pageCount-4 ~ pageCount时(pageCount为总⻚数),第⼀个按钮显示第1页,其余按钮显示pageCount-3 ~ pageCount页,第2个按钮为折叠按钮显示…,page按纽为折叠按钮
⑥ 如果总页数⼤于7页,当前显⽰⻚⼤于5,小于pageCount-4时,第1个按钮和第7个按钮固定展示第⼀页和最后⼀页,中间五个按钮⼀次为page-2、page-1,page,page+1,page+2,并且将第2个按
钮和第6个按钮为折叠按钮显⽰…,最中间按钮(即第三个按钮)为激活按钮。
弄清分页器的逻辑之后,我们便可以着手开始分页器的设计了:
由于分页器上每个按钮有特定样式,并且有些折叠显示,有些显示页数,有些处于点击选中状态,因此将QPushButton进⾏简单封装。添加⼀个C++头⽂件和源⽂件,类名为PageButton,继承自QPushButton。具体实现如下:
//////pagebutton.h
#ifndef PAGEBUTTON_H
#define PAGEBUTTON_H#include <QPushButton>class PageButton : public QPushButton
{Q_OBJECT
public:explicit PageButton(QWidget *parent = nullptr,int page = 1);//获取按钮对应的页面序号int getPage() const;void setPage(int page);//设置与获取按钮的激活状态bool getIsActive() const;void setActive(bool isActive);//设置与获取按钮的折叠状态bool getIsFoldBtn() const;void setFold(bool isFold);private:int page;//按钮对应的页面序号bool isActiveBtn;//是否为选中状态bool isFoldBtn;//是否为折叠按钮
};#endif // PAGEBUTTON_H//////////////pagebutton.cpp
#include "pagebutton.h"PageButton::PageButton(QWidget *parent,int page): QPushButton{parent},isActiveBtn(false),isFoldBtn(false)
{this->page = page;// 设置按钮的图标尺⼨ 和 按钮⼤⼩this->setIconSize(QSize(12, 12));this->setFixedSize(QSize(32, 32));//设置按钮对应的页面setPage(this->page);//设置按钮的激活与折叠状态setFold(isFoldBtn);setActive(isActiveBtn);
}int PageButton::getPage() const
{return page;
}void PageButton::setPage(int page)
{this->page = page;setText(QString::number(page));
}bool PageButton::getIsActive() const
{return isActiveBtn;
}void PageButton::setActive(bool isActive)
{this->isActiveBtn = isActive;if(isActive){//选中状态setStyleSheet("color : #FFFFFF;""background-color : #3ECEFF;""border : 1px solid #3ECEFF;""border-radius : 2px");}else{//非选中状态setStyleSheet("color : #000000;""background-color : #FFFFFF;""border : 1px solid #D9D9D9;""border-radius : 2px");}
}bool PageButton::getIsFoldBtn() const
{return isFoldBtn;
}void PageButton::setFold(bool isFold)
{this->isFoldBtn = isFold;if(isFold){setText("...");}else{setText(QString::number(page));}
}
然后我们添加一个Paginator设计师类,这里我们不借助ui界面进行布局了,换一种方式尝试我们直接在代码中进行控件的创建与布局,同时基于我们上面对分页器逻辑的分析,我们可以这样实现Paginator类:
///////paginator.h
#ifndef PAGINATOR_H
#define PAGINATOR_H#include <QWidget>
#include <QPushButton>
#include <QLineEdit>
#include "pagebutton.h"namespace Ui {
class Paginator;
}class Paginator : public QWidget
{Q_OBJECTpublic:explicit Paginator(QWidget *parent = nullptr,int pageCount = 7,int btnNumber = 7);~Paginator();
private:void initUi();void initSignalsAndSlots();//初始化所有的信号槽函数void onPageBtnClicked();//页面按钮被按下void onPrevBtnClicked();//前一页按钮被按下void onNextBtnClicked();//后一页按钮被按下void onPageEditEdited();//跳转页编辑框输入返回void jumpToPage(int page);//设置跳往的页数//跳转页数的三种情况void jumpToPageCase1(int page);void jumpToPageCase2(int page);void jumpToPageCase3(int page);
private:Ui::Paginator *ui;//当前选中的页面int currentPage;//默认显示的按钮数-除去<与>按钮int defaultBtnNum;//总页数int pageCount;//跳转页编辑框QLineEdit* pageEdit;//当前选中页面的<与>对应的按钮QPushButton* prevBtn;QPushButton* nextBtn;//存储除<与>的所有pageBtn按钮QList<PageButton*> pageButtons;
};#endif // PAGINATOR_H////////paginator.cpp
#include "paginator.h"
#include "ui_paginator.h"
#include <QHBoxLayout>
#include <QLabel>Paginator::Paginator(QWidget *parent,int pageCount,int btnNumber): QWidget(parent), ui(new Ui::Paginator),pageCount(pageCount)
{ui->setupUi(this);//设置defaultBtnNum必须在7-12之间btnNumber = std::min(12,btnNumber);btnNumber = std::max(7,btnNumber);defaultBtnNum = btnNumber;//初始化分页器界面initUi();//绑定信号槽函数initSignalsAndSlots();
}void Paginator::initUi()
{//添加水平布局器QHBoxLayout* layout = new QHBoxLayout(this);layout->setContentsMargins(0,0,3,0);//左 上 右 下:顺时针layout->setSpacing(4);layout->addStretch();//水平弹簧//设置固定宽高setFixedSize(1270,32);//设置<与>按钮prevBtn = new QPushButton();prevBtn->setFixedSize(32,32);prevBtn->setIconSize(QSize(12,12));prevBtn->setIcon(QIcon(":/images/admin/arrow-left.png"));nextBtn = new QPushButton();nextBtn->setFixedSize(32,32);nextBtn->setIconSize(QSize(12,12));nextBtn->setIcon(QIcon(":/images/admin/arrow-right.png"));layout->addWidget(prevBtn);//1号按钮默认为激活状态PageButton* btn0 = new PageButton(nullptr,1);pageButtons.append(btn0);btn0->setActive(true);layout->addWidget(btn0);currentPage = 1;//设置当前页面if(pageCount <= defaultBtnNum){//按照pageCount设置总按钮数for(int i = 1;i < pageCount;i++){PageButton* btn = new PageButton(nullptr,i + 1);pageButtons.append(btn);layout->addWidget(btn);}}else{//需要设置折叠按钮-按照defaultBtnNumber设置总按钮数for(int i = 1;i < defaultBtnNum;i++){PageButton* btn = new PageButton();if(i == defaultBtnNum - 2){btn->setPage(i + 1);btn->setFold(true);}else if(i == defaultBtnNum - 1){btn->setPage(pageCount);}else{btn->setPage(i + 1);}pageButtons.append(btn);layout->addWidget(btn);}}layout->addWidget(nextBtn);//添加编辑框和提示labelQLabel* hintLabel1 = new QLabel("跳转至");layout->addWidget(hintLabel1);pageEdit = new QLineEdit();pageEdit->setFixedSize(QSize(48, 32));pageEdit->setAlignment(Qt::AlignCenter);//文字居中pageEdit->setStyleSheet("QLineEdit{""background-color: #FFFFFF; ""border: 1px solid #D9D9D9; ""border-radius: 4px;}");layout->addWidget(pageEdit);QLabel* hintLabel2 = new QLabel("页");layout->addWidget(hintLabel2);
}void Paginator::initSignalsAndSlots()
{//绑定prevBtn与nextBtn的槽函数connect(prevBtn,&QPushButton::clicked,this,&Paginator::onPrevBtnClicked);connect(nextBtn,&QPushButton::clicked,this,&Paginator::onNextBtnClicked);//绑定pageButton对应的槽函数for(int i = 0;i < defaultBtnNum;i++){connect(pageButtons[i],&PageButton::clicked,this,&Paginator::onPageBtnClicked);}//绑定编辑框输入完毕的槽函数connect(pageEdit,&QLineEdit::returnPressed,this,&Paginator::onPageEditEdited);
}Paginator::~Paginator()
{delete ui;
}void Paginator::onPageBtnClicked()
{//从sender也就是激活该函数的控件处获取设置页int page = static_cast<PageButton*>(sender())->getPage();jumpToPage(page);
}void Paginator::onPrevBtnClicked()
{if(currentPage - 1 < 1){return;}jumpToPage(currentPage - 1);
}void Paginator::onNextBtnClicked()
{if(currentPage + 1 > pageCount){return;}jumpToPage(currentPage + 1);
}void Paginator::onPageEditEdited()
{int jumpPage = pageEdit->text().toInt();if(jumpPage < 1)jumpPage = 1;else if(jumpPage > pageCount)jumpPage = pageCount;jumpToPage(jumpPage);pageEdit->setText("");
}void Paginator::jumpToPage(int page)
{currentPage = page;//再次验证page是否合法if(page < 1 || page > pageCount){return;}//当没有折叠按钮时if(pageCount <= defaultBtnNum){for(int i = 0;i < pageCount;i++){if(page - 1 == i)pageButtons[i]->setActive(true);elsepageButtons[i]->setActive(false);}}else{//有折叠按钮时分为三种情况if(page >= 1 && page <= defaultBtnNum - 2){jumpToPageCase1(page);}else if(page > defaultBtnNum - 2 && page <= pageCount - defaultBtnNum + 2){jumpToPageCase2(page);}else{jumpToPageCase3(page);}}
}void Paginator::jumpToPageCase1(int page)
{for(int i = 0;i < defaultBtnNum;i++){//设置按钮对应的页号-同时设置折叠状态if(i == defaultBtnNum - 2){pageButtons[i]->setPage(i + 1);pageButtons[i]->setFold(true);}else if(i == defaultBtnNum - 1){pageButtons[i]->setPage(pageCount);}else{pageButtons[i]->setPage(i + 1);}//设置激活状态if(page == pageButtons[i]->getPage()){pageButtons[i]->setActive(true);}else{pageButtons[i]->setActive(false);}}
}void Paginator::jumpToPageCase2(int page)
{//清除对应1与pageCount页的按钮的活跃状态pageButtons[0]->setPage(1);pageButtons[0]->setActive(false);pageButtons[defaultBtnNum - 1]->setPage(pageCount);pageButtons[defaultBtnNum - 1]->setActive(false);//剩余可设置按钮数的一半-向上取整int residueBtn = (defaultBtnNum - 1) / 2;//(defaultBtnNum - 2 + 1)/2//设置中间部分for(int i = 1;i < defaultBtnNum - 1;i++){//设置按钮对应的页号if(i <= residueBtn){pageButtons[i]->setPage(page - (residueBtn - i));}else{pageButtons[i]->setPage(page + i - residueBtn);}//设置激活状态if(page == pageButtons[i]->getPage()){pageButtons[i]->setActive(true);}else{pageButtons[i]->setActive(false);}//设置折叠状态if(i == 1 || i == defaultBtnNum - 2){pageButtons[i]->setFold(true);}}
}void Paginator::jumpToPageCase3(int page)
{for(int i = 0;i < defaultBtnNum;i++){//设置按钮对应的页号-同时设置折叠状态if(i == 0){pageButtons[i]->setPage(1);}else if(i == 1){pageButtons[i]->setPage(pageCount - defaultBtnNum + i + 1);pageButtons[i]->setFold(true);}else{pageButtons[i]->setPage(pageCount - defaultBtnNum + i + 1);}//设置激活状态if(page == pageButtons[i]->getPage()){pageButtons[i]->setActive(true);}else{pageButtons[i]->setActive(false);}}
}
在审核页面和角色管理页面都有分页器,在这两个窗口中添加并显示分⻚器。
///////checktable.cpp
CheckTable::CheckTable(QWidget *parent): QWidget(parent), ui(new Ui::CheckTable)
{//设置分页器的显示Paginator* paginator = new Paginator(ui->PaginatorArea,36,9);paginator->move(0,15);paginator->show();
}
/////roletable.cpp中添加方式和上面一样
三.登录界面布局
刚开始用户为临时用户,临时用户操作有限,只能观看视频等,如果要发送弹幕、视频点赞等操作,必须先要登录进系统中,因此需要添加⼀个登录页面。⽀持两种登录⽅式:密码登录 和 短信登录。那我们可以新建一个设计师类类名为Login然后在其ui文件中进行布局:
界面中控件的嵌套关系如下:
当然因为登录窗口是一个弹窗,我们可以去让他继承自QDialog,但是我们这里换一种写法,同时设置输入限制和密码登录与短信登录界面切换的逻辑,实现如下:
////////login.h
#ifndef LOGIN_H
#define LOGIN_H#include <QWidget>
#include <QRegularExpressionValidator>namespace Ui {
class Login;
}class Login : public QWidget
{Q_OBJECTpublic:explicit Login(QWidget *parent = nullptr);~Login();private:void initSignalsAndSlots();void onPasswordBtnClicked();void onMessageBtnClicked();
private:Ui::Login *ui;QRegularExpressionValidator* authcodeValidator;//验证码一栏的验证器
};#endif // LOGIN_H////////login.cpp
#include "login.h"
#include "ui_login.h"
#include "limeplayer.h"
#include <QGraphicsDropShadowEffect>
#include <QRegularExpression>Login::Login(QWidget *parent): QWidget(parent), ui(new Ui::Login)
{ui->setupUi(this);//移动窗口位置this->move(LimePlayer::getInstance()->mapToGlobal(QPoint(0,0)));//设置为模态对话框并拿掉标题栏setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);//设置Dialog与修改继承类为QDialog效果一样setAttribute(Qt::WA_ShowModal,true);//设置窗口close时自动析构setAttribute(Qt::WA_DeleteOnClose,true);// 窗⼝加上阴影效果setAttribute(Qt::WA_TranslucentBackground); // 阴影效果必须要窗⼝透明QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(this);shadowEffect->setColor(Qt::black);shadowEffect->setBlurRadius(25);shadowEffect->setOffset(0);ui->background->setGraphicsEffect(shadowEffect);//设置正则表达式以限制手机号的输入QRegularExpression regExp("^1\\d{10}$");QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp,this);//为手机号位置设置正则表达式加以输入限制ui->accountEdit->setValidator(validator);//设置验证码位置的验证器QRegularExpression regExp1("^\\d{6}$");authcodeValidator = new QRegularExpressionValidator(regExp1,this);ui->passwordEdit->setValidator(authcodeValidator);//连接所有信号槽initSignalsAndSlots();
}void Login::initSignalsAndSlots()
{connect(ui->minBtn,&QPushButton::clicked,this,&Login::showMinimized);connect(ui->quitBtn,&QPushButton::clicked,this,&Login::close);connect(ui->passwordBtn,&QPushButton::clicked,this,&Login::onPasswordBtnClicked);connect(ui->messageBtn,&QPushButton::clicked,this,&Login::onMessageBtnClicked);
}Login::~Login()
{delete ui;
}void Login::onPasswordBtnClicked()
{ui->loginOrRegister->hide();ui->authcodeBtn->hide();ui->passwordLabel->setText("密码");ui->passwordEdit->setText("");ui->passwordEdit->setPlaceholderText("请输入密码");ui->passwordBtn->setStyleSheet("#passwordBtn{""font-size : 22px;""color : #3ECEFE;""font-weight:bold;""border : none;""border-bottom : 6px solid #3ECEFE;""}");ui->messageBtn->setStyleSheet("#messageBtn{""font-size : 22px;""color : #222222;""border : none;""border-bottom: 2px solid #B5ECFF;""}");ui->passwordEdit->setValidator(nullptr);
}void Login::onMessageBtnClicked()
{ui->loginOrRegister->show();ui->authcodeBtn->show();ui->passwordLabel->setText("验证码");ui->passwordEdit->setText("");ui->passwordEdit->setPlaceholderText("请输入验证码");ui->messageBtn->setStyleSheet("#messageBtn{""font-size : 22px;""color : #3ECEFE;""font-weight:bold;""border : none;""border-bottom : 6px solid #3ECEFE;""}");ui->passwordBtn->setStyleSheet("#passwordBtn{""font-size : 22px;""color : #222222;""border : none;""border-bottom: 2px solid #B5ECFF;""}");ui->passwordEdit->setValidator(authcodeValidator);
}
这里为了测试,我们为playerPage界面的点赞按钮设置点击后弹出此对话框:
//////playerpage.h
#ifndef PLAYERPAGE_H
#define PLAYERPAGE_H#include <QWidget>
#include <QGraphicsOpacityEffect>
#include <QPropertyAnimation>
#include <QTimer>
#include "volume.h"
#include "playspeed.h"namespace Ui {
class PlayerPage;
}class PlayerPage : public QWidget
{private slots:void onLikeImageBtnClicked();//点赞按钮被点击
};#endif // PLAYERPAGE_H
//////playerpage.cpp
void PlayerPage::initPlayer()
{//测试-显示登录窗口connect(ui->likeImageBtn,&QPushButton::clicked,this,&PlayerPage::onLikeImageBtnClicked);
}void PlayerPage::onLikeImageBtnClicked()
{Login* login = new Login();//Toast::showToast("登录之后才可以点赞哦~",login);-下面实现login->show();
}
在视频播放界面点击点赞按钮就有如下效果了:
四.toast窗口
Toast提示是⼀种用来进行关键信息提示的弹出式消息对话框,⼀般只显示几秒钟,然后⾃动消失不会干扰用户的正常操作。通常用于通知用户某些操作结果、错误或状态提示的弹出式消息框。最初在 Android 系统中被广泛使用,后来也被许多其他平台和应用程序借鉴。 界面上有些操作需要用户登录之后才有权限,比如发送弹幕、点赞、修改⽤⼾图像等,在用户没有登录时进行这些操作,应该给用户Toast提示。
我们对上面的onLikeImageBtnClicked函数中的Toast::showToast(“登录之后才可以点赞哦~”,login);取消注释后点击点赞按钮会先显示toast窗口然后再弹出登录界面,显示效果如下:
这里我们新建一个普通c++类类名为Toast,实现toast窗口的代码如下:
/////////toast.h
#ifndef TOAST_H
#define TOAST_H#include <QWidget>
#include <QPropertyAnimation>class Toast : public QWidget
{Q_OBJECT
public://静态的去显示消息提示窗口static void showToast(const QString& text,QWidget *widget);static void showToast(const QString& text);
private://提示窗先显示后显示窗口Toast(const QString& text,QWidget *widget);//仅仅显示提示窗口Toast(const QString& text);//初始化提示窗口的uivoid initUi(const QString& text);
private://窗口消失时的动画QPropertyAnimation* windowHide;
};#endif // TOAST_H//////toast.cpp
#include "toast.h"
#include <QTimer>
#include <QGraphicsOpacityEffect>
#include <QHBoxLayout>
#include <QLabel>
#include <QGuiApplication>
#include <QScreen>Toast::Toast(const QString &text, QWidget *widget)
{initUi(text);//设置定时器QTimer* timer = new QTimer(this);connect(timer,&QTimer::timeout,this,[=](){this->close();timer->stop();this->deleteLater();if(widget){widget->show();}});windowHide->start();timer->start(3000);
}Toast::Toast(const QString &text)
{initUi(text);//设置定时器QTimer* timer = new QTimer(this);connect(timer,&QTimer::timeout,this,[=](){this->close();timer->stop();this->deleteLater();//切记释放toast窗口});windowHide->start();timer->start(3000);
}void Toast::showToast(const QString &text, QWidget *widget)
{Toast* toast = new Toast(text,widget);toast->show();
}void Toast::showToast(const QString &text)
{Toast* toast = new Toast(text);toast->show();
}void Toast::initUi(const QString &text)
{//拿掉窗口的标题栏与设置背景透明-设置窗口固定宽高setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);setAttribute(Qt::WA_TranslucentBackground);setFixedSize(500,60);//初始化动画效果QGraphicsOpacityEffect* toastOpacity = new QGraphicsOpacityEffect(this);toastOpacity->setOpacity(1.0); // 初始完全不透明this->setGraphicsEffect(toastOpacity); // 将效果应用到控件windowHide = new QPropertyAnimation(toastOpacity, "opacity",this);//设置透明效果并挂到对象树上windowHide->setDuration(3000); // 动画持续3000毫秒(3秒)windowHide->setStartValue(1.0); // 起始值:完全不透明windowHide->setEndValue(0.0); // 结束值:完全透明windowHide->setEasingCurve(QEasingCurve::InOutQuad); // 使用平滑的缓动曲线//添加背景QWidget* toastBg = new QWidget(this);toastBg->setFixedSize(500,60);toastBg->setStyleSheet("background-color : rgba(106,106,106,0.9);""border-radius : 5px");//添加布局器QHBoxLayout* layout = new QHBoxLayout(toastBg);layout->setContentsMargins(0,5,0,5);//添加文本提示框QLabel* label = new QLabel(toastBg);label->setWordWrap(true);//设置自动换行label->setAlignment(Qt::AlignCenter);//设置文本居中label->setText(text);label->setStyleSheet("color : #FFFFFF;""font-family :""幼圆,""Microsoft YaHei,""sans-serif;");//添加label到背景框中layout->addWidget(label);//调整toast窗口的显示位置// 获取主屏幕的可用几何区域(排除任务栏等)QScreen *screen = QGuiApplication::primaryScreen();QRect screenGeometry = screen->geometry();int x = (screenGeometry.width() - 500) / 2;int y = screenGeometry.height() - 50 - 60;this->move(x,y);
}