fix(nmNum): 修正流量数据读取并优化包含井表格

- 按 GaugeDataEx2 实际内容判断单相与多相流量
- 将多相数据中的空相按零流量处理
- 优化包含井表格样式、序号和自适应尺寸
- 隐藏不可用的压力流量下拉项
- 更新中文翻译文件
feature/nmNum-multi-gauge-record
lh 4 weeks ago
parent 2be80141cd
commit 34d745ff60

Binary file not shown.

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "nmDefines.h"
@ -26,14 +26,14 @@ struct nmPressureGaugeRecord {
/**
* @brief 工程井下一条流量测量记录的只读快照。
* @note 单相记录解析 GaugeData 并按井别归入对应相;多相记录从
* GaugeDataEx2 的 N 行数据中按 [段时长、油、气、水] 解析全部相。
* @note GaugeDataEx2 非空时按 N 行 [段时长、油、气、水] 解析多相数据;
* 否则解析 GaugeData,并按井别归入对应的单相流量。
*/
struct nmFlowGaugeRecord {
QString sGaugeCode; // 对应 ZxDataGaugeF::getCode(),选择持久化键
QString sGaugeName; // 对应 ZxDataGaugeF::getGaugeName(),界面展示用
QString sGaugeTime; // 对应 ZxDataGaugeF::getGaugeTime(),界面展示用
bool bMultiPhase; // 对应 ZxDataGaugeF::getMultiPhase()
bool bMultiPhase; // 实际是否采用 GaugeDataEx2 多相表,不依赖框架多相标志
NM_GAUGE_RECORD_STATUS eStatus; // 记录可用状态
QVector<QPointF> vecOilPoints; // 油相流量点,允许首项为 (0,0) 占位点
QVector<QPointF> vecGasPoints; // 气相流量点,允许首项为 (0,0) 占位点

@ -82,6 +82,8 @@ private:
void setupLayouts();
/** @brief 连接按钮及表格交互信号。 */
void setupConnections();
/** @brief 根据列宽和候选井数量设置受屏幕范围约束的初始窗口尺寸。 */
void adjustDialogSizeToTable();
/** @brief 将一条候选井数据写入指定表格行。 */
void addWellDataToTable(const WellDataRow& data, int row);

@ -18,6 +18,7 @@
#include "ZxBaseUtil.h"
#include <QSet>
#include <QThread>
#include <QVariant>
#include "zxLogInstance.h"
#include "nmDataReservoir.h"
@ -192,6 +193,21 @@ static NM_GAUGE_RECORD_STATUS readBlockAndClassify(
? NM_GaugeRecord_Usable : NM_GaugeRecord_Corrupt;
}
// 油水等非全相记录会把未使用相保存为空单元格,数值层按零产量接收。
// 非空内容仍必须是有效数字,避免把真正损坏的数据静默转换为零。
static bool readOptionalPhaseRate(const QVariant& oValue, double& dRate)
{
if(!oValue.isValid() || oValue.isNull() ||
oValue.toString().trimmed().isEmpty()) {
dRate = 0.0;
return true;
}
bool bOk = false;
dRate = oValue.toDouble(&bOk);
return bOk;
}
// 框架的 GaugeDataEx2 保存完整多相表,格式为 N 行 x 4 列;
// 每行依次为 [段时长、油、气、水]。数值模块读取时拆成三组同轴流量点,
// 不根据井别或非零值删改任一相的数据。
@ -231,14 +247,14 @@ static bool readMultiPhaseFlowTable(
nPointIndex < nPointCount;
++nPointIndex) {
bool bTimeOk = false;
bool bOilOk = false;
bool bGasOk = false;
bool bWaterOk = false;
const QVector<QVariant>& vecRow = vvecRows[nPointIndex];
const double dTime = vecRow[0].toDouble(&bTimeOk);
const double dOil = vecRow[1].toDouble(&bOilOk);
const double dGas = vecRow[2].toDouble(&bGasOk);
const double dWater = vecRow[3].toDouble(&bWaterOk);
double dOil = 0.0;
double dGas = 0.0;
double dWater = 0.0;
const bool bOilOk = readOptionalPhaseRate(vecRow[1], dOil);
const bool bGasOk = readOptionalPhaseRate(vecRow[2], dGas);
const bool bWaterOk = readOptionalPhaseRate(vecRow[3], dWater);
if(!bTimeOk || !bOilOk || !bGasOk || !bWaterOk) {
vecOilPoints.clear();
vecGasPoints.clear();
@ -400,7 +416,9 @@ bool nmDataAnalyzeManager::readFrameworkWellCurves(
oRecord.sGaugeCode = pGaugeF->getCode();
oRecord.sGaugeName = pGaugeF->getGaugeName();
oRecord.sGaugeTime = pGaugeF->getGaugeTime();
oRecord.bMultiPhase = pGaugeF->getMultiPhase();
// 框架多相标志可能与实际存储不一致,正式读取只依据数据块:
// Ex2 非空表示多相表,否则按井别解析 GaugeData 单相主块。
oRecord.bMultiPhase = !pGaugeF->getGaugeDataEx2().isEmpty();
oRecord.eStatus = oRecord.bMultiPhase
? classifyMultiPhaseFlowRecord(
pGaugeF, oRecord.vecOilPoints,

@ -3,10 +3,11 @@
#include "nmDataWellBase.h"
#include <QAbstractItemView>
#include <QApplication>
#include <QCheckBox>
#include <QDesktopWidget>
#include <QHBoxLayout>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QStyle>
#include <QWidget>
namespace
@ -124,37 +125,6 @@ QString gaugeRecordLabel(const QString& sGaugeName,
return sLabel;
}
// 异常记录仍显示在下拉列表中,后缀用于说明其不可用于当前选择的原因。
QString recordStatusSuffix(NM_GAUGE_RECORD_STATUS eStatus)
{
switch(eStatus) {
case NM_GaugeRecord_Empty:
return nmWxIncludeOtherWells::tr(" (Empty)");
case NM_GaugeRecord_Corrupt:
return nmWxIncludeOtherWells::tr(" (Corrupt)");
case NM_GaugeRecord_InvalidIdentity:
return nmWxIncludeOtherWells::tr(" (Invalid)");
default:
return QString();
}
}
// Qt 4.8 的 QComboBox 没有直接禁用单项的接口,通过其标准项模型设置标志。
void setComboItemEnabled(QComboBox* pComboBox, int nIndex, bool bEnabled)
{
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(
pComboBox == nullptr ? nullptr : pComboBox->model());
QStandardItem* pItem = pModel == nullptr ? nullptr : pModel->item(nIndex);
if(pItem == nullptr) {
return;
}
if(bEnabled) {
pItem->setFlags(pItem->flags() | Qt::ItemIsEnabled);
} else {
pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
}
}
// 流动段索引是一基索引;统计段数时必须先排除框架可能写入的公共占位行。
int flowRecordSegmentCount(const nmFlowGaugeRecord& oRecord)
{
@ -252,9 +222,9 @@ void nmWxIncludeOtherWells::initUI()
this->setupConnections();
this->setWindowTitle(tr("Include Other Wells"));
// 缩小时依靠横向滚动保留稳定列宽,不让中英文标题挤压下拉内容。
this->setMinimumSize(760, 280);
this->resize(1240, 420);
// 初始尺寸跟随列宽和井数量,手动缩小时仍由滚动条保护表头及下拉内容。
this->setMinimumSize(720, 240);
this->adjustDialogSizeToTable();
}
void nmWxIncludeOtherWells::setupTable()
@ -262,14 +232,54 @@ void nmWxIncludeOtherWells::setupTable()
m_pTableWidget = new QTableWidget(this);
m_pTableWidget->setColumnCount(ColumnCount);
m_pTableWidget->setRowCount(0);
m_pTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
// 本表以复选框和下拉框作为操作入口,不使用表格行选中态。
m_pTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
m_pTableWidget->setFocusPolicy(Qt::NoFocus);
m_pTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pTableWidget->setSortingEnabled(false);
m_pTableWidget->setDragEnabled(false);
m_pTableWidget->setAcceptDrops(false);
m_pTableWidget->setDropIndicatorShown(false);
m_pTableWidget->setAlternatingRowColors(true);
m_pTableWidget->setWordWrap(false);
m_pTableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
m_pTableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
// 与编辑井流量明细表使用相同的调色板、表头和网格边框。
// 表格内下拉框采用透明无边框样式,使交替行色连续显示。
m_pTableWidget->setStyleSheet(
"QTableWidget {"
" background-color: palette(base);"
" alternate-background-color: rgb(232, 232, 232);"
" gridline-color: palette(midlight);"
" border: 1px solid palette(mid);"
"}"
"QHeaderView {"
" background-color: palette(button);"
" color: palette(button-text);"
"}"
"QHeaderView::section {"
" background-color: palette(button);"
" color: palette(button-text);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}"
"QTableCornerButton::section {"
" background-color: palette(base);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}"
"QTableWidget QComboBox {"
" border: none;"
" background: transparent;"
" padding-left: 6px;"
"}"
"QTableWidget QComboBox::drop-down {"
" border: none;"
"}");
QStringList listHeaders;
listHeaders << tr("Name") << tr("Well category") << tr("Included")
<< tr("Oil production") << tr("Gas production")
@ -277,6 +287,7 @@ void nmWxIncludeOtherWells::setupTable()
<< tr("Flow segment");
m_pTableWidget->setHorizontalHeaderLabels(listHeaders);
m_pTableWidget->horizontalHeader()->setVisible(true);
m_pTableWidget->horizontalHeader()->setMovable(false);
m_pTableWidget->horizontalHeader()->setMinimumSectionSize(64);
m_pTableWidget->horizontalHeader()->setStretchLastSection(false);
m_pTableWidget->horizontalHeader()->setResizeMode(
@ -284,25 +295,21 @@ void nmWxIncludeOtherWells::setupTable()
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnWellCategory, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnIncluded, QHeaderView::Fixed);
ColumnIncluded, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnOilRate, QHeaderView::Interactive);
ColumnOilRate, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnGasRate, QHeaderView::Interactive);
ColumnGasRate, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnWaterRate, QHeaderView::Interactive);
ColumnWaterRate, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnPressure, QHeaderView::Interactive);
ColumnPressure, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnFlowSegment, QHeaderView::Fixed);
m_pTableWidget->setColumnWidth(ColumnIncluded, 76);
m_pTableWidget->setColumnWidth(ColumnOilRate, 180);
m_pTableWidget->setColumnWidth(ColumnGasRate, 180);
m_pTableWidget->setColumnWidth(ColumnWaterRate, 180);
m_pTableWidget->setColumnWidth(ColumnPressure, 190);
m_pTableWidget->setColumnWidth(ColumnFlowSegment, 96);
m_pTableWidget->verticalHeader()->setVisible(false);
ColumnFlowSegment, QHeaderView::ResizeToContents);
m_pTableWidget->verticalHeader()->setVisible(true);
m_pTableWidget->verticalHeader()->setMovable(false);
m_pTableWidget->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
m_pTableWidget->verticalHeader()->setDefaultSectionSize(32);
m_pTableWidget->verticalHeader()->setMinimumSectionSize(28);
@ -396,7 +403,73 @@ void nmWxIncludeOtherWells::setupTable()
pEmptyItem->setFlags(Qt::NoItemFlags);
pEmptyItem->setTextAlignment(Qt::AlignCenter);
m_pTableWidget->setItem(0, 0, pEmptyItem);
// 空状态行不是候选井,不显示容易产生误解的行号“1”。
m_pTableWidget->verticalHeader()->setVisible(false);
} else {
// Qt 默认按一基显示纵向表头;宽度随最大行号位数增长。
const int nRowNumberWidth = m_pTableWidget->fontMetrics().width(
QString::number(m_vecWellData.size())) + 18;
m_pTableWidget->verticalHeader()->setFixedWidth(
qMax(32, nRowNumberWidth));
}
// 单元格控件全部创建完成后再按真实表头和下拉内容计算列宽。
m_pTableWidget->resizeColumnsToContents();
}
void nmWxIncludeOtherWells::adjustDialogSizeToTable()
{
if(m_pTableWidget == nullptr || m_pMainLayout == nullptr) {
return;
}
int nTableWidth = m_pTableWidget->frameWidth() * 2;
if(m_pTableWidget->verticalHeader()->isVisible()) {
nTableWidth += m_pTableWidget->verticalHeader()->width();
}
for(int nColumn = 0; nColumn < ColumnCount; ++nColumn) {
nTableWidth += m_pTableWidget->columnWidth(nColumn);
}
const int nVisibleRows = qMin(qMax(1, m_pTableWidget->rowCount()), 10);
int nTableHeight = m_pTableWidget->frameWidth() * 2 +
m_pTableWidget->horizontalHeader()->sizeHint().height() +
nVisibleRows * m_pTableWidget->verticalHeader()->defaultSectionSize();
const int nScrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
if(m_pTableWidget->rowCount() > nVisibleRows) {
nTableWidth += nScrollBarExtent;
}
int nLeft = 0;
int nTop = 0;
int nRight = 0;
int nBottom = 0;
m_pMainLayout->getContentsMargins(&nLeft, &nTop, &nRight, &nBottom);
const int nLayoutWidth = nLeft + nRight;
const int nButtonHeight = qMax(m_pOkButton->sizeHint().height(),
m_pCancelButton->sizeHint().height());
const int nLayoutHeight = nTop + nBottom + nButtonHeight +
m_pMainLayout->spacing();
QRect oAvailableGeometry;
QDesktopWidget* pDesktop = QApplication::desktop();
if(pDesktop != nullptr) {
oAvailableGeometry = pDesktop->availableGeometry(this);
}
if(!oAvailableGeometry.isValid()) {
oAvailableGeometry = QRect(0, 0, 1280, 720);
}
const int nMaximumWidth = qMax(640, oAvailableGeometry.width() * 9 / 10);
const int nMaximumHeight = qMax(260, oAvailableGeometry.height() * 4 / 5);
int nDialogWidth = qMin(nTableWidth + nLayoutWidth, nMaximumWidth);
if(nTableWidth + nLayoutWidth > nDialogWidth) {
nTableHeight += nScrollBarExtent;
}
const int nDialogHeight = qMin(nTableHeight + nLayoutHeight,
nMaximumHeight);
resize(qMax(minimumWidth(), nDialogWidth),
qMax(minimumHeight(), nDialogHeight));
}
void nmWxIncludeOtherWells::addWellDataToTable(
@ -405,6 +478,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(
QTableWidgetItem* pNameItem = new QTableWidgetItem(data.m_sName);
pNameItem->setData(Qt::UserRole, data.m_sWellCode);
pNameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
pNameItem->setToolTip(data.m_sName);
m_pTableWidget->setItem(row, ColumnName, pNameItem);
QTableWidgetItem* pCategoryItem = new QTableWidgetItem(
@ -433,6 +507,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(
QComboBox* pRateCombo = new QComboBox(m_pTableWidget);
pRateCombo->setProperty("row", row);
pRateCombo->setProperty("phase", static_cast<int>(arrPhases[nIndex]));
pRateCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
pRateCombo->setMaxVisibleItems(16);
connect(pRateCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onRateSourceChanged(int)));
@ -441,6 +516,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(
QComboBox* pPressureCombo = new QComboBox(m_pTableWidget);
pPressureCombo->setProperty("row", row);
pPressureCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
pPressureCombo->setMaxVisibleItems(16);
connect(pPressureCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPressureSourceChanged(int)));
@ -448,6 +524,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(
QComboBox* pSegmentCombo = new QComboBox(m_pTableWidget);
pSegmentCombo->setProperty("row", row);
pSegmentCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
connect(pSegmentCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onFlowSegmentChanged(int)));
m_pTableWidget->setCellWidget(row, ColumnFlowSegment, pSegmentCombo);
@ -512,18 +589,15 @@ void nmWxIncludeOtherWells::populateRateCombo(
pComboBox->clear();
pComboBox->addItem(tr("None"), QString());
// 三个分相下拉框都列出完整记录集合,但当前相无数据的记录保持可见且禁用。
// 选择控件只展示当前相真正可用的记录;异常记录仍可在“检查数据”中查看。
for(int nIndex = 0; nIndex < data.m_vecFlowRecords.size(); ++nIndex) {
const nmFlowGaugeRecord& oRecord = data.m_vecFlowRecords[nIndex];
const bool bSelectable = isSelectableFlowPhase(oRecord, ePhase);
QString sLabel = gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
sLabel += recordStatusSuffix(oRecord.eStatus);
if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
sLabel += tr(" (No data)");
if(!isSelectableFlowPhase(oRecord, ePhase)) {
continue;
}
pComboBox->addItem(sLabel, oRecord.sGaugeCode);
setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
pComboBox->addItem(gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime),
oRecord.sGaugeCode);
}
int nCurrentIndex = 0;
@ -549,15 +623,12 @@ void nmWxIncludeOtherWells::populatePressureCombo(
pComboBox->addItem(tr("None"), QString());
for(int nIndex = 0; nIndex < data.m_vecPressureRecords.size(); ++nIndex) {
const nmPressureGaugeRecord& oRecord = data.m_vecPressureRecords[nIndex];
const bool bSelectable = isSelectablePressureRecord(oRecord);
QString sLabel = gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
sLabel += recordStatusSuffix(oRecord.eStatus);
if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
sLabel += tr(" (No data)");
if(!isSelectablePressureRecord(oRecord)) {
continue;
}
pComboBox->addItem(sLabel, oRecord.sGaugeCode);
setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
pComboBox->addItem(gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime),
oRecord.sGaugeCode);
}
const int nCurrentIndex = pComboBox->findData(
@ -656,7 +727,6 @@ void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
return;
}
oData.m_bIsIncluded = state == Qt::Checked;
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex)
@ -695,7 +765,6 @@ void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex)
// 自动使用同一记录;新记录没有的相自动回到“无”。
normalizeFlowSelection(oData);
refreshRowEditors(nRow);
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex)
@ -711,7 +780,6 @@ void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex)
m_vecWellData[nRow].m_sPressureGaugeCode =
pComboBox->itemData(nIndex).toString();
pComboBox->setToolTip(pComboBox->currentText());
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex)
@ -727,7 +795,6 @@ void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex)
m_vecWellData[nRow].m_nFlowSegmentIndex =
pComboBox->itemData(nIndex).toInt();
pComboBox->setToolTip(pComboBox->currentText());
m_pTableWidget->selectRow(nRow);
}
QVector<WellDataRow> nmWxIncludeOtherWells::getWellData() const
@ -749,7 +816,7 @@ void nmWxIncludeOtherWells::setupLayouts()
m_pButtonLayout->addWidget(m_pCancelButton);
m_pMainLayout = new QVBoxLayout(this);
m_pMainLayout->addWidget(m_pTableWidget);
m_pMainLayout->addWidget(m_pTableWidget, 1);
m_pMainLayout->addLayout(m_pButtonLayout);
setLayout(m_pMainLayout);
}

Loading…
Cancel
Save