feat/dataset-detail-chart #5

Merged
gaozheng merged 74 commits from feat/dataset-detail-chart into main 2026-06-13 17:30:37 +08:00
3 changed files with 46 additions and 264 deletions
Showing only changes of commit 08ba483550 - Show all commits

View File

@ -80,10 +80,13 @@
#include "CentralScene.hpp" #include "CentralScene.hpp"
#include "ProjectListDialog.hpp" #include "ProjectListDialog.hpp"
#include "WorkbenchNavController.hpp" #include "WorkbenchNavController.hpp"
#include "DatasetDetailController.hpp"
#include "api/ApiProjectRepository.hpp" #include "api/ApiProjectRepository.hpp"
#include "api/ApiDatasetRepository.hpp"
#include "panels/ObjectTreePanel.hpp" #include "panels/ObjectTreePanel.hpp"
#include "login/LoginWindow.hpp" #include "login/LoginWindow.hpp"
#include "panels/DatasetListPanel.hpp" #include "panels/DatasetListPanel.hpp"
#include "panels/DatasetDetailPanel.hpp"
#include "panels/DynamicFormView.hpp" #include "panels/DynamicFormView.hpp"
#include "panels/ObjectExceptionPanel.hpp" #include "panels/ObjectExceptionPanel.hpp"
@ -155,44 +158,6 @@ private:
QWidget* host_; QWidget* host_;
}; };
// 相机补间 + actor 淡入:从 from 位姿平滑过渡到 to 位姿,同时 actors 透明度 0→1。
// vtkCameraInterpolator 两关键帧线性插值(缓动交给 QEasingCurve单条 QVariantAnimation
// 逐帧驱动并 Render结束回调锁定到目标态防插值末值误差/残留半透明)。
// 渐进增强:动效只是过渡,最终一帧永远是正确的目标态,故即使观感不佳也不破坏功能。
void animateReveal(vtkRenderer* renderer, vtkGenericOpenGLRenderWindow* rw,
vtkSmartPointer<vtkCamera> fromCam, vtkSmartPointer<vtkCamera> toCam,
std::vector<vtkSmartPointer<vtkActor>> actors, int durationMs, QObject* owner)
{
auto interp = vtkSmartPointer<vtkCameraInterpolator>::New();
interp->SetInterpolationTypeToLinear();
interp->AddCamera(0.0, fromCam);
interp->AddCamera(1.0, toCam);
auto* anim = new QVariantAnimation(owner);
anim->setDuration(durationMs);
anim->setStartValue(0.0);
anim->setEndValue(1.0);
anim->setEasingCurve(QEasingCurve::OutCubic);
QObject::connect(anim, &QVariantAnimation::valueChanged, owner,
[interp, renderer, rw, actors](const QVariant& v) {
const double t = v.toDouble();
interp->InterpolateCamera(t, renderer->GetActiveCamera());
for (const auto& a : actors)
if (a) a->GetProperty()->SetOpacity(t);
renderer->ResetCameraClippingRange();
rw->Render();
});
QObject::connect(anim, &QVariantAnimation::finished, owner,
[renderer, rw, actors, toCam]() {
renderer->GetActiveCamera()->DeepCopy(toCam);
for (const auto& a : actors)
if (a) a->GetProperty()->SetOpacity(1.0);
renderer->ResetCameraClippingRange();
rw->Render();
});
anim->start(QAbstractAnimation::DeleteWhenStopped);
}
// 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。 // 读取 RSA 公钥 PEM 全文(登录时密码加密用)。读不到返回空串,登录将报错。
std::string readPem(const std::string& path) std::string readPem(const std::string& path)
{ {
@ -215,12 +180,6 @@ double median(std::vector<double> v)
// 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。 // 当前中央视图(默认二维地图)。二维地图=测线红线俯视;三维视图=断面墙。
using geopro::app::ViewMode; using geopro::app::ViewMode;
// 数据详情显示内容(默认网格数据)。网格数据=#18 banded原数据=#17 散点(对齐原型命名)。
enum class DetailMode { Section18, Scatter17 };
// #17 散点屏幕像素方块边长。
constexpr float kScatterPointSize = 4.0F;
// 纵向夸张倍数Z 基准统一M-3全项目共用同一倍数使 帘面(z) / 体素 / 切片 / // 纵向夸张倍数Z 基准统一M-3全项目共用同一倍数使 帘面(z) / 体素 / 切片 /
// 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。 // 数据详情剖面(y) / 地形(relief) 的纵向比例一致——避免「剖面×1.5、帘面×3」不一致。
// 单一可调常量:要整体调纵向观感改这一处即可。 // 单一可调常量:要整体调纵向观感改这一处即可。
@ -234,7 +193,8 @@ constexpr const char* kWgs84 = "EPSG:4326";
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。 // repo 生命周期须覆盖到事件循环结束(由调用方保证)。
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo, void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo,
geopro::data::IProjectRepository& projectRepo, geopro::data::IProjectRepository& projectRepo,
geopro::controller::WorkbenchNavController& nav) geopro::controller::WorkbenchNavController& nav,
geopro::controller::DatasetDetailController& detailCtrl)
{ {
// ── 世界系:启动取一次 grid1 的 lat/lon用中位数作 GeoLocalFrame 原点 ── // ── 世界系:启动取一次 grid1 的 lat/lon用中位数作 GeoLocalFrame 原点 ──
// 全项目共享shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。 // 全项目共享shared_ptr 持有):所有帘面用同一 frame 投影,保证多条测线空间配准。
@ -322,23 +282,6 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
centerLayout->setContentsMargins(0, 0, 0, 0); centerLayout->setContentsMargins(0, 0, 0, 0);
centerLayout->setSpacing(0); centerLayout->setSpacing(0);
// 分段工具条按钮样式QToolButton + 主题化 QSS选中=强调色文字 + 强调色下划线,明暗都清晰。
// ElaToolButton 选中只画极淡 BasicHover、且不可经 QSS 改,故这类需清晰选中态的用 QToolButton。
const QString kBarBtnQss =
QStringLiteral(
"QToolButton{ border:none; border-radius:6px; padding:6px 12px; color:{{text/primary}};"
" font-size:%1px; }"
"QToolButton:hover{ background:{{bg/hover}}; }"
"QToolButton:checked{ color:{{accent/primary}}; font-weight:%2;"
" border-bottom:2px solid {{accent/primary}}; }"
"QToolButton#dataTab{ border:none; border-radius:0; background:transparent;"
" border-bottom:2px solid transparent; color:{{text/secondary}}; padding:8px 8px; }"
"QToolButton#dataTab:hover{ color:{{text/primary}}; background:transparent; }"
"QToolButton#dataTab:checked{ color:{{accent/primary}}; font-weight:%2;"
" border-bottom:2px solid {{accent/primary}}; }")
.arg(geopro::app::scaledPx(geopro::app::type::kBody))
.arg(geopro::app::type::kWeightSemibold);
// 「二维地图/三维视图」分段切换表头:与「异常/属性」面板表头同款42px 表头底 + 强调色下划线页签)。 // 「二维地图/三维视图」分段切换表头:与「异常/属性」面板表头同款42px 表头底 + 强调色下划线页签)。
auto seg = geopro::app::buildSegmentedHeader( auto seg = geopro::app::buildSegmentedHeader(
{QStringLiteral("二维地图"), QStringLiteral("三维视图")}, {QStringLiteral("二维地图"), QStringLiteral("三维视图")},
@ -448,77 +391,12 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
vtkDock->setWidget(centerWidget); vtkDock->setWidget(centerWidget);
auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock); auto* centerDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
// ── 下方「数据详情」dock独立 QVTK 小视图(独立 renderer/renderWindow── // ── 下方「数据详情」dock平面图表多 Tab 面板QGraphicsViewVTK 仅算几何)──
// 单击 DS → 显示该数据集平面反演剖面(#18 banded平躺俯视正交 // 单击数据集 → 聚焦已开页;双击 → 新建/聚焦页(真实反演剖面/散点/异常/色阶)。
auto* detailWidget = new QVTKOpenGLStereoWidget(); auto* detailPanel = new geopro::app::DatasetDetailPanel();
vtkNew<vtkGenericOpenGLRenderWindow> detailRenderWindow;
vtkNew<vtkRenderer> detailRenderer;
{
double r, g, b;
geopro::app::vtkBackground(r, g, b); // 背景随主题
detailRenderer->SetBackground(r, g, b);
}
detailWidget->setRenderWindow(detailRenderWindow);
detailRenderWindow->AddRenderer(detailRenderer);
vtkRenderer* detailRendererPtr = detailRenderer.Get();
vtkGenericOpenGLRenderWindow* detailRenderWindowPtr = detailRenderWindow.Get();
// 注VTK 背景随主题切换的连接放在 rebuildCentral/rebuildDetail 定义之后(直接重跑它们,
// 走完整渲染路径必重绘,比手动 SetBackground+Render 稳)。
// 数据详情容器:顶部「反演剖面/原数据」工具条 + 下方 QVTK 小视图。
auto* detailContainer = new QWidget();
auto* detailLayout = new QVBoxLayout(detailContainer);
detailLayout->setContentsMargins(0, 0, 0, 0);
detailLayout->setSpacing(0);
// 工具条对齐原型:「原数据 | 网格数据」互斥 +「显示异常/电极/等值线」开关。
// QToolButton + 主题化 QSS选中=强调色文字+下划线,明暗都清晰)。
auto* detailToolBar = new QWidget();
auto* detailBarLay = new QHBoxLayout(detailToolBar);
detailBarLay->setContentsMargins(8, 6, 8, 6);
detailBarLay->setSpacing(6);
auto makeBarBtn = [detailToolBar](const QString& text, bool checkable) {
auto* b = new QToolButton(detailToolBar);
b->setText(text);
b->setCheckable(checkable);
return b;
};
auto* detailGroup = new QButtonGroup(detailToolBar);
detailGroup->setExclusive(true);
auto* actScatter = makeBarBtn(QStringLiteral("原数据"), true);
auto* actSection = makeBarBtn(QStringLiteral("网格数据"), true);
actScatter->setObjectName(QStringLiteral("dataTab"));
actSection->setObjectName(QStringLiteral("dataTab"));
detailGroup->addButton(actScatter);
detailGroup->addButton(actSection);
detailBarLay->addWidget(actScatter);
detailBarLay->addWidget(actSection);
actSection->setChecked(true); // 默认网格数据 (#18)
auto* barSep = new QFrame(detailToolBar);
barSep->setFrameShape(QFrame::VLine);
barSep->setObjectName(QStringLiteral("topDivider"));
detailBarLay->addSpacing(4);
detailBarLay->addWidget(barSep);
detailBarLay->addSpacing(4);
auto* actShowAnomaly = makeBarBtn(QStringLiteral("显示异常"), true);
actShowAnomaly->setChecked(true); // 默认显示异常(对齐原型 ☑显示异常)
auto* actShowElectrodes = makeBarBtn(QStringLiteral("显示电极"), true);
actShowElectrodes->setChecked(true); // 默认显示电极 ▼(对齐原型)
auto* actShowContour = makeBarBtn(QStringLiteral("显示等值线"), true);
actShowContour->setChecked(true); // 默认显示等值线(对齐原型)
detailBarLay->addWidget(actShowAnomaly);
detailBarLay->addWidget(actShowElectrodes);
detailBarLay->addWidget(actShowContour);
detailBarLay->addStretch();
geopro::app::applyTokenizedStyleSheet(detailToolBar, kBarBtnQss);
detailLayout->addWidget(detailToolBar);
detailLayout->addWidget(detailWidget, 1);
auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情")); auto* detailDock = new ads::CDockWidget(QStringLiteral("数据详情"));
detailDock->setWidget(wrapWithHeader( detailDock->setWidget(wrapWithHeader(
geopro::app::Glyph::Detail, QStringLiteral("数据详情"), detailContainer, geopro::app::Glyph::Detail, QStringLiteral("数据详情"), detailPanel));
{{geopro::app::Glyph::Collapse, QStringLiteral("折叠")},
{geopro::app::Glyph::Download, QStringLiteral("导出")}}));
// 放在中央视图下方。 // 放在中央视图下方。
dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea); dockManager->addDockWidget(ads::BottomDockWidgetArea, detailDock, centerDockArea);
@ -599,146 +477,45 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
*frame, kVerticalExaggeration); *frame, kVerticalExaggeration);
}; };
// ── 数据详情共享状态 + 重建 ────────────────────────────────────────── // ── 单击左下数据列表的采集批次(DS) → 属性表单 + 聚焦详情已开页 ──
// 当前选中数据集 id空=未选)与详情显示模式(反演剖面/原数据);切模式或换选中都重建。
auto currentDsId = std::make_shared<QString>();
auto detailMode = std::make_shared<DetailMode>(DetailMode::Section18);
auto showAnomalies = std::make_shared<bool>(true); // 默认显示异常(对齐原型)
auto showElectrodes = std::make_shared<bool>(true); // 默认显示电极 ▼
auto showContour = std::make_shared<bool>(true); // 默认显示等值线
auto hiddenAnoms = std::make_shared<std::set<int>>(); // 异常列表中被取消勾选(隐藏)的异常下标
auto prevDsId = std::make_shared<QString>(); // 上次渲染的 DS id判定“切换数据集”以触发揭示过渡
// 按当前选中 DS + 详情模式重建下方数据详情(平躺俯视正交,纵向夸张填面板)。
// 勾选「显示异常/电极/等值线」控制对应叠加(同纵向夸张对齐)。
// overdrive(A):仅“切换数据集”这一加载时刻播放相机补间 + actor 淡入揭示;模式/叠加层开关
// 属同一数据集内微调,直接落定不放动画(特殊时刻才特殊,避免每次交互都动的疲劳)。
auto rebuildDetail = [&repo, detailRendererPtr, detailRenderWindowPtr, detailWidget, currentDsId,
prevDsId, detailMode, showAnomalies, showElectrodes, showContour,
hiddenAnoms]() {
const bool dsChanged = (*currentDsId != *prevDsId);
const bool animate = dsChanged && !prevDsId->isEmpty() && !currentDsId->isEmpty();
// 过渡起点:清场景前先快照当前相机位姿。
auto fromCam = vtkSmartPointer<vtkCamera>::New();
fromCam->DeepCopy(detailRendererPtr->GetActiveCamera());
detailRendererPtr->RemoveAllViewProps();
{ // 背景随主题
double r, g, b;
geopro::app::vtkBackground(r, g, b);
detailRendererPtr->SetBackground(r, g, b);
}
if (currentDsId->isEmpty()) { // 未选数据集:清空即可
*prevDsId = *currentDsId;
detailRenderWindowPtr->Render();
return;
}
std::vector<vtkSmartPointer<vtkActor>> added; // 本次加入的 actor供淡入
const std::string id = currentDsId->toStdString();
if (*detailMode == DetailMode::Section18) {
// 网格数据:#18 banded 等值面(+「显示等值线」时叠黑色等值线),纵向夸张 1.5x(沿 y
const auto g = repo.loadGrid(id);
const auto cs = repo.loadColorScale(id);
const auto actors = geopro::render::buildGridContour(g, cs);
if (actors.bands) {
actors.bands->SetScale(1.0, kVerticalExaggeration, 1.0);
detailRendererPtr->AddViewProp(actors.bands);
added.push_back(actors.bands);
}
if (actors.edges && *showContour) {
actors.edges->SetScale(1.0, kVerticalExaggeration, 1.0);
detailRendererPtr->AddViewProp(actors.edges);
added.push_back(actors.edges);
}
// 顶部电极标记 ▼(仅网格数据;同纵向夸张对齐)。
if (*showElectrodes) {
auto elec = geopro::render::buildElectrodes(g);
if (elec) {
elec->SetScale(1.0, kVerticalExaggeration, 1.0);
detailRendererPtr->AddViewProp(elec);
added.push_back(elec);
}
}
} else {
// 原数据:#17 彩色散点,用散点自带色阶;纵向夸张同剖面以对齐观感。
const auto s = repo.loadScatter(id);
const auto scs = repo.loadScatterColorScale(id);
auto a = geopro::render::buildScatter(s, scs, kScatterPointSize);
if (a) {
a->SetScale(1.0, kVerticalExaggeration, 1.0);
detailRendererPtr->AddViewProp(a);
added.push_back(a);
}
}
// 异常叠加(与剖面同坐标系/同纵向夸张)。逐异常构建以按列表显隐(下标=原 vector 序)过滤。
if (*showAnomalies) {
const auto anomalies = repo.loadAnomalies(id);
for (int i = 0; i < static_cast<int>(anomalies.size()); ++i) {
if (hiddenAnoms->count(i)) continue; // 列表中取消勾选→隐藏
for (auto& act : geopro::render::buildAnomalies({anomalies[i]})) {
act->SetScale(1.0, kVerticalExaggeration, 1.0);
detailRendererPtr->AddViewProp(act);
added.push_back(act);
}
}
}
geopro::render::applyTop2D(detailRendererPtr);
detailRendererPtr->ResetCamera();
*prevDsId = *currentDsId;
if (animate) {
// 目标位姿快照 → 相机回退到旧位姿 + actors 透明 → 补间到目标并淡入。
auto toCam = vtkSmartPointer<vtkCamera>::New();
toCam->DeepCopy(detailRendererPtr->GetActiveCamera());
for (const auto& a : added) a->GetProperty()->SetOpacity(0.0);
detailRendererPtr->GetActiveCamera()->DeepCopy(fromCam);
detailRendererPtr->ResetCameraClippingRange();
animateReveal(detailRendererPtr, detailRenderWindowPtr, fromCam, toCam, added, 450,
detailWidget);
} else {
detailRenderWindowPtr->Render();
}
};
// ── 单击左下数据列表的采集批次(DS) → 加载数据集动态表单(数据集属性面板)──
QObject::connect(datasetList, &QListWidget::itemClicked, datasetList, QObject::connect(datasetList, &QListWidget::itemClicked, datasetList,
[&nav](QListWidgetItem* item) { [&nav, &detailCtrl](QListWidgetItem* item) {
if (item->data(geopro::app::kDsLoadMoreRole).toBool()) { if (item->data(geopro::app::kDsLoadMoreRole).toBool()) {
nav.loadMoreData(); nav.loadMoreData();
return; return;
} }
const QString dsId = item->data(geopro::app::kDsIdRole).toString(); const QString dsId = item->data(geopro::app::kDsIdRole).toString();
if (!dsId.isEmpty()) nav.selectDataset(dsId); if (dsId.isEmpty()) return;
nav.selectDataset(dsId); // 属性表单(现状)
detailCtrl.focusDataset(dsId); // 单击=聚焦已开页
}); });
// ── 数据详情工具条「反演剖面/原数据」:切模式 → 重建数据详情 ── // ── 双击 → 打开/聚焦该数据集的详情图表页(拉真实反演剖面/散点/异常/色阶)──
QObject::connect(actSection, &QAbstractButton::clicked, detailWidget, QObject::connect(datasetList, &QListWidget::itemDoubleClicked, datasetList,
[detailMode, rebuildDetail]() { [&detailCtrl](QListWidgetItem* item) {
*detailMode = DetailMode::Section18; const QString dsId = item->data(geopro::app::kDsIdRole).toString();
rebuildDetail(); const QString ddCode = item->data(geopro::app::kDsDdCodeRole).toString();
}); if (!dsId.isEmpty()) detailCtrl.openDataset(dsId, ddCode);
QObject::connect(actScatter, &QAbstractButton::clicked, detailWidget,
[detailMode, rebuildDetail]() {
*detailMode = DetailMode::Scatter17;
rebuildDetail();
}); });
// ──「显示异常 / 显示电极 / 显示等值线」开关:切换叠加 → 重建数据详情 ── // ── 控制器信号 → 详情面板:数据就绪开页 / 聚焦请求 ──
QObject::connect(actShowAnomaly, &QAbstractButton::toggled, detailWidget, QObject::connect(
[showAnomalies, rebuildDetail](bool on) { &detailCtrl, &geopro::controller::DatasetDetailController::chartReady, detailPanel,
*showAnomalies = on; [detailPanel](const geopro::controller::DatasetDetailController::ChartData& d) {
rebuildDetail(); detailPanel->openOrUpdate(d);
});
QObject::connect(&detailCtrl, &geopro::controller::DatasetDetailController::focusRequested,
detailPanel, [detailPanel](const QString& dsId) {
detailPanel->focusDataset(dsId);
}); });
QObject::connect(actShowElectrodes, &QAbstractButton::toggled, detailWidget,
[showElectrodes, rebuildDetail](bool on) { // ── 详情面板切 Tab → 反向高亮数据集列表对应行 ──
*showElectrodes = on; QObject::connect(detailPanel, &geopro::app::DatasetDetailPanel::activeDatasetChanged,
rebuildDetail(); datasetList, [datasetList](const QString& dsId) {
}); for (int i = 0; i < datasetList->count(); ++i)
QObject::connect(actShowContour, &QAbstractButton::toggled, detailWidget, if (datasetList->item(i)->data(geopro::app::kDsIdRole).toString() ==
[showContour, rebuildDetail](bool on) { dsId)
*showContour = on; datasetList->setCurrentRow(i);
rebuildDetail();
}); });
// 「视图详情」浮层显隐:仅三维显示,置于 QVTK 左上(工具条下方)并置顶。 // 「视图详情」浮层显隐:仅三维显示,置于 QVTK 左上(工具条下方)并置顶。
@ -792,12 +569,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
// ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。 // ── 启动:建立一次空背景中央视图(真实 sections 数据由下一轮接入)。
rebuildCentral(); rebuildCentral();
// VTK 背景随主题切换:直接重跑 rebuildCentral/rebuildDetail(走完整渲染路径、末尾必 Render // VTK 背景随主题切换:直接重跑 rebuildCentral(走完整渲染路径、末尾必 Render
// 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。 // 比手动 SetBackground+Render 稳;兼顾 syncSystemTheme 异步切暗的时序)。
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window, QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, &window,
[rebuildCentral, rebuildDetail]() { [rebuildCentral]() {
rebuildCentral(); rebuildCentral();
rebuildDetail();
}); });
// 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备), // 顶部应用区(静态视觉壳,对齐原型):上=菜单栏(视图/项目管理/业务工具/设备),
@ -1050,6 +826,10 @@ int main(int argc, char* argv[])
geopro::data::ApiProjectRepository projectRepo(api); geopro::data::ApiProjectRepository projectRepo(api);
geopro::controller::WorkbenchNavController nav(projectRepo); geopro::controller::WorkbenchNavController nav(projectRepo);
// 数据详情仓储 + 控制器(接真实反演 API同一共享会话 ApiClient。
geopro::data::ApiDatasetRepository datasetRepo(api);
geopro::controller::DatasetDetailController detailCtrl(datasetRepo);
// ── 外壳:标准 QMainWindow原生标题栏。buildWorkbench 直接用其 // ── 外壳:标准 QMainWindow原生标题栏。buildWorkbench 直接用其
// setCentralWidget/setMenuWidget/statusBar 承载工作台。 // setCentralWidget/setMenuWidget/statusBar 承载工作台。
const QString kTitle = QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)"); const QString kTitle = QStringLiteral("Geopro 3.0 — 项目分析视图 (M1)");
@ -1057,7 +837,7 @@ int main(int argc, char* argv[])
window->setWindowTitle(kTitle); window->setWindowTitle(kTitle);
window->resize(1280, 800); window->resize(1280, 800);
window->setMinimumSize(1024, 680); window->setMinimumSize(1024, 680);
buildWorkbench(*window, repo, projectRepo, nav); buildWorkbench(*window, repo, projectRepo, nav, detailCtrl);
// 主题桥ThemeManager 明/暗切换 → 重应用全局 QSS+调色板(标准控件 + ADS内联 chrome 经各自连接)。 // 主题桥ThemeManager 明/暗切换 → 重应用全局 QSS+调色板(标准控件 + ADS内联 chrome 经各自连接)。
QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed, QObject::connect(&geopro::app::ThemeManager::instance(), &geopro::app::ThemeManager::changed,

View File

@ -116,6 +116,7 @@ void populateDatasetList(QListWidget* list, const std::vector<geopro::data::DsRo
auto* item = new QListWidgetItem(text, list); auto* item = new QListWidgetItem(text, list);
item->setData(kDsIdRole, QString::fromStdString(d.id)); item->setData(kDsIdRole, QString::fromStdString(d.id));
item->setData(kDsDdTypeRole, QString::fromStdString(d.ddCode)); item->setData(kDsDdTypeRole, QString::fromStdString(d.ddCode));
item->setData(kDsDdCodeRole, QString::fromStdString(d.ddCode));
} }
} }

View File

@ -12,6 +12,7 @@ constexpr int kDsIdRole = 0x0100; // Qt::UserRole
constexpr int kDsDdTypeRole = 0x0101; // Qt::UserRole + 1 constexpr int kDsDdTypeRole = 0x0101; // Qt::UserRole + 1
constexpr int kDsFileUrlRole = 0x0102; // Qt::UserRole + 2文件下载 url备用 constexpr int kDsFileUrlRole = 0x0102; // Qt::UserRole + 2文件下载 url备用
constexpr int kDsLoadMoreRole = 0x0103; // 标记"加载更多"行 constexpr int kDsLoadMoreRole = 0x0103; // 标记"加载更多"行
constexpr int kDsDdCodeRole = 0x0104; // Qt::UserRole + 4ddCode双击详情选策略用
// 数据页签:每条 = dsName +类型名UserRole 存 dsId、+1 存 ddCode。 // 数据页签:每条 = dsName +类型名UserRole 存 dsId、+1 存 ddCode。
void populateDatasetList(QListWidget* list, const std::vector<geopro::data::DsRow>& rows, bool append); void populateDatasetList(QListWidget* list, const std::vector<geopro::data::DsRow>& rows, bool append);