feat/vtk-3d-view #7

Merged
gaozheng merged 301 commits from feat/vtk-3d-view into main 2026-06-27 18:43:52 +08:00
6 changed files with 173 additions and 47 deletions
Showing only changes of commit d5e3522bfa - Show all commits

View File

@ -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

View File

@ -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_)

View File

@ -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; // 用户区整块(头像+姓名/职务+箭头)

View File

@ -66,6 +66,7 @@
#include <QStringList>
#include <QTabWidget>
#include <QMainWindow>
#include <QStackedWidget>
#include <QStatusBar>
#include <QStyle>
#include <QSurfaceFormat>
@ -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 的第二页,整窗加载(覆盖整个工作台)。
// 单实例复用——点不同菜单项时重新 loadtoken 已注入页面 localStorage。
auto* projectWebView = new geopro::app::ProjectWebView(sessionToken);
centralStack->addWidget(projectWebView); // index 1项目管理 web 整窗
// ── 下方「数据详情」dock平面图表多 Tab 面板QGraphicsViewVTK 仅算几何)──
// 单击数据集 → 聚焦已开页;双击 → 新建/聚焦页(真实反演剖面/散点/异常/色阶)。
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("启动失败"),

View File

@ -0,0 +1,63 @@
#include "panels/web/ProjectWebView.hpp"
#include <QUrl>
#include <QVBoxLayout>
#include <QWebEnginePage>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QWebEngineView>
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

View File

@ -0,0 +1,25 @@
#pragma once
#include <QString>
#include <QWidget>
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