聊天项目(12) 注册界面完善

增加定时按钮

点击获取验证码后需要让按钮显示倒计时,然后倒计时结束后再次可点击。
添加TimberBtn类

  1. #ifndef TIMERBTN_H
  2. #define TIMERBTN_H
  3. #include <QPushButton>
  4. #include <QTimer>
  5. class TimerBtn : public QPushButton
  6. {
  7. public:
  8. TimerBtn(QWidget *parent = nullptr);
  9. ~ TimerBtn();
  10. // 重写mouseReleaseEvent
  11. virtual void mouseReleaseEvent(QMouseEvent *e) override;
  12. private:
  13. QTimer *_timer;
  14. int _counter;
  15. };
  16. #endif // TIMERBTN_H

添加实现

  1. #include "timerbtn.h"
  2. #include <QMouseEvent>
  3. #include <QDebug>
  4. TimerBtn::TimerBtn(QWidget *parent):QPushButton(parent),_counter(10)
  5. {
  6. _timer = new QTimer(this);
  7. connect(_timer, &QTimer::timeout, [this](){
  8. _counter--;
  9. if(_counter <= 0){
  10. _timer->stop();
  11. _counter = 10;
  12. this->setText("获取");
  13. this->setEnabled(true);
  14. return;
  15. }
  16. this->setText(QString::number(_counter));
  17. });
  18. }
  19. TimerBtn::~TimerBtn()
  20. {
  21. _timer->stop();
  22. }
  23. void TimerBtn::mouseReleaseEvent(QMouseEvent *e)
  24. {
  25. if (e->button() == Qt::LeftButton) {
  26. // 在这里处理鼠标左键释放事件
  27. qDebug() << "MyButton was released!";
  28. this->setEnabled(false);
  29. this->setText(QString::number(_counter));
  30. _timer->start(1000);
  31. emit clicked();
  32. }
  33. // 调用基类的mouseReleaseEvent以确保正常的事件处理(如点击效果)
  34. QPushButton::mouseReleaseEvent(e);
  35. }

然后将注册界面获取按钮升级为TimerBtn

调整输入框错误提示

在RegisterDialog构造函数中删除原来的输入框editing信号和逻辑,添加editingFinished信号和处理逻辑。

  1. //day11 设定输入框输入后清空字符串
  2. ui->err_tip->clear();
  3. connect(ui->user_edit,&QLineEdit::editingFinished,this,[this](){
  4. checkUserValid();
  5. });
  6. connect(ui->email_edit, &QLineEdit::editingFinished, this, [this](){
  7. checkEmailValid();
  8. });
  9. connect(ui->pass_edit, &QLineEdit::editingFinished, this, [this](){
  10. checkPassValid();
  11. });
  12. connect(ui->confirm_edit, &QLineEdit::editingFinished, this, [this](){
  13. checkConfirmValid();
  14. });
  15. connect(ui->varify_edit, &QLineEdit::editingFinished, this, [this](){
  16. checkVarifyValid();
  17. });

global.h中添加TipErr定义

  1. enum TipErr{
  2. TIP_SUCCESS = 0,
  3. TIP_EMAIL_ERR = 1,
  4. TIP_PWD_ERR = 2,
  5. TIP_CONFIRM_ERR = 3,
  6. TIP_PWD_CONFIRM = 4,
  7. TIP_VARIFY_ERR = 5,
  8. TIP_USER_ERR = 6
  9. };

RegisterDialog声明中添加

  1. QMap<TipErr, QString> _tip_errs;

_tip_errs用来缓存各个输入框输入完成后提示的错误,如果该输入框错误清除后就显示剩余的错误,每次只显示一条

实现添加错误和删除错误

  1. void ResetDialog::AddTipErr(TipErr te, QString tips)
  2. {
  3. _tip_errs[te] = tips;
  4. showTip(tips, false);
  5. }
  6. void ResetDialog::DelTipErr(TipErr te)
  7. {
  8. _tip_errs.remove(te);
  9. if(_tip_errs.empty()){
  10. ui->err_tip->clear();
  11. return;
  12. }
  13. showTip(_tip_errs.first(), false);
  14. }

实现错误检测

  1. bool ResetDialog::checkUserValid()
  2. {
  3. if(ui->user_edit->text() == ""){
  4. AddTipErr(TipErr::TIP_USER_ERR, tr("用户名不能为空"));
  5. return false;
  6. }
  7. DelTipErr(TipErr::TIP_USER_ERR);
  8. return true;
  9. }
  10. bool ResetDialog::checkPassValid()
  11. {
  12. auto pass = ui->pwd_edit->text();
  13. if(pass.length() < 6 || pass.length()>15){
  14. //提示长度不准确
  15. AddTipErr(TipErr::TIP_PWD_ERR, tr("密码长度应为6~15"));
  16. return false;
  17. }
  18. // 创建一个正则表达式对象,按照上述密码要求
  19. // 这个正则表达式解释:
  20. // ^[a-zA-Z0-9!@#$%^&*]{6,15}$ 密码长度至少6,可以是字母、数字和特定的特殊字符
  21. QRegularExpression regExp("^[a-zA-Z0-9!@#$%^&*]{6,15}$");
  22. bool match = regExp.match(pass).hasMatch();
  23. if(!match){
  24. //提示字符非法
  25. AddTipErr(TipErr::TIP_PWD_ERR, tr("不能包含非法字符"));
  26. return false;;
  27. }
  28. DelTipErr(TipErr::TIP_PWD_ERR);
  29. return true;
  30. }
  31. bool ResetDialog::checkEmailValid()
  32. {
  33. //验证邮箱的地址正则表达式
  34. auto email = ui->email_edit->text();
  35. // 邮箱地址的正则表达式
  36. QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");
  37. bool match = regex.match(email).hasMatch(); // 执行正则表达式匹配
  38. if(!match){
  39. //提示邮箱不正确
  40. AddTipErr(TipErr::TIP_EMAIL_ERR, tr("邮箱地址不正确"));
  41. return false;
  42. }
  43. DelTipErr(TipErr::TIP_EMAIL_ERR);
  44. return true;
  45. }
  46. bool ResetDialog::checkVarifyValid()
  47. {
  48. auto pass = ui->varify_edit->text();
  49. if(pass.isEmpty()){
  50. AddTipErr(TipErr::TIP_VARIFY_ERR, tr("验证码不能为空"));
  51. return false;
  52. }
  53. DelTipErr(TipErr::TIP_VARIFY_ERR);
  54. return true;
  55. }

除此之外修改之前点击确认按钮的逻辑,改为检测所有条件成立后再发送请求

  1. void ResetDialog::on_sure_btn_clicked()
  2. {
  3. bool valid = checkUserValid();
  4. if(!valid){
  5. return;
  6. }
  7. valid = checkEmailValid();
  8. if(!valid){
  9. return;
  10. }
  11. valid = checkPassValid();
  12. if(!valid){
  13. return;
  14. }
  15. valid = checkVarifyValid();
  16. if(!valid){
  17. return;
  18. }
  19. //发送http重置用户请求
  20. QJsonObject json_obj;
  21. json_obj["user"] = ui->user_edit->text();
  22. json_obj["email"] = ui->email_edit->text();
  23. json_obj["passwd"] = xorString(ui->pwd_edit->text());
  24. json_obj["varifycode"] = ui->varify_edit->text();
  25. HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/reset_pwd"),
  26. json_obj, ReqId::ID_RESET_PWD,Modules::RESETMOD);
  27. }

隐藏和显示密码

我们在输入密码时希望能通过点击可见还是不可见,显示密码和隐藏密码,这里先添加图片放入资源中,然后在Register.ui中添加两个label,分别命名为pass_visible和confirm_visible, 用来占据位置。

因为我们要做的点击后图片要有状态切换,以及浮动显示不一样的效果等,所以我们重写ClickedLabel,继承自QLabel.

  1. #ifndef CLICKEDLABEL_H
  2. #define CLICKEDLABEL_H
  3. #include <QLabel>
  4. #include "global.h"
  5. class ClickedLabel:public QLabel
  6. {
  7. Q_OBJECT
  8. public:
  9. ClickedLabel(QWidget* parent);
  10. virtual void mousePressEvent(QMouseEvent *ev) override;
  11. virtual void enterEvent(QEvent* event) override;
  12. virtual void leaveEvent(QEvent* event) override;
  13. void SetState(QString normal="", QString hover="", QString press="",
  14. QString select="", QString select_hover="", QString select_press="");
  15. ClickLbState GetCurState();
  16. protected:
  17. private:
  18. QString _normal;
  19. QString _normal_hover;
  20. QString _normal_press;
  21. QString _selected;
  22. QString _selected_hover;
  23. QString _selected_press;
  24. ClickLbState _curstate;
  25. signals:
  26. void clicked(void);
  27. };
  28. #endif // CLICKEDLABEL_H

一个Label有六种状态,普通状态,普通的悬浮状态,普通的点击状态,选中状态,选中的悬浮状态,选中的点击状态。

当Label处于普通状态,被点击后,切换为选中状态,再次点击又切换为普通状态。

ClickLbState定义在global.h中,包含两种状态一个是普通状态,一个是选中状态。而Label中的六种状态就是基于这两种状态嵌套实现的。

  1. enum ClickLbState{
  2. Normal = 0,
  3. Selected = 1
  4. };

六种状态用qss写好,这样我们只需要根据鼠标事件切换不同的qss就可以实现样式变换。

  1. #pass_visible[state='unvisible']{
  2. border-image: url(:/res/unvisible.png);
  3. }
  4. #pass_visible[state='unvisible_hover']{
  5. border-image: url(:/res/unvisible_hover.png);
  6. }
  7. #pass_visible[state='visible']{
  8. border-image: url(:/res/visible.png);
  9. }
  10. #pass_visible[state='visible_hover']{
  11. border-image: url(:/res/visible_hover.png);
  12. }
  13. #confirm_visible[state='unvisible']{
  14. border-image: url(:/res/unvisible.png);
  15. }
  16. #confirm_visible[state='unvisible_hover']{
  17. border-image: url(:/res/unvisible_hover.png);
  18. }
  19. #confirm_visible[state='visible']{
  20. border-image: url(:/res/visible.png);
  21. }
  22. #confirm_visible[state='visible_hover']{
  23. border-image: url(:/res/visible_hover.png);
  24. }

我们实现ClickedLabel功能

  1. #include "clickedlabel.h"
  2. #include <QMouseEvent>
  3. ClickedLabel::ClickedLabel(QWidget* parent):QLabel (parent),_curstate(ClickLbState::Normal)
  4. {
  5. }
  6. // 处理鼠标点击事件
  7. void ClickedLabel::mousePressEvent(QMouseEvent* event) {
  8. if (event->button() == Qt::LeftButton) {
  9. if(_curstate == ClickLbState::Normal){
  10. qDebug()<<"clicked , change to selected hover: "<< _selected_hover;
  11. _curstate = ClickLbState::Selected;
  12. setProperty("state",_selected_hover);
  13. repolish(this);
  14. update();
  15. }else{
  16. qDebug()<<"clicked , change to normal hover: "<< _normal_hover;
  17. _curstate = ClickLbState::Normal;
  18. setProperty("state",_normal_hover);
  19. repolish(this);
  20. update();
  21. }
  22. emit clicked();
  23. }
  24. // 调用基类的mousePressEvent以保证正常的事件处理
  25. QLabel::mousePressEvent(event);
  26. }
  27. // 处理鼠标悬停进入事件
  28. void ClickedLabel::enterEvent(QEvent* event) {
  29. // 在这里处理鼠标悬停进入的逻辑
  30. if(_curstate == ClickLbState::Normal){
  31. qDebug()<<"enter , change to normal hover: "<< _normal_hover;
  32. setProperty("state",_normal_hover);
  33. repolish(this);
  34. update();
  35. }else{
  36. qDebug()<<"enter , change to selected hover: "<< _selected_hover;
  37. setProperty("state",_selected_hover);
  38. repolish(this);
  39. update();
  40. }
  41. QLabel::enterEvent(event);
  42. }
  43. // 处理鼠标悬停离开事件
  44. void ClickedLabel::leaveEvent(QEvent* event){
  45. // 在这里处理鼠标悬停离开的逻辑
  46. if(_curstate == ClickLbState::Normal){
  47. qDebug()<<"leave , change to normal : "<< _normal;
  48. setProperty("state",_normal);
  49. repolish(this);
  50. update();
  51. }else{
  52. qDebug()<<"leave , change to normal hover: "<< _selected;
  53. setProperty("state",_selected);
  54. repolish(this);
  55. update();
  56. }
  57. QLabel::leaveEvent(event);
  58. }
  59. void ClickedLabel::SetState(QString normal, QString hover, QString press,
  60. QString select, QString select_hover, QString select_press)
  61. {
  62. _normal = normal;
  63. _normal_hover = hover;
  64. _normal_press = press;
  65. _selected = select;
  66. _selected_hover = select_hover;
  67. _selected_press = select_press;
  68. setProperty("state",normal);
  69. repolish(this);
  70. }
  71. ClickLbState ClickedLabel::GetCurState(){
  72. return _curstate;
  73. }

将label升级为ClickedLabel,然后在RegisterDialog的构造函数中添加label点击的响应函数

  1. //设置浮动显示手形状
  2. ui->pass_visible->setCursor(Qt::PointingHandCursor);
  3. ui->confirm_visible->setCursor(Qt::PointingHandCursor);
  4. ui->pass_visible->SetState("unvisible","unvisible_hover","","visible",
  5. "visible_hover","");
  6. ui->confirm_visible->SetState("unvisible","unvisible_hover","","visible",
  7. "visible_hover","");
  8. //连接点击事件
  9. connect(ui->pass_visible, &ClickedLabel::clicked, this, [this]() {
  10. auto state = ui->pass_visible->GetCurState();
  11. if(state == ClickLbState::Normal){
  12. ui->pass_edit->setEchoMode(QLineEdit::Password);
  13. }else{
  14. ui->pass_edit->setEchoMode(QLineEdit::Normal);
  15. }
  16. qDebug() << "Label was clicked!";
  17. });
  18. connect(ui->confirm_visible, &ClickedLabel::clicked, this, [this]() {
  19. auto state = ui->confirm_visible->GetCurState();
  20. if(state == ClickLbState::Normal){
  21. ui->confirm_edit->setEchoMode(QLineEdit::Password);
  22. }else{
  23. ui->confirm_edit->setEchoMode(QLineEdit::Normal);
  24. }
  25. qDebug() << "Label was clicked!";
  26. });

这样就实现了通过点击切换密码的显示和隐藏。

注册成功提示页面

注册成功后要切换到提示页面,所以在initHandlers函数内实现收到服务器注册回复的请求

  1. //注册注册用户回包逻辑
  2. _handlers.insert(ReqId::ID_REG_USER, [this](QJsonObject jsonObj){
  3. int error = jsonObj["error"].toInt();
  4. if(error != ErrorCodes::SUCCESS){
  5. showTip(tr("参数错误"),false);
  6. return;
  7. }
  8. auto email = jsonObj["email"].toString();
  9. showTip(tr("用户注册成功"), true);
  10. qDebug()<< "email is " << email ;
  11. qDebug()<< "user uuid is " << jsonObj["uuid"].toString();
  12. ChangeTipPage();
  13. });

页面切换逻辑

  1. void RegisterDialog::ChangeTipPage()
  2. {
  3. _countdown_timer->stop();
  4. ui->stackedWidget->setCurrentWidget(ui->page_2);
  5. // 启动定时器,设置间隔为1000毫秒(1秒)
  6. _countdown_timer->start(1000);
  7. }

在RegisterDialog.ui中stackwidget的page2添加标签和返回按钮

https://cdn.llfc.club/1712821604946.jpg

在RegisterDialog构造函数中添加定时器回调

  1. // 创建定时器
  2. _countdown_timer = new QTimer(this);
  3. // 连接信号和槽
  4. connect(_countdown_timer, &QTimer::timeout, [this](){
  5. if(_countdown==0){
  6. _countdown_timer->stop();
  7. emit sigSwitchLogin();
  8. return;
  9. }
  10. _countdown--;
  11. auto str = QString("注册成功,%1 s后返回登录").arg(_countdown);
  12. ui->tip_lb->setText(str);
  13. });

除此之外在返回按钮的槽函数中停止定时器并发送切换登录的信号

  1. void RegisterDialog::on_return_btn_clicked()
  2. {
  3. _countdown_timer->stop();
  4. emit sigSwitchLogin();
  5. }

取消注册也发送切换登录信号

  1. void RegisterDialog::on_cancel_btn_clicked()
  2. {
  3. _countdown_timer->stop();
  4. emit sigSwitchLogin();
  5. }

界面跳转

回到mainwindow,构造函数简化,只做登录界面初始化

  1. MainWindow::MainWindow(QWidget *parent) :
  2. QMainWindow(parent),
  3. ui(new Ui::MainWindow)
  4. {
  5. ui->setupUi(this);
  6. //创建一个CentralWidget, 并将其设置为MainWindow的中心部件
  7. _login_dlg = new LoginDialog(this);
  8. _login_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);
  9. setCentralWidget(_login_dlg);
  10. //连接登录界面注册信号
  11. connect(_login_dlg, &LoginDialog::switchRegister, this, &MainWindow::SlotSwitchReg);
  12. //连接登录界面忘记密码信号
  13. connect(_login_dlg, &LoginDialog::switchReset, this, &MainWindow::SlotSwitchReset);
  14. }

在点击注册按钮的槽函数中

  1. void MainWindow::SlotSwitchReg()
  2. {
  3. _reg_dlg = new RegisterDialog(this);
  4. _reg_dlg->hide();
  5. _reg_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);
  6. //连接注册界面返回登录信号
  7. connect(_reg_dlg, &RegisterDialog::sigSwitchLogin, this, &MainWindow::SlotSwitchLogin);
  8. setCentralWidget(_reg_dlg);
  9. _login_dlg->hide();
  10. _reg_dlg->show();
  11. }

切换登录界面

  1. //从注册界面返回登录界面
  2. void MainWindow::SlotSwitchLogin()
  3. {
  4. //创建一个CentralWidget, 并将其设置为MainWindow的中心部件
  5. _login_dlg = new LoginDialog(this);
  6. _login_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);
  7. setCentralWidget(_login_dlg);
  8. _reg_dlg->hide();
  9. _login_dlg->show();
  10. //连接登录界面注册信号
  11. connect(_login_dlg, &LoginDialog::switchRegister, this, &MainWindow::SlotSwitchReg);
  12. //连接登录界面忘记密码信号
  13. connect(_login_dlg, &LoginDialog::switchReset, this, &MainWindow::SlotSwitchReset);
  14. }

这样登录界面和注册界面的切换逻辑就写完了。

热门评论

热门文章

  1. 解密定时器的实现细节

    喜欢(566) 浏览(1914)
  2. C++ 类的继承封装和多态

    喜欢(588) 浏览(2642)
  3. slice介绍和使用

    喜欢(521) 浏览(1770)
  4. Linux环境搭建和编码

    喜欢(594) 浏览(5676)
  5. windows环境搭建和vscode配置

    喜欢(587) 浏览(1723)

最新评论

  1. 双链表实现LRU算法 secondtonone1:双链表插入和删除节点是本篇的难点,多多练习即可。
  2. 线程安全的无锁栈 secondtonone1:谢谢支持,如果pop的次数大于push的次数是会让线程处于重试的,这个是测试用例,必须满足push和pop的次数相同,实际情况不会这么使用。栈的设计没有问题。
  3. 再谈单例模式 secondtonone1:是的,C++11以后返回局部static变量对象能保证线程安全了。
  4. Linux环境搭建和编码 恋恋风辰:Linux环境下go的安装比较简单,可以不用设置GOPATH环境变量,后期我们学习go mod 之后就拜托了go文件目录的限制了。
  5. C++ 类的拷贝构造、赋值运算、单例模式 secondtonone1:好的,已修复。

个人公众号

个人微信