瀏覽代碼

添加: 登录也以及必要的实现

zhuizhu 10 月之前
父節點
當前提交
898087c780
共有 10 個文件被更改,包括 826 次插入4 次删除
  1. 314 0
      api/tapi.cpp
  2. 57 0
      api/tapi.h
  3. 142 0
      api/tloginapi.cpp
  4. 73 0
      api/tloginapi.h
  5. 34 1
      appevent.cpp
  6. 10 0
      appevent.h
  7. 19 3
      main.cpp
  8. 6 0
      teacherServer.pro
  9. 137 0
      tlogin.cpp
  10. 34 0
      tlogin.h

+ 314 - 0
api/tapi.cpp

@@ -0,0 +1,314 @@
+#include "tapi.h"
+#include "appevent.h"
+#include "qcoreapplication.h"
+#include "qglobal.h"
+#include "qjsonobject.h"
+#include "qobject.h"
+
+#include <functional>
+#include <optional>
+
+#include <QApplication>
+#include <QFile>
+#include <QFileInfo>
+#include <QHttpMultiPart>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QNetworkReply>
+#include <QUrlQuery>
+
+#include <QMessageBox>
+
+#include <QCoreApplication>
+
+struct Tr
+{
+    Q_DECLARE_TR_FUNCTIONS(TeacherServer)
+};
+
+#include "api/tloginapi.h"
+namespace TC {
+// 创建一个映射来存储错误码和对应的信息
+static QMap<RequestCode, QString> errorMessages
+    = {{RequestCode::OK, Tr::tr("OK")},
+       {RequestCode::GeneralServerError, Tr::tr("General Server Error")},
+       {RequestCode::InvalidRequest, Tr::tr("Invalid Request")},
+       {RequestCode::TokenExpired, Tr::tr("Token Expired")},
+       {RequestCode::DatabaseError, Tr::tr("Database Error")},
+       {RequestCode::UsernameError, Tr::tr("Username Error")},
+       {RequestCode::InvalidPhoneFormat, Tr::tr("Invalid Phone Format")},
+       {RequestCode::PasswordRequirementsNotMet, Tr::tr("Password Requirements Not Met")},
+       {RequestCode::PhoneAlreadyRegistered, Tr::tr("Phone Already Registered")},
+       {RequestCode::IncorrectCredentials, Tr::tr("Incorrect Credentials")},
+       {RequestCode::GetPhoneCodeError, Tr::tr("Get Phone Code Error")},
+       {RequestCode::PhoneCodeExists, Tr::tr("Phone Code Exists")},
+       {RequestCode::PhoneCodeNotFound, Tr::tr("Phone Code Not Found")},
+       {RequestCode::CorrectVerificationCode, Tr::tr("Correct Verification Code")},
+       {RequestCode::UpdateLoginPasswordFailed, Tr::tr("Update Login Password Failed")},
+       {RequestCode::RealNameVerificationFailed, Tr::tr("Real Name Verification Failed")},
+       {RequestCode::VIPExpirationTimeFailed, Tr::tr("VIP Expiration Time Failed")}};
+
+static QString base_url("http://127.0.0.1:8080");
+
+static const QLatin1String scCode("code");
+static const QLatin1String scMessage("message");
+static const QLatin1String scData("data");
+
+// 响应拦截
+static bool InterceptorsResponse(QNetworkReply *reply, QJsonValue &data)
+{
+    if (reply->error() == QNetworkReply::NoError) {
+        const QByteArray allData = reply->readAll();
+
+        QJsonParseError jsonError;
+        QJsonDocument jsonDoc = QJsonDocument::fromJson(allData, &jsonError);
+        if (jsonError.error != QJsonParseError::NoError) {
+            qDebug() << data;
+            return false;
+        }
+#ifdef QT_DEBUG
+        qDebug() << QString::fromUtf8(jsonDoc.toJson());
+#endif
+        if (jsonDoc.isObject()) {
+            const QJsonObject &object = jsonDoc.object();
+            if (object.contains(scCode)) {
+                int code = object.value(scCode).toInt();
+                if (code != 0) {
+                    // RequestCode requestCode = static_cast<RequestCode>(code);
+                    const QWidgetList widgetList = QApplication::topLevelWidgets();
+                    QWidget *parentWidget = nullptr;
+                    if (!widgetList.isEmpty()) {
+                        parentWidget = widgetList.first();
+                    }
+                    // 获取当前活动窗口
+                    QMessageBox::critical(parentWidget,
+                                          Tr::tr("Error"),
+                                          object[scMessage].toString());
+                    // if (errorMessages.contains(requestCode)) {
+                    //     //Core::MessageManager::writeFlashing(errorMessages[requestCode]);
+                    // } else {
+                    //     //Core::MessageManager::writeFlashing(Tr::tr("unknown error"));
+                    // }
+                    return false;
+                }
+            }
+            if (object.contains(scMessage)) {
+                const QString &message = object.value(scMessage).toString();
+                // Core::MessageManager::writeFlashing(message);
+            }
+            if (object.contains(scData)) {
+                data = object.value(scData);
+            }
+        }
+        return true;
+    } else if (reply->error() == QNetworkReply::InternalServerError) { //权限错误
+        qDebug() << "0" << reply->error();
+        AppEvent::instance()->setJwtToken(QString());
+        QWidget *parentWidget = QApplication::topLevelWidgets().first();
+        QMessageBox::critical(parentWidget, Tr::tr("Server Error"), reply->errorString());
+        return false;
+    } else { //其他错误
+        AppEvent::instance()->setJwtToken(QString());
+        qDebug() << "1" << reply->error();
+        QWidget *parentWidget = QApplication::topLevelWidgets().first();
+        // 获取当前活动窗口
+        QMessageBox::critical(parentWidget, Tr::tr("Server Error"), reply->errorString());
+        // Core::MessageManager::writeFlashing(reply->errorString());
+        return false;
+    }
+    return false;
+}
+
+}; // namespace TC
+
+namespace TC {
+
+static NetworkAccessManager *namInstance = nullptr;
+
+void cleanupNetworkAccessManager()
+{
+    delete namInstance;
+    namInstance = nullptr;
+}
+
+NetworkAccessManager *NetworkAccessManager::instance()
+{
+    if (!namInstance) {
+        namInstance = new NetworkAccessManager;
+        qAddPostRoutine(cleanupNetworkAccessManager);
+    }
+    return namInstance;
+}
+
+NetworkAccessManager::NetworkAccessManager(QObject *parent)
+    : QNetworkAccessManager(parent)
+{
+    const QString ip = AppEvent::instance()->configValue("serverIP").toString();
+    const QString port = AppEvent::instance()->configValue("serverPort").toString();
+    base_url = QString("http://%1:%2").arg(ip).arg(port);
+}
+
+QNetworkReply *NetworkAccessManager::createRequest(Operation op,
+                                                   const QNetworkRequest &request,
+                                                   QIODevice *outgoingData)
+{
+    QString agentStr = QString::fromLatin1("%1/%2 (QNetworkAccessManager %3; %4; %5; %6 bit)")
+                           .arg(QCoreApplication::applicationName(),
+                                QCoreApplication::applicationVersion(),
+                                QLatin1String(qVersion()),
+                                QSysInfo::prettyProductName(),
+                                QLocale::system().name())
+                           .arg(QSysInfo::WordSize);
+    QNetworkRequest req(request);
+    req.setRawHeader("User-Agent", agentStr.toLatin1());
+
+    // QHttpMultiPartIODevice *multiPart = dynamic_cast<QHttpMultiPartIODevice *>(outgoingData);
+
+    // if (multiPart) {
+    //     // req.setHeader(QNetworkRequest::ContentTypeHeader, "multipart");
+    // } else
+    {
+        req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+    }
+
+    const QUrl &url = request.url();
+    const QString urlPath = url.path();
+    qDebug() << url;
+    //设置白名单
+    if (urlPath.indexOf(RefreshToken().url) >= 0 || urlPath.indexOf(Login().url) >= 0) {
+    } else {
+        //需要刷新token
+        // qDebug() << "RefreshToken" << urlPath;
+        if (AppEvent::instance()->isRefreshToken()) {
+            RefreshToken().get();
+        }
+    }
+
+    //这里考虑处理 JWT 数据
+    const QString &token = AppEvent::instance()->jwtToken();
+    // 加个事件校验 防止token 失效导致异常
+    if (!token.isEmpty()) {
+        req.setRawHeader("Authorization", "Bearer " + token.toUtf8());
+    }
+
+    return QNetworkAccessManager::createRequest(op, req, outgoingData);
+}
+
+//
+
+std::optional<QJsonValue> sendRequest(QNetworkAccessManager::Operation op,
+                                      const QString &url,
+                                      const QByteArray &postData = QByteArray())
+{
+    QNetworkAccessManager *manager = NetworkAccessManager::instance();
+    manager->setTransferTimeout(3000);
+    QNetworkReply *reply = nullptr;
+
+    QUrl fullUrl(QString(base_url).append(url));
+    switch (op) {
+    case QNetworkAccessManager::HeadOperation:
+        reply = manager->head(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::GetOperation:
+        reply = manager->get(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::PutOperation:
+        reply = manager->put(QNetworkRequest(fullUrl), postData);
+        break;
+    case QNetworkAccessManager::PostOperation:
+        reply = manager->post(QNetworkRequest(fullUrl), postData);
+        break;
+    case QNetworkAccessManager::DeleteOperation:
+        reply = manager->deleteResource(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::CustomOperation:
+        break;
+    default:
+        break;
+    }
+    if (!reply) {
+        qDebug() << "sendRequest Operation error";
+    }
+
+    //等待请求结束
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    QJsonValue data;
+    if (!InterceptorsResponse(reply, data)) {
+        return std::nullopt;
+    }
+    return data;
+}
+bool downRequest(const QString &url,
+                 const QByteArray &fileName,
+                 std::function<bool(QNetworkReply *)> callback)
+{
+    QNetworkAccessManager *manager = NetworkAccessManager::instance();
+    manager->setTransferTimeout(30000);
+
+    QUrl fullUrl(QString(base_url).append(url));
+    QUrlQuery query;
+    query.addQueryItem("file", fileName);
+    fullUrl.setQuery(query);
+
+    QNetworkReply *reply = manager->get(QNetworkRequest(fullUrl));
+
+    if (!reply) {
+        qDebug() << "sendRequest Operation error";
+    }
+
+    //等待请求结束
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    return callback(reply);
+}
+
+std::optional<QJsonValue> uploaderRequest(const QString &url, const QStringList &fileList)
+{
+    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+    // Create a file part
+
+    for (const QString &filePath : fileList) {
+        QFile *file = new QFile(filePath);
+        if (!file->open(QIODevice::ReadOnly)) {
+            qWarning() << "Cannot open file:" << file->errorString();
+            return false;
+        }
+        QHttpPart filePart;
+        filePart.setBodyDevice(file);
+        filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
+        filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
+                           QString("form-data; name=\"file\"; filename=\"%1\"")
+                               .arg(QFileInfo(filePath).fileName()));
+
+        file->setParent(multiPart); // Ensure file is deleted with multiPart
+        multiPart->append(filePart);
+    }
+
+    QNetworkAccessManager *manager = NetworkAccessManager::instance();
+
+    QUrl fullUrl(QString(base_url).append(url));
+
+    QNetworkReply *reply = manager->post(QNetworkRequest(fullUrl), multiPart);
+
+    if (!reply) {
+        qDebug() << "sendRequest Operation error";
+    }
+
+    //等待请求结束
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    QJsonValue data;
+    if (!InterceptorsResponse(reply, data)) {
+        return std::nullopt;
+    }
+    return data;
+}
+} // namespace TC

+ 57 - 0
api/tapi.h

@@ -0,0 +1,57 @@
+#ifndef TAPI_H
+#define TAPI_H
+
+#include <QNetworkAccessManager>
+#include <QObject>
+
+namespace TC {
+
+class NetworkAccessManager : public QNetworkAccessManager
+{
+    Q_OBJECT
+public:
+    NetworkAccessManager(QObject *parent = nullptr);
+
+    static NetworkAccessManager *instance();
+
+protected:
+    QNetworkReply *createRequest(Operation op,
+                                 const QNetworkRequest &request,
+                                 QIODevice *outgoingData) override;
+};
+
+// 正常请求数据信息
+// {
+//     "code": 200,
+//     "msg": "OK",
+//     "data": {
+//         "access_token": "",
+//         "access_expire": 1710850692,
+//         "refresh_after": 1710849792
+//     }
+// }
+
+enum RequestCode : int {
+    OK = 200,
+    GeneralServerError = 100001,
+    InvalidRequest = 100002,
+    TokenExpired = 100003,
+    DatabaseError = 100005,
+    // 用户错误代码
+    UsernameError = 101001,
+    InvalidPhoneFormat = 101002,
+    PasswordRequirementsNotMet = 101003,
+    PhoneAlreadyRegistered = 101004,
+    IncorrectCredentials = 101005,
+    GetPhoneCodeError = 101010,
+    PhoneCodeExists = 101011,
+    PhoneCodeNotFound = 101012,
+    CorrectVerificationCode = 101013,
+    UpdateLoginPasswordFailed = 101014,
+    RealNameVerificationFailed = 101020,
+    VIPExpirationTimeFailed = 101030
+};
+
+} // namespace TC
+
+#endif // TAPI_H

+ 142 - 0
api/tloginapi.cpp

@@ -0,0 +1,142 @@
+#include "tloginapi.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QNetworkAccessManager>
+
+#include <optional>
+
+#include "appevent.h"
+
+namespace TC {
+
+std::optional<QJsonValue> sendRequest(QNetworkAccessManager::Operation op,
+                                      const QString &url,
+                                      const QByteArray &postData = QByteArray());
+
+static const QLatin1String scJwtToken("JwtToken");
+static const QLatin1String scAccessToken("access_token");
+static const QLatin1String scAccessExpire("access_expire");
+static const QLatin1String scRefreshAfter("refresh_after");
+
+Login::Login()
+    : QObject()
+    , url("/api/v1/user/login")
+{}
+
+Login::Login(const QString &_user, const QString &_password)
+    : QObject()
+    , url("/api/v1/user/login")
+{
+    QJsonObject json;
+    json["name"] = _user;
+    json["swID"] = _password;
+    json["examineeNumber"] = _password;
+
+    QJsonDocument jsonData(json);
+    postData = jsonData.toJson();
+}
+
+bool Login::post()
+{
+    std::optional<QJsonValue> data = sendRequest(QNetworkAccessManager::PostOperation,
+                                                 url,
+                                                 postData);
+    if (!data.has_value()) {
+        return false;
+    }
+    const QJsonObject &object = data.value().toObject();
+
+    Login::Data ret;
+
+    if (object[scJwtToken].isObject()) {
+        const QJsonObject &jwtObject = object[scJwtToken].toObject();
+        ret.accessToken = jwtObject[scAccessToken].toString();
+        ret.accessExpire = jwtObject[scAccessExpire].toVariant().toLongLong();
+        ret.refreshAfter = jwtObject[scRefreshAfter].toVariant().toLongLong();
+    }
+    qDebug() << object;
+
+    //设置 token 到全局变量
+    if (ret.accessExpire > AppEvent::time()) {
+        AppEvent::instance()->setJwtToken(ret.accessToken);
+        AppEvent::instance()->setRefreshTime(ret.refreshAfter);
+        return true;
+    }
+    return false;
+}
+
+LoginOut::LoginOut()
+    : QObject()
+    , url("/api/v1/user/loginOut")
+{}
+
+bool LoginOut::post()
+{
+    std::optional<QJsonValue> data = sendRequest(QNetworkAccessManager::PostOperation, url, {});
+    if (!data.has_value()) {
+        return false;
+    }
+    const QJsonObject &object = data.value().toObject();
+
+    AppEvent::instance()->setJwtToken(QString());
+    return true;
+}
+
+RefreshToken::RefreshToken()
+    : QObject()
+    , url("/api/v1/user/refresh_token")
+{}
+
+bool RefreshToken::get()
+{
+    std::optional<QJsonValue> data = sendRequest(QNetworkAccessManager::GetOperation, url);
+    if (!data.has_value()) {
+        return false;
+    }
+    RefreshToken::Data ret;
+    if (!data.has_value()) {
+        return false;
+    }
+    const QJsonObject &object = data.value().toObject();
+
+    ret.accessToken = object[scAccessToken].toString();
+    ret.accessExpire = object[scAccessExpire].toVariant().toLongLong();
+    ret.refreshAfter = object[scRefreshAfter].toVariant().toLongLong();
+
+    //设置 token 到全局变量
+    if (ret.accessExpire > AppEvent::time()) {
+        AppEvent::instance()->setJwtToken(ret.accessToken);
+        AppEvent::instance()->setRefreshTime(ret.refreshAfter);
+        return true;
+    }
+    return false;
+}
+
+UserInfo::UserInfo()
+    : QObject()
+    , url("/api/v1/user/userInfo")
+{}
+
+UserInfo::Data UserInfo::get(bool *b)
+{
+    if (b) {
+        *b = false;
+    }
+    UserInfo::Data ret;
+    std::optional<QJsonValue> data = sendRequest(QNetworkAccessManager::GetOperation, url);
+    if (!data.has_value()) {
+        return ret;
+    }
+    const QJsonObject &object = data.value().toObject();
+
+    ret.username = object["username"].toString();
+    ret.maxTime = object["maxTime"].toVariant().toLongLong();
+    ret.checkinNumber = object["checkinNumber"].toString();
+    if (b) {
+        *b = true;
+    }
+    return ret;
+}
+} // namespace TC

+ 73 - 0
api/tloginapi.h

@@ -0,0 +1,73 @@
+#ifndef TLOGINAPI_H
+#define TLOGINAPI_H
+
+#include <QObject>
+
+namespace TC {
+
+class Login : public QObject
+{
+    Q_OBJECT
+public:
+    struct Data
+    {
+        QString accessToken;
+        qint64 accessExpire;
+        qint64 refreshAfter;
+    };
+    Login();
+    Login(const QString &user, const QString &password);
+
+    bool post();
+
+    QString url;
+    QByteArray postData;
+};
+
+class UserInfo : public QObject
+{
+    Q_OBJECT
+public:
+    struct Data
+    {
+        QString username;
+        qint64 maxTime;
+        QString checkinNumber;
+    };
+    UserInfo();
+
+    Data get(bool *b = nullptr);
+
+private:
+    QString url;
+};
+
+class LoginOut : public QObject
+{
+    Q_OBJECT
+public:
+    LoginOut();
+
+    bool post();
+
+    QString url;
+};
+
+class RefreshToken : public QObject
+{
+    Q_OBJECT
+public:
+    struct Data
+    {
+        QString accessToken;
+        qint64 accessExpire;
+        qint64 refreshAfter;
+    };
+    RefreshToken();
+
+    bool get();
+
+    QString url;
+};
+} // namespace TC
+#endif // TLOGINAPI_H

+ 34 - 1
appevent.cpp

@@ -10,11 +10,17 @@ static AppEvent *g_instance = nullptr;
 class AppEventPrivate
 class AppEventPrivate
 {
 {
 public:
 public:
-    AppEventPrivate() {}
+    AppEventPrivate()
+        : token(QString())
+        , accessExpire(0)
+    {}
     QJsonObject jsonConfig;
     QJsonObject jsonConfig;
 
 
     QString examRoom;
     QString examRoom;
     QString examNumber;
     QString examNumber;
+
+    QString token;
+    qint64 accessExpire;
 };
 };
 
 
 AppEvent::AppEvent(QObject *parent)
 AppEvent::AppEvent(QObject *parent)
@@ -49,6 +55,33 @@ qint64 AppEvent::time()
     return timestamp;
     return timestamp;
 }
 }
 
 
+bool AppEvent::isLogin()
+{
+    return !d->token.isEmpty();
+}
+
+void AppEvent::setJwtToken(const QString &token)
+{
+    d->token = token;
+}
+
+QString AppEvent::jwtToken() const
+{
+    return d->token;
+}
+bool AppEvent::isRefreshToken() const
+{
+    if (time() > d->accessExpire && !d->token.isEmpty()) {
+        return true;
+    }
+    return false;
+}
+
+void AppEvent::setRefreshTime(qint64 time) const
+{
+    d->accessExpire = time;
+}
+
 void AppEvent::setExam(const QString &examRoom, const QString &examNumber)
 void AppEvent::setExam(const QString &examRoom, const QString &examNumber)
 {
 {
     d->examNumber = examNumber;
     d->examNumber = examNumber;

+ 10 - 0
appevent.h

@@ -30,6 +30,16 @@ public:
     static AppEvent *instance(); // 静态方法返回单例实例
     static AppEvent *instance(); // 静态方法返回单例实例
 
 
     static qint64 time();
     static qint64 time();
+
+    // 教室判断是否登录
+    bool isLogin();
+
+    void setJwtToken(const QString &token);
+    QString jwtToken() const;
+
+    bool isRefreshToken() const;
+    void setRefreshTime(qint64 time) const;
+
     // 考试配置
     // 考试配置
     void setExam(const QString &examRoom, const QString &examNumber);
     void setExam(const QString &examRoom, const QString &examNumber);
     QString examRoom() const;
     QString examRoom() const;

+ 19 - 3
main.cpp

@@ -14,6 +14,7 @@
 #include "appevent.h"
 #include "appevent.h"
 #include "qglobal.h"
 #include "qglobal.h"
 #include "tcontroller.h"
 #include "tcontroller.h"
+#include "tlogin.h"
 #include "tmodel.h"
 #include "tmodel.h"
 #include "websocketserver.h"
 #include "websocketserver.h"
 
 
@@ -124,10 +125,24 @@ int main(int argc, char *argv[])
 
 
     server.addController<TableDataController>("/api/v1/tableData");
     server.addController<TableDataController>("/api/v1/tableData");
     server.addController<TableDataExamRoomController>("/api/v1/tableDataRoom");
     server.addController<TableDataExamRoomController>("/api/v1/tableDataRoom");
-    MainWindow w;
-    w.show();
 
 
-    a.setActivationWindow(&w);
+    TLogin login;
+    std::unique_ptr<MainWindow> mainWindow = nullptr;
+
+    QObject::connect(&login, &TLogin::loginSuccessful, [&]() {
+        mainWindow = std::make_unique<MainWindow>();
+        mainWindow->show();
+    });
+
+    login.exec();
+    if (!mainWindow) {
+        return 0;
+    }
+
+    // MainWindow w;
+    // w.show();
+
+    a.setActivationWindow(mainWindow.get());
 
 
     const QString ip = AppEvent::instance()->configValue("serverIP").toString();
     const QString ip = AppEvent::instance()->configValue("serverIP").toString();
     const int port = AppEvent::instance()->configValue("serverPort").toInt();
     const int port = AppEvent::instance()->configValue("serverPort").toInt();
@@ -137,5 +152,6 @@ int main(int argc, char *argv[])
     settings.setValue("host", ip);
     settings.setValue("host", ip);
     settings.setValue("port", port);
     settings.setValue("port", port);
     server.start(QHostAddress(ip), port);
     server.start(QHostAddress(ip), port);
+    // return a.exec();
     return a.exec();
     return a.exec();
 }
 }

+ 6 - 0
teacherServer.pro

@@ -9,6 +9,8 @@ CONFIG += c++17
 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 
 
 SOURCES += \
 SOURCES += \
+    api/tapi.cpp \
+    api/tloginapi.cpp \
     appevent.cpp \
     appevent.cpp \
     examquestionpage.cpp \
     examquestionpage.cpp \
     examtestpage.cpp \
     examtestpage.cpp \
@@ -18,10 +20,13 @@ SOURCES += \
     settingspage.cpp \
     settingspage.cpp \
     studentpage.cpp \
     studentpage.cpp \
     tcontroller.cpp \
     tcontroller.cpp \
+    tlogin.cpp \
     tmodel.cpp \
     tmodel.cpp \
     websocketserver.cpp
     websocketserver.cpp
 
 
 HEADERS += \
 HEADERS += \
+    api/tapi.h \
+    api/tloginapi.h \
     appevent.h \
     appevent.h \
     examquestionpage.h \
     examquestionpage.h \
     examtestpage.h \
     examtestpage.h \
@@ -31,6 +36,7 @@ HEADERS += \
     studentpage.h \
     studentpage.h \
     tcontroller.h \
     tcontroller.h \
     teachertr.h \
     teachertr.h \
+    tlogin.h \
     tmodel.h \
     tmodel.h \
     websocketserver.h
     websocketserver.h
 
 

+ 137 - 0
tlogin.cpp

@@ -0,0 +1,137 @@
+#include "tlogin.h"
+
+#include "appevent.h"
+#include "qlabel.h"
+#include "qlineedit.h"
+
+#include <QGroupBox>
+#include <QMessageBox>
+#include <QMouseEvent>
+#include <QRegExpValidator>
+
+#include "qpushbutton.h"
+#include "qstyle.h"
+#include "qwidget.h"
+#include "utils/layoutbuilder.h"
+
+#include "api/tloginapi.h"
+
+TLogin::TLogin(QWidget *parent)
+    : QDialog(parent)
+{
+    setObjectName("Login");
+    setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
+    setWindowTitle(tr("Login"));
+
+    using namespace Layouting;
+
+    info = new QLabel;
+    info->setStyleSheet("color: white;");
+    info->setText(tr("link failure"));
+    // 创建登录组件
+    usernameEdit = new QLineEdit();
+    usernameEdit->setPlaceholderText(tr("Enter your user name"));
+    passwordEdit = new QLineEdit();
+    // passwordEdit->setEchoMode(QLineEdit::Password);
+    passwordEdit->setPlaceholderText(tr("Enter your ExamNo"));
+    // QRegExp regExp("[a-zA-Z0-9]{6,12}");
+    // QValidator *validator = new QRegExpValidator(regExp, passwordEdit);
+    // passwordEdit->setValidator(validator);
+
+    QWidget *w = Group{Column{Row{st, tr("Login"), st},
+                              Form{tr("Username:"), usernameEdit, br, tr("ExamNo:"), passwordEdit},
+                              PushButton{bindTo(&loginPushButton),
+                                         text(tr("Login")),
+                                         onClicked(std::bind(&TLogin::on_login_clicked, this))}}}
+                     .emerge();
+
+    QPushButton *close;
+    auto h = Row{"", st, PushButton{bindTo(&close), "test"}};
+
+    Column{h, st, Row{st, w, st}, st, info, noMargin}.attachTo(this);
+
+    QString styleSheet = R"(
+    QDialog#Login {
+        background-image: url(:/bg/bg1.png);
+    }
+    QGroupBox {
+        background-color: rgba(255, 255, 255, 128);
+    }
+)";
+    setStyleSheet(styleSheet);
+
+    QPixmap closePix = style()->standardPixmap(QStyle::SP_TitleBarCloseButton);
+
+    close->setIcon(closePix);
+    close->setStyleSheet(R"__(
+    QPushButton {
+        border: none;
+        background-color: #ffffff; /* 红色背景 */
+        color: white; /* 文字颜色 */
+        padding: 10px 10px; /* 内边距 */
+        text-align: center; /* 文字居中 */
+        text-decoration: none; /* 去掉下划线 */
+        font-size: 12px; /* 字体大小 */
+        margin: 2px 2px; /* 外边距 */
+        border-radius: 10px; /* 圆角 */
+    }
+    QPushButton:hover {
+        background-color: #d32f2f; /* 鼠标悬停时背景色 */
+    }
+    )__");
+
+    resize({400, 400});
+    // usernameEdit->setText("test");
+    // passwordEdit->setText("SW1111112");
+    loginPushButton->setDisabled(true);
+    QObject::connect(close, &QPushButton::clicked, this, &TLogin::close);
+    // QObject::connect(AppEvent::instance(), &AppEvent::serverLinkSignals, this, [this](bool isok) {
+    //     if (isok) {
+    //         info->setText(tr("link succeeded"));
+    //         loginPushButton->setDisabled(false);
+    //     } else {
+    //         info->setText(tr("link failure"));
+    //         loginPushButton->setDisabled(true);
+    //     }
+    // });
+}
+
+void TLogin::mousePressEvent(QMouseEvent *event)
+{
+    if (event->button() == Qt::LeftButton) {
+        m_dragPosition = event->globalPos() - frameGeometry().topLeft();
+        event->accept();
+    }
+}
+
+void TLogin::mouseMoveEvent(QMouseEvent *event)
+{
+    if (event->buttons() & Qt::LeftButton) {
+        move(event->globalPos() - m_dragPosition);
+        event->accept();
+    }
+}
+void TLogin::on_login_clicked()
+{
+    const QString userName = usernameEdit->text();
+    const QString password = passwordEdit->text();
+
+    if (userName.isEmpty() || password.isEmpty()) {
+        QMessageBox::warning(this,
+                             tr("Login failed"),
+                             tr("User name or password error, please try again."));
+        return;
+    }
+    bool ok = TC::Login(userName, password).post();
+    if (ok && AppEvent::instance()->isLogin()) {
+        // 登录成功,发出信号
+        emit loginSuccessful();
+        close();
+    } else {
+        // 登录失败,显示提示框
+        QMessageBox::warning(this,
+                             tr("Login failed"),
+                             tr("User name or password error, please try again."));
+        return;
+    }
+}

+ 34 - 0
tlogin.h

@@ -0,0 +1,34 @@
+#ifndef TLOGIN_H
+#define TLOGIN_H
+
+#include <QDialog>
+#include "qpushbutton.h"
+
+class QLineEdit;
+class QLabel;
+
+class TLogin : public QDialog
+{
+    Q_OBJECT
+public:
+    explicit TLogin(QWidget *parent = nullptr);
+
+    void on_login_clicked();
+
+protected:
+    void mousePressEvent(QMouseEvent *event);
+    void mouseMoveEvent(QMouseEvent *event);
+signals:
+    void loginSuccessful();
+
+private:
+    QPoint m_dragPosition;
+
+    QPushButton *loginPushButton;
+    QLineEdit *usernameEdit;
+    QLineEdit *passwordEdit;
+
+    QLabel *info;
+};
+
+#endif // TLOGIN_H