第一版完整版
This commit is contained in:
27942
2026-01-26 00:59:04 +08:00
parent d70e6cef49
commit 2a25d21fa8

View File

@@ -771,7 +771,6 @@ class MainWindow(QMainWindow):
self.table_proxy = None
self.log_match_positions = []
self.log_match_index = -1
self.log_match_items = []
self.is_updating_table = False
self._is_closing = False # 标记是否正在关闭窗口
# 任务执行时用于“多多ID+序号 -> 行号”的映射(用于精确回写状态)
@@ -1050,47 +1049,29 @@ class MainWindow(QMainWindow):
self.table_search_input = LineEdit()
self.table_search_input.setPlaceholderText("搜索表格(支持空格多关键词)")
self.table_search_input.setClearButtonEnabled(True)
self.table_search_input.setFixedWidth(250)
self.table_search_input.textChanged.connect(self.filter_table)
search_row.addWidget(self.table_search_input)
self.table_column_filter = QComboBox()
self.table_column_filter.currentIndexChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_column_filter)
self.table_case_sensitive = CheckBox("区分大小写")
self.table_case_sensitive.stateChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_case_sensitive)
self.table_regex = CheckBox("正则")
self.table_regex.stateChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_regex)
self.table_any_term = CheckBox("任意词匹配")
self.table_any_term.stateChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_any_term)
self.table_highlight = CheckBox("高亮匹配")
self.table_highlight.setChecked(True)
self.table_highlight.stateChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_highlight)
self.table_locate_btn = PushButton("定位")
self.table_locate_btn.clicked.connect(self.locate_table)
search_row.addWidget(self.table_locate_btn)
self.table_only_match = CheckBox("仅显示匹配")
self.table_only_match = CheckBox("仅显示匹配项")
self.table_only_match.setChecked(True)
self.table_only_match.stateChanged.connect(lambda: self.filter_table(self.table_search_input.text()))
search_row.addWidget(self.table_only_match)
self.table_clear_btn = PushButton("清空筛选")
self.table_clear_btn.clicked.connect(lambda: self.table_search_input.setText(""))
search_row.addWidget(self.table_clear_btn)
self.table_filter_status = QLabel("显示: 0/0")
self.table_filter_status.setStyleSheet("color: #666; font-size: 10px;")
search_row.addWidget(self.table_filter_status)
self.table_match_selector = QComboBox()
self.table_match_selector.setMinimumWidth(180)
self.table_match_selector.currentIndexChanged.connect(self.jump_to_table_match)
search_row.addWidget(self.table_match_selector)
self.table_prev_match_btn = PushButton("上一条")
self.table_prev_match_btn.clicked.connect(self.prev_table_match)
search_row.addWidget(self.table_prev_match_btn)
self.table_next_match_btn = PushButton("下一条")
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(""))
search_row.addWidget(self.table_clear_btn)
self.table_export_all_btn = PushButton("导出全部")
self.table_export_all_btn.clicked.connect(self.export_all_rows)
search_row.addWidget(self.table_export_all_btn)
@@ -1268,15 +1249,9 @@ class MainWindow(QMainWindow):
self.log_highlight_check.setChecked(True)
self.log_highlight_check.stateChanged.connect(lambda: self.filter_log(self.log_search_input.text()))
log_header.addWidget(self.log_highlight_check)
self.log_case_sensitive = CheckBox("区分大小写")
self.log_case_sensitive.stateChanged.connect(lambda: self.filter_log(self.log_search_input.text()))
log_header.addWidget(self.log_case_sensitive)
self.log_whole_word = CheckBox("整词匹配")
self.log_whole_word.stateChanged.connect(lambda: self.filter_log(self.log_search_input.text()))
log_header.addWidget(self.log_whole_word)
self.log_regex = CheckBox("正则")
self.log_regex.stateChanged.connect(lambda: self.filter_log(self.log_search_input.text()))
log_header.addWidget(self.log_regex)
self.log_prev_btn = PushButton("上一个")
self.log_prev_btn.clicked.connect(lambda: self.find_log(backward=True))
log_header.addWidget(self.log_prev_btn)
@@ -1286,10 +1261,6 @@ class MainWindow(QMainWindow):
self.log_match_status = QLabel("匹配: 0")
self.log_match_status.setStyleSheet("color: #666; font-size: 10px;")
log_header.addWidget(self.log_match_status)
self.log_match_selector = QComboBox()
self.log_match_selector.setMinimumWidth(160)
self.log_match_selector.currentIndexChanged.connect(self.jump_to_log_match)
log_header.addWidget(self.log_match_selector)
self.log_export_btn = PushButton("导出日志")
self.log_export_btn.clicked.connect(self.export_log)
log_header.addWidget(self.log_export_btn)
@@ -1322,10 +1293,6 @@ class MainWindow(QMainWindow):
self.shortcut_log_next.activated.connect(lambda: self.find_log(backward=False))
self.shortcut_log_prev = QShortcut(QKeySequence("Shift+F3"), self)
self.shortcut_log_prev.activated.connect(lambda: self.find_log(backward=True))
self.shortcut_table_next = QShortcut(QKeySequence("Ctrl+F3"), self)
self.shortcut_table_next.activated.connect(self.next_table_match)
self.shortcut_table_prev = QShortcut(QKeySequence("Ctrl+Shift+F3"), self)
self.shortcut_table_prev.activated.connect(self.prev_table_match)
# 程序启动时重置状态(不累计历史数据)
self.set_status_cards(update_text="未更新", pending=0, running=0, success=0, failed=0)
@@ -2133,36 +2100,18 @@ class MainWindow(QMainWindow):
return
if not keyword_raw:
self.table_proxy.setFilterRegularExpression(QRegularExpression())
if hasattr(self, "table_filter_status"):
total_rows = self.table_proxy.rowCount()
self.table_filter_status.setText(f"显示: {total_rows}/{total_rows} | 命中: 0")
return
regex_enabled = self.table_regex.isChecked()
any_term = self.table_any_term.isChecked()
terms = [re.escape(t) for t in keyword_raw.split() if t]
if not terms:
pattern = ""
else:
pattern = "".join([f"(?=.*{t})" for t in terms]) + ".*"
regex = QRegularExpression(pattern, QRegularExpression.CaseInsensitiveOption)
column_index = self._filter_model_column_index()
self.table_proxy.setFilterKeyColumn(column_index)
if regex_enabled:
pattern = keyword_raw
else:
terms = [re.escape(t) for t in keyword_raw.split() if t]
if not terms:
pattern = ""
elif any_term:
pattern = "|".join(terms)
else:
pattern = "".join([f"(?=.*{t})" for t in terms]) + ".*"
regex = QRegularExpression(pattern)
if not self.table_case_sensitive.isChecked():
regex.setPatternOptions(QRegularExpression.CaseInsensitiveOption)
self.table_proxy.setFilterRegularExpression(regex)
if hasattr(self, "table_filter_status"):
self.table_filter_status.setText(
f"显示: {self.table_proxy.rowCount()}/{self.table_model.rowCount()} | 命中: 0")
return
if not self.config_table or self.config_table.rowCount() == 0:
if hasattr(self, "table_filter_status"):
self.table_filter_status.setText("显示: 0/0")
self._refresh_table_match_selector([])
return
if not keyword_raw:
# 清空筛选
@@ -2172,27 +2121,14 @@ class MainWindow(QMainWindow):
if item:
item.setBackground(self._default_color())
self.config_table.setRowHidden(row, False)
if hasattr(self, "table_filter_status"):
total_rows = self.config_table.rowCount()
self.table_filter_status.setText(f"显示: {total_rows}/{total_rows} | 命中: 0")
self._refresh_table_match_selector([])
self.table_match_rows = []
self.table_match_index = -1
return
terms_raw = [t for t in keyword_raw.split() if t]
keyword = keyword_raw if self.table_case_sensitive.isChecked() else keyword_raw.lower()
keyword = keyword_raw.lower()
column_index = self._filter_column_index()
visible_count = 0
match_count = 0
matched_rows = []
regex_enabled = self.table_regex.isChecked()
any_term = self.table_any_term.isChecked()
pattern = None
if keyword and regex_enabled:
flags = 0 if self.table_case_sensitive.isChecked() else re.IGNORECASE
try:
pattern = re.compile(keyword_raw, flags)
except re.error:
self._show_infobar("warning", "提示", "正则表达式无效")
return
for row in range(self.config_table.rowCount()):
match = False
for col in range(self.config_table.columnCount()):
@@ -2204,88 +2140,56 @@ class MainWindow(QMainWindow):
item = self.config_table.item(row, col)
if item:
cell_text = item.text()
cell_compare = cell_text if self.table_case_sensitive.isChecked() else cell_text.lower()
cell_compare = cell_text.lower()
if keyword:
if regex_enabled and pattern:
if pattern.search(cell_text):
match = True
if self.table_highlight.isChecked():
item.setBackground(self._highlight_color())
match_count += 1
else:
item.setBackground(self._default_color())
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:
terms = terms_raw if self.table_case_sensitive.isChecked() else [t.lower() for t in
terms_raw]
term_hit = any(term in cell_compare for term in terms) if any_term else 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())
item.setBackground(self._default_color())
else:
item.setBackground(self._default_color())
else:
continue
only_match = self.table_only_match.isChecked()
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)
if keyword:
if match:
visible_count += 1
matched_rows.append(row)
else:
visible_count = self.config_table.rowCount()
if hasattr(self, "table_filter_status"):
self.table_filter_status.setText(
f"显示: {visible_count}/{self.config_table.rowCount()} | 命中: {match_count}"
)
self._refresh_table_match_selector(matched_rows)
if keyword and match:
matched_rows.append(row)
# 更新匹配行列表
self.table_match_rows = matched_rows
self.table_match_index = -1
def locate_table(self):
"""快速定位匹配"""
keyword_raw = self.table_search_input.text().strip()
if not keyword_raw:
def next_table_match(self):
"""跳转到下一条匹配"""
if not self.table_match_rows:
return
terms_raw = [t for t in keyword_raw.split() if t]
keyword = keyword_raw if self.table_case_sensitive.isChecked() else keyword_raw.lower()
column_index = self._filter_column_index() # 使用正确的列索引映射(排除情况列)
regex_enabled = self.table_regex.isChecked()
pattern = None
if keyword and regex_enabled:
flags = 0 if self.table_case_sensitive.isChecked() else re.IGNORECASE
try:
pattern = re.compile(keyword_raw, flags)
except re.error:
self._show_infobar("warning", "提示", "正则表达式无效")
return
for row in range(self.config_table.rowCount()):
for col in range(self.config_table.columnCount()):
if column_index >= 0 and col != column_index:
continue
item = self.config_table.item(row, col)
if item:
cell_text = item.text()
cell_compare = cell_text if self.table_case_sensitive.isChecked() else cell_text.lower()
if regex_enabled and pattern:
if pattern.search(cell_text):
self.config_table.setCurrentItem(item)
self.config_table.scrollToItem(item)
return
else:
terms = terms_raw if self.table_case_sensitive.isChecked() else [t.lower() for t in terms_raw]
term_hit = any(
term in cell_compare for term in terms) if self.table_any_term.isChecked() else all(
term in cell_compare for term in terms
)
if term_hit:
self.config_table.setCurrentItem(item)
self.config_table.scrollToItem(item)
return
self._show_infobar("warning", "提示", "未找到匹配内容")
if self.table_match_index < 0:
self.table_match_index = 0
else:
self.table_match_index = (self.table_match_index + 1) % len(self.table_match_rows)
row = self.table_match_rows[self.table_match_index]
self.config_table.selectRow(row)
item = self.config_table.item(row, 0) or self.config_table.item(row, 1)
if item:
self.config_table.scrollToItem(item)
def prev_table_match(self):
"""跳转到上一条匹配"""
if not self.table_match_rows:
return
if self.table_match_index < 0:
self.table_match_index = len(self.table_match_rows) - 1
else:
self.table_match_index = (self.table_match_index - 1) % len(self.table_match_rows)
row = self.table_match_rows[self.table_match_index]
self.config_table.selectRow(row)
item = self.config_table.item(row, 0) or self.config_table.item(row, 1)
if item:
self.config_table.scrollToItem(item)
def _apply_schedule_intervals(self, configs_with_rows):
"""按多多ID应用定时发布+间隔时间规则
@@ -2499,75 +2403,6 @@ class MainWindow(QMainWindow):
except:
pass
def _refresh_table_match_selector(self, rows):
"""更新表格匹配列表"""
self.table_match_rows = rows
self.table_match_index = -1
if not hasattr(self, "table_match_selector"):
return
self.table_match_selector.blockSignals(True)
self.table_match_selector.clear()
self.table_match_selector.addItem(f"匹配列表({len(rows)}")
for row in rows[:200]:
self.table_match_selector.addItem(self._build_table_match_label(row))
self.table_match_selector.setCurrentIndex(0)
self.table_match_selector.blockSignals(False)
def _build_table_match_label(self, row):
"""构建表格匹配项显示文本"""
def cell_text(col):
item = self.config_table.item(row, col)
return item.text().strip() if item else ""
# 第0列是勾选框数据列从第1列开始
user_id = cell_text(1)
index = cell_text(2)
topic = cell_text(3)
label = " | ".join([v for v in [user_id, index, topic] if v])
if not label:
for col in range(self.config_table.columnCount()):
value = cell_text(col)
if value:
label = value
break
if len(label) > 60:
label = label[:57] + "..."
return f"R{row + 1}: {label}"
def jump_to_table_match(self, index):
"""跳转到表格匹配行"""
if index <= 0:
return
actual_index = index - 1
if actual_index >= len(self.table_match_rows):
return
row = self.table_match_rows[actual_index]
self.table_match_index = actual_index
self.config_table.selectRow(row)
item = self.config_table.item(row, 0) or self.config_table.item(row, 1)
if item:
self.config_table.scrollToItem(item)
def next_table_match(self):
"""跳转到下一条匹配"""
if not self.table_match_rows:
return
if self.table_match_index < 0:
self.table_match_index = 0
else:
self.table_match_index = (self.table_match_index + 1) % len(self.table_match_rows)
self.table_match_selector.setCurrentIndex(self.table_match_index + 1)
def prev_table_match(self):
"""跳转到上一条匹配"""
if not self.table_match_rows:
return
if self.table_match_index < 0:
self.table_match_index = len(self.table_match_rows) - 1
else:
self.table_match_index = (self.table_match_index - 1) % len(self.table_match_rows)
self.table_match_selector.setCurrentIndex(self.table_match_index + 1)
def _highlight_color(self):
"""高亮颜色"""
@@ -2635,9 +2470,6 @@ class MainWindow(QMainWindow):
self._current_status_filter = None
self._show_all_rows()
self._show_infobar("success", "提示", "已显示全部记录")
if hasattr(self, "table_filter_status"):
total = self.config_table.rowCount()
self.table_filter_status.setText(f"显示: {total}/{total} | 命中: 0")
return
self._current_status_filter = status
@@ -2662,9 +2494,6 @@ class MainWindow(QMainWindow):
else:
self.config_table.setRowHidden(row, True)
if hasattr(self, "table_filter_status"):
self.table_filter_status.setText(f"显示: {visible_count}/{total_count} | 筛选: {status}")
if visible_count == 0:
self._show_infobar("warning", "提示", f"没有{status}的记录")
else:
@@ -2821,7 +2650,6 @@ class MainWindow(QMainWindow):
self._clear_log_highlight()
self._update_log_match_status(0)
if not keyword:
self._refresh_log_match_selector([])
return
self._update_log_matches(keyword)
if self.log_highlight_check.isChecked():
@@ -2890,36 +2718,18 @@ class MainWindow(QMainWindow):
"""更新日志匹配位置"""
self.log_match_positions = []
self.log_match_index = -1
self.log_match_items = []
if not keyword:
self._update_log_match_status(0)
self._refresh_log_match_selector([])
return
content = self.log_text.toPlainText()
use_regex = self.log_regex.isChecked()
case_sensitive = self.log_case_sensitive.isChecked()
whole_word = self.log_whole_word.isChecked()
flags = 0 if case_sensitive else re.IGNORECASE
if use_regex:
pattern_text = keyword
if whole_word:
pattern_text = rf"\b(?:{pattern_text})\b"
try:
pattern = re.compile(pattern_text, flags)
except re.error:
self._show_infobar("warning", "提示", "日志正则表达式无效")
self._update_log_match_status(0)
return
else:
pattern_text = re.escape(keyword)
if whole_word:
pattern_text = rf"\b{pattern_text}\b"
pattern = re.compile(pattern_text, flags)
pattern_text = re.escape(keyword)
if whole_word:
pattern_text = rf"\b{pattern_text}\b"
pattern = re.compile(pattern_text, re.IGNORECASE)
for match in pattern.finditer(content):
self.log_match_positions.append((match.start(), match.end()))
self.log_match_items.append(self._build_log_match_label(content, match.start(), match.end()))
self._update_log_match_status(len(self.log_match_positions))
self._refresh_log_match_selector(self.log_match_items)
def _update_log_match_status(self, count):
"""更新日志匹配统计"""
@@ -2967,44 +2777,6 @@ class MainWindow(QMainWindow):
self.table_view.setColumnWidth(col, width)
# 所有列都可以手动调整宽度Interactive模式
def _refresh_log_match_selector(self, items):
"""更新日志匹配下拉"""
if not hasattr(self, "log_match_selector"):
return
self.log_match_selector.blockSignals(True)
self.log_match_selector.clear()
self.log_match_selector.addItem(f"匹配列表({len(items)}")
for label in items[:200]:
self.log_match_selector.addItem(label)
self.log_match_selector.setCurrentIndex(0)
self.log_match_selector.blockSignals(False)
def _build_log_match_label(self, content, start, end):
"""构建匹配项显示文本"""
line_start = content.rfind("\n", 0, start) + 1
line_end = content.find("\n", end)
if line_end == -1:
line_end = len(content)
line_text = content[line_start:line_end].strip()
line_no = content.count("\n", 0, start) + 1
if len(line_text) > 60:
line_text = line_text[:57] + "..."
return f"L{line_no}: {line_text}"
def jump_to_log_match(self, index):
"""跳转到指定匹配"""
if index <= 0:
return
actual_index = index - 1
if actual_index >= len(self.log_match_positions):
return
start, end = self.log_match_positions[actual_index]
self.log_match_index = actual_index
cursor = self.log_text.textCursor()
cursor.setPosition(start)
cursor.setPosition(end, QTextCursor.KeepAnchor)
self.log_text.setTextCursor(cursor)
self.log_text.ensureCursorVisible()
def _show_infobar(self, level, title, content):
"""显示提示条"""
@@ -3250,10 +3022,6 @@ class MainWindow(QMainWindow):
self._apply_table_column_widths()
# 未更新前,用配置行数作为待执行提示
self.set_status_cards(pending=self.config_table.rowCount())
if hasattr(self, "table_filter_status"):
self.table_filter_status.setText(
f"显示: {self.config_table.rowCount()}/{total_rows} | 命中: 0"
)
if hasattr(self, "table_empty_label"):
# 即使没有数据也显示表格,不显示"暂无数据"提示
self.table_empty_label.setVisible(False)