You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

611 lines
22 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "weighui.h"
#include "./ui_weighui.h"
#include <QTimer>
#include <QPixmap>
#include <QDebug>
#include <QCloseEvent>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTransform>
#include <QLabel>
#include <QFrame>
#include <thread>
#include <mutex>
#include <queue>
#include <utility>
#include "dds/Subscriber.hpp"
#include "dds/msg.hpp"
// DdsMsgData 静态成员定义
std::queue<WeighingSystem::WeightInfoOk> DdsMsgData::WeightInfoOk_queue_;
std::queue<WeighingSystem::WeightInfoError> DdsMsgData::WeightInfoError_queue_;
std::queue<WeighingSystem::SummaryUpdate> DdsMsgData::SummaryUpdate_queue_;
std::queue<WeighingSystem::ScaleInfo> DdsMsgData::ScaleInfo_queue_;
std::mutex DdsMsgData::queue_cv_mtx_;
// 实时弹窗重量阈值: weigh 程序中 ScaleInfo.Value 单位为吨, 10kg = 0.01 吨
static const float kWeightThresholdTon = 0.01f; // 10 kg
WeighUI::WeighUI(int domainId, QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::WeighUI)
, m_imageLabel(nullptr)
, m_popupWidget(nullptr)
, m_ddsSubscriber(nullptr)
, m_ddsThread(nullptr)
, m_soundEffect(nullptr)
, m_soundReadCardStart(nullptr)
, m_soundError(nullptr)
// 实时重量显示(嵌入主窗口内的子控件, 不进窗口管理器; 视觉与 Warning 弹窗一致: 离屏渲染 -> rotate(90))
, m_realTimeWeightContainer(nullptr)
, m_realTimeWeightImage(nullptr)
, m_realTimePopupSource(nullptr)
, m_realTimeSourceWeight(nullptr)
, m_realTimeLastWeightText()
, m_realTimePopupEnabled(false)
, m_realTimePopupVisible(false)
, m_domainId(domainId)
{
qDebug() << "=== WeighUI 构造函数开始 ===";
ui->setupUi(this);
qDebug() << "setupUi 完成, 窗口大小:" << width() << "x" << height();
this->setAttribute(Qt::WA_DeleteOnClose);
// 设置窗口为全屏无边框(适应 800x1280 竖屏)
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
this->resize(800, 1280);
this->showFullScreen();
this->raise();
this->activateWindow();
qDebug() << "窗口大小已调整为:" << width() << "x" << height();
// 隐藏菜单栏和状态栏
ui->menubar->hide();
ui->statusbar->hide();
// 设置 centralwidget 透明并隐藏
ui->centralwidget->setStyleSheet("background-color: transparent;");
ui->centralwidget->hide();
// 创建图片显示Label
m_imageLabel = new QLabel(this);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_imageLabel->setScaledContents(true);
m_imageLabel->setGeometry(0, 0, width(), height());
m_imageLabel->show();
// 加载图片并旋转90度
QString imagePath = QCoreApplication::applicationDirPath() + "/123.png";
qDebug() << "尝试加载图片:" << imagePath;
QPixmap pixmap(imagePath);
if (!pixmap.isNull()) {
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = pixmap.transformed(transform);
m_imageLabel->setPixmap(rotatedPixmap.scaled(width(), height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
qDebug() << "图片加载并旋转成功!";
} else {
qDebug() << "图片加载失败!";
}
// 创建定时器
m_closeTimer = new QTimer(this);
m_pollTimer = new QTimer(this);
m_errorPollTimer = new QTimer(this);
m_summaryPollTimer = new QTimer(this);
m_realTimePollTimer = new QTimer(this);
m_enablePopupTimer = new QTimer(this);
connect(m_closeTimer, &QTimer::timeout, this, &WeighUI::onPopupClosed);
connect(m_pollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoOk);
connect(m_errorPollTimer, &QTimer::timeout, this, &WeighUI::onCheckWeightInfoError);
connect(m_summaryPollTimer, &QTimer::timeout, this, &WeighUI::onCheckSummaryUpdate);
connect(m_realTimePollTimer, &QTimer::timeout, this, &WeighUI::onCheckRealTimeWeight);
connect(m_enablePopupTimer, &QTimer::timeout, this, &WeighUI::onEnableRealTimePopup);
// 加载称重完成提示音
QString soundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound19.wav";
qDebug() << "加载提示音:" << soundPath;
m_soundEffect = new QSoundEffect(this);
m_soundEffect->setSource(QUrl::fromLocalFile(soundPath));
m_soundEffect->setVolume(1.0f);
// 加载读卡成功提示音
QString readCardSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/readcardstart.wav";
qDebug() << "加载读卡提示音:" << readCardSoundPath;
m_soundReadCardStart = new QSoundEffect(this);
m_soundReadCardStart->setSource(QUrl::fromLocalFile(readCardSoundPath));
m_soundReadCardStart->setVolume(1.0f);
// 加载称重异常提示音
QString errorSoundPath = QCoreApplication::applicationDirPath() + "/voice2.0/sound51.wav";
qDebug() << "加载称重异常提示音:" << errorSoundPath;
m_soundError = new QSoundEffect(this);
m_soundError->setSource(QUrl::fromLocalFile(errorSoundPath));
m_soundError->setVolume(1.0f);
// 构造实时重量显示控件(嵌入主窗口内的子控件, 默认隐藏)
buildRealTimeWeightDisplay();
// 启动 DDS 订阅 WeightInfoOk
startDdsSubscriber();
// 启动轮询定时器(每 200ms 检查队列)
m_pollTimer->start(200);
m_errorPollTimer->start(200);
m_summaryPollTimer->start(200);
m_realTimePollTimer->start(200);
// 启动5秒后启用实时弹窗的定时器
m_enablePopupTimer->setSingleShot(true);
m_enablePopupTimer->start(5000);
qDebug() << "=== WeighUI 构造函数结束(等待 WeightInfoOk 触发弹窗)===";
}
WeighUI::~WeighUI()
{
stopDdsSubscriber();
destroyRealTimeWeightDisplay();
delete ui;
}
void WeighUI::startDdsSubscriber()
{
try {
qDebug() << "DDS Subscriber 启动, domainId=" << m_domainId;
m_ddsSubscriber = std::make_shared<DdsSubscriberApp>(m_domainId);
m_ddsThread = new std::thread([this]() { m_ddsSubscriber->run(); });
qDebug() << "DDS Subscriber 启动成功";
} catch (const std::exception& e) {
qDebug() << "DDS Subscriber 启动失败:" << e.what();
m_ddsSubscriber = nullptr;
m_ddsThread = nullptr;
}
}
void WeighUI::stopDdsSubscriber()
{
if (m_ddsSubscriber) {
m_ddsSubscriber->stop();
}
if (m_ddsThread) {
if (m_ddsThread->joinable()) {
m_ddsThread->join();
}
delete m_ddsThread;
m_ddsThread = nullptr;
}
m_ddsSubscriber.reset();
}
void WeighUI::closeEvent(QCloseEvent *event)
{
stopDdsSubscriber();
if (m_pollTimer) m_pollTimer->stop();
if (m_errorPollTimer) m_errorPollTimer->stop();
if (m_summaryPollTimer) m_summaryPollTimer->stop();
if (m_realTimePollTimer) m_realTimePollTimer->stop();
if (m_enablePopupTimer) m_enablePopupTimer->stop();
if (m_closeTimer) m_closeTimer->stop();
destroyRealTimeWeightDisplay();
hide();
QTimer::singleShot(100, [this]() {
deleteLater();
});
event->accept();
}
void WeighUI::onCheckSummaryUpdate()
{
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
while (!DdsMsgData::SummaryUpdate_queue_.empty())
{
// 全部清空,只关心 "有一次 SummaryUpdate" 即可触发声音(避免重复堆积)
DdsMsgData::SummaryUpdate_queue_.pop();
hasData = true;
}
}
if (hasData) {
if (m_soundReadCardStart && m_soundReadCardStart->isLoaded()) {
m_soundReadCardStart->stop();
m_soundReadCardStart->play();
}
}
}
void WeighUI::onCheckWeightInfoOk()
{
QString warningText;
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
if (!DdsMsgData::WeightInfoOk_queue_.empty()) {
WeighingSystem::WeightInfoOk info = std::move(DdsMsgData::WeightInfoOk_queue_.front());
DdsMsgData::WeightInfoOk_queue_.pop();
lock.unlock();
warningText = QString::fromStdString(info.Warning());
if (warningText.isEmpty()) {
warningText = "称重完成";
}
hasData = true;
qDebug() << "[WeighUI] 收到 WeightInfoOk, Warning =" << warningText;
}
}
if (hasData) {
// 播放提示音
if (m_soundEffect && m_soundEffect->isLoaded()) {
m_soundEffect->stop();
m_soundEffect->play();
}
onShowPopup(warningText);
}
}
void WeighUI::onCheckWeightInfoError()
{
QString warningText;
bool hasData = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
if (!DdsMsgData::WeightInfoError_queue_.empty()) {
WeighingSystem::WeightInfoError info = std::move(DdsMsgData::WeightInfoError_queue_.front());
DdsMsgData::WeightInfoError_queue_.pop();
lock.unlock();
warningText = QString::fromStdString(info.Warning());
if (warningText.isEmpty()) {
warningText = "称重异常";
}
hasData = true;
qDebug() << "[WeighUI] 收到 WeightInfoError, Warning =" << warningText;
}
}
if (hasData) {
// 弹窗样式与 WeightInfoOk 完全一致: 复用 onShowPopup
if (m_soundError && m_soundError->isLoaded()) {
m_soundError->stop();
m_soundError->play();
}
onShowPopup(warningText);
}
}
void WeighUI::onShowPopup(const QString& warningText)
{
if (m_popupWidget) {
m_popupWidget->close();
m_popupWidget->deleteLater();
m_popupWidget = nullptr;
}
// 创建弹窗容器横屏尺寸宽800高500
QWidget *popupSource = new QWidget(this);
popupSource->setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
popupSource->resize(800, 500);
// 内部布局:上方标题 + 下方信息Warning 文本)
QVBoxLayout *mainLayout = new QVBoxLayout(popupSource);
mainLayout->setContentsMargins(20, 20, 20, 20);
mainLayout->setSpacing(20);
mainLayout->setAlignment(Qt::AlignCenter);
// 上方:标题
QLabel *titleLabel = new QLabel("称重提示", popupSource);
titleLabel->setAlignment(Qt::AlignCenter);
titleLabel->setStyleSheet("font-size: 48px; font-weight: bold; color: #4169E1; background: transparent;");
QFrame *line = new QFrame(popupSource);
line->setFrameShape(QFrame::HLine);
line->setStyleSheet("background-color: #4169E1;");
// 下方信息Warning 文本)
QLabel *warningLabel = new QLabel(warningText, popupSource);
warningLabel->setAlignment(Qt::AlignCenter);
warningLabel->setWordWrap(true);
warningLabel->setStyleSheet("font-size: 42px; font-weight: bold; color: #FF4500; background: transparent;");
mainLayout->addWidget(titleLabel);
mainLayout->addWidget(line);
mainLayout->addWidget(warningLabel);
// 把弹窗渲染成图片
popupSource->show();
popupSource->repaint();
QPixmap popupPixmap = popupSource->grab();
// 关闭原弹窗
popupSource->close();
popupSource->deleteLater();
// 旋转90度跟图片一致
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = popupPixmap.transformed(transform, Qt::SmoothTransformation);
// 创建新弹窗显示旋转后的图片
m_popupWidget = new QWidget(this);
m_popupWidget->setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
m_popupWidget->setStyleSheet(
"background-color: rgba(240, 248, 255, 240);"
"border: 3px solid #4169E1;"
"border-radius: 15px;"
);
m_popupWidget->resize(rotatedPixmap.width(), rotatedPixmap.height());
QLabel *imageLabel = new QLabel(m_popupWidget);
imageLabel->setPixmap(rotatedPixmap);
imageLabel->setScaledContents(true);
imageLabel->setGeometry(0, 0, rotatedPixmap.width(), rotatedPixmap.height());
m_popupWidget->move((width() - rotatedPixmap.width()) / 2, (height() - rotatedPixmap.height()) / 2);
m_popupWidget->show();
// 5秒后关闭弹窗
m_closeTimer->setSingleShot(true);
m_closeTimer->start(5000);
}
void WeighUI::onPopupClosed()
{
if (m_popupWidget) {
m_popupWidget->close();
m_popupWidget->deleteLater();
m_popupWidget = nullptr;
}
}
// ----------------------------------------------------------------------------
// 实时重量显示: 在主窗口内嵌入一个 500x800 的子控件,
// 不再是独立的顶层窗口, 完全不经过 X11/Wayland 窗口管理器,
// 避免 show/hide 与 Warning 弹窗抢焦点、避免被窗口管理器反复隐藏.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// 实时重量显示: 完全复用 Warning 弹窗的视觉风格
// 离屏源 800x500 横屏 -> 离屏渲染 -> 旋转 90° -> 嵌入主窗口的 500x800 容器内显示
// 容器直接挂在 m_imageLabel 下, 不是顶层窗口, 不会被 X11/Wayland 窗口管理器反复隐藏
// ----------------------------------------------------------------------------
void WeighUI::buildRealTimeWeightDisplay()
{
if (m_realTimeWeightContainer) {
return;
}
// ---------- 1. 离屏渲染源 (800x500 横屏, 持久, 不 show) ----------
// 与 Warning 弹窗的 popupSource 结构完全一致: 标题 + 分隔线 + 文本
m_realTimePopupSource = new QWidget(); // 不挂任何 parent, 避免被 WeighUI 路由
m_realTimePopupSource->setAttribute(Qt::WA_DontShowOnScreen, true);
// 关键: 用 setFixedSize 锁定 800x500, 不要让 layout/adjustSize 把它缩成 251x211
m_realTimePopupSource->setFixedSize(800, 500);
QVBoxLayout *srcLayout = new QVBoxLayout(m_realTimePopupSource);
srcLayout->setContentsMargins(20, 20, 20, 20);
srcLayout->setSpacing(20);
srcLayout->setAlignment(Qt::AlignCenter);
// 标题 (与 Warning 弹窗标题样式一致)
QLabel *srcTitle = new QLabel(QStringLiteral("实时称重"), m_realTimePopupSource);
srcTitle->setAlignment(Qt::AlignCenter);
srcTitle->setStyleSheet(
"font-size: 48px;"
"font-weight: bold;"
"color: #4169E1;"
"background: transparent;"
);
srcLayout->addWidget(srcTitle);
// 分隔线 (与 Warning 弹窗一致)
QFrame *srcLine = new QFrame(m_realTimePopupSource);
srcLine->setFrameShape(QFrame::HLine);
srcLine->setStyleSheet("background-color: #4169E1;");
srcLayout->addWidget(srcLine);
// 重量 Label (与 Warning 弹窗中 warning 文本一致)
m_realTimeSourceWeight = new QLabel(QStringLiteral("重量: -- kg"), m_realTimePopupSource);
m_realTimeSourceWeight->setAlignment(Qt::AlignCenter);
m_realTimeSourceWeight->setWordWrap(true);
m_realTimeSourceWeight->setStyleSheet(
"font-size: 42px;"
"font-weight: bold;"
"color: #FF4500;"
"background: transparent;"
);
srcLayout->addWidget(m_realTimeSourceWeight);
// 强制布局完成, 这样 render() 能拿到正确尺寸
m_realTimePopupSource->adjustSize();
// ---------- 2. 显示容器 (500x800, 嵌入 m_imageLabel) ----------
if (m_imageLabel) {
m_realTimeWeightContainer = new QWidget(m_imageLabel);
} else {
m_realTimeWeightContainer = new QWidget(this);
}
m_realTimeWeightContainer->setFixedSize(500, 800);
m_realTimeWeightContainer->setStyleSheet(
"QWidget {"
" background-color: rgb(240, 248, 255);"
" border: 3px solid #4169E1;"
" border-radius: 15px;"
"}"
);
m_realTimeWeightContainer->setAutoFillBackground(true);
// 容器内只有一个 QLabel, 显示旋转后的 pixmap
m_realTimeWeightImage = new QLabel(m_realTimeWeightContainer);
m_realTimeWeightImage->setScaledContents(true);
m_realTimeWeightImage->setAlignment(Qt::AlignCenter);
m_realTimeWeightImage->setGeometry(0, 0, 500, 800);
// 把容器放到 m_imageLabel 中央
int cx = 0, cy = 0;
if (m_imageLabel) {
cx = (m_imageLabel->width() - 500) / 2;
cy = (m_imageLabel->height() - 800) / 2;
} else {
cx = (width() - 500) / 2;
cy = (height() - 800) / 2;
}
m_realTimeWeightContainer->setGeometry(cx, cy, 500, 800);
// 初始渲染一次 (空文本), 让首帧就有图
{
QPixmap pixmap = renderRealTimePopupContent(QStringLiteral("-- kg"));
m_realTimeWeightImage->setPixmap(pixmap);
}
// 初始隐藏, 等 onCheckRealTimeWeight 触发显示
m_realTimeWeightContainer->hide();
m_realTimePopupVisible = false;
qDebug() << "[WeighUI] buildRealTimeWeightDisplay: container"
<< m_realTimeWeightContainer
<< "parent=" << m_realTimeWeightContainer->parent()
<< "size=" << m_realTimeWeightContainer->size()
<< "source=" << m_realTimePopupSource
<< "sourceSize=" << m_realTimePopupSource->size();
}
void WeighUI::destroyRealTimeWeightDisplay()
{
if (m_realTimeWeightContainer) {
m_realTimeWeightContainer->deleteLater();
m_realTimeWeightContainer = nullptr;
}
m_realTimeWeightImage = nullptr;
if (m_realTimePopupSource) {
m_realTimePopupSource->deleteLater();
m_realTimePopupSource = nullptr;
}
m_realTimeSourceWeight = nullptr;
m_realTimeLastWeightText.clear();
m_realTimePopupVisible = false;
}
void WeighUI::onEnableRealTimePopup()
{
m_realTimePopupEnabled = true;
qDebug() << "[WeighUI] 实时重量显示已启用 (启动 5 秒后)";
// 立即触发一次轮询, 根据当前最新重量决定是否显示
onCheckRealTimeWeight();
}
// 渲染一次实时弹窗内容, 返回旋转 90 度后的 QPixmap (与 Warning 弹窗同款)
// 注意: 使用 m_realTimePopupSource(持久离屏源)避免每帧 new/delete 控件;
// 文本未变化时复用上次缓存的 pixmap, 不重复渲染.
QPixmap WeighUI::renderRealTimePopupContent(const QString& weightText)
{
if (!m_realTimePopupSource || !m_realTimeSourceWeight) {
return QPixmap();
}
QString fullText = QStringLiteral("重量: ") + weightText;
if (fullText == m_realTimeLastWeightText && !m_realTimeLastWeightText.isEmpty()) {
// 文本未变: 直接复用上次的 pixmap, 不重新 render/rotate
// (避免每帧无意义的渲染开销, 也避免 setPixmap 重复触发 paintEvent 造成闪烁)
return m_realTimeLastPixmap;
}
// 1) 更新离屏源上的文本
m_realTimeSourceWeight->setText(fullText);
// 2) 离屏源渲染到 pixmap
QPixmap sourcePixmap(m_realTimePopupSource->size());
sourcePixmap.fill(QColor(240, 248, 255)); // 与 Warning 弹窗背景一致
m_realTimePopupSource->render(&sourcePixmap);
// 3) 旋转 90 度 (与 Warning 弹窗一致)
QTransform transform;
transform.rotate(90);
QPixmap rotatedPixmap = sourcePixmap.transformed(transform, Qt::SmoothTransformation);
m_realTimeLastWeightText = fullText;
m_realTimeLastPixmap = rotatedPixmap;
return rotatedPixmap;
}
void WeighUI::onCheckRealTimeWeight()
{
// 消费队列中所有 ScaleInfo, 只保留最新一条
float latestWeightTon = 0.0f;
bool hasWeight = false;
{
std::unique_lock<std::mutex> lock(DdsMsgData::queue_cv_mtx_);
while (!DdsMsgData::ScaleInfo_queue_.empty()) {
WeighingSystem::ScaleInfo info = std::move(DdsMsgData::ScaleInfo_queue_.front());
DdsMsgData::ScaleInfo_queue_.pop();
latestWeightTon = info.Value();
hasWeight = true;
}
}
// 启动未满5秒: 直接返回, 显示控件保持隐藏
if (!m_realTimePopupEnabled) {
return;
}
// 控件未构造: 防御性返回
if (!m_realTimeWeightContainer || !m_realTimeWeightImage) {
return;
}
// 还没有收到重量数据: 保持当前状态(不要 hide, 这是上一次闪烁的根因)
// DDS ScaleInfo 的发布频率和我们的轮询频率不一定同步,
// 如果每 200ms 轮询时 DDS 还没发, 队列就会是空的, 之前的代码会 hide + 重置 flag,
// 下一次 DDS 推到数据就又 show 一次, 形成 show/hide/show/hide 的闪烁.
// 正确做法: 没有新数据时维持上一次状态不变, 直到明确收到 "<10kg" 才 hide.
if (!hasWeight) {
return;
}
// DDS ScaleInfo.Value 的单位本身就是"吨" (见 kWeightThresholdTon 的注释)
// 显示也用吨, 保留 2 位小数: 1.42 吨
// - 不要 ×1000, 否则会把"1.42 吨"显示成"1420 kg"
QString weightText = QString::number(latestWeightTon, 'f', 2) + QStringLiteral("");
// 重量 < 10 kg (即 < 0.01 吨) -> 隐藏控件 (这是唯一会真正 hide 的路径)
if (latestWeightTon < kWeightThresholdTon) {
if (m_realTimeWeightContainer->isVisible()) {
m_realTimeWeightContainer->hide();
m_realTimePopupVisible = false;
qDebug() << "[WeighUI] 实时重量 < 10kg, 界面隐藏, weight=" << latestWeightTon << "";
}
return;
}
// 重量 >= 0.01 吨 (10 kg) -> 在主窗口内显示实时重量控件
// 关键: 离屏渲染 + 旋转 90° 与 Warning 弹窗完全一致, 视觉风格统一;
// 容器嵌入 m_imageLabel, 不是顶层窗口, 不进 X11/Wayland 窗口管理器;
// show() 只在"从无到有"时调一次, 后续只 setPixmap 不打日志, 避免闪烁.
QPixmap pixmap = renderRealTimePopupContent(weightText);
if (!pixmap.isNull() && m_realTimeWeightImage) {
m_realTimeWeightImage->setPixmap(pixmap);
}
if (!m_realTimeWeightContainer->isVisible()) {
m_realTimeWeightContainer->show();
m_realTimeWeightContainer->raise(); // 提到 m_imageLabel 内最前
m_realTimeWeightContainer->update(); // 强制重绘, 防止某些嵌入式合成器延迟
m_realTimePopupVisible = true;
qDebug() << "[WeighUI] 实时重量 >= 10kg, 界面显示, weight=" << latestWeightTon << ""
<< " container.isVisible=" << m_realTimeWeightContainer->isVisible()
<< " pos=" << m_realTimeWeightContainer->pos()
<< " size=" << m_realTimeWeightContainer->size()
<< " pixmap.size=" << pixmap.size();
}
// 已经在显示: 仅 setPixmap 更新, 不调 show, 不打日志, 避免闪烁
}