活动介绍

verticalLayout = new QVBoxLayout(centralWidget); verticalLayout->setSpacing(0); verticalLayout->setContentsMargins(11, 11, 11, 11); verticalLayout->setObjectName(QStringLiteral("verticalLayout")); verticalLayout->setContentsMargins(2, 0, 2, 2); 是什么意思

时间: 2025-08-06 07:03:34 浏览: 7
<think>我们正在分析一段Qt布局管理器设置的代码。这段代码创建了一个垂直布局管理器,并对其进行了多项设置。 代码逐行解释如下: 1. `verticalLayout = new QVBoxLayout(centralWidget);` 创建一个新的垂直布局管理器(QVBoxLayout)对象,并将其父对象设置为centralWidget。这意味着这个布局将管理centralWidget内部的子部件。 2. `verticalLayout->setSpacing(0);` 设置布局内各部件之间的间距为0像素。这样,相邻部件之间将没有空隙。 3. `verticalLayout->setContentsMargins(11, 11, 11, 11);` 设置布局的内容边距(即布局边界与内部部件之间的空间)为11像素(左、上、右、下各11像素)。 4. `verticalLayout->setObjectName(QStringLiteral("verticalLayout"));` 给布局对象设置一个对象名,这样可以在代码中通过对象名来查找这个布局(例如在样式表中使用,或者通过findChild方法获取)。 5. `verticalLayout->setContentsMargins(2, 0, 2, 2);` 再次设置内容边距,这次将左、右、下边距设置为2像素,上边距设置为0像素。注意:这行代码会覆盖之前设置的边距(11,11,11,11),所以最终边距是(2,0,2,2)。 因此,最终效果是: - 布局内各部件之间没有间隔(spacing=0) - 布局的边界:上边距0像素,左、右、下边距各2像素。 为什么先设置一次边距再覆盖? 可能的原因是:初始设置可能是默认值或者设计器生成的初始值,然后根据实际需求调整。但最终以最后一次设置为准。 在Qt中,布局管理器用于自动排列子部件。垂直布局(QVBoxLayout)将子部件按垂直方向依次排列。 示例图示: +---------------------------------+ | 上边距0(没有上边距) | | +-----------------------------+ | | | 第一个子部件 | | | +-----------------------------+ | | 间距0(没有间隔) | | +-----------------------------+ | | | 第二个子部件 | | | +-----------------------------+ | | ... | | +-----------------------------+ | | | 最后一个子部件 | | | +-----------------------------+ | | 下边距2 | +---------------------------------+ 左边距2 右边距2 注意:内容边距是布局与其父部件(这里是centralWidget)边界之间的空间,而spacing是布局内相邻部件之间的空间。 应用场景: - 设置spacing为0适用于需要紧密排列的部件(例如多个自定义面板拼合) - 调整边距可以控制整个布局在父部件中的位置,比如去掉上边距可以让顶部子部件紧贴父部件上边缘 代码中可能存在的问题: 重复设置边距,第二次设置覆盖第一次,所以第一次设置是多余的。通常我们只设置一次。 在Qt Designer中自动生成的代码有时会保留初始设置,然后用户修改后生成新的设置,因此会出现覆盖的情况。 最终,这个垂直布局将应用于centralWidget,后续通过verticalLayout->addWidget()添加的部件将按照垂直方向紧密排列(无间隔),且整体在centralWidget中的位置是:上边缘紧贴,左、右、下边缘有2像素的边距。</think>这段代码是 Qt 框架中用于创建和配置垂直布局管理器(QVBoxLayout)的标准操作,具体含义如下: ```cpp // 1. 创建垂直布局管理器并关联到中央部件 verticalLayout = new QVBoxLayout(centralWidget); // 2. 设置布局内控件间的间距 verticalLayout->setSpacing(0); // 所有控件间垂直间距为0像素 // 3. 初始设置布局与父容器的边距 verticalLayout->setContentsMargins(11, 11, 11, 11); // 左、上、右、下各11像素 // 4. 为布局设置唯一标识名 verticalLayout->setObjectName(QStringLiteral("verticalLayout")); // 5. 重新设置布局边距(覆盖之前的设置) verticalLayout->setContentsMargins(2, 0, 2, 2); // 左2px, 上0px, 右2px, 下2px ``` ### 详细解析 1. **布局管理器创建** ```cpp verticalLayout = new QVBoxLayout(centralWidget); ``` - 创建 `QVBoxLayout` 垂直布局管理器 - 自动成为 `centralWidget` 的子对象 - 后续通过 `addWidget()` 添加的控件将按垂直方向排列 2. **间距控制** ```cpp verticalLayout->setSpacing(0); ``` | 间距值 | 效果 | |--------|------| | 0 | 所有控件紧密排列,无垂直间隙 | | >0 | 控件间有指定像素的间隔 | | <0 | 无效值,使用默认间距 | 3. **内容边距设置** ```cpp // 初始设置 setContentsMargins(11, 11, 11, 11); // 覆盖设置 setContentsMargins(2, 0, 2, 2); ``` - 最终生效的边距:**左2px, 上0px, 右2px, 下2px** - 参数顺序:`(左, 上, 右, 下)` - 上边距为0表示布局顶部紧贴父容器边缘 4. **对象标识名设置** ```cpp verticalLayout->setObjectName(QStringLiteral("verticalLayout")); ``` - 在代码中可通过 `findChild<QVBoxLayout*>("verticalLayout")` 查找 - 在样式表中可针对特定布局设置样式:`#verticalLayout { ... }` ### 布局效果示意图 ``` +---------------------------------+ | centralWidget (父容器) | | +---------------------------+ | | | verticalLayout (布局) | <- 上边距0px | | | | | | [控件1] | | | | | | <- 控件间距0px | | [控件2] | | | | | | | | [控件3] | | | | | | | +---------------------------+ | | 左2px 右2px | | 下边距2px| +---------------------------------+ ``` ### 实际应用场景 1. **监控系统界面布局** ```cpp verticalLayout->addWidget(videoDisplay); // 视频显示区域(占主要空间) verticalLayout->addWidget(controlPanel); // 控制面板(底部固定高度) ``` 2. **响应式设计** ```cpp // 设置控件拉伸因子 verticalLayout->setStretch(0, 8); // 视频区域占8份空间 verticalLayout->setStretch(1, 1); // 控制面板占1份空间 ``` 3. **动态调整** ```cpp // 运行时根据窗口大小调整边距 void resizeEvent(QResizeEvent* event) { if(width() > 1200) { verticalLayout->setContentsMargins(50, 0, 50, 20); } else { verticalLayout->setContentsMargins(2, 0, 2, 2); } } ``` ### 注意事项 1. **边距设置优先级** - 最后一次 `setContentsMargins()` 调用会覆盖之前设置 - 推荐直接设置最终需要的值,避免冗余设置 2. **布局嵌套** ```cpp // 复杂界面中的典型嵌套结构 QHBoxLayout* hLayout = new QHBoxLayout(); // 水平布局 hLayout->addWidget(leftPanel); hLayout->addWidget(rightPanel); verticalLayout->addLayout(hLayout); // 将水平布局加入垂直布局 verticalLayout->addWidget(statusBar); ``` 3. **样式表应用** ```cpp // 通过对象名设置特定布局样式 verticalLayout->setStyleSheet("#verticalLayout { background-color: #1E1E2D; }"); ``` 4. **布局管理最佳实践** - 优先使用布局管理器而非固定位置 - 结合大小策略(`QSizePolicy`)实现弹性布局 - 使用 `addStretch()` 填充空白区域
阅读全文

相关推荐

centralWidget = new QWidget(SurveillanceSystem); centralWidget->setObjectName(QStringLiteral("centralWidget")); centralWidget->setStyleSheet(QStringLiteral("")); verticalLayout = new QVBoxLayout(centralWidget); verticalLayout->setSpacing(0); verticalLayout->setContentsMargins(11, 11, 11, 11); verticalLayout->setObjectName(QStringLiteral("verticalLayout")); verticalLayout->setContentsMargins(2, 0, 2, 2); topWidget = new QWidget(centralWidget); topWidget->setObjectName(QStringLiteral("topWidget")); topWidget->setMinimumSize(QSize(0, 56)); topWidget->setMaximumSize(QSize(16777215, 56)); horizontalLayout = new QHBoxLayout(topWidget); horizontalLayout->setSpacing(8); horizontalLayout->setContentsMargins(11, 11, 11, 11); horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); horizontalLayout->setContentsMargins(16, 8, 10, 3); imageStackedWidget = new QStackedWidget(topWidget); imageStackedWidget->setObjectName(QStringLiteral("imageStackedWidget")); page = new QWidget(); page->setObjectName(QStringLiteral("page")); horizontalLayout_2 = new QHBoxLayout(page); horizontalLayout_2->setSpacing(0); horizontalLayout_2->setContentsMargins(11, 11, 11, 11); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); horizontalLayout_2->setContentsMargins(0, 0, 0, 0); stLogo = new QLabel(page); stLogo->setObjectName(QStringLiteral("stLogo")); QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy1.setHorizontalStretch(0); sizePolicy1.setVerticalStretch(0); sizePolicy1.setHeightForWidth(stLogo->sizePolicy().hasHeightForWidth()); stLogo->setSizePolicy(sizePolicy1); stLogo->setMinimumSize(QSize(104, 30)); stLogo->setMaximumSize(QSize(104, 30)); stLogo->setCursor(QCursor(Qt::PointingHandCursor)); stLogo->setStyleSheet(QStringLiteral("")); horizontalLayout_2->addWidget(stLogo); 这段代码是什么意思

#include "mainwindow.h" #include "ui_mainwindow.h" #include <QHBoxLayout> #include <QVBoxLayout> #include <QMessageBox> #include <QGroupBox> #include <QFormLayout> #include <QPixmap> #include <QSerialPortInfo> #include <QDateTime> #include <windows.h> #include <dwmapi.h> #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_serialWorker(nullptr) , timeoutTimer(new QTimer(this)) , currentActiveButton(nullptr) , workModeButton(nullptr) , m_isHandshakeSuccess(false) , sensorVoltageTimer(new QTimer(this)) , isGettingSensorVoltage(false) , m_isCISChecking(false) , m_agingTimer(new QTimer(this)) { this->setStyleSheet("QMainWindow{background-color:#F6F6F2;}"); if(HWND hwnd = (HWND)winId()) { BOOL darkMode = FALSE; DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, sizeof(darkMode)); } ui->setupUi(this); setWindowTitle(tr("点钞机")); setMinimumSize(800, 600); //主布局-左右分割1:3 mainLayout = new QHBoxLayout(); mainLayout->setSpacing(10); mainLayout->setStretch(0, 1); //左侧区域 mainLayout->setStretch(1, 2); //中间区域 mainLayout->setStretch(2, 1); //右侧区域 //========== 左侧区域 ========== QWidget *leftWidget = new QWidget(); QVBoxLayout *leftLayout = new QVBoxLayout(leftWidget); leftLayout->setSpacing(10); QGroupBox *portGroup = new QGroupBox(tr("串口设置")); portGroup->setStyleSheet("QGroupBox { background-color: #F6F6F2; border: 1px solid #ccc; border-radius: 5px; margin-top: 10px;color:#388087; font-weight: bold; }" "QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; background-color:#F6F6F2;font-weight: bold; }"); QVBoxLayout *portLayout = new QVBoxLayout(portGroup); portComboBox = new QComboBox(portGroup); portComboBox->setStyleSheet("background-color:#ebebe7;color:#388087;font-weight: bold; "); //波特率选择下拉框 baudRateComboBox = new QComboBox(portGroup); baudRateComboBox->setStyleSheet("background-color:#ebebe7;color:#388087;font-weight: bold; "); baudRateComboBox->addItem("115200", QSerialPort::Baud115200); baudRateComboBox->addItem("9600", QSerialPort::Baud9600); baudRateComboBox->setCurrentIndex(0); //默认115200 currentBaudRate = QSerialPort::Baud115200; //连接波特率变化信号 connect(baudRateComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::onBaudRateChanged); btnRefreshPorts = new QPushButton("刷新串口", portGroup); btnRefreshPorts->setStyleSheet("QPushButton {" "background-color:#e0e0e0;" "color:#388087;" "font-weight: bold;" "}" "QPushButton:hover{""background-color: #e0e0e0;color:#388087;" "}" "QPushButton:pressed {" "background-color: #a0a0a0;" "color:#388087;" "}"); btnConnect = new QPushButton("连接串口", portGroup); btnConnect->setStyleSheet("QPushButton {" "background-color:#e0e0e0;" "color:#388087;" "font-weight: bold;" "}" "QPushButton:hover{""background-color: #e0e0e0;color:#388087;" "}" "QPushButton:pressed {" "background-color: #a0a0a0;" "color:#388087;" "}"); portLayout->addWidget(portComboBox); portLayout->addWidget(baudRateComboBox); //添加波特率下拉框 portLayout->addWidget(btnRefreshPorts); portLayout->addWidget(btnConnect); portLayout->setStretch(0, 1); portLayout->setStretch(1, 1); portLayout->setStretch(2, 1); portLayout->setStretch(3, 1); leftLayout->addWidget(portGroup,2); QGroupBox *infoGroup = new QGroupBox("机器信息"); infoGroup->setStyleSheet("background-color:#F6F6F2;color:#388087;border: 1px solid #ccc;padding: 5px;font-weight: bold; "); QVBoxLayout *infoLayout = new QVBoxLayout(infoGroup); machineInfoLineEdit = new QTextEdit(); machineInfoLineEdit->setReadOnly(true); machineInfoLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QFont font; font.setPointSize(12); machineInfoLineEdit->setFont(font); //设置行间距 machineInfoLineEdit->setStyleSheet( "QTextEdit {" " line-height: 150%;" " background-color: #F6F6F2;" " border: 1px solid #ccc;" " padding: 5px;" " color: black;" " font-weight: bold; " "}" ); machineInfoLineEdit->setLineWrapMode(QTextEdit::WidgetWidth); machineInfoLineEdit->setWordWrapMode(QTextOption::WrapAnywhere); //machineInfoLineEdit->setMinimumHeight(200); machineInfoLineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); infoLayout->addWidget(machineInfoLineEdit); leftLayout->addWidget(infoGroup, 3); //-------------计数显示区域-------------- CountingInfoWidget *countingWidget = new CountingInfoWidget(); QVBoxLayout *countLayout = new QVBoxLayout(countingWidget); countLayout->setSpacing(10); countLayout->setContentsMargins(10, 10, 10, 10); countLayout->setStretch(0, 3); //表格区域 countLayout->setStretch(1, 1); //按钮区域 //创建并配置表格 denominationTable = new QTableWidget(countingWidget); denominationTable->setObjectName("denominationTable"); denominationTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); denominationTable->setColumnCount(3); //金额、数量、单位 denominationTable->setRowCount(0); denominationTable->verticalHeader()->setVisible(false); //隐藏行头 denominationTable->horizontalHeader()->setVisible(false); //隐藏列头 denominationTable->setShowGrid(false); //隐藏网格线 denominationTable->setEditTriggers(QAbstractItemView::NoEditTriggers); //禁止编辑 denominationTable->setSelectionMode(QAbstractItemView::NoSelection); //禁止选择 denominationTable->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); //固定大小 //设置表格样式 denominationTable->setStyleSheet( "QTableWidget {" " background-color: transparent;" " border: none;" " color: #388087;" "}" "QTableWidget::item {" " padding: 2px;" " border: none;" "}" ); //设置列宽 denominationTable->setColumnWidth(0, 80); //金额列 denominationTable->setColumnWidth(1, 40); //数量列 denominationTable->setColumnWidth(2, 40); //单位列 //将表格添加到布局 //countLayout->addWidget(denominationTable, 3, Qt::AlignLeft | Qt::AlignTop); connect(countingWidget, &CountingInfoWidget::blankAreaClicked, this, &MainWindow::onAmountLabelClicked); //主布局:上部显示区域和下部按钮 QHBoxLayout *mainCountLayout = new QHBoxLayout(); //左侧布局 - 退钞数 QVBoxLayout *refundLayout = new QVBoxLayout(); QLabel* refundTitleLabel = new QLabel("退钞数"); //refundTitleLabel->setFixedWidth(120); refundTitleLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); refundTitleLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;font:14px;border: none;font-weight: bold; "); refundTitleLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); refundLabel = new ClickableLabel(); // refundLabel->setFixedWidth(90); refundLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); refundLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;padding: 5px;border: none;font:bold 17px;font-weight: bold; "); refundLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); //添加SN显示label QHBoxLayout *refundRowLayout = new QHBoxLayout(); //refundRowLayout->addWidget(refundLabel); snLabel = new QLabel(); snLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;padding: 5px;border: none;font:bold 12px;font-weight: bold; "); snLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); snLabel->setMinimumWidth(150); //为SN留出足够空间 refundRowLayout->addWidget(refundTitleLabel); refundRowLayout->addWidget(snLabel); refundLayout->addWidget(denominationTable, 1); refundLayout->addLayout(refundRowLayout,0); refundLayout->addWidget(refundLabel,0); //使用水平布局替代直接添加refundLabel //中间布局-弹性空间 QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); //右侧布局-包含金额和张数 QVBoxLayout *rightcountLayout = new QVBoxLayout(); //金额部分 QVBoxLayout *amountLayout = new QVBoxLayout(); QLabel* amountTitleLabel = new QLabel("金额"); amountTitleLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); amountTitleLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;font:14px;border: none;font-weight: bold; "); amountTitleLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); amountLabel = new ClickableLabel(); amountLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;padding: 5px;border: none;font:bold 17px;font-weight: bold; "); amountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); amountLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); amountLayout->addStretch(); //弹性空间使金额靠右 amountLayout->addWidget(amountTitleLabel); amountLayout->addWidget(amountLabel); rightcountLayout->addLayout(amountLayout); rightcountLayout->addStretch(); //张数部分 QVBoxLayout *sheetLayout = new QVBoxLayout(); QLabel* sheetTitleLabel = new QLabel("张数"); sheetTitleLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;font:14px;border: none;font-weight: bold; "); sheetTitleLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); sheetNumberLabel = new ClickableLabel(); sheetNumberLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;padding: 5px;border: none;font:bold 17px;font-weight: bold; "); sheetNumberLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); sheetTitleLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sheetNumberLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sheetLayout->addStretch(); //弹性空间使张数靠右 sheetLayout->addWidget(sheetTitleLabel); sheetLayout->addWidget(sheetNumberLabel); rightcountLayout->addLayout(sheetLayout); refundLabel->setText("0"); amountLabel->setText("0"); sheetNumberLabel->setText("0"); //设置标签可点击 refundLabel->setCursor(Qt::PointingHandCursor); amountLabel->setCursor(Qt::PointingHandCursor); sheetNumberLabel->setCursor(Qt::PointingHandCursor); //将各部分添加到主布局 mainCountLayout->addLayout(refundLayout,1); mainCountLayout->addItem(spacer); mainCountLayout->addLayout(rightcountLayout,1); //连接点击信号 connect(refundLabel, &ClickableLabel::clicked, this, &MainWindow::onRefundLabelClicked); connect(amountLabel, &ClickableLabel::clicked, this, &MainWindow::onAmountLabelClicked); connect(sheetNumberLabel, &ClickableLabel::clicked, this, &MainWindow::onSheetNumberLabelClicked); //将主显示布局添加到计数组 countLayout->addLayout(mainCountLayout,2); buttonStyle = "QPushButton {" "background-color: #f8f8f8;" "border: 1px solid #ccc;" "border-radius: 5px;" "padding: 8px;" "width: 120px;" "height: 50px;" "color:#388087;" "font-weight: bold; " "}" "QPushButton:hover {" "background-color: #f0f0f0;" "color:#388087;" "font-weight: bold; " "}" "QPushButton:pressed {" "background-color: #c0c0c0;" "color:#388087;" "font-weight: bold; " "}"; //添加重置按钮 resetCountButton = new QPushButton("重置计数"); resetCountButton->setStyleSheet(buttonStyle); resetCountButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); connect(resetCountButton, &QPushButton::clicked, this,&MainWindow::on_resetCountButton_clicked); countLayout->addWidget(resetCountButton); leftLayout->addWidget(countingWidget, 3); mainLayout->addWidget(leftWidget, 1); //========== 中间区域 ========== QWidget *centerWidget = new QWidget(); QVBoxLayout *centerLayout = new QVBoxLayout(centerWidget); centerLayout->setSpacing(10); //机器状态区域 QGroupBox *statusGroup = new QGroupBox("机器状态"); statusGroup->setStyleSheet("QGroupBox { background-color: #F6F6F2; border: 1px solid #ccc; border-radius: 5px; margin-top: 10px;color:#388087; font-weight: bold; }" "QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; background-color:#F6F6F2;font-weight: bold; }"); QHBoxLayout *statusLayout = new QHBoxLayout(statusGroup); machineStatusImage = new QLabel(); machineStatusImage->setAlignment(Qt::AlignCenter); machineStatusImage->setScaledContents(true); machineStatusImage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // QLabel* overlayLabel = new QLabel(machineStatusImage); // overlayLabel->setStyleSheet("background-color: transparent; color: red; font-weight: bold;"); // overlayLabel->setText("QT"); // overlayLabel->move(250, 110); // 设置位置 // overlayLabel->adjustSize(); // overlayLabel->setAttribute(Qt::WA_TransparentForMouseEvents); // 允许点击穿透 voltageTable = new QTableWidget(); voltageTable->setColumnCount(3); //voltageTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); voltageTable->setHorizontalHeaderLabels({"传感器ID", "传感器名称", "电压值(V)"}); voltageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); voltageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); voltageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); voltageTable->setStyleSheet( "QTableWidget {" " background-color: #F6F6F2;" " border: 1px solid #ccc;" " color: #388087;" "}" "QTableWidget::item {" " padding: 2px;" " border: none;" " background-color: #F6F6F2;" " color: #388087;" "}" "QTableWidget::item:selected {" " background-color: #F6F6F2;" " color: #388087;" "}" "QHeaderView {" " background-color: #F6F6F2;" //表头整体背景色 "}" "QScrollBar:vertical {" " background: #F6F6F2;" " width: 12px;" " margin: 0px 0px 0px 0px;" "}" "QHeaderView::section {" " background-color: #F6F6F2;" " padding: 4px;" " border: 1px solid #ccc;" " color: #388087;" "}" "QTableCornerButton::section {" " background-color: #F6F6F2;" //左上角空白区域 " border: 1px solid #ccc;" "}" ); voltageTable->setVisible(false); //初始隐藏表格 QPixmap pixmap(":/image/333.svg"); if(!pixmap.isNull()) { machineStatusImage->setMaximumSize(450, 700); QPixmap scaled = pixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); machineStatusImage->setPixmap(scaled); } else { machineStatusImage->setText("图片加载失败"); } statusLayout->addWidget(machineStatusImage, 3); statusLayout->addWidget(voltageTable, 1); centerLayout->addWidget(statusGroup, 3); //信息显示区域 QGroupBox *messageGroup = new QGroupBox(); messageGroup->setStyleSheet("background-color:#F6F6F2;color:#388087;border: 1px solid #ccc;padding: 5px;font-weight: bold; "); QVBoxLayout *messageLayout = new QVBoxLayout(messageGroup); //添加顶部布局,包含标题和清除按钮 QHBoxLayout *messageHeaderLayout = new QHBoxLayout(); QLabel *messageTitleLabel = new QLabel("信息显示"); messageTitleLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;font-weight: bold;"); QPushButton *clearButton = new QPushButton("清空"); clearButton->setStyleSheet("QPushButton {" "background-color: #f0f0f0;" "border: 1px solid #ccc;" "border-radius: 3px;" "padding: 3px 8px;" "color:#388087;" "font-weight: bold;" "}" "QPushButton:hover {" "background-color: #e0e0e0;" "}" "QPushButton:pressed {" "background-color: #a0a0a0;" "}"); clearButton->setFixedWidth(60); //设置固定宽度 messageHeaderLayout->addWidget(messageTitleLabel); messageHeaderLayout->addStretch(); messageHeaderLayout->addWidget(clearButton); messageLayout->addLayout(messageHeaderLayout); messageTextEdit = new QTextEdit(); messageTextEdit->setReadOnly(true); messageTextEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); messageTextEdit->setStyleSheet("QTextEdit { background-color: #F6F6F2; border: 1px solid #ccc; padding: 5px; color:black;font-weight: bold;/* font-family:宋体; */font-size:14px;}"); messageLayout->addWidget(messageTextEdit); //连接清除按钮的信号 connect(clearButton, &QPushButton::clicked, this, [this](){ messageTextEdit->clear(); }); centerLayout->addWidget(messageGroup, 2); mainLayout->addWidget(centerWidget, 2); //========== 右侧区域 ========== QWidget *rightWidget = new QWidget(); QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); rightLayout->setSpacing(10); //按钮区域 QGroupBox *buttonGroup = new QGroupBox("操作命令"); buttonGroup->setStyleSheet("QGroupBox { background-color: #F6F6F2; border: 1px solid #ccc black; border-radius: 5px; margin-top: 10px;color:#388087; font-weight: bold; }" "QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; background-color:#F6F6F2;font-weight: bold; }"); QVBoxLayout *buttonLayout = new QVBoxLayout(buttonGroup); commandStack = new QStackedWidget(); commandStack->addWidget(createFirstCommandPage()); //第一页 commandStack->addWidget((createSecondCommandPage())); //第二页 //添加导航按钮 QHBoxLayout* navLayout = new QHBoxLayout(); addNavigationButtons(navLayout); buttonLayout->addLayout(navLayout); buttonLayout->addWidget(commandStack); rightLayout->addWidget(buttonGroup); mainLayout->addWidget(rightWidget, 1); //设置中心窗口 QWidget *centralWidget = new QWidget(); centralWidget->setLayout(mainLayout); setCentralWidget(centralWidget); // QToolBar* langToolBar = addToolBar(tr("语言")); // QAction* zhAction = langToolBar->addAction("中文"); // QAction* enAction = langToolBar->addAction("英语"); // connect(zhAction, &QAction::triggered, this, [this]() { // loadLanguage("zh_CN"); // }); // connect(enAction, &QAction::triggered, this, [this]() { // loadLanguage("en"); // }); //连接信号槽 connect(btnRefreshPorts, &QPushButton::clicked, this, &MainWindow::refreshPorts); connect(btnConnect, &QPushButton::clicked, this, &MainWindow::toggleConnection); //初始化UI状态 refreshPorts(); enableControls(false); initializeErrorMaps(); //初始化传感器名称映射 sensorNameMap["0X01"] = "QTH"; sensorNameMap["0X02"] = "QTL"; sensorNameMap["0X03"] = "PS1L"; sensorNameMap["0X04"] = "PS1M"; sensorNameMap["0X05"] = "PS1R"; sensorNameMap["0X06"] = "PS2L"; sensorNameMap["0X07"] = "PS2R"; sensorNameMap["0X08"] = "PS3L"; sensorNameMap["0X09"] = "PS3M"; sensorNameMap["0X0A"] = "PS3R"; sensorNameMap["0X0B"] = "ST"; sensorNameMap["0X0C"] = "STL"; sensorNameMap["0X0D"] = "STR"; sensorNameMap["0X0E"] = "STU"; sensorNameMap["0X0F"] = "PS4"; sensorNameMap["0X10"] = "UV1"; sensorNameMap["0X11"] = "UV2"; sensorNameMap["0X12"] = "DWP"; sensorNameMap["0X13"] = "RJH"; sensorNameMap["0X14"] = "RJL"; sensorNameMap["0X15"] = "BTU"; sensorNameMap["0X16"] = "BTD"; sensorNameMap["0X17"] = "CVU"; sensorNameMap["0X18"] = "CVD"; sensorNameMap["0X19"] = "UVM"; sensorNameMap["0X1A"] = "SAFE"; sensorNameMap["0X1B"] = "PS5"; sensorNameMap["0X1C"] = "TKL"; sensorNameMap["0X1D"] = "TKR"; sensorNameMap["0X1E"] = "QTLoc"; sensorNameMap["0X1F"] = "TDLoc"; sensorNameMap["0X20"] = "RJLoc"; sensorNameMap["0X21"] = "FL"; sensorNameMap["0X22"] = "Chn"; //老化定时器 connect(m_agingTimer, &QTimer::timeout, this, [this]() { m_agingSeconds++; updateAgingTimeDisplay(); }); } MainWindow::~MainWindow() { //清理串口工作线程 if(m_serialWorker) { m_serialWorker->stop(); //停止线程 m_serialWorker->wait(); delete m_serialWorker; m_serialWorker = nullptr; } delete ui; } //波特率改变处理 void MainWindow::onBaudRateChanged(int index) { currentBaudRate = baudRateComboBox->currentData().toInt(); //如果已连接,则更新波特率 if(m_serialWorker) { m_serialWorker->setBaudRate(currentBaudRate); logMessage(QString("波特率已更改为: %1").arg(baudRateComboBox->currentText())); } } void MainWindow::onRefundLabelClicked() { if(!m_serialWorker) { logMessage("串口未打开"); return; } //清空之前的退钞数据 m_rejectList.clear(); m_rejectReasons.clear(); if(m_rejectDialog && m_rejectDialog->isVisible()) { m_rejectDialog->close(); } //初始化对话框 initRejectDialog(); //发送获取退钞口信息列表命令 sendGetRejectListCommand(); setButtonActive(rejectListButton); } void MainWindow::onAmountLabelClicked() { if(!m_serialWorker) { logMessage("串口未打开"); return; } //清空之前的明细数据 m_detailList.clear(); initDetailDialog(); sendDetailedlistCommand(); setButtonActive(detailedlistButton); } void MainWindow::onSheetNumberLabelClicked() { onAmountLabelClicked(); } QWidget* MainWindow::createFirstCommandPage() { QWidget *page = new QWidget(); QGridLayout *grid = new QGridLayout(page); grid->setSpacing(10); grid->setContentsMargins(10, 10, 10, 10); buttonStyle = "QPushButton {" "background-color: #f8f8f8;" "border: 1px solid #ccc;" "border-radius: 5px;" "padding: 8px;" "width: 120px;" "height: 50px;" "color:#388087;" "font-weight: bold; " "}" "QPushButton:hover {" "background-color: #f0f0f0;" "color:#388087;" "font-weight: bold; " "}" "QPushButton:pressed {" "background-color: #c0c0c0;" "color:#388087;" "font-weight: bold; " "}"; QWidget *handshakeWidget = new QWidget(); QVBoxLayout *handshakeLayout = new QVBoxLayout(handshakeWidget); handshakeLayout->setContentsMargins(0, 0, 0, 0); handshakeLayout->setSpacing(2); QLabel *emptyLabel3 = new QLabel(); emptyLabel3->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel3->setAlignment(Qt::AlignLeft | Qt::AlignBottom); handshakeLayout->addWidget(emptyLabel3); QPushButton *handshakeButton = new QPushButton("握手命令"); handshakeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); handshakeButton->setStyleSheet(buttonStyle); handshakeLayout->addWidget(handshakeButton); grid->addWidget(handshakeWidget, 0, 0); QWidget *testWidget = new QWidget(); QVBoxLayout *testLayout = new QVBoxLayout(testWidget); testLayout->setContentsMargins(0, 0, 0, 0); testLayout->setSpacing(2); QLabel *emptyLabel4 = new QLabel(); emptyLabel4->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel4->setAlignment(Qt::AlignLeft | Qt::AlignBottom); testLayout->addWidget(emptyLabel4); QPushButton *testButton = new QPushButton("获取软件版本信息"); testButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); testButton->setStyleSheet(buttonStyle); testLayout->addWidget(testButton); grid->addWidget(testWidget, 0, 1); //启动机器按钮 QWidget *startMachineWidget = new QWidget(); QVBoxLayout *startMachineLayout = new QVBoxLayout(startMachineWidget); startMachineLayout->setContentsMargins(0, 0, 0, 0); startMachineLayout->setSpacing(2); QLabel *startMachineLabel = new QLabel("机器控制"); startMachineLabel->setStyleSheet("color:#388087; font-weight: bold;"); startMachineLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); startMachineLayout->addWidget(startMachineLabel); startMachineButton = new QPushButton("启动机器"); startMachineButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); startMachineButton->setStyleSheet(buttonStyle); startMachineLayout->addWidget(startMachineButton); grid->addWidget(startMachineWidget, 1, 0); //设置手动/自动启动 QWidget *handleSelfWidget = new QWidget(); QVBoxLayout *handleSelfLayout = new QVBoxLayout(handleSelfWidget); handleSelfLayout->setContentsMargins(0, 0, 0, 0); handleSelfLayout->setSpacing(2); QLabel *handleSelfLabel = new QLabel("手动/自动启动"); handleSelfLabel->setStyleSheet("color:#388087; font-weight: bold;"); handleSelfLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); handleSelfLayout->addWidget(handleSelfLabel); handleSelfButton = new QPushButton("设置手动/自动启动"); handleSelfButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); handleSelfButton->setStyleSheet(buttonStyle); handleSelfLayout->addWidget(handleSelfButton); grid->addWidget(handleSelfWidget, 1, 1); //工作模式按钮 QWidget *workModeWidget = new QWidget(); QVBoxLayout *workModeLayout = new QVBoxLayout(workModeWidget); workModeLayout->setContentsMargins(0, 0, 0, 0); workModeLayout->setSpacing(2); QLabel *workModeLabel = new QLabel("工作模式"); workModeLabel->setStyleSheet("color:#388087; font-weight: bold;"); workModeLayout->addWidget(workModeLabel); workModeLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); workModeButton = new QPushButton("选择工作模式"); workModeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); workModeButton->setStyleSheet(buttonStyle); // workModeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); workModeLayout->addWidget(workModeButton); grid->addWidget(workModeWidget, 2, 0); QWidget *currencyWidget = new QWidget(); QVBoxLayout *currencyLayout = new QVBoxLayout(currencyWidget); currencyLayout->setContentsMargins(0, 0, 0, 0); currencyLayout->setSpacing(2); QLabel *currencyLabel = new QLabel("货币币种"); currencyLabel->setStyleSheet("color:#388087; font-weight: bold;"); currencyLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); currencyLayout->addWidget(currencyLabel); currencyButton = new QPushButton("获取货币币种"); currencyButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); currencyButton->setStyleSheet(buttonStyle); currencyLayout->addWidget(currencyButton); grid->addWidget(currencyWidget, 2, 1); //预置数按钮 QWidget *presetWidget = new QWidget(); QVBoxLayout *presetLayout = new QVBoxLayout(presetWidget); presetLayout->setContentsMargins(0, 0, 0, 0); presetLayout->setSpacing(2); QLabel *presetLabel = new QLabel("预置数"); presetLabel->setStyleSheet("color:#388087; font-weight: bold;"); presetLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); presetLayout->addWidget(presetLabel); presetButton = new QPushButton("设置预置数"); presetButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); presetButton->setStyleSheet(buttonStyle); presetLayout->addWidget(presetButton); grid->addWidget(presetWidget, 3, 0); //预置金额按钮 QWidget *presetAmountWidget = new QWidget(); QVBoxLayout *presetAmountLayout = new QVBoxLayout(presetAmountWidget); presetAmountLayout->setContentsMargins(0, 0, 0, 0); presetAmountLayout->setSpacing(2); QLabel *presetAmountLabel = new QLabel("预置金额"); presetAmountLabel->setStyleSheet("color:#388087; font-weight: bold;"); presetAmountLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); presetAmountLayout->addWidget(presetAmountLabel); presetAmountButton = new QPushButton("设置预置金额"); presetAmountButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); presetAmountButton->setStyleSheet(buttonStyle); presetAmountLayout->addWidget(presetAmountButton); grid->addWidget(presetAmountWidget, 3, 1); //走钞速度按钮 QWidget *speedWidget = new QWidget(); QVBoxLayout *speedLayout = new QVBoxLayout(speedWidget); speedLayout->setContentsMargins(0, 0, 0, 0); speedLayout->setSpacing(2); QLabel *speedLabel = new QLabel("走钞速度"); speedLabel->setStyleSheet("color:#388087; font-weight: bold;"); speedLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); speedLayout->addWidget(speedLabel); speedButton = new QPushButton("设置机器走钞速度"); speedButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); speedButton->setStyleSheet(buttonStyle); speedLayout->addWidget(speedButton); grid->addWidget(speedWidget, 4, 0); //版本清分按钮 QWidget *versionSortWidget = new QWidget(); QVBoxLayout *versionSortLayout = new QVBoxLayout(versionSortWidget); versionSortLayout->setContentsMargins(0, 0, 0, 0); versionSortLayout->setSpacing(2); QLabel *versionSortLabel = new QLabel("版本清分"); versionSortLabel->setStyleSheet("color:#388087; font-weight: bold;"); versionSortLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); versionSortLayout->addWidget(versionSortLabel); versionSortButton = new QPushButton("设置版本清分"); versionSortButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); versionSortButton->setStyleSheet(buttonStyle); versionSortLayout->addWidget(versionSortButton); grid->addWidget(versionSortWidget, 4, 1); //退钞口张数按钮 QWidget *rejectCountWidget = new QWidget(); QVBoxLayout *rejectCountLayout = new QVBoxLayout(rejectCountWidget); rejectCountLayout->setContentsMargins(0, 0, 0, 0); rejectCountLayout->setSpacing(2); QLabel *rejectCountLabel = new QLabel("退钞口张数"); rejectCountLabel->setStyleSheet("color:#388087; font-weight: bold;"); rejectCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); rejectCountLayout->addWidget(rejectCountLabel); rejectCountButton = new QPushButton("设置退钞口张数"); rejectCountButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); rejectCountButton->setStyleSheet(buttonStyle); rejectCountLayout->addWidget(rejectCountButton); grid->addWidget(rejectCountWidget, 5, 0); //查询货币组合按钮 QWidget *queryCurrencyWidget = new QWidget(); QVBoxLayout *queryCurrencyLayout = new QVBoxLayout(queryCurrencyWidget); queryCurrencyLayout->setContentsMargins(0, 0, 0, 0); queryCurrencyLayout->setSpacing(2); QLabel *queryCurrencyLabel = new QLabel("货币组合"); queryCurrencyLabel->setStyleSheet("color:#388087; font-weight: bold;"); queryCurrencyLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); queryCurrencyLayout->addWidget(queryCurrencyLabel); queryCurrencyButton = new QPushButton("查询货币组合"); queryCurrencyButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); queryCurrencyButton->setStyleSheet(buttonStyle); queryCurrencyLayout->addWidget(queryCurrencyButton); grid->addWidget(queryCurrencyWidget, 5, 1); QWidget *detailedListWidget = new QWidget(); QVBoxLayout *detailedListLayout = new QVBoxLayout(detailedListWidget); detailedListLayout->setContentsMargins(0, 0, 0, 0); detailedListLayout->setSpacing(2); QLabel *emptyLabel8 = new QLabel("明细列表"); emptyLabel8->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel8->setAlignment(Qt::AlignLeft | Qt::AlignBottom); detailedListLayout->addWidget(emptyLabel8); detailedlistButton = new QPushButton("获取明细列表"); detailedlistButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); detailedlistButton->setStyleSheet(buttonStyle); detailedListLayout->addWidget(detailedlistButton); grid->addWidget(detailedListWidget, 6, 0); QWidget *rejectListWidget = new QWidget(); QVBoxLayout *rejectListLayout = new QVBoxLayout(rejectListWidget); rejectListLayout->setContentsMargins(0, 0, 0, 0); rejectListLayout->setSpacing(2); QLabel *emptyLabel7 = new QLabel("退钞口信息列表"); emptyLabel7->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel7->setAlignment(Qt::AlignLeft | Qt::AlignBottom); rejectListLayout->addWidget(emptyLabel7); rejectListButton = new QPushButton("获取退钞口信息列表"); rejectListButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); rejectListButton->setStyleSheet(buttonStyle); rejectListLayout->addWidget(rejectListButton); grid->addWidget(rejectListWidget, 6, 1); //获取传感器电压 QWidget *SensorvoltageWidget = new QWidget(); QVBoxLayout *SensorvoltageLayout = new QVBoxLayout(SensorvoltageWidget); SensorvoltageLayout->setContentsMargins(0, 0, 0, 0); SensorvoltageLayout->setSpacing(2); QLabel *SensorvoltageLabel = new QLabel("传感器电压"); SensorvoltageLabel->setStyleSheet("color:#388087; font-weight: bold;"); SensorvoltageLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); SensorvoltageLayout->addWidget(SensorvoltageLabel); SensorvoltageButton = new QPushButton("获取传感器电压"); SensorvoltageButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); SensorvoltageButton->setStyleSheet(buttonStyle); SensorvoltageLayout->addWidget(SensorvoltageButton); grid->addWidget(SensorvoltageWidget, 7, 0); //防钓鱼传感器按钮 QWidget *antiFishWidget = new QWidget(); QVBoxLayout *antiFishLayout = new QVBoxLayout(antiFishWidget); antiFishLayout->setContentsMargins(0, 0, 0, 0); antiFishLayout->setSpacing(2); QLabel *antiFishLabel = new QLabel("防钓鱼"); antiFishLabel->setStyleSheet("color:#388087; font-weight: bold;"); antiFishLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); antiFishLayout->addWidget(antiFishLabel); ansiFishButton = new QPushButton("防钓鱼传感器"); ansiFishButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); ansiFishButton->setStyleSheet(buttonStyle); antiFishLayout->addWidget(ansiFishButton); grid->addWidget(antiFishWidget, 7, 1); //连接信号槽 connect(testButton, &QPushButton::clicked, this, &MainWindow::onGetDetailListClicked); connect(handshakeButton, &QPushButton::clicked, this, &MainWindow::onHandshakeClicked); connect(workModeButton, &QPushButton::clicked, this, &MainWindow::onWorkModeClicked); connect(currencyButton, &QPushButton::clicked, this, &MainWindow::onCurrencyButtonClicked); connect(presetButton, &QPushButton::clicked, this, &MainWindow::onPresetButtonClicked); connect(presetAmountButton, &QPushButton::clicked, this, &MainWindow::onPresetAmountButtonClicked); connect(rejectListButton, &QPushButton::clicked, this, &MainWindow::onGetRejectListClicked); connect(detailedlistButton, &QPushButton::clicked,this,&MainWindow::onDetailedlistClicked); connect(SensorvoltageButton, &QPushButton::clicked, this, &MainWindow::onSensorvoltageClicked); connect(speedButton,&QPushButton::clicked,this,&MainWindow::onSpeedButtonClicked); connect(versionSortButton,&QPushButton::clicked,this,&MainWindow::onVersionSortButtonClicked); connect(rejectCountButton, &QPushButton::clicked, this, &MainWindow::onRejectCountButtonClicked); connect(queryCurrencyButton, &QPushButton::clicked, this, &MainWindow::onSelectCurrency); connect(ansiFishButton,&QPushButton::clicked,this,&MainWindow::onAntiFishClicked); connect(startMachineButton, &QPushButton::clicked, this, &MainWindow::onStartMachineClicked); connect(handleSelfButton, &QPushButton::clicked, this, &MainWindow::onhandleSelfClicked); return page; } QWidget* MainWindow::createSecondCommandPage() { QWidget *page = new QWidget(); QGridLayout *grid = new QGridLayout(page); grid->setSpacing(10); grid->setContentsMargins(10, 10, 10, 10); //蜂鸣器按钮 QWidget *buzzerWidget = new QWidget(); QVBoxLayout *buzzerLayout = new QVBoxLayout(buzzerWidget); buzzerLayout->setContentsMargins(0, 0, 0, 0); buzzerLayout->setSpacing(2); buzzerLabel = new QLabel("蜂鸣器"); buzzerLabel->setStyleSheet("color:#388087; font-weight: bold;"); buzzerLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); buzzerLayout->addWidget(buzzerLabel); buzzerButton = new QPushButton("设置机器蜂鸣器"); buzzerButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); buzzerButton->setStyleSheet(buttonStyle); buzzerLayout->addWidget(buzzerButton); grid->addWidget(buzzerWidget, 0, 0); //防尘罩按钮 - 无label QWidget *dustCoverWidget = new QWidget(); QVBoxLayout *dustCoverLayout = new QVBoxLayout(dustCoverWidget); dustCoverLayout->setContentsMargins(0, 0, 0, 0); dustCoverLayout->setSpacing(2); //添加一个空的占位label,保持高度一致 QLabel *emptyLabel1 = new QLabel(); emptyLabel1->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel1->setAlignment(Qt::AlignLeft | Qt::AlignBottom); dustCoverLayout->addWidget(emptyLabel1); QPushButton *openDustCoverButton = new QPushButton("打开防尘罩"); openDustCoverButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); openDustCoverButton->setStyleSheet(buttonStyle); dustCoverLayout->addWidget(openDustCoverButton); grid->addWidget(dustCoverWidget,0, 1); //底部挡板按钮 - 无label QWidget *bottomGateWidget = new QWidget(); QVBoxLayout *bottomGateLayout = new QVBoxLayout(bottomGateWidget); bottomGateLayout->setContentsMargins(0, 0, 0, 0); bottomGateLayout->setSpacing(2); QLabel *emptyLabel2 = new QLabel(); emptyLabel2->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel2->setAlignment(Qt::AlignLeft | Qt::AlignBottom); bottomGateLayout->addWidget(emptyLabel2); QPushButton *openBottomGateButton = new QPushButton("打开底部挡板"); openBottomGateButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); openBottomGateButton->setStyleSheet(buttonStyle); bottomGateLayout->addWidget(openBottomGateButton); grid->addWidget(bottomGateWidget, 1, 0); //关闭防尘罩按钮 - 无label QWidget *closeDustCoverWidget = new QWidget(); QVBoxLayout *closeDustCoverLayout = new QVBoxLayout(closeDustCoverWidget); closeDustCoverLayout->setContentsMargins(0, 0, 0, 0); closeDustCoverLayout->setSpacing(2); QLabel *emptyLabel5 = new QLabel(); emptyLabel5->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel5->setAlignment(Qt::AlignLeft | Qt::AlignBottom); closeDustCoverLayout->addWidget(emptyLabel5); QPushButton *closeDustCoverButton = new QPushButton("关闭防尘罩"); closeDustCoverButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); closeDustCoverButton->setStyleSheet(buttonStyle); closeDustCoverLayout->addWidget(closeDustCoverButton); grid->addWidget(closeDustCoverWidget, 1, 1); //关闭底部挡板按钮 - 无label QWidget *closeBottomGateWidget = new QWidget(); QVBoxLayout *closeBottomGateLayout = new QVBoxLayout(closeBottomGateWidget); closeBottomGateLayout->setContentsMargins(0, 0, 0, 0); closeBottomGateLayout->setSpacing(2); QLabel *emptyLabel6 = new QLabel(); emptyLabel6->setStyleSheet("color:#388087; font-weight: bold;"); emptyLabel6->setAlignment(Qt::AlignLeft | Qt::AlignBottom); closeBottomGateLayout->addWidget(emptyLabel6); QPushButton *closeBottomGateButton = new QPushButton("关闭底部挡板"); closeBottomGateButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); closeBottomGateButton->setStyleSheet(buttonStyle); closeBottomGateLayout->addWidget(closeBottomGateButton); grid->addWidget(closeBottomGateWidget, 2, 0); //CIS校验按钮 QWidget *cisCheckWidget = new QWidget(); QVBoxLayout *cisCheckLayout = new QVBoxLayout(cisCheckWidget); cisCheckLayout->setContentsMargins(0, 0, 0, 0); cisCheckLayout->setSpacing(2); QLabel *cisCheckLabel = new QLabel("CIS校验"); cisCheckLabel->setStyleSheet("color:#388087; font-weight: bold;"); cisCheckLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); cisCheckLayout->addWidget(cisCheckLabel); cisCheckButton = new QPushButton("CIS传感器校验"); cisCheckButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); cisCheckButton->setStyleSheet(buttonStyle); cisCheckLayout->addWidget(cisCheckButton); grid->addWidget(cisCheckWidget, 2, 1); //出厂设置 QWidget *factoryResetWidget = new QWidget(); QVBoxLayout *factoryResetLayout = new QVBoxLayout(factoryResetWidget); factoryResetLayout->setContentsMargins(0, 0, 0, 0); factoryResetLayout->setSpacing(2); QLabel *factoryResetLabel = new QLabel(); factoryResetLabel->setStyleSheet("color:#388087; font-weight: bold;"); factoryResetLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); factoryResetLayout->addWidget(factoryResetLabel); QPushButton *factoryResetButton = new QPushButton("恢复出厂设置"); factoryResetButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); factoryResetButton->setStyleSheet(buttonStyle); factoryResetLayout->addWidget(factoryResetButton); grid->addWidget(factoryResetWidget, 3, 0); //按键功能按钮 QWidget *pressWidget = new QWidget(); QVBoxLayout *pressLayout = new QVBoxLayout(pressWidget); pressLayout->setContentsMargins(0, 0, 0, 0); pressLayout->setSpacing(2); QLabel *pressLabel = new QLabel("按键功能"); pressLabel->setStyleSheet("color:#388087; font-weight: bold;"); pressLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); pressLayout->addWidget(pressLabel); pressButton = new QPushButton("按键功能"); pressButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); pressButton->setStyleSheet(buttonStyle); pressLayout->addWidget(pressButton); grid->addWidget(pressWidget, 3, 1); //老化命令 QWidget *agingWidget = new QWidget(); QVBoxLayout *agingLayout = new QVBoxLayout(agingWidget); agingLayout->setContentsMargins(0, 0, 0, 0); agingLayout->setSpacing(2); QLabel *agingLabel = new QLabel("老化"); agingLabel->setStyleSheet("color:#388087; font-weight: bold;"); agingLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); agingLayout->addWidget(agingLabel); agingButton = new QPushButton("准备老化"); agingButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); agingButton->setStyleSheet(buttonStyle); agingLayout->addWidget(agingButton); grid->addWidget(agingWidget, 4, 0); grid->setRowStretch(5, 1); //让第5行(索引4)占据剩余空间 //连接信号槽 connect(cisCheckButton, &QPushButton::clicked, this, [this](){ if(!m_serialWorker) { logMessage("串口未打开"); return; } setButtonActive(qobject_cast<QPushButton*>(sender())); sendCISCheckCommand(); }); connect(pressButton,&QPushButton::clicked,this,&MainWindow::onPressButtonClicked); connect(openDustCoverButton, &QPushButton::clicked, this, &MainWindow::onOpenDustCoverClicked); connect(openBottomGateButton, &QPushButton::clicked, this, &MainWindow::onOpenBottomGateClicked); connect(closeDustCoverButton, &QPushButton::clicked, this, &MainWindow::onCloseDustCoverClicked); connect(closeBottomGateButton, &QPushButton::clicked, this, &MainWindow::onCloseBottomGateClicked); connect(buzzerButton, &QPushButton::clicked, this, &MainWindow::onSetBuzzerClicked); connect(factoryResetButton, &QPushButton::clicked, this, &MainWindow::onFactoryResetClicked); connect(agingButton, &QPushButton::clicked, this, &MainWindow::onAgingClicked); return page; } #include "serialworker.h" #include <QDebug> #include <QSerialPortInfo> SerialWorker::SerialWorker(const QString &portName, qint32 baudRate, QObject *parent) : QThread(parent), m_portName(portName), m_serialPort(nullptr), m_running(false), m_baudRate(baudRate) { } SerialWorker::~SerialWorker() { stop(); } //设置波特率 void SerialWorker::setBaudRate(qint32 baudRate) { m_baudRate = baudRate; if(m_serialPort && m_serialPort->isOpen()) { m_serialPort->setBaudRate(m_baudRate); } } void SerialWorker::run() { // 检查串口是否已存在 bool portAvailable = false; const auto ports = QSerialPortInfo::availablePorts(); for(const QSerialPortInfo &port : ports) { if(port.portName() == m_portName) { portAvailable = true; break; } } if(!portAvailable) { emit errorOccurred(tr("串口 %1 不存在").arg(m_portName)); return; } m_serialPort = new QSerialPort(); m_serialPort->setPortName(m_portName); m_serialPort->setBaudRate(m_baudRate); //成员变量设置波特率 m_serialPort->setDataBits(QSerialPort::Data8); m_serialPort->setParity(QSerialPort::NoParity); m_serialPort->setStopBits(QSerialPort::OneStop); m_serialPort->setFlowControl(QSerialPort::NoFlowControl); if(!m_serialPort->open(QIODevice::ReadWrite)) { emit errorOccurred(tr("无法打开串口 %1: %2").arg(m_portName).arg(m_serialPort->errorString())); delete m_serialPort; m_serialPort = nullptr; return; } m_running = true; //读取串口数据 while(m_running) { if(m_serialPort->waitForReadyRead(100)) { //等待数据到达 QByteArray data = m_serialPort->readAll(); //读取可用数据 //尝试读取剩余数据 while(m_serialPort->waitForReadyRead(10)) { data += m_serialPort->readAll(); } emit dataReceived(data); //发送接收到的数据 } //检查串口是否仍然打开 if(!m_serialPort->isOpen()) { emit errorOccurred(tr("串口 %1 意外关闭").arg(m_portName)); break; } } //清理串口对象 if(m_serialPort) { m_serialPort->close(); delete m_serialPort; m_serialPort = nullptr; } } void SerialWorker::stop() { m_running = false; wait(); } void SerialWorker::send(const QByteArray &data) { //发送数据到串口 if(m_serialPort && m_serialPort->isOpen()) { if(m_serialPort->write(data) == -1) { emit errorOccurred(tr("发送数据失败: %1").arg(m_serialPort->errorString())); } } } 这个能否增加一个识别数据线是否断开呢,如果数据线断开我的PC就主动断开串口,并且在断开串口前在logMessage显示数据线已断开

#include "AutoCuttingWidget.h" #include "AutoCuttingAlarmTable.h" #include "Component/LightLabel/lightLabel.h" #include "Component/QFlightInstruments/LayoutSquare.hpp" #include "Component/LineRuler/LineRuler.h" #include "Component/LineRuler/RulerWidget.h" #include "AutoCuttingActionDBManager.h" #include "CircuitBaseScene.h" #include "GraphicElementManage.h" #include <QDebug> #include <SystemSetting.h> AutoCuttingWidget::AutoCuttingWidget(QWidget* parent) : QWidget(parent) , ui(new Ui::AutoCuttingWidgetClass()) { ui->setupUi(this); initLineRuler(); initAmpHistoryLineCharts(); initAlarmMessageTable(); initRemotePanel(); initPFDParameter(); initRemoteConnectParam(); initCutMonitor(); initDrawCutPathViewer(); m_autoCutXmlFilePath = QCoreApplication::applicationDirPath() + "/autoCutXML/"; } AutoCuttingWidget::~AutoCuttingWidget() { if (m_updateCutMonitorTimer) { m_updateCutMonitorTimer->stop(); //delete m_updateCutMonitorTimer; m_updateCutMonitorTimer = nullptr; } delete ui; } void AutoCuttingWidget::on_pushButton_drawPath_clicked() { ui->stackedWidget_autocut->setCurrentWidget(ui->page_cutPath); m_curView->enbaleDrawLine(true); } void AutoCuttingWidget::on_pushButton_clearCutPath_clicked() { QList<QGraphicsItem*> itemList = m_curView->items(); for (QGraphicsItem* item : itemList) { m_curView->scene()->removeItem(item); } } void AutoCuttingWidget::on_pushButton_startAuto_clicked() { m_updateCutMonitorTimer->stop(); resetAutoCuttingDataList(); m_updateCutMonitorTimer->start(hxcommon::SystemSetting::autoCutRefreshInterval); ui->stackedWidget_autocut->setCurrentWidget(ui->page_cutMonitor); } void AutoCuttingWidget::on_pushButton_stopAuto_clicked() { m_updateCutMonitorTimer->stop(); } void AutoCuttingWidget::on_pushButton_systemVoltage_clicked() { qDebug() << "on_pushButton_systemVoltage_clicked"; ui->stackedWidget->setCurrentIndex(0); } void AutoCuttingWidget::on_pushButton_pumpCurrent_clicked() { qDebug() << "on_pushButton_pumpCurrent_clicked"; ui->stackedWidget->setCurrentIndex(1); } void AutoCuttingWidget::on_pushButton_cutLowCurrent_clicked() { qDebug() << "on_pushButton_cutLowCurrent_clicked"; ui->stackedWidget->setCurrentIndex(2); } void AutoCuttingWidget::on_pushButton_cutHighCurrent_clicked() { qDebug() << "on_pushButton_cutHighCurrent_clicked"; ui->stackedWidget->setCurrentIndex(3); } void AutoCuttingWidget::on_pushButton_memoryStart_clicked() { QDateTime time = QDateTime::currentDateTime(); QString dateTime = time.toString("MM-dd-hh-mm"); QString pathname = m_autoCutXmlFilePath + QStringLiteral("自动截割路径_%1.xml").arg(dateTime); m_autoCutFileSavePath = QFileDialog::getSaveFileName(this, QStringLiteral("保存自动截割路径"), pathname, "Xml(*.xml)"); } void AutoCuttingWidget::on_pushButton_memoryStop_clicked() { QString savePath = m_autoCutFileSavePath; if (savePath.isEmpty()) { QDateTime time = QDateTime::currentDateTime(); QString dateTime = time.toString("MM-dd-hh-mm"); savePath = m_autoCutXmlFilePath + QStringLiteral("自动截割路径_%1.xml").arg(dateTime); } if (m_graphicElementManager.saveCurrentPage(savePath)) { emit showResponseMessage(hxcommon::NotifyType::Notify_Type_Success,QStringLiteral("自动截割路径保存成功!")); } else { emit showResponseMessage(hxcommon::NotifyType::Notify_Type_Error, QStringLiteral("保存失败!")); } } void AutoCuttingWidget::on_pushButton_memoryModel_clicked() { QString fileName = QFileDialog::getOpenFileName(this, QStringLiteral("请打开自动截割路径文件"), m_autoCutFileSavePath, "Xml(*.xml)"); QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Open failed"; return; } m_graphicElementManager.openPage(file); } void AutoCuttingWidget::initAlarmMessageTable() { m_alarmMessageTable = new AutoCuttingAlarmTable(ui->widget_abnormalInfo); QLayout* alarmMessageTableLayout = ui->widget_abnormalInfo->layout(); if (alarmMessageTableLayout) { // 如果 layout 是 QVBoxLayout 类型,添加控件 QVBoxLayout* vboxLayout = qobject_cast<QVBoxLayout*>(alarmMessageTableLayout); if (vboxLayout) { vboxLayout->addWidget(m_alarmMessageTable); } } } void AutoCuttingWidget::initRemotePanel() { // 创建 4x4 的网格布局 m_remotePanelLayout = new QGridLayout(ui->widget_remotePanel); ui->widget_remotePanel->setLayout(m_remotePanelLayout); // 定义 4x4 的内容 QStringList labels = { QStringLiteral("截割头伸"), QStringLiteral("铲板升"), QStringLiteral("一运正转"), QStringLiteral("左后退"), QStringLiteral("截割头缩"), QStringLiteral("铲板降"), QStringLiteral("一运反转"), QStringLiteral("右后退"), QStringLiteral("左前进"), QStringLiteral("后支撑升"), QStringLiteral("截割左"), QStringLiteral("星轮正转"), QStringLiteral("右前进"), QStringLiteral("后支撑降"), QStringLiteral("截割右"), QStringLiteral("星轮反转") }; // 创建并添加 lightLabel 到布局 int index = 0; for (int row = 0; row < 4; ++row) { for (int col = 0; col < 4; ++col) { lightLabel* label = new lightLabel(ui->widget_remotePanel); label->setLabelText(labels[index++]); // 设置内容 label->setLightPointStep(0); m_remotePanelLayout->addWidget(label, row, col); // 添加到网格布局 } } } void AutoCuttingWidget::initPFDParameter() { LayoutSquare* m_layoutSq = new LayoutSquare(this); m_layoutSq->setContentsMargins(0, 0, 0, 0); m_layoutSq->addWidget(ui->framePFD); } void AutoCuttingWidget::initRemoteConnectParam() { //m_remoteConnectLayout = new QGridLayout(ui->widget_remoteConnectInfo); //ui->widget_remoteConnectInfo->setLayout(m_remoteConnectLayout); //QStringList labels = { // QStringLiteral("遥控在线"), QStringLiteral("油泵启动"), QStringLiteral("截割启动"), QStringLiteral("一运启动"), // QStringLiteral("二运启动"), QStringLiteral("皮带启动"), QStringLiteral("星轮启动"), QStringLiteral("人员误入") //}; //int index = 0; //for (int row = 0; row < 2; ++row) { // for (int col = 0; col < 4; ++col) { // lightLabel* label = new lightLabel(ui->widget_remoteConnectInfo); // label->setLabelText(labels[index++]); // 设置内容 // label->setLightPointStep(0); // m_remoteConnectLayout->addWidget(label, row, col); // 添加到网格布局 // } //} // 创建 4x4 的网格布局 //m_remoteConnectLayout = new QGridLayout(ui->widget_remoteConnectInfo); //ui->widget_remoteConnectInfo->setLayout(m_remoteConnectLayout); //// 定义 4x4 的内容 //QStringList labels = { // QStringLiteral("截割头伸"), QStringLiteral("铲板升"), QStringLiteral("一运正转"), QStringLiteral("左后退"), // QStringLiteral("截割头缩"), QStringLiteral("铲板降"), QStringLiteral("一运反转"), QStringLiteral("右后退"), // QStringLiteral("左前进"), QStringLiteral("后支撑升"), QStringLiteral("截割左"), QStringLiteral("星轮正转"), // QStringLiteral("右前进"), QStringLiteral("后支撑降"), QStringLiteral("截割右"), QStringLiteral("星轮反转") //}; //// 创建并添加 lightLabel 到布局 //int index = 0; //for (int row = 0; row < 4; ++row) { // for (int col = 0; col < 4; ++col) { // lightLabel* label = new lightLabel(ui->widget_remoteConnectInfo); // label->setLabelText(labels[index++]); // 设置内容 // label->setLightPointStep(0); // m_remoteConnectLayout->addWidget(label, row, col); // 添加到网格布局 // } //} } void AutoCuttingWidget::initLineRuler() { // 创建 RulerWidget 实例,方向为垂直 RulerWidget* verticalRuler = new RulerWidget(Qt::Vertical, this); // 检查 widget_verticalRuler 是否已有布局 QLayout* verticallayout = ui->widget_verticalRuler->layout(); if (!verticallayout) { // 如果没有布局,创建一个 QVBoxLayout verticallayout = new QVBoxLayout(ui->widget_verticalRuler); ui->widget_verticalRuler->setLayout(verticallayout); } // 设置布局的间隔和边距为 0 verticallayout->setSpacing(0); verticallayout->setContentsMargins(0, 0, 0, 0); // 将 RulerWidget 添加到布局中 verticallayout->addWidget(verticalRuler); // 创建 RulerWidget 实例,方向为垂直 RulerWidget* horizontalRuler = new RulerWidget(Qt::Horizontal, this); // 检查 widget_verticalRuler 是否已有布局 QLayout* horizontalLayout = ui->widget_horizontalRuler->layout(); if (!horizontalLayout) { // 如果没有布局,创建一个 QVBoxLayout horizontalLayout = new QVBoxLayout(ui->widget_horizontalRuler); ui->widget_verticalRuler->setLayout(horizontalLayout); } // 设置布局的间隔和边距为 0 horizontalLayout->setSpacing(0); horizontalLayout->setContentsMargins(0, 0, 0, 0); // 将 RulerWidget 添加到布局中 horizontalLayout->addWidget(horizontalRuler); } void AutoCuttingWidget::initAmpHistoryLineCharts() { m_lineChartSystemVoltage = new AmpHistoryLineChart(ui->page_systemVoltage); m_lineChartPumpAmp = new AmpHistoryLineChart(ui->page_pumpCurrent); m_lineChartLowCutAmp = new AmpHistoryLineChart(ui->page_cutLowCurrent); m_lineChartHighCutAmp = new AmpHistoryLineChart(ui->page_cutHighCurrent); // 获取 ui 中的布局,并确保布局充满空间 QLayout* layoutSystemVoltage = ui->page_systemVoltage->layout(); if (layoutSystemVoltage) { // 如果 layout 是 QVBoxLayout 类型,添加控件 QVBoxLayout* vboxLayoutSystemVoltage = qobject_cast<QVBoxLayout*>(layoutSystemVoltage); if (vboxLayoutSystemVoltage) { vboxLayoutSystemVoltage->addWidget(m_lineChartSystemVoltage); } } QLayout* layoutPump = ui->page_pumpCurrent->layout(); if (layoutPump) { QVBoxLayout* vboxLayoutPump = qobject_cast<QVBoxLayout*>(layoutPump); if (vboxLayoutPump) { vboxLayoutPump->addWidget(m_lineChartPumpAmp); } } QLayout* layoutLowCut = ui->page_cutLowCurrent->layout(); if (layoutLowCut) { QVBoxLayout* vboxLayoutLowCut = qobject_cast<QVBoxLayout*>(layoutLowCut); if (vboxLayoutLowCut) { vboxLayoutLowCut->addWidget(m_lineChartLowCutAmp); } } QLayout* layoutHighCut = ui->page_cutHighCurrent->layout(); if (layoutHighCut) { QVBoxLayout* vboxLayoutHighCut = qobject_cast<QVBoxLayout*>(layoutHighCut); if (vboxLayoutHighCut) { vboxLayoutHighCut->addWidget(m_lineChartHighCutAmp); } } m_lineChartPumpAmp->setYAxisRange(0, 100); m_lineChartLowCutAmp->setYAxisRange(0, 100); m_lineChartHighCutAmp->setYAxisRange(0, 100); // TODO:假设数据点,替换为实时数据 QList<QPointF> list; QDateTime currentTime = QDateTime::currentDateTime(); for (int i = 0; i < 120; ++i) { // 120个数据点,代表最近两个小时 list.append(QPointF(currentTime.addSecs(-i * 60).toMSecsSinceEpoch(), QRandomGenerator::global()->bounded(10, 14))); } m_lineChartPumpAmp->setLineData(list); m_lineChartLowCutAmp->setLineData(list); m_lineChartHighCutAmp->setLineData(list); } void AutoCuttingWidget::initCutMonitor() { m_autoCuttingProcessor = new AutoCuttingProcessor(this); hxDataBaseManager::AutoCuttingActionDBManager dbManager; QList<hxcommon::AutoCuttingAction*> list = dbManager.getActionList(); m_autoCuttingProcessor->setActionList(list); m_autoCuttingProcessor->doWork(); m_updateCutMonitorTimer = new QTimer(); connect(m_updateCutMonitorTimer, &QTimer::timeout, this, &AutoCuttingWidget::testDrawCutterMonitor); m_cutter = new QGraphicsPixmapItem(QPixmap(":/mainwindow/Resource/AutoCutting/cutter.png").scaled(60, 60, Qt::KeepAspectRatio)); } void AutoCuttingWidget::resetAutoCuttingDataList() { if (m_updateCutMonitorTimer) { m_updateCutMonitorTimer->stop();//先停止可能正在进行的 } // 重置GraphicsScene int width = ui->widget_cutterMonitorPainter->width(); int height = ui->widget_cutterMonitorPainter->height(); int rowPointCount = hxcommon::SystemSetting::autoCutRowTakeTime / hxcommon::SystemSetting::autoCutRefreshInterval;//一行多少点 int columnPointCount = hxcommon::SystemSetting::autoCutColumnTakeTime / hxcommon::SystemSetting::autoCutRefreshInterval;//一列多少点 int columnSplitPointCount = columnPointCount / hxcommon::SystemSetting::autoCutColumnSplit;//向上的每段多少点 double wItem = 540.0 / rowPointCount;//SceneRect尺寸600 - 截割头60 double hItem = 360.0 / columnPointCount;//SceneRect尺寸420 - 截割头60 ui->graphicsViewCutterMonitor->setGeometry(width / 2 - 310, height / 2 - 220, 620, 440);//居中,因为带20边框 QGraphicsScene* scene = ui->graphicsViewCutterMonitor->scene(); if (!scene) { scene = new QGraphicsScene(this); ui->graphicsViewCutterMonitor->setScene(scene); } scene->setSceneRect(0, 0, 600, 420); // 禁用滚动条 ui->graphicsViewCutterMonitor->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsViewCutterMonitor->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 禁用鼠标拖拽滚动 ui->graphicsViewCutterMonitor->setDragMode(QGraphicsView::NoDrag); // 禁用鼠标滚轮滚动 ui->graphicsViewCutterMonitor->setTransformationAnchor(QGraphicsView::NoAnchor); // 禁用索引提高性能 scene->setItemIndexMethod(QGraphicsScene::NoIndex); //清空画布 scene->clear(); //清空指令列表 m_autoCuttingDataList.clear(); //不管分多少段,最底下从左到右都是一样的 for (int x = 0; x < rowPointCount; x++) { int y = QRandomGenerator::global()->bounded(columnPointCount - 2, columnPointCount); //int y = columnPointCount - 1; AutoCuttingPoint point(x, y, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128 - hxcommon::SystemSetting::autoCutHorizontalSpeed); m_autoCuttingDataList.append(point);//从左到右 } for (int i = 0; i < hxcommon::SystemSetting::autoCutColumnSplit; i++) { if (i % 2 == 0) { //顺着右侧向上 int yStart = columnPointCount - columnSplitPointCount * i - 1; int yEnd = columnPointCount - columnSplitPointCount * (i + 1); for (int y = yStart; y >= yEnd; y--) { int x = QRandomGenerator::global()->bounded(rowPointCount - 2, rowPointCount); //int x = rowPointCount - 1; AutoCuttingPoint point(x, y, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128 - hxcommon::SystemSetting::autoCutHorizontalSpeed, 128); m_autoCuttingDataList.append(point);//右边向上 } for (int x = rowPointCount - 1; x >= 0; x--) { int y = QRandomGenerator::global()->bounded(yEnd - 2, yEnd); //int y = yEnd - 1; AutoCuttingPoint point(x, y, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128 + hxcommon::SystemSetting::autoCutHorizontalSpeed); m_autoCuttingDataList.append(point);//从右到左 } } else { //顺着左侧向上 int yStart = columnPointCount - columnSplitPointCount * i - 1; int yEnd = columnPointCount - columnSplitPointCount * (i + 1); for (int y = yStart; y >= yEnd; y--) { int x = QRandomGenerator::global()->bounded(-1, 1); //int x = 0; AutoCuttingPoint point(x, y, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128 - hxcommon::SystemSetting::autoCutHorizontalSpeed, 128); m_autoCuttingDataList.append(point);//左边向上 } for (int x = 0; x < rowPointCount; x++) { int y = QRandomGenerator::global()->bounded(yEnd - 2, yEnd); //int y = yEnd - 1; AutoCuttingPoint point(x, y, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128 - hxcommon::SystemSetting::autoCutHorizontalSpeed); m_autoCuttingDataList.append(point);//从左到右 } } } //结束时添加停止命令 if (hxcommon::SystemSetting::autoCutColumnSplit % 2 == 0) { AutoCuttingPoint point1(rowPointCount - 1, 0, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128); m_autoCuttingDataList.append(point1); AutoCuttingPoint point2(rowPointCount - 1, 0, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128); m_autoCuttingDataList.append(point2); } else { AutoCuttingPoint point1(0, 0, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128); m_autoCuttingDataList.append(point1); AutoCuttingPoint point2(0, 0, hxcommon::HeadingCutterCmdEnum::JoystickArm, 128, 128); m_autoCuttingDataList.append(point2); } } void AutoCuttingWidget::initDrawCutPathViewer() { // 创建管理器 hxdraw2dlib::GraphicElementManage manager; // 创建场景 hxdraw2dlib::CircuitBaseScene* pScene = new hxdraw2dlib::CircuitBaseScene(this); //pScene->setSceneRect(-297 * 3 / 2, -210 * 3 / 2, 297 * 3, 210 * 3); pScene->setScalePerGrid(10.0); QTimer::singleShot(0, this, [=]() { QSize widgetSize = ui->widget_drawCutPathView->size(); pScene->setSceneRect(0, 0, widgetSize.width(), widgetSize.height()); }); // 创建视图(不要设置 parent) hxdraw2dlib::CircuitBaseView* view = new hxdraw2dlib::CircuitBaseView(pScene); m_curView = view; view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 获取或创建布局 QLayout* layout = ui->widget_drawCutPathView->layout(); if (layout) { // 如果 layout 是 QVBoxLayout 类型,添加控件 QVBoxLayout* vboxLayout = qobject_cast<QVBoxLayout*>(layout); vboxLayout->setContentsMargins(0, 0, 0, 0); // 去除边距 vboxLayout->setSpacing(0); // 去除间隔 if (vboxLayout) { //QTimer::singleShot(0, this, [=]() { //vboxLayout->addWidget(m_curView); //}); } } m_graphicElementManager.setCurView(m_curView); } void AutoCuttingWidget::testDrawCutterMonitor() { //获取本次要绘制的点 if (m_autoCuttingDataList.size() == 0) { return; } AutoCuttingPoint point = m_autoCuttingDataList.takeFirst(); emit sendAutoCuttingCmd(point.cmd, point.b0, point.b1); m_cutterRotation -= 13; if (m_cutterRotation <= -360) { m_cutterRotation = 0; } int width = ui->widget_cutterMonitorPainter->width(); int height = ui->widget_cutterMonitorPainter->height(); int rowPointCount = hxcommon::SystemSetting::autoCutRowTakeTime / hxcommon::SystemSetting::autoCutRefreshInterval;//一行多少点 int columnPointCount = hxcommon::SystemSetting::autoCutColumnTakeTime / hxcommon::SystemSetting::autoCutRefreshInterval;//一列多少点 double wItem = 540.0 / rowPointCount;//SceneRect尺寸600 - 截割头60 double hItem = 360.0 / columnPointCount;//SceneRect尺寸420 - 截割头60 //qDebug() << "width" << width << "height" << height; ui->graphicsViewCutterMonitor->setGeometry(width / 2 - 310, height / 2 - 220, 620, 440);//居中,因为带20边框 QGraphicsScene* scene = ui->graphicsViewCutterMonitor->scene(); if (!scene) { scene = new QGraphicsScene(this); ui->graphicsViewCutterMonitor->setScene(scene); } scene->setSceneRect(0, 0, 600, 420); // 禁用滚动条 ui->graphicsViewCutterMonitor->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->graphicsViewCutterMonitor->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 禁用鼠标拖拽滚动 ui->graphicsViewCutterMonitor->setDragMode(QGraphicsView::NoDrag); // 禁用鼠标滚轮滚动 ui->graphicsViewCutterMonitor->setTransformationAnchor(QGraphicsView::NoAnchor); // 禁用索引提高性能 scene->setItemIndexMethod(QGraphicsScene::NoIndex); // 先移除画上去的截割头 scene->removeItem(m_cutter); //画很粗的圆组成切割图形 QGraphicsEllipseItem* circle = new QGraphicsEllipseItem(); circle->setRect(0, 0, 60, 60); // x, y, width, height (width=height时为圆形) circle->setPos(point.x * wItem, point.y * hItem); // 设置位置 circle->setBrush(QColor(26, 155, 114)); // 设置填充颜色 circle->setPen(QPen(QColor(26, 155, 114), 1)); // 设置边框 scene->addItem(circle);//添加到场景 //画小点连成轨迹 QGraphicsEllipseItem* linePoint = new QGraphicsEllipseItem(); linePoint->setRect(0, 0, 1, 1); // x, y, width, height (width=height时为圆形) linePoint->setPos(point.x * wItem + 30, point.y * hItem + 30); // 设置位置 linePoint->setBrush(Qt::green); // 设置填充颜色 linePoint->setPen(QPen(Qt::green, 1)); // 设置边框 scene->addItem(linePoint);//添加到场景 //画截割头和截割头旋转 scene->addItem(m_cutter); m_cutter->setPos(point.x * wItem, point.y * hItem); m_cutter->setTransformOriginPoint(QPointF(30, 30)); m_cutter->setRotation(m_cutterRotation); //联动左边姿态 float pitch = (columnPointCount / 2 - point.y) / columnPointCount * 80.0; float heading = (point.x - rowPointCount / 2) / rowPointCount * 80.0; ui->graphicsPFD->setPitch(pitch); ui->graphicsPFD->setHeading(heading); ui->graphicsPFD->update(); // ui->label_cutX->setText(QString::number(point.x)); ui->label_cutY->setText(QString::number(point.y)); }

verticalLayout->addWidget(groupBox_2); groupBox = new QGroupBox(QWDialog); groupBox->setObjectName(QString::fromUtf8("groupBox")); horizontalLayout_3 = new QHBoxLayout(groupBox); horizontalLayout_3->setSpacing(6); horizontalLayout_3->setContentsMargins(11, 11, 11, 11); horizontalLayout_3->setObjectName(QString::fromUtf8("horizontalLayout_3")); rBtnBlack = new QRadioButton(groupBox); rBtnBlack->setObjectName(QString::fromUtf8("rBtnBlack")); rBtnBlack->setChecked(true); horizontalLayout_3->addWidget(rBtnBlack); rBtnRed = new QRadioButton(groupBox); rBtnRed->setObjectName(QString::fromUtf8("rBtnRed")); horizontalLayout_3->addWidget(rBtnRed); rBtnBlue = new QRadioButton(groupBox); rBtnBlue->setObjectName(QString::fromUtf8("rBtnBlue")); horizontalLayout_3->addWidget(rBtnBlue); verticalLayout->addWidget(groupBox); txtEdit = new QPlainTextEdit(QWDialog); txtEdit->setObjectName(QString::fromUtf8("txtEdit")); QFont font1; font1.setPointSize(20); txtEdit->setFont(font1); verticalLayout->addWidget(txtEdit); verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); verticalLayout->addItem(verticalSpacer); horizontalLayout = new QHBoxLayout(); horizontalLayout->setSpacing(6); horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout->addItem(horizontalSpacer_2); btnOK = new QPushButton(QWDialog); btnOK->setObjectName(QString::fromUtf8("btnOK")); horizontalLayout->addWidget(btnOK); btnCancel = new QPushButton(QWDialog); btnCancel->setObjectName(QString::fromUtf8("btnCancel")); horizontalLayout->addWidget(btnCancel); horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout->addItem(horizontalSpacer); btnClose = new QPushButton(QWDialog); btnClose->setObjectName(QString::fromUtf8("btnClose")); horizontalLayout->addWidget(btnClose); verticalLayout->addLayout(horizontalLayout); retranslateUi(QWDialog); QObject::connect(btnOK, SIGNAL(clicked()), QWDialog, SLOT(accept())); QObject::connect(btnCancel, SIGNAL(clicked()), QWDialog, SLOT(reject())); QObject::connect(btnClose, SIGNAL(clicked()), QWDialog, SLOT(close())); QMetaObject::connectSlotsByName(QWDialog); } // setupUi对以上的每一行代码都进行详细的注释,并用代码框发给我

void PersonManagement::initUI() { setObjectName("PersonManagement"); m_personModel = new personModel(this, Database::GetInstance()->getDB()); m_personModel->setTable("personInfo"); m_personModel->select(); // 创建数据模型 m_personTable = new QTableView(this); m_personTable->setModel(m_personModel); QHeaderView* horizontalHeader = m_personTable->horizontalHeader(); // 设置除第五列外的所有列自由拉伸 for (int i = 0; i < m_personModel->columnCount(); ++i) { if (i != 5 || i != 6) { horizontalHeader->setSectionResizeMode(i, QHeaderView::Stretch); } } // 设置第五列固定宽度 horizontalHeader->setSectionResizeMode(5, QHeaderView::Fixed); m_personTable->setColumnWidth(5, 100); // 设置第六列固定宽度 horizontalHeader->setSectionResizeMode(6, QHeaderView::Fixed); m_personTable->setColumnWidth(6, 130); // 其他设置保持不变 m_personTable->verticalHeader()->setVisible(false); m_personTable->setEditTriggers(QAbstractItemView::NoEditTriggers); m_personTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_personTable->hideColumn(0); m_personTable->verticalHeader()->setDefaultSectionSize(100); // 创建搜索框 m_searchLineEdit = new QLineEdit(this); m_searchLineEdit->setPlaceholderText("搜索人员..."); m_searchLineEdit->setMinimumWidth(250); // 使用 Qt 自带的搜索图标(兼容性版本) QIcon searchIcon = style()->standardIcon(QStyle::SP_FileDialogContentsView); QAction *searchAction = new QAction(searchIcon, "搜索", this); m_searchLineEdit->addAction(searchAction, QLineEdit::TrailingPosition); // 创建按钮 m_addPersonButton = new QPushButton("增加人员", this); m_addPersonButton->setFixedSize(95, 30); m_exportButton = new QPushButton("导出数据", this); m_exportButton->setFixedSize(95, 30); m_importButton = new QPushButton("导入数据", this); m_importButton->setFixedSize(95, 30); QPushButton *rename = new QPushButton("修改照片名", this); connect(rename, &QPushButton::clicked, this, &PersonManagement::onrename); // 按钮布局(添加间距和对齐) QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addWidget(m_searchLineEdit); // 搜索框居左 buttonLayout->addStretch(1); // 间距 buttonLayout->addWidget(m_addPersonButton); buttonLayout->addSpacing(10); // 按钮间距 buttonLayout->addWidget(m_exportButton); buttonLayout->addSpacing(10); buttonLayout->addWidget(m_importButton); buttonLayout->addWidget(rename); buttonLayout->setContentsMargins(10, 10, 10, 10); // 布局边距 //分页 m_upPageBtn = new QPushButton("上一页", this); m_upPageBtn->setFixedSize(80, 30); m_downPageBtn = new QPushButton("下一页", this); m_downPageBtn->setFixedSize(80, 30); m_jumpBtn = new QPushButton("跳转", this); m_jumpBtn->setFixedSize(80, 30); m_currentPage = new QLabel("当前 页", this); m_currentPage->setFixedSize(60, 30); //跳转输入页 m_jumpBox = new QSpinBox(this); m_jumpBox->setRange(1,999); m_jumpBox->setFixedSize(50, 30); QHBoxLayout *pageTurnLayout = new QHBoxLayout(); pageTurnLayout->addWidget(m_upPageBtn); pageTurnLayout->addSpacing(35); pageTurnLayout->addWidget(m_currentPage); pageTurnLayout->addSpacing(35); pageTurnLayout->addWidget(m_downPageBtn); QHBoxLayout *pageJumpLayout = new QHBoxLayout(); pageJumpLayout->addWidget(m_jumpBox); pageJumpLayout->addSpacing(10); pageJumpLayout->addWidget(m_jumpBtn); pageJumpLayout->setContentsMargins(5, 5, 5, 5); // 布局边距 QHBoxLayout *pageLayout = new QHBoxLayout(); pageLayout->addLayout(pageTurnLayout); pageLayout->addLayout(pageJumpLayout); // 主布局 QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addLayout(buttonLayout); mainLayout->addWidget(m_personTable); mainLayout->addLayout(pageLayout); mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(10); // 设置操作委托到操作列 EditDeleteButtonDelegate *delegate = new EditDeleteButtonDelegate(this); m_personTable->setItemDelegateForColumn(6, delegate); connect(delegate, &EditDeleteButtonDelegate::editClicked, this, [this](const QModelIndex &index) { onEditButtonClicked(index); }); connect(delegate, &EditDeleteButtonDelegate::deleteClicked, this, [this](const QModelIndex &index) { onDelButtonClicked(index); }); // 连接信号 connect(m_addPersonButton, &QPushButton::clicked, this, &PersonManagement::onAddPersonClicked); connect(m_exportButton, &QPushButton::clicked, this, &PersonManagement::onExportClicked); connect(m_importButton, &QPushButton::clicked, this, &PersonManagement::onImportClicked); connect(searchAction, &QAction::triggered, this, &PersonManagement::onSearchClicked); connect(m_searchLineEdit, &QLineEdit::returnPressed, this, &PersonManagement::onSearchClicked); }这个是我的界面代码,在这个代码基础上完成翻页功能和跳转功能

//========== 中间区域 ========== QWidget *centerWidget = new QWidget(); QVBoxLayout *centerLayout = new QVBoxLayout(centerWidget); centerLayout->setSpacing(10); centerLayout->setContentsMargins(0, 0, 0, 0); // 移除默认边距 //机器状态区域 QGroupBox *statusGroup = new QGroupBox("机器状态"); statusGroup->setStyleSheet("QGroupBox { background-color: #F6F6F2; border: 1px solid #ccc; border-radius: 5px; margin-top: 10px;color:#388087; font-weight: bold; }" "QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; background-color:#F6F6F2;font-weight: bold; }"); QHBoxLayout *statusLayout = new QHBoxLayout(statusGroup); machineStatusImage = new QLabel(); machineStatusImage->setAlignment(Qt::AlignCenter); machineStatusImage->setScaledContents(true); machineStatusImage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // QLabel* overlayLabel = new QLabel(machineStatusImage); // overlayLabel->setStyleSheet("background-color: transparent; color: red; font-weight: bold;"); // overlayLabel->setText("QT"); // overlayLabel->move(250, 110); // 设置位置 // overlayLabel->adjustSize(); // overlayLabel->setAttribute(Qt::WA_TransparentForMouseEvents); // 允许点击穿透 voltageTable = new QTableWidget(); voltageTable->setColumnCount(3); //voltageTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); voltageTable->setHorizontalHeaderLabels({"传感器ID", "传感器名称", "电压值(V)"}); voltageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); voltageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); voltageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); voltageTable->setStyleSheet( "QTableWidget {" " background-color: #F6F6F2;" " border: 1px solid #ccc;" " color: #388087;" "}" "QTableWidget::item {" " padding: 2px;" " border: none;" " background-color: #F6F6F2;" " color: #388087;" "}" "QTableWidget::item:selected {" " background-color: #F6F6F2;" " color: #388087;" "}" "QHeaderView {" " background-color: #F6F6F2;" //表头整体背景色 "}" "QScrollBar:vertical {" " background: #F6F6F2;" " width: 12px;" " margin: 0px 0px 0px 0px;" "}" "QHeaderView::section {" " background-color: #F6F6F2;" " padding: 4px;" " border: 1px solid #ccc;" " color: #388087;" "}" "QTableCornerButton::section {" " background-color: #F6F6F2;" //左上角空白区域 " border: 1px solid #ccc;" "}" ); voltageTable->setVisible(false); //初始隐藏表格 QPixmap pixmap(":/image/333.svg"); if(!pixmap.isNull()) { machineStatusImage->setMaximumSize(450, 700); QPixmap scaled = pixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); machineStatusImage->setPixmap(scaled); } else { machineStatusImage->setText("图片加载失败"); } statusLayout->addWidget(machineStatusImage, 3); statusLayout->addWidget(voltageTable, 1); centerLayout->addWidget(statusGroup, 3); //信息显示区域 QGroupBox *messageGroup = new QGroupBox(); messageGroup->setStyleSheet("background-color:#F6F6F2;color:#388087;border: 1px solid #ccc;padding: 5px;font-weight: bold; "); QVBoxLayout *messageLayout = new QVBoxLayout(messageGroup); //添加顶部布局,包含标题和清除按钮 QHBoxLayout *messageHeaderLayout = new QHBoxLayout(); QLabel *messageTitleLabel = new QLabel("信息显示"); messageTitleLabel->setStyleSheet("background-color:#F6F6F2;color:#388087;font-weight: bold;"); QPushButton *clearButton = new QPushButton("清空"); clearButton->setStyleSheet("QPushButton {" "background-color: #f0f0f0;" "border: 1px solid #ccc;" "border-radius: 3px;" "padding: 3px 8px;" "color:#388087;" "font-weight: bold;" "}" "QPushButton:hover {" "background-color: #e0e0e0;" "}" "QPushButton:pressed {" "background-color: #a0a0a0;" "}"); clearButton->setFixedWidth(60); //设置固定宽度 messageHeaderLayout->addWidget(messageTitleLabel); messageHeaderLayout->addStretch(); messageHeaderLayout->addWidget(clearButton); messageLayout->addLayout(messageHeaderLayout); messageTextEdit = new QTextEdit(); messageTextEdit->setReadOnly(true); messageTextEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); messageTextEdit->setStyleSheet("QTextEdit { background-color: #F6F6F2; border: 1px solid #ccc; padding: 5px; color:black;font-weight: bold;/* font-family:宋体; */font-size:14px;}"); messageLayout->addWidget(messageTextEdit); //连接清除按钮的信号 connect(clearButton, &QPushButton::clicked, this, [this](){ messageTextEdit->clear(); }); centerLayout->addWidget(messageGroup, 2); mainLayout->addWidget(centerWidget, 2); 这块区域的信息显示groupbox,无法跟随窗口的放大缩小而放大缩小是怎么回事

#include "o3_JTR.h" #include "DatabaseManager.h" TemperatureGauge::TemperatureGauge(QWidget *parent) :QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(2,2,2,2); layout->setSpacing(2); gauge = new QProgressBar(); tempLabel = new QLabel(this); gauge->setRange(0,300); gauge->setValue(0); gauge->setOrientation(Qt::Vertical); gauge->setTextVisible(false); gauge->setStyleSheet("QProgressBar{border:2px solid #888;border-radius:5px;background:white;}" "QProgressBar::chunk{background:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4CAF50, stop:0.5 #FFC107, stop:1 #F44336);border-radius:3px;}"); tempLabel->setAlignment(Qt::AlignCenter); tempLabel->setStyleSheet("font-size:12px;"); layout->addWidget(gauge,1); layout->addWidget(tempLabel); } void TemperatureGauge::setTemperatureDirect(float temp) { gauge->setValue(temp); tempLabel->setText(QString("%1°C").arg(temp)); gauge->setStyleSheet("QProgressBar::chunk{background:#c9ffa7;border-radius:3px;}"); } void TemperatureGauge::setTemperature(float temp) { gauge->setValue(temp); tempLabel->setText(QString("%1°C").arg(temp)); gauge->setStyleSheet("QProgressBar::chunk{background:#c9ffa7;border-radius:3px;}"); } JTR::JTR(QWidget *parent) :QWidget(parent),dbManager(DatabaseManager::instance()) { setUpUI(); // 立即创建条形图 createBarChart(); //定时刷新数据 refreshTimer = new QTimer(this); connect(refreshTimer, &QTimer::timeout, this, &JTR::refreshData); refreshTimer->start(2000); //每2秒刷新一次 // //延迟2秒后开启动画 // QTimer::singleShot(2000, this, &JTR::startAnimation); } // void JTR::startAnimation() // { // if(animation) // { // movingRect->move(0, 10); //重置位置 // animation->start(); //开启动画 // } // } //显示温度,进去板子数量,出来板子数量 void JTR::setUpUI() { QHBoxLayout* mainLayout = new QHBoxLayout(this); // 改为水平布局 mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(15); //左侧区域 - 温度显示 QWidget* tempWidget = new QWidget(); createTemperatureDisplay(tempWidget); //修改为在tempWidget上创建温度显示 //右侧区域 - 日志显示 QWidget* logWidget = new QWidget(); createLogDisplay(logWidget); //设置左右比例约为3:2 mainLayout->addWidget(tempWidget, 3); mainLayout->addWidget(logWidget, 2); this->setLayout(mainLayout); //初始化出板数定时器 (5s刷新一次) boardCountTimer = new QTimer(this); connect(boardCountTimer, &QTimer::timeout, this, &JTR::refreshBoardCount); boardCountTimer->start(5000); //5s QTimer::singleShot(0, this, &JTR::refreshBoardCount); //立即刷新一次 } void JTR::createLogDisplay(QWidget* parent) { QVBoxLayout* layout = new QVBoxLayout(parent); layout->setContentsMargins(5, 5, 5, 5); layout->setSpacing(10); QLabel* titleLabel = new QLabel("操作日志"); titleLabel->setStyleSheet("font-weight: bold; font-size: 14px;"); layout->addWidget(titleLabel); logDisplay = new QTextEdit(); logDisplay->setReadOnly(true); logDisplay->setStyleSheet( "font-family: 'Courier New';" "font-size: 12px;" "background-color: white;" "border: 1px solid #ccc;" ); layout->addWidget(logDisplay,1); //拉伸因子为1 //出板数显示区域 QHBoxLayout* boardCountLayout = new QHBoxLayout(); boardCountLabel = new QLabel("出板数:"); boardCountLabel->setStyleSheet("font-weight: bold; font-size: 14px;"); boardCountDisplay = new QTextEdit(); boardCountDisplay->setReadOnly(true); boardCountDisplay->setFixedHeight(30); boardCountDisplay->setStyleSheet( "font-size: 14px;" "background-color: white;" "border: 1px solid #ccc;" ); boardCountLayout->addWidget(boardCountLabel); boardCountLayout->addWidget(boardCountDisplay, 1); //设置拉伸因子 layout->addLayout(boardCountLayout); } //获取当天日期字符串 QString JTR::getTodayDateString() { return QDateTime::currentDateTime().toString("yyyy-MM-dd"); } void JTR::createBarChart() { QSqlDatabase db = dbManager.getDatabase(); QString today = getTodayDateString(); //获取今天的第一条数据的年月日 QString productionDateStr = "生产日期:" + today; QDateTime startTime = QDateTime::fromString(today + " 07:00:00", "yyyy-MM-dd hh:mm:ss"); //固定为当天7:00 QDateTime endTime = QDateTime::fromString(today + " 23:00:00", "yyyy-MM-dd hh:mm:ss"); //固定为当天23:00 //查询保持不变 QString queryStr = QString( "SELECT " "CONCAT(LPAD(HOUR(record_time), 2, '0'), ':', " "IF(MINUTE(record_time) < 30, '00', '30')) AS time_slot, " "MAX(board_out_count) - MIN(board_out_count) + 1 AS board_count " "FROM board_count " "WHERE DATE(record_time) = '%1' " "AND TIME(record_time) BETWEEN '07:00:00' AND '23:00:00' " "GROUP BY HOUR(record_time), FLOOR(MINUTE(record_time)/30) " "ORDER BY time_slot" ).arg(today); QSqlQuery query(db); if(!query.exec(queryStr)) { qDebug() << "条形图查询失败:" << query.lastError().text(); // 即使查询失败也创建空图表,保证UI显示 createEmptyBarChart(productionDateStr); return; } //创建时间槽列表(7:00-23:00,半小时一个槽) QStringList timeSlots; QMap<QString, int> timeSlotCounts; //生成所有可能的时间槽并初始化为0 for(int hour = 7; hour <= 23; hour++) { timeSlots << QString("%1:00").arg(hour, 2, 10, QChar('0')); timeSlotCounts[QString("%1:00").arg(hour, 2, 10, QChar('0'))] = 0; if(hour < 23) { //23点不添加30分(避免超过23:00) timeSlots << QString("%1:30").arg(hour, 2, 10, QChar('0')); timeSlotCounts[QString("%1:30").arg(hour, 2, 10, QChar('0'))] = 0; } } //填充查询结果 bool hasData = false; while(query.next()) { QString timeSlot = query.value("time_slot").toString(); int count = query.value("board_count").toInt(); timeSlotCounts[timeSlot] = count; if(count > 0) hasData = true; } //如果没有数据,创建空图表 if(!hasData) { createEmptyBarChart(productionDateStr); return; } //创建条形图 QChart* barChart = new QChart(); barChart->setAnimationOptions(QChart::SeriesAnimations); barChart->legend()->hide(); //设置标题样式 QFont titleFont = barChart->titleFont(); titleFont.setPointSize(10); titleFont.setBold(true); barChart->setTitleFont(titleFont); //设置标题内容 barChart->setTitle(QString("生产数量统计(半小时) - %1").arg(productionDateStr)); QBarSeries *series = new QBarSeries(); QBarSet *set = new QBarSet("生产数量"); set->setBrush(QBrush(QColor("#4CAF50"))); int maxCount = 0; foreach(const QString &slot, timeSlots) { int count = timeSlotCounts[slot]; *set << count; if(count > maxCount) { maxCount = count; } } maxCount = ((maxCount / 10) + 1) * 10; if(maxCount < 15) maxCount = 15; series->append(set); barChart->addSeries(series); //===== 使用 QCategoryAxis ===== QCategoryAxis *axisX = new QCategoryAxis(); axisX->setMin(0); axisX->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); //创建时间标签(7:00-23:00,每半小时一个刻度) QStringList timeLabels; QList<double> tickPositions; for(int hour = 7; hour <= 23; hour++) { //整点标签和位置 timeLabels << QString("%1:00").arg(hour, 2, 10, QChar('0')); tickPositions << (hour - 7) * 2; //转换为0-based索引(每半小时=1单位) //半点标签和位置(23:00除外) if(hour < 23) { timeLabels << QString("%1:30").arg(hour, 2, 10, QChar('0')); tickPositions << (hour - 7) * 2 + 1; } } //设置分类边界(关键修正:直接使用刻度位置) for(int i = 0; i < timeLabels.size(); i++) { axisX->append(timeLabels[i], tickPositions[i] - 0.5); } //设置轴范围(覆盖所有时间点) axisX->setRange(0, tickPositions.last()); //其他设置 axisX->setTitleText("时间"); axisX->setLabelsAngle(-90); //设置Y轴(数量) QValueAxis *axisY = new QValueAxis(); axisY->setRange(0, maxCount); axisY->setTitleText("数量"); axisY->setTickCount((maxCount / 5) + 1); axisY->setLabelFormat("%d"); //将轴添加到图表并将系列附加到轴 barChart->addAxis(axisX, Qt::AlignBottom); barChart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisX); series->attachAxis(axisY); //在条的顶部添加值标签 series->setLabelsVisible(true); series->setLabelsFormat("@value"); series->setLabelsPosition(QAbstractBarSeries::LabelsCenter); //调整图表边距,确保标签显示完整 barChart->setMargins(QMargins(30, 10, 30, 30)); //设置条形宽度 series->setBarWidth(0.9); //适当调整条形宽度 //将图表应用于视图 if(barChartView->chart()) { delete barChartView->chart(); } barChartView->setChart(barChart); barChartView->setRenderHint(QPainter::Antialiasing); } // 创建空条形图 void JTR::createEmptyBarChart(const QString& title) { QChart* barChart = new QChart(); barChart->setAnimationOptions(QChart::SeriesAnimations); barChart->legend()->hide(); QFont titleFont = barChart->titleFont(); titleFont.setPointSize(10); titleFont.setBold(true); barChart->setTitleFont(titleFont); barChart->setTitle(QString("生产数量统计(半小时) - %1").arg(title)); QBarSeries *series = new QBarSeries(); QBarSet *set = new QBarSet("生产数量"); set->setBrush(QBrush(QColor("#4CAF50"))); // 添加空数据 for(int i = 0; i < 32; i++) // 7:00-23:00共32个半小时段 { *set << 0; } series->append(set); barChart->addSeries(series); // 创建X轴 QCategoryAxis *axisX = new QCategoryAxis(); axisX->setMin(0); axisX->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); QStringList timeLabels; QList<double> tickPositions; for(int hour = 7; hour <= 23; hour++) { timeLabels << QString("%1:00").arg(hour, 2, 10, QChar('0')); tickPositions << (hour - 7) * 2; if(hour < 23) { timeLabels << QString("%1:30").arg(hour, 2, 10, QChar('0')); tickPositions << (hour - 7) * 2 + 1; } } for(int i = 0; i < timeLabels.size(); i++) { axisX->append(timeLabels[i], tickPositions[i] - 0.5); } axisX->setRange(0, tickPositions.last()); axisX->setTitleText("时间"); axisX->setLabelsAngle(-90); // 创建Y轴 QValueAxis *axisY = new QValueAxis(); axisY->setRange(0, 10); // 默认范围0-10 axisY->setTitleText("数量"); axisY->setTickCount(3); axisY->setLabelFormat("%d"); barChart->addAxis(axisX, Qt::AlignBottom); barChart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisX); series->attachAxis(axisY); barChart->setMargins(QMargins(30, 10, 30, 30)); if(barChartView->chart()) { delete barChartView->chart(); } barChartView->setChart(barChart); barChartView->setRenderHint(QPainter::Antialiasing); } void JTR::updateBarChart() { createBarChart(); // 直接调用现有的创建函数 } //创建温区 void JTR::createTemperatureDisplay(QWidget* parent) { QVBoxLayout* vLayout = new QVBoxLayout(parent); vLayout->setSpacing(15); //创建上层温区 QHBoxLayout* upperHeaderLayout = new QHBoxLayout(); upperHeaderLayout->addSpacing(0); //增加间距对齐 for(int i = 1;i < 11;++i) { QLabel* numLabel = new QLabel(QString::number(i)); numLabel->setAlignment(Qt::AlignCenter); numLabel->setFixedWidth(50); upperHeaderLayout->addWidget(numLabel); } vLayout->addLayout(upperHeaderLayout,1); //创建上层温区 QGroupBox* upGroup = new QGroupBox("上层温区"); QHBoxLayout* upLayout = new QHBoxLayout(); upLayout->setSpacing(15); for(int i =0;i < 10;++i) { TemperatureGauge* gauge = new TemperatureGauge(); gauge->setFixedSize(60,150); upLayout->addWidget(gauge); upGauges.append(gauge); } upGroup->setLayout(upLayout); vLayout->addWidget(upGroup,2); // //创建动画区域 // animationWidget = new QWidget(); // animationWidget->setFixedHeight(30); // animationWidget->setStyleSheet("background-color:white;"); // animLayout = new QHBoxLayout(animationWidget); // animLayout->setContentsMargins(0,0,0,0); // animLayout->setSpacing(0); // //创建十个动画片段 // for(int i = 0;i < 10;++i) // { // QWidget* segment = new QWidget(); // segment->setStyleSheet("background-color:transparent;border-right:1px dashed #888"); // segment->setFixedWidth(60); // animLayout->addWidget(segment); // } // //创建移动的矩形 // movingRect = new QWidget(animationWidget); // movingRect->setStyleSheet("background-color: #4CAF50;"); // movingRect->setFixedSize(60, 10); // movingRect->move(0, 10); // //设置动画 // animation = new QPropertyAnimation(movingRect,"pos",this); // animation->setDuration(300000); //通过时间 // animation->setStartValue(QPoint(0,10)); // animation->setEndValue(QPoint(1400, 10)); //间距 // animation->setEasingCurve(QEasingCurve::Linear); // animation->setLoopCount(-1); // vLayout->addWidget(animationWidget,1); //创建下层温区 QGroupBox* lowerGroup = new QGroupBox("下层温区"); QHBoxLayout* lowerLayout = new QHBoxLayout(); lowerLayout->setSpacing(15); for(int i = 0;i < 10;++i) { TemperatureGauge* gauge = new TemperatureGauge(); gauge->setFixedSize(60,150); lowerLayout->addWidget(gauge); lowGauges.append(gauge); } lowerGroup->setLayout(lowerLayout); vLayout->addWidget(lowerGroup,2); //添加条形图 QGroupBox* barChartGroup = new QGroupBox("生产数量统计(半小时)"); barChartGroup->setStyleSheet("font:600 12pt '宋体';"); QVBoxLayout* barChartLayout = new QVBoxLayout(barChartGroup); //创建条形图 barChartView = new QChartView(this); barChartView->setRenderHint(QPainter::Antialiasing); barChartLayout->addWidget(barChartView); vLayout->addWidget(barChartGroup,4); //添加主布局 QVBoxLayout *mainLayout = qobject_cast<QVBoxLayout*>(this->layout()); if(mainLayout) { mainLayout->insertLayout(0,vLayout); } createBarChart(); } void JTR::refreshData() { if(!DatabaseManager::instance().isConnected()) { qDebug() << "数据库未连接"; return; } //刷新温度数据 refreshTemperatureData(); //刷新日志数据 refreshLogData(); } void JTR::refreshLogData() { QSqlQuery query(DatabaseManager::instance().getDatabase()); bool querySuccess = query.exec("SELECT time, roleE, roleC, content FROM logs WHERE date(time) = CURRENT_DATE ORDER BY time ASC"); if(!querySuccess) { qDebug() << "日志查询失败:" << query.lastError().text(); return; } QString logText; while(query.next()) { QDateTime time = query.value(0).toDateTime(); QString roleE = query.value(1).toString(); QString roleC = query.value(2).toString(); QString content = query.value(3).toString(); //格式化日志行 logText += QString("%1\t%2\t%3\t%4\n") .arg(time.toString("yyyy-MM-dd hh:mm:ss")) .arg(roleE, -8) //左对齐,占8字符宽度 .arg(roleC, -6) //左对齐,占6字符宽度 .arg(content); } //只有当内容变化时才更新,避免频繁刷新 if(logDisplay->toPlainText() != logText) { logDisplay->setPlainText(logText); logDisplay->moveCursor(QTextCursor::Start); //滚动到顶部 } } // void JTR::refreshTemperatureData() // { // //获取最新的20条记录 // QSqlQuery query(DatabaseManager::instance().getDatabase()); // bool querySuccess = query.exec("SELECT zone_name, zone_level, actual_value, record_time FROM temperature_data " // "WHERE zone_level IN ('上层', '下层') " // "ORDER BY record_time DESC LIMIT 20"); // if(!querySuccess) // { // qDebug() << "查询失败:" << query.lastError().text(); // return; // } // //初始化温度数组 // QVector<float> upTemps(10,0.0f); // QVector<float> lowTemps(10,0.0f); // bool needUpdate = false; // //QDateTime latestTime; // while(query.next()) // { // QString zoneName = query.value(0).toString(); // QString zoneLevel = query.value(1).toString(); // float temp = query.value(2).toFloat(); // //解析温区位置和编号 // QRegularExpression re("(\\d+)"); // QRegularExpressionMatch match = re.match(zoneName); // if(match.hasMatch()) // { // int zoneNum = match.captured(1).toInt(); // if(zoneNum >= 1 && zoneNum <= 10) // { // if(zoneLevel == "上层") // { // if(upTemps[zoneNum - 1] == 0.0f) // 只取第一个有效值 // { // upTemps[zoneNum - 1] = temp; // // 检查是否需要更新 // if(lastUpTemps.size() > zoneNum - 1 && // qAbs(lastUpTemps[zoneNum - 1] - temp) > 0.1f) // { // needUpdate = true; // } // } // } // else if(zoneLevel == "下层") // { // if(lowTemps[zoneNum - 1] == 0.0f) // 只取第一个有效值 // { // lowTemps[zoneNum - 1] = temp; // // 检查是否需要更新 // if(lastLowTemps.size() > zoneNum - 1 && // qAbs(lastLowTemps[zoneNum - 1] - temp) > 0.1f) // { // needUpdate = true; // } // } // } // } // } // } // // 如果没有变化且不是第一次更新,则不执行更新 // if(!needUpdate && !lastUpTemps.isEmpty()) // { // return; // } // // 更新显示 - 使用直接设置方法避免闪烁 // for(int i = 0; i < upGauges.size() && i < upTemps.size(); ++i) // { // if(lastUpTemps.size() <= i || qAbs(lastUpTemps[i] - upTemps[i]) > 0.1f) // { // upGauges[i]->setTemperatureDirect(upTemps[i]); // } // } // for(int i = 0; i < lowGauges.size() && i < lowTemps.size(); ++i) // { // if(lastLowTemps.size() <= i || qAbs(lastLowTemps[i] - lowTemps[i]) > 0.1f) // { // lowGauges[i]->setTemperatureDirect(lowTemps[i]); // } // } // // 保存当前温度值 // lastUpTemps = upTemps; // lastLowTemps = lowTemps; // } void JTR::refreshTemperatureData() { if(!DatabaseManager::instance().isConnected()) { qDebug() << "数据库未连接"; return; } // 初始化温度数组,使用上一次的值或0 QVector<float> upTemps = lastUpTemps.isEmpty() ? QVector<float>(10, 0.0f) : lastUpTemps; QVector<float> lowTemps = lastLowTemps.isEmpty() ? QVector<float>(10, 0.0f) : lastLowTemps; bool upUpdated = false; bool lowUpdated = false; // 获取最新的温度数据(扩大查询范围确保能获取到所有温区数据) QSqlQuery query(DatabaseManager::instance().getDatabase()); bool querySuccess = query.exec("SELECT zone_name, zone_level, actual_value, record_time FROM temperature_data " "WHERE zone_level IN ('上层', '下层') " "ORDER BY record_time DESC LIMIT 40"); // 增加查询数量 if(!querySuccess) { qDebug() << "温度查询失败:" << query.lastError().text(); return; } // 使用QSet记录已经处理过的温区,避免重复处理 QSet<QString> processedZones; while(query.next()) { QString zoneName = query.value(0).toString(); QString zoneLevel = query.value(1).toString(); float temp = query.value(2).toFloat(); QString zoneKey = zoneLevel + zoneName; // 创建唯一键 // 如果已经处理过这个温区,跳过 if(processedZones.contains(zoneKey)) continue; processedZones.insert(zoneKey); // 解析温区编号 QRegularExpression re("(\\d+)"); QRegularExpressionMatch match = re.match(zoneName); if(!match.hasMatch()) continue; int zoneNum = match.captured(1).toInt(); if(zoneNum < 1 || zoneNum > 10) continue; if(zoneLevel == "上层") { if(upTemps[zoneNum - 1] != temp) { upTemps[zoneNum - 1] = temp; upUpdated = true; } } else if(zoneLevel == "下层") { if(lowTemps[zoneNum - 1] != temp) { lowTemps[zoneNum - 1] = temp; lowUpdated = true; } } } // 更新上层温区显示 if(upUpdated || lastUpTemps.isEmpty()) { for(int i = 0; i < qMin(upGauges.size(), upTemps.size()); ++i) { upGauges[i]->setTemperatureDirect(upTemps[i]); } } // 更新下层温区显示 if(lowUpdated || lastLowTemps.isEmpty()) { for(int i = 0; i < qMin(lowGauges.size(), lowTemps.size()); ++i) { lowGauges[i]->setTemperatureDirect(lowTemps[i]); } } // 保存当前温度值 lastUpTemps = upTemps; lastLowTemps = lowTemps; qDebug() << "温度更新完成 - 上层:" << upTemps << "下层:" << lowTemps; } void JTR::refreshBoardCount() { if(!DatabaseManager::instance().isConnected()) { qDebug() << "数据库未连接"; return; } QSqlQuery query(DatabaseManager::instance().getDatabase()); bool querySuccess = query.exec( "SELECT board_out_count, record_time FROM board_count " "ORDER BY record_time DESC LIMIT 1" ); if(!querySuccess) { qDebug() << "出板数查询失败:" << query.lastError().text(); return; } if(query.next()) { int count = query.value(0).toInt(); QDateTime recordTime = query.value(1).toDateTime(); // 只更新比当前显示更新的数据 if(!lastBoardCountTime.isValid() || recordTime > lastBoardCountTime) { boardCountDisplay->setPlainText(QString::number(count)); lastBoardCountTime = recordTime; // 出板数更新后也更新条形图 updateBarChart(); qDebug() << "更新出板数显示:" << count << "时间:" << recordTime.toString(); } }else { boardCountDisplay->setPlainText("0"); } } 现在这个更新条形图的时候会闪烁更新,可能是因为每次更新会把原来的图像清除掉,然后显示新的数据图像,但是我需要不是这样的,我需要的是在原来的图像的基础上变化为新的图像,这样看起来比较流畅

import sys from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QRadioButton, QPushButton, QGroupBox, QFileDialog, QLabel, QScrollArea) from PySide6.QtGui import QPixmap, QImage, QPainter from PySide6.QtCore import Qt class PuzzleEditor(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("拼图编辑器") self.resize(800, 600) # ---------------- 初始化选择页控件 ---------------- self.selection_widget = QWidget() # 选择页容器 selection_layout = QVBoxLayout(self.selection_widget) selection_layout.setContentsMargins(50, 50, 50, 50) # 方向选择组 group_box = QGroupBox("请选择排列方向") group_layout = QHBoxLayout(group_box) self.horizontal_radio = QRadioButton("横向排列(左右拼接)") self.vertical_radio = QRadioButton("纵向排列(上下拼接)") self.vertical_radio.setChecked(True) # 默认纵向 group_layout.addWidget(self.horizontal_radio) group_layout.addWidget(self.vertical_radio) selection_layout.addWidget(group_box) # 确认按钮(切换到编辑页) self.confirm_btn = QPushButton("确认并开始编辑") self.confirm_btn.clicked.connect(self.show_editor) selection_layout.addWidget(self.confirm_btn) # ---------------- 初始化编辑页控件 ---------------- self.editor_widget = QWidget() # 编辑页容器 editor_layout = QVBoxLayout(self.editor_widget) # 操作按钮区 button_layout = QHBoxLayout() self.add_btn = QPushButton('添加图片') self.add_btn.clicked.connect(self.add_image) button_layout.addWidget(self.add_btn) self.export_btn = QPushButton('导出拼图') self.export_btn.clicked.connect(self.export_image) self.export_btn.setEnabled(False) # 初始不可用 button_layout.addWidget(self.export_btn) editor_layout.addLayout(button_layout) # 图片显示区(带滚动条) self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) self.images_container = QWidget() # 图片容器 self.images_layout = QVBoxLayout(self.images_container) # 默认纵向布局 self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) self.scroll_area.setWidget(self.images_container) editor_layout.addWidget(self.scroll_area) # ---------------- 初始显示选择页 ---------------- self.setCentralWidget(self.selection_widget) def show_editor(self): """切换到编辑页并设置布局方向""" # 根据选择切换布局 if self.horizontal_radio.isChecked(): # 清空原布局并设置横向 self.clear_layout(self.images_layout) self.images_layout = QHBoxLayout(self.images_container) self.images_layout.setAlignment(Qt.AlignLeft) else: # 清空原布局并保持纵向 self.clear_layout(self.images_layout) self.images_layout = QVBoxLayout(self.images_container) self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) # 显示编辑页 self.setCentralWidget(self.editor_widget) def clear_layout(self, layout): """清空布局中的控件""" if layout: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget: widget.deleteLater() def add_image(self): """添加图片到布局""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif)" ) if not file_paths: return for path in file_paths: pixmap = QPixmap(path) if pixmap.isNull(): continue # 跳过无效图片 # 缩放图片(保持比例,最大200x200) scaled_pixmap = pixmap.scaled( 200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation ) # 创建图片标签并添加到布局 label = QLabel() label.setPixmap(scaled_pixmap) label.setAlignment(Qt.AlignCenter) self.images_layout.addWidget(label) # 添加图片后启用导出按钮 self.export_btn.setEnabled(True) def export_image(self): """导出拼接后的图片(直接合并布局中的图片)""" # 收集所有图片的pixmap pixmaps = [] for i in range(self.images_layout.count()): widget = self.images_layout.itemAt(i).widget() if isinstance(widget, QLabel) and widget.pixmap(): pixmaps.append(widget.pixmap()) if not pixmaps: return # 无图片可导出 # 计算合并后的尺寸(根据布局方向) if isinstance(self.images_layout, QHBoxLayout): # 横向 total_width = sum(p.width() for p in pixmaps) max_height = max(p.height() for p in pixmaps) else: # 纵向 max_width = max(p.width() for p in pixmaps) total_height = sum(p.height() for p in pixmaps) # 创建合并后的图片(直接绘制) if isinstance(self.images_layout, QHBoxLayout): merged_image = QImage(total_width, max_height, QImage.Format_ARGB32) else: merged_image = QImage(max_width, total_height, QImage.Format_ARGB32) merged_image.fill(Qt.white) # 白色背景 painter = QPainter(merged_image) x, y = 0, 0 for pixmap in pixmaps: if isinstance(self.images_layout, QHBoxLayout): # 横向:垂直居中绘制 y = (max_height - pixmap.height()) // 2 painter.drawPixmap(x, y, pixmap) x += pixmap.width() else: # 纵向:水平居中绘制 x = (max_width - pixmap.width()) // 2 painter.drawPixmap(x, y, pixmap) y += pixmap.height() painter.end() # 保存图片 file_path, _ = QFileDialog.getSaveFileName( self, "保存拼图", "", "PNG图片 (*.png);;JPG图片 (*.jpg)" ) if file_path: if not file_path.lower().endswith(('.png', '.jpg')): file_path += '.png' merged_image.save(file_path) if __name__ == "__main__": app = QApplication(sys.argv) window = PuzzleEditor() window.show() sys.exit(app.exec()) 不需要居中绘制

import sys from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QLabel, QScrollArea, QRadioButton, QGroupBox) from PySide6.QtGui import QPixmap from PySide6.QtCore import Qt class choosepage(QMainWindow): def __init__(self): super().__init__() layout_choose = QGroupBox("请选择排列方向") layout_choosebox = QHBoxLayout(layout_choose) self.wide_btn = QRadioButton("横向排列拼图") self.high_btn = QRadioButton("纵向排列拼图") self.wide_btn.clicked.connect(self.arrange_horizontal) self.high_btn.clicked.connect(self.arrange_vertical) layout_choosebox.addWidget(self.wide_btn) layout_choosebox.addWidget(self.high_btn) class PuzzleEditor(QMainWindow): def __init__(self): super().__init__() self.image_labels = [] self.container_layout = QVBoxLayout(self.images_container) self.images_container = QWidget() self.images_layout = QVBoxLayout() self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) self.container_layout.addLayout(self.images_layout) button_layout = QHBoxLayout() self.add_btn = QPushButton('添加图片') self.add_btn.clicked.connect(self.add_image) button_layout.addWidget(self.add_btn) self.export_btn = QPushButton('导出拼图') self.export_btn.clicked.connect(self.export_image) def arrange_horizontal(self): self.images_layout = QHBoxLayout() self.images_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.images_layout.setSpacing(10) def arrange_vertical(self): self.images_layout = QVBoxLayout() self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) self.container_layout.addLayout(self.images_layout) def add_image(self): """添加图片""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)") pixmap = QPixmap(file_paths) scaled_pixmap = pixmap.scaled(200, 200, # 缩放尺寸 Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_list.append(scaled_pixmap) def export_image : if __name__ == "__main__": app = QApplication(sys.argv) window = PuzzleEditor() window.resize(800, 600) window.show() sys.exit(app.exec()) 修改

import sys from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QLabel, QScrollArea, QRadioButton, QGroupBox, QFileDialog) from PySide6.QtGui import QPixmap, QImage, QPainter from PySide6.QtCore import Qt class PuzzleEditor(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("拼图编辑器") self.image_labels = [] self.current_layout = "vertical" # 记录当前布局方向 main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QVBoxLayout(main_widget) # 布局选择区域 layout_choose = QGroupBox("请选择排列方向") layout_choosebox = QHBoxLayout(layout_choose) self.wide_btn = QRadioButton("横向排列拼图") self.high_btn = QRadioButton("纵向排列拼图") self.high_btn.setChecked(True) # 默认选择纵向排列 # 连接信号 self.wide_btn.clicked.connect(self.arrange_horizontal) self.high_btn.clicked.connect(self.arrange_vertical) layout_choosebox.addWidget(self.wide_btn) layout_choosebox.addWidget(self.high_btn) main_layout.addWidget(layout_choose) # 按钮区域 button_layout = QHBoxLayout() self.add_btn = QPushButton('添加图片') self.add_btn.clicked.connect(self.add_image) button_layout.addWidget(self.add_btn) self.export_btn = QPushButton('导出拼图') self.export_btn.clicked.connect(self.export_puzzle) self.export_btn.setEnabled(False) # 初始不可用 button_layout.addWidget(self.export_btn) main_layout.addLayout(button_layout) # 图片显示区域 self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) self.images_container = QWidget() # 必须先创建容器 self.container_layout = QVBoxLayout(self.images_container) # 容器布局 self.images_layout = QVBoxLayout() # 图片布局 self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) self.container_layout.addLayout(self.images_layout) self.scroll_area.setWidget(self.images_container) main_layout.addWidget(self.scroll_area) self.resize(800, 600) def arrange_horizontal(self): """切换为横向布局""" self.current_layout = "horizontal" self._rearrange_layout() def arrange_vertical(self): """切换为纵向布局""" self.current_layout = "vertical" self._rearrange_layout() def _rearrange_layout(self): """重新排列图片""" # 保存当前图片 current_images = [] for label in self.image_labels: current_images.append(label.pixmap()) # 清除现有布局 self._clear_layout(self.images_layout) self.image_labels = [] # 创建新布局 if self.current_layout == "horizontal": self.images_layout = QHBoxLayout() self.images_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) else: self.images_layout = QVBoxLayout() self.images_layout.setAlignment(Qt.AlignTop) self.images_layout.setSpacing(10) # 移除旧布局,添加新布局 if self.container_layout.count() > 0: old_layout = self.container_layout.takeAt(0) if old_layout.layout(): QWidget().setLayout(old_layout.layout()) self.container_layout.addLayout(self.images_layout) # 重新添加图片 for pixmap in current_images: label = QLabel() label.setPixmap(pixmap) label.setAlignment(Qt.AlignCenter) self.images_layout.addWidget(label) self.image_labels.append(label) # 如果有图片,启用导出按钮 self.export_btn.setEnabled(len(self.image_labels) > 0) def _clear_layout(self, layout): """清除布局中的所有控件""" while layout.count(): child = layout.takeAt(0) if child.widget(): child.widget().deleteLater() def add_image(self): """添加多张图片""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)" ) if file_paths: for file_path in file_paths: pixmap = QPixmap(file_path) if not pixmap.isNull(): # 缩放图片 scaled_pixmap = pixmap.scaled( 200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation ) # 创建标签并显示图片 label = QLabel() label.setPixmap(scaled_pixmap) label.setAlignment(Qt.AlignCenter) # 添加到布局 self.images_layout.addWidget(label) self.image_labels.append(label) # 如果有图片,启用导出按钮 self.export_btn.setEnabled(len(self.image_labels) > 0) def export_puzzle(self): """导出拼图为图片文件""" if not self.image_labels: return # 获取保存路径 file_path, _ = QFileDialog.getSaveFileName( self, "保存拼图", "", "图片文件 (*.png);;所有文件 (*)" ) if not file_path: return # 确保文件扩展名 if not file_path.lower().endswith('.png'): file_path += '.png' # 创建大图 if self.current_layout == "horizontal": # 横向排列:宽度是所有图片宽度之和,高度是最大图片高度 total_width = sum(label.pixmap().width() for label in self.image_labels) max_height = max(label.pixmap().height() for label in self.image_labels) result_image = QImage(total_width, max_height, QImage.Format_ARGB32) else: # 纵向排列:宽度是最大图片宽度,高度是所有图片高度之和 max_width = max(label.pixmap().width() for label in self.image_labels) total_height = sum(label.pixmap().height() for label in self.image_labels) result_image = QImage(max_width, total_height, QImage.Format_ARGB32) # 填充白色背景 result_image.fill(Qt.white) # 绘制所有图片 painter = QPainter(result_image) x, y = 0, 0 for label in self.image_labels: pixmap = label.pixmap() if self.current_layout == "horizontal": # 横向排列:水平放置 painter.drawPixmap(x, (max_height - pixmap.height()) // 2, pixmap) x += pixmap.width() else: # 纵向排列:垂直放置 painter.drawPixmap((max_width - pixmap.width()) // 2, y, pixmap) y += pixmap.height() painter.end() # 保存图片 result_image.save(file_path) if __name__ == "__main__": app = QApplication(sys.argv) window = PuzzleEditor() window.show() sys.exit(app.exec()) 不需要清楚

import sys import json import os from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QLabel, QDialog, QLineEdit, QTextEdit, QComboBox, QMessageBox, QTabWidget, QToolBar, QAction, QTreeWidget, QTreeWidgetItem, QSplitter, QStatusBar, QToolButton, QStackedWidget, QDateEdit, QListWidgetItem, QCheckBox, QFormLayout) from PyQt5.QtCore import Qt, QSize, QDate from PyQt5.QtGui import QIcon, QFont, QColor, QPalette, QBrush, QLinearGradient class WorkflowManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("高级工作流程管理器 - 专业版") self.resize(1200, 800) self.workflows = [] self.current_workflow = None self.current_stage_index = 0 self.load_workflows() # 设置主窗口样式 - 现代专业配色方案 self.setStyleSheet(""" QMainWindow { background-color: #f8f9fa; font-family: 'Segoe UI', 'Arial', sans-serif; } QToolBar { background-color: #ffffff; border-bottom: 1px solid #e9ecef; padding: 5px; spacing: 10px; } QPushButton { background-color: #4e73df; color: white; border-radius: 6px; padding: 8px 16px; min-width: 90px; font-size: 14px; font-weight: 500; border: none; transition: background-color 0.3s; } QPushButton:hover { background-color: #2e59d9; } QPushButton:pressed { background-color: #1a3bb3; } QListWidget, QTreeWidget { background-color: white; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; padding: 5px; } QTabWidget::pane { border: 1px solid #e0e0e0; border-radius: 8px; background: white; margin-top: 5px; } QTabBar::tab { background: #f8f9fa; border: 1px solid #e0e0e0; border-bottom: none; padding: 10px 20px; font-size: 14px; color: #495057; border-top-left-radius: 6px; border-top-right-radius: 6px; margin-right: 2px; } QTabBar::tab:selected { background: white; border-bottom-color: white; font-weight: 600; color: #4e73df; } QTabBar::tab:hover { background: #e9ecef; } QLabel { font-size: 14px; color: #495057; } QLineEdit, QTextEdit, QComboBox { font-size: 14px; padding: 8px; background-color: white; color: #495057; border: 1px solid #ced4da; border-radius: 4px; } QLineEdit:focus, QTextEdit:focus { border: 1px solid #4e73df; } QCheckBox { font-size: 14px; color: #495057; spacing: 8px; } QStatusBar { background-color: #ffffff; color: #495057; font-size: 13px; border-top: 1px solid #e9ecef; } """) # 设置应用字体 app_font = QFont("Segoe UI", 10) QApplication.setFont(app_font) # 创建主界面 self.create_main_ui() # 加载默认工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.update_workflow_display() def create_main_ui(self): """创建主界面""" # 创建工具栏 toolbar = QToolBar("主工具栏") toolbar.setIconSize(QSize(24, 24)) toolbar.setMovable(False) self.addToolBar(toolbar) # 添加工具栏动作 new_workflow_action = QAction(QIcon.fromTheme("document-new"), "新建流程", self) new_workflow_action.triggered.connect(self.new_workflow) toolbar.addAction(new_workflow_action) edit_workflow_action = QAction(QIcon.fromTheme("document-edit"), "编辑流程", self) edit_workflow_action.triggered.connect(self.edit_workflow) toolbar.addAction(edit_workflow_action) delete_workflow_action = QAction(QIcon.fromTheme("edit-delete"), "删除流程", self) delete_workflow_action.triggered.connect(self.delete_workflow) toolbar.addAction(delete_workflow_action) save_action = QAction(QIcon.fromTheme("document-save"), "保存", self) save_action.triggered.connect(self.save_workflows) toolbar.addAction(save_action) toolbar.addSeparator() stats_action = QAction(QIcon.fromTheme("view-statistics"), "统计", self) stats_action.triggered.connect(self.show_stats) toolbar.addAction(stats_action) # 创建主内容区域 main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QHBoxLayout(main_widget) main_layout.setContentsMargins(15, 15, 15, 15) main_layout.setSpacing(15) # 左侧工作流程列表 left_panel = QWidget() left_panel.setFixedWidth(280) left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 0, 0) workflow_label = QLabel("工作流程") workflow_label.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 8px;") left_layout.addWidget(workflow_label) self.workflow_list = QTreeWidget() self.workflow_list.setHeaderLabel("") self.workflow_list.setRootIsDecorated(False) self.workflow_list.itemClicked.connect(self.select_workflow) left_layout.addWidget(self.workflow_list) main_layout.addWidget(left_panel) # 右侧主内容区 right_splitter = QSplitter(Qt.Vertical) # 工作流程可视化区 self.flow_visualization = QWidget() self.flow_visualization.setStyleSheet("background-color: white; border-radius: 8px; padding: 15px;") flow_viz_layout = QVBoxLayout(self.flow_visualization) self.flow_title = QLabel() self.flow_title.setStyleSheet("font-size: 20px; font-weight: 600; color: #4e73df; margin-bottom: 15px;") flow_viz_layout.addWidget(self.flow_title) # 添加滚动区域 scroll_widget = QWidget() scroll_layout = QHBoxLayout(scroll_widget) scroll_layout.setContentsMargins(0, 0, 0, 0) self.flow_stages = QHBoxLayout() self.flow_stages.setSpacing(15) scroll_layout.addLayout(self.flow_stages) flow_viz_layout.addWidget(scroll_widget) # 添加流程控制按钮 self.flow_control = QHBoxLayout() self.flow_control.setAlignment(Qt.AlignCenter) self.flow_control.setSpacing(20) self.prev_stage_btn = QPushButton("◀ 上一阶段") self.prev_stage_btn.setIcon(QIcon.fromTheme("go-previous")) self.prev_stage_btn.setStyleSheet("min-width: 120px;") self.prev_stage_btn.clicked.connect(self.prev_stage) self.next_stage_btn = QPushButton("下一阶段 ▶") self.next_stage_btn.setIcon(QIcon.fromTheme("go-next")) self.next_stage_btn.setStyleSheet("min-width: 120px;") self.next_stage_btn.clicked.connect(self.next_stage) self.flow_control.addWidget(self.prev_stage_btn) self.flow_control.addWidget(self.next_stage_btn) flow_viz_layout.addLayout(self.flow_control) right_splitter.addWidget(self.flow_visualization) # 成果物管理区 self.artifacts_management = QTabWidget() self.artifacts_management.setStyleSheet(""" QTabWidget::pane { border: none; } """) # 成果物检查 self.artifacts_check = QWidget() self.artifacts_check.setStyleSheet("background-color: white; border-radius: 8px; padding: 15px;") artifacts_layout = QVBoxLayout(self.artifacts_check) self.artifacts_label = QLabel("成果物检查") self.artifacts_label.setStyleSheet("font-size: 18px; font-weight: 600; color: #4e73df; margin-bottom: 10px;") artifacts_layout.addWidget(self.artifacts_label) self.artifacts_list = QListWidget() self.artifacts_list.setStyleSheet("border-radius: 6px;") artifacts_layout.addWidget(self.artifacts_list) # 成果物状态说明 self.status_info = QLabel("") self.status_info.setStyleSheet(""" font-size: 14px; font-weight: 500; color: #6c757d; background-color: #f8f9fa; padding: 10px; border-radius: 6px; margin-top: 10px; """) artifacts_layout.addWidget(self.status_info) self.artifacts_management.addTab(self.artifacts_check, "成果物检查") # 统计 self.stats_widget = QWidget() self.stats_widget.setStyleSheet("background-color: white; border-radius: 8px; padding: 15px;") stats_layout = QVBoxLayout(self.stats_widget) self.stats_label = QLabel("工作流程统计信息将显示在这里") self.stats_label.setStyleSheet("font-size: 14px;") stats_layout.addWidget(self.stats_label) self.artifacts_management.addTab(self.stats_widget, "统计") right_splitter.addWidget(self.artifacts_management) main_layout.addWidget(right_splitter, 1) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") def load_workflows(self): """加载工作流程""" if os.path.exists("workflows.json"): with open("workflows.json", "r") as f: self.workflows = json.load(f) else: # 创建默认工作流程 - 使用更协调的颜色方案 default_workflow = { "name": "软件开发变更流程", "description": "标准的软件开发变更管理流程", "stages": [ {"name": "变更点分析", "description": "分析客户提供的变更需求", "color": "#4e73df", "artifacts": ["需求分析文档"], "completed": False}, {"name": "变更点差分", "description": "识别变更前后的差异", "color": "#1cc88a", "artifacts": ["差异分析报告"], "completed": False}, {"name": "设计书修改", "description": "更新设计文档", "color": "#36b9cc", "artifacts": ["更新后的设计文档"], "completed": False}, {"name": "代码修改", "description": "实施代码变更", "color": "#f6c23e", "artifacts": ["变更后的代码"], "completed": False}, {"name": "代码验证", "description": "测试变更代码", "color": "#e74a3b", "artifacts": ["测试报告"], "completed": False}, {"name": "代码审核", "description": "进行代码审查", "color": "#6f42c1", "artifacts": ["代码审查意见"], "completed": False}, {"name": "代码集成", "description": "合并到主分支", "color": "#fd7e14", "artifacts": ["集成后的代码"], "completed": False}, {"name": "成果物输出", "description": "生成最终交付物", "color": "#20c997", "artifacts": ["最终交付包"], "completed": False} ], "tasks": [] } self.workflows.append(default_workflow) self.save_workflows() def save_workflows(self): """保存工作流程""" with open("workflows.json", "w") as f: json.dump(self.workflows, f, indent=4) self.status_bar.showMessage("工作流程已保存", 3000) def update_workflow_display(self): """更新工作流程显示""" if not self.current_workflow: return # 更新工作流程列表 self.workflow_list.clear() for workflow in self.workflows: item = QTreeWidgetItem(self.workflow_list) item.setText(0, workflow["name"]) item.setFont(0, QFont("Segoe UI", 11)) item.setData(0, Qt.UserRole, workflow) # 高亮显示当前选中的工作流程 if workflow == self.current_workflow: item.setBackground(0, QColor("#e9ecef")) # 更新流程标题 self.flow_title.setText(f"{self.current_workflow['name']} - {self.current_workflow['description']}") # 更新流程阶段显示 self.clear_flow_stages() for i, stage in enumerate(self.current_workflow["stages"]): stage_widget = self.create_stage_widget(stage, i) self.flow_stages.addWidget(stage_widget) # 更新按钮状态 self.update_buttons_state() def clear_flow_stages(self): """清空流程阶段显示""" while self.flow_stages.count(): item = self.flow_stages.takeAt(0) if item.widget(): item.widget().deleteLater() def create_stage_widget(self, stage, index): """创建单个阶段显示组件""" stage_widget = QWidget() stage_widget.setFixedWidth(200) # 根据阶段状态设置样式 if index == self.current_stage_index: # 当前阶段 - 高亮显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: {stage['color']}20; border: 2px solid {stage['color']}; border-radius: 10px; padding: 15px; }} QLabel {{ color: {stage['color']}; }} """) elif index < self.current_stage_index: # 已完成阶段 - 灰色显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #e9ecef; border: 2px solid #ced4da; border-radius: 10px; padding: 15px; }} QLabel {{ color: #6c757d; }} """) else: # 未开始阶段 - 默认样式 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #f8f9fa; border: 2px solid #e9ecef; border-radius: 10px; padding: 15px; }} QLabel {{ color: #6c757d; }} """) layout = QVBoxLayout(stage_widget) layout.setSpacing(8) title = QLabel(stage["name"]) title.setStyleSheet("font-weight: 600; font-size: 16px;") layout.addWidget(title) desc = QLabel(stage["description"]) desc.setWordWrap(True) desc.setStyleSheet("font-size: 13px;") layout.addWidget(desc) # 显示阶段状态 status_text = "已完成" if stage.get("completed", False) else "进行中" if index == self.current_stage_index else "未开始" status = QLabel(f"状态: {status_text}") status.setStyleSheet("font-size: 12px; font-style: italic; margin-top: 8px;") layout.addWidget(status) # 添加阶段选择按钮 select_btn = QPushButton("选择此阶段") select_btn.setStyleSheet(f""" QPushButton {{ background-color: {stage['color']}; color: white; font-weight: 500; margin-top: 10px; }} QPushButton:hover {{ background-color: {self.darken_color(stage['color'], 20)}; }} """) select_btn.clicked.connect(lambda _, i=index: self.select_stage(i)) layout.addWidget(select_btn) return stage_widget def darken_color(self, hex_color, percent): """使颜色变暗指定百分比""" r = int(int(hex_color[1:3], 16) * (100 - percent) / 100) g = int(int(hex_color[3:5], 16) * (100 - percent) / 100) b = int(int(hex_color[5:7], 16) * (100 - percent) / 100) return f"#{max(0, min(255, r)):02x}{max(0, min(255, g)):02x}{max(0, min(255, b)):02x}" def select_workflow(self, item): """选择工作流程""" self.current_workflow = item.data(0, Qt.UserRole) self.current_stage_index = 0 self.update_workflow_display() self.update_task_display() self.update_artifacts_display() def select_stage(self, index): """选择阶段""" # 允许选择任何阶段,但会提示如果跳过未完成阶段 if index > self.current_stage_index: reply = QMessageBox.question( self, '确认跳转', '您正在跳转到未完成的阶段。确定要继续吗?', QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.No: return self.current_stage_index = index self.update_workflow_display() self.update_task_display() self.update_artifacts_display() def prev_stage(self): """返回上一阶段""" if self.current_stage_index > 0: self.current_stage_index -= 1 self.update_workflow_display() self.update_task_display() self.update_artifacts_display() self.status_bar.showMessage(f"已返回阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 3000) def next_stage(self): """进入下一阶段""" if self.current_stage_index < len(self.current_workflow["stages"]) - 1: # 检查当前阶段是否已完成 current_stage = self.current_workflow["stages"][self.current_stage_index] # 检查成果物是否全部完成 if not current_stage.get("completed", False): QMessageBox.warning(self, "警告", "当前阶段成果物未全部完成,无法进入下一阶段!") return self.current_stage_index += 1 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已进入新阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 3000) def check_artifacts_completed(self): """检查成果物是否全部完成""" for i in range(self.artifacts_list.count()): item = self.artifacts_list.item(i) widget = self.artifacts_list.itemWidget(item) checkbox = widget.findChild(QCheckBox) if not checkbox.isChecked(): return False return True def update_buttons_state(self): """更新按钮状态""" self.prev_stage_btn.setEnabled(self.current_stage_index > 0) self.next_stage_btn.setEnabled( self.current_stage_index < len(self.current_workflow["stages"]) - 1 ) def new_workflow(self): """新建工作流程""" dialog = WorkflowDialog(self) if dialog.exec_() == QDialog.Accepted: new_workflow = dialog.get_workflow() self.workflows.append(new_workflow) self.current_workflow = new_workflow self.current_stage_index = 0 self.save_workflows() self.update_workflow_display() self.update_task_display() self.update_artifacts_display() def edit_workflow(self): """编辑工作流程""" if not self.current_workflow: return dialog = WorkflowDialog(self, self.current_workflow) if dialog.exec_() == QDialog.Accepted: edited_workflow = dialog.get_workflow() index = self.workflows.index(self.current_workflow) self.workflows[index] = edited_workflow self.current_workflow = edited_workflow self.save_workflows() self.update_workflow_display() self.update_task_display() self.update_artifacts_display() def delete_workflow(self): """删除当前工作流程""" if not self.current_workflow: QMessageBox.warning(self, "警告", "请先选择一个工作流程!") return reply = QMessageBox.question( self, '确认删除', f"确定要删除工作流程【{self.current_workflow['name']}】吗?\n此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: # 从工作流程列表中移除当前工作流程 self.workflows.remove(self.current_workflow) # 更新当前选中的工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.current_stage_index = 0 else: self.current_workflow = None self.current_stage_index = -1 # 保存更改并更新界面 self.save_workflows() self.update_workflow_display() self.update_task_display() self.update_artifacts_display() self.status_bar.showMessage(f"工作流程已删除", 3000) def update_task_display(self): """更新成果物显示""" self.update_artifacts_display() def update_artifacts_display(self): """更新成果物显示""" self.artifacts_list.clear() if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] self.artifacts_label.setText(f"成果物检查 - {current_stage['name']}") for artifact in current_stage.get("artifacts", []): item = QListWidgetItem() widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(10, 5, 10, 5) checkbox = QCheckBox(artifact) checkbox.setStyleSheet("font-size: 11px;") checkbox.stateChanged.connect(self.update_stage_status) layout.addWidget(checkbox) item.setSizeHint(widget.sizeHint()) self.artifacts_list.addItem(item) self.artifacts_list.setItemWidget(item, widget) # 更新状态信息 self.update_stage_status() def update_stage_status(self): """更新阶段状态""" if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] total_artifacts = len(current_stage.get("artifacts", [])) completed_artifacts = 0 for i in range(self.artifacts_list.count()): item = self.artifacts_list.item(i) widget = self.artifacts_list.itemWidget(item) checkbox = widget.findChild(QCheckBox) if checkbox.isChecked(): completed_artifacts += 1 # 更新状态文本 if total_artifacts == 0: status_text = "当前阶段没有成果物" status_color = "#6c757d" elif completed_artifacts == 0: status_text = "状态: 待开始 (0% 完成)" status_color = "#e74a3b" current_stage["completed"] = False elif completed_artifacts < total_artifacts: percent = (completed_artifacts / total_artifacts) * 100 status_text = f"状态: 进行中 ({percent:.0f}% 完成)" status_color = "#f6c23e" current_stage["completed"] = False else: status_text = "状态: 已完成 (100% 完成)" status_color = "#1cc88a" current_stage["completed"] = True self.status_info.setText(status_text) self.status_info.setStyleSheet(f""" font-size: 14px; font-weight: 500; color: white; background-color: {status_color}; padding: 10px; border-radius: 6px; margin-top: 10px; """) self.update_buttons_state() def show_stats(self): """显示统计信息""" if not self.current_workflow: return stats_text = f"{self.current_workflow['name']} 统计" # 按阶段统计 stats_text += "按阶段统计" for i, stage in enumerate(self.current_workflow["stages"]): total_artifacts = len(stage.get("artifacts", [])) status = "已完成" if stage.get("completed", False) else "进行中" if i == self.current_stage_index else "未开始" stats_text += f"" stats_text += f"" stats_text += f"{stage['name']}: {total_artifacts} 个成果物 ({status})" stats_text += "" stats_text += "" # 完成率统计 completed_stages = sum(1 for stage in self.current_workflow["stages"] if stage.get("completed", False)) total_stages = len(self.current_workflow["stages"]) completion_rate = (completed_stages / total_stages) * 100 if total_stages > 0 else 0 # 进度条 stats_text += f"流程完成率: {completion_rate:.0f}%" stats_text += f""" """ self.stats_label.setText(stats_text) self.artifacts_management.setCurrentWidget(self.stats_widget) class WorkflowDialog(QDialog): """工作流程编辑对话框""" def __init__(self, parent=None, workflow=None): super().__init__(parent) self.setWindowTitle("编辑工作流程" if workflow else "新建工作流程") self.resize(700, 500) self.workflow = workflow if workflow else { "name": "", "description": "", "stages": [], "tasks": [] } self.setup_ui() def setup_ui(self): """设置对话框UI""" layout = QVBoxLayout(self) layout.setContentsMargins(20, 20, 20, 20) # 标题 title = QLabel("编辑工作流程" if self.workflow.get("name") else "新建工作流程") title.setStyleSheet("font-size: 18px; font-weight: 600; color: #4e73df; margin-bottom: 20px;") layout.addWidget(title) # 基本信息 form_layout = QFormLayout() form_layout.setLabelAlignment(Qt.AlignRight) form_layout.setVerticalSpacing(15) self.name_edit = QLineEdit(self.workflow["name"]) self.name_edit.setPlaceholderText("输入工作流程名称") form_layout.addRow("流程名称:", self.name_edit) self.desc_edit = QTextEdit(self.workflow["description"]) self.desc_edit.setPlaceholderText("输入工作流程描述") self.desc_edit.setFixedHeight(80) form_layout.addRow("流程描述:", self.desc_edit) layout.addLayout(form_layout) # 阶段管理 stages_label = QLabel("阶段管理") stages_label.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-top: 20px; margin-bottom: 10px;") layout.addWidget(stages_label) # 阶段列表和编辑区 stages_container = QHBoxLayout() stages_container.setSpacing(20) # 阶段列表 stages_list_container = QVBoxLayout() self.stages_list = QListWidget() self.stages_list.setFixedHeight(180) self.update_stages_list() self.stages_list.itemClicked.connect(self.select_stage) stages_list_container.addWidget(self.stages_list) stages_container.addLayout(stages_list_container, 1) # 阶段编辑区 stage_edit_container = QVBoxLayout() self.stage_edit = QWidget() self.stage_edit.setStyleSheet("background-color: #f8f9fa; border-radius: 8px; padding: 15px;") stage_edit_layout = QFormLayout(self.stage_edit) stage_edit_layout.setVerticalSpacing(12) self.stage_name_edit = QLineEdit() self.stage_name_edit.setPlaceholderText("输入阶段名称") stage_edit_layout.addRow("阶段名称:", self.stage_name_edit) self.stage_desc_edit = QTextEdit() self.stage_desc_edit.setPlaceholderText("输入阶段描述") self.stage_desc_edit.setFixedHeight(60) stage_edit_layout.addRow("阶段描述:", self.stage_desc_edit) self.stage_color_edit = QComboBox() self.stage_color_edit.addItems(["#4e73df", "#1cc88a", "#36b9cc", "#f6c23e", "#e74a3b", "#6f42c1", "#fd7e14", "#20c997"]) stage_edit_layout.addRow("阶段颜色:", self.stage_color_edit) # 成果物编辑 self.artifacts_edit = QTextEdit() self.artifacts_edit.setPlaceholderText("每行输入一个成果物名称") stage_edit_layout.addRow("成果物:", self.artifacts_edit) stage_edit_container.addWidget(self.stage_edit) # 阶段操作按钮 stage_btn_layout = QHBoxLayout() stage_btn_layout.setSpacing(10) self.add_stage_btn = QPushButton("添加阶段") self.add_stage_btn.setStyleSheet("background-color: #1cc88a; color: white;") self.add_stage_btn.clicked.connect(self.add_stage) stage_btn_layout.addWidget(self.add_stage_btn) self.update_stage_btn = QPushButton("更新阶段") self.update_stage_btn.setStyleSheet("background-color: #4e73df; color: white;") self.update_stage_btn.clicked.connect(self.update_stage) stage_btn_layout.addWidget(self.update_stage_btn) self.remove_stage_btn = QPushButton("删除阶段") self.remove_stage_btn.setStyleSheet("background-color: #e74a3b; color: white;") self.remove_stage_btn.clicked.connect(self.remove_stage) stage_btn_layout.addWidget(self.remove_stage_btn) stage_edit_container.addLayout(stage_btn_layout) stages_container.addLayout(stage_edit_container, 2) layout.addLayout(stages_container) # 对话框按钮 btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignRight) btn_layout.setSpacing(15) save_btn = QPushButton("保存") save_btn.setStyleSheet("background-color: #4e73df; color: white; min-width: 100px;") save_btn.clicked.connect(self.accept) btn_layout.addWidget(save_btn) cancel_btn = QPushButton("取消") cancel_btn.setStyleSheet("background-color: #6c757d; color: white; min-width: 100px;") cancel_btn.clicked.connect(self.reject) btn_layout.addWidget(cancel_btn) layout.addLayout(btn_layout) def update_stages_list(self): """更新阶段列表""" self.stages_list.clear() for stage in self.workflow["stages"]: item = QListWidgetItem(stage["name"]) item.setData(Qt.UserRole, stage) self.stages_list.addItem(item) def select_stage(self, item): """选择阶段进行编辑""" stage = item.data(Qt.UserRole) self.stage_name_edit.setText(stage["name"]) self.stage_desc_edit.setText(stage["description"]) self.stage_color_edit.setCurrentText(stage["color"]) self.artifacts_edit.setText("\n".join(stage.get("artifacts", []))) def add_stage(self): """添加新阶段""" name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return new_stage = { "name": name, "description": self.stage_desc_edit.toPlainText().strip(), "color": self.stage_color_edit.currentText(), "artifacts": [a.strip() for a in self.artifacts_edit.toPlainText().split("\n") if a.strip()], "completed": False } self.workflow["stages"].append(new_stage) self.update_stages_list() self.clear_stage_edit() def update_stage(self): """更新当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return stage = current_item.data(Qt.UserRole) stage["name"] = name stage["description"] = self.stage_desc_edit.toPlainText().strip() stage["color"] = self.stage_color_edit.currentText() stage["artifacts"] = [a.strip() for a in self.artifacts_edit.toPlainText().split("\n") if a.strip()] self.update_stages_list() def remove_stage(self): """删除当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return reply = QMessageBox.question(self, '确认删除', f"确定要删除阶段【{current_item.text()}】吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: stage = current_item.data(Qt.UserRole) self.workflow["stages"].remove(stage) self.update_stages_list() self.clear_stage_edit() def clear_stage_edit(self): """清空阶段编辑区""" self.stage_name_edit.clear() self.stage_desc_edit.clear() self.stage_color_edit.setCurrentIndex(0) self.artifacts_edit.clear() def get_workflow(self): """获取编辑后的工作流程""" return { "name": self.name_edit.text().strip(), "description": self.desc_edit.toPlainText().strip(), "stages": self.workflow["stages"], "tasks": self.workflow["tasks"] } if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") palette = QPalette() palette.setColor(QPalette.Window, QColor(248, 249, 250)) palette.setColor(QPalette.WindowText, QColor(73, 80, 87)) palette.setColor(QPalette.Base, QColor(255, 255, 255)) palette.setColor(QPalette.AlternateBase, QColor(248, 249, 250)) palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) palette.setColor(QPalette.ToolTipText, QColor(73, 80, 87)) palette.setColor(QPalette.Text, QColor(73, 80, 87)) palette.setColor(QPalette.Button, QColor(248, 249, 250)) palette.setColor(QPalette.ButtonText, QColor(73, 80, 87)) palette.setColor(QPalette.BrightText, QColor(255, 255, 255)) palette.setColor(QPalette.Highlight, QColor(78, 115, 223)) palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) app.setPalette(palette) # 设置应用字体 app_font = QFont("Segoe UI", 10) app.setFont(app_font) manager = WorkflowManager() manager.show() sys.exit(app.exec_()) 上一阶段与下一阶段的按钮在界面上看不见,实际存在

import sys import json import os from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QLabel, QDialog, QLineEdit, QTextEdit, QComboBox, QMessageBox, QTabWidget, QToolBar, QAction, QTreeWidget, QTreeWidgetItem, QSplitter, QStatusBar, QToolButton, QStackedWidget, QDateEdit, QListWidgetItem, QCheckBox, QFormLayout, QScrollArea, QSizePolicy) from PyQt5.QtCore import Qt, QSize, QDate from PyQt5.QtGui import QIcon, QFont, QColor, QPalette, QBrush, QLinearGradient class WorkflowManager(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("工作流程管理器") self.resize(1000, 650) # 缩小窗口尺寸 self.workflows = [] self.current_workflow = None self.current_stage_index = 0 self.load_workflows() # 设置主窗口样式 - 紧凑现代风格 self.setStyleSheet(""" QMainWindow { background-color: #f8f9fa; font-family: 'Segoe UI', 'Arial', sans-serif; } QToolBar { background-color: #ffffff; border-bottom: 1px solid #e9ecef; padding: 3px; spacing: 5px; } QPushButton { background-color: #4e73df; color: white; border-radius: 4px; padding: 6px 12px; min-width: 80px; font-size: 13px; font-weight: 500; border: none; } QListWidget, QTreeWidget { background-color: white; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 13px; padding: 3px; } QTabWidget::pane { border: 1px solid #e0e0e0; border-radius: 6px; background: white; margin-top: 3px; } QTabBar::tab { background: #f8f9fa; border: 1px solid #e0e0e0; border-bottom: none; padding: 8px 15px; font-size: 13px; color: #495057; border-top-left-radius: 4px; border-top-right-radius: 4px; margin-right: 2px; } QLabel { font-size: 13px; color: #495057; } QLineEdit, QTextEdit, QComboBox { font-size: 13px; padding: 6px; border: 1px solid #ced4da; border-radius: 4px; } QCheckBox { font-size: 13px; spacing: 6px; } QStatusBar { font-size: 12px; } """) # 设置应用字体 app_font = QFont("Segoe UI", 9) # 使用更小字号 QApplication.setFont(app_font) # 创建主界面 self.create_main_ui() # 加载默认工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.update_workflow_display() def create_main_ui(self): """创建主界面""" # 创建工具栏 toolbar = QToolBar("工具栏") toolbar.setIconSize(QSize(20, 20)) # 更小的图标 toolbar.setMovable(False) self.addToolBar(toolbar) # 添加工具栏动作 actions = [ ("document-new", "新建", self.new_workflow), ("document-edit", "编辑", self.edit_workflow), ("edit-delete", "删除", self.delete_workflow), ("document-save", "保存", self.save_workflows), ("view-statistics", "统计", self.show_stats) ] for icon, text, callback in actions: action = QAction(QIcon.fromTheme(icon), text, self) action.triggered.connect(callback) toolbar.addAction(action) toolbar.addSeparator() # 创建主内容区域 main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QHBoxLayout(main_widget) main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(10) # 左侧工作流程列表 (更窄) left_panel = QWidget() left_panel.setFixedWidth(220) # 缩小宽度 left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 0, 0) workflow_label = QLabel("工作流程") workflow_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df;") left_layout.addWidget(workflow_label) self.workflow_list = QTreeWidget() self.workflow_list.setHeaderLabel("") self.workflow_list.setRootIsDecorated(False) self.workflow_list.itemClicked.connect(self.select_workflow) left_layout.addWidget(self.workflow_list) main_layout.addWidget(left_panel) # 右侧主内容区 (使用分割器) right_splitter = QSplitter(Qt.Vertical) right_splitter.setHandleWidth(5) # 工作流程可视化区 (更紧凑) self.flow_visualization = QWidget() self.flow_visualization.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") flow_viz_layout = QVBoxLayout(self.flow_visualization) flow_viz_layout.setContentsMargins(5, 5, 5, 5) self.flow_title = QLabel() self.flow_title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 10px;") flow_viz_layout.addWidget(self.flow_title) # 滚动区域容器 scroll_container = QWidget() scroll_layout = QVBoxLayout(scroll_container) scroll_layout.setContentsMargins(0, 0, 0, 0) # 添加滚动区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_area.setStyleSheet("border: none;") scroll_widget = QWidget() self.flow_stages = QHBoxLayout(scroll_widget) self.flow_stages.setSpacing(10) self.flow_stages.setContentsMargins(5, 5, 5, 5) scroll_area.setWidget(scroll_widget) scroll_layout.addWidget(scroll_area) flow_viz_layout.addWidget(scroll_container, 1) # 添加权重 # 流程控制按钮 self.flow_control = QHBoxLayout() self.flow_control.setAlignment(Qt.AlignCenter) self.flow_control.setSpacing(15) self.flow_control.setContentsMargins(0, 10, 0, 0) self.prev_stage_btn = QPushButton("◀ 上一阶段") self.prev_stage_btn.setIcon(QIcon.fromTheme("go-previous")) self.prev_stage_btn.setStyleSheet(""" QPushButton { min-width: 100px; padding: 6px; background-color: #4e73df; color: white; border-radius: 4px; } QPushButton:disabled { background-color: #cccccc; color: #666666; } """) self.prev_stage_btn.clicked.connect(self.prev_stage) self.next_stage_btn = QPushButton("下一阶段 ▶") self.next_stage_btn.setIcon(QIcon.fromTheme("go-next")) self.next_stage_btn.setStyleSheet(""" QPushButton { min-width: 100px; padding: 6px; background-color: #1cc88a; color: white; border-radius: 4px; } QPushButton:disabled { background-color: #cccccc; color: #666666; } """) self.next_stage_btn.clicked.connect(self.next_stage) self.flow_control.addWidget(self.prev_stage_btn) self.flow_control.addWidget(self.next_stage_btn) # 创建按钮容器确保按钮始终可见 button_container = QWidget() button_container.setLayout(self.flow_control) button_container.setStyleSheet("background: transparent;") flow_viz_layout.addWidget(button_container) right_splitter.addWidget(self.flow_visualization) # 成果物管理区 (更紧凑) self.artifacts_management = QTabWidget() self.artifacts_management.setStyleSheet(""" QTabWidget::pane { border: none; } """) # 成果物检查 self.artifacts_check = QWidget() self.artifacts_check.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") artifacts_layout = QVBoxLayout(self.artifacts_check) artifacts_layout.setContentsMargins(5, 5, 5, 5) self.artifacts_label = QLabel("成果物检查") self.artifacts_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df;") artifacts_layout.addWidget(self.artifacts_label) self.artifacts_list = QListWidget() self.artifacts_list.setStyleSheet("border-radius: 4px;") artifacts_layout.addWidget(self.artifacts_list, 1) # 添加权重 # 成果物状态说明 (更紧凑) self.status_info = QLabel("") self.status_info.setStyleSheet(""" font-size: 12px; font-weight: 500; color: white; background-color: #6c757d; padding: 6px; border-radius: 4px; margin-top: 8px; """) artifacts_layout.addWidget(self.status_info) self.artifacts_management.addTab(self.artifacts_check, "成果物") # 统计 self.stats_widget = QWidget() self.stats_widget.setStyleSheet("background-color: white; border-radius: 6px; padding: 10px;") stats_layout = QVBoxLayout(self.stats_widget) self.stats_label = QLabel("工作流程统计信息将显示在这里") self.stats_label.setStyleSheet("font-size: 13px;") stats_layout.addWidget(self.stats_label) self.artifacts_management.addTab(self.stats_widget, "统计") right_splitter.addWidget(self.artifacts_management) # 设置分割器比例 right_splitter.setSizes([300, 400]) main_layout.addWidget(right_splitter, 1) # 状态栏 self.status_bar = QStatusBar() self.status_bar.setSizeGripEnabled(False) self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") def load_workflows(self): """加载工作流程并处理兼容性""" if os.path.exists("workflows.json"): with open("workflows.json", "r") as f: self.workflows = json.load(f) # 兼容旧数据结构:将字符串成果物转换为对象 for workflow in self.workflows: for stage in workflow["stages"]: if stage.get("artifacts") and isinstance(stage["artifacts"][0], str): stage["artifacts"] = [{"name": name, "completed": False} for name in stage["artifacts"]] else: # 创建默认工作流程 - 使用新数据结构 default_workflow = { "name": "软件开发变更流程", "description": "标准的软件开发变更管理流程", "stages": [ {"name": "变更点分析", "description": "分析客户提供的变更需求", "color": "#4e73df", "artifacts": [{"name": "需求分析文档", "completed": False}], "completed": False}, {"name": "变更点差分", "description": "识别变更前后的差异", "color": "#1cc88a", "artifacts": [{"name": "差异分析报告", "completed": False}], "completed": False}, {"name": "设计书修改", "description": "更新设计文档", "color": "#36b9cc", "artifacts": [{"name": "更新后的设计文档", "completed": False}], "completed": False}, {"name": "代码修改", "description": "实施代码变更", "color": "#f6c23e", "artifacts": [{"name": "变更后的代码", "completed": False}], "completed": False}, {"name": "代码验证", "description": "测试变更代码", "color": "#e74a3b", "artifacts": [{"name": "测试报告", "completed": False}], "completed": False}, {"name": "代码审核", "description": "进行代码审查", "color": "#6f42c1", "artifacts": [{"name": "代码审查意见", "completed": False}], "completed": False}, {"name": "代码集成", "description": "合并到主分支", "color": "#fd7e14", "artifacts": [{"name": "集成后的代码", "completed": False}], "completed": False}, {"name": "成果物输出", "description": "生成最终交付物", "color": "#20c997", "artifacts": [{"name": "最终交付包", "completed": False}], "completed": False} ], "tasks": [] } self.workflows.append(default_workflow) self.save_workflows() def save_workflows(self): """保存工作流程""" with open("workflows.json", "w") as f: json.dump(self.workflows, f, indent=4) self.status_bar.showMessage("工作流程已保存", 2000) def update_workflow_display(self): """更新工作流程显示""" if not self.current_workflow: return # 更新工作流程列表 self.workflow_list.clear() for workflow in self.workflows: item = QTreeWidgetItem(self.workflow_list) item.setText(0, workflow["name"]) item.setFont(0, QFont("Segoe UI", 10)) item.setData(0, Qt.UserRole, workflow) # 高亮显示当前选中的工作流程 if workflow == self.current_workflow: item.setBackground(0, QColor("#e9ecef")) # 更新流程标题 self.flow_title.setText(f"{self.current_workflow['name']} - {self.current_workflow['description']}") # 更新流程阶段显示 self.clear_flow_stages() for i, stage in enumerate(self.current_workflow["stages"]): stage_widget = self.create_stage_widget(stage, i) self.flow_stages.addWidget(stage_widget) # 更新按钮状态 self.update_buttons_state() def clear_flow_stages(self): """清空流程阶段显示""" while self.flow_stages.count(): item = self.flow_stages.takeAt(0) if item.widget(): item.widget().deleteLater() def create_stage_widget(self, stage, index): """创建单个阶段显示组件""" stage_widget = QWidget() stage_widget.setFixedWidth(180) # 更窄的组件 # 根据阶段状态设置样式 if index == self.current_stage_index: # 当前阶段 - 高亮显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: {stage['color']}20; border: 2px solid {stage['color']}; border-radius: 8px; padding: 10px; }} QLabel {{ color: {stage['color']}; }} """) elif index < self.current_stage_index: # 已完成阶段 - 灰色显示 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #e9ecef; border: 2px solid #ced4da; border-radius: 8px; padding: 10px; }} QLabel {{ color: #6c757d; }} """) else: # 未开始阶段 - 默认样式 stage_widget.setStyleSheet(f""" QWidget {{ background-color: #f8f9fa; border: 2px solid #e9ecef; border-radius: 8px; padding: 10px; }} QLabel {{ color: #6c757d; }} """) layout = QVBoxLayout(stage_widget) layout.setSpacing(5) title = QLabel(stage["name"]) title.setStyleSheet("font-weight: 600; font-size: 14px;") layout.addWidget(title) desc = QLabel(stage["description"]) desc.setWordWrap(True) desc.setStyleSheet("font-size: 11px;") layout.addWidget(desc) # 显示阶段状态 status_text = "已完成" if stage.get("completed", False) else "进行中" if index == self.current_stage_index else "未开始" status = QLabel(f"状态: {status_text}") status.setStyleSheet("font-size: 10px; font-style: italic; margin-top: 5px;") layout.addWidget(status) # 添加阶段选择按钮 select_btn = QPushButton("选择") select_btn.setStyleSheet(f""" QPushButton {{ background-color: {stage['color']}; color: white; font-weight: 500; margin-top: 8px; padding: 5px; font-size: 11px; }} QPushButton:hover {{ background-color: {self.darken_color(stage['color'], 20)}; }} """) select_btn.clicked.connect(lambda _, i=index: self.select_stage(i)) layout.addWidget(select_btn) return stage_widget def darken_color(self, hex_color, percent): """使颜色变暗指定百分比""" r = int(int(hex_color[1:3], 16) * (100 - percent) / 100) g = int(int(hex_color[3:5], 16) * (100 - percent) / 100) b = int(int(hex_color[5:7], 16) * (100 - percent) / 100) return f"#{max(0, min(255, r)):02x}{max(0, min(255, g)):02x}{max(0, min(255, b)):02x}" def select_workflow(self, item): """选择工作流程""" self.current_workflow = item.data(0, Qt.UserRole) self.current_stage_index = 0 self.update_workflow_display() self.update_artifacts_display() def select_stage(self, index): """选择阶段""" # 获取目标阶段 target_stage = self.current_workflow["stages"][index] # 如果目标阶段已完成,直接跳转 if target_stage.get("completed", False): self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() return # 如果跳转到已完成阶段之后的阶段,允许直接跳转 if index <= self.current_stage_index: self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() return # 只有跳转到未完成的未来阶段才需要警告 reply = QMessageBox.question( self, '确认跳转', '您正在跳转到未完成的阶段。确定要继续吗?', QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.current_stage_index = index self.update_workflow_display() self.update_artifacts_display() def prev_stage(self): """返回上一阶段""" if self.current_stage_index > 0: self.current_stage_index -= 1 self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已返回阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def next_stage(self): """进入下一阶段""" if self.current_stage_index < len(self.current_workflow["stages"]) - 1: # 检查当前阶段是否已完成 current_stage = self.current_workflow["stages"][self.current_stage_index] # 检查成果物是否全部完成 if not current_stage.get("completed", False): QMessageBox.warning(self, "警告", "当前阶段成果物未全部完成,无法进入下一阶段!") return self.current_stage_index += 1 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"已进入新阶段: {self.current_workflow['stages'][self.current_stage_index]['name']}", 2000) def update_buttons_state(self): """更新按钮状态""" self.prev_stage_btn.setEnabled(self.current_stage_index > 0) self.next_stage_btn.setEnabled( self.current_stage_index < len(self.current_workflow["stages"]) - 1 ) def new_workflow(self): """新建工作流程""" dialog = WorkflowDialog(self) if dialog.exec_() == QDialog.Accepted: new_workflow = dialog.get_workflow() self.workflows.append(new_workflow) self.current_workflow = new_workflow self.current_stage_index = 0 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() def edit_workflow(self): """编辑工作流程""" if not self.current_workflow: return dialog = WorkflowDialog(self, self.current_workflow) if dialog.exec_() == QDialog.Accepted: edited_workflow = dialog.get_workflow() index = self.workflows.index(self.current_workflow) self.workflows[index] = edited_workflow self.current_workflow = edited_workflow self.save_workflows() self.update_workflow_display() self.update_artifacts_display() def delete_workflow(self): """删除当前工作流程""" if not self.current_workflow: QMessageBox.warning(self, "警告", "请先选择一个工作流程!") return reply = QMessageBox.question( self, '确认删除', f"确定要删除工作流程【{self.current_workflow['name']}】吗?\n此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: # 从工作流程列表中移除当前工作流程 self.workflows.remove(self.current_workflow) # 更新当前选中的工作流程 if self.workflows: self.current_workflow = self.workflows[0] self.current_stage_index = 0 else: self.current_workflow = None self.current_stage_index = -1 # 保存更改并更新界面 self.save_workflows() self.update_workflow_display() self.update_artifacts_display() self.status_bar.showMessage(f"工作流程已删除", 2000) def update_artifacts_display(self): """更新成果物显示并恢复状态""" self.artifacts_list.clear() if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] self.artifacts_label.setText(f"成果物检查 - {current_stage['name']}") for artifact in current_stage.get("artifacts", []): item = QListWidgetItem() widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(10, 5, 10, 5) # 更紧凑的内边距 checkbox = QCheckBox(artifact["name"]) checkbox.setChecked(artifact["completed"]) # 恢复状态 checkbox.setStyleSheet("font-size: 19px;") checkbox.stateChanged.connect(lambda state, a=artifact: self.update_artifact_status(a, state)) layout.addWidget(checkbox) item.setSizeHint(widget.sizeHint()) self.artifacts_list.addItem(item) self.artifacts_list.setItemWidget(item, widget) # 更新状态信息 self.update_stage_status() def update_artifact_status(self, artifact, state): """更新成果物状态""" artifact["completed"] = (state == Qt.Checked) self.update_stage_status() def update_stage_status(self): """更新阶段状态""" if not self.current_workflow: return current_stage = self.current_workflow["stages"][self.current_stage_index] artifacts = current_stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) # 更新状态文本 if total_artifacts == 0: status_text = "当前阶段没有成果物" status_color = "#6c757d" elif completed_artifacts == 0: status_text = "状态: 待开始 (0% 完成)" status_color = "#e74a3b" current_stage["completed"] = False elif completed_artifacts < total_artifacts: percent = (completed_artifacts / total_artifacts) * 100 status_text = f"状态: 进行中 ({percent:.0f}% 完成)" status_color = "#f6c23e" current_stage["completed"] = False else: status_text = "状态: 已完成 (100% 完成)" status_color = "#1cc88a" current_stage["completed"] = True self.status_info.setText(status_text) self.status_info.setStyleSheet(f""" font-size: 12px; font-weight: 500; color: white; background-color: {status_color}; padding: 6px; border-radius: 4px; margin-top: 8px; """) self.update_buttons_state() def show_stats(self): """显示统计信息""" if not self.current_workflow: return stats_text = f"{self.current_workflow['name']} 统计" # 按阶段统计 stats_text += "按阶段统计" for i, stage in enumerate(self.current_workflow["stages"]): artifacts = stage.get("artifacts", []) total_artifacts = len(artifacts) completed_artifacts = sum(1 for a in artifacts if a["completed"]) status = "已完成" if stage.get("completed", False) else "进行中" if i == self.current_stage_index else "未开始" stats_text += f"" stats_text += f"" stats_text += f"{stage['name']}: {completed_artifacts}/{total_artifacts} 完成 ({status})" stats_text += "" stats_text += "" # 完成率统计 completed_stages = sum(1 for stage in self.current_workflow["stages"] if stage.get("completed", False)) total_stages = len(self.current_workflow["stages"]) completion_rate = (completed_stages / total_stages) * 100 if total_stages > 0 else 0 # 进度条 stats_text += f"流程完成率: {completion_rate:.0f}%" stats_text += f""" """ self.stats_label.setText(stats_text) self.artifacts_management.setCurrentWidget(self.stats_widget) class WorkflowDialog(QDialog): """工作流程编辑对话框""" def __init__(self, parent=None, workflow=None): super().__init__(parent) self.setWindowTitle("编辑工作流程" if workflow else "新建工作流程") self.resize(600, 450) # 更小的对话框 self.workflow = workflow if workflow else { "name": "", "description": "", "stages": [], "tasks": [] } self.setup_ui() def setup_ui(self): """设置对话框UI""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) # 标题 title = QLabel("编辑工作流程" if self.workflow.get("name") else "新建工作流程") title.setStyleSheet("font-size: 16px; font-weight: 600; color: #4e73df; margin-bottom: 15px;") layout.addWidget(title) # 基本信息 form_layout = QFormLayout() form_layout.setLabelAlignment(Qt.AlignRight) form_layout.setVerticalSpacing(10) self.name_edit = QLineEdit(self.workflow["name"]) self.name_edit.setPlaceholderText("输入工作流程名称") form_layout.addRow("流程名称:", self.name_edit) self.desc_edit = QTextEdit(self.workflow["description"]) self.desc_edit.setPlaceholderText("输入工作流程描述") self.desc_edit.setFixedHeight(60) form_layout.addRow("流程描述:", self.desc_edit) layout.addLayout(form_layout) # 阶段管理 stages_label = QLabel("阶段管理") stages_label.setStyleSheet("font-size: 14px; font-weight: 600; color: #4e73df; margin-top: 15px; margin-bottom: 8px;") layout.addWidget(stages_label) # 阶段列表和编辑区 stages_container = QHBoxLayout() stages_container.setSpacing(15) # 阶段列表 stages_list_container = QVBoxLayout() self.stages_list = QListWidget() self.stages_list.setFixedHeight(150) self.update_stages_list() self.stages_list.itemClicked.connect(self.select_stage) stages_list_container.addWidget(self.stages_list) stages_container.addLayout(stages_list_container, 1) # 阶段编辑区 stage_edit_container = QVBoxLayout() self.stage_edit = QWidget() self.stage_edit.setStyleSheet("background-color: #f8f9fa; border-radius: 6px; padding: 10px;") stage_edit_layout = QFormLayout(self.stage_edit) stage_edit_layout.setVerticalSpacing(8) self.stage_name_edit = QLineEdit() self.stage_name_edit.setPlaceholderText("输入阶段名称") stage_edit_layout.addRow("阶段名称:", self.stage_name_edit) self.stage_desc_edit = QTextEdit() self.stage_desc_edit.setPlaceholderText("输入阶段描述") self.stage_desc_edit.setFixedHeight(50) stage_edit_layout.addRow("阶段描述:", self.stage_desc_edit) self.stage_color_edit = QComboBox() self.stage_color_edit.addItems(["#4e73df", "#1cc88a", "#36b9cc", "#f6c23e", "#e74a3b", "#6f42c1", "#fd7e14", "#20c997"]) stage_edit_layout.addRow("阶段颜色:", self.stage_color_edit) # 成果物编辑 self.artifacts_edit = QTextEdit() self.artifacts_edit.setPlaceholderText("每行输入一个成果物名称") stage_edit_layout.addRow("成果物:", self.artifacts_edit) stage_edit_container.addWidget(self.stage_edit) # 阶段操作按钮 stage_btn_layout = QHBoxLayout() stage_btn_layout.setSpacing(8) self.add_stage_btn = QPushButton("添加") self.add_stage_btn.setStyleSheet("background-color: #1cc88a; color: white; padding: 5px;") self.add_stage_btn.clicked.connect(self.add_stage) stage_btn_layout.addWidget(self.add_stage_btn) self.update_stage_btn = QPushButton("更新") self.update_stage_btn.setStyleSheet("background-color: #4e73df; color: white; padding: 5px;") self.update_stage_btn.clicked.connect(self.update_stage) stage_btn_layout.addWidget(self.update_stage_btn) self.remove_stage_btn = QPushButton("删除") self.remove_stage_btn.setStyleSheet("background-color: #e74a3b; color: white; padding: 5px;") self.remove_stage_btn.clicked.connect(self.remove_stage) stage_btn_layout.addWidget(self.remove_stage_btn) stage_edit_container.addLayout(stage_btn_layout) stages_container.addLayout(stage_edit_container, 2) layout.addLayout(stages_container) # 对话框按钮 btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignRight) btn_layout.setSpacing(10) save_btn = QPushButton("保存") save_btn.setStyleSheet("background-color: #4e73df; color: white; min-width: 80px; padding: 6px;") save_btn.clicked.connect(self.accept) btn_layout.addWidget(save_btn) cancel_btn = QPushButton("取消") cancel_btn.setStyleSheet("background-color: #6c757d; color: white; min-width: 80px; padding: 6px;") cancel_btn.clicked.connect(self.reject) btn_layout.addWidget(cancel_btn) layout.addLayout(btn_layout) def update_stages_list(self): """更新阶段列表""" self.stages_list.clear() for stage in self.workflow["stages"]: item = QListWidgetItem(stage["name"]) item.setData(Qt.UserRole, stage) self.stages_list.addItem(item) def select_stage(self, item): """选择阶段进行编辑""" stage = item.data(Qt.UserRole) self.stage_name_edit.setText(stage["name"]) self.stage_desc_edit.setText(stage["description"]) self.stage_color_edit.setCurrentText(stage["color"]) # 将成果物列表转换为文本 artifact_names = [a["name"] for a in stage.get("artifacts", [])] self.artifacts_edit.setText("\n".join(artifact_names)) def add_stage(self): """添加新阶段""" name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return # 从文本创建成果物对象 artifacts = [ {"name": a.strip(), "completed": False} for a in self.artifacts_edit.toPlainText().split("\n") if a.strip() ] new_stage = { "name": name, "description": self.stage_desc_edit.toPlainText().strip(), "color": self.stage_color_edit.currentText(), "artifacts": artifacts, "completed": False } self.workflow["stages"].append(new_stage) self.update_stages_list() self.clear_stage_edit() def update_stage(self): """更新当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return name = self.stage_name_edit.text().strip() if not name: QMessageBox.warning(self, "警告", "请输入阶段名称!") return stage = current_item.data(Qt.UserRole) stage["name"] = name stage["description"] = self.stage_desc_edit.toPlainText().strip() stage["color"] = self.stage_color_edit.currentText() # 更新成果物列表 artifact_names = [a.strip() for a in self.artifacts_edit.toPlainText().split("\n") if a.strip()] # 保留现有成果物的状态 existing_artifacts = {a["name"]: a["completed"] for a in stage.get("artifacts", [])} stage["artifacts"] = [ {"name": name, "completed": existing_artifacts.get(name, False)} for name in artifact_names ] self.update_stages_list() def remove_stage(self): """删除当前阶段""" current_item = self.stages_list.currentItem() if not current_item: QMessageBox.warning(self, "警告", "请先选择一个阶段!") return reply = QMessageBox.question(self, '确认删除', f"确定要删除阶段【{current_item.text()}】吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: stage = current_item.data(Qt.UserRole) self.workflow["stages"].remove(stage) self.update_stages_list() self.clear_stage_edit() def clear_stage_edit(self): """清空阶段编辑区""" self.stage_name_edit.clear() self.stage_desc_edit.clear() self.stage_color_edit.setCurrentIndex(0) self.artifacts_edit.clear() def get_workflow(self): """获取编辑后的工作流程""" return { "name": self.name_edit.text().strip(), "description": self.desc_edit.toPlainText().strip(), "stages": self.workflow["stages"], "tasks": self.workflow["tasks"] } if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") palette = QPalette() palette.setColor(QPalette.Window, QColor(248, 249, 250)) palette.setColor(QPalette.WindowText, QColor(73, 80, 87)) palette.setColor(QPalette.Base, QColor(255, 255, 255)) palette.setColor(QPalette.AlternateBase, QColor(248, 249, 250)) palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) palette.setColor(QPalette.ToolTipText, QColor(73, 80, 87)) palette.setColor(QPalette.Text, QColor(73, 80, 87)) palette.setColor(QPalette.Button, QColor(248, 249, 250)) palette.setColor(QPalette.ButtonText, QColor(73, 80, 87)) palette.setColor(QPalette.BrightText, QColor(255, 255, 255)) palette.setColor(QPalette.Highlight, QColor(78, 115, 223)) palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) app.setPalette(palette) # 设置应用字体 app_font = QFont("Segoe UI", 9) app.setFont(app_font) manager = WorkflowManager() manager.show() sys.exit(app.exec_()) 1、点击进行编辑时,想要新增成果物,点击保存,新增的成果物并没有保存

import sys import os import fitz # PyMuPDF import pandas as pd import numpy as np import tempfile import shutil import re import time from datetime import datetime from PIL import Image, ImageDraw import cv2 import csv import json from collections import defaultdict from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QStackedWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFileDialog, QListWidget, QTableWidget, QTableWidgetItem, QAbstractItemView, QHeaderView, QLineEdit, QFrame, QSizePolicy, QProgressBar, QMessageBox, QComboBox, QGridLayout, QTextEdit, QDialog, QDialogButtonBox ) from PyQt5.QtCore import Qt, QSize, QTimer, QThread, pyqtSignal from PyQt5.QtGui import QIcon, QFont, QColor, QPixmap, QBrush, QPainter # 集成PaddleOCR try: from paddleocr import PaddleOCR except ImportError: print("PaddleOCR not installed. Please install with: pip install paddlepaddle paddleocr") sys.exit(1) class OCRWorker(QThread): progress_updated = pyqtSignal(int, str) extraction_complete = pyqtSignal(list, str) watermark_removed = pyqtSignal(str, str) def __init__(self, pdf_path, output_dir, watermark_text=None, parent=None): super().__init__(parent) self.pdf_path = pdf_path self.output_dir = output_dir self.watermark_text = watermark_text self.file_name = os.path.basename(pdf_path) self.canceled = False self.ocr = PaddleOCR(use_angle_cls=True, lang='en', use_gpu=True) def run(self): try: # 第一步:去除水印(如果需要) processed_path = self.pdf_path if self.watermark_text: processed_path = self.remove_watermark() if processed_path: self.watermark_removed.emit(processed_path, self.file_name) else: self.progress_updated.emit(100, "水印去除失败") return # 第二步:提取内容 self.extract_content(processed_path) except Exception as e: self.progress_updated.emit(100, f"处理失败: {str(e)}") def remove_watermark(self): """使用OCR检测并去除水印""" try: doc = fitz.open(self.pdf_path) new_doc = fitz.open() output_path = os.path.join(self.output_dir, f"processed_{self.file_name}") total_pages = len(doc) for page_num in range(total_pages): if self.canceled: return None self.progress_updated.emit(int(30 * page_num / total_pages), f"正在处理第 {page_num+1} 页水印") page = doc.load_page(page_num) pix = page.get_pixmap(matrix=fitz.Matrix(2, 2)) img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) img_np = np.array(img) # 使用PaddleOCR检测文本 result = self.ocr.ocr(img_np, cls=True) # 检测水印文本 watermark_boxes = [] for line in result: for word_info in line: text = word_info[1][0] if self.watermark_text.lower() in text.lower(): box = word_info[0] # 将浮点坐标转换为整数 int_box = [(int(x), int(y)) for x, y in box] watermark_boxes.append(int_box) # 去除水印(用白色覆盖) if watermark_boxes: img_pil = Image.fromarray(img_np) draw = ImageDraw.Draw(img_pil) for box in watermark_boxes: # 创建覆盖矩形 min_x = min(point[0] for point in box) max_x = max(point[0] for point in box) min_y = min(point[1] for point in box) max_y = max(point[1] for point in box) # 扩展矩形范围确保完全覆盖 expand = 5 draw.rectangle( [min_x - expand, min_y - expand, max_x + expand, max_y + expand], fill=(255, 255, 255) img_np = np.array(img_pil) # 保存处理后的页面 img_bytes = Image.fromarray(img_np).tobytes() new_page = new_doc.new_page(width=img_np.shape[1], height=img_np.shape[0]) new_page.insert_image(fitz.Rect(0, 0, img_np.shape[1], img_np.shape[0]), stream=img_bytes) new_doc.save(output_path) new_doc.close() doc.close() return output_path except Exception as e: print(f"Error removing watermark: {e}") return None def extract_content(self, pdf_path): """使用PaddleOCR提取内容""" try: doc = fitz.open(pdf_path) extracted_data = [] total_pages = len(doc) for page_num in range(total_pages): if self.canceled: return self.progress_updated.emit(30 + int(70 * page_num / total_pages), f"正在提取第 {page_num+1} 页内容") page = doc.load_page(page_num) pix = page.get_pixmap(matrix=fitz.Matrix(2, 2)) img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) img_np = np.array(img) # 使用PaddleOCR提取文本 result = self.ocr.ocr(img_np, cls=True) # 处理OCR结果 page_text = "" for line in result: line_text = " ".join([word_info[1][0] for word_info in line]) page_text += line_text + "\n" # 提取结构化数据(示例逻辑) extracted = self.extract_structured_data(page_text, page_num + 1) extracted_data.extend(extracted) doc.close() self.extraction_complete.emit(extracted_data, self.file_name) except Exception as e: self.progress_updated.emit(100, f"内容提取失败: {str(e)}") def extact_structured_data(self, text, page_num): """从文本中提取结构化数据(示例实现)""" extracted = [] # 提取发票信息 invoice_match = re.search(r'Invoice\s+Number\s*:\s*(\w+)', text, re.IGNORECASE) date_match = re.search(r'Date\s*:\s*(\d{2}/\d{2}/\d{4})', text, re.IGNORECASE) total_match = re.search(r'Total\s+Amount\s*:\s*([\d,]+\.\d{2})', text, re.IGNORECASE) if invoice_match or date_match or total_match: extracted.append({ "Document": self.file_name, "Page": page_num, "Type": "Invoice", "Invoice Number": invoice_match.group(1) if invoice_match else "N/A", "Date": date_match.group(1) if date_match else "N/A", "Amount": f"${total_match.group(1)}" if total_match else "N/A" }) # 提取报告信息 report_match = re.search(r'Report\s+Title\s*:\s*(.+)', text, re.IGNORECASE) author_match = re.search(r'Author\s*:\s*(.+)', text, re.IGNORECASE) if report_match or author_match: extracted.append({ "Document": self.file_name, "Page": page_num, "Type": "Report", "Report Title": report_match.group(1) if report_match else "N/A", "Author": author_match.group(1) if author_match else "N/A", "Summary": text[:200] + "..." if len(text) > 200 else text }) # 如果没有匹配到特定结构,返回整个页面文本 if not extracted: extracted.append({ "Document": self.file_name, "Page": page_num, "Type": "General", "Content": text }) return extracted def cancel(self): self.canceled = True class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("PDF智能处理工具") self.setGeometry(100, 100, 1200, 800) self.setMinimumSize(1000, 700) # 应用主色调 self.primary_color = "#2c3e50" self.secondary_color = "#3498db" self.accent_color = "#e67e22" self.light_color = "#ecf0f1" self.dark_color = "#34495e" # 初始化状态 self.current_files = [] self.extracted_data = [] self.history_data = [] self.ocr_worker = None self.temp_dir = tempfile.mkdtemp() # 创建中央部件和主布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # 创建顶部栏 self.create_header(main_layout) # 创建主内容区域 self.content_stack = QStackedWidget() main_layout.addWidget(self.content_stack, 1) # 创建各个页面 self.home_page = self.create_home_page() self.upload_page = self.create_upload_page() self.history_page = self.create_history_page() self.analysis_page = self.create_analysis_page() self.content_stack.addWidget(self.home_page) self.content_stack.addWidget(self.upload_page) self.content_stack.addWidget(self.history_page) self.content_stack.addWidget(self.analysis_page) # 创建底部导航栏 self.create_footer(main_layout) # 模拟一些历史数据 self.simulate_history_data() # 设置首页为默认页面 self.content_stack.setCurrentIndex(0) self.update_home_page() def closeEvent(self, event): """清理临时文件""" try: shutil.rmtree(self.temp_dir, ignore_errors=True) except: pass event.accept() def simulate_history_data(self): """模拟一些历史数据用于展示""" for i in range(5): self.history_data.append({ "id": i, "file_name": f"document_{i+1}.pdf", "date": f"2023-0{i+1}-15", "status": "Completed", "pages": i+3, "type": "Invoice" if i % 2 == 0 else "Report", "extracted_data": [ { "Document": f"document_{i+1}.pdf", "Page": 1, "Type": "Invoice", "Invoice Number": f"INV-2023-{i+1:04d}", "Date": f"2023-0{i+1}-15", "Amount": f"${(i+1)*250:.2f}" } ] }) def create_header(self, main_layout): """创建应用头部""" header = QWidget() header.setStyleSheet(f"background-color: {self.primary_color}; padding: 15px;") header_layout = QHBoxLayout(header) header_layout.setContentsMargins(20, 10, 20, 10) # 应用标题 title_label = QLabel("PDF智能处理工具") title_label.setStyleSheet(f"color: white; font-size: 24px; font-weight: bold;") header_layout.addWidget(title_label) # 右侧用户区域 user_widget = QWidget() user_layout = QHBoxLayout(user_widget) user_layout.setSpacing(15) user_icon = QLabel() user_icon.setPixmap(self.create_icon("👤", 40)) user_layout.addWidget(user_icon) user_name = QLabel("管理员") user_name.setStyleSheet("color: white; font-size: 16px;") user_layout.addWidget(user_name) header_layout.addWidget(user_widget) main_layout.addWidget(header) def create_footer(self, main_layout): """创建底部导航栏""" footer = QWidget() footer.setStyleSheet(f"background-color: {self.dark_color};") footer.setFixedHeight(60) footer_layout = QHBoxLayout(footer) footer_layout.setContentsMargins(0, 0, 0, 0) footer_layout.setSpacing(0) # 导航按钮 nav_items = [ ("首页", "home", 0), ("上传与提取", "upload", 1), ("历史记录", "history", 2), ("数据分析", "analysis", 3) ] for text, icon_name, index in nav_items: btn = QPushButton(text) btn.setIcon(QIcon(self.create_icon("🏠" if icon_name=="home" else "📤" if icon_name=="upload" else "📋" if icon_name=="history" else "📊", 24))) btn.setIconSize(QSize(24, 24)) btn.setFixedHeight(60) btn.setStyleSheet(f""" QPushButton {{ color: {self.light_color}; font-size: 14px; font-weight: bold; border: none; background-color: {self.dark_color}; }} QPushButton:hover {{ background-color: {self.primary_color}; }} """) btn.clicked.connect(lambda _, idx=index: self.navigate_to(idx)) footer_layout.addWidget(btn) main_layout.addWidget(footer) def create_icon(self, emoji, size=24): """创建表情符号图标""" pixmap = QPixmap(size, size) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) painter.setFont(QFont("Arial", size - 4)) painter.drawText(pixmap.rect(), Qt.AlignCenter, emoji) painter.end() return pixmap def navigate_to(self, index): """导航到指定页面""" self.content_stack.setCurrentIndex(index) # 更新页面内容 if index == 0: # 首页 self.update_home_page() elif index == 1: # 上传与提取 pass elif index == 2: # 历史记录 self.update_history_page() elif index == 3: # 数据分析 self.update_analysis_page() def create_home_page(self): """创建首页""" page = QWidget() layout = QVBoxLayout(page) layout.setContentsMargins(30, 20, 30, 20) layout.setSpacing(20) # 欢迎卡片 welcome_card = QFrame() welcome_card.setStyleSheet(f""" QFrame {{ background-color: white; border-radius: 10px; padding: 20px; }} """) welcome_layout = QVBoxLayout(welcome_card) welcome_title = QLabel("欢迎使用PDF智能处理工具") welcome_title.setStyleSheet("font-size: 24px; font-weight: bold; color: #2c3e50;") welcome_layout.addWidget(welcome_title) welcome_text = QLabel("本工具提供PDF水印去除、内容提取和数据分析功能,支持批量处理PDF文件,快速提取结构化数据并导出为Excel或CSV格式。") welcome_text.setStyleSheet("font-size: 16px; color: #7f8c8d;") welcome_text.setWordWrap(True) welcome_layout.addWidget(welcome_text) # OCR信息 ocr_info = QLabel("当前使用PaddleOCR引擎,支持中英文识别") ocr_info.setStyleSheet("font-size: 14px; color: #3498db; font-weight: bold;") welcome_layout.addWidget(ocr_info) layout.addWidget(welcome_card) # 最近上传区域 recent_label = QLabel("最近上传") recent_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #2c3e50;") layout.addWidget(recent_label) # 最近上传列表 self.recent_list = QListWidget() self.recent_list.setStyleSheet(""" QListWidget { background-color: white; border-radius: 10px; border: 1px solid #ddd; } QListWidget::item { padding: 15px; border-bottom: 1px solid #eee; } QListWidget::item:selected { background-color: #e6f7ff; } """) self.recent_list.setAlternatingRowColors(True) layout.addWidget(self.recent_list, 1) # 快捷操作按钮 quick_actions = QWidget() quick_layout = QHBoxLayout(quick_actions) quick_layout.setSpacing(15) actions = [ ("上传新文件", "upload", self.navigate_to_upload), ("查看历史记录", "history", lambda: self.navigate_to(2)), ("数据分析", "analysis", lambda: self.navigate_to(3)) ] for text, icon_name, action in actions: btn = QPushButton(text) btn.setIcon(QIcon(self.create_icon("📤" if icon_name=="upload" else "📋" if icon_name=="history" else "📊", 24))) btn.setIconSize(QSize(24, 24)) btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; font-size: 16px; font-weight: bold; padding: 12px 20px; border-radius: 8px; }} QPushButton:hover {{ background-color: #2980b9; }} """) btn.clicked.connect(action) quick_layout.addWidget(btn) layout.addWidget(quick_actions) return page def update_home_page(self): """更新首页内容""" self.recent_list.clear() # 显示最近的5条记录 for item in self.history_data[:5]: list_item = QLabel(f""" {item['file_name']} 上传时间: {item['date']} | 状态: {item['status']} | 类型: {item['type']} """) list_widget = QListWidgetItem(self.recent_list) list_widget.setSizeHint(list_item.sizeHint()) self.recent_list.addItem(list_widget) self.recent_list.setItemWidget(list_widget, list_item) def navigate_to_upload(self): """导航到上传页面""" self.content_stack.setCurrentIndex(1) def create_upload_page(self): """创建上传与提取页面""" page = QWidget() layout = QVBoxLayout(page) layout.setContentsMargins(30, 20, 30, 20) layout.setSpacing(20) # 标题 title = QLabel("上传与提取") title.setStyleSheet("font-size: 24px; font-weight: bold; color: #2c3e50;") layout.addWidget(title) # 上传区域 upload_card = QFrame() upload_card.setStyleSheet(f""" QFrame {{ background-color: white; border-radius: 10px; border: 2px dashed {self.secondary_color}; padding: 40px; }} """) upload_layout = QVBoxLayout(upload_card) upload_layout.setAlignment(Qt.AlignCenter) upload_icon = QLabel() upload_icon.setPixmap(self.create_icon("📤", 80)) upload_icon.setAlignment(Qt.AlignCenter) upload_layout.addWidget(upload_icon) upload_text = QLabel("拖放PDF文件到此处,或点击选择文件") upload_text.setStyleSheet("font-size: 18px; color: #7f8c8d; margin-top: 20px;") upload_text.setAlignment(Qt.AlignCenter) upload_layout.addWidget(upload_text) upload_btn = QPushButton("选择PDF文件") upload_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; font-size: 16px; padding: 10px 20px; border-radius: 5px; margin-top: 20px; }} QPushButton:hover {{ background-color: #2980b9; }} """) upload_btn.clicked.connect(self.select_pdf_files) upload_layout.addWidget(upload_btn, alignment=Qt.AlignCenter) # 水印选项 watermark_layout = QHBoxLayout() watermark_layout.setSpacing(10) watermark_label = QLabel("水印文本(可选):") watermark_label.setStyleSheet("font-size: 14px;") watermark_layout.addWidget(watermark_label) self.watermark_input = QLineEdit() self.watermark_input.setPlaceholderText("输入要删除的水印文本") self.watermark_input.setStyleSheet("padding: 5px; border: 1px solid #ddd; border-radius: 3px;") watermark_layout.addWidget(self.watermark_input, 1) upload_layout.addLayout(watermark_layout) layout.addWidget(upload_card, 1) # 进度区域 progress_layout = QVBoxLayout() progress_layout.setSpacing(10) progress_label = QLabel("处理进度") progress_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2c3e50;") progress_layout.addWidget(progress_label) self.progress_bar = QProgressBar() self.progress_bar.setStyleSheet(""" QProgressBar { border: 1px solid #ddd; border-radius: 5px; text-align: center; height: 25px; } QProgressBar::chunk { background-color: #3498db; width: 10px; } """) progress_layout.addWidget(self.progress_bar) self.progress_text = QLabel("等待处理文件...") self.progress_text.setStyleSheet("font-size: 14px; color: #7f8c8d;") progress_layout.addWidget(self.progress_text) # 取消按钮 self.cancel_btn = QPushButton("取消处理") self.cancel_btn.setStyleSheet(""" QPushButton { background-color: #e74c3c; color: white; padding: 8px 20px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #c0392b; } """) self.cancel_btn.clicked.connect(self.cancel_processing) self.cancel_btn.setVisible(False) progress_layout.addWidget(self.cancel_btn, alignment=Qt.AlignRight) layout.addLayout(progress_layout) return page def select_pdf_files(self): """选择PDF文件""" files, _ = QFileDialog.getOpenFileNames( self, "选择PDF文件", "", "PDF文件 (*.pdf)" ) if files: self.current_files = files self.process_files() def process_files(self): """处理选中的文件""" if not self.current_files: return # 重置状态 self.extracted_data = [] # 显示取消按钮 self.cancel_btn.setVisible(True) # 处理第一个文件 file_path = self.current_files[0] file_name = os.path.basename(file_path) # 获取水印文本 watermark_text = self.watermark_input.text().strip() or None # 创建工作线程 self.ocr_worker = OCRWorker( file_path, self.temp_dir, watermark_text ) # 连接信号 self.ocr_worker.progress_updated.connect(self.update_progress) self.ocr_worker.watermark_removed.connect(self.on_watermark_removed) self.ocr_worker.extraction_complete.connect(self.on_extraction_complete) # 开始处理 self.ocr_worker.start() def update_progress(self, progress, message): """更新处理进度""" self.progress_bar.setValue(progress) self.progress_text.setText(message) def on_watermark_removed(self, output_path, file_name): """水印去除完成""" self.progress_text.setText(f"水印已移除: {file_name}") def on_extraction_complete(self, extracted_data, file_name): """内容提取完成""" self.extracted_data.extend(extracted_data) # 保存到历史记录 self.history_data.insert(0, { "id": len(self.history_data), "file_name": file_name, "date": datetime.now().strftime("%Y-%m-%d %H:%M"), "status": "Completed", "pages": len(extracted_data), "type": "Invoice" if any(d['Type'] == 'Invoice' for d in extracted_data) else "Report", "extracted_data": extracted_data }) # 显示预览 self.show_preview(extracted_data, file_name) # 处理下一个文件(如果有) if len(self.current_files) > 1: self.current_files.pop(0) self.process_files() else: self.current_files = [] self.cancel_btn.setVisible(False) def cancel_processing(self): """取消处理""" if self.ocr_worker and self.ocr_worker.isRunning(): self.ocr_worker.cancel() self.ocr_worker.wait() self.progress_text.setText("处理已取消") self.cancel_btn.setVisible(False) def show_preview(self, data, file_name): """显示预览窗口""" # 创建预览对话框 preview_dialog = QDialog(self) preview_dialog.setWindowTitle(f"预览 - {file_name}") preview_dialog.resize(1000, 700) layout = QVBoxLayout(preview_dialog) layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(15) # 标题 title = QLabel(f"文件内容提取结果: {file_name}") title.setStyleSheet("font-size: 20px; font-weight: bold; color: #2c3e50;") layout.addWidget(title) # OCR引擎信息 ocr_info = QLabel("使用PaddleOCR引擎提取内容") ocr_info.setStyleSheet("font-size: 14px; color: #3498db; font-weight: bold;") layout.addWidget(ocr_info) # 数据表格 if data: table = QTableWidget() table.setRowCount(len(data)) # 获取所有可能的列 all_columns = set() for item in data: all_columns.update(item.keys()) columns = sorted(all_columns) table.setColumnCount(len(columns)) table.setHorizontalHeaderLabels(columns) # 填充数据 for row_idx, row_data in enumerate(data): for col_idx, col_name in enumerate(columns): value = str(row_data.get(col_name, "")) item = QTableWidgetItem(value) table.setItem(row_idx, col_idx, item) # 表格样式 table.setStyleSheet(""" QTableWidget { background-color: white; border: 1px solid #ddd; gridline-color: #eee; } QHeaderView::section { background-color: #f8f9fa; padding: 8px; border: none; font-weight: bold; } """) table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked) table.setSelectionMode(QAbstractItemView.SingleSelection) table.setSelectionBehavior(QAbstractItemView.SelectRows) layout.addWidget(table, 1) else: no_data_label = QLabel("未提取到有效数据") no_data_label.setStyleSheet("font-size: 16px; color: #7f8c8d;") no_data_label.setAlignment(Qt.AlignCenter) layout.addWidget(no_data_label, 1) # 操作按钮 btn_layout = QHBoxLayout() btn_layout.setSpacing(15) export_excel_btn = QPushButton("导出为Excel") export_excel_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} QPushButton:hover {{ background-color: #2980b9; }} """) export_excel_btn.clicked.connect(self.export_to_excel) btn_layout.addWidget(export_excel_btn) export_csv_btn = QPushButton("导出为CSV") export_csv_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.accent_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} QPushButton:hover {{ background-color: #d35400; }} """) export_csv_btn.clicked.connect(self.export_to_csv) btn_layout.addWidget(export_csv_btn) close_btn = QPushButton("关闭") close_btn.setStyleSheet(""" QPushButton { background-color: #95a5a6; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #7f8c8d; } """) close_btn.clicked.connect(preview_dialog.accept) btn_layout.addWidget(close_btn) layout.addLayout(btn_layout) preview_dialog.exec_() def export_to_excel(self): """导出为Excel""" if not self.extracted_data: QMessageBox.warning(self, "导出失败", "没有可导出的数据") return file_path, _ = QFileDialog.getSaveFileName( self, "保存Excel文件", "", "Excel文件 (*.xlsx)" ) if file_path: if not file_path.endswith('.xlsx'): file_path += '.xlsx' try: # 将数据转换为DataFrame df = pd.DataFrame(self.extracted_data) # 导出到Excel df.to_excel(file_path, index=False) QMessageBox.information(self, "导出成功", f"数据已成功导出到: {file_path}") except Exception as e: QMessageBox.critical(self, "导出失败", f"导出过程中发生错误: {str(e)}") def export_to_csv(self): """导出为CSV""" if not self.extracted_data: QMessageBox.warning(self, "导出失败", "没有可导出的数据") return file_path, _ = QFileDialog.getSaveFileName( self, "保存CSV文件", "", "CSV文件 (*.csv)" ) if file_path: if not file_path.endswith('.csv'): file_path += '.csv' try: # 获取所有可能的字段 all_fields = set() for item in self.extracted_data: all_fields.update(item.keys()) # 写入CSV with open(file_path, 'w', newline='', encoding='utf-8') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=sorted(all_fields)) writer.writeheader() writer.writerows(self.extracted_data) QMessageBox.information(self, "导出成功", f"数据已成功导出到: {file_path}") except Exception as e: QMessageBox.critical(self, "导出失败", f"导出过程中发生错误: {str(e)}") def create_history_page(self): """创建历史记录页面""" page = QWidget() layout = QVBoxLayout(page) layout.setContentsMargins(30, 20, 30, 20) layout.setSpacing(20) # 标题和搜索 header_layout = QHBoxLayout() title = QLabel("历史记录") title.setStyleSheet("font-size: 24px; font-weight: bold; color: #2c3e50;") header_layout.addWidget(title) search_layout = QHBoxLayout() search_layout.setSpacing(10) self.search_input = QLineEdit() self.search_input.setPlaceholderText("搜索文件名...") self.search_input.setStyleSheet(""" QLineEdit { padding: 8px 15px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; } """) self.search_input.setFixedWidth(300) self.search_input.returnPressed.connect(self.search_history) search_layout.addWidget(self.search_input) search_btn = QPushButton("搜索") search_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; padding: 8px 20px; border-radius: 5px; }} """) search_btn.clicked.connect(self.search_history) search_layout.addWidget(search_btn) header_layout.addLayout(search_layout) layout.addLayout(header_layout) # 历史记录表格 self.history_table = QTableWidget() self.history_table.setColumnCount(5) self.history_table.setHorizontalHeaderLabels(["文件名", "上传时间", "状态", "页数", "类型"]) self.history_table.setStyleSheet(""" QTableWidget { background-color: white; border: 1px solid #ddd; gridline-color: #eee; } QHeaderView::section { background-color: #f8f9fa; padding: 12px; border: none; font-weight: bold; } """) self.history_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.history_table.verticalHeader().setVisible(False) self.history_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.history_table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.history_table.setSortingEnabled(True) layout.addWidget(self.history_table, 1) # 操作按钮 btn_layout = QHBoxLayout() btn_layout.setSpacing(15) view_btn = QPushButton("查看详情") view_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} """) view_btn.clicked.connect(self.view_history_detail) btn_layout.addWidget(view_btn) export_btn = QPushButton("导出记录") export_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.accent_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} """) export_btn.clicked.connect(self.export_history) btn_layout.addWidget(export_btn) delete_btn = QPushButton("删除记录") delete_btn.setStyleSheet(""" QPushButton { background-color: #e74c3c; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; } """) delete_btn.clicked.connect(self.delete_history) btn_layout.addWidget(delete_btn) layout.addLayout(btn_layout) return page def update_history_page(self): """更新历史记录页面""" self.history_table.setRowCount(len(self.history_data)) for row_idx, item in enumerate(self.history_data): self.history_table.setItem(row_idx, 0, QTableWidgetItem(item["file_name"])) self.history_table.setItem(row_idx, 1, QTableWidgetItem(item["date"])) status_item = QTableWidgetItem(item["status"]) if item["status"] == "Completed": status_item.setForeground(QBrush(QColor("#27ae60"))) else: status_item.setForeground(QBrush(QColor("#e74c3c"))) self.history_table.setItem(row_idx, 2, status_item) self.history_table.setItem(row_idx, 3, QTableWidgetItem(str(item["pages"]))) self.history_table.setItem(row_idx, 4, QTableWidgetItem(item["type"])) def search_history(self): """搜索历史记录""" search_text = self.search_input.text().lower() if not search_text: self.update_history_page() return filtered_data = [item for item in self.history_data if search_text in item["file_name"].lower()] self.history_table.setRowCount(len(filtered_data)) for row_idx, item in enumerate(filtered_data): self.history_table.setItem(row_idx, 0, QTableWidgetItem(item["file_name"])) self.history_table.setItem(row_idx, 1, QTableWidgetItem(item["date"])) status_item = QTableWidgetItem(item["status"]) if item["status"] == "Completed": status_item.setForeground(QBrush(QColor("#27ae60"))) else: status_item.setForeground(QBrush(QColor("#e74c3c"))) self.history_table.setItem(row_idx, 2, status_item) self.history_table.setItem(row_idx, 3, QTableWidgetItem(str(item["pages"]))) self.history_table.setItem(row_idx, 4, QTableWidgetItem(item["type"])) def view_history_detail(self): """查看历史记录详情""" selected_row = self.history_table.currentRow() if selected_row >= 0: file_name = self.history_table.item(selected_row, 0).text() history_item = next((item for item in self.history_data if item["file_name"] == file_name), None) if history_item: self.show_preview(history_item["extracted_data"], file_name) else: QMessageBox.warning(self, "选择记录", "请先选择一条历史记录") def export_history(self): """导出历史记录""" if not self.history_data: QMessageBox.warning(self, "导出失败", "没有可导出的历史记录") return file_path, _ = QFileDialog.getSaveFileName( self, "保存历史记录", "", "CSV文件 (*.csv)" ) if file_path: if not file_path.endswith('.csv'): file_path += '.csv' try: with open(file_path, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = ['id', 'file_name', 'date', 'status', 'pages', 'type'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for item in self.history_data: writer.writerow({ 'id': item['id'], 'file_name': item['file_name'], 'date': item['date'], 'status': item['status'], 'pages': item['pages'], 'type': item['type'] }) QMessageBox.information(self, "导出成功", f"历史记录已成功导出到: {file_path}") except Exception as e: QMessageBox.critical(self, "导出失败", f"导出过程中发生错误: {str(e)}") def delete_history(self): """删除历史记录""" selected_row = self.history_table.currentRow() if selected_row >= 0: file_name = self.history_table.item(selected_row, 0).text() reply = QMessageBox.question( self, '确认删除', f"确定要删除 '{file_name}' 的记录吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: self.history_data = [item for item in self.history_data if item['file_name'] != file_name] self.update_history_page() else: QMessageBox.warning(self, "选择记录", "请先选择一条历史记录") def create_analysis_page(self): """创建数据分析页面""" page = QWidget() layout = QVBoxLayout(page) layout.setContentsMargins(30, 20, 30, 20) layout.setSpacing(20) # 标题 title = QLabel("数据分析") title.setStyleSheet("font-size: 24px; font-weight: bold; color: #2c3e50;") layout.addWidget(title) # 选择历史记录 select_layout = QHBoxLayout() select_label = QLabel("选择历史记录:") select_label.setStyleSheet("font-size: 16px;") select_layout.addWidget(select_label) self.history_combo = QComboBox() self.history_combo.setFixedWidth(300) self.history_combo.setStyleSheet("padding: 5px; border: 1px solid #ddd; border-radius: 3px;") select_layout.addWidget(self.history_combo) analyze_btn = QPushButton("分析数据") analyze_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; padding: 8px 20px; border-radius: 5px; }} """) analyze_btn.clicked.connect(self.analyze_data) select_layout.addWidget(analyze_btn) layout.addLayout(select_layout) # 图表区域 chart_container = QWidget() chart_layout = QGridLayout(chart_container) # 图表1 self.chart1_label = QLabel() self.chart1_label.setStyleSheet("background-color: white; border-radius: 10px; padding: 10px;") self.chart1_label.setAlignment(Qt.AlignCenter) chart_layout.addWidget(self.chart1_label, 0, 0) # 图表2 self.chart2_label = QLabel() self.chart2_label.setStyleSheet("background-color: white; border-radius: 10px; padding: 10px;") self.chart2_label.setAlignment(Qt.AlignCenter) chart_layout.addWidget(self.chart2_label, 0, 1) # 图表3 self.chart3_label = QLabel() self.chart3_label.setStyleSheet("background-color: white; border-radius: 10px; padding: 10px;") self.chart3_label.setAlignment(Qt.AlignCenter) chart_layout.addWidget(self.chart3_label, 1, 0) # 图表4 self.chart4_label = QLabel() self.chart4_label.setStyleSheet("background-color: white; border-radius: 10px; padding: 10px;") self.chart4_label.setAlignment(Qt.AlignCenter) chart_layout.addWidget(self.chart4_label, 1, 1) layout.addWidget(chart_container, 1) # 操作按钮 btn_layout = QHBoxLayout() btn_layout.setSpacing(15) refresh_btn = QPushButton("刷新数据") refresh_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.secondary_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} """) refresh_btn.clicked.connect(self.update_analysis_page) btn_layout.addWidget(refresh_btn) export_btn = QPushButton("导出报告") export_btn.setStyleSheet(f""" QPushButton {{ background-color: {self.accent_color}; color: white; padding: 10px 20px; border-radius: 5px; font-weight: bold; }} """) export_btn.clicked.connect(self.export_report) btn_layout.addWidget(export_btn) layout.addLayout(btn_layout) return page def update_analysis_page(self): """更新数据分析页面""" # 更新历史记录选择框 self.history_combo.clear() for item in self.history_data: self.history_combo.addItem(f"{item['file_name']} - {item['date']}", item) # 显示初始图表 self.show_chart(self.chart1_label, "📊", "数据统计") self.show_chart(self.chart2_label, "📈", "趋势分析") self.show_chart(self.chart3_label, "📉", "比较分析") self.show_chart(self.chart4_label, "🧮", "类型分布") def show_chart(self, label, emoji, title): """显示模拟图表""" pixmap = QPixmap(400, 250) pixmap.fill(Qt.white) painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing) # 绘制标题 painter.setFont(QFont("Arial", 14, QFont.Bold)) painter.drawText(pixmap.rect().adjusted(0, 10, 0, 0), Qt.AlignTop | Qt.AlignHCenter, title) # 绘制图表图标 painter.setFont(QFont("Arial", 80)) painter.drawText(pixmap.rect(), Qt.AlignCenter, emoji) # 绘制边框 painter.setPen(QColor("#ddd")) painter.drawRect(pixmap.rect().adjusted(0, 0, -1, -1)) painter.end() label.setPixmap(pixmap) def analyze_data(self): """分析数据""" if self.history_combo.currentIndex() < 0: QMessageBox.warning(self, "选择记录", "请先选择一条历史记录") return selected_item = self.history_combo.currentData() extracted_data = selected_item.get('extracted_data', []) if not extracted_data: QMessageBox.warning(self, "分析失败", "所选记录没有可分析的数据") return # 在实际应用中,这里会使用真实的数据分析逻辑 # 这里仅显示消息 QMessageBox.information( self, "数据分析", f"已对 '{selected_item['file_name']}' 进行数据分析\n" f"包含 {len(extracted_data)} 条记录" ) def export_report(self): """导出分析报告""" if self.history_combo.currentIndex() < 0: QMessageBox.warning(self, "选择记录", "请先选择一条历史记录") return selected_item = self.history_combo.currentData() file_path, _ = QFileDialog.getSaveFileName( self, "保存分析报告", "", "PDF文件 (*.pdf)" ) if file_path: if not file_path.endswith('.pdf'): file_path += '.pdf' try: # 在实际应用中,这里会生成真实的PDF报告 # 这里仅模拟导出 time.sleep(1) # 模拟生成报告的时间 QMessageBox.information( self, "导出成功", f"分析报告已成功导出到: {file_path}\n" f"包含对 '{selected_item['file_name']}' 的分析结果" ) except Exception as e: QMessageBox.critical(self, "导出失败", f"导出过程中发生错误: {str(e)}") if __name__ == "__main__": # 设置应用程序 app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") # 创建并显示主窗口 window = MainWindow() window.show() # 执行应用程序 sys.exit(app.exec_())将数据分析功能改为接入本地部署的AI大模型进行分析和绘制图表

最新推荐

recommend-type

三菱FX3U三轴伺服电机与威纶通触摸屏组合程序详解:轴点动、回零与定位控制及全流程解析

三菱FX3U三轴伺服电机与威纶通触摸屏的程序编写方法及其应用。主要内容涵盖伺服电机主控程序、触摸屏程序、轴点动、回零及定位程序、通讯模块程序以及威纶显示器程序的分析。通过对各个模块的深入探讨,帮助读者理解每个部分的功能和实现方式,确保机械运动控制的准确性、高效性和稳定性。此外,文章还提供了关于程序编写过程中可能遇到的问题及解决方案。 适合人群:从事自动化控制领域的工程师和技术人员,尤其是对三菱FX3U三轴伺服电机和威纶通触摸屏有实际操作需求的专业人士。 使用场景及目标:适用于工业自动化项目中,旨在提高对三菱FX3U三轴伺服电机和威纶通触摸屏的理解和应用能力,掌握模块化编程技巧,解决实际工程中的编程难题。 其他说明:文中不仅讲解了各模块的具体实现细节,还强调了程序的安全性和可靠性,为项目的成功实施提供了有力的支持。
recommend-type

职业介绍与人才招聘综合管理系统-基于宏达数据库信息管理开发平台的专业人力资源服务软件-包含基本信息设置-用人单位管理-求职人员登记-数据查询-统计分析-报表生成-打印输出-权限控制.zip

cursor免费次数用完职业介绍与人才招聘综合管理系统_基于宏达数据库信息管理开发平台的专业人力资源服务软件_包含基本信息设置_用人单位管理_求职人员登记_数据查询_统计分析_报表生成_打印输出_权限控制.zip
recommend-type

Pansophica开源项目:智能Web搜索代理的探索

Pansophica开源项目是一个相对较新且具有创新性的智能Web搜索代理,它突破了传统搜索引擎的界限,提供了一种全新的交互方式。首先,我们来探讨“智能Web搜索代理”这一概念。智能Web搜索代理是一个软件程序或服务,它可以根据用户的查询自动执行Web搜索,并尝试根据用户的兴趣、历史搜索记录或其他输入来提供个性化的搜索结果。 Pansophica所代表的不仅仅是搜索结果的展示,它还强调了一个交互式的体验,在动态和交互式虚拟现实中呈现搜索结果。这种呈现方式与现有的搜索体验有着根本的不同。目前的搜索引擎,如Google、Bing和Baidu等,多以静态文本和链接列表的形式展示结果。而Pansophica通过提供一个虚拟现实环境,使得搜索者可以“扭转”视角,进行“飞行”探索,以及“弹网”来浏览不同的内容。这种多维度的交互方式使得信息的浏览变得更加快速和直观,有望改变用户与网络信息互动的方式。 接着,我们关注Pansophica的“开源”属性。所谓开源,指的是软件的源代码可以被公众获取,任何个人或组织都可以自由地使用、学习、修改和分发这些代码。开源软件通常由社区进行开发和维护,这样的模式鼓励了协作创新并减少了重复性劳动,因为全世界的开发者都可以贡献自己的力量。Pansophica项目作为开源软件,意味着其他开发者可以访问和使用其源代码,进一步改进和扩展其功能,甚至可以为Pansophica构建新的应用或服务。 最后,文件名称“Pansophica-src-1.3”表明了我们讨论的特定版本的Pansophica开源代码。数字“1.3”很可能指的是该版本号,表明这是Pansophica项目的第1.3个公开版本。这个版本号对于开发者来说很重要,因为它可以帮助他们追踪更新的进度、比较版本间的差异以及选择最适合其项目的版本。 综合以上信息,Pansophica开源项目不仅在搜索代理领域引入了创新的交互方式,而且通过开源的方式,提供了技术共享和协作的可能性。对于IT行业来说,开源项目是推动技术发展的重要力量,它激发了社区的活力,促进了知识的传播和技术的进步。通过构建一个开源社区,Pansophica可以吸引来自世界各地的开发者和研究人员,共同探索如何将搜索技术与交互式虚拟现实技术结合起来,为用户带来更加丰富和有效的信息检索体验。同时,Pansophica的开源性质也使其更容易适应未来技术的发展,随着社区的不断贡献和反馈,这一平台有望在功能和性能上得到持续的优化和增强。
recommend-type

跨平台内容提取无忧:coze工作流应对社交媒体挑战

# 1. 社交媒体内容提取的必要性与挑战 ## 1.1 内容提取的必要性 在数字时代,社交媒体内容是信息与数据流动的主干道,公司、个人和研究者都在寻求从这些信息中提取价值。无论是为了市场趋势分析、品牌监控、消费者洞察还是公共舆论监测,有效的社交媒体内容提取变得至关重要。这不仅有助于企业做出数据驱动的决策,也能使个人更好地理
recommend-type

vrrp主设备发送的免费arp

<think>好的,我现在需要回答用户关于VRRP主设备发送免费ARP的目的和工作原理的问题。首先,我得回忆一下VRRP的基本概念和工作机制。VRRP,虚拟路由冗余协议,主要用于在局域网中提供默认网关的冗余备份,确保网络的高可用性。主设备(Master)负责转发流量,而备用设备(Backup)则在主设备故障时接管工作。 用户的问题集中在主设备发送免费ARP的目的和机制上。根据引用[2],免费ARP用于通知下游设备虚拟MAC地址的变更。当主设备被选举出来后,它需要让局域网内的其他设备知道虚拟IP对应的MAC地址已经指向自己,这样流量才会被正确路由到主设备。免费ARP的作用应该就是更新这些设备的
recommend-type

为Ghost博客平台打造的Meteor流星包装使用指南

从给定文件信息中,我们可以提炼出以下IT知识点: ### 标题知识点:流星Ghost软件包 1. **流星Ghost软件包的用途**:流星Ghost软件包是专为Ghost博客平台设计的流星(Meteor)应用程序。流星是一个开源的全栈JavaScript平台,用于开发高性能和易于编写的Web应用程序。Ghost是一个开源博客平台,它提供了一个简单且专业的写作环境。 2. **软件包的作用**:流星Ghost软件包允许用户在流星平台上轻松集成Ghost博客。这样做的好处是可以利用流星的实时特性以及易于开发和部署的应用程序框架,同时还能享受到Ghost博客系统的便利和美观。 ### 描述知识点:流星Ghost软件包的使用方法 1. **软件包安装方式**:用户可以通过流星的命令行工具添加名为`mrt:ghost`的软件包。`mrt`是流星的一个命令行工具,用于添加、管理以及配置软件包。 2. **初始化Ghost服务器**:描述中提供了如何在服务器启动时运行Ghost的基本代码示例。这段代码使用了JavaScript的Promise异步操作,`ghost().then(function (ghostServer) {...})`这行代码表示当Ghost服务器初始化完成后,会在Promise的回调函数中提供一个Ghost服务器实例。 3. **配置Ghost博客**:在`then`方法中,首先会获取到Ghost服务器的配置对象`config`,用户可以在此处进行自定义设置,例如修改主题、配置等。 4. **启动Ghost服务器**:在配置完成之后,通过调用`ghostServer.start()`来启动Ghost服务,使其能够处理博客相关的请求。 5. **Web浏览器导航**:一旦流星服务器启动并运行,用户便可以通过Web浏览器访问Ghost博客平台。 ### 标签知识点:JavaScript 1. **JavaScript作为流星Ghost软件包的开发语言**:标签指出流星Ghost软件包是使用JavaScript语言开发的。JavaScript是一种在浏览器端广泛使用的脚本语言,它也是流星平台的基础编程语言。 2. **流星和Ghost共同使用的语言**:JavaScript同样也是Ghost博客平台的开发语言。这表明流星Ghost软件包可以无缝集成,因为底层技术栈相同。 ### 压缩包子文件的文件名称列表知识点:meteor-ghost-master 1. **版本控制和软件包结构**:文件名称`meteor-ghost-master`暗示了该软件包可能托管在像GitHub这样的版本控制系统上。文件名中的`master`通常指的是主分支或主版本。 2. **软件包的目录结构**:通过文件名称可以推断出该软件包可能拥有一个标准的流星软件包结构,包含了初始化、配置、运行等必要的模块和文件。 3. **软件包的维护状态**:由于文件名没有包含特定的版本号,我们无法直接得知软件包的最新更新情况。通常,软件包维护者会将最新的版本代码放在`master`分支上。 ### 总结 流星Ghost软件包提供了一个有效的解决方案,使得流星平台的开发者能够在他们的应用中添加Ghost博客功能。软件包的使用简便,通过流星的命令行工具安装,并通过JavaScript代码配置和启动Ghost服务。通过流星Ghost软件包,开发者能够享受流星的实时特性以及Ghost博客系统的便利性。此外,软件包的命名和结构也暗示了其维护和版本控制的模式,有助于开发者更好地理解如何使用和维护这一软件包。
recommend-type

抖音标题生成自动化:用coze工作流释放创意

# 1. 抖音标题生成自动化的重要性 随着社交媒体平台的崛起,内容的吸引力很大程度上取决于标题的创意与精准性。抖音作为一个日活亿级的短视频平台,高质量的标题能够有效提高视频的点击率,增加内容的传播。但是,人工撰写标题不仅耗时耗力,而且很难做到快速响应热点,自动化标题生成工具应运而生。coze工作流,作为一种实现自动化生成抖音标题的工具,其重要性不言而喻。它能够利用大数据分析和机器学习技术,提高标题的吸引
recommend-type

spss消费结构因子分析

<think>好的,我现在需要帮助用户在SPSS中进行消费结构的因子分析。首先,我要回忆一下因子分析的基本步骤和SPSS的操作流程。用户可能对SPSS不太熟悉,所以步骤要详细,同时需要结合他们提供的引用内容,特别是引用[2]中的适用条件和检验方法。 首先,用户提到了消费结构的数据,这可能包括多个变量,如食品、住房、交通等支出。因子分析适用于这种情况,可以降维并找出潜在因子。根据引用[2],需要检查样本量是否足够,变量间是否有相关性,以及KMO和Bartlett检验的结果。 接下来,我需要按照步骤组织回答:数据准备、适用性检验、因子提取、因子旋转、命名解释、计算得分。每个步骤都要简明扼要,说
recommend-type

OpenMediaVault的Docker映像:快速部署与管理指南

根据提供的文件信息,我们将详细讨论与标题和描述中提及的Docker、OpenMediaVault以及如何部署OpenMediaVault的Docker镜像相关的一系列知识点。 首先,Docker是一个开源的应用容器引擎,允许开发者打包应用及其依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。 OpenMediaVault是一个基于Debian的NAS(网络附加存储)解决方案。它专为家庭或小型办公室提供文件共享、网络附加存储以及打印服务。它提供了一个易用的Web界面,通过这个界面用户可以管理服务器配置、网络设置、用户权限、文件服务等。 在描述中提到了一些Docker命令行操作: 1. `git clone`:用于克隆仓库到本地,这里的仓库指的是“docker-images-openmedivault”。 2. `docker build -t omv`:这是一个构建Docker镜像的命令,其中`-t`参数用于标记镜像名称和标签,这里是标记为“omv”。 3. `docker run`:运行一个容器实例,`-t`参数用于分配一个伪终端,`-i`参数用于交互式操作,`-p 80:80`则是将容器的80端口映射到宿主机的80端口。 启动服务的部分涉及OpenMediaVault的配置和初始化: - ssh服务:用于远程登录到服务器的协议。 - php5-fpm:是PHP的一个FastCGI实现,用于加速PHP的运行。 - nginx:是一个高性能的HTTP和反向代理服务器,常用于优化静态内容的分发。 - openmediavault引擎:指的是OpenMediaVault的核心服务。 - rrdcached:用于收集和缓存性能数据,这些数据可以被rrdtool图形化工具读取。 - collectd:是一个守护进程,用于收集系统性能和提供各种存储方式和传输方式来存储所收集的数据。 为了访问服务,需要在浏览器中输入"http:// IP_OF_DOCKER",其中`IP_OF_DOCKER`指的是运行Docker容器的主机IP地址。 描述中还提到了一个步骤:“在System-> Network-> Interfaces中添加带有dhcp的eth0”,这指的是需要在OpenMediaVault的Web管理界面中配置网络接口。`eth0`是网络接口的名称,通常代表第一个以太网接口。DHCP(动态主机配置协议)是一种自动为网络中的设备分配IP地址的协议,这样设备就可以连接网络并开始通信,无需手动配置IP地址。 【压缩包子文件的文件名称列表】中的“docker-images-openmediavault-master”暗示了这是一个包含Docker镜像文件的代码仓库。通常,“master”分支是代码的主分支,包含了代码库中最新且通常是最稳定的版本。用户可以通过克隆该仓库到本地来获取所有相关的Dockerfile、配置脚本及依赖文件,以便能够自行构建和运行OpenMediaVault的Docker镜像。 综上所述,这些知识点涵盖了从基本的Docker概念、Docker命令行操作、OpenMediaVault服务启动和管理,到具体的网络配置及Docker仓库操作,都是进行Docker化OpenMediaVault部署的关键步骤。
recommend-type

小红书文案提取一步到位:coze工作流操作全攻略

# 1. coze工作流概述 工作流系统是企业信息化和数字化转型的核心组件之一,它通过自动化流程管理提升效率,确保业务流程的顺畅执行。coze工作流作为当前市场上较为先进的工作流解决方案,它不仅仅是一套软件工具,更是一个集成化的平台,旨在通过流程自动化和智能化提升企业运营效率。 coze工作流的引入不仅有助于标准化和优化企业的业务流程,还可以通过可配置的流程设计,满足不同部门的特定需求。在组织的业务流程中