Преглед изворни кода

fix: 修正运行时报警列表显示

main
suyu пре 1 месец
родитељ
комит
cda8f0eef0
5 измењених фајлова са 69 додато и 64 уклоњено
  1. +4
    -23
      app/src/services/alarm_service.cpp
  2. +0
    -6
      app/src/services/alarm_service.h
  3. +20
    -12
      app/src/ui/hmi_editor_widget.cpp
  4. +12
    -14
      app/tests/alarm_service_tests.cpp
  5. +33
    -9
      app/tests/main_window_tests.cpp

+ 4
- 23
app/src/services/alarm_service.cpp Прегледај датотеку

@@ -29,21 +29,19 @@ void AlarmService::refresh()
records_.begin(), records_.end(),
[&definition](const AlarmRecord &record)
{
return record.definitionId == definition.id && record.active;
return record.definitionId == definition.id;
});
if (*active && current == records_.end())
{
records_.insert(
records_.begin(),
AlarmRecord{definition.id, definition.message, true, false, now, {}});
AlarmRecord{definition.id, definition.message, false, now});
}
else if (!*active && current != records_.end())
{
current->active = false;
current->clearedAt = now;
records_.erase(current);
}
}
trimHistory();
}

bool AlarmService::acknowledge(const std::string &definition_id)
@@ -52,7 +50,7 @@ bool AlarmService::acknowledge(const std::string &definition_id)
records_.begin(), records_.end(),
[&definition_id](const AlarmRecord &candidate)
{
return candidate.definitionId == definition_id && candidate.active;
return candidate.definitionId == definition_id;
});
if (record == records_.end())
{
@@ -95,20 +93,3 @@ std::optional<bool> AlarmService::evaluate(
}
return std::nullopt;
}

void AlarmService::trimHistory()
{
std::size_t history_count = static_cast<std::size_t>(std::count_if(
records_.cbegin(), records_.cend(),
[](const AlarmRecord &record) { return !record.active; }));
for (auto record = records_.end();
record != records_.begin() && history_count > kHistoryLimit;)
{
--record;
if (!record->active)
{
record = records_.erase(record);
--history_count;
}
}
}

+ 0
- 6
app/src/services/alarm_service.h Прегледај датотеку

@@ -1,7 +1,6 @@
#pragma once

#include <chrono>
#include <cstddef>
#include <optional>
#include <string>
#include <vector>
@@ -14,10 +13,8 @@ struct AlarmRecord
{
std::string definitionId;
std::string message;
bool active = true;
bool acknowledged = false;
std::chrono::system_clock::time_point occurredAt;
std::optional<std::chrono::system_clock::time_point> clearedAt;
};

class AlarmService
@@ -34,9 +31,6 @@ public:

private:
std::optional<bool> evaluate(const AlarmDefinition &definition) const;
void trimHistory();

static constexpr std::size_t kHistoryLimit = 20;

const ProjectService &project_service_;
RegisterRepository &repository_;


+ 20
- 12
app/src/ui/hmi_editor_widget.cpp Прегледај датотеку

@@ -224,7 +224,7 @@ public:
rect.height() - kAlarmHeaderHeight),
Qt::AlignCenter,
runtime_active_ ? QObject::tr("暂无报警")
: QObject::tr("运行时显示报警记录"));
: QObject::tr("运行时显示当前报警"));
break;
}
for (int row = 0; row < visible_rows; ++row)
@@ -237,18 +237,12 @@ public:
kAlarmRowHeight);
painter->fillRect(
row_rect,
record.active
? QColor(QStringLiteral("#fde8e8"))
: QColor(QStringLiteral("#f0f2f3")));
QColor(QStringLiteral("#fde8e8")));
painter->setPen(QColor(QStringLiteral("#d3d8dc")));
painter->drawLine(row_rect.bottomLeft(), row_rect.bottomRight());
painter->setPen(record.active
? QColor(QStringLiteral("#8d1f1f"))
: QColor(QStringLiteral("#667078")));
const QString state = record.active
? (record.acknowledged ? QObject::tr("已确认")
: QObject::tr("未确认"))
: QObject::tr("已清除");
painter->setPen(QColor(QStringLiteral("#8d1f1f")));
const QString state = record.acknowledged
? QObject::tr("已确认") : QObject::tr("未确认");
painter->drawText(
QRectF(row_rect.left() + 5, row_rect.top(), 58, row_rect.height()),
Qt::AlignVCenter | Qt::AlignLeft,
@@ -341,6 +335,7 @@ public:
page_hovered_ = false;
button_pressed_ = false;
}
updateAlarmVisibility();
update();
}

@@ -356,6 +351,7 @@ public:
void setAlarmRecords(const std::vector<AlarmRecord> &records)
{
alarm_records_ = records;
updateAlarmVisibility();
update();
}

@@ -394,7 +390,7 @@ protected:
if (row >= 0 && row < static_cast<int>(alarm_records_.size()))
{
const AlarmRecord &record = alarm_records_[static_cast<std::size_t>(row)];
if (record.active && !record.acknowledged && alarm_acknowledge_)
if (!record.acknowledged && alarm_acknowledge_)
{
alarm_acknowledge_(record.definitionId);
}
@@ -501,6 +497,17 @@ protected:
}

private:
void updateAlarmVisibility()
{
if (control_.type != HmiControlType::AlarmList)
{
return;
}
const bool has_active_alarm = !alarm_records_.empty();
setVisible(editing_enabled_ || !runtime_active_ || has_active_alarm);
setZValue(runtime_active_ && has_active_alarm ? 1000.0 : 0.0);
}

// 控件本体范围保持领域坐标和尺寸语义不变
QRectF controlRect() const
{
@@ -631,6 +638,7 @@ void HmiEditorWidget::setRuntimeActive(bool active)
if (control_item != nullptr)
{
control_item->setRuntimeValue(false, 0, false);
control_item->setAlarmRecords({});
}
}
}


+ 12
- 14
app/tests/alarm_service_tests.cpp Прегледај датотеку

@@ -34,7 +34,7 @@ void require(bool condition, const std::string &message)
}
}

const AlarmRecord *findActiveRecord(
const AlarmRecord *findRecord(
const AlarmService &service, const std::string &definition_id)
{
const auto &records = service.records();
@@ -42,7 +42,7 @@ const AlarmRecord *findActiveRecord(
records.cbegin(), records.cend(),
[&definition_id](const AlarmRecord &candidate)
{
return candidate.definitionId == definition_id && candidate.active;
return candidate.definitionId == definition_id;
});
return record == records.cend() ? nullptr : &*record;
}
@@ -92,29 +92,27 @@ void testAlarmDefinitionsAndRuntimeLifecycle()
repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 80);
repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 10);
alarm_service.refresh();
require(findActiveRecord(alarm_service, m_result.id) != nullptr
&& findActiveRecord(alarm_service, high_result.id) != nullptr
&& findActiveRecord(alarm_service, low_result.id) != nullptr,
require(findRecord(alarm_service, m_result.id) != nullptr
&& findRecord(alarm_service, high_result.id) != nullptr
&& findRecord(alarm_service, low_result.id) != nullptr,
"all three alarm conditions must create active records");
require(alarm_service.acknowledge(m_result.id)
&& findActiveRecord(alarm_service, m_result.id)->acknowledged,
&& findRecord(alarm_service, m_result.id)->acknowledged,
"an active alarm must be locally acknowledged");

repository.writeBit(RegisterAddress{RegisterArea::M, 0}, false);
repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 79);
repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 11);
alarm_service.refresh();
require(std::none_of(
alarm_service.records().cbegin(),
alarm_service.records().cend(),
[](const AlarmRecord &record) { return record.active; }),
"cleared conditions must move active alarms into history");
require(alarm_service.records().empty(),
"cleared conditions must immediately remove active alarms");

repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true);
alarm_service.refresh();
require(findActiveRecord(alarm_service, m_result.id) != nullptr
&& alarm_service.records().size() == 4,
"a repeated alarm must create a new occurrence and keep history");
require(findRecord(alarm_service, m_result.id) != nullptr
&& alarm_service.records().size() == 1
&& !alarm_service.records().front().acknowledged,
"a repeated alarm must create a new unacknowledged occurrence");

alarm_service.reset();
require(alarm_service.records().empty(),


+ 33
- 9
app/tests/main_window_tests.cpp Прегледај датотеку

@@ -428,14 +428,20 @@ void testRuntimeAlarmListInteraction()
require(alarm_result.succeeded,
"runtime alarm test must create an M alarm definition");

repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true);
alarm_service.refresh();
require(alarm_service.records().size() == 1
&& alarm_service.records().front().active,
"an active M alarm must be available to AlarmList");

HmiEditorWidget view(editor_service, runtime_service, alarm_service);
view.setPageId(page_id);
QGraphicsItem *alarm_item = nullptr;
for (QGraphicsItem *item : view.scene()->items())
{
if (item->zValue() >= 0.0)
{
alarm_item = item;
break;
}
}
require(alarm_item != nullptr && alarm_item->isVisible(),
"AlarmList must remain visible while editing");

view.setEditingEnabled(false);
view.setRuntimeActive(true);
view.setRuntimeWriteEnabled(false);
@@ -443,6 +449,17 @@ void testRuntimeAlarmListInteraction()
view.resize(900, 560);
view.show();
QApplication::processEvents();
require(!alarm_item->isVisible(),
"AlarmList must be hidden when runtime has no active alarm");

repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true);
alarm_service.refresh();
view.refreshRuntimeValues();
QApplication::processEvents();
require(alarm_service.records().size() == 1,
"an active M alarm must be available to AlarmList");
require(alarm_item->isVisible() && alarm_item->zValue() > 0.0,
"AlarmList must appear above the page for an active alarm");

const QPoint header_position = view.mapFromScene(QPointF(
alarm_list->bounds.x + 20.0, alarm_list->bounds.y + 10.0));
@@ -461,9 +478,16 @@ void testRuntimeAlarmListInteraction()
repository.writeBit(RegisterAddress{RegisterArea::M, 0}, false);
alarm_service.refresh();
view.refreshRuntimeValues();
require(!alarm_service.records().front().active
&& alarm_service.records().front().clearedAt.has_value(),
"clearing the M condition must retain a cleared history record");
QApplication::processEvents();
require(alarm_service.records().empty(),
"clearing the M condition must remove the alarm immediately");
require(!alarm_item->isVisible(),
"AlarmList must hide after the final alarm recovers");

view.setRuntimeActive(false);
view.setEditingEnabled(true);
require(alarm_item->isVisible(),
"AlarmList must become visible again after returning to editing");
}

void testModeActionsControlEditingAvailability()


Loading…
Откажи
Сачувај