diff --git a/AicsKnowledgeBase/qml/MainWindow.qml b/AicsKnowledgeBase/qml/MainWindow.qml index a6449dd..7e1eb3c 100644 --- a/AicsKnowledgeBase/qml/MainWindow.qml +++ b/AicsKnowledgeBase/qml/MainWindow.qml @@ -54,6 +54,13 @@ FluWindow { } } + StackView { + id: stack_view + Layout.fillHeight: true + Layout.fillWidth: true + + } + ContentPage { Layout.fillHeight: true Layout.fillWidth: true diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index 11e348c..5ea92ec 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -53,12 +53,12 @@ Item { width: 28 InputDialog { id: dialog - title: "新建文件夹" + title: "½ļ" buttonFlags: FluContentDialog.PositiveButton | FluContentDialog.NegativeButton - negativeText: "取消" - positiveText: "确定" - message: "请输入文件夹名称" + negativeText: "ȡ" + positiveText: "ȷ" + message: "ļ" onPositiveClicked: text => { console.log(text) } @@ -77,7 +77,7 @@ Item { } FluButton { Layout.alignment: Qt.AlignRight - text: "上传" + text: "ϴ" onClicked: function () { console.log("click") fileDialog.open() diff --git a/AicsKnowledgeBase/qml/component/FileListItem.qml b/AicsKnowledgeBase/qml/component/FileListItem.qml index 7516c72..2eaab06 100644 --- a/AicsKnowledgeBase/qml/component/FileListItem.qml +++ b/AicsKnowledgeBase/qml/component/FileListItem.qml @@ -39,13 +39,13 @@ FluArea { Image { id: icon source: type ? "qrc:/AicsKnowledgeBase/res/" + type + ".png" : "" - Layout.preferredHeight: 32 - Layout.preferredWidth: 32 + Layout.preferredHeight: 24 + Layout.preferredWidth: 24 } FluText { id: title font.bold: true - font.pointSize: 15 + font.pointSize: 12 text: fileItem.title } } diff --git a/AicsKnowledgeBase/qml/component/UploadButton.qml b/AicsKnowledgeBase/qml/component/UploadButton.qml new file mode 100644 index 0000000..200c825 --- /dev/null +++ b/AicsKnowledgeBase/qml/component/UploadButton.qml @@ -0,0 +1,146 @@ +import QtQuick 2.15 +import QtQuick.Layouts +import FluentUI +import QtQuick.Dialogs +import "qrc:///AicsKnowledgeBase/qml/global" +import SignalFileOperation 1.0 +import AicsKB.FileTransferManager + +FluButton { + property var header + property var currentSelectedFile: null + + Layout.alignment: Qt.AlignRight + text: "上传" + onClicked: function () { + console.log("click") + fileDialog.open() + } + FileDialog { + id: fileDialog + onAccepted: function () { + currentSelectedFile = selectedFile + popup.open() + } + onRejected: currentSelectedFile = null + } + + FluPopup { + id: popup + focus: true + Rectangle { + id: layout_content + anchors.fill: parent + implicitWidth: Window.window == null ? 400 : Math.min( + Window.window.width, 400) + + implicitHeight: text_title.height + text_message.height + layout_actions.height + color: 'transparent' + radius: 5 + FluText { + id: text_title + font: FluTextStyle.TitleLarge + text: "上传知识文件" + topPadding: 20 + leftPadding: 20 + rightPadding: 20 + wrapMode: Text.WrapAnywhere + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + FluText { + id: text_message + font: FluTextStyle.Body + wrapMode: Text.WrapAnywhere + text: "content" + topPadding: 14 + leftPadding: 20 + rightPadding: 20 + bottomPadding: 14 + anchors { + top: text_title.bottom + left: parent.left + right: parent.right + } + } + Rectangle { + id: layout_actions + height: 68 + radius: 5 + color: FluTheme.dark ? Qt.rgba( + 32 / 255, 32 / 255, 32 / 255, + blurBackground ? blurOpacity - 0.4 : 1) : Qt.rgba( + 243 / 255, 243 / 255, 243 / 255, + blurBackground ? blurOpacity - 0.4 : 1) + anchors { + top: text_message.bottom + left: parent.left + right: parent.right + } + RowLayout { + anchors { + centerIn: parent + margins: spacing + fill: parent + } + spacing: 15 + FluButton { + id: negative_btn + Layout.fillWidth: true + Layout.fillHeight: true + text: "取消" + onClicked: { + popup.close() + } + } + FluFilledButton { + id: positive_btn + Layout.fillWidth: true + Layout.fillHeight: true + text: "上传" + onClicked: { + popup.close() + let name = FileTransferManager.getFileName( + currentSelectedFile) + const size = FileTransferManager.getFileSize( + currentSelectedFile) + const md5 = FileTransferManager.getFileMd5( + currentSelectedFile) + if (size <= 0 || md5 === '') + return + var body = { + "name": name, + "brief": "brief", + "size": size, + "md5": md5, + "tags": [], + "parentId": currentParentId() + } + console.log("begin") + console.log(JSON.stringify(body)) + Request.post("knowledge/file", + JSON.stringify(body), + function (res, data) { + console.log(res) + console.log(data) + FileTransferManager.upload( + currentSelectedFile, + data.id, data.ticket, + name) + }, function (res, data) { + console.log(res) + }) + } + } + } + } + } + } + + function currentParentId() { + return header.items.length !== 0 ? header.items[header.items.length - 1].uuid : null + } +} diff --git a/AicsKnowledgeBase/qml/page/ContentPage.qml b/AicsKnowledgeBase/qml/page/ContentPage.qml index fccba38..5e248c2 100644 --- a/AicsKnowledgeBase/qml/page/ContentPage.qml +++ b/AicsKnowledgeBase/qml/page/ContentPage.qml @@ -193,9 +193,7 @@ FluArea { } onClicked: { emit: content_area.download(content_area.knowledgeFileId) - // FileTransferManager.download(content_area.knowledgeFileId) - FileTransferManager.download( - "4973c59e-3ba1-41f2-a57e-3b53c2b5e2a4") + FileTransferManager.download(content_area.knowledgeFileId) } } } diff --git a/AicsKnowledgeBase/qml/page/NotePage.qml b/AicsKnowledgeBase/qml/page/NotePage.qml new file mode 100644 index 0000000..480e67e --- /dev/null +++ b/AicsKnowledgeBase/qml/page/NotePage.qml @@ -0,0 +1,230 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import QtWebEngine 1.2 +import FluentUI +import AicsKB.FileTransferManager +import SignalFileOperation 1.0 +import "qrc:///AicsKnowledgeBase/qml/page" + +FluArea { + id: note_area + paddings: 0 + backgroundColor: "#f9f9f9" + visible: false + property string type: "" + property string knowledgeFileId + property string isFile: false + property string knowledgeUrl : "aics/main/knowledge/" + signal download(string knowledgeFileId) + signal clickTags(string tagName) + + // paddings: { + // top: 10 + // right: 0 + // bottom: 10 + // left: 10 + // } + Connections { + target: SignalFileOperation + function onOpen(file) { + + } + } + + FluScrollablePage { + id: note_page + anchors.fill: parent + leftPadding: 10 + topPadding: 10 + rightPadding: 10 + bottomPadding: 0 + + property int likeCount: 0 + property int favoriteCount: 0 + property int shareCount: 0 + property int browsCount: 555 + property bool isLike: false + property bool isFavorite: false + property bool isDownload: false + property double fileSize: 455 + property list tags: ["tag1", "tag2", "tag3"] + property string publishTime: "2002-01-01" + property string brief: "这是一个简介" + + function getType(suffix) { + if(suffix === "md") + return "md" + else if(suffix === "mp4" || suffix === "avi" || suffix === "rmvb" || suffix === "mkv") + return "video" + else return "other" + } + + function loadFile(noteId) { + var fileUrl = knowledgeUrl+noteId + Request.get(fileUrl, function (response) { + var data = JSON.parse(response) + console.log(data.knowledgeFileAttribute) + publishTime = data.createTime + type = getType(data.knowledgeFileAttribute.suffix) + brief = data.knowledgeFileAttribute.brief + fileSize = data.knowledgeFileAttribute.size + browsCount = data.knowledgeFileAttribute.pageView + var tagString = "" + for (var j = 0; j < file.knowledgeFileAttribute.tags.length; j++) { + if (j != 0) + tagString = tagString + "," + tagString = tagString + file.knowledgeFileAttribute.tags[j].name + } + tags = tagString.split(",") + }) + } + + Item { + Layout.fillWidth: true + implicitHeight: 50 + + FluText { + id: text_title + padding: 10 + text: "文章标题" + font { + pointSize: 15 + bold: true + } + } + ColumnLayout { + id: layout_share + FluIconButton { + id: button_share + iconSize: 15 + iconSource: FluentIcons.Share + text: content_page.shareCount.toString() + } + FluText { + id: text_share + text: note_page.shareCount + font.pointSize: 8 + Layout.alignment: button_share.Center + Layout.topMargin: -5 + } + anchors { + verticalCenter: text_title.verticalCenter + right: parent.right + } + } + ColumnLayout { + id: layout_favorite + FluIconButton { + id: button_favorite + + iconSize: 15 + iconSource: note_page.isFavorite ? FluentIcons.FavoriteStarFill : FluentIcons.FavoriteStar + onClicked: { + Request.put() + } + } + FluText { + id: text_favorite + text: note_page.favoriteCount + font.pointSize: 8 + Layout.alignment: button_favorite.Center + Layout.topMargin: -5 + } + anchors { + verticalCenter: text_title.verticalCenter + right: layout_share.left + } + } + ColumnLayout { + id: layout_like + FluIconButton { + id: button_like + iconSize: 15 + iconSource: note_page.isLike ? FluentIcons.HeartFill : FluentIcons.Heart + } + FluText { + id: text_like + text: note_page.favoriteCount + font.pointSize: 8 + Layout.alignment: button_like.Center + Layout.topMargin: -5 + } + anchors { + verticalCenter: text_title.verticalCenter + right: layout_favorite.left + } + } + FluIconButton { + id: button_download + iconSize: 25 + iconSource: note_page.isDownload ? FluentIcons.OEM : FluentIcons.Download + anchors { + verticalCenter: text_title.verticalCenter + right: layout_like.left + rightMargin: 20 + } + onClicked: { + emit: content_area.download(content_area.knowledgeFileId) + FileTransferManager.download(content_area.knowledgeFileId) + } + } + } + + FluArea { + Layout.fillWidth: true + implicitHeight: 100 + ColumnLayout { + RowLayout { + FluText { + padding: 10 + text: content_page.publishTime + } + FluText { + padding: 10 + text: content_page.fileSize.toString() + "MB" + } + FluText { + padding: 10 + text: content_page.browsCount.toString() + "浏览量" + } + } + FluText { + Layout.topMargin: -2 + Layout.leftMargin: 10 + text: content_page.brief + } + RowLayout { + Layout.topMargin: 2 + Layout.leftMargin: 5 + Repeater { + model: content_page.tags + delegate: Button { + Layout.margins: 2 + text: "#" + content_page.tags[index] + background: Rectangle { + implicitHeight: 10 + implicitWidth: 10 + color: FluColors.Grey20 + radius: 10 + } + onClicked: { + emit: content_area.clickTags(text) + } + } + } + } + } + } + + NoteEditPage { + id: md_edit_page + noteId: "255454" + Layout.fillWidth: true + implicitHeight: 400 + } + + } +} diff --git a/AicsKnowledgeBase/src/FileTransferManager.cpp b/AicsKnowledgeBase/src/FileTransferManager.cpp index 2f68638..35888fc 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.cpp +++ b/AicsKnowledgeBase/src/FileTransferManager.cpp @@ -30,6 +30,24 @@ struct FileUploading : FileItem std::unordered_map uploadingMap; std::mutex uploadingMapMutex; +CURL *initCurlWithCommonOptions() +{ + CURL *curl = curl_easy_init(); + if (!curl) { + qCritical() << "CURL INIT FAILED!"; + return nullptr; + } + curl_easy_setopt(curl, CURLOPT_VERBOSE, true); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3000); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https"); + //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 0L); + //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, 0L);//不验证证书和HOST + return curl; +} + + size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) { auto buf = (FileDownloading *) obj; @@ -41,9 +59,8 @@ size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) size_t readDataFunc(void *ptr, size_t size, size_t nmemb, void *obj) { auto buf = (FileUploading *) obj; - auto readSize = size * nmemb; - buf->file.read(reinterpret_cast(ptr), readSize); - return readSize; + buf->file.read(reinterpret_cast(ptr), size * nmemb); + return buf->file.gcount(); } static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) @@ -113,25 +130,18 @@ static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void } CURLcode -httpGet(const std::string &url, std::string &response, long httpVersion = CURL_HTTP_VERSION_3ONLY, int timeout = 3000) +httpGet(const std::string &url, std::string &response) { CURLcode res; // 获取easy handle - CURL *easy_handle = curl_easy_init(); + CURL *easy_handle = initCurlWithCommonOptions(); if (!easy_handle) return CURLE_FAILED_INIT; - // 设置easy handle属性 - curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, httpVersion); curl_easy_setopt(easy_handle, CURLOPT_URL, (baseUrl + url).c_str()); curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, writeDataCallback); - curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, (void *) &response); - curl_easy_setopt(easy_handle, CURLOPT_FOLLOWLOCATION, 1L); - //curl_easy_setopt(easy_handle, CURLOPT_DEFAULT_PROTOCOL, "https"); - //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 0L); - //curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, 0L);//不验证证书和HOST + // 执行数据请求 res = curl_easy_perform(easy_handle); curl_easy_cleanup(easy_handle); @@ -148,12 +158,40 @@ FileTransferManager::FileTransferManager(QObject *parent) qDebug() << "HTTP3 FAILED"; } +bool uploadComplete(const QString &fileId, const QString &ticket) +{ + CURLcode res; + auto curlHandle = initCurlWithCommonOptions(); + if (!curlHandle) return false; + + curl_httppost *formpost = nullptr; + curl_httppost *lastptr = nullptr; + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket", + CURLFORM_COPYCONTENTS, ticket.toStdString().c_str(), + CURLFORM_END); + + curl_easy_setopt(curlHandle, CURLOPT_URL, (baseUrl + "File/" + fileId.toStdString() + "/complete").c_str()); + curl_easy_setopt(curlHandle, CURLOPT_HTTPPOST, formpost); + + res = curl_easy_perform(curlHandle); + curl_easy_cleanup(curlHandle); + curl_formfree(formpost); + return res == CURLE_OK; +} + void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket, const QString &fileName) { qDebug() << fileUrl.fileName(); QtConcurrent::run([fileUrl, fileId, ticket, fileName] { + std::string resData; + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData)) + return false; + + qDebug() << resData.c_str(); + auto resJson = QJsonDocument::fromJson(resData.c_str()); + //if(!resJson["isCompleted"].toBool()) auto fileSize = QFileInfo(fileUrl.toLocalFile()).size(); @@ -176,7 +214,7 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS } CURLcode res; - fileUploading.curlHandle = curl_easy_init(); + fileUploading.curlHandle = initCurlWithCommonOptions(); if (!fileUploading.curlHandle) return false; @@ -194,27 +232,33 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS CURLFORM_END); curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "data", + CURLFORM_COPYNAME, "file", + CURLFORM_FILENAME, fileName.toLocal8Bit().toStdString().c_str(), CURLFORM_STREAM, &fileUploading, CURLFORM_CONTENTLEN, fileSize, CURLFORM_END); - curl_easy_setopt(fileUploading.curlHandle, CURLOPT_URL, (baseUrl + fileId.toStdString()).c_str()); - curl_easy_setopt(fileUploading.curlHandle, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(fileUploading.curlHandle, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(fileUploading.curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY); + curl_easy_setopt(fileUploading.curlHandle, CURLOPT_URL, (baseUrl + "File/" + fileId.toStdString()).c_str()); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_HTTPPOST, formpost); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_READFUNCTION, readDataFunc); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_NOPROGRESS, false); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_XFERINFOFUNCTION, uploadInfo); curl_easy_setopt(fileUploading.curlHandle, CURLOPT_XFERINFODATA, &fileUploading); + curl_easy_setopt(fileUploading.curlHandle, CURLOPT_WRITEFUNCTION, writeDataCallback); + std::string response; + curl_easy_setopt(fileUploading.curlHandle, CURLOPT_WRITEDATA, (void *) &response); qDebug() << "Begin Upload"; + qDebug() << ticket.toStdString().c_str(); + qDebug() << std::to_string(fileSize).c_str(); + + res = curl_easy_perform(fileUploading.curlHandle); if (res == CURLE_OK) qDebug() << "Upload Success"; else qDebug() << "Upload Failed"; + qDebug() << response.c_str(); curl_easy_cleanup(fileUploading.curlHandle); curl_formfree(formpost); fileUploading.file.close(); @@ -223,6 +267,8 @@ FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QS uploadingMap.erase(fileId); uploadingMapMutex.unlock(); + if (res == CURLE_OK) + uploadComplete(fileId, ticket); }); } @@ -231,11 +277,10 @@ curl_off_t getHttpFileSize(const std::string &url) { curl_off_t fileLength = 0; - CURL *handle = curl_easy_init(); + CURL *handle = initCurlWithCommonOptions(); curl_easy_setopt(handle, CURLOPT_URL, url.c_str()); curl_easy_setopt(handle, CURLOPT_HEADER, 0); //只需要header头 curl_easy_setopt(handle, CURLOPT_NOBODY, 1); //不需要body - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); if (curl_easy_perform(handle) == CURLE_OK) curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &fileLength); @@ -261,7 +306,7 @@ bool httpDownload(const std::string &url, const FileItem &item) FileDownloading &obj = iter->second; - obj.curlHandle = curl_easy_init(); + obj.curlHandle = initCurlWithCommonOptions(); if (!obj.curlHandle) return false; obj.totalSize = fileSize;//原始文件大小 @@ -273,10 +318,8 @@ bool httpDownload(const std::string &url, const FileItem &item) return false; } - curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L); curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0); curl_easy_setopt(obj.curlHandle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(obj.curlHandle, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(obj.curlHandle, CURLOPT_URL, url.c_str()); curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEDATA, (void *) &obj); curl_easy_setopt(obj.curlHandle, CURLOPT_WRITEFUNCTION, writeDataFunc); @@ -296,11 +339,8 @@ bool httpDownload(const std::string &url, const FileItem &item) return res == CURLE_OK; } -void FileTransferManager::download(QString fileId) +void FileTransferManager::download(const QString& fileId) { - static int _fileId = 0; - //fileId = QString::number(_fileId++); - QtConcurrent::run([fileId, this] { qDebug() << "Start Get"; @@ -321,7 +361,7 @@ void FileTransferManager::download(QString fileId) FileTransferListModel::instance().insertItem(item); });*/ - auto fileUrl = "https://curl.se/download/curl-8.1.2.zip"; + auto fileUrl = "File/" + fileId.toStdString(); /* curl_slist *headers = nullptr; @@ -377,7 +417,7 @@ void FileTransferManager::resume(const QString &fileId) qDebug() << "Start Get"; std::string resData; - if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData, CURL_HTTP_VERSION_1_1)) + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData)) return; qDebug() << resData.c_str(); diff --git a/AicsKnowledgeBase/src/FileTransferManager.h b/AicsKnowledgeBase/src/FileTransferManager.h index 256aec0..2f2406a 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.h +++ b/AicsKnowledgeBase/src/FileTransferManager.h @@ -24,7 +24,7 @@ public: Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl); Q_INVOKABLE void upload(const QUrl& fileUrl, const QString& fileId, const QString& ticket, const QString &fileName); - Q_INVOKABLE void download(QString fileId); + Q_INVOKABLE void download(const QString& fileId); Q_INVOKABLE void pause(const QString& fileId); Q_INVOKABLE void resume(const QString& fileId);