基于脚手架微服务的视频点播系统-界面布局部分(二):用户界面及系统管理界面布局

基于脚手架微服务的视频点播系统-界面布局部分:二.首页及播放界面布局

我们先来看下本文结束后需要完成的界面:
1.用户界面:

2.修改个人信息界面

3.上传视频界面

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


5.登录注册界面

一.用户界面布局

1.1用户界面布局分析与实现

当用户点击"我的"页面切换按钮时,就会显示我的页面。仔细观察发现,我的页面整体属于上下结构布局,从上往下依次为:基本信息区、我的视频区、视频信息显⽰区,每个视频信息框可以复用VideoBox。

所以我们可以新增一个ui设计师类,类名为ModifyMyselfDialog然后在其ui界面进行布局如下:

界面控件嵌套关系如下:

和前文一样,详细的布局信息以及qss样式代码可以在本项目更新完毕之后的最后一篇博客置顶获取源码进行参考,这里我们就不罗列了为了避免文章长度冗余。

1.2更新用户图像按钮及逻辑

首先我的界面不会出现关注按钮,所以这里我们需要先隐藏掉:

cpp 复制代码
/////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文件详情实现如下:

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*60
    mask->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大小的,同时我们的图片还是圆形,所以这里我们就需要对用户上传的头像文件先转化为二进制流同时对该图像进行裁剪:

cpp 复制代码
//////////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,并设置去掉边框。

cpp 复制代码
//////////////////////////////// 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_OBJECT

public:
    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);
    //默认隐藏修改密码后的widget
    ui->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类:

cpp 复制代码
///////////////newpassworddialog.h
#ifndef NEWPASSWORDDIALOG_H
#define NEWPASSWORDDIALOG_H

#include <QDialog>

namespace Ui {
class NewPasswordDialog;
}

class NewPasswordDialog : public QDialog
{
    Q_OBJECT

public:
    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处理该信号将页面切换到上传视频页面,当用户填好视频信息点击提交按钮时再跳回我的页面:

cpp 复制代码
///////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H

#include <QWidget>
#include "pageswitchbutton.h"

namespace Ui {
class UploadVideoPage;
}

class UploadVideoPage : public QWidget
{
    Q_OBJECT

public:
    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()){
        // 视频⼤小限制,上限为4G
        QFileInfo 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信号,在绑定的槽函数中实现剩余字数提示功能。

cpp 复制代码
//////////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H

#include <QWidget>
#include "pageswitchbutton.h"

namespace Ui {
class UploadVideoPage;
}

class UploadVideoPage : public QWidget
{
    Q_OBJECT

public:
    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:

cpp 复制代码
//////////////////////uploadvideopage.h
#ifndef UPLOADVIDEOPAGE_H
#define UPLOADVIDEOPAGE_H

#include <QWidget>
#include "pageswitchbutton.h"

namespace Ui {
class UploadVideoPage;
}

class UploadVideoPage : public QWidget
{
    Q_OBJECT

public:
    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编辑框进行条件限制。当然当⽤⼾点击重置和查看按钮后,按钮也需要响应对应的操作。

cpp 复制代码
/////////////////checktable.h
#ifndef CHECKTABLE_H
#define CHECKTABLE_H

#include <QWidget>

namespace Ui {
class CheckTable;
}

class CheckTable : public QWidget
{
    Q_OBJECT

public:
    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。在⻆⾊管理页面,管理员通过用户⼿机号查看用户信息,因此⼿机号编辑框需要进行输入限制。此外管理员也可以根据用户状态查看该状态下的所有用户信息,因此用户状态的初始数据需要提前设置好。此外重置和审核按钮点击时,也需要处理相应的逻辑:

cpp 复制代码
///////////////////////roletable.h
#ifndef ROLETABLE_H
#define ROLETABLE_H

#include <QWidget>

namespace Ui {
class RoleTable;
}

class RoleTable : public QWidget
{
    Q_OBJECT

public:
    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界面即可。

cpp 复制代码
//////////adminwidget.h
#ifndef ADMINWIDGET_H
#define ADMINWIDGET_H

#include <QWidget>

namespace Ui {
class AdminWidget;
}

class AdminWidget : public QWidget
{
    Q_OBJECT

public:
    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中添加更新函数就能有文章刚开始展示的效果了:

cpp 复制代码
////////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那样给他设置几个到界面中就能有开头展示的效果了:

cpp 复制代码
/////roletableitem.h
#ifndef ROLETABLEITEM_H
#define ROLETABLEITEM_H

#include <QWidget>

namespace Ui {
class RoleTableItem;
}

class RoleTableItem : public QWidget
{
    Q_OBJECT

public:
    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创建是⼀个模态对话框,并且能够在外部设置创建新增对话框,还是编辑对话框。同时我们设置正则表达式限制输入框中的输入

cpp 复制代码
///////////////////edituserdialog.h
#ifndef EDITUSERDIALOG_H
#define EDITUSERDIALOG_H

#include <QDialog>

namespace Ui {
class EditUserDialog;
}

class EditUserDialog : public QDialog
{
    Q_OBJECT

public:
    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窗口。

cpp 复制代码
////////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设置为单例模式:

cpp 复制代码
/////limeplayer.h
#ifndef LIMEPLAYER_H
#define LIMEPLAYER_H

#include <QWidget>
#include "pageswitchbutton.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class LimePlayer;
}
QT_END_NAMESPACE

class LimePlayer : public QWidget
{
    Q_OBJECT

public:
    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设置其初始位置即可:

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。具体实现如下:

cpp 复制代码
//////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类:

cpp 复制代码
///////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_OBJECT

public:
    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);

    //添加编辑框和提示label
    QLabel* 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);
            else
                pageButtons[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);
        }
    }
}

在审核页面和角色管理页面都有分页器,在这两个窗口中添加并显示分⻚器。

cpp 复制代码
///////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,但是我们这里换一种写法,同时设置输入限制和密码登录与短信登录界面切换的逻辑,实现如下:

cpp 复制代码
////////login.h
#ifndef LOGIN_H
#define LOGIN_H

#include <QWidget>
#include <QRegularExpressionValidator>

namespace Ui {
class Login;
}

class Login : public QWidget
{
    Q_OBJECT

public:
    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界面的点赞按钮设置点击后弹出此对话框:

cpp 复制代码
//////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窗口的代码如下:

cpp 复制代码
/////////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);
    //初始化提示窗口的ui
    void 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);
}
相关推荐
喂完待续5 小时前
【Big Data】云原生与AI时代的存储基石 Apache Ozone 的技术演进路径
云原生·架构·apache·big data·序列晋升
mit6.8245 小时前
[re_3]
c++·算法
乌萨奇也要立志学C++5 小时前
【C++详解】异常概念、抛出捕获与处理机制全解析
开发语言·c++
码农客栈5 小时前
qt QWebSocket详解
qt
XXYBMOOO5 小时前
使用Qt Charts实现高效多系列数据可视化
开发语言·qt·ui·信息可视化
源代码•宸6 小时前
Leetcode—3516. 找到最近的人【简单】
c++·经验分享·算法·leetcode
1白天的黑夜16 小时前
哈希表-219.存在重复元素II-力扣(LeetCode)
数据结构·c++·leetcode
小欣加油6 小时前
leetcode 38 外观数列
c++·算法·leetcode
lssjzmn6 小时前
会话管理巅峰对决:Spring Web中Cookie-Session、JWT、Spring Session + Redis深度秘籍
java·spring·架构