From 2706ba50dc009c38d5c8aa0a78f14b5263bc8097 Mon Sep 17 00:00:00 2001 From: karlis <2995621482@qq.com> Date: Wed, 5 Jul 2023 20:59:57 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86FileList?= =?UTF-8?q?=E8=B0=83=E7=94=A8undefined=E5=AF=B9=E8=B1=A1=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/component/FileList.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index ab662a5..8ca3732 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -151,8 +151,9 @@ Item { size: isDir ? 0 : model.size stars: isDir ? 0 : model.stars // split string to array - tags: isDir ? [] : model.tags.length === 0 ? [] : model.tags.split( - ",") + tags: isDir ? [] : model.tags === null + || model.tags === undefined + || model.tags === "" ? [] : model.tags.split(",") onTagClicked: { emit: search(tag) console.log(tag) From d5d707f891bf4b1aaef319630fa1791bea7003f5 Mon Sep 17 00:00:00 2001 From: wuyize Date: Wed, 5 Jul 2023 21:14:33 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/LoginWindow.qml | 4 +- AicsKnowledgeBase/qml/MainWindow.qml | 14 - .../qml/component/TransferListPopup.qml | 102 ++++++- AicsKnowledgeBase/qml/global/Request.qml | 2 +- AicsKnowledgeBase/qml/page/ContentPage.qml | 51 ++-- AicsKnowledgeBase/qml/page/FilePage.qml | 29 +- .../src/FileTransferListModel.cpp | 6 + AicsKnowledgeBase/src/FileTransferListModel.h | 6 +- AicsKnowledgeBase/src/FileTransferManager.cpp | 277 +++++++++++++----- AicsKnowledgeBase/src/FileTransferManager.h | 11 +- 10 files changed, 379 insertions(+), 123 deletions(-) diff --git a/AicsKnowledgeBase/qml/LoginWindow.qml b/AicsKnowledgeBase/qml/LoginWindow.qml index 07735c4..743f2a5 100644 --- a/AicsKnowledgeBase/qml/LoginWindow.qml +++ b/AicsKnowledgeBase/qml/LoginWindow.qml @@ -108,8 +108,8 @@ AppFluWindow { }, function (p1, p2) { console.log(p1) console.log(p2) - FluApp.navigate("/") - window.close() + //FluApp.navigate("/") + //window.close() btn_login.disabled = false btn_login.text = "登录" }) diff --git a/AicsKnowledgeBase/qml/MainWindow.qml b/AicsKnowledgeBase/qml/MainWindow.qml index 00c8aac..a6449dd 100644 --- a/AicsKnowledgeBase/qml/MainWindow.qml +++ b/AicsKnowledgeBase/qml/MainWindow.qml @@ -89,20 +89,6 @@ FluWindow { title_bar.maximizeButton.visible = true // window.backgroundVisible = false window.show() - - Request.post( - "knowledge/file", "{\n" - + " \"name\": \"特东局却热\",\n" + " \"brief\": \"fugiat occaecat\",\n" - + " \"size\": 88,\n" + " \"sha256\": \"ea incididunt pariatur\",\n" - + " \"tags\": [\n" + " 43,\n" - + " 45\n" + " ],\n" + " \"parentId\": \"4\"\n" + "}", - function (p1, p2) { - console.log(p1) - console.log(p2) - }, function (p1, p2) { - console.log(p1) - console.log(p2) - }) } } } diff --git a/AicsKnowledgeBase/qml/component/TransferListPopup.qml b/AicsKnowledgeBase/qml/component/TransferListPopup.qml index dece83f..bd3c66e 100644 --- a/AicsKnowledgeBase/qml/component/TransferListPopup.qml +++ b/AicsKnowledgeBase/qml/component/TransferListPopup.qml @@ -2,7 +2,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Window import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import FluentUI +import AicsKB.FileTransferManager Popup { id: transfer_popup @@ -21,21 +23,99 @@ Popup { anchors.topMargin: 10 ColumnLayout { Layout.fillWidth: true - Layout.bottomMargin: -10 + spacing: 0 Repeater { model: FileTransferListModel - delegate: ColumnLayout { - Layout.bottomMargin: 10 - spacing: 10 - width: parent.width - Text { - text: name + delegate: Item { + Layout.fillWidth: true + height: 50 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 5 + spacing: 2 + Text { + text: name + } + + FluProgressBar { + Layout.fillWidth: true + progress: completedSize / totalSize + indeterminate: false + } + + Text { + color: FluColors.Grey130 + text: formatSize(speed) + "/s - " + formatSize( + completedSize) + "/" + formatSize( + totalSize) + + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @param {Number} size 文件大小 + * @param {Number} [pointLength=1] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。 + * 如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + */ + function formatSize(size, pointLength, units) { + var unit + units = units || ['B', 'KB', 'MB', 'GB', 'TB'] + while ((unit = units.shift()) && size > 1024) { + size = size / 1024 + } + return (unit === 'B' ? size : size.toFixed( + pointLength === undefined ? 1 : pointLength)) + ' ' + unit + } + } } - FluProgressBar { - Layout.fillWidth: true - progress: completedSize / totalSize - indeterminate: false + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: file_buttons.visible = true + onExited: file_buttons.visible = false + + LinearGradient { + anchors.fill: file_buttons + visible: file_buttons.visible + + start: Qt.point(0, 0) + end: Qt.point(20, 0) + gradient: Gradient { + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: 1.0 + color: "#f8f8f8" + } + } + } + RowLayout { + id: file_buttons + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + layoutDirection: Qt.RightToLeft + visible: false + + FluIconButton { + iconSource: FluentIcons.Cancel + } + + FluIconButton { + Layout.leftMargin: 20 + iconSource: paused ? FluentIcons.Play : FluentIcons.Pause + onClicked: { + if (!paused) { + console.log("pause") + FileTransferManager.pause(fileId) + } + } + } + } } } } diff --git a/AicsKnowledgeBase/qml/global/Request.qml b/AicsKnowledgeBase/qml/global/Request.qml index e8539d9..29fae57 100644 --- a/AicsKnowledgeBase/qml/global/Request.qml +++ b/AicsKnowledgeBase/qml/global/Request.qml @@ -5,7 +5,7 @@ import QtQuick QtObject { id: request - property string baseUrl: "http://127.0.0.1:4523/m1/2914957-0-5604d062/" + property string baseUrl: "https://api.hammer-hfut.tk:233/aics/main/" //property string baseUrl: "http://192.168.156.74:8080/" // GET diff --git a/AicsKnowledgeBase/qml/page/ContentPage.qml b/AicsKnowledgeBase/qml/page/ContentPage.qml index debb554..1ec7828 100644 --- a/AicsKnowledgeBase/qml/page/ContentPage.qml +++ b/AicsKnowledgeBase/qml/page/ContentPage.qml @@ -136,7 +136,9 @@ FluArea { } onClicked: { emit: content_area.download(content_area.knowledgeFileId) - FileTransferManager.download(content_area.knowledgeFileId) + // FileTransferManager.download(content_area.knowledgeFileId) + FileTransferManager.download( + "4973c59e-3ba1-41f2-a57e-3b53c2b5e2a4") } } } @@ -207,7 +209,6 @@ FluArea { Layout.fillWidth: true implicitHeight: 400 } - } Component { id: other_view @@ -222,28 +223,28 @@ FluArea { } } -// Item { -// Layout.fillWidth: true -// implicitHeight: 50 -// FluText { -// id: text_note -// text: "笔记" -// padding: 10 -// font { -// pointSize: 15 -// bold: true -// } -// } -// FluTextButton { -// id: button_publish -// text: "发布笔记" -// hoverColor: "blue" -// normalColor: "black" -// anchors { -// verticalCenter: text_note.verticalCenter -// right: parent.right -// } -// } -// } + // Item { + // Layout.fillWidth: true + // implicitHeight: 50 + // FluText { + // id: text_note + // text: "笔记" + // padding: 10 + // font { + // pointSize: 15 + // bold: true + // } + // } + // FluTextButton { + // id: button_publish + // text: "发布笔记" + // hoverColor: "blue" + // normalColor: "black" + // anchors { + // verticalCenter: text_note.verticalCenter + // right: parent.right + // } + // } + // } } } diff --git a/AicsKnowledgeBase/qml/page/FilePage.qml b/AicsKnowledgeBase/qml/page/FilePage.qml index dfdd14b..bde427f 100644 --- a/AicsKnowledgeBase/qml/page/FilePage.qml +++ b/AicsKnowledgeBase/qml/page/FilePage.qml @@ -7,6 +7,7 @@ import QtQuick.Dialogs import FluentUI import AicsKB.FileTransferManager import "qrc:///AicsKnowledgeBase/qml/component" +import "qrc:///AicsKnowledgeBase/qml/global" FluArea { property string url: '' @@ -32,7 +33,33 @@ FluArea { FileDialog { id: fileDialog onAccepted: function () { - FileTransferManager.upload(selectedFile) + const size = FileTransferManager.getFileSize(selectedFile) + const md5 = FileTransferManager.getFileMd5(selectedFile) + + if (size <= 0 || md5 === '') + return + + var body = { + "name": "test2", + "brief": "brief", + "size": size, + "md5": md5, + "tags": [], + "parentId": null + } + 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(selectedFile, + data.id, + data.ticket) + }, function (res, data) { + console.log(res) + }) } } } diff --git a/AicsKnowledgeBase/src/FileTransferListModel.cpp b/AicsKnowledgeBase/src/FileTransferListModel.cpp index 663eb4b..67e9477 100644 --- a/AicsKnowledgeBase/src/FileTransferListModel.cpp +++ b/AicsKnowledgeBase/src/FileTransferListModel.cpp @@ -7,6 +7,8 @@ FileTransferListModel::FileTransferListModel(QObject *parent) m_roleName.insert(kNameRole, "name"); m_roleName.insert(kCompletedSizeRole, "completedSize"); m_roleName.insert(kTotalSizeRole, "totalSize"); + m_roleName.insert(kSpeedRole, "speed"); + m_roleName.insert(kPausedRole, "paused"); m_data = { {"v", "vivo", 1200, 2000}, @@ -42,6 +44,10 @@ QVariant FileTransferListModel::data(const QModelIndex &index, int role) const return m_data.value(index.row()).completedSize; case kTotalSizeRole: return m_data.value(index.row()).totalSize; + case kSpeedRole: + return m_data.value(index.row()).speed; + case kPausedRole: + return m_data.value(index.row()).paused; default: break; } diff --git a/AicsKnowledgeBase/src/FileTransferListModel.h b/AicsKnowledgeBase/src/FileTransferListModel.h index dfb14fd..9fc0fdc 100644 --- a/AicsKnowledgeBase/src/FileTransferListModel.h +++ b/AicsKnowledgeBase/src/FileTransferListModel.h @@ -11,6 +11,8 @@ struct FileItem QString name; int64_t completedSize; int64_t totalSize; + int64_t speed; + bool paused = false; }; @@ -39,7 +41,9 @@ public: kIdRole = Qt::UserRole + 1, kNameRole, kCompletedSizeRole, - kTotalSizeRole + kTotalSizeRole, + kSpeedRole, + kPausedRole }; private: explicit FileTransferListModel(QObject *parent = nullptr); diff --git a/AicsKnowledgeBase/src/FileTransferManager.cpp b/AicsKnowledgeBase/src/FileTransferManager.cpp index a2ba674..55ef572 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.cpp +++ b/AicsKnowledgeBase/src/FileTransferManager.cpp @@ -9,7 +9,9 @@ #include #include -static const std::string baseUrl = "http://127.0.0.1:4523/m1/2914957-0-default/"; +static const std::string baseUrl = "https://api.hammer-hfut.tk:233/aics/file/"; +//static const std::string baseUrl = "http://127.0.0.1:4523/m1/2914957-0-6e5f2db1/"; +//static const std::string baseUrl = "https://quic.nginx.org/"; static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void *userp) { @@ -28,24 +30,26 @@ static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void return 0; } -CURLcode httpGet(const std::string &url, std::string &response, int timeout = 3000) +CURLcode +httpGet(const std::string &url, std::string &response, long httpVersion = CURL_HTTP_VERSION_3ONLY, int timeout = 3000) { CURLcode res; // 获取easy handle CURL *easy_handle = curl_easy_init(); - if (nullptr == easy_handle) { - curl_global_cleanup(); - return CURLE_FAILED_INIT; - } + if (!easy_handle) return CURLE_FAILED_INIT; + // 设置easy handle属性 - //curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_3ONLY); + 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); @@ -55,55 +59,78 @@ CURLcode httpGet(const std::string &url, std::string &response, int timeout = 30 FileTransferManager::FileTransferManager(QObject *parent) : QObject(parent) { + std::string resData; + if (CURLE_OK == httpGet("file/hello", resData)) + qDebug() << "HTTP3 OK\n" << resData.c_str(); + else + qDebug() << "HTTP3 FAILED"; } +int64_t completedSize = 0; -void FileTransferManager::upload(QUrl fileUrl) +size_t readDataFunc(void *ptr, size_t size, size_t nmemb, void *file) +{ + auto s = reinterpret_cast(file)->read(reinterpret_cast(ptr), + size * nmemb); + completedSize += s; + qDebug() << std::format("Uploading: {} / {}", completedSize, + reinterpret_cast(file)->size()).c_str(); + return s; +} + +void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket) { qDebug() << fileUrl.fileName(); + QFile file = fileUrl.toLocalFile(); + if (!file.open(QIODevice::ReadOnly)) + return; + auto fileSize = file.size(); - CURL *curl; CURLcode res; - curl = curl_easy_init(); - if (curl) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:4523/m1/2914957-0-default/"); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https"); - struct curl_slist *headers = NULL; - headers = curl_slist_append(headers, "User-Agent: Apifox/1.0.0 (https://apifox.com)"); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_mime *mime; - curl_mimepart *part; - mime = curl_mime_init(curl); - part = curl_mime_addpart(mime); - curl_mime_name(part, "ticket"); - curl_mime_data(part, "", CURL_ZERO_TERMINATED); - part = curl_mime_addpart(mime); - curl_mime_name(part, "rangeStart"); - curl_mime_data(part, "", CURL_ZERO_TERMINATED); - part = curl_mime_addpart(mime); - curl_mime_name(part, "rangeEnd"); - curl_mime_data(part, "", CURL_ZERO_TERMINATED); - part = curl_mime_addpart(mime); - curl_mime_name(part, "data"); - curl_mime_filedata(part, ""); - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - res = curl_easy_perform(curl); - curl_mime_free(mime); - } + CURL *curl = curl_easy_init(); + if (!curl) + return; + + curl_easy_setopt(curl, CURLOPT_URL, (baseUrl + "file/" + fileId.toStdString()).c_str()); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https"); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY); + curl_httppost *formpost = nullptr; + curl_httppost *lastptr = nullptr; + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket", + CURLFORM_COPYCONTENTS, ticket.toStdString().c_str(), + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeStart", + CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), + CURLFORM_END); + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeEnd", + CURLFORM_COPYCONTENTS, std::to_string(fileSize).c_str(), + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "data", + CURLFORM_STREAM, &file, + CURLFORM_CONTENTLEN, fileSize, + CURLFORM_END); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, readDataFunc); + qDebug() << "Begin Upload"; + completedSize = 0; + res = curl_easy_perform(curl); + if (res == CURLE_OK) + qDebug() << "Upload Success"; + else + qDebug() << "Upload Failed"; curl_easy_cleanup(curl); + curl_formfree(formpost); + file.close(); } -struct FileTransfering : FileItem -{ - CURL *curlHandle = nullptr; - std::ofstream file; -}; - curl_off_t getHttpFileSize(const std::string &url) { curl_off_t fileLength = 0; @@ -112,6 +139,7 @@ curl_off_t getHttpFileSize(const std::string &url) 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); @@ -123,28 +151,66 @@ curl_off_t getHttpFileSize(const std::string &url) return fileLength; } +struct FileTransfering : FileItem +{ + CURL *curlHandle = nullptr; + std::ofstream file; +}; + size_t receiveDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) { auto buf = (FileTransfering *) obj; auto writeSize = size * nmemb; buf->file.write(reinterpret_cast(ptr), writeSize); - buf->completedSize += writeSize; - qDebug() << std::format("Downloading: {} / {}", buf->completedSize, buf->totalSize).c_str(); - - QTimer::singleShot(0, qApp, [buf]() { - FileTransferListModel::instance().setItem(static_cast(*buf)); - }); - return writeSize; } +static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + auto fileTrans = (FileTransfering *) p; + curl_off_t speed = 0; + + curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed); + + fileTrans->completedSize += dlnow; + + fileTrans->speed = speed; + qDebug() << std::format("Downloading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize, + speed).c_str(); + + auto item = static_cast(*fileTrans); + QTimer::singleShot(0, qApp, [item]() { + FileTransferListModel::instance().setItem(item); + }); + + if (fileTrans->paused) { + qDebug() << "Pause"; + return 1; + } + + + fileTrans->completedSize -= dlnow; + + + return 0; +} + + +std::unordered_map transferMap; + +std::mutex transferMapMutex; + bool httpDownload(const std::string &url, const FileItem &item) { auto fileSize = getHttpFileSize(url);//获得文件大小。 if (fileSize != -1) { qDebug() << "FileSize: " << fileSize; - FileTransfering obj = (FileTransfering) item; + transferMapMutex.lock(); + auto [iter, _] = transferMap.emplace(item.id, (FileTransfering) item); + transferMapMutex.unlock(); + + FileTransfering &obj = iter->second; obj.curlHandle = curl_easy_init(); if (!obj.curlHandle) @@ -157,19 +223,29 @@ bool httpDownload(const std::string &url, const FileItem &item) curl_easy_cleanup(obj.curlHandle); return false; } else { + curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L); curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0); curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, 1L); 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, receiveDataFunc); + curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, false);//设为false 下面才能设置进度响应函数 + curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFOFUNCTION, xferinfo); + curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFODATA, &obj); //下载 auto res = curl_easy_perform(obj.curlHandle); curl_easy_cleanup(obj.curlHandle); obj.file.close(); - return res == CURLE_OK && obj.completedSize == obj.totalSize; + + transferMapMutex.lock(); + transferMap.erase(obj.id); + transferMapMutex.unlock(); + + return res == CURLE_OK; } } else return false; @@ -178,18 +254,91 @@ bool httpDownload(const std::string &url, const FileItem &item) void FileTransferManager::download(QString fileId) { static int _fileId = 0; - fileId = QString::number(_fileId++); + //fileId = QString::number(_fileId++); QtConcurrent::run([fileId, this] { qDebug() << "Start Get"; std::string resData; - if (CURLE_OK != httpGet(fileId.toStdString() + "/status", resData)) + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData)) return; qDebug() << resData.c_str(); auto resJson = QJsonDocument::fromJson(resData.c_str()); //if(!resJson["isCompleted"].toBool()) + return; + + + +/* int size = resJson["size"].toInt(); + FileItem item{fileId, resJson["md5"].toString(), 0, size}; + QTimer::singleShot(0, qApp, [this, item]() { + FileTransferListModel::instance().insertItem(item); + });*/ + + auto fileUrl = "https://curl.se/download/curl-8.1.2.zip"; + + +/* curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Range: bytes=0-"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);*/ + + + int64_t size = getHttpFileSize(fileUrl); + FileItem item{fileId, fileId, 0, size}; + + auto res = httpDownload(fileUrl, item); + + + qDebug() << "End Get" << res; + }); + + +} + +void FileTransferManager::pause(const QString &fileId) +{ + transferMapMutex.lock(); + transferMap[fileId].paused = true; + transferMapMutex.unlock(); +} + +QString FileTransferManager::getFileMd5(const QUrl &fileUrl) +{ + QFile sourceFile = fileUrl.toLocalFile(); + + if (sourceFile.open(QIODevice::ReadOnly)) { + QCryptographicHash hash(QCryptographicHash::Md5); + QByteArray buffer; + while ((buffer = sourceFile.read(10240)).size() > 0) { + hash.addData(buffer); + } + + sourceFile.close(); + return {hash.result().toHex()}; + } + return {}; +} + +int64_t FileTransferManager::getFileSize(const QUrl &fileUrl) +{ + return QFile(fileUrl.toLocalFile()).size(); +} + +void FileTransferManager::resume(const QString &fileId) +{ + + QtConcurrent::run([fileId, this] { + qDebug() << "Start Get"; + + std::string resData; + if (CURLE_OK != httpGet("File/" + fileId.toStdString() + "/status", resData, CURL_HTTP_VERSION_1_1)) + return; + + qDebug() << resData.c_str(); + return; + auto resJson = QJsonDocument::fromJson(resData.c_str()); + //if(!resJson["isCompleted"].toBool()) // return; @@ -202,8 +351,14 @@ void FileTransferManager::download(QString fileId) auto fileUrl = "https://curl.se/download/curl-8.1.2.zip"; + +/* curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Range: bytes=0-"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);*/ + + int64_t size = getHttpFileSize(fileUrl); - FileItem item{fileId, resJson["md5"].toString(), 0, size}; + FileItem item{fileId, fileId, 0, size}; QTimer::singleShot(0, qApp, [this, item]() { FileTransferListModel::instance().insertItem(item); }); @@ -211,20 +366,8 @@ void FileTransferManager::download(QString fileId) auto res = httpDownload(fileUrl, item); - while (item.completedSize < item.totalSize && false) { - item.completedSize += 1; - QTimer::singleShot(0, qApp, [this, item]() { - FileTransferListModel::instance().setItem(item); - }); - - using namespace std::chrono_literals; - std::this_thread::sleep_for(100ms); - } - - qDebug() << "End Get" << res; }); - } diff --git a/AicsKnowledgeBase/src/FileTransferManager.h b/AicsKnowledgeBase/src/FileTransferManager.h index e7096f9..2ef71d0 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.h +++ b/AicsKnowledgeBase/src/FileTransferManager.h @@ -8,17 +8,26 @@ #include #include +#include #include "FileTransferListModel.h" + + class FileTransferManager : public QObject { Q_OBJECT public: explicit FileTransferManager(QObject *parent = nullptr); - Q_INVOKABLE void upload(QUrl fileUrl); + Q_INVOKABLE int64_t getFileSize(const QUrl& fileUrl); + Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl); + Q_INVOKABLE void upload(const QUrl& fileUrl, const QString& fileId, const QString& ticket); Q_INVOKABLE void download(QString fileId); + Q_INVOKABLE void pause(const QString& fileId); + Q_INVOKABLE void resume(const QString& fileId); + +private: }; From 0014cc018a1f857e426020956963c8f1b5f3a38f Mon Sep 17 00:00:00 2001 From: wuyize Date: Wed, 5 Jul 2023 21:49:04 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/component/FileList.qml | 95 ++++++++++++++------ AicsKnowledgeBase/qml/page/FilePage.qml | 42 +-------- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index e514a09..5e0fb06 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -1,6 +1,7 @@ import QtQuick 2.15 import QtQuick.Layouts import FluentUI +import QtQuick.Dialogs import "qrc:///AicsKnowledgeBase/qml/global" import SignalFileOperation 1.0 @@ -34,39 +35,81 @@ Item { Component { id: fileListItemHeader - Item { + + RowLayout { id: fileListItemHeaderItem width: ListView.view.width height: 48 - RowLayout { - FluBreadcrumbBar { - id: header - width: parent.width - height: parent.height - separator: ">" - items: [] - onClickItem: function (model) { - if (model.index + 1 !== count()) { - items = items.slice(0, model.index + 1) - } + + // back to folder button + FluIconButton { + Layout.alignment: Qt.AlignVCenter + id: backButton + width: 24 + height: 24 + iconSource: FluentIcons.ForwardCall + onClicked: { + if (header.count() > 0) { + header.items = header.items.slice(0, header.count() - 1) } - onItemsChanged: { - fileListItemHeaderItem.update() + fileListItemHeaderItem.update() + } + } + FluBreadcrumbBar { + id: header + //width: parent.width + height: parent.height + separator: ">" + items: [] + onClickItem: function (model) { + if (model.index + 1 !== count()) { + items = items.slice(0, model.index + 1) } } - // back to folder button - FluIconButton { - Layout.alignment: Qt.AlignVCenter - id: backButton - width: 24 - height: 24 - iconSource: FluentIcons.ChromeBack - onClicked: { - if (header.count() > 0) { - header.items = header.items.slice( - 0, header.count() - 1) + onItemsChanged: { + fileListItemHeaderItem.update() + } + } + + FluButton { + Layout.alignment: Qt.AlignRight + text: "上传" + onClicked: function () { + console.log("click") + fileDialog.open() + } + + FileDialog { + id: fileDialog + onAccepted: function () { + const size = FileTransferManager.getFileSize( + selectedFile) + const md5 = FileTransferManager.getFileMd5(selectedFile) + + if (size <= 0 || md5 === '') + return + + var body = { + "name": "test2", + "brief": "brief", + "size": size, + "md5": md5, + "tags": [], + "parentId": null } - fileListItemHeaderItem.update() + 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( + selectedFile, data.id, + data.ticket) + }, function (res, data) { + console.log(res) + }) } } } diff --git a/AicsKnowledgeBase/qml/page/FilePage.qml b/AicsKnowledgeBase/qml/page/FilePage.qml index 90aefd9..c43a418 100644 --- a/AicsKnowledgeBase/qml/page/FilePage.qml +++ b/AicsKnowledgeBase/qml/page/FilePage.qml @@ -23,48 +23,8 @@ FluArea { text: "" } - FluButton { - text: "上传" - onClicked: function () { - console.log("click") - fileDialog.open() - } - - FileDialog { - id: fileDialog - onAccepted: function () { - const size = FileTransferManager.getFileSize(selectedFile) - const md5 = FileTransferManager.getFileMd5(selectedFile) - - if (size <= 0 || md5 === '') - return - - var body = { - "name": "test2", - "brief": "brief", - "size": size, - "md5": md5, - "tags": [], - "parentId": null - } - 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(selectedFile, - data.id, - data.ticket) - }, function (res, data) { - console.log(res) - }) - } - } - } - FileList { autoRequest: true + width: parent.width } } From 5c1b23421c571e0e926976e20dc169b109c764f9 Mon Sep 17 00:00:00 2001 From: karlis <2995621482@qq.com> Date: Wed, 5 Jul 2023 22:31:14 +0800 Subject: [PATCH 4/7] =?UTF-8?q?FileList=E4=BF=AE=E6=94=B9=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/component/FileList.qml | 208 ++++++++++-------- .../qml/component/FileListItem.qml | 14 +- AicsKnowledgeBase/res/AUDIO.png | Bin 0 -> 5404 bytes AicsKnowledgeBase/res/OTHER.png | Bin 0 -> 6461 bytes 4 files changed, 125 insertions(+), 97 deletions(-) create mode 100644 AicsKnowledgeBase/res/AUDIO.png create mode 100644 AicsKnowledgeBase/res/OTHER.png diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index 5e0fb06..ec72db6 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -35,42 +35,14 @@ Item { Component { id: fileListItemHeader - - RowLayout { - id: fileListItemHeaderItem - width: ListView.view.width - height: 48 - - // back to folder button - FluIconButton { - Layout.alignment: Qt.AlignVCenter - id: backButton - width: 24 - height: 24 - iconSource: FluentIcons.ForwardCall - onClicked: { - if (header.count() > 0) { - header.items = header.items.slice(0, header.count() - 1) - } - fileListItemHeaderItem.update() - } + ColumnLayout { + function add(folderName, uuid) { + console.log(header.items) + header.items = header.items.concat([{ + "title": folderName, + "uuid": uuid + }]) } - FluBreadcrumbBar { - id: header - //width: parent.width - height: parent.height - separator: ">" - items: [] - onClickItem: function (model) { - if (model.index + 1 !== count()) { - items = items.slice(0, model.index + 1) - } - } - onItemsChanged: { - fileListItemHeaderItem.update() - } - } - FluButton { Layout.alignment: Qt.AlignRight text: "上传" @@ -78,28 +50,25 @@ Item { console.log("click") fileDialog.open() } - FileDialog { id: fileDialog onAccepted: function () { const size = FileTransferManager.getFileSize( selectedFile) const md5 = FileTransferManager.getFileMd5(selectedFile) - if (size <= 0 || md5 === '') return - var body = { "name": "test2", "brief": "brief", "size": size, "md5": md5, "tags": [], - "parentId": null + "parentId": header.items.length + !== 0 ? header.items[header.items.length - 1].uuid : null } console.log("begin") console.log(JSON.stringify(body)) - Request.post("knowledge/file", JSON.stringify(body), function (res, data) { console.log(res) @@ -113,64 +82,112 @@ Item { } } } + RowLayout { + id: fileListItemHeaderItem + width: ListView.view.width + height: 48 - function add(folderName, uuid) { - console.log(header.items) - header.items = header.items.concat([{ - "title": folderName, - "uuid": uuid - }]) - } - - function getType(suffix) { - if (suffix === "ppt" || suffix === "pptx") - return "PPT" - else if (suffix === "doc" || suffix === "docx") - return "WORD" - } - - function update() { - var uuid = header.items.length - === 0 ? "null" : header.items[header.items.length - 1].uuid - Request.get(uuid, function (response) { - var data = JSON.parse(response) - console.log(data.knowledgeFileAttribute) - fileListModel.clear() - var files = data.children - for (var i = 0; i < files.length; i++) { - var file = files[i] - console.log(file.name) - var modelItem = { - "title": file.name, - "uuid": file.id, - "date": file.createTime + // back to folder button + FluIconButton { + Layout.alignment: Qt.AlignVCenter + id: backButton + width: 24 + height: 24 + iconSource: FluentIcons.ForwardCall + onClicked: { + if (header.count() > 0) { + header.items = header.items.slice( + 0, header.count() - 1) } - if (file.knowledgeFileAttribute === null) { - modelItem.type = "folder" - modelItem.isDir = true - modelItem.size = 0 - } else { - modelItem.isDir = false - modelItem.type = getType( - file.knowledgeFileAttribute.suffix) - modelItem.size = file.knowledgeFileAttribute.size - modelItem.brief = file.knowledgeFileAttribute.brief - modelItem.pageView = file.knowledgeFileAttribute.pageView - modelItem.stars = 0 - // merge file.knowledgeFileAttribute.tags array to a string - var tagString = "" - for (var j = 0; j < file.knowledgeFileAttribute.tags.length; j++) { - if (j != 0) - tagString = tagString + "," - tagString = tagString + file.knowledgeFileAttribute.tags[j].name - } - modelItem.tags = tagString - } - fileListModel.append(modelItem) + fileListItemHeaderItem.update() } - console.log(fileListModel) - listView.currentIndex = -1 - }) + } + FluBreadcrumbBar { + id: header + width: parent.width + height: parent.height + separator: ">" + items: [] + onClickItem: function (model) { + if (model.index + 1 !== count()) { + items = items.slice(0, model.index + 1) + } + } + onItemsChanged: { + fileListItemHeaderItem.update() + } + } + + function getType(suffix) { + if (suffix === "ppt" || suffix === "pptx") + return "PPT" + else if (suffix === "doc" || suffix === "docx") + return "WORD" + else if (suffix === "pdf") + return "PDF" + else if (suffix === "txt") + return "TXT" + else if (suffix === "xls" || suffix === "xlsx") + return "EXCEL" + else if (suffix === "zip" || suffix === "rar") + return "ZIP" + else if (suffix === "png" || suffix === "jpg" + || suffix === "jpeg" || suffix === "gif") + return "IMAGE" + else if (suffix === "mp3" || suffix === "wav") + return "AUDIO" + else if (suffix === "mp4" || suffix === "avi" + || suffix === "rmvb" || suffix === "rm" + || suffix === "wmv" || suffix === "mkv") + return "VIDEO" + else + return "OTHER" + } + + function update() { + var uuid = header.items.length + === 0 ? "null" : header.items[header.items.length - 1].uuid + Request.get("/knowledge/" + uuid, function (response) { + var data = JSON.parse(response) + console.log(data.knowledgeFileAttribute) + fileListModel.clear() + var files = data.children + for (var i = 0; i < files.length; i++) { + var file = files[i] + console.log(file.name) + var modelItem = { + "title": file.name, + "uuid": file.id, + "date"// cut time after 'T' + : file.createTime.substring(0, 10) + } + if (file.knowledgeFileAttribute === null) { + modelItem.type = "folder" + modelItem.isDir = true + modelItem.size = 0 + } else { + modelItem.isDir = false + modelItem.type = getType( + file.knowledgeFileAttribute.suffix) + modelItem.size = file.knowledgeFileAttribute.size + modelItem.brief = file.knowledgeFileAttribute.brief + modelItem.pageView = file.knowledgeFileAttribute.pageView + modelItem.stars = 0 + // merge file.knowledgeFileAttribute.tags array to a string + var tagString = "" + for (var j = 0; j < file.knowledgeFileAttribute.tags.length; j++) { + if (j != 0) + tagString = tagString + "," + tagString = tagString + file.knowledgeFileAttribute.tags[j].name + } + modelItem.tags = tagString + } + fileListModel.append(modelItem) + } + console.log(fileListModel) + listView.currentIndex = -1 + }) + } } } } @@ -206,6 +223,7 @@ Item { function doubleClicked() { listView.currentIndex = index if (model.isDir) { + console.log(listView.headerItem) listView.headerItem.add(model.title, model.uuid) } else { emit: SignalFileOperation.open(model.uuid) diff --git a/AicsKnowledgeBase/qml/component/FileListItem.qml b/AicsKnowledgeBase/qml/component/FileListItem.qml index 8b8d5e4..7516c72 100644 --- a/AicsKnowledgeBase/qml/component/FileListItem.qml +++ b/AicsKnowledgeBase/qml/component/FileListItem.qml @@ -39,11 +39,13 @@ FluArea { Image { id: icon source: type ? "qrc:/AicsKnowledgeBase/res/" + type + ".png" : "" - Layout.preferredHeight: 18 - Layout.preferredWidth: 18 + Layout.preferredHeight: 32 + Layout.preferredWidth: 32 } FluText { id: title + font.bold: true + font.pointSize: 15 text: fileItem.title } } @@ -52,17 +54,23 @@ FluArea { id: brief visible: !fileItem.isDir text: fileItem.brief + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + elide: Text.ElideRight + maximumLineCount: 2 } RowLayout { id: infoRow visible: !fileItem.isDir FluText { id: date + color: "#5F5F5F" text: fileItem.date } FluText { id: size // cast Byte size to right text size + color: "#5F5F5F" text: fileItem.size > 1024 * 1024 ? (fileItem.size / 1024 / 1024).toFixed( 2) + "MB" : (fileItem.size / 1024).toFixed( @@ -70,10 +78,12 @@ FluArea { } FluText { id: pageView + color: "#5F5F5F" text: fileItem.pageView + "浏览" } FluText { id: stars + color: "#5F5F5F" text: fileItem.stars + "收藏" } } diff --git a/AicsKnowledgeBase/res/AUDIO.png b/AicsKnowledgeBase/res/AUDIO.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc20396579c9abd02e4dc8d41db6b6ea0cf3b6c GIT binary patch literal 5404 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 NPsa#fu5I_|e*k~3z=!|< literal 0 HcmV?d00001 diff --git a/AicsKnowledgeBase/res/OTHER.png b/AicsKnowledgeBase/res/OTHER.png new file mode 100644 index 0000000000000000000000000000000000000000..54a10d1853fc6441334c7560d42ccaf30d3004f8 GIT binary patch literal 6461 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 iv_M293~NAF Date: Wed, 5 Jul 2023 23:50:11 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/component/FileList.qml | 6 +- AicsKnowledgeBase/qml/page/FilePage.qml | 1 - .../src/FileTransferListModel.cpp | 11 +- AicsKnowledgeBase/src/FileTransferListModel.h | 4 +- AicsKnowledgeBase/src/FileTransferManager.cpp | 360 ++++++++++-------- AicsKnowledgeBase/src/FileTransferManager.h | 3 +- 6 files changed, 217 insertions(+), 168 deletions(-) diff --git a/AicsKnowledgeBase/qml/component/FileList.qml b/AicsKnowledgeBase/qml/component/FileList.qml index ec72db6..e992e3e 100644 --- a/AicsKnowledgeBase/qml/component/FileList.qml +++ b/AicsKnowledgeBase/qml/component/FileList.qml @@ -4,6 +4,7 @@ import FluentUI import QtQuick.Dialogs import "qrc:///AicsKnowledgeBase/qml/global" import SignalFileOperation 1.0 +import AicsKB.FileTransferManager Item { anchors.fill: parent @@ -53,13 +54,14 @@ Item { FileDialog { id: fileDialog onAccepted: function () { + let name = FileTransferManager.getFileName(selectedFile) const size = FileTransferManager.getFileSize( selectedFile) const md5 = FileTransferManager.getFileMd5(selectedFile) if (size <= 0 || md5 === '') return var body = { - "name": "test2", + "name": name, "brief": "brief", "size": size, "md5": md5, @@ -75,7 +77,7 @@ Item { console.log(data) FileTransferManager.upload( selectedFile, data.id, - data.ticket) + data.ticket, name) }, function (res, data) { console.log(res) }) diff --git a/AicsKnowledgeBase/qml/page/FilePage.qml b/AicsKnowledgeBase/qml/page/FilePage.qml index c43a418..57675d7 100644 --- a/AicsKnowledgeBase/qml/page/FilePage.qml +++ b/AicsKnowledgeBase/qml/page/FilePage.qml @@ -5,7 +5,6 @@ import QtQuick.Controls import QtQuick.Controls.Basic import QtQuick.Dialogs import FluentUI -import AicsKB.FileTransferManager import "qrc:///AicsKnowledgeBase/qml/component" import "qrc:///AicsKnowledgeBase/qml/global" diff --git a/AicsKnowledgeBase/src/FileTransferListModel.cpp b/AicsKnowledgeBase/src/FileTransferListModel.cpp index 67e9477..6cb46eb 100644 --- a/AicsKnowledgeBase/src/FileTransferListModel.cpp +++ b/AicsKnowledgeBase/src/FileTransferListModel.cpp @@ -3,20 +3,13 @@ FileTransferListModel::FileTransferListModel(QObject *parent) : QAbstractListModel(parent) { + m_roleName.insert(kDownloadRole, "download"); m_roleName.insert(kIdRole, "fileId"); m_roleName.insert(kNameRole, "name"); m_roleName.insert(kCompletedSizeRole, "completedSize"); m_roleName.insert(kTotalSizeRole, "totalSize"); m_roleName.insert(kSpeedRole, "speed"); m_roleName.insert(kPausedRole, "paused"); - - m_data = { - {"v", "vivo", 1200, 2000}, - {"m", "meizu", 1300, 1500}, - {"a", "apple", 1400, 1400}, - }; - - } @@ -36,6 +29,8 @@ QVariant FileTransferListModel::data(const QModelIndex &index, int role) const return QVariant(); switch (role) { + case kDownloadRole: + return m_data.value(index.row()).download; case kIdRole: return m_data.value(index.row()).id; case kNameRole: diff --git a/AicsKnowledgeBase/src/FileTransferListModel.h b/AicsKnowledgeBase/src/FileTransferListModel.h index 9fc0fdc..593ba04 100644 --- a/AicsKnowledgeBase/src/FileTransferListModel.h +++ b/AicsKnowledgeBase/src/FileTransferListModel.h @@ -7,6 +7,7 @@ struct FileItem { + bool download = true; QString id; QString name; int64_t completedSize; @@ -38,7 +39,8 @@ public: enum FileItemRole { - kIdRole = Qt::UserRole + 1, + kDownloadRole = Qt::UserRole + 1, + kIdRole, kNameRole, kCompletedSizeRole, kTotalSizeRole, diff --git a/AicsKnowledgeBase/src/FileTransferManager.cpp b/AicsKnowledgeBase/src/FileTransferManager.cpp index 55ef572..2f68638 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.cpp +++ b/AicsKnowledgeBase/src/FileTransferManager.cpp @@ -13,6 +13,88 @@ static const std::string baseUrl = "https://api.hammer-hfut.tk:233/aics/file/"; //static const std::string baseUrl = "http://127.0.0.1:4523/m1/2914957-0-6e5f2db1/"; //static const std::string baseUrl = "https://quic.nginx.org/"; +struct FileDownloading : FileItem +{ + CURL *curlHandle = nullptr; + std::ofstream file; +}; +std::unordered_map downloadingMap; +std::mutex downloadingMapMutex; + +struct FileUploading : FileItem +{ + CURL *curlHandle = nullptr; + std::ifstream file; +}; + +std::unordered_map uploadingMap; +std::mutex uploadingMapMutex; + +size_t writeDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) +{ + auto buf = (FileDownloading *) obj; + auto writeSize = size * nmemb; + buf->file.write(reinterpret_cast(ptr), writeSize); + return writeSize; +} + +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; +} + +static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + auto fileTrans = (FileDownloading *) p; + curl_off_t speed = 0; + + curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed); + + fileTrans->completedSize += dlnow; + + fileTrans->speed = speed; + qDebug() << std::format("Downloading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize, + speed).c_str(); + + auto item = static_cast(*fileTrans); + QTimer::singleShot(0, qApp, [item]() { + FileTransferListModel::instance().setItem(item); + }); + + if (fileTrans->paused) { + qDebug() << "Pause"; + return 1; + } + fileTrans->completedSize -= dlnow; + return 0; +} + +static int uploadInfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + auto fileTrans = (FileUploading *) p; + curl_off_t speed = 0; + curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_UPLOAD_T, &speed); + fileTrans->completedSize += ulnow; + fileTrans->speed = speed; + qDebug() << std::format("Uploading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize, + speed).c_str(); + + auto item = static_cast(*fileTrans); + QTimer::singleShot(0, qApp, [item]() { + FileTransferListModel::instance().setItem(item); + }); + + if (fileTrans->paused) { + qDebug() << "Pause"; + return 1; + } + fileTrans->completedSize -= ulnow; + return 0; +} + static size_t writeDataCallback(void *contents, size_t size, size_t nmemb, void *userp) { try { @@ -60,74 +142,88 @@ FileTransferManager::FileTransferManager(QObject *parent) : QObject(parent) { std::string resData; - if (CURLE_OK == httpGet("file/hello", resData)) + if (CURLE_OK == httpGet("hello", resData)) qDebug() << "HTTP3 OK\n" << resData.c_str(); else qDebug() << "HTTP3 FAILED"; - } -int64_t completedSize = 0; - -size_t readDataFunc(void *ptr, size_t size, size_t nmemb, void *file) -{ - auto s = reinterpret_cast(file)->read(reinterpret_cast(ptr), - size * nmemb); - completedSize += s; - qDebug() << std::format("Uploading: {} / {}", completedSize, - reinterpret_cast(file)->size()).c_str(); - return s; -} - -void FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket) +void +FileTransferManager::upload(const QUrl &fileUrl, const QString &fileId, const QString &ticket, const QString &fileName) { qDebug() << fileUrl.fileName(); - QFile file = fileUrl.toLocalFile(); - if (!file.open(QIODevice::ReadOnly)) - return; - auto fileSize = file.size(); + QtConcurrent::run([fileUrl, fileId, ticket, fileName] { - CURLcode res; - CURL *curl = curl_easy_init(); - if (!curl) - return; - curl_easy_setopt(curl, CURLOPT_URL, (baseUrl + "file/" + fileId.toStdString()).c_str()); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https"); - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY); - curl_httppost *formpost = nullptr; - curl_httppost *lastptr = nullptr; - curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "ticket", - CURLFORM_COPYCONTENTS, ticket.toStdString().c_str(), - CURLFORM_END); + auto fileSize = QFileInfo(fileUrl.toLocalFile()).size(); - curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeStart", - CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), - CURLFORM_END); - curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeEnd", - CURLFORM_COPYCONTENTS, std::to_string(fileSize).c_str(), - CURLFORM_END); + FileItem item{false, fileId, fileName, 0, fileSize}; + QTimer::singleShot(0, qApp, [item]() { + FileTransferListModel::instance().insertItem(item); + }); - curl_formadd(&formpost, &lastptr, - CURLFORM_COPYNAME, "data", - CURLFORM_STREAM, &file, - CURLFORM_CONTENTLEN, fileSize, - CURLFORM_END); - curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, readDataFunc); - qDebug() << "Begin Upload"; - completedSize = 0; - res = curl_easy_perform(curl); - if (res == CURLE_OK) - qDebug() << "Upload Success"; - else - qDebug() << "Upload Failed"; - curl_easy_cleanup(curl); - curl_formfree(formpost); - file.close(); + uploadingMapMutex.lock(); + auto [iter, _] = uploadingMap.emplace(item.id, (FileUploading) item); + uploadingMapMutex.unlock(); + + FileUploading &fileUploading = iter->second; + fileUploading.file = std::ifstream(fileUrl.toLocalFile().toLocal8Bit().toStdString(), std::ios::binary); + + if (!fileUploading.file.is_open()) { + qDebug() << "Open File Failed"; + return false; + } + + CURLcode res; + fileUploading.curlHandle = curl_easy_init(); + if (!fileUploading.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_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeStart", + CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), + CURLFORM_END); + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "rangeEnd", + CURLFORM_COPYCONTENTS, std::to_string(fileSize).c_str(), + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "data", + 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_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); + + qDebug() << "Begin Upload"; + + res = curl_easy_perform(fileUploading.curlHandle); + if (res == CURLE_OK) + qDebug() << "Upload Success"; + else + qDebug() << "Upload Failed"; + curl_easy_cleanup(fileUploading.curlHandle); + curl_formfree(formpost); + fileUploading.file.close(); + + uploadingMapMutex.lock(); + uploadingMap.erase(fileId); + uploadingMapMutex.unlock(); + + }); } @@ -151,104 +247,53 @@ curl_off_t getHttpFileSize(const std::string &url) return fileLength; } -struct FileTransfering : FileItem -{ - CURL *curlHandle = nullptr; - std::ofstream file; -}; - -size_t receiveDataFunc(const void *ptr, size_t size, size_t nmemb, void *obj) -{ - auto buf = (FileTransfering *) obj; - auto writeSize = size * nmemb; - buf->file.write(reinterpret_cast(ptr), writeSize); - return writeSize; -} - -static int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -{ - auto fileTrans = (FileTransfering *) p; - curl_off_t speed = 0; - - curl_easy_getinfo(fileTrans->curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed); - - fileTrans->completedSize += dlnow; - - fileTrans->speed = speed; - qDebug() << std::format("Downloading: {} / {}, Speed: {}", fileTrans->completedSize, fileTrans->totalSize, - speed).c_str(); - - auto item = static_cast(*fileTrans); - QTimer::singleShot(0, qApp, [item]() { - FileTransferListModel::instance().setItem(item); - }); - - if (fileTrans->paused) { - qDebug() << "Pause"; - return 1; - } - - - fileTrans->completedSize -= dlnow; - - - return 0; -} - - -std::unordered_map transferMap; - -std::mutex transferMapMutex; - bool httpDownload(const std::string &url, const FileItem &item) { auto fileSize = getHttpFileSize(url);//获得文件大小。 - if (fileSize != -1) { - qDebug() << "FileSize: " << fileSize; - - transferMapMutex.lock(); - auto [iter, _] = transferMap.emplace(item.id, (FileTransfering) item); - transferMapMutex.unlock(); - - FileTransfering &obj = iter->second; - - obj.curlHandle = curl_easy_init(); - if (!obj.curlHandle) - return false; - obj.totalSize = fileSize;//原始文件大小 - obj.file = std::ofstream("D:\\Downloads\\" + obj.name.toStdString() + ".zip", std::ios::binary); - - if (!obj.file.is_open()) { - qDebug() << "Open File Failed"; - curl_easy_cleanup(obj.curlHandle); - return false; - } else { - - curl_easy_setopt(obj.curlHandle, CURLOPT_VERBOSE, 0L); - curl_easy_setopt(obj.curlHandle, CURLOPT_HEADER, 0); - curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, 1L); - 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, receiveDataFunc); - curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, false);//设为false 下面才能设置进度响应函数 - curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFOFUNCTION, xferinfo); - curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFODATA, &obj); - - //下载 - auto res = curl_easy_perform(obj.curlHandle); - curl_easy_cleanup(obj.curlHandle); - obj.file.close(); - - transferMapMutex.lock(); - transferMap.erase(obj.id); - transferMapMutex.unlock(); - - return res == CURLE_OK; - } - } else + if (fileSize == -1) return false; + + qDebug() << "FileSize: " << fileSize; + + downloadingMapMutex.lock(); + auto [iter, _] = downloadingMap.emplace(item.id, (FileDownloading) item); + downloadingMapMutex.unlock(); + + FileDownloading &obj = iter->second; + + obj.curlHandle = curl_easy_init(); + if (!obj.curlHandle) + return false; + obj.totalSize = fileSize;//原始文件大小 + obj.file = std::ofstream("D:\\Downloads\\" + obj.name.toStdString() + ".zip", std::ios::binary); + + if (!obj.file.is_open()) { + qDebug() << "Open File Failed"; + curl_easy_cleanup(obj.curlHandle); + 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); + curl_easy_setopt(obj.curlHandle, CURLOPT_NOPROGRESS, false);//设为false 下面才能设置进度响应函数 + curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFOFUNCTION, xferinfo); + curl_easy_setopt(obj.curlHandle, CURLOPT_XFERINFODATA, &obj); + + //下载 + auto res = curl_easy_perform(obj.curlHandle); + curl_easy_cleanup(obj.curlHandle); + obj.file.close(); + + downloadingMapMutex.lock(); + downloadingMap.erase(obj.id); + downloadingMapMutex.unlock(); + + return res == CURLE_OK; } void FileTransferManager::download(QString fileId) @@ -285,7 +330,7 @@ void FileTransferManager::download(QString fileId) int64_t size = getHttpFileSize(fileUrl); - FileItem item{fileId, fileId, 0, size}; + FileItem item{true, fileId, fileId, 0, size}; auto res = httpDownload(fileUrl, item); @@ -298,9 +343,9 @@ void FileTransferManager::download(QString fileId) void FileTransferManager::pause(const QString &fileId) { - transferMapMutex.lock(); - transferMap[fileId].paused = true; - transferMapMutex.unlock(); + downloadingMapMutex.lock(); + downloadingMap[fileId].paused = true; + downloadingMapMutex.unlock(); } QString FileTransferManager::getFileMd5(const QUrl &fileUrl) @@ -358,7 +403,7 @@ void FileTransferManager::resume(const QString &fileId) int64_t size = getHttpFileSize(fileUrl); - FileItem item{fileId, fileId, 0, size}; + FileItem item{true, fileId, fileId, 0, size}; QTimer::singleShot(0, qApp, [this, item]() { FileTransferListModel::instance().insertItem(item); }); @@ -371,3 +416,8 @@ void FileTransferManager::resume(const QString &fileId) } +QString FileTransferManager::getFileName(const QUrl &fileUrl) +{ + return fileUrl.fileName(); +} + diff --git a/AicsKnowledgeBase/src/FileTransferManager.h b/AicsKnowledgeBase/src/FileTransferManager.h index 2ef71d0..256aec0 100644 --- a/AicsKnowledgeBase/src/FileTransferManager.h +++ b/AicsKnowledgeBase/src/FileTransferManager.h @@ -19,9 +19,10 @@ Q_OBJECT public: explicit FileTransferManager(QObject *parent = nullptr); + Q_INVOKABLE QString getFileName(const QUrl& fileUrl); Q_INVOKABLE int64_t getFileSize(const QUrl& fileUrl); Q_INVOKABLE QString getFileMd5(const QUrl& fileUrl); - Q_INVOKABLE void upload(const QUrl& fileUrl, const QString& fileId, const QString& ticket); + 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 pause(const QString& fileId); From 1a4d662e73252a89cfbfdd46b9347c1920f314c7 Mon Sep 17 00:00:00 2001 From: karlis <2995621482@qq.com> Date: Thu, 6 Jul 2023 14:00:27 +0800 Subject: [PATCH 6/7] =?UTF-8?q?ContenPage=E6=B7=BB=E5=8A=A0NoteList?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/qml/LoginWindow.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AicsKnowledgeBase/qml/LoginWindow.qml b/AicsKnowledgeBase/qml/LoginWindow.qml index 743f2a5..9ff5ccd 100644 --- a/AicsKnowledgeBase/qml/LoginWindow.qml +++ b/AicsKnowledgeBase/qml/LoginWindow.qml @@ -93,8 +93,8 @@ AppFluWindow { text: "登录" onClicked: { var param = { - "username": account.text, - "password": password.text + "username": "admin", + "password": "123456" } btn_login.disabled = true btn_login.text = "登录中" From 428ee6fafef1a499358001a3b93d5a8f34e6d1d3 Mon Sep 17 00:00:00 2001 From: karlis <2995621482@qq.com> Date: Thu, 6 Jul 2023 14:00:55 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=20ContenPage=E6=B7=BB=E5=8A=A0NoteList?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AicsKnowledgeBase/NoteListWindow.qml | 5 ++ AicsKnowledgeBase/qml/component/NoteList.qml | 84 +++++++++++++------ AicsKnowledgeBase/qml/page/ContentPage.qml | 51 ++++++++++- AicsKnowledgeBase/res/note.png | Bin 0 -> 6673 bytes 4 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 AicsKnowledgeBase/NoteListWindow.qml create mode 100644 AicsKnowledgeBase/res/note.png diff --git a/AicsKnowledgeBase/NoteListWindow.qml b/AicsKnowledgeBase/NoteListWindow.qml new file mode 100644 index 0000000..5560aee --- /dev/null +++ b/AicsKnowledgeBase/NoteListWindow.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/AicsKnowledgeBase/qml/component/NoteList.qml b/AicsKnowledgeBase/qml/component/NoteList.qml index c5010c1..539c917 100644 --- a/AicsKnowledgeBase/qml/component/NoteList.qml +++ b/AicsKnowledgeBase/qml/component/NoteList.qml @@ -6,6 +6,46 @@ import "qrc:///AicsKnowledgeBase/qml/global" Item { anchors.fill: parent signal open(string note) + signal createNote + width: parent.width - 8 + property ListModel noteListModel: ListModel { + ListElement { + uuid: "1" + title: "超级无敌报错" + brief: "file:///D:/academic/2023-qtBig/AicsKnowledgeBase_client/AicsKnowledgeBase/qml/component/NoteList.qml:41:21: Unable to assign [undefined] to QString" + author: "admin" + pageView: 123 + stars: 27 + date: "2022-02-02" + } + ListElement { + uuid: "2" + title: "Qt布局" + brief: "锚定(anchors)在确定父子组件之间,同级组件之间的相对位置时非常常用,若使用锚定方式确定子组件与父组件之间的位置关系,使用 top,bottom,left,right, topMargin,bottomMargin,leftMargin,rightMargin进行上下左右对齐,以及对齐后的留白距离。若要使子组件在父组件的水平,垂直居中,使用:" + author: "超级无敌长的账户名" + pageView: 123 + stars: 27 + date: "2022-02-02" + } + ListElement { + uuid: "2" + title: "Qt布局" + brief: "锚定(anchors)在确定父子组件之间,同级组件之间的相对位置时非常常用,若使用锚定方式确定子组件与父组件之间的位置关系,使用 top,bottom,left,right, topMargin,bottomMargin,leftMargin,rightMargin进行上下左右对齐,以及对齐后的留白距离。若要使子组件在父组件的水平,垂直居中,使用:" + author: "超级无敌长的账户名" + pageView: 123 + stars: 27 + date: "2022-02-02" + } + ListElement { + uuid: "2" + title: "Qt布局" + brief: "锚定(anchors)在确定父子组件之间,同级组件之间的相对位置时非常常用,若使用锚定方式确定子组件与父组件之间的位置关系,使用 top,bottom,left,right, topMargin,bottomMargin,leftMargin,rightMargin进行上下左右对齐,以及对齐后的留白距离。若要使子组件在父组件的水平,垂直居中,使用:" + author: "超级无敌长的账户名" + pageView: 123 + stars: 27 + date: "2022-02-02" + } + } ListView { id: listView anchors.fill: parent @@ -18,11 +58,24 @@ Item { id: noteListItemHeader Item { id: noteListItemHeaderItem - // width: ListView.view.width - // height: 48 - // FluText { - // text: "笔记" - // } + width: ListView.view.width + height: 48 + RowLayout { + width: parent.width + FluText { + text: "笔记列表" + font.pixelSize: 18 + font.bold: true + } + FluButton { + id: createNoteButton + text: "新建笔记" + Layout.alignment: Qt.AlignRight + onClicked: { + createNote() + } + } + } } } Component { @@ -54,25 +107,4 @@ Item { function setListModel(listModel) { listView.model = listModel } - ListModel { - id: noteListModel - ListElement { - uuid: "1" - title: "超级无敌报错" - brief: "file:///D:/academic/2023-qtBig/AicsKnowledgeBase_client/AicsKnowledgeBase/qml/component/NoteList.qml:41:21: Unable to assign [undefined] to QString" - author: "admin" - pageView: 123 - stars: 27 - date: "2022-02-02" - } - ListElement { - uuid: "2" - title: "Qt布局" - brief: "锚定(anchors)在确定父子组件之间,同级组件之间的相对位置时非常常用,若使用锚定方式确定子组件与父组件之间的位置关系,使用 top,bottom,left,right, topMargin,bottomMargin,leftMargin,rightMargin进行上下左右对齐,以及对齐后的留白距离。若要使子组件在父组件的水平,垂直居中,使用:" - author: "超级无敌长的账户名" - pageView: 123 - stars: 27 - date: "2022-02-02" - } - } } diff --git a/AicsKnowledgeBase/qml/page/ContentPage.qml b/AicsKnowledgeBase/qml/page/ContentPage.qml index 90bf486..fccba38 100644 --- a/AicsKnowledgeBase/qml/page/ContentPage.qml +++ b/AicsKnowledgeBase/qml/page/ContentPage.qml @@ -8,8 +8,34 @@ import FluentUI import AicsKB.FileTransferManager import SignalFileOperation 1.0 import "qrc:///AicsKnowledgeBase/qml/page" +import "qrc:///AicsKnowledgeBase/qml/component" FluArea { + Popup { + id: popup + modal: true //模态, 为 true后弹出窗口会叠加一个独特的背景调光效果 + focus: true //焦点, 当弹出窗口实际接收到焦点时,activeFocus将为真 + padding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + property var raiseItem: null + background: Rectangle { + color: Qt.rgba(0, 0, 0, 0) //背景为无色 + } + FluArea { + width: 350 + height: 500 + backgroundColor: "#f9f9f9" + NoteList { + id: noteList + onOpen: function handle(noteId) { + console.log("open note: " + noteId) + } + onCreateNote: function handle() { + console.log("create note") + } + } + } + } id: content_area paddings: 0 backgroundColor: "#f9f9f9" @@ -76,6 +102,28 @@ FluArea { bold: true } } + ColumnLayout { + id: layout_note + FluIconButton { + id: button_note + iconSize: 15 + iconSource: FluentIcons.QuickNote + text: "笔记" + onClicked: { + popup.open() + } + } + FluText { + text: "" + font.pointSize: 8 + Layout.alignment: button_note.Center + Layout.topMargin: -5 + } + anchors { + verticalCenter: text_title.verticalCenter + right: parent.right + } + } ColumnLayout { id: layout_share FluIconButton { @@ -93,7 +141,7 @@ FluArea { } anchors { verticalCenter: text_title.verticalCenter - right: parent.right + right: layout_note.left } } ColumnLayout { @@ -157,6 +205,7 @@ FluArea { implicitHeight: 100 ColumnLayout { RowLayout { + width: parent.width FluText { padding: 10 text: content_page.publishTime.toDateString() diff --git a/AicsKnowledgeBase/res/note.png b/AicsKnowledgeBase/res/note.png new file mode 100644 index 0000000000000000000000000000000000000000..348ce7706e1c0b59dd130c24a0f1ef0d00fa9e3f GIT binary patch literal 6673 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 q8!NV+u`xpU)9%K_|6@w$$*G#I;*