#include "panels/columns/CategorySection.hpp" #include #include #include "panels/columns/DateRangeEdit.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "Theme.hpp" #include "panels/DatasetListPanel.hpp" #include "repo/DatasetFieldDictionary.hpp" namespace geopro::app { using geopro::data::DsRow; using geopro::data::DsTypeFields; CategorySection::CategorySection(const CategorySpec& spec, geopro::data::DatasetFieldDictionary* dict, QWidget* parent) : QWidget(parent), spec_(spec), dict_(dict) { auto* root = new QVBoxLayout(this); root->setContentsMargins(0, 0, 0, 0); root->setSpacing(0); // 数据类型标题行(spec §7):折叠箭头+标题(左) | 「+新增三维体」(右,仅反演类,在标题行而非筛选行)。 auto* headerRow = new QWidget(this); auto* hl = new QHBoxLayout(headerRow); hl->setContentsMargins(space::kSm, 0, space::kSm, 0); hl->setSpacing(space::kSm); header_ = new QToolButton(headerRow); header_->setText(QString::fromStdString(spec_.title)); header_->setCheckable(true); header_->setChecked(true); header_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); header_->setArrowType(Qt::DownArrow); header_->setAutoRaise(true); hl->addWidget(header_); hl->addStretch(1); if (spec_.canGenerateVolume) { auto* gen = new QToolButton(headerRow); gen->setText(QStringLiteral("+ 新增三维体")); gen->setAutoRaise(true); connect(gen, &QToolButton::clicked, this, [this] { emit generateVolumeRequested(QString::fromStdString(spec_.dsTypeCode), checkedDsIds()); }); hl->addWidget(gen); } root->addWidget(headerRow); body_ = new QWidget(this); auto* body = new QVBoxLayout(body_); body->setContentsMargins(space::kMd, space::kSm, space::kMd, space::kMd); body->setSpacing(space::kSm); // 筛选行:装置类型(仅 ERT 类)+ 采集时间段(起止;最小日期显示"不限",不再露 1752)。 auto* filterRow = new QHBoxLayout(); if (spec_.hasArrayTypeFilter) { arrayCombo_ = new QComboBox(body_); arrayCombo_->addItem(QStringLiteral("全部装置"), QString()); connect(arrayCombo_, qOverload(&QComboBox::currentIndexChanged), this, [this](int) { rebuildList(); }); filterRow->addWidget(arrayCombo_); } filterRow->addWidget(new QLabel(QStringLiteral("采集"), body_)); dateRange_ = new DateRangeEdit(body_); connect(dateRange_, &DateRangeEdit::rangeChanged, this, [this] { rebuildList(); }); filterRow->addWidget(dateRange_, 1); body->addLayout(filterRow); // 段体:可勾选数据树(勾选=渲染)。复用数据列表卡片委托与 populateDatasetList。 list_ = new QTreeWidget(body_); list_->setHeaderHidden(true); list_->setRootIsDecorated(true); applyDatasetCardDelegate(list_); connect(list_, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem*, int) { emitChecked(); }); connect(list_, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* it, int) { const QString id = it->data(0, kDsIdRole).toString(); if (id.isEmpty()) return; emit detailRequested(id, it->data(0, kDsDdCodeRole).toString(), it->data(0, kDsNameRole).toString()); }); list_->setContextMenuPolicy(Qt::CustomContextMenu); connect(list_, &QTreeWidget::customContextMenuRequested, this, &CategorySection::showContextMenu); body->addWidget(list_, 1); root->addWidget(body_, 1); connect(header_, &QToolButton::toggled, this, [this](bool on) { body_->setVisible(on); header_->setArrowType(on ? Qt::DownArrow : Qt::RightArrow); }); } void CategorySection::setStructure(const std::vector& nodes) { structure_ = nodes; // 容器分层(项目根/GS/TM→ds)在 Task 12 接入真实结构后据此构建。 } void CategorySection::setDatasets(const std::vector& rows) { rows_ = rows; refreshArrayCombo(); rebuildList(); } void CategorySection::refreshArrayCombo() { if (!spec_.hasArrayTypeFilter || !arrayCombo_) return; const QString prev = arrayCombo_->currentData().toString(); const QSignalBlocker block(arrayCombo_); arrayCombo_->clear(); arrayCombo_->addItem(QStringLiteral("全部装置"), QString()); if (dict_) { const auto& en = dict_->arrayTypeEnum(); // 全局装置枚举 itemValue→中文 QSet seen; // 列出当前数据里出现过、且命中枚举的装置值 → 中文名。 for (const auto& r : rows_) { for (const auto& kv : r.properties) { const auto it = en.find(kv.value); if (it == en.end()) continue; // 非装置类型值 const QString val = QString::fromStdString(kv.value); if (seen.contains(val)) continue; seen.insert(val); arrayCombo_->addItem(QString::fromStdString(it->second), val); } } } const int idx = arrayCombo_->findData(prev); // 尽量保留上次选择 arrayCombo_->setCurrentIndex(idx >= 0 ? idx : 0); } bool CategorySection::passesFilters(const DsRow& row) const { // 装置类型筛选(全局枚举;"全部"=空不筛):ds.properties 任一 value 命中选中 itemValue。 if (spec_.hasArrayTypeFilter && arrayCombo_) { const QString sel = arrayCombo_->currentData().toString(); if (!sel.isEmpty()) { bool hit = false; for (const auto& kv : row.properties) if (QString::fromStdString(kv.value) == sel) { hit = true; break; } if (!hit) return false; } } // 采集时间范围(collectTime 经 dict 取;缺则回退 createTime;空范围不约束)。 const QDate from = dateRange_ ? dateRange_->from() : QDate(); const QDate to = dateRange_ ? dateRange_->to() : QDate(); if (from.isValid() || to.isValid()) { const DsTypeFields* f = dict_ ? dict_->fields(spec_.dsTypeCode) : nullptr; std::string ts = f ? collectTimeOf(row, *f) : std::string(); if (ts.empty()) ts = row.createTime; const QDate d = QDate::fromString(QString::fromStdString(ts).left(10), QStringLiteral("yyyy-MM-dd")); if (d.isValid()) { if (from.isValid() && d < from) return false; if (to.isValid() && d > to) return false; } } return true; } void CategorySection::rebuildList() { std::vector filtered; filtered.reserve(rows_.size()); for (const auto& r : rows_) if (passesFilters(r)) filtered.push_back(r); { const QSignalBlocker block(list_); populateDatasetList(list_, filtered, /*append=*/false); for (QTreeWidgetItemIterator it(list_); *it; ++it) { // 容器节点(项目根/GS/TM)只作层级骨架,不可勾选。 if ((*it)->data(0, kDsDdCodeRole).toString() == QStringLiteral("container")) continue; (*it)->setFlags((*it)->flags() | Qt::ItemIsUserCheckable); (*it)->setCheckState(0, Qt::Unchecked); } } list_->expandAll(); // 展开容器层级(项目根/GS/TM),让体/切片/异常可见 emitChecked(); // 重建后必为空选,清掉上次渲染勾选 } QStringList CategorySection::checkedDsIds() const { QStringList ids; for (QTreeWidgetItemIterator it(list_); *it; ++it) if ((*it)->checkState(0) == Qt::Checked) ids << (*it)->data(0, kDsIdRole).toString(); return ids; } void CategorySection::emitChecked() { emit checkedDatasetsChanged(checkedDsIds()); } void CategorySection::showContextMenu(const QPoint& pos) { QTreeWidgetItem* it = list_->itemAt(pos); if (!it) return; const QString id = it->data(0, kDsIdRole).toString(); const QString ddCode = it->data(0, kDsDdCodeRole).toString(); if (id.isEmpty() || ddCode == QStringLiteral("container")) return; // 容器节点无操作菜单 const QString name = it->data(0, kDsNameRole).toString(); QMenu menu(this); menu.addAction(QStringLiteral("详情"), this, [this, id, ddCode, name] { emit detailRequested(id, ddCode, name); }); // 切片 / 异常可删除(三维体段无 deleteVolume 端点)。 if (ddCode == QStringLiteral("dd_slice") || ddCode == QStringLiteral("dd_anomaly")) { menu.addAction(QStringLiteral("删除"), this, [this, id, ddCode] { emit deleteDatasetRequested(id, ddCode); }); } menu.exec(list_->viewport()->mapToGlobal(pos)); } } // namespace geopro::app