diff --git a/AicsKnowledgeBase/CMakeLists.txt b/AicsKnowledgeBase/CMakeLists.txt index 20a2bb9..852abc6 100644 --- a/AicsKnowledgeBase/CMakeLists.txt +++ b/AicsKnowledgeBase/CMakeLists.txt @@ -4,6 +4,7 @@ project(AicsKnowledgeBase LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 20) find_package(Qt6 COMPONENTS Quick REQUIRED) @@ -22,7 +23,7 @@ foreach (filepath ${QML_PATHS}) endforeach (filepath) #遍历所有资源文件 -file(GLOB_RECURSE RES_PATHS res/*.png res/*.jpg res/*.svg res/*.ico res/*.ttf res/*.webp res/qmldir) +file(GLOB_RECURSE RES_PATHS res/*.png res/*.jpg res/*.svg res/*.ico res/*.ttf res/*.webp qmldir) foreach (filepath ${RES_PATHS}) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) list(APPEND resource_files ${filename}) @@ -62,4 +63,4 @@ file(GLOB MSQUIC_BIN_FILES ../libcurl/dll/*) add_custom_command(TARGET AicsKnowledgeBase POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${MSQUIC_BIN_FILES} ${CMAKE_CURRENT_BINARY_DIR} - ) \ No newline at end of file + ) diff --git a/AicsKnowledgeBase/qml/App.qml b/AicsKnowledgeBase/qml/App.qml index cd058a1..8631e3d 100644 --- a/AicsKnowledgeBase/qml/App.qml +++ b/AicsKnowledgeBase/qml/App.qml @@ -10,9 +10,10 @@ Window { FluApp.init(app); FluTheme.darkMode = FluDarkMode.System; FluApp.routes = { + "/login": "qrc:/AicsKnowledgeBase/qml/LoginWindow.qml", "/": "qrc:/AicsKnowledgeBase/qml/MainWindow.qml" }; - FluApp.initialRoute = "/"; + FluApp.initialRoute = "/login"; FluApp.run(); } } diff --git a/AicsKnowledgeBase/qml/AppFluWindow.qml b/AicsKnowledgeBase/qml/AppFluWindow.qml new file mode 100644 index 0000000..ed1e6d9 --- /dev/null +++ b/AicsKnowledgeBase/qml/AppFluWindow.qml @@ -0,0 +1,71 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI +import org.wangwenx190.FramelessHelper + +FluWindow { + property bool appBarVisible: true + + function setHitTestVisible(com) { + framless_helper.setHitTestVisible(com) + } + function setTitleBarItem(com) { + framless_helper.setTitleBarItem(com) + } + id: window + //color: "transparent" + + + FluAppBar { + id: title_bar + title: window.title + visible: appBarVisible + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + } + /*FluIconButton { + id: refreshButton + height: 30 + iconSize: 13 + iconSource: FluentIcons.Sync + width: 30 + z: 8 + + onClicked: { + showSuccess("reload") + loader.reload() + } + + anchors { + top: title_bar.bottom + } + }*/ + FramelessHelper { + id: framless_helper + onReady: { + setTitleBarItem(title_bar) + moveWindowToDesktopCenter() + setHitTestVisible(title_bar.minimizeButton()) + setHitTestVisible(title_bar.maximizeButton()) + setHitTestVisible(title_bar.closeButton()) + //setHitTestVisible(refreshButton) + title_bar.maximizeButton.visible = true + //if (blurBehindWindowEnabled) + // window.backgroundVisible = false + window.show() + } + } + + /* FluRemoteLoader { + id: loader + anchors.fill: parent + source: "file:///D:\\Courses\\Qt\\AicsKnowledgeBase_client\\AicsKnowledgeBase\\qml\\LoginPage.qml" + }*/ +} diff --git a/AicsKnowledgeBase/qml/LoginPage.qml b/AicsKnowledgeBase/qml/LoginPage.qml deleted file mode 100644 index 7d6cd0d..0000000 --- a/AicsKnowledgeBase/qml/LoginPage.qml +++ /dev/null @@ -1,98 +0,0 @@ -import QtQuick -import FluentUI -import QtQuick.Controls -import QtQuick.Window -import QtQuick.Layouts -import org.wangwenx190.FramelessHelper -import AicsKB.HttpClient - -Row { - anchors.fill: parent - - Rectangle { - id: introdutcitonItem - height: parent.height - width: parent.width * 0.5 - color: "#f3fbff" - - //color: "transparent" - Image { - anchors.fill: parent - fillMode: Image.PreserveAspectFit - source: "qrc:/AicsKnowledgeBase/res/login_background.webp" - } - } - - ColumnLayout { - id: loginRect - width: parent.width * 0.5 - height: parent.height - FluPivot { - - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - Layout.margins: 20 - height: 200 - - FluPivotItem { - id: loginItem - title: "登录" - - function login(s) - { - showSuccess(s); - } - - contentItem: Column { - anchors.margins: { - top: 10 - } - anchors.top: parent.top - spacing: 12 - - FluTextBox { - anchors.left: parent.left - anchors.right: parent.right - id: account - placeholderText: "用户名" - } - FluPasswordBox { - anchors.left: parent.left - anchors.right: parent.right - id: password - placeholderText: "密码" - } - RowLayout { - anchors.left: parent.left - anchors.right: parent.right - FluCheckBox { - Layout.alignment: Qt.AlignLeft - //anchors.left: parent.left - text: "记住密码" - } - FluTextButton { - Layout.alignment: Qt.AlignRight - //anchors.right: parent.right - text: "忘记密码?" - } - } - FluFilledButton { - anchors.horizontalCenter: parent.horizontalCenter - normalColor: FluColors.Green.normal - text: "登录" - onClicked: { - HttpClient.doGetRequest("https://quic.aiortc.org/", loginItem, "login"); - } - } - } - } - FluPivotItem { - title: "注册" - // Rectangle{ - // anchors.fill: parent - // color: "blue" - // } - } - } - } -} diff --git a/AicsKnowledgeBase/qml/LoginWindow.qml b/AicsKnowledgeBase/qml/LoginWindow.qml new file mode 100644 index 0000000..a7dbc86 --- /dev/null +++ b/AicsKnowledgeBase/qml/LoginWindow.qml @@ -0,0 +1,114 @@ +import QtQuick +import FluentUI +import QtQuick.Controls +import QtQuick.Window +import QtQuick.Layouts +import org.wangwenx190.FramelessHelper +import AicsKB.HttpClient + +AppFluWindow { + id: window + height: 420 + title: "智能客服知识库" + width: 640 + + Row { + anchors.fill: parent + + Rectangle { + id: introdutcitonItem + height: parent.height + width: parent.width * 0.5 + color: "#f3fbff" + + //color: "transparent" + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: "qrc:/AicsKnowledgeBase/res/login_background.webp" + } + } + + Rectangle{ + width: parent.width * 0.5 + height: parent.height + color: "#f3f3f3" + ColumnLayout { + id: loginRect + anchors.fill: parent + FluPivot { + + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + Layout.margins: 20 + height: 200 + + FluPivotItem { + id: loginItem + title: "登录" + + function login(s) { + showSuccess(s) + } + + contentItem: Column { + anchors.margins: { + top: 10 + } + anchors.top: parent.top + spacing: 12 + + FluTextBox { + anchors.left: parent.left + anchors.right: parent.right + id: account + placeholderText: "用户名" + } + FluPasswordBox { + anchors.left: parent.left + anchors.right: parent.right + id: password + placeholderText: "密码" + } + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + FluCheckBox { + Layout.alignment: Qt.AlignLeft + //anchors.left: parent.left + text: "记住密码" + } + FluTextButton { + Layout.alignment: Qt.AlignRight + //anchors.right: parent.right + text: "忘记密码?" + } + } + FluFilledButton { + anchors.horizontalCenter: parent.horizontalCenter + //normalColor: FluColors.Green.normal + //normalColor: "#ffffff" + text: "登录" + onClicked: { + FluApp.navigate("/"); + window.close(); + //HttpClient.doGetRequest( + // "https://quic.aiortc.org/", + // loginItem, "login") + } + } + } + } + // FluPivotItem { + // title: "注册" + // Rectangle{ + // anchors.fill: parent + // color: "blue" + // } + //} + } + } + } + + } +} diff --git a/AicsKnowledgeBase/qml/MainWindow.qml b/AicsKnowledgeBase/qml/MainWindow.qml index ff8fdcf..262e2d2 100644 --- a/AicsKnowledgeBase/qml/MainWindow.qml +++ b/AicsKnowledgeBase/qml/MainWindow.qml @@ -5,50 +5,68 @@ import QtQuick.Controls import QtQuick.Controls.Basic import FluentUI import org.wangwenx190.FramelessHelper +import "qrc:///AicsKnowledgeBase/qml/global" FluWindow { - property bool appBarVisible: true - - function setHitTestVisible(com) { - framless_helper.setHitTestVisible(com) - } - function setTitleBarItem(com) { - framless_helper.setTitleBarItem(com) - } id: window - //color: "transparent" - height: 420 - title: "智能客服知识库" - width: 640 + width: 1000 + height: 640 FluAppBar { id: title_bar title: window.title - visible: appBarVisible anchors { left: parent.left right: parent.right top: parent.top + leftMargin: 35 } } - /*FluIconButton { - id: refreshButton - height: 30 - iconSize: 13 - iconSource: FluentIcons.Sync - width: 30 - z: 8 - onClicked: { - showSuccess("reload") - loader.reload() + RowLayout { + anchors.fill: parent + + FluNavigationView { + id: nav_view + Layout.fillHeight: true + Layout.preferredWidth: 400 + Layout.bottomMargin: 4 + z: 999 + items: NavItems + footerItems: FooterItems + topPadding: 5 + displayMode: FluNavigationView.Compact + logo: "qrc:/AicsKnowledgeBase/res/logo.png" + title: "智能客服知识库" + Behavior on rotation { + NumberAnimation { + duration: 167 + } + } + transformOrigin: Item.Center + Component.onCompleted: { + NavItems.navigationView = nav_view + FooterItems.navigationView = nav_view + setCurrentIndex(0) + } } - anchors { - top: title_bar.bottom + FluArea { + Layout.fillHeight: true + Layout.fillWidth: true + paddings: 10 + Layout.topMargin: 45 + Layout.bottomMargin: 4 + Layout.rightMargin: 4 + + FluText { + Layout.topMargin: 20 + text: "Content" + } } - }*/ + } + FramelessHelper { id: framless_helper onReady: { @@ -57,20 +75,12 @@ FluWindow { setHitTestVisible(title_bar.minimizeButton()) setHitTestVisible(title_bar.maximizeButton()) setHitTestVisible(title_bar.closeButton()) - //setHitTestVisible(refreshButton) title_bar.maximizeButton.visible = true - //if (blurBehindWindowEnabled) // window.backgroundVisible = false window.show() } } - /* FluRemoteLoader { - id: loader - anchors.fill: parent - source: "file:///D:\\Courses\\Qt\\AicsKnowledgeBase_client\\AicsKnowledgeBase\\qml\\LoginPage.qml" - }*/ - LoginPage { - anchors.fill: parent - } + } + diff --git a/AicsKnowledgeBase/qml/global/FooterItems.qml b/AicsKnowledgeBase/qml/global/FooterItems.qml new file mode 100644 index 0000000..f051bfc --- /dev/null +++ b/AicsKnowledgeBase/qml/global/FooterItems.qml @@ -0,0 +1,36 @@ +pragma Singleton + +import QtQuick +import FluentUI + +FluObject { + + property var navigationView + + FluPaneItemSeparator{} + + FluPaneItem { + title: "我的收藏" + icon: FluentIcons.FavoriteList + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/FavoritePage.qml") + } + } + + FluPaneItem { + title: "浏览历史" + icon: FluentIcons.History + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/HistoryPage.qml") + } + } + + FluPaneItem { + title: "已下载" + icon: FluentIcons.Download + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/LocalPage.qml") + } + } + +} diff --git a/AicsKnowledgeBase/qml/global/NavItems.qml b/AicsKnowledgeBase/qml/global/NavItems.qml new file mode 100644 index 0000000..f56ac79 --- /dev/null +++ b/AicsKnowledgeBase/qml/global/NavItems.qml @@ -0,0 +1,53 @@ +pragma Singleton + +import QtQuick +import FluentUI + +FluObject { + + property var navigationView + + FluPaneItem { + title: "探索" + icon: FluentIcons.Home + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/HomePage.qml") + } + } + + FluPaneItem { + title: "全部" + icon: FluentIcons.FileExplorer + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/FilePage.qml") + } + } + + FluPaneItem { + title: "文档" + icon: FluentIcons.Document + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/DocumentPage.qml") + } + } + + FluPaneItem { + title: "视频" + icon: FluentIcons.Movies + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/VideoPage.qml") + } + } + + FluPaneItem { + title: "音频" + icon: FluentIcons.Audio + onTap: { + navigationView.push("qrc:/AicsKnowledgeBase/qml/page/AudioPage.qml") + } + } + + function startPageByItem(data) { + navigationView.startPageByItem(data) + } +} diff --git a/AicsKnowledgeBase/qml/global/qmldir b/AicsKnowledgeBase/qml/global/qmldir new file mode 100644 index 0000000..5878107 --- /dev/null +++ b/AicsKnowledgeBase/qml/global/qmldir @@ -0,0 +1,2 @@ +singleton NavItems 1.0 NavItems.qml +singleton FooterItems 1.0 FooterItems.qml diff --git a/AicsKnowledgeBase/qml/page/AudioPage.qml b/AicsKnowledgeBase/qml/page/AudioPage.qml new file mode 100644 index 0000000..3b606c4 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/AudioPage.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text:"Audio" + } + +} diff --git a/AicsKnowledgeBase/qml/page/DocumentPage.qml b/AicsKnowledgeBase/qml/page/DocumentPage.qml new file mode 100644 index 0000000..a7d88b1 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/DocumentPage.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text:"Document" + } + +} diff --git a/AicsKnowledgeBase/qml/page/FavoritePage.qml b/AicsKnowledgeBase/qml/page/FavoritePage.qml new file mode 100644 index 0000000..756d4e9 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/FavoritePage.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text: "Favorite" + } + +} + diff --git a/AicsKnowledgeBase/qml/page/FilePage.qml b/AicsKnowledgeBase/qml/page/FilePage.qml new file mode 100644 index 0000000..28977da --- /dev/null +++ b/AicsKnowledgeBase/qml/page/FilePage.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text: "File" + } + +} + diff --git a/AicsKnowledgeBase/qml/page/HistoryPage.qml b/AicsKnowledgeBase/qml/page/HistoryPage.qml new file mode 100644 index 0000000..73663e6 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/HistoryPage.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text: "History" + } + +} + diff --git a/AicsKnowledgeBase/qml/page/HomePage.qml b/AicsKnowledgeBase/qml/page/HomePage.qml new file mode 100644 index 0000000..b0d63b7 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/HomePage.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text:"Home" + } + +} + +//FluScrollablePage{ +// Rectangle { +// Layout.fillWidth: true +// height: 200 +// color: "red" +// } +//} + + diff --git a/AicsKnowledgeBase/qml/page/LocalPage.qml b/AicsKnowledgeBase/qml/page/LocalPage.qml new file mode 100644 index 0000000..54ea6aa --- /dev/null +++ b/AicsKnowledgeBase/qml/page/LocalPage.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text: "Local" + } + +} + diff --git a/AicsKnowledgeBase/qml/page/VideoPage.qml b/AicsKnowledgeBase/qml/page/VideoPage.qml new file mode 100644 index 0000000..639f180 --- /dev/null +++ b/AicsKnowledgeBase/qml/page/VideoPage.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI + +FluArea{ + property string url: '' + + Layout.fillWidth: true + Layout.fillHeight: true + paddings: 10 + Layout.topMargin: 20 + + FluText{ + Layout.topMargin: 20 + text:"Video" + } + +} diff --git a/AicsKnowledgeBase/res/logo.png b/AicsKnowledgeBase/res/logo.png new file mode 100644 index 0000000..25cbd4a Binary files /dev/null and b/AicsKnowledgeBase/res/logo.png differ diff --git a/AicsKnowledgeBase/src/HttpClient.cpp b/AicsKnowledgeBase/src/HttpClient.cpp index 3a97580..80083ee 100644 --- a/AicsKnowledgeBase/src/HttpClient.cpp +++ b/AicsKnowledgeBase/src/HttpClient.cpp @@ -3,6 +3,8 @@ // #include "HttpClient.h" +#include +#include HttpClient::HttpClient(QQmlApplicationEngine &engine, QObject *parent) : engine(engine), QObject(parent) @@ -11,28 +13,40 @@ HttpClient::HttpClient(QQmlApplicationEngine &engine, QObject *parent) void HttpClient::doGetRequest(QString url, QObject *object, QString callback) { - CURL *curl; - CURLcode res; - - curl = curl_easy_init(); - if(curl) { - curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); - - /* Use HTTP/3 but fallback to earlier HTTP if necessary */ - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, - (long)CURL_HTTP_VERSION_3); - - /* Perform the request, res will get the return code */ - res = curl_easy_perform(curl); - + auto *watcher = new QFutureWatcher; + connect(watcher, &QFutureWatcher::finished, this, [watcher, object, callback] { + auto res = watcher->future().result(); + delete watcher; + qDebug() << (res == CURLE_OK ? "success" : "fail"); QVariant r; QMetaObject::invokeMethod(object, callback.toStdString().c_str(), Q_RETURN_ARG(QVariant, r), - Q_ARG(QVariant, res==CURLE_OK? "success":"fail")); + Q_ARG(QVariant, res == CURLE_OK ? "success" : "fail")); + }); - /* always cleanup */ - curl_easy_cleanup(curl); - } + watcher->setFuture(QtConcurrent::run([url] { + qDebug() << "Start Get"; + CURL *curl; + CURLcode res; + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); + + /* Use HTTP/3 but fallback to earlier HTTP if necessary */ + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, + (long) CURL_HTTP_VERSION_3); + + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + + /* always cleanup */ + curl_easy_cleanup(curl); + + } + qDebug() << "End Get"; + return res; + })); } + diff --git a/AicsKnowledgeBase/src/main.cpp b/AicsKnowledgeBase/src/main.cpp index 092fd85..4ab03f6 100644 --- a/AicsKnowledgeBase/src/main.cpp +++ b/AicsKnowledgeBase/src/main.cpp @@ -34,7 +34,7 @@ int main(int argc, char* argv[]) qmlRegisterSingletonInstance("AicsKB.HttpClient", 1, 0, "HttpClient", httpClient); - const QUrl url(u"qrc:/AicsKnowledgeBase/qml/App.qml"_qs); + const QUrl url(u"qrc:/AicsKnowledgeBase/qml/App.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject* obj, const QUrl& objUrl) { if (!obj && url == objUrl) diff --git a/README.md b/README.md index 28a237e..c188ac2 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,11 @@ # AicsKnowledgeBase - 智能客服知识库 - 提供客服场景统一、便捷的知识管理,实现对客服常用知识的统一存储、更新; 提供客户快速检索,帮助客户解决在知识生产-管理-获取-应用流程中的需求 和问题,快速沉淀知识。 -1、要求采用UDP协议、支持断点续传。 - -2、支持支持常见知识录入,如pdf/word、ppt,音视频等,支持知识导入、导出。支持多级目录、多级标签整理知识。 - -3、支持常见知识的快速检索、热点知识分类呈现。 - -4、能够基于某个知识内容进行知识分享、知识评论等。 +1. 要求采用UDP协议、支持断点续传。 +2. 支持支持常见知识录入,如pdf/word、ppt,音视频等,支持知识导入、导出。支持多级目录、多级标签整理知识。 +3. 支持常见知识的快速检索、热点知识分类呈现。 +4. 能够基于某个知识内容进行知识分享、知识评论等。 ## 需求