From 57269bcd3b1d2097fb57d03dbd8aaef69c96985a Mon Sep 17 00:00:00 2001 From: 27942 Date: Thu, 29 Jan 2026 14:04:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=AD=E7=9A=84=E5=BE=85?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E4=BC=98=E5=8C=96=E5=A5=BD=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gui_app.py | 243 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 178 insertions(+), 65 deletions(-) diff --git a/gui_app.py b/gui_app.py index 970b6ec..ed82b94 100644 --- a/gui_app.py +++ b/gui_app.py @@ -15,7 +15,7 @@ from PyQt5.QtWidgets import ( QStyle, QComboBox, QFrame, QShortcut, QMenu, QAbstractButton, QAbstractItemView, QTableView, QStyledItemDelegate, QStyleOptionProgressBar, QStyleOptionButton, QHeaderView, - QTabWidget, QSplitter, QSizePolicy + QTabWidget, QSplitter, QSizePolicy, QCheckBox ) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDateTime, QSize, QAbstractTableModel, QModelIndex, \ QSortFilterProxyModel, QRegularExpression, QSettings, QTimer, QEvent @@ -1120,7 +1120,7 @@ class MainWindow(QMainWindow): self.table_next_match_btn.clicked.connect(self.next_table_match) search_row.addWidget(self.table_next_match_btn) self.table_clear_btn = PushButton("清空筛选") - self.table_clear_btn.clicked.connect(lambda: self.table_search_input.setText("")) + self.table_clear_btn.clicked.connect(self._clear_filter_and_selection) search_row.addWidget(self.table_clear_btn) self.table_export_all_btn = PushButton("导出全部") self.table_export_all_btn.clicked.connect(self.export_all_rows) @@ -1154,15 +1154,16 @@ class MainWindow(QMainWindow): '执行人', '情况', '文件路径', '进度', '操作' ]) self.table_column_filter.addItem("全部列") - # 第0列为勾选框;排除"情况"列,以多多ID等为筛选项;记录下拉项对应的表格列索引及 Model 列索引 + # 第0列为勾选框;记录下拉项对应的表格列索引及 Model 列索引 self._filter_table_columns = [] - self._filter_model_columns = [] # Model/View 无勾选列,情况=7 跳过 + self._filter_model_columns = [] # Model/View 无勾选列 for col in range(1, 10): header = self.config_table.horizontalHeaderItem(col) - if header and header.text() != "情况": + if header: self.table_column_filter.addItem(header.text()) self._filter_table_columns.append(col) - self._filter_model_columns.append(8 if col == 9 else col - 1) # 1..7→0..6, 9→8 + # Model/View 列映射:1..8→0..7, 9→8 + self._filter_model_columns.append(8 if col == 9 else col - 1) # 默认按多多ID筛选(多多ID为下拉第2项,index=1) if self._filter_table_columns and self._filter_table_columns[0] == 1: self.table_column_filter.setCurrentIndex(1) @@ -1191,12 +1192,10 @@ class MainWindow(QMainWindow): self.config_table.horizontalHeader().sectionResized.connect(self.on_column_resized) # 连接表格resize事件,用于自动按比例调整列宽 self.config_table.horizontalHeader().geometriesChanged.connect(self.on_table_geometry_changed) - # 设置表格最小高度,确保显示更多行 - self.config_table.setMinimumHeight(600) # 最小高度600像素 # 点击空白区域或按Esc键时退出编辑状态 self.config_table.viewport().installEventFilter(self) self.config_table.installEventFilter(self) - table_layout.addWidget(self.config_table) + table_layout.addWidget(self.config_table, 1) # stretch=1,占据剩余空间 # 大数据模式表格(Model/View) self.table_view = QTableView() @@ -1227,14 +1226,16 @@ class MainWindow(QMainWindow): } """) self.table_view.setVisible(False) - table_layout.addWidget(self.table_view) + table_layout.addWidget(self.table_view, 1) # stretch=1,占据剩余空间 self.table_empty_label = QLabel("暂无数据,请先导入Excel配置") self.table_empty_label.setStyleSheet("color: #999; font-size: 12px;") self.table_empty_label.setAlignment(Qt.AlignCenter) self.table_empty_label.setVisible(True) table_layout.addWidget(self.table_empty_label) + # 分页控件行(不设置 stretch,固定在底部) pagination_row = QHBoxLayout() + pagination_row.setContentsMargins(0, 8, 0, 0) pagination_row.addStretch() self.page_size_combo = QComboBox() self.page_size_combo.addItems(["10", "20", "50", "100"]) @@ -1256,7 +1257,7 @@ class MainWindow(QMainWindow): pagination_row.addWidget(self.page_prev_btn) pagination_row.addWidget(self.page_next_btn) pagination_row.addWidget(self.page_last_btn) - table_layout.addLayout(pagination_row) + table_layout.addLayout(pagination_row, 0) # stretch=0,固定大小 self.table_group.setVisible(True) self.table_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -1501,16 +1502,29 @@ class MainWindow(QMainWindow): def _set_checkbox_item(self, row, config_index): """设置勾选框列(第0列),勾选框在单元格内水平、垂直居中""" - checkbox = CheckBox() + checkbox = QCheckBox() checkbox.setChecked(self.configs[config_index].get('勾选', False)) # 默认不勾选 checkbox.stateChanged.connect(lambda state, idx=config_index: self._on_checkbox_changed(idx, state)) + checkbox.setStyleSheet( + "QCheckBox { margin: 0px; padding: 0px; }" + "QCheckBox::indicator { width: 25px; height: 25px; }" + ) wrapper = QWidget() - layout = QHBoxLayout(wrapper) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - layout.addStretch() - layout.addWidget(checkbox, 0, Qt.AlignCenter) - layout.addStretch() + # 使用嵌套布局确保水平和垂直都居中 + outer_layout = QVBoxLayout(wrapper) + outer_layout.setContentsMargins(0, 0, 0, 0) + outer_layout.setSpacing(0) + outer_layout.addStretch() + + inner_layout = QHBoxLayout() + inner_layout.setContentsMargins(0, 0, 0, 0) + inner_layout.setSpacing(0) + inner_layout.addStretch() + inner_layout.addWidget(checkbox) + inner_layout.addStretch() + + outer_layout.addLayout(inner_layout) + outer_layout.addStretch() self.config_table.setCellWidget(row, 0, wrapper) def _on_checkbox_changed(self, config_index, state): @@ -1519,6 +1533,8 @@ class MainWindow(QMainWindow): self.configs[config_index]['勾选'] = (state == Qt.Checked) # 更新已选数量显示 self._update_checked_count() + # 更新状态统计 + self._update_status_statistics() def _update_checked_count(self): """更新已勾选的数量显示""" @@ -1527,18 +1543,85 @@ class MainWindow(QMainWindow): checked_count = sum(1 for config in self.configs if config.get('勾选', False)) self.table_select_count.setText(f"已选: {checked_count}") - def toggle_all_checkboxes(self): - """全选/取消全选所有勾选框""" - is_checked = self.table_select_all_checkbox.isChecked() + def _clear_filter_and_selection(self): + """清空筛选并取消所有勾选""" + # 取消所有勾选 for config in self.configs: - config['勾选'] = is_checked + config['勾选'] = False + # 取消全选复选框 + if hasattr(self, 'table_select_all_checkbox'): + self.table_select_all_checkbox.setChecked(False) + # 清空搜索框(会触发 filter_table 显示所有行) + self.table_search_input.setText("") # 刷新表格显示 self.update_table() self._update_checked_count() - if is_checked: - self._show_infobar("success", "提示", f"已全选 {len(self.configs)} 行") + self._show_infobar("info", "已清空", "筛选条件和勾选已清空") + + def toggle_all_checkboxes(self): + """全选/取消全选 - 跨页操作所有数据(考虑筛选条件)""" + is_checked = self.table_select_all_checkbox.isChecked() + visible_count = 0 + + # 检查是否有筛选条件(搜索关键词或状态筛选) + has_search_filter = hasattr(self, 'table_search_input') and self.table_search_input.text().strip() + has_status_filter = hasattr(self, '_current_status_filter') and self._current_status_filter + + if has_search_filter or has_status_filter: + # 有筛选条件:只操作筛选后的数据 + # 遍历所有配置,只操作符合筛选条件的 + for config_index, config in enumerate(self.configs): + # 检查是否符合搜索筛选(支持多关键词,所有关键词都要匹配) + if has_search_filter: + search_text = self.table_search_input.text().strip() + terms_raw = [t for t in search_text.split() if t] + if terms_raw: + column_index = self._filter_column_index() + match = False + if column_index >= 0: + # 按指定列筛选 + col_key_map = {0: '多多id', 1: '序号', 2: '话题', 3: '定时发布', + 4: '间隔时间', 5: '达人链接', 6: '执行人', 7: '情况', 8: '文件路径'} + if column_index in col_key_map: + cell_value = str(config.get(col_key_map[column_index], '')).lower() + # 所有关键词都要在单元格中 + if all(term.lower() in cell_value for term in terms_raw): + match = True + else: + # 全列搜索 + all_text = ' '.join([str(config.get(k, '')) for k in + ['多多id', '序号', '话题', '定时发布', '间隔时间', + '达人链接', '执行人', '情况', '文件路径']]).lower() + # 所有关键词都要在文本中 + if all(term.lower() in all_text for term in terms_raw): + match = True + if not match: + continue + + # 检查是否符合状态筛选 + if has_status_filter: + config_status = config.get('情况', '待执行') + if config_status != self._current_status_filter: + continue + + # 符合筛选条件,更新勾选状态 + self.configs[config_index]['勾选'] = is_checked + visible_count += 1 else: - self._show_infobar("success", "提示", "已取消全选") + # 无筛选条件:操作所有数据(跨页) + for config_index in range(len(self.configs)): + self.configs[config_index]['勾选'] = is_checked + visible_count += 1 + + # 刷新表格显示 + self.update_table() + self._update_checked_count() + # 更新状态统计 + self._update_status_statistics() + if is_checked: + self._show_infobar("success", "提示", f"已勾选 {visible_count} 行(跨页操作)") + else: + self._show_infobar("success", "提示", f"已取消 {visible_count} 行勾选(跨页操作)") def _create_centered_item(self, text): """创建居中对齐的表格单元格""" @@ -1549,7 +1632,7 @@ class MainWindow(QMainWindow): return item def _set_status_item(self, row, text): - """设置状态列图标与文本""" + """设置状态列文本""" try: # 使用 blockSignals 临时阻止信号,防止递归 self.config_table.blockSignals(True) @@ -1557,16 +1640,14 @@ class MainWindow(QMainWindow): item = QTableWidgetItem(text) item.setTextAlignment(Qt.AlignCenter) # 居中对齐 item.setFlags(item.flags() & ~Qt.ItemIsEditable) + # 根据状态设置文字颜色 if "完成" in text or "成功" in text: - item.setIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton)) + item.setForeground(QColor("#155724")) # 绿色 elif "失败" in text or "错误" in text: - item.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxCritical)) + item.setForeground(QColor("#721C24")) # 红色 elif "执行中" in text or "进行" in text: - item.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) - elif "待" in text: - item.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation)) - else: - item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView)) + item.setForeground(QColor("#0C5460")) # 蓝色 + self._apply_current_highlight_to_item(row, 8, item, text) self.config_table.setItem(row, 8, item) finally: # 恢复信号 @@ -2181,27 +2262,32 @@ class MainWindow(QMainWindow): match = False for col in range(self.config_table.columnCount()): if column_index >= 0 and col != column_index: + try: + item = self.config_table.item(row, col) + if item: + item.setBackground(self._default_color()) + except RuntimeError: + pass + continue + try: item = self.config_table.item(row, col) if item: - item.setBackground(self._default_color()) - continue - item = self.config_table.item(row, col) - if item: - cell_text = item.text() - cell_compare = cell_text.lower() - if keyword: - terms = [t.lower() for t in terms_raw] - term_hit = all(term in cell_compare for term in terms) - if term_hit: - match = True - if self.table_highlight.isChecked(): - item.setBackground(self._highlight_color()) - match_count += 1 + cell_text = item.text() + cell_compare = cell_text.lower() + if keyword: + terms = [t.lower() for t in terms_raw] + term_hit = all(term in cell_compare for term in terms) + if term_hit: + match = True + if self.table_highlight.isChecked(): + item.setBackground(self._highlight_color()) + match_count += 1 + else: + item.setBackground(self._default_color()) else: item.setBackground(self._default_color()) - else: - item.setBackground(self._default_color()) - else: + except RuntimeError: + # item 已被删除,跳过 continue only_match = self.table_only_match.isChecked() if hasattr(self, 'table_only_match') else False self.config_table.setRowHidden(row, (not match) if (keyword and only_match) else False) @@ -2413,24 +2499,17 @@ class MainWindow(QMainWindow): # 使用 blockSignals 临时阻止信号,防止递归 self.config_table.blockSignals(True) try: - # 第8列是"情况"列,颜色与图标与 _set_status_item 一致 + # 第8列是"情况"列,只设置文字颜色,不设置背景色和图标 status_item = QTableWidgetItem(status) status_item.setTextAlignment(Qt.AlignCenter) status_item.setFlags(status_item.flags() & ~Qt.ItemIsEditable) if status == "已完成": - status_item.setBackground(QColor("#D4EDDA")) - status_item.setForeground(QColor("#155724")) - status_item.setIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton)) + status_item.setForeground(QColor("#155724")) # 绿色 elif status == "失败": - status_item.setBackground(QColor("#F8D7DA")) - status_item.setForeground(QColor("#721C24")) - status_item.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxCritical)) + status_item.setForeground(QColor("#721C24")) # 红色 elif status == "执行中": - status_item.setBackground(QColor("#D1ECF1")) - status_item.setForeground(QColor("#0C5460")) - status_item.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) - else: - status_item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView)) + status_item.setForeground(QColor("#0C5460")) # 蓝色 + self._apply_current_highlight_to_item(row_idx, 8, status_item, status) self.config_table.setItem(row_idx, 8, status_item) finally: self.config_table.blockSignals(False) @@ -2440,13 +2519,35 @@ class MainWindow(QMainWindow): def _highlight_color(self): - """高亮颜色""" - return self.config_table.palette().color(self.config_table.palette().Highlight).lighter(160) + """高亮颜色 - 淡蓝色背景""" + return QColor(173, 216, 230) # 淡蓝色 (Light Blue) def _default_color(self): """默认背景色""" return self.config_table.palette().color(self.config_table.palette().Base) + def _apply_current_highlight_to_item(self, row, col, item, text=None): + """根据当前筛选条件对单元格应用高亮""" + if not item or not hasattr(self, "table_search_input"): + return + if not self.table_highlight.isChecked(): + return + keyword_raw = self.table_search_input.text().strip() + if not keyword_raw: + return + column_index = self._filter_column_index() + if column_index >= 0 and col != column_index: + return + terms = [t.lower() for t in keyword_raw.split() if t] + if not terms: + return + cell_text = text if text is not None else item.text() + cell_compare = (cell_text or "").lower() + if all(term in cell_compare for term in terms): + item.setBackground(self._highlight_color()) + else: + item.setBackground(self._default_color()) + def _filter_column_index(self): """筛选项下拉对应的表格列索引;-1 表示全部列。排除情况列,以多多ID等为筛选项。""" i = self.table_column_filter.currentIndex() @@ -3139,7 +3240,7 @@ class MainWindow(QMainWindow): status_value = '待执行' config = { - '勾选': True, # 默认勾选,方便导入后直接操作 + '勾选': False, # 导入后默认不勾选,交由用户选择 '多多id': clean_str(row.get('多多id', '')), '序号': clean_str(row.get('序号', '')), '话题': str(row.get('话题', '')).strip() if pd.notna(row.get('话题')) else '', @@ -3171,6 +3272,12 @@ class MainWindow(QMainWindow): logger.error(f"更新表格失败: {e}") return + # 导入后默认不勾选,确保全选状态为未选中 + if hasattr(self, "table_select_all_checkbox"): + self.table_select_all_checkbox.blockSignals(True) + self.table_select_all_checkbox.setChecked(False) + self.table_select_all_checkbox.blockSignals(False) + # 显示表格 self.table_group.setVisible(True) # 更新状态统计(基于新导入的配置重新计算) @@ -3754,6 +3861,12 @@ class MainWindow(QMainWindow): self._show_infobar("warning", "提示", "所选项已完成或数据不完整") return + # 重置所有要处理的配置状态为"待执行"(确保第二次运行时清除旧状态) + for item in configs_to_process: + idx = item["config_index"] + self.configs[idx]['情况'] = '待执行' + self._update_table_status(idx, "待执行", is_config_index=True) + # 应用定时发布 + 间隔时间逻辑(跨分页全局应用) self._apply_schedule_intervals(configs_to_process)