diff --git a/example/example.qrc b/example/example.qrc index 32ef4459..093cdf11 100644 --- a/example/example.qrc +++ b/example/example.qrc @@ -189,5 +189,6 @@ qml/page/T_Clip.qml qml/page/T_3D.qml qml/global/Lang.qml + qml/page/T_Network.qml diff --git a/example/qml-Qt6/global/ItemsOriginal.qml b/example/qml-Qt6/global/ItemsOriginal.qml index 99af7cd2..5462e7ff 100644 --- a/example/qml-Qt6/global/ItemsOriginal.qml +++ b/example/qml-Qt6/global/ItemsOriginal.qml @@ -407,11 +407,17 @@ FluObject{ onTap:{ navigationView.push(url) } } FluPaneItem{ - title:"Http" + title:"Http(Deprecated)" menuDelegate: paneItemMenu url:"qrc:/example/qml/page/T_Http.qml" onTap:{ navigationView.push(url) } } + FluPaneItem{ + title:"Network" + menuDelegate: paneItemMenu + url:"qrc:/example/qml/page/T_Network.qml" + onTap:{ navigationView.push(url) } + } FluPaneItem{ id:item_other title:"RemoteLoader" diff --git a/example/qml-Qt6/page/T_Network.qml b/example/qml-Qt6/page/T_Network.qml new file mode 100644 index 00000000..ef08e62b --- /dev/null +++ b/example/qml-Qt6/page/T_Network.qml @@ -0,0 +1,149 @@ +import QtQuick +import Qt.labs.platform +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Dialogs +import FluentUI +import "qrc:///example/qml/component" + +FluContentPage{ + + title:"Network" + + FluNetworkCallable{ + id:callable + onStart: { + showLoading() + } + onFinish: { + hideLoading() + } + onError: + (status,errorString,result)=>{ + console.debug(status+";"+errorString+";"+result) + } + onSuccess: + (result)=>{ + text_info.text = result + } + } + + Flickable{ + id:layout_flick + width: 200 + clip: true + anchors{ + top: parent.top + topMargin: 20 + bottom: parent.bottom + left: parent.left + } + ScrollBar.vertical: FluScrollBar {} + contentHeight:layout_column.height + Column{ + spacing: 2 + id:layout_column + width: parent.width + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Get" + onClicked: { + FluNetwork.get("https://httpbingo.org/get") + .setTimeOut(10000)//默认15000毫秒 + .setRetry(2)//默认3次 + .addQuery("name","孙悟空") + .addQuery("age",500) + .addQuery("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Head" + onClicked: { + FluNetwork.head("https://httpbingo.org/head") + .addQuery("name","孙悟空") + .addQuery("age",500) + .addQuery("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post Body" + onClicked: { + FluNetwork.postBody("https://httpbingo.org/post") + .setBody("花果山水帘洞美猴王齐天大圣孙悟空") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post JSON" + onClicked: { + FluNetwork.postJson("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post JSON Array" + onClicked: { + FluNetwork.postJsonArray("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post Form" + onClicked: { + FluNetwork.postForm("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + + } + } + + FluArea{ + anchors{ + top: layout_flick.top + bottom: layout_flick.bottom + left: layout_flick.right + right: parent.right + leftMargin: 8 + } + Flickable{ + clip: true + id:scrollview + boundsBehavior:Flickable.StopAtBounds + width: parent.width + height: parent.height + contentWidth: width + contentHeight: text_info.height + ScrollBar.vertical: FluScrollBar {} + FluText{ + id:text_info + width: scrollview.width + wrapMode: Text.WrapAnywhere + padding: 14 + } + } + } +} diff --git a/example/qml/global/ItemsOriginal.qml b/example/qml/global/ItemsOriginal.qml index b1e3b0dc..6012fb73 100644 --- a/example/qml/global/ItemsOriginal.qml +++ b/example/qml/global/ItemsOriginal.qml @@ -407,11 +407,17 @@ FluObject{ onTap:{ navigationView.push(url) } } FluPaneItem{ - title:"Http" + title:"Http(Deprecated)" menuDelegate: paneItemMenu url:"qrc:/example/qml/page/T_Http.qml" onTap:{ navigationView.push(url) } } + FluPaneItem{ + title:"Network" + menuDelegate: paneItemMenu + url:"qrc:/example/qml/page/T_Network.qml" + onTap:{ navigationView.push(url) } + } FluPaneItem{ id:item_other title:"RemoteLoader" diff --git a/example/qml/page/T_Network.qml b/example/qml/page/T_Network.qml new file mode 100644 index 00000000..22d369a7 --- /dev/null +++ b/example/qml/page/T_Network.qml @@ -0,0 +1,148 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 +import "qrc:///example/qml/component" +import "../component" + +FluContentPage{ + + title:"Network" + + FluNetworkCallable{ + id:callable + onStart: { + showLoading() + } + onFinish: { + hideLoading() + } + onError: + (status,errorString,result)=>{ + console.debug(status+";"+errorString+";"+result) + } + onSuccess: + (result)=>{ + text_info.text = result + } + } + + Flickable{ + id:layout_flick + width: 200 + clip: true + anchors{ + top: parent.top + topMargin: 20 + bottom: parent.bottom + left: parent.left + } + ScrollBar.vertical: FluScrollBar {} + contentHeight:layout_column.height + Column{ + spacing: 2 + id:layout_column + width: parent.width + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Get" + onClicked: { + FluNetwork.get("https://httpbingo.org/get") + .setTimeOut(10000)//默认15000毫秒 + .setRetry(2)//默认3次 + .addQuery("name","孙悟空") + .addQuery("age",500) + .addQuery("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Head" + onClicked: { + FluNetwork.head("https://httpbingo.org/head") + .addQuery("name","孙悟空") + .addQuery("age",500) + .addQuery("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post Body" + onClicked: { + FluNetwork.postBody("https://httpbingo.org/post") + .setBody("花果山水帘洞美猴王齐天大圣孙悟空") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post JSON" + onClicked: { + FluNetwork.postJson("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post JSON Array" + onClicked: { + FluNetwork.postJsonArray("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post Form" + onClicked: { + FluNetwork.postForm("https://httpbingo.org/post") + .add("name","孙悟空") + .add("age",500) + .add("address","花果山水帘洞") + .go(callable) + } + } + + } + } + + FluArea{ + anchors{ + top: layout_flick.top + bottom: layout_flick.bottom + left: layout_flick.right + right: parent.right + leftMargin: 8 + } + Flickable{ + clip: true + id:scrollview + boundsBehavior:Flickable.StopAtBounds + width: parent.width + height: parent.height + contentWidth: width + contentHeight: text_info.height + ScrollBar.vertical: FluScrollBar {} + FluText{ + id:text_info + width: scrollview.width + wrapMode: Text.WrapAnywhere + padding: 14 + } + } + } +} diff --git a/src/FluNetwork.cpp b/src/FluNetwork.cpp new file mode 100644 index 00000000..d7e49d2b --- /dev/null +++ b/src/FluNetwork.cpp @@ -0,0 +1,288 @@ +#include "FluNetwork.h" + +#include +#include +#include +#include +#include +#include +#include + +NetworkCallable::NetworkCallable(QObject *parent):QObject{parent}{ + +} + + +QString NetworkParams::method2String(){ + switch (_method) { + case METHOD_GET: + return "GET"; + case METHOD_HEAD: + return "HEAD"; + case METHOD_POST: + return "POST"; + case METHOD_PUT: + return "PUT"; + case METHOD_PATCH: + return "PATCH"; + case METHOD_DELETE: + return "DELETE"; + default: + return ""; + } +} + +int NetworkParams::getTimeout(){ + if(_timeout != -1){ + return _timeout; + } + return FluNetwork::getInstance()->timeout(); +} + +int NetworkParams::getRetry(){ + if(_retry != -1){ + return _retry; + } + return FluNetwork::getInstance()->retry(); +} + +NetworkParams::NetworkParams(QObject *parent) + : QObject{parent} +{ +} + +NetworkParams::NetworkParams(QString url,Type type,Method method,QObject *parent) + : QObject{parent} +{ + this->_method = method; + this->_url = url; + this->_type = type; +} + +NetworkParams* NetworkParams::add(QString key,QVariant val){ + _paramMap.insert(key,val); + return this; +} + +NetworkParams* NetworkParams::addHeader(QString key,QVariant val){ + _headerMap.insert(key,val); + return this; +} + +NetworkParams* NetworkParams::addQuery(QString key,QVariant val){ + _queryMap.insert(key,val); + return this; +} + +NetworkParams* NetworkParams::setBody(QString val){ + _body = val; + return this; +} + +NetworkParams* NetworkParams::setTimeOut(int val){ + _timeout = val; + return this; +} + +NetworkParams* NetworkParams::setRetry(int val){ + _retry = val; + return this; +} + +void NetworkParams::go(NetworkCallable* callable){ + FluNetwork::getInstance()->handle(this,callable); +} + +void FluNetwork::handle(NetworkParams* params,NetworkCallable* c){ + std::shared_ptr times = std::make_shared(0); + QPointer callable(c); + if(!callable.isNull()){ + callable->start(); + } + QUrl url(params->_url); + QNetworkAccessManager* manager = new QNetworkAccessManager(); + QNetworkReply *reply = nullptr; + manager->setTransferTimeout(params->getTimeout()); + addQueryParam(&url,params->_queryMap); + QNetworkRequest request(url); + addHeaders(&request,params->_headerMap); + connect(manager,&QNetworkAccessManager::finished,this,[this,params,request,callable,manager,times](QNetworkReply *reply){ + if(reply->error() != QNetworkReply::NoError && *times < params->getRetry()) { + (*times)++; + sendRequest(manager,request,params,reply); + } else { + QString response = QString::fromUtf8(reply->readAll()); + QNetworkReply::NetworkError error = reply->error(); + if(error == QNetworkReply::NoError){ + if(!callable.isNull()){ + callable->success(response); + } + }else{ + if(!callable.isNull()){ + int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + callable->error(httpStatus,reply->errorString(),response); + } + } + reply->deleteLater(); + manager->deleteLater(); + if(!callable.isNull()){ + callable->finish(); + } + } + }); + sendRequest(manager,request,params,reply); +} + +void FluNetwork::sendRequest(QNetworkAccessManager* manager,QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply){ + if(reply){ + reply->deleteLater(); + } + QByteArray verb = params->method2String().toUtf8(); + switch (params->_type) { + case NetworkParams::TYPE_FORM:{ + QHttpMultiPart *multiPart = new QHttpMultiPart(); + multiPart->setContentType(QHttpMultiPart::FormDataType); + for (const auto& each : params->_paramMap.toStdMap()) + { + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(each.first)); + part.setBody(each.second.toByteArray()); + multiPart->append(part); + } + reply = manager->sendCustomRequest(request,verb,multiPart); + multiPart->setParent(reply); + break; + } + case NetworkParams::TYPE_JSON:{ + request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json;charset=utf-8")); + QJsonObject json; + for (const auto& each : params->_paramMap.toStdMap()) + { + json.insert(each.first,each.second.toJsonValue()); + } + QByteArray data = QJsonDocument(json).toJson(QJsonDocument::Compact); + reply = manager->sendCustomRequest(request,verb,data); + break; + } + case NetworkParams::TYPE_JSONARRAY:{ + request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json;charset=utf-8")); + QJsonArray jsonArray; + for (const auto& each : params->_paramMap.toStdMap()) + { + QJsonObject json; + json.insert(each.first,each.second.toJsonValue()); + jsonArray.append(json); + } + QByteArray data = QJsonDocument(jsonArray).toJson(QJsonDocument::Compact); + reply = manager->sendCustomRequest(request,params->method2String().toUtf8(),data); + break; + } + case NetworkParams::TYPE_BODY:{ + request.setHeader(QNetworkRequest::ContentTypeHeader, QString("text/plain;charset=utf-8")); + QByteArray data = params->_body.toUtf8(); + reply = manager->sendCustomRequest(request,verb,data); + break; + } + default: + reply = manager->sendCustomRequest(request,verb); + break; + } +} + +void FluNetwork::addHeaders(QNetworkRequest* request,const QMap& headers){ + QMapIterator iter(headers); + while (iter.hasNext()) + { + iter.next(); + request->setRawHeader(iter.key().toUtf8(), iter.value().toString().toUtf8()); + } +} + +void FluNetwork::addQueryParam(QUrl* url,const QMap& params){ + QMapIterator iter(params); + QUrlQuery urlQuery(*url); + while (iter.hasNext()) + { + iter.next(); + urlQuery.addQueryItem(iter.key(), iter.value().toString()); + } + url->setQuery(urlQuery); +} + +FluNetwork::FluNetwork(QObject *parent): QObject{parent} +{ + timeout(15000); + retry(3); +} + +NetworkParams* FluNetwork::get(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_NONE,NetworkParams::METHOD_GET); +} + +NetworkParams* FluNetwork::head(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_NONE,NetworkParams::METHOD_HEAD); +} + +NetworkParams* FluNetwork::postBody(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_POST); +} + +NetworkParams* FluNetwork::putBody(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_PUT); +} + +NetworkParams* FluNetwork::patchBody(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_PATCH); +} + +NetworkParams* FluNetwork::deleteBody(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_DELETE); +} + +NetworkParams* FluNetwork::postForm(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_POST); +} + +NetworkParams* FluNetwork::putForm(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_PUT); +} + +NetworkParams* FluNetwork::patchForm(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_PATCH); +} + +NetworkParams* FluNetwork::deleteForm(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_DELETE); +} + +NetworkParams* FluNetwork::postJson(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_POST); +} + +NetworkParams* FluNetwork::putJson(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_PUT); +} + +NetworkParams* FluNetwork::patchJson(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_PATCH); +} + +NetworkParams* FluNetwork::deleteJson(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_DELETE); +} + +NetworkParams* FluNetwork::postJsonArray(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_POST); +} + +NetworkParams* FluNetwork::putJsonArray(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_PUT); +} + +NetworkParams* FluNetwork::patchJsonArray(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_PATCH); +} + +NetworkParams* FluNetwork::deleteJsonArray(const QString& url){ + return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_DELETE); +} diff --git a/src/FluNetwork.h b/src/FluNetwork.h new file mode 100644 index 00000000..a74a461c --- /dev/null +++ b/src/FluNetwork.h @@ -0,0 +1,105 @@ +#ifndef FLUNETWORK_H +#define FLUNETWORK_H + +#include +#include +#include +#include +#include +#include "stdafx.h" +#include "singleton.h" + + +class NetworkCallable : public QObject{ + Q_OBJECT + QML_NAMED_ELEMENT(FluNetworkCallable) +public: + explicit NetworkCallable(QObject *parent = nullptr); + Q_SIGNAL void start(); + Q_SIGNAL void finish(); + Q_SIGNAL void error(int status,QString errorString,QString result); + Q_SIGNAL void success(QString result); +}; + +class NetworkParams : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(FluNetworkParams) +public: + enum Method{ + METHOD_GET, + METHOD_HEAD, + METHOD_POST, + METHOD_PUT, + METHOD_PATCH, + METHOD_DELETE + }; + enum Type{ + TYPE_NONE, + TYPE_FORM, + TYPE_JSON, + TYPE_JSONARRAY, + TYPE_BODY + }; + explicit NetworkParams(QObject *parent = nullptr); + NetworkParams(QString url,Type type,Method method,QObject *parent = nullptr); + Q_INVOKABLE NetworkParams* addQuery(QString key,QVariant val); + Q_INVOKABLE NetworkParams* addHeader(QString key,QVariant val); + Q_INVOKABLE NetworkParams* add(QString key,QVariant val); + Q_INVOKABLE NetworkParams* setBody(QString val); + Q_INVOKABLE NetworkParams* setTimeOut(int val); + Q_INVOKABLE NetworkParams* setRetry(int val); + Q_INVOKABLE void go(NetworkCallable* result); + QString method2String(); + int getTimeout(); + int getRetry(); +public: + Method _method; + Type _type; + QString _url; + QString _body; + QMap _queryMap; + QMap _headerMap; + QMap _paramMap; + int _timeout = -1; + int _retry = -1; +}; + +class FluNetwork : public QObject +{ + Q_OBJECT + Q_PROPERTY_AUTO(int,timeout) + Q_PROPERTY_AUTO(int,retry) + QML_NAMED_ELEMENT(FluNetwork) + QML_SINGLETON +private: + explicit FluNetwork(QObject *parent = nullptr); +public: + SINGLETONG(FluNetwork) + static FluNetwork *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine){return getInstance();} + Q_INVOKABLE NetworkParams* get(const QString& url); + Q_INVOKABLE NetworkParams* head(const QString& url); + Q_INVOKABLE NetworkParams* postBody(const QString& url); + Q_INVOKABLE NetworkParams* putBody(const QString& url); + Q_INVOKABLE NetworkParams* patchBody(const QString& url); + Q_INVOKABLE NetworkParams* deleteBody(const QString& url); + Q_INVOKABLE NetworkParams* postForm(const QString& url); + Q_INVOKABLE NetworkParams* putForm(const QString& url); + Q_INVOKABLE NetworkParams* patchForm(const QString& url); + Q_INVOKABLE NetworkParams* deleteForm(const QString& url); + Q_INVOKABLE NetworkParams* postJson(const QString& url); + Q_INVOKABLE NetworkParams* putJson(const QString& url); + Q_INVOKABLE NetworkParams* patchJson(const QString& url); + Q_INVOKABLE NetworkParams* deleteJson(const QString& url); + Q_INVOKABLE NetworkParams* postJsonArray(const QString& url); + Q_INVOKABLE NetworkParams* putJsonArray(const QString& url); + Q_INVOKABLE NetworkParams* patchJsonArray(const QString& url); + Q_INVOKABLE NetworkParams* deleteJsonArray(const QString& url); + void handle(NetworkParams* params,NetworkCallable* result); +private: + void sendRequest(QNetworkAccessManager* manager,QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply); + void addQueryParam(QUrl* url,const QMap& params); + void addHeaders(QNetworkRequest* request,const QMap& headers); +}; + +#endif // FLUNETWORK_H diff --git a/src/FluentUI.cpp b/src/FluentUI.cpp index 702da9ea..f90c2afd 100644 --- a/src/FluentUI.cpp +++ b/src/FluentUI.cpp @@ -17,6 +17,7 @@ #include "FluViewModel.h" #include "Screenshot.h" #include "FluRectangle.h" +#include "FluNetwork.h" #include "QRCode.h" int major = 1; @@ -57,6 +58,8 @@ void FluentUI::registerTypes(const char *uri){ qmlRegisterType(uri,major,minor,"FluViewModel"); qmlRegisterType(uri,major,minor,"FluTreeModel"); qmlRegisterType(uri,major,minor,"FluRectangle"); + qmlRegisterType(uri,major,minor,"FluNetworkCallable"); + qmlRegisterType(uri,major,minor,"FluNetworkParams"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/ColorPicker.qml"),uri,major,minor,"ColorPicker"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml"),uri,major,minor,"Checkerboard"); @@ -191,5 +194,7 @@ void FluentUI::initializeEngine(QQmlEngine *engine, const char *uri){ engine->rootContext()->setContextProperty("FluTextStyle",textStyle); FluEventBus* eventBus = FluEventBus::getInstance(); engine->rootContext()->setContextProperty("FluEventBus",eventBus); + FluNetwork* network = FluNetwork::getInstance(); + engine->rootContext()->setContextProperty("FluNetwork",network); engine->addImportPath("qrc:/qt/qml"); }