QT--科目一考试系统

2023-05-16

大纲

  • 一.创建项目
  • 二.登录界面
    • 界面控件
    • 背景
    • 标题栏
  • 三.验证账号和密码
    • 登录按钮
    • 验证
  • 四.考试计时
  • 五.初始化题库
  • 六.按钮布局
  • 七.提交题目
  • 八.窗口交互
  • 九.发布
  • 十.全部代码

效果:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

一.创建项目

新建一个项目:
1.新建->application->Qt widgets Application
2.为项目起一个名字:ExamSys
3.选择组件: MinGW 32bit
4.选择类信息:
类名:LoginDialog
基类:QDialog(对话框类)

二.登录界面

在这里插入图片描述

界面控件

首先在设计模式下:
加入两个标签(Label),两个按钮(QPushButton),两个线(QLineEdit),分别把他们的的Text设置为如果的文字。

背景

1.将要加载的图片放入项目的文件夹中,
2.切换到编辑模式,右击ExamSys项目,选择添加新文件
在这里插入图片描述

3.选择Qt->Qt Resource File,建立名称为image
4.建立好之后在前缀那一行只输入一个/,之后点击添加文件,将文件加载进来
5.在设计模式中,加入一个标签,在Qlabel中选择pixmap将图片放入,将它放到最后面在这里插入图片描述
6.调整图片与标签与对话框三者之间的大小:
图片与对话框调整:
在logindialog.cpp:

    ui->imgLabel->setScaledContents(true);  //对图片进行填充设置
    this->resize(ui->imgLabel->width(),ui->imgLabel->height());
    //将窗口的大小设置为图片的大小

图片与标签调整:
在设计模式下:在QWidget中将X,Y设置为0

标题栏

 this->setWindowTitle("驾校科目一考试登录");         //对标题进行设置
    this->setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
    //只剩下一个关闭按钮

三.验证账号和密码

登录按钮

右击登录按钮->转到槽->clicked(),之后它会创建一个方法

验证

1.在项目文件夹中放入存放数据的文件(本程序加入一个名为"account.txt"的文件)
2.利用正则表达式查看账号格式是否正确
3.如果正确了在判断用户输入的账号密码是否在文件中

//需要包含以下两个头文件
#include "QFile"
#include "QTextStream"
void LoginDialog::on_LoginButton_clicked()
{
    QRegExp rx("^[A-Za-z0-9]+([_\\.][A-Za-z0-9]+)*@([A-Za-z0-9\\-]+\\.)+[A-Za-z]{2,6}$");
    /*开始的用户名一定是字母且不止一个,所以匹配多次.
     * 用户名中可能会有_或.所以匹配可以为0次.
     * 中间的@是一定存在的.
     * 域名会有字母或数字下划线.
     * 之后一定有.最后那一段不会太长,所以匹配2-6次({2,6}是为最后一个准备).
     */
    bool res = rx.exactMatch(ui->accountEdit->text());
    if(!res){
        QMessageBox::information(this,"提示","非法的邮箱地址,请你重新输入!");
        ui->accountEdit->clear();     //账号一行数据清空
        ui->codeEdit->clear();        //密码一行数据清空
        ui->accountEdit->setFocus();  //将光标重新对准账号那行
        return;
    }else{
        QString filename;   //账号密码数据文件
        QString strAccInput;//用户输入的账号
        QString strCode;    //用户输入的密码
        QString strLine;    //文件里的一行数据
        QStringList strList;    //分割读取的一行数据(字符串链表)
        filename  = "../account.txt";
        strAccInput = ui->accountEdit->text();
        strCode = ui->codeEdit->text();

        QFile file(filename);  //将文件赋给到file对象中
        QTextStream stream(&file); //给file对象插入一个流
        /*
        利用file对象的打开属性,以文本只读形式打开
        不断的循环遍历文件中的每一行数据,之后末尾
        将一行数据分别两段,分别对比账号和密码
        为什么 if(strAccInput == strList.at(0))没有else
        因为账号要 遍历文件中全部数据才能知道有没有,所以是循环结束还没有找到,才断定为账号不成立
        而密码是与账号一对一匹配的,所以如果账号正确了,密码就是账号数据这一行,不需要遍历全部,所以有else
         */
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
            while(!stream.atEnd()){
                strLine = stream.readLine();
                strList = strLine.split(","); //将字符串分别成一个字符数组,用,分割
                if(strAccInput == strList.at(0)){
                    if(strCode == strList.at(1)){
                      QMessageBox::information(this,"提示","欢迎登录科目一考试系统!");
                      file.close();
                      return;
                    }else{
                      QMessageBox::information(this,"提示","密码输入错误,请重新输入");
                      ui->codeEdit->clear();
                      ui->codeEdit->setFocus();
                      file.close();
                      return;
                    }
                }
            }
            QMessageBox::information(this,"提示","您输入的账号有误,请重新输入!");
            ui->accountEdit->clear();
            ui->codeEdit->clear();
            ui->accountEdit->setFocus();
            file.close();
            return;
        }else{
            QMessageBox::information(this,"提示","文件读取失败");
            return;
        }

    }

}

四.考试计时

先建立一个考试窗口类:
右击项目名->文件和类->C+±>C++ Class
类名:examdialog | baseclass: QDialog

examdialog.h

#ifndef EXAMDIALOG_H
#define EXAMDIALOG_H
#include<QDialog>
#include<QTimer>

class ExamDialog : public QDialog
{
    Q_OBJECT      
public:
    ExamDialog(QWidget* parent = 0);//构造函数
    void initTimer(); //初始化计时器
private:
    QTimer *m_timer;  //计时器
    int m_timeGO;     //考试已用时
private slots:
    void freshTime();
};

#endif // EXAMDIALOG_H

examdialog.cpp

#include "examdialog.h"

ExamDialog::ExamDialog(QWidget *parent):QDialog(parent)
{
    setWindowTitle("考试已用时: 0分0秒");
    initTimer();
}

void ExamDialog::initTimer()
{
    m_timeGO =0;
    m_timer = new QTimer(this);
    m_timer->setInterval(1000);
    m_timer->start();
    //连接信号与槽 connect(发送信号者,发送信号,响应信号者,响应的槽方法)
    connect(m_timer,SIGNAL(timeout()),this,SLOT(freshTime()));
}

void ExamDialog::freshTime()
{
    m_timeGO++;
    QString min = QString::number(m_timeGO/60);
    QString sec = QString::number(m_timeGO%60);
    setWindowTitle("考试已用时: "+min+"分"+sec+"秒");
}

main.cpp

#include "logindialog.h"
#include <QApplication>
#include <examdialog.h>
int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    //支持高分屏自动缩放
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QApplication a(argc, argv);//应用程序类创建一个对象
   // LoginDialog w;             //登录窗对象
  //  w.show();                  //显示
     ExamDialog w;
     w.show();
    return a.exec();
}
实例:

在这里插入图片描述

五.初始化题库

在文件夹中添加一个"exam.txt"文件
examdialog.h中添加
头文件:

#include<QTextEdit>
#include<QLabel>
#include<QRadioButton>
#include<QCheckBox>
#include<QGridLayout>

方法:

void initLayout(); //初始化布局管理器
bool initTextEdit();//初始化文本编译器

字段:

	QTextEdit *m_textEdit;  //考试题库显示
    QLabel *m_titleLabels[10];//题目标签
    QRadioButton *m_radioBtns[32];//单选题按钮
    QCheckBox *m_checkBtns[4];//多选题按钮
    QRadioButton *m_radioA;   //判断题A选项
    QRadioButton *m_radioB;  //判断题B选项
    QGridLayout *m_layout;  //布局管理器
    QStringList m_answerList; //答案

examdialog.cpp中

添加的头文件:

#include<QFile>
#include<QTextStream>
#include<QMessageBox>
#include<QApplication>

方法实现:


void ExamDialog::initLayout()
{
    m_layout = new QGridLayout(this);
    m_layout->setSpacing(10); //设置控件间的间距
    m_layout->setMargin(10);  //设置窗体与控件间的间隙
}

bool ExamDialog::initTextEdit()
{
    QString strLine;   //保存文件中读取到的一行数据
    QStringList strList; //保存读取到的答案行
    QString filename("../exam.txt");
    QFile file(filename);
    QTextStream stream(&file);
    stream.setCodec("UTF-8");
    if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
          m_textEdit = new QTextEdit(this);
          m_textEdit->setReadOnly(true);

          QString strText;//用于保存显示到文本编译器的数据
          int nLines = 0;
          while(!stream.atEnd()){
              //过滤首行
              if(nLines == 0){
                  stream.readLine();
                  nLines++;
                   continue;
              }
              //过滤答案行
              if((nLines>=6 && nLines<=6*9&&(nLines %6 == 0))
                      ||(nLines == 6*9+4)){
                  strLine =stream.readLine();
                  strList = strLine.split(" ");
                  m_answerList.append(strList.at(1));
                  strText+="\n";
                  nLines++;
                  continue;
              }
              strText+=stream.readLine();
              strText+="\n";
              nLines++;
          }
        m_textEdit->setText(strText);
        m_layout->addWidget(m_textEdit,0,0,1,10);
        file.close();
        return true;
    }else{
        return false;
    }
}

//并且更新一下构造函数
ExamDialog::ExamDialog(QWidget *parent):QDialog(parent)
{
    //设置字体大小
    QFont font;
    font.setPointSize(12);
    setFont(font);
    //设置窗体背景颜色
    setPalette(QPalette(QColor(209,215,255)));


    setWindowTitle("考试已用时: 0分0秒");
    setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint);
    resize(800,900);

    initTimer();
    initLayout();
    if(!initTextEdit()){
        QMessageBox::information(this,"提示","初始化题库数据文件失败");
        QTimer::singleShot(0,qApp,SLOT(quit()));
    }


}

效果:
在这里插入图片描述

六.按钮布局

examdialog.h中添加
头文件:#include<QButtonGroup>
方法:
void initButtons(); //初始化按钮及标签
字段:
QButtonGroup *m_btnGroups[9]; //单项按钮分组

examdialog.cpp中添加
头文件:#include<QPushButton>
S

void ExamDialog::initButtons()
{
    QStringList strList ={"A","B","c","D"};
    for(int i=0;i<10;i++){
        //题目标签
        m_titleLabels[i] = new QLabel(this);
        m_titleLabels[i]->setText("第"+QString::number(i+1)+"题");
        m_layout->addWidget(m_titleLabels[i],1,i);

        //判断题
        if(i==9){
            m_radioA = new QRadioButton(this);
            m_radioB = new QRadioButton(this);

            m_radioA->setText("正确");
            m_radioB->setText("错误");

            m_layout->addWidget(m_radioA,2,9);
            m_layout->addWidget(m_radioB,3,9);

            m_btnGroups[8] = new QButtonGroup(this);
            m_btnGroups[8]->addButton(m_radioA);
            m_btnGroups[8]->addButton(m_radioB);
            break;
        }
        if(i<8) m_btnGroups[i] = new QButtonGroup(this);
        //选择题
        for(int j=0;j<4;j++){
            if(i == 8){//多项选择
                m_checkBtns[j] = new QCheckBox(this);
                m_checkBtns[j]->setText(strList.at(j));
                m_layout->addWidget(m_checkBtns[j],2+j,8);
            }else{//多项选择
                m_radioBtns[4*i+j] = new QRadioButton(this);
                m_radioBtns[4*i+j]->setText(strList.at(j));
                m_layout->addWidget(m_radioBtns[4*i+j],2+j,i);

                m_btnGroups[i]->addButton(m_radioBtns[4*i+j]);
            }
        }
    }
    QPushButton *submitBtn = new QPushButton(this);
    submitBtn->setText("提交");
    submitBtn->setFixedSize(100,35);
    m_layout->addWidget(submitBtn,6,9);
}

图示:
在这里插入图片描述

七.提交题目

在void ExamDialog::initButtons()中添加槽方法
在这里插入图片描述
examdialog.h中添加
方法:
bool hasNoSelect(); //判断题目是否有未完成的
槽方法:
void getScore();
examdialog.cpp中添加

bool ExamDialog::hasNoSelect()
{
    int radioSelects = 0;
    for(int i=0;i<8;i++){
        if(m_btnGroups[i]->checkedButton())//判断是否一组中有被选中的
              radioSelects++;
    }
    //单选题有未完成的
    if(radioSelects!=8)
        return true;
    int checkSelects=0;
    for(int i=0;i<4;i++){
        if(m_checkBtns[i]->isChecked())
            checkSelects++;
    }
    //多选题有未完成的
    if(checkSelects==0)
        return true;
    //判断题有未完成的
    if(!m_radioA->isChecked() &&!m_radioB->isChecked())
        return true;

    return false;
}

void ExamDialog::getScore()
{
    if(hasNoSelect()){
        QMessageBox::information(this,"提示","您有未完成的题目,请完成考试!","是");
        return;
    }
    int scores=0;
    for(int i=0;i<10;i++){
        //单选题计分
        if(i<8)
            if(m_btnGroups[i]->checkedButton()->text() == m_answerList.at(i))
                scores+=10;
        //多项选择题计分
        if(i==8){
         QString answer = m_answerList.at(i);
         bool hasA =false;
         bool hasB =false;
         bool hasC =false;
         bool hasD =false;

         if(answer.contains("A")) hasA = true;
         if(answer.contains("B")) hasB = true;
         if(answer.contains("C")) hasC = true;
         if(answer.contains("D")) hasD = true;

         bool checkA = m_checkBtns[0]->checkState();
         bool checkB = m_checkBtns[1]->checkState();
         bool checkC = m_checkBtns[2]->checkState();
         bool checkD = m_checkBtns[3]->checkState();

         if(hasA!=checkA) continue;
         if(hasB!=checkB) continue;
         if(hasC!=checkC) continue;
         if(hasD!=checkD) continue;
         scores+=10;
        }
        //判断题计分
        if(i==9){
            if(m_btnGroups[8]->checkedButton()->text() == m_answerList.at(i)){
                scores+=10;
            }
        }
    }
       QString str = "您的分数是:"+QString::number(scores) +"分,是否重新考试?";
       int res = QMessageBox::information(this,"提示",str,QMessageBox::Yes|
                                          QMessageBox::No);
       if(res == QMessageBox::Yes)
           return;
       else
           close();
}

八.窗口交互

.exec()处于一个循环等待事件的状态,接下来就等待接受用户和系统的消息并进行处理,里面就包含所谓的信号槽机制

首先在登录类lodDialog窗口中进行操作,如果点击确定后密码账号正确,
那么利用done()把该窗口关闭,并以接受方式返回状态,
之后创建一个ExamDialog类窗口(构造函数中,调用了show方法),进入考试界面
如果点击取消done会关闭当前窗口并且以用户拒绝状态返回,就结束了

1.给取消按钮创建一个槽方法
在方法中

void LoginDialog::on_cannelButton_clicked()
{
    done(Rejected);//关当前窗口并且以用户拒绝状态返回
    
}

2.在on_loginBtn_clicked()中添加一句:
在这里插入图片描述
主函数:

int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    //支持高分屏自动缩放
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    
    QApplication a(argc, argv);//应用程序类创建一个对象
     LoginDialog lodDialog;                       //登录窗对象
     int res = lodDialog.exec();                  //显示
     if(res == QDialog::Accepted) 
     {
         ExamDialog *examDialog;
         examDialog = new ExamDialog;
     }else{
         return 0;
     }
     //ExamDialog w;
     //w.show();
    return a.exec();
}

九.发布

1.先将运行的工作目录中的Debug删除
在这里插入图片描述
把构建改成Release这种适用于发布,其中的调试信息少,所以所占用的空间比较少
2.在这里插入图片描述
3.准备一个图标,一般是icon后缀
把它加入ExamSys.pro文件中
在这里插入图片描述
4.在桌面建立一个"科目一考试系统"将可执行文件和数据文件拖入其中,再从Qt文件中拖入几个链接库
在这里插入图片描述
但是这种发布模式,只能在有QT环境的情况下使用

十.全部代码

ExamSys.pro

#-------------------------------------------------
#
# Project created by QtCreator 2021-05-29T08:13:05
#
#-------------------------------------------------

QT       += core gui
RC_ICONS +=login.ico
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = ExamSys
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += \
        main.cpp \
        logindialog.cpp \
    examdialog.cpp

HEADERS += \
        logindialog.h \
    examdialog.h

FORMS += \
        logindialog.ui

RESOURCES += \
    image.qrc

examdialog.h

#ifndef EXAMDIALOG_H
#define EXAMDIALOG_H
#include<QDialog>
#include<QTimer>
#include<QTextEdit>
#include<QLabel>
#include<QRadioButton>
#include<QCheckBox>
#include<QGridLayout>
#include<QButtonGroup>
class ExamDialog : public QDialog
{
    Q_OBJECT
public:
    ExamDialog(QWidget* parent = 0);
    void initTimer(); //初始化计时器
    void initLayout(); //初始化布局管理器
    bool initTextEdit();//初始化文本编译器
    void initButtons(); //初始化按钮及标签
    bool hasNoSelect(); //判断题目是否有未完成的

private:
    QTimer *m_timer;  //计时器
    int m_timeGO;     //考试已用时

    QTextEdit *m_textEdit;  //考试题库显示
    QLabel *m_titleLabels[10];//题目标签
    QRadioButton *m_radioBtns[32];//单选题按钮
    QCheckBox *m_checkBtns[4];//多选题按钮
    QRadioButton *m_radioA;   //判断题A选项
    QRadioButton *m_radioB;  //判断题B选项
    QGridLayout *m_layout;  //布局管理器
    QStringList m_answerList; //答案
    QButtonGroup *m_btnGroups[9]; //单项按钮分组

private slots:
    void freshTime();
    void getScore();
};

#endif // EXAMDIALOG_H

logindialog.h

#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

namespace Ui {
class LoginDialog;
}

class LoginDialog : public QDialog
{
    Q_OBJECT

public:
    explicit LoginDialog(QWidget *parent = 0);
    ~LoginDialog();

private slots:
    void on_LoginButton_clicked();

    void on_cannelButton_clicked();

private:
    Ui::LoginDialog *ui;
};

#endif // LOGINDIALOG_H

examdialog.cpp

#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

namespace Ui {
class LoginDialog;
}

class LoginDialog : public QDialog
{
    Q_OBJECT

public:
    explicit LoginDialog(QWidget *parent = 0);
    ~LoginDialog();

private slots:
    void on_LoginButton_clicked();

    void on_cannelButton_clicked();

private:
    Ui::LoginDialog *ui;
};

#endif // LOGINDIALOG_H

logindialog.cpp

#include "logindialog.h"
#include "ui_logindialog.h"
#include"QMessageBox"
#include "QFile"
#include "QTextStream"

LoginDialog::LoginDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::LoginDialog)
{
    ui->setupUi(this);//初始化界面
    ui->imgLabel->setScaledContents(true);  //对图片进行填充设置
    this->resize(ui->imgLabel->width(),ui->imgLabel->height());
    setFixedSize(width(),height()); //固定窗口大小
    this->setWindowTitle("驾校科目一考试登录");         //对标题进行设置
    this->setWindowFlags(Qt::Dialog     | Qt::WindowCloseButtonHint);
}

LoginDialog::~LoginDialog()//自然回收
{
    delete ui;
}

void LoginDialog::on_LoginButton_clicked()
{
    QRegExp rx("^[A-Za-z0-9]+([_\\.][A-Za-z0-9]+)*@([A-Za-z0-9\\-]+\\.)+[A-Za-z]{2,6}$");
    /*开始的用户名一定是字母且不止一个,所以匹配多次.
     * 用户名中可能会有_或.所以匹配可以为0次.
     * 中间的@是一定存在的.
     * 域名会有字母或数字下划线.
     * 之后一定有.最后那一段不会太长,所以匹配2-6次({2,6}是为最后一个准备).
     */
    bool res = rx.exactMatch(ui->accountEdit->text());
    if(!res){
        QMessageBox::information(this,"提示","非法的邮箱地址,请你重新输入!");
        ui->accountEdit->clear();     //账号一行数据清空
        ui->codeEdit->clear();        //密码一行数据清空
        ui->accountEdit->setFocus();  //将光标重新对准账号那行
        return;
    }else{
        QString filename;   //账号密码数据文件
        QString strAccInput;//用户输入的账号
        QString strCode;    //用户输入的密码
        QString strLine;    //文件里的一行数据
        QStringList strList;    //分割读取的一行数据(字符串链表)
        filename  = "account.txt";
        strAccInput = ui->accountEdit->text();
        strCode = ui->codeEdit->text();

        QFile file(filename);  //将文件赋给到file对象中
        QTextStream stream(&file); //给file对象插入一个流
        /*
        利用file对象的打开属性,以文本只读形式打开
        不断的循环遍历文件中的每一行数据,之后末尾
        将一行数据分别两段,分别对比账号和密码
        为什么 if(strAccInput == strList.at(0))没有else
        因为账号要 遍历文件中全部数据才能知道有没有,所以是循环结束还没有找到,才断定为账号不成立
        而密码是与账号一对一匹配的,所以如果账号正确了,密码就是账号数据这一行,不需要遍历全部,所以有else
         */
        if(file.open(QIODevice::ReadOnly | QIODevice::Text)){
            while(!stream.atEnd()){
                strLine = stream.readLine();
                strList = strLine.split(","); //将字符串分别成一个字符数组,用,分割
                if(strAccInput == strList.at(0)){
                    if(strCode == strList.at(1)){
                      QMessageBox::information(this,"提示","欢迎登录科目一考试系统!");
                      file.close();
                      done(Accepted);//关闭当前窗体,并且以指定的方式(状态)返回
                      return;
                    }else{
                      QMessageBox::information(this,"提示","密码输入错误,请重新输入");
                      ui->codeEdit->clear();
                      ui->codeEdit->setFocus();
                      file.close();
                      return;
                    }
                }
            }
            QMessageBox::information(this,"提示","您输入的账号有误,请重新输入!");
            ui->accountEdit->clear();
            ui->codeEdit->clear();
            ui->accountEdit->setFocus();
            file.close();
            return;
        }else{
            QMessageBox::information(this,"提示","文件读取失败");
            return;
        }

    }

}

void LoginDialog::on_cannelButton_clicked()
{
    done(Rejected);//关当前窗口并且以用户拒绝状态返回
}

mian.cpp

#include "logindialog.h"
#include <QApplication>
#include <examdialog.h>
int main(int argc, char *argv[])
{
#if(QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    //支持高分屏自动缩放
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif

    QApplication a(argc, argv);//应用程序类创建一个对象
     LoginDialog lodDialog;             //登录窗对象
     int res = lodDialog.exec();                  //显示
     if(res == QDialog::Accepted)
     {
         ExamDialog *examDialog;
         examDialog = new ExamDialog;
     }else{
         return 0;
     }
     //ExamDialog w;
     //w.show();
    return a.exec();
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT--科目一考试系统 的相关文章

随机推荐