#include "DatasetDetailController.hpp" #include #include #include "repo/IAsyncDatasetRepository.hpp" #include "api/DatasetLoadHandles.hpp" namespace geopro::controller { DatasetDetailController::DatasetDetailController(data::IAsyncDatasetRepository& repo, ChartStrategyRegistry& registry, QObject* parent) : QObject(parent), repo_(repo), registry_(registry) { // QSignalSpy / 队列连接对自定义类型需注册(页签集随 datasetOpened 传递)。 qRegisterMetaType>("std::vector"); } DatasetDetailController::~DatasetDetailController() { // 退出契约:abort 全部在飞句柄,不依赖外部析构顺序兜底。 for (auto& load : inflight_) if (load) load->abort(); } void DatasetDetailController::openDataset(const QString& dsId, const QString& ddCode, const QString& dsName, const QString& tmObjectId) { qInfo("[detail] openDataset id=%s ddCode=%s name=%s tm=%s", qUtf8Printable(dsId), qUtf8Printable(ddCode), qUtf8Printable(dsName), qUtf8Printable(tmObjectId)); auto* s = registry_.find(ddCode.toStdString()); if (!s) { // 未注册策略 → 优雅降级 qWarning("[detail] 未注册策略 ddCode=%s → 降级提示", qUtf8Printable(ddCode)); emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览")); return; } const std::vector tabs = s->tabs(); emit datasetOpened(dsId, ddCode, dsName, tmObjectId, tabs); for (int i = 0; i < static_cast(tabs.size()); ++i) if (!tabs[static_cast(i)].lazy) loadTab(dsId, ddCode, i); } void DatasetDetailController::loadTab(const QString& dsId, const QString& ddCode, int tabIndex) { loadTabImpl(dsId, ddCode, tabIndex, /*pageNo*/ 1, /*pageSize*/ 0); } void DatasetDetailController::loadTabPaged(const QString& dsId, const QString& ddCode, int tabIndex, int pageNo, int pageSize) { loadTabImpl(dsId, ddCode, tabIndex, pageNo, pageSize); } void DatasetDetailController::loadTabImpl(const QString& dsId, const QString& ddCode, int tabIndex, int pageNo, int pageSize) { auto* s = registry_.find(ddCode.toStdString()); if (!s) return; // 策略消失(不应发生):静默不加载 const std::vector tabs = s->tabs(); if (tabIndex < 0 || tabIndex >= static_cast(tabs.size())) return; const controller::TabSpec& spec = tabs[static_cast(tabIndex)]; // loadAsync 对未知 loaderKey 抛 std::runtime_error;若逃逸槽函数会被 GuardedApplication // 吞掉、遮罩永久悬挂(文档化的崩溃/挂起类)。就地兜底为 loadFailed,且不留半注册的在飞句柄。 data::DetailLoad* load = nullptr; try { load = repo_.loadAsync(spec.loaderKey.toStdString(), dsId.toStdString(), pageNo, pageSize); } catch (const std::exception& e) { qWarning("[detail] loadAsync 失败 id=%s tab=%d key=%s: %s", qUtf8Printable(dsId), tabIndex, qUtf8Printable(spec.loaderKey), e.what()); emit loadFailed(dsId, QStringLiteral("暂不支持该数据类型的预览")); return; } if (auto& prev = inflight_[tabIndex]) prev->abort(); // abort-and-replace 该槽位 inflight_[tabIndex] = load; emit tabLoadStarted(dsId, tabIndex); QObject::connect(load, &data::DetailLoad::done, this, [this, load, dsId, tabIndex](const QVariant& payload) { if (load != inflight_.value(tabIndex)) return; // §5.0 句柄身份比对:丢弃迟到信号 inflight_.remove(tabIndex); emit tabReady(dsId, tabIndex, payload); }); QObject::connect(load, &data::DetailLoad::failed, this, [this, load, dsId, tabIndex](const QString& msg) { if (load != inflight_.value(tabIndex)) return; inflight_.remove(tabIndex); qWarning("[detail] 页签加载失败 id=%s tab=%d: %s", qUtf8Printable(dsId), tabIndex, qUtf8Printable(msg)); emit loadFailed(dsId, msg); }); } void DatasetDetailController::focusDataset(const QString& dsId) { emit focusRequested(dsId); } } // namespace geopro::controller