feat/dataset-detail-chart #5
|
|
@ -30,6 +30,7 @@ add_executable(geopro_desktop WIN32
|
||||||
panels/chart/RawDataChartView.cpp
|
panels/chart/RawDataChartView.cpp
|
||||||
panels/chart/GridDataChartView.cpp
|
panels/chart/GridDataChartView.cpp
|
||||||
panels/chart/DataTableView.cpp
|
panels/chart/DataTableView.cpp
|
||||||
|
panels/chart/BarChartView.cpp
|
||||||
panels/chart/DetailViewFactory.cpp
|
panels/chart/DetailViewFactory.cpp
|
||||||
panels/chart/ChartTheme.cpp
|
panels/chart/ChartTheme.cpp
|
||||||
panels/chart/ColorMapService.cpp
|
panels/chart/ColorMapService.cpp
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@
|
||||||
#include "DatasetDetailController.hpp"
|
#include "DatasetDetailController.hpp"
|
||||||
#include "panels/chart/ErtInversionStrategy.hpp"
|
#include "panels/chart/ErtInversionStrategy.hpp"
|
||||||
#include "panels/chart/MeasurementStrategy.hpp"
|
#include "panels/chart/MeasurementStrategy.hpp"
|
||||||
|
#include "panels/chart/GrMeasurementStrategy.hpp"
|
||||||
#include "api/ApiProjectRepository.hpp"
|
#include "api/ApiProjectRepository.hpp"
|
||||||
#include "api/ApiDatasetRepository.hpp"
|
#include "api/ApiDatasetRepository.hpp"
|
||||||
#include "panels/ObjectTreePanel.hpp"
|
#include "panels/ObjectTreePanel.hpp"
|
||||||
|
|
@ -912,6 +913,7 @@ int main(int argc, char* argv[])
|
||||||
geopro::controller::ChartStrategyRegistry chartRegistry;
|
geopro::controller::ChartStrategyRegistry chartRegistry;
|
||||||
chartRegistry.add(std::make_unique<geopro::app::ErtInversionStrategy>());
|
chartRegistry.add(std::make_unique<geopro::app::ErtInversionStrategy>());
|
||||||
chartRegistry.add(std::make_unique<geopro::app::MeasurementStrategy>());
|
chartRegistry.add(std::make_unique<geopro::app::MeasurementStrategy>());
|
||||||
|
chartRegistry.add(std::make_unique<geopro::app::GrMeasurementStrategy>());
|
||||||
geopro::controller::DatasetDetailController detailCtrl(datasetRepo, chartRegistry);
|
geopro::controller::DatasetDetailController detailCtrl(datasetRepo, chartRegistry);
|
||||||
|
|
||||||
// ── 外壳:标准 QMainWindow(原生标题栏)。buildWorkbench 直接用其
|
// ── 外壳:标准 QMainWindow(原生标题栏)。buildWorkbench 直接用其
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
#include "panels/chart/BarChartView.hpp"
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <qwt_column_symbol.h>
|
||||||
|
#include <qwt_legend.h>
|
||||||
|
#include <qwt_plot.h>
|
||||||
|
#include <qwt_plot_barchart.h>
|
||||||
|
#include <qwt_plot_grid.h>
|
||||||
|
#include <qwt_plot_multi_barchart.h>
|
||||||
|
#include <qwt_samples.h>
|
||||||
|
#include <qwt_scale_draw.h>
|
||||||
|
#include <qwt_text.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Theme.hpp"
|
||||||
|
#include "panels/chart/ChartTheme.hpp"
|
||||||
|
|
||||||
|
namespace geopro::app {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 类目轴刻度:把整数刻度位(0,1,2,…)映射为类目标签 "#1","#2",…(来自 categories)。
|
||||||
|
// 非整数/越界刻度返回空标签(避免次刻度污染)。
|
||||||
|
class CategoryScaleDraw : public QwtScaleDraw {
|
||||||
|
public:
|
||||||
|
explicit CategoryScaleDraw(std::vector<QString> labels) : labels_(std::move(labels)) {
|
||||||
|
enableComponent(QwtScaleDraw::Backbone, true);
|
||||||
|
enableComponent(QwtScaleDraw::Ticks, true);
|
||||||
|
}
|
||||||
|
QwtText label(double v) const override {
|
||||||
|
const double r = std::round(v);
|
||||||
|
if (std::abs(v - r) > 1e-6) return QwtText(); // 仅整数刻度出标签
|
||||||
|
const int i = static_cast<int>(r);
|
||||||
|
if (i < 0 || i >= static_cast<int>(labels_.size())) return QwtText();
|
||||||
|
return labels_[static_cast<size_t>(i)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<QString> labels_;
|
||||||
|
};
|
||||||
|
|
||||||
|
QColor barColor(const QString& hex) {
|
||||||
|
QColor c(hex);
|
||||||
|
return c.isValid() ? c : QColor(0x54, 0x70, 0xc6); // 回退 ECharts 蓝
|
||||||
|
}
|
||||||
|
|
||||||
|
// 造一个 Box 样式柱符号(实心填充、无边框)。
|
||||||
|
QwtColumnSymbol* makeBarSymbol(const QColor& fill) {
|
||||||
|
auto* sym = new QwtColumnSymbol(QwtColumnSymbol::Box);
|
||||||
|
sym->setLineWidth(0);
|
||||||
|
sym->setFrameStyle(QwtColumnSymbol::NoFrame);
|
||||||
|
QPalette pal(fill);
|
||||||
|
pal.setColor(QPalette::Window, fill);
|
||||||
|
pal.setColor(QPalette::Dark, fill);
|
||||||
|
sym->setPalette(pal);
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BarChartView::BarChartView(QWidget* parent) : QWidget(parent) {
|
||||||
|
auto* lay = new QVBoxLayout(this);
|
||||||
|
lay->setContentsMargins(0, 0, 0, 0);
|
||||||
|
lay->setSpacing(0);
|
||||||
|
|
||||||
|
// 左上水平 y 轴标题(ECharts 风格,置于图例行左侧)。
|
||||||
|
yTitle_ = new QLabel(this);
|
||||||
|
auto* titleRow = new QWidget(this);
|
||||||
|
auto* titleLay = new QHBoxLayout(titleRow);
|
||||||
|
titleLay->setContentsMargins(48, 6, 8, 0); // 左缩进对齐 y 轴上方
|
||||||
|
titleLay->setSpacing(0);
|
||||||
|
titleLay->addWidget(yTitle_);
|
||||||
|
titleLay->addStretch();
|
||||||
|
lay->addWidget(titleRow);
|
||||||
|
|
||||||
|
plot_ = new QwtPlot(this);
|
||||||
|
plot_->setObjectName(QStringLiteral("grBarPlotArea"));
|
||||||
|
plot_->enableAxis(QwtPlot::xBottom, true);
|
||||||
|
plot_->enableAxis(QwtPlot::yLeft, true);
|
||||||
|
|
||||||
|
// x 轴标题「电极点」底部居中(setData 设文本)。
|
||||||
|
plot_->setAxisTitle(QwtPlot::xBottom, QwtText());
|
||||||
|
|
||||||
|
// 顶部图例(P1/P2)。
|
||||||
|
plot_->insertLegend(new QwtLegend(), QwtPlot::TopLegend);
|
||||||
|
|
||||||
|
// 仅横向(y)网格,弱化(与原版 ECharts 一致:仅水平刻度线)。
|
||||||
|
auto* grid = new QwtPlotGrid();
|
||||||
|
grid->enableX(false);
|
||||||
|
grid->enableY(true);
|
||||||
|
grid->enableXMin(false);
|
||||||
|
grid->enableYMin(false);
|
||||||
|
grid->setMajorPen(QColor(225, 225, 225), 1.0, Qt::SolidLine);
|
||||||
|
grid->attach(plot_);
|
||||||
|
|
||||||
|
plot_->setMinimumSize(0, 0);
|
||||||
|
lay->addWidget(plot_, 1);
|
||||||
|
|
||||||
|
// 主题:底色/轴字/网格按当前主题套一次 + 热切换。
|
||||||
|
applyChartPlotTheme(plot_);
|
||||||
|
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, plot_,
|
||||||
|
[this]() {
|
||||||
|
applyChartPlotTheme(plot_);
|
||||||
|
// y 轴标题文字色随主题。
|
||||||
|
QPalette pal = yTitle_->palette();
|
||||||
|
pal.setColor(QPalette::WindowText,
|
||||||
|
isDarkTheme() ? tokenColor("text/secondary")
|
||||||
|
: QColor(90, 90, 90));
|
||||||
|
yTitle_->setPalette(pal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BarChartView::~BarChartView() {
|
||||||
|
// 卸载并删除已挂的柱状项,先于 QwtPlot autoDelete 触发(与 RawDataChartView/
|
||||||
|
// GridDataChartView 析构对称,避免悬垂/双删)。
|
||||||
|
clearSeries();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarChartView::clearSeries() {
|
||||||
|
for (QwtPlotItem* it : barItems_) {
|
||||||
|
it->detach();
|
||||||
|
delete it;
|
||||||
|
}
|
||||||
|
barItems_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarChartView::setPayload(const QVariant& payload) {
|
||||||
|
if (!payload.canConvert<geopro::core::BarPayload>()) return; // 坏/空 → 空态
|
||||||
|
setData(payload.value<geopro::core::BarPayload>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarChartView::setData(const geopro::core::BarPayload& p) {
|
||||||
|
data_ = p;
|
||||||
|
clearSeries();
|
||||||
|
|
||||||
|
const int n = static_cast<int>(p.categories.size());
|
||||||
|
|
||||||
|
// y 轴标题(左上水平 QLabel)。
|
||||||
|
yTitle_->setText(p.yTitle);
|
||||||
|
QPalette tpal = yTitle_->palette();
|
||||||
|
tpal.setColor(QPalette::WindowText,
|
||||||
|
isDarkTheme() ? tokenColor("text/secondary") : QColor(90, 90, 90));
|
||||||
|
yTitle_->setPalette(tpal);
|
||||||
|
|
||||||
|
// x 轴标题「电极点」+ 类目刻度 "#1".."#40"。
|
||||||
|
plot_->setAxisTitle(QwtPlot::xBottom, p.xTitle);
|
||||||
|
plot_->setAxisScaleDraw(QwtPlot::xBottom, new CategoryScaleDraw(p.categories));
|
||||||
|
plot_->setAxisScale(QwtPlot::xBottom, -0.5, n - 0.5, 1.0); // 每类目一刻度
|
||||||
|
plot_->setAxisMaxMinor(QwtPlot::xBottom, 0);
|
||||||
|
|
||||||
|
// y 范围 0..max(自动从数据取上界,留 5% 余量)。
|
||||||
|
double yMax = 0.0;
|
||||||
|
for (const auto& s : p.series)
|
||||||
|
for (double v : s.values) yMax = std::max(yMax, v);
|
||||||
|
plot_->setAxisScale(QwtPlot::yLeft, 0.0, yMax > 0 ? yMax * 1.05 : 1.0);
|
||||||
|
|
||||||
|
if (p.series.empty()) {
|
||||||
|
plot_->replot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.series.size() == 1) {
|
||||||
|
// 单系列 → QwtPlotBarChart。
|
||||||
|
const auto& s = p.series.front();
|
||||||
|
auto* chart = new QwtPlotBarChart(s.name);
|
||||||
|
QVector<QPointF> samples;
|
||||||
|
samples.reserve(n);
|
||||||
|
for (int i = 0; i < n && i < static_cast<int>(s.values.size()); ++i)
|
||||||
|
samples.append(QPointF(i, s.values[static_cast<size_t>(i)]));
|
||||||
|
chart->setSamples(samples);
|
||||||
|
chart->setSymbol(makeBarSymbol(barColor(s.color)));
|
||||||
|
chart->setLegendMode(QwtPlotBarChart::LegendChartTitle);
|
||||||
|
chart->setLayoutPolicy(QwtPlotBarChart::AutoAdjustSamples);
|
||||||
|
chart->setSpacing(10); // 柱间留白(默认柱状观感)
|
||||||
|
chart->setMargin(4);
|
||||||
|
chart->attach(plot_);
|
||||||
|
barItems_.push_back(chart);
|
||||||
|
} else {
|
||||||
|
// 多系列(P1+P2)→ QwtPlotMultiBarChart(分组柱)。
|
||||||
|
auto* chart = new QwtPlotMultiBarChart();
|
||||||
|
QList<QwtText> titles;
|
||||||
|
for (size_t k = 0; k < p.series.size(); ++k) {
|
||||||
|
chart->setSymbol(static_cast<int>(k), makeBarSymbol(barColor(p.series[k].color)));
|
||||||
|
titles.append(QwtText(p.series[k].name));
|
||||||
|
}
|
||||||
|
chart->setBarTitles(titles);
|
||||||
|
chart->setStyle(QwtPlotMultiBarChart::Grouped);
|
||||||
|
chart->setLayoutPolicy(QwtPlotMultiBarChart::AutoAdjustSamples);
|
||||||
|
chart->setSpacing(10);
|
||||||
|
chart->setMargin(4);
|
||||||
|
|
||||||
|
QVector<QwtSetSample> samples;
|
||||||
|
samples.reserve(n);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
QVector<double> set;
|
||||||
|
set.reserve(static_cast<int>(p.series.size()));
|
||||||
|
for (const auto& s : p.series)
|
||||||
|
set.append(i < static_cast<int>(s.values.size()) ? s.values[static_cast<size_t>(i)]
|
||||||
|
: 0.0);
|
||||||
|
samples.append(QwtSetSample(i, set));
|
||||||
|
}
|
||||||
|
chart->setSamples(samples);
|
||||||
|
chart->attach(plot_);
|
||||||
|
barItems_.push_back(chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
plot_->replot();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::app
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "model/detail/DetailPayloads.hpp"
|
||||||
|
#include "panels/chart/IDetailView.hpp"
|
||||||
|
|
||||||
|
class QLabel;
|
||||||
|
class QwtPlot;
|
||||||
|
class QwtPlotItem;
|
||||||
|
|
||||||
|
namespace geopro::app {
|
||||||
|
|
||||||
|
// 柱状图视图(dd_ert_measurement_gr_data 接地电阻 柱状图页签):
|
||||||
|
// QwtPlot + QwtPlotBarChart(单系列 P1)或 QwtPlotMultiBarChart(分组 P1+P2)。
|
||||||
|
// x 轴类目标签 "#1".."#40"(自定义 QwtScaleDraw);x 轴标题「电极点」(底部居中);
|
||||||
|
// y 轴标题「电阻(单位:欧姆)」用左上水平 QLabel(ECharts 风格,对齐原版);
|
||||||
|
// 顶部图例(P1/P2);柱填充 #5470c6(P1,数据色,两主题一致)。
|
||||||
|
// 背景/轴字/网格随主题(ChartTheme / ThemeManager)。
|
||||||
|
class BarChartView : public QWidget, public IDetailView {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit BarChartView(QWidget* parent = nullptr);
|
||||||
|
~BarChartView() override;
|
||||||
|
|
||||||
|
void setData(const geopro::core::BarPayload& p);
|
||||||
|
|
||||||
|
QWidget* widget() override { return this; }
|
||||||
|
void setPayload(const QVariant& payload) override; // 坏/空 variant → 空态不崩
|
||||||
|
|
||||||
|
private:
|
||||||
|
void clearSeries(); // 卸载并删除已挂的柱状项(避免 QwtPlot autoDelete 双删)
|
||||||
|
|
||||||
|
geopro::core::BarPayload data_;
|
||||||
|
QwtPlot* plot_;
|
||||||
|
QLabel* yTitle_; // 左上水平 y 轴标题(ECharts 风格)
|
||||||
|
std::vector<QwtPlotItem*> barItems_; // 当前挂载的柱状项(已 attach;卸载时 detach+delete)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace geopro::app
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "panels/chart/BarChartView.hpp"
|
||||||
#include "panels/chart/DataTableView.hpp"
|
#include "panels/chart/DataTableView.hpp"
|
||||||
#include "panels/chart/GridDataChartView.hpp"
|
#include "panels/chart/GridDataChartView.hpp"
|
||||||
#include "panels/chart/RawDataChartView.hpp"
|
#include "panels/chart/RawDataChartView.hpp"
|
||||||
|
|
@ -17,9 +18,10 @@ std::unique_ptr<IDetailView> makeDetailView(controller::ViewKind kind, QWidget*
|
||||||
case controller::ViewKind::Table:
|
case controller::ViewKind::Table:
|
||||||
return std::unique_ptr<IDetailView>(new DataTableView(parent));
|
return std::unique_ptr<IDetailView>(new DataTableView(parent));
|
||||||
case controller::ViewKind::Bar:
|
case controller::ViewKind::Bar:
|
||||||
|
return std::unique_ptr<IDetailView>(new BarChartView(parent));
|
||||||
case controller::ViewKind::LineProfile:
|
case controller::ViewKind::LineProfile:
|
||||||
case controller::ViewKind::PolylineMap:
|
case controller::ViewKind::PolylineMap:
|
||||||
// 后续阶段补:Bar(gr_data)/LineProfile,PolylineMap(trajectory)。
|
// 后续阶段补:LineProfile,PolylineMap(trajectory)。
|
||||||
throw std::runtime_error("makeDetailView: ViewKind not yet implemented");
|
throw std::runtime_error("makeDetailView: ViewKind not yet implemented");
|
||||||
}
|
}
|
||||||
throw std::runtime_error("makeDetailView: unknown ViewKind");
|
throw std::runtime_error("makeDetailView: unknown ViewKind");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include "IDatasetChartStrategy.hpp" // geopro::controller
|
||||||
|
namespace geopro::app {
|
||||||
|
|
||||||
|
// ERT 接地电阻(measurement gr_data)策略:柱状图(同步)+ 列表(懒加载)两页签。
|
||||||
|
// 两页签同一端点 measurement/gr/rows,loaderKey 不同(gr.bar 产 BarPayload / gr.rows 产 TablePayload)。
|
||||||
|
struct GrMeasurementStrategy : controller::IDatasetChartStrategy {
|
||||||
|
std::string ddCode() const override { return "dd_ert_measurement_gr_data"; }
|
||||||
|
std::vector<controller::TabSpec> tabs() const override {
|
||||||
|
return {
|
||||||
|
{QStringLiteral("柱状图"), controller::ViewKind::Bar,
|
||||||
|
QStringLiteral("gr.bar"), /*lazy*/ false, /*paginated*/ false},
|
||||||
|
{QStringLiteral("列表"), controller::ViewKind::Table,
|
||||||
|
QStringLiteral("gr.rows"), /*lazy*/ true, /*paginated*/ false},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace geopro::app
|
||||||
|
|
@ -67,8 +67,25 @@ struct TablePayload {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 柱状图系列:名称(图例/legend)+ 各类目的 y 值 + 填充色(hex,如 #5470c6;数据色,两主题一致)。
|
||||||
|
struct BarSeries {
|
||||||
|
QString name;
|
||||||
|
std::vector<double> values;
|
||||||
|
QString color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 柱状图载荷(dd_ert_measurement_gr_data 接地电阻):类目(x 轴标签,如 "#1".."#40")+
|
||||||
|
// 一个或多个系列(P1/P2,每系列一组按类目对齐的 y 值)+ 轴标题。
|
||||||
|
struct BarPayload {
|
||||||
|
std::vector<QString> categories;
|
||||||
|
std::vector<BarSeries> series;
|
||||||
|
QString xTitle;
|
||||||
|
QString yTitle;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace geopro::core
|
} // namespace geopro::core
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(geopro::core::ScatterPayload)
|
Q_DECLARE_METATYPE(geopro::core::ScatterPayload)
|
||||||
Q_DECLARE_METATYPE(geopro::core::ContourPayload)
|
Q_DECLARE_METATYPE(geopro::core::ContourPayload)
|
||||||
Q_DECLARE_METATYPE(geopro::core::TablePayload)
|
Q_DECLARE_METATYPE(geopro::core::TablePayload)
|
||||||
|
Q_DECLARE_METATYPE(geopro::core::BarPayload)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ add_library(geopro_data STATIC
|
||||||
dto/NavDto.cpp
|
dto/NavDto.cpp
|
||||||
dto/DatasetChartDto.cpp
|
dto/DatasetChartDto.cpp
|
||||||
dto/MeasurementDto.cpp
|
dto/MeasurementDto.cpp
|
||||||
|
dto/GrMeasurementDto.cpp
|
||||||
api/ApiProjectRepository.cpp
|
api/ApiProjectRepository.cpp
|
||||||
api/ApiDatasetRepository.cpp
|
api/ApiDatasetRepository.cpp
|
||||||
api/DatasetLoadHandles.cpp
|
api/DatasetLoadHandles.cpp
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "api/ApiDatasetRepository.hpp"
|
#include "api/ApiDatasetRepository.hpp"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
@ -8,6 +9,7 @@
|
||||||
#include "ApiBatch.hpp"
|
#include "ApiBatch.hpp"
|
||||||
#include "api/DatasetLoadHandles.hpp"
|
#include "api/DatasetLoadHandles.hpp"
|
||||||
#include "dto/DatasetChartDto.hpp"
|
#include "dto/DatasetChartDto.hpp"
|
||||||
|
#include "dto/GrMeasurementDto.hpp"
|
||||||
#include "dto/MeasurementDto.hpp"
|
#include "dto/MeasurementDto.hpp"
|
||||||
#include "model/detail/DetailPayloads.hpp"
|
#include "model/detail/DetailPayloads.hpp"
|
||||||
|
|
||||||
|
|
@ -96,6 +98,20 @@ net::ApiBatch* measurementRowsBatch(net::ApiClient& api, const std::string& dsId
|
||||||
return new net::ApiBatch(calls, &isFailure);
|
return new net::ApiBatch(calls, &isFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 接地电阻(gr):柱状图与列表同一端点 measurement/gr/rows(GET,query 参数)。
|
||||||
|
// 响应 data 是 JSON 数组 → ApiResponseParse 包成 {value:[...]},取 r[0].data.value("value").toArray()。
|
||||||
|
net::ApiBatch* grRowsBatch(net::ApiClient& api, const std::string& dsId) {
|
||||||
|
QList<net::IApiCall*> calls{
|
||||||
|
api.getAsync(QStringLiteral("/business/dd/ert/measurement/gr/rows?dsObjectId=%1").arg(enc(dsId))),
|
||||||
|
};
|
||||||
|
return new net::ApiBatch(calls, &isFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gr 响应数组(包在 data.value 里)。
|
||||||
|
QJsonArray grDataArray(const QList<net::ApiResponse>& r) {
|
||||||
|
return r[0].data.value(QStringLiteral("value")).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ApiDatasetRepository::ApiDatasetRepository(net::ApiClient& api) : api_(api) {}
|
ApiDatasetRepository::ApiDatasetRepository(net::ApiClient& api) : api_(api) {}
|
||||||
|
|
@ -105,6 +121,8 @@ DetailLoad* ApiDatasetRepository::loadAsync(const std::string& loaderKey, const
|
||||||
if (loaderKey == "inversion.grid") return makeInversionGrid(dsId);
|
if (loaderKey == "inversion.grid") return makeInversionGrid(dsId);
|
||||||
if (loaderKey == "ert_measurement.scatter") return makeMeasurementScatter(dsId);
|
if (loaderKey == "ert_measurement.scatter") return makeMeasurementScatter(dsId);
|
||||||
if (loaderKey == "ert_measurement.rows") return makeMeasurementRows(dsId);
|
if (loaderKey == "ert_measurement.rows") return makeMeasurementRows(dsId);
|
||||||
|
if (loaderKey == "gr.bar") return makeGrBar(dsId);
|
||||||
|
if (loaderKey == "gr.rows") return makeGrRows(dsId);
|
||||||
throw std::runtime_error("unknown loaderKey: " + loaderKey);
|
throw std::runtime_error("unknown loaderKey: " + loaderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,4 +154,16 @@ DetailLoad* ApiDatasetRepository::makeMeasurementRows(const std::string& dsId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DetailLoad* ApiDatasetRepository::makeGrBar(const std::string& dsId) {
|
||||||
|
return new ApiDetailLoad(grRowsBatch(api_, dsId), [](const QList<net::ApiResponse>& r) {
|
||||||
|
return QVariant::fromValue(dto::parseGrBar(grDataArray(r)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailLoad* ApiDatasetRepository::makeGrRows(const std::string& dsId) {
|
||||||
|
return new ApiDetailLoad(grRowsBatch(api_, dsId), [](const QList<net::ApiResponse>& r) {
|
||||||
|
return QVariant::fromValue(dto::parseGrTable(grDataArray(r)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace geopro::data
|
} // namespace geopro::data
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ private:
|
||||||
DetailLoad* makeInversionGrid(const std::string& dsId);
|
DetailLoad* makeInversionGrid(const std::string& dsId);
|
||||||
DetailLoad* makeMeasurementScatter(const std::string& dsId);
|
DetailLoad* makeMeasurementScatter(const std::string& dsId);
|
||||||
DetailLoad* makeMeasurementRows(const std::string& dsId);
|
DetailLoad* makeMeasurementRows(const std::string& dsId);
|
||||||
|
DetailLoad* makeGrBar(const std::string& dsId);
|
||||||
|
DetailLoad* makeGrRows(const std::string& dsId);
|
||||||
net::ApiClient& api_;
|
net::ApiClient& api_;
|
||||||
};
|
};
|
||||||
} // namespace geopro::data
|
} // namespace geopro::data
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
#include "dto/GrMeasurementDto.hpp"
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
namespace geopro::data::dto {
|
||||||
|
using namespace geopro::core;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// ECharts 默认蓝(柱填充,数据色——浅/暗主题一致)。
|
||||||
|
const QString kP1Color = QStringLiteral("#5470c6");
|
||||||
|
const QString kP2Color = QStringLiteral("#91cc75"); // ECharts 默认绿(备用 P2 系列)
|
||||||
|
|
||||||
|
// 把 JSON 数值/字符串预格式化为单元格 QString(整数不带小数点;null/缺省 → 空串)。
|
||||||
|
QString cellText(const QJsonValue& v) {
|
||||||
|
if (v.isDouble()) {
|
||||||
|
const double d = v.toDouble();
|
||||||
|
if (d == static_cast<double>(static_cast<long long>(d)))
|
||||||
|
return QString::number(static_cast<long long>(d));
|
||||||
|
return QString::number(d, 'g', 10);
|
||||||
|
}
|
||||||
|
if (v.isString()) return v.toString();
|
||||||
|
return QString(); // null/undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BarPayload parseGrBar(const QJsonArray& rows) {
|
||||||
|
BarPayload p;
|
||||||
|
p.xTitle = QStringLiteral("电极点");
|
||||||
|
p.yTitle = QStringLiteral("电阻(单位:欧姆)");
|
||||||
|
|
||||||
|
BarSeries p1{QStringLiteral("P1"), {}, kP1Color};
|
||||||
|
BarSeries p2{QStringLiteral("P2"), {}, kP2Color};
|
||||||
|
bool hasP2 = false; // 任一行 p2Rg 非空 → 建 P2 系列(分组柱)。
|
||||||
|
|
||||||
|
for (const auto& e : rows) {
|
||||||
|
const QJsonObject o = e.toObject();
|
||||||
|
p.categories.push_back(
|
||||||
|
QStringLiteral("#") + QString::number(o.value(QStringLiteral("electrodeId")).toInt()));
|
||||||
|
const QJsonValue p1v = o.value(QStringLiteral("p1Rg"));
|
||||||
|
p1.values.push_back(p1v.isDouble() ? p1v.toDouble() : 0.0); // null/缺省 → 0(与 P2 对称防脏数据)
|
||||||
|
|
||||||
|
const QJsonValue p2v = o.value(QStringLiteral("p2Rg"));
|
||||||
|
if (p2v.isDouble()) hasP2 = true;
|
||||||
|
p2.values.push_back(p2v.isDouble() ? p2v.toDouble() : 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.series.push_back(std::move(p1)); // P1 恒有
|
||||||
|
if (hasP2) p.series.push_back(std::move(p2));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
TablePayload parseGrTable(const QJsonArray& rows) {
|
||||||
|
TablePayload t;
|
||||||
|
|
||||||
|
// 7 固定列(硬编码:gr 响应无列定义)。code = JSON 字段名,title = 原版列标题。
|
||||||
|
const struct { const char* code; const char* title; } kCols[] = {
|
||||||
|
{"electrodeId", "ID"},
|
||||||
|
{"testDate", "日期"},
|
||||||
|
{"testTime", "时间"},
|
||||||
|
{"p1Rg", "P1 Rg(Ω)"},
|
||||||
|
{"p1RgStatus", "P1状态"},
|
||||||
|
{"p2Rg", "P2 Rg(Ω)"},
|
||||||
|
{"p2RgStatus", "P2状态"},
|
||||||
|
};
|
||||||
|
for (const auto& c : kCols) {
|
||||||
|
TableColumn col;
|
||||||
|
col.code = QString::fromUtf8(c.code);
|
||||||
|
col.title = QString::fromUtf8(c.title);
|
||||||
|
t.columns.push_back(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& e : rows) {
|
||||||
|
const QJsonObject o = e.toObject();
|
||||||
|
std::vector<QString> cells;
|
||||||
|
cells.reserve(t.columns.size());
|
||||||
|
for (const auto& col : t.columns) cells.push_back(cellText(o.value(col.code)));
|
||||||
|
t.rows.push_back(std::move(cells));
|
||||||
|
}
|
||||||
|
|
||||||
|
t.total = static_cast<int>(t.rows.size());
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::data::dto
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include "model/detail/DetailPayloads.hpp"
|
||||||
|
|
||||||
|
namespace geopro::data::dto {
|
||||||
|
|
||||||
|
// dd_ert_measurement_gr_data 接地电阻:两页签同一端点 measurement/gr/rows,data 是 JSON 数组
|
||||||
|
// (每元素 {electrodeId,testDate,testTime,p1Rg,p1RgStatus,p2Rg,p2RgStatus},无 gridHeaderDisplay)。
|
||||||
|
//
|
||||||
|
// 柱状图:类目 = "#"+electrodeId("#1".."#40");系列 P1 = p1Rg(恒有,色 #5470c6);
|
||||||
|
// 系列 P2 = p2Rg,仅当任一行 p2Rg 非空才建(本样本全 null → 只有 P1)。
|
||||||
|
// x 轴标题「电极点」,y 轴标题「电阻(单位:欧姆)」。
|
||||||
|
geopro::core::BarPayload parseGrBar(const QJsonArray& rows);
|
||||||
|
|
||||||
|
// 列表:7 固定列(gr 无 gridHeaderDisplay,故硬编码列标题/列码):
|
||||||
|
// ID | 日期 | 时间 | P1 Rg(Ω) | P1状态 | P2 Rg(Ω) | P2状态。
|
||||||
|
geopro::core::TablePayload parseGrTable(const QJsonArray& rows);
|
||||||
|
|
||||||
|
} // namespace geopro::data::dto
|
||||||
|
|
@ -39,6 +39,7 @@ target_sources(geopro_tests PRIVATE data/test_local_repo.cpp)
|
||||||
target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp)
|
target_sources(geopro_tests PRIVATE data/test_nav_dto.cpp)
|
||||||
target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp)
|
target_sources(geopro_tests PRIVATE data/test_dataset_chart_dto.cpp)
|
||||||
target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp)
|
target_sources(geopro_tests PRIVATE data/test_measurement_dto.cpp)
|
||||||
|
target_sources(geopro_tests PRIVATE data/test_gr_dto.cpp)
|
||||||
target_sources(geopro_tests PRIVATE data/test_dataset_load_handles.cpp)
|
target_sources(geopro_tests PRIVATE data/test_dataset_load_handles.cpp)
|
||||||
# 通用仓储分派离线单测(loadAsync 分派 + QVariant payload round-trip)。
|
# 通用仓储分派离线单测(loadAsync 分派 + QVariant payload round-trip)。
|
||||||
target_sources(geopro_tests PRIVATE data/test_async_repo_dispatch.cpp)
|
target_sources(geopro_tests PRIVATE data/test_async_repo_dispatch.cpp)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "DatasetDetailTab.hpp"
|
#include "DatasetDetailTab.hpp"
|
||||||
#include "IDatasetChartStrategy.hpp" // geopro::controller(控制器层)
|
#include "IDatasetChartStrategy.hpp" // geopro::controller(控制器层)
|
||||||
#include "panels/chart/MeasurementStrategy.hpp"
|
#include "panels/chart/MeasurementStrategy.hpp"
|
||||||
|
#include "panels/chart/GrMeasurementStrategy.hpp"
|
||||||
using namespace geopro::controller;
|
using namespace geopro::controller;
|
||||||
namespace {
|
namespace {
|
||||||
struct Fake : IDatasetChartStrategy {
|
struct Fake : IDatasetChartStrategy {
|
||||||
|
|
@ -52,3 +53,20 @@ TEST(MeasurementStrategy, DrivesTwoTabsScatterAndTable) {
|
||||||
EXPECT_TRUE(tabs[1].lazy);
|
EXPECT_TRUE(tabs[1].lazy);
|
||||||
EXPECT_EQ(tabs[1].loaderKey.toStdString(), "ert_measurement.rows");
|
EXPECT_EQ(tabs[1].loaderKey.toStdString(), "ert_measurement.rows");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(GrMeasurementStrategy, DrivesBarAndTableTabs) {
|
||||||
|
geopro::app::GrMeasurementStrategy s;
|
||||||
|
EXPECT_EQ(s.ddCode(), "dd_ert_measurement_gr_data");
|
||||||
|
const auto tabs = s.tabs();
|
||||||
|
ASSERT_EQ(tabs.size(), 2u);
|
||||||
|
// 柱状图:Bar,非 lazy。
|
||||||
|
EXPECT_EQ(tabs[0].title.toStdString(), std::string("柱状图"));
|
||||||
|
EXPECT_EQ(tabs[0].kind, ViewKind::Bar);
|
||||||
|
EXPECT_FALSE(tabs[0].lazy);
|
||||||
|
EXPECT_EQ(tabs[0].loaderKey.toStdString(), "gr.bar");
|
||||||
|
// 列表:Table,lazy。
|
||||||
|
EXPECT_EQ(tabs[1].title.toStdString(), std::string("列表"));
|
||||||
|
EXPECT_EQ(tabs[1].kind, ViewKind::Table);
|
||||||
|
EXPECT_TRUE(tabs[1].lazy);
|
||||||
|
EXPECT_EQ(tabs[1].loaderKey.toStdString(), "gr.rows");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,14 @@ TEST(AsyncRepoDispatch, KnownKeysReturnNonNullHandle) {
|
||||||
DetailLoad* measRows = repo.loadAsync("ert_measurement.rows", "ds1");
|
DetailLoad* measRows = repo.loadAsync("ert_measurement.rows", "ds1");
|
||||||
ASSERT_NE(measRows, nullptr);
|
ASSERT_NE(measRows, nullptr);
|
||||||
measRows->abort();
|
measRows->abort();
|
||||||
|
|
||||||
|
DetailLoad* grBar = repo.loadAsync("gr.bar", "ds1");
|
||||||
|
ASSERT_NE(grBar, nullptr);
|
||||||
|
grBar->abort();
|
||||||
|
|
||||||
|
DetailLoad* grRows = repo.loadAsync("gr.rows", "ds1");
|
||||||
|
ASSERT_NE(grRows, nullptr);
|
||||||
|
grRows->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未知 loaderKey 抛 std::runtime_error。
|
// 未知 loaderKey 抛 std::runtime_error。
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
#include "dto/GrMeasurementDto.hpp"
|
||||||
|
|
||||||
|
using namespace geopro::data::dto;
|
||||||
|
using geopro::core::TableColumnKind;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 取自真实夹具 tests/fixtures/dd/ert-gr-rows.json 的 data 数组(裁剪至 20 行,逐字一致)。
|
||||||
|
// 与 test_measurement_dto.cpp 同法:内联夹具,避免引入 fixture 路径编译定义。
|
||||||
|
const char* kGrRows = R"([
|
||||||
|
{ "electrodeId": 1, "testDate": "5/26/12", "testTime": "18:25:15", "p1Rg": 1385, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 2, "testDate": "5/26/12", "testTime": "18:25:16", "p1Rg": 3444, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 3, "testDate": "5/26/12", "testTime": "18:25:17", "p1Rg": 737, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 4, "testDate": "5/26/12", "testTime": "18:25:18", "p1Rg": 813, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 5, "testDate": "5/26/12", "testTime": "18:25:19", "p1Rg": 577, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 6, "testDate": "5/26/12", "testTime": "18:25:20", "p1Rg": 652, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 7, "testDate": "5/26/12", "testTime": "18:25:20", "p1Rg": 696, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 8, "testDate": "5/26/12", "testTime": "18:25:21", "p1Rg": 523, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 9, "testDate": "5/26/12", "testTime": "18:25:22", "p1Rg": 587, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 10, "testDate": "5/26/12", "testTime": "18:25:23", "p1Rg": 968, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 11, "testDate": "5/26/12", "testTime": "18:25:24", "p1Rg": 1701, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 12, "testDate": "5/26/12", "testTime": "18:25:25", "p1Rg": 1951, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 13, "testDate": "5/26/12", "testTime": "18:25:26", "p1Rg": 1405, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 14, "testDate": "5/26/12", "testTime": "18:25:27", "p1Rg": 1166, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 15, "testDate": "5/26/12", "testTime": "18:25:28", "p1Rg": 1222, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 16, "testDate": "5/26/12", "testTime": "18:25:29", "p1Rg": 1043, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 17, "testDate": "5/26/12", "testTime": "18:25:30", "p1Rg": 1272, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 18, "testDate": "5/26/12", "testTime": "18:25:30", "p1Rg": 2150, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 19, "testDate": "5/26/12", "testTime": "18:25:31", "p1Rg": 1806, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 20, "testDate": "5/26/12", "testTime": "18:25:32", "p1Rg": 971, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" }
|
||||||
|
])";
|
||||||
|
|
||||||
|
QJsonArray grRows() { return QJsonDocument::fromJson(kGrRows).array(); }
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(GrDto, ParsesBarSingleP1Series) {
|
||||||
|
const QJsonArray rows = grRows();
|
||||||
|
ASSERT_EQ(rows.size(), 20);
|
||||||
|
|
||||||
|
auto bar = parseGrBar(rows);
|
||||||
|
|
||||||
|
// 类目 "#1".."#20"。
|
||||||
|
ASSERT_EQ(bar.categories.size(), 20u);
|
||||||
|
EXPECT_EQ(bar.categories.front().toStdString(), "#1");
|
||||||
|
EXPECT_EQ(bar.categories.back().toStdString(), "#20");
|
||||||
|
|
||||||
|
// 轴标题。
|
||||||
|
EXPECT_EQ(bar.xTitle.toStdString(), std::string("电极点"));
|
||||||
|
EXPECT_EQ(bar.yTitle.toStdString(), std::string("电阻(单位:欧姆)"));
|
||||||
|
|
||||||
|
// 单系列 P1(p2Rg 全 null → 无 P2),色 #5470c6。
|
||||||
|
ASSERT_EQ(bar.series.size(), 1u);
|
||||||
|
const auto& s = bar.series.front();
|
||||||
|
EXPECT_EQ(s.name.toStdString(), "P1");
|
||||||
|
EXPECT_EQ(s.color.toStdString(), "#5470c6");
|
||||||
|
ASSERT_EQ(s.values.size(), 20u);
|
||||||
|
EXPECT_DOUBLE_EQ(s.values.front(), 1385.0); // 首行 p1Rg
|
||||||
|
EXPECT_DOUBLE_EQ(s.values[1], 3444.0);
|
||||||
|
EXPECT_DOUBLE_EQ(s.values.back(), 971.0); // 第 20 行 p1Rg
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrDto, AddsP2SeriesWhenAnyNonNull) {
|
||||||
|
// 合成:含一行 p2Rg 非空 → 应建 P2 系列(分组柱)。
|
||||||
|
const char* json = R"([
|
||||||
|
{"electrodeId":1,"p1Rg":100,"p1RgStatus":"正常","p2Rg":null,"p2RgStatus":""},
|
||||||
|
{"electrodeId":2,"p1Rg":200,"p1RgStatus":"正常","p2Rg":55,"p2RgStatus":"正常"}
|
||||||
|
])";
|
||||||
|
const QJsonArray rows = QJsonDocument::fromJson(json).array();
|
||||||
|
|
||||||
|
auto bar = parseGrBar(rows);
|
||||||
|
ASSERT_EQ(bar.series.size(), 2u);
|
||||||
|
EXPECT_EQ(bar.series[0].name.toStdString(), "P1");
|
||||||
|
EXPECT_EQ(bar.series[1].name.toStdString(), "P2");
|
||||||
|
ASSERT_EQ(bar.series[1].values.size(), 2u);
|
||||||
|
EXPECT_DOUBLE_EQ(bar.series[1].values[1], 55.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrDto, ParsesEmptyArrayToValidEmptyPayloads) {
|
||||||
|
// 空/畸形响应:空 QJsonArray → 有效但空的载荷,不崩。
|
||||||
|
const QJsonArray empty;
|
||||||
|
|
||||||
|
auto bar = parseGrBar(empty);
|
||||||
|
EXPECT_EQ(bar.categories.size(), 0u);
|
||||||
|
EXPECT_EQ(bar.series.size(), 1u); // P1 恒有(系列存在但无值)
|
||||||
|
EXPECT_EQ(bar.series.front().values.size(), 0u);
|
||||||
|
EXPECT_EQ(bar.xTitle.toStdString(), std::string("电极点"));
|
||||||
|
|
||||||
|
auto t = parseGrTable(empty);
|
||||||
|
EXPECT_EQ(t.columns.size(), 7u); // 7 列硬编码,恒有
|
||||||
|
EXPECT_EQ(t.rows.size(), 0u);
|
||||||
|
EXPECT_EQ(t.total, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GrDto, ParsesTableSevenFixedColumns) {
|
||||||
|
const QJsonArray rows = grRows();
|
||||||
|
auto t = parseGrTable(rows);
|
||||||
|
|
||||||
|
// 7 固定列,无 Toggle 列(gr 不复用 measurement 的开关列)。
|
||||||
|
ASSERT_EQ(t.columns.size(), 7u);
|
||||||
|
EXPECT_EQ(t.columns[0].title.toStdString(), "ID");
|
||||||
|
EXPECT_EQ(t.columns[1].title.toStdString(), std::string("日期"));
|
||||||
|
EXPECT_EQ(t.columns[2].title.toStdString(), std::string("时间"));
|
||||||
|
EXPECT_EQ(t.columns[3].title.toStdString(), "P1 Rg(Ω)");
|
||||||
|
EXPECT_EQ(t.columns[4].title.toStdString(), std::string("P1状态"));
|
||||||
|
EXPECT_EQ(t.columns[5].title.toStdString(), "P2 Rg(Ω)");
|
||||||
|
EXPECT_EQ(t.columns[6].title.toStdString(), std::string("P2状态"));
|
||||||
|
for (const auto& c : t.columns) EXPECT_EQ(c.kind, TableColumnKind::Text);
|
||||||
|
|
||||||
|
// 20 行;首行 ID=1, p1Rg=1385, P1状态=正常;p2Rg null → 空串。
|
||||||
|
ASSERT_EQ(t.rows.size(), 20u);
|
||||||
|
ASSERT_EQ(t.rows[0].size(), 7u);
|
||||||
|
EXPECT_EQ(t.rows[0][0].toStdString(), "1");
|
||||||
|
EXPECT_EQ(t.rows[0][3].toStdString(), "1385");
|
||||||
|
EXPECT_EQ(t.rows[0][4].toStdString(), std::string("正常"));
|
||||||
|
EXPECT_TRUE(t.rows[0][5].isEmpty()); // p2Rg null
|
||||||
|
EXPECT_EQ(t.total, 20);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "成功",
|
||||||
|
"__total": 40,
|
||||||
|
"data": [
|
||||||
|
{ "electrodeId": 1, "testDate": "5/26/12", "testTime": "18:25:15", "p1Rg": 1385, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 2, "testDate": "5/26/12", "testTime": "18:25:16", "p1Rg": 3444, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 3, "testDate": "5/26/12", "testTime": "18:25:17", "p1Rg": 737, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 4, "testDate": "5/26/12", "testTime": "18:25:18", "p1Rg": 813, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 5, "testDate": "5/26/12", "testTime": "18:25:19", "p1Rg": 577, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 6, "testDate": "5/26/12", "testTime": "18:25:20", "p1Rg": 652, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 7, "testDate": "5/26/12", "testTime": "18:25:20", "p1Rg": 696, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 8, "testDate": "5/26/12", "testTime": "18:25:21", "p1Rg": 523, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 9, "testDate": "5/26/12", "testTime": "18:25:22", "p1Rg": 587, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 10, "testDate": "5/26/12", "testTime": "18:25:23", "p1Rg": 968, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 11, "testDate": "5/26/12", "testTime": "18:25:24", "p1Rg": 1701, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 12, "testDate": "5/26/12", "testTime": "18:25:25", "p1Rg": 1951, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 13, "testDate": "5/26/12", "testTime": "18:25:26", "p1Rg": 1405, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 14, "testDate": "5/26/12", "testTime": "18:25:27", "p1Rg": 1166, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 15, "testDate": "5/26/12", "testTime": "18:25:28", "p1Rg": 1222, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 16, "testDate": "5/26/12", "testTime": "18:25:29", "p1Rg": 1043, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 17, "testDate": "5/26/12", "testTime": "18:25:30", "p1Rg": 1272, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 18, "testDate": "5/26/12", "testTime": "18:25:30", "p1Rg": 2150, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 19, "testDate": "5/26/12", "testTime": "18:25:31", "p1Rg": 1806, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" },
|
||||||
|
{ "electrodeId": 20, "testDate": "5/26/12", "testTime": "18:25:32", "p1Rg": 971, "p1RgStatus": "正常", "p2Rg": null, "p2RgStatus": "" }
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue