diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 8f01088..77ab369 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(geopro_desktop WIN32 panels/chart/BarChartView.cpp panels/chart/LineChartView.cpp panels/chart/TrajectoryMapView.cpp + panels/web/ProjectWebView.cpp panels/chart/DetailViewFactory.cpp resources/map/map.qrc resources/keys.qrc diff --git a/src/app/TopBar.cpp b/src/app/TopBar.cpp index f337438..aba8d82 100644 --- a/src/app/TopBar.cpp +++ b/src/app/TopBar.cpp @@ -119,48 +119,6 @@ QPixmap renderAvatar(const QString& initials, int px, const QColor& bg, const QC } // ── 四个菜单(结构对齐需求;叶子项当前为静态占位,后续接真实页面)── -QMenu* buildViewMenu(QWidget* p) -{ - auto* m = new QMenu(QStringLiteral("视图"), p); - m->addAction(QStringLiteral("分析视图")); - m->addAction(QStringLiteral("大屏视图")); - return m; -} - -QMenu* buildProjectMenu(QWidget* p) -{ - auto* m = new QMenu(QStringLiteral("项目管理"), p); - m->addAction(QStringLiteral("数据视图")); - auto* cfg = m->addMenu(QStringLiteral("项目配置")); - cfg->addAction(QStringLiteral("基本信息")); - cfg->addAction(QStringLiteral("项目结构")); - cfg->addAction(QStringLiteral("视图配置")); - m->addAction(QStringLiteral("数据管理")); - auto* biz = m->addMenu(QStringLiteral("业务管理")); - biz->addAction(QStringLiteral("异常管理")); - biz->addAction(QStringLiteral("异常体管理")); - auto* mon = m->addMenu(QStringLiteral("在线监测")); - mon->addAction(QStringLiteral("项目设备")); - mon->addAction(QStringLiteral("在线任务管理")); - auto* doc = m->addMenu(QStringLiteral("项目资料管理")); - doc->addAction(QStringLiteral("项目资料管理")); - doc->addAction(QStringLiteral("报告列表")); - auto* tools = m->addMenu(QStringLiteral("工具组件")); - tools->addAction(QStringLiteral("装置与脚本")); - tools->addAction(QStringLiteral("色阶配置")); - tools->addAction(QStringLiteral("异常类型管理")); - tools->addAction(QStringLiteral("模型管理")); - auto* exp = m->addMenu(QStringLiteral("批量导出")); - exp->addAction(QStringLiteral("文件导出")); - exp->addAction(QStringLiteral("报告导出")); - auto* alarm = m->addMenu(QStringLiteral("告警管理")); - alarm->addAction(QStringLiteral("设备告警")); - alarm->addAction(QStringLiteral("告警查询")); - m->addAction(QStringLiteral("自动任务")); - m->addAction(QStringLiteral("模板管理")); - return m; -} - QMenu* buildToolsMenu(QWidget* p) { auto* m = new QMenu(QStringLiteral("业务工具"), p); @@ -259,8 +217,8 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) { // 一级菜单 → 工具条按钮(视图/项目管理/业务工具/设备),纯文字 + 下拉箭头。 // 复用原菜单构造器;菜单作为 popup 挂到按钮(按钮文字取菜单标题)。 - lay->addWidget(makeMenuButton(this, buildViewMenu(this))); - lay->addWidget(makeMenuButton(this, buildProjectMenu(this))); + lay->addWidget(makeMenuButton(this, buildViewMenu())); + lay->addWidget(makeMenuButton(this, buildProjectMenu())); lay->addWidget(makeMenuButton(this, buildToolsMenu(this))); lay->addWidget(makeMenuButton(this, buildDeviceMenu(this))); @@ -332,6 +290,49 @@ TopBar::TopBar(QWidget* parent) : QWidget(parent) { lay->addWidget(userRow_); } +// 视图菜单。「分析视图」=默认工作台(emit analysisViewRequested,中央区从 web 整窗切回工作台); +// 「大屏视图」当前为占位。 +QMenu* TopBar::buildViewMenu() { + auto* m = new QMenu(QStringLiteral("视图"), this); + QObject::connect(m->addAction(QStringLiteral("分析视图")), &QAction::triggered, this, + [this] { emit analysisViewRequested(); }); + m->addAction(QStringLiteral("大屏视图")); + return m; +} + +// 项目管理菜单。仅保留需「直接嵌入」的 web 页(Excel「单个项目」页签第 10~21 行带嵌入地址者): +// 在线监测 / 工具组件 / 批量导出 / 告警管理,点击 emit webPageRequested,由 main 独立整窗加载。 +// 其余全部隐藏(数据视图、项目配置、数据管理、业务管理、项目资料管理、自动任务、模板管理 …)。 +QMenu* TopBar::buildProjectMenu() { + auto* m = new QMenu(QStringLiteral("项目管理"), this); + // web 叶子项:携带 target 路径,点击发信号。 + auto addWeb = [this](QMenu* parent, const QString& title, const QString& target) { + auto* a = parent->addAction(title); + QObject::connect(a, &QAction::triggered, this, + [this, title, target] { emit webPageRequested(title, target); }); + }; + + auto* mon = m->addMenu(QStringLiteral("在线监测")); + addWeb(mon, QStringLiteral("项目设备"), QStringLiteral("/projectSpace/onlineMonitor/projectDevice")); + addWeb(mon, QStringLiteral("在线任务管理"), QStringLiteral("/projectSpace/onlineMonitor/onlineTask")); + + auto* tools = m->addMenu(QStringLiteral("工具组件")); + addWeb(tools, QStringLiteral("装置与脚本"), QStringLiteral("/projectSpace/toolComponent/deviceScript")); + addWeb(tools, QStringLiteral("色阶配置"), QStringLiteral("/projectSpace/toolComponent/levelConfigure")); + addWeb(tools, QStringLiteral("异常类型管理"), QStringLiteral("/projectSpace/toolComponent/exceptionType")); + addWeb(tools, QStringLiteral("模型管理"), QStringLiteral("/projectSpace/toolComponent/modelManage")); + + auto* exp = m->addMenu(QStringLiteral("批量导出")); + addWeb(exp, QStringLiteral("文件导出"), QStringLiteral("/projectSpace/bulkExport/fileExport")); + addWeb(exp, QStringLiteral("报告导出"), QStringLiteral("/projectSpace/bulkExport/templateExport")); + + auto* alarm = m->addMenu(QStringLiteral("告警管理")); + addWeb(alarm, QStringLiteral("设备告警"), QStringLiteral("/projectSpace/alarmManage/deviceAlarm")); + addWeb(alarm, QStringLiteral("告警查询"), QStringLiteral("/projectSpace/alarmManage/alarmQuery")); + + return m; +} + bool TopBar::eventFilter(QObject* obj, QEvent* event) { if (obj == userRow_ && event->type() == QEvent::MouseButtonRelease) { if (userMenu_) diff --git a/src/app/TopBar.hpp b/src/app/TopBar.hpp index 36e800b..e19478a 100644 --- a/src/app/TopBar.hpp +++ b/src/app/TopBar.hpp @@ -29,8 +29,14 @@ signals: void allProjectsRequested(); // 点击"全部项目…" void logoutRequested(); // 头像菜单「退出登录」 void settingsRequested(); // 点击齿轮图标 → 打开设置 + // 项目管理菜单中「直接嵌入」的 web 页被点击:title=窗口标题,target=嵌入页 target 路径。 + void webPageRequested(const QString& title, const QString& target); + void analysisViewRequested(); // 视图菜单「分析视图」→ 中央区切回默认工作台 private: + QMenu* buildViewMenu(); // 视图菜单(成员:「分析视图」需 emit 信号) + QMenu* buildProjectMenu(); // 项目管理菜单(成员:webview 叶子项需 emit 信号) + QToolButton* wsBtn_ = nullptr; QToolButton* projBtn_ = nullptr; QWidget* userRow_ = nullptr; // 用户区整块(头像+姓名/职务+箭头) diff --git a/src/app/main.cpp b/src/app/main.cpp index a9d1cdc..8ef5986 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,7 @@ #include "ProjectListDialog.hpp" #include "ObjectFormDialog.hpp" #include "ImportDatasetDialog.hpp" +#include "panels/web/ProjectWebView.hpp" #include "WorkbenchNavController.hpp" #include "VtkSceneController.hpp" #include "VtkSceneView.hpp" @@ -252,7 +254,8 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re geopro::data::IColorTemplateRepository& colorTplRepo, geopro::data::IDatasetCommandRepository& cmdRepo, geopro::controller::WorkbenchNavController& nav, - geopro::controller::DatasetDetailController& detailCtrl) + geopro::controller::DatasetDetailController& detailCtrl, + const QString& sessionToken) { // ── 世界系:启动取一次 grid1 的 lat/lon,用中位数作 GeoLocalFrame 原点 ── // 全项目共享(shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。 @@ -325,7 +328,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re ads::CDockManager::setAutoHideConfigFlags(ads::CDockManager::AutoHideFlags()); // 禁用自动隐藏 auto* dockManager = new ads::CDockManager(&window); - window.setCentralWidget(dockManager); + // 中央区用 QStackedWidget 承载:page0=工作台(dockManager,默认「分析视图」), + // page1=项目管理 web 页(点项目管理菜单整窗加载,视图菜单「分析视图」切回 page0)。 + auto* centralStack = new QStackedWidget(&window); + centralStack->addWidget(dockManager); // index 0:工作台 + window.setCentralWidget(centralStack); // 覆盖 ADS 自带分隔条样式:其 default.css 用 palette(dark) 把面板间分隔画成深灰粗线, // 且设在管理器自身、选择器更具体(优先级高于全局主题)。在其后追加同选择器、按主题着色的极淡分隔覆盖。 @@ -1239,6 +1246,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re vtkDock->setWidget(centerWidget); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); + // 项目管理「直接嵌入」web 页:作为中央 QStackedWidget 的第二页,整窗加载(覆盖整个工作台)。 + // 单实例复用——点不同菜单项时重新 load;token 已注入页面 localStorage。 + auto* projectWebView = new geopro::app::ProjectWebView(sessionToken); + centralStack->addWidget(projectWebView); // index 1:项目管理 web 整窗 + // ── 下方「数据详情」dock:平面图表多 Tab 面板(QGraphicsView,VTK 仅算几何)── // 单击数据集 → 聚焦已开页;双击 → 新建/聚焦页(真实反演剖面/散点/异常/色阶)。 auto* detailPanel = new geopro::app::DatasetDetailPanel(); @@ -1488,6 +1500,24 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re QObject::connect(topBar, &geopro::app::TopBar::workspaceSwitchRequested, &nav, &geopro::controller::WorkbenchNavController::switchWorkspace); + // 项目管理「直接嵌入」web 页:拼当前项目的嵌入 URL,在中央区整窗加载(切到 web 页)。 + // space=3 为「项目空间(projectSpace)」固定常量——Excel 所有 projectSpace 页均 space=3, + // 与租户/工作空间 id 无关(误用工作空间 snowflake 会被后端拒:space 参数无效)。 + QObject::connect( + topBar, &geopro::app::TopBar::webPageRequested, &window, + [projectWebView, centralStack, &nav](const QString& /*title*/, const QString& target) { + const QString url = + QStringLiteral("http://tenant.geomative.cn/#/embed?space=3&projectId=%1&target=%2") + .arg(nav.currentProjectId(), target); + projectWebView->load(url); + centralStack->setCurrentWidget(projectWebView); // 整窗切到 web 页 + }); + // 视图菜单「分析视图」:中央区切回工作台(默认视图)。 + QObject::connect(topBar, &geopro::app::TopBar::analysisViewRequested, &window, + [centralStack, dockManager]() { + centralStack->setCurrentWidget(dockManager); + }); + // 切换到「不同项目」时先清空中央区,避免新项目残留旧项目的三栏数据与 VTK 渲染。 // 仅真正换项目用(delete-refresh 等 switchProject(currentProjectId) 不走此处,避免误清)。 auto clearCentral = [sceneCtrl, emptyState, checkedProfiles, checkedAnalysis, @@ -2207,7 +2237,7 @@ int main(int argc, char* argv[]) // 而是给出可见错误提示。否则登录后窗口未显示、进程消失,用户无从排查。 try { buildWorkbench(*window, repo, projectRepo, datasetRepo, colorTplRepo, cmdRepo, nav, - detailCtrl); + detailCtrl, token); } catch (const std::exception& e) { QMessageBox::critical( nullptr, QStringLiteral("启动失败"), diff --git a/src/app/panels/web/ProjectWebView.cpp b/src/app/panels/web/ProjectWebView.cpp new file mode 100644 index 0000000..4a50953 --- /dev/null +++ b/src/app/panels/web/ProjectWebView.cpp @@ -0,0 +1,63 @@ +#include "panels/web/ProjectWebView.hpp" + +#include +#include +#include +#include +#include +#include + +namespace geopro::app { + +namespace { + +// 把字符串转成安全的 JS 字面量(带引号、转义),用于拼进注入脚本。 +QString jsStringLiteral(const QString& s) { + // QJsonValue::toJson 不直接给单值字符串;手工转义足够(token 仅含 base64/空格)。 + QString out; + out.reserve(s.size() + 2); + out += QLatin1Char('"'); + for (const QChar c : s) { + switch (c.unicode()) { + case '\\': out += QStringLiteral("\\\\"); break; + case '"': out += QStringLiteral("\\\""); break; + case '\n': out += QStringLiteral("\\n"); break; + case '\r': out += QStringLiteral("\\r"); break; + case '\t': out += QStringLiteral("\\t"); break; + default: out += c; break; + } + } + out += QLatin1Char('"'); + return out; +} + +} // namespace + +ProjectWebView::ProjectWebView(const QString& token, QWidget* parent) : QWidget(parent) { + auto* lay = new QVBoxLayout(this); + lay->setContentsMargins(0, 0, 0, 0); + lay->setSpacing(0); + + view_ = new QWebEngineView(this); + lay->addWidget(view_, 1); + + // token 注入:DocumentCreation 阶段把登录 token 写入 localStorage["token"], + // 早于嵌入页 SPA 启动脚本,保证其读取鉴权时已就绪。每次 load 都会重新执行。 + if (!token.isEmpty()) { + QWebEngineScript script; + script.setName(QStringLiteral("inject-geopro-token")); + script.setInjectionPoint(QWebEngineScript::DocumentCreation); + script.setWorldId(QWebEngineScript::MainWorld); + script.setRunsOnSubFrames(true); + script.setSourceCode( + QStringLiteral("try{localStorage.setItem('token', %1);}catch(e){}") + .arg(jsStringLiteral(token))); + view_->page()->scripts().insert(script); + } +} + +void ProjectWebView::load(const QString& url) { + view_->load(QUrl(url)); +} + +} // namespace geopro::app diff --git a/src/app/panels/web/ProjectWebView.hpp b/src/app/panels/web/ProjectWebView.hpp new file mode 100644 index 0000000..e8dcafe --- /dev/null +++ b/src/app/panels/web/ProjectWebView.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +class QWebEngineView; + +namespace geopro::app { + +// 项目管理 webview 宿主:内嵌 QWebEngineView,承载需「直接嵌入」的 web 管理页 +// (在线监测 / 工具组件 / 批量导出 / 告警管理)。 +// 构造期注入 DocumentCreation 脚本,把登录 token 写入页面 localStorage["token"], +// 早于页面自身脚本执行,确保 web 端读取鉴权时已就绪。 +class ProjectWebView : public QWidget { + Q_OBJECT +public: + explicit ProjectWebView(const QString& token, QWidget* parent = nullptr); + + // 加载嵌入页(完整 URL,含 #/embed?space=..&projectId=..&target=..)。 + void load(const QString& url); + +private: + QWebEngineView* view_ = nullptr; +}; + +} // namespace geopro::app