feat: 完成Tab①导入汇总栏

在 CollectTab 增加导入汇总栏,展示文件数、解析行数、有效/无效、匹配与未匹配数量。

TaskTableModel 支持全部/未匹配过滤,未匹配按钮可筛出别名未匹配账号的任务。

补充 GUI 单测覆盖汇总文本、匹配明细和未匹配筛选;同步任务看板、API 合约、当前状态与 progress。
This commit is contained in:
chengma
2026-06-27 11:40:51 +08:00
parent 6f36118b7e
commit 3bf42dc11e
6 changed files with 185 additions and 25 deletions
+107 -15
View File
@@ -105,18 +105,32 @@ if QT_IMPORT_ERROR is None:
def __init__(self, parent=None):
super().__init__(parent)
self.tasks = []
self.all_tasks = []
self.account_by_alias = {}
self.filter_mode = "all"
def set_tasks(self, tasks, accounts):
self.beginResetModel()
self.tasks = list(tasks)
self.all_tasks = list(tasks)
self.account_by_alias = {
str(account.alias).strip(): account
for account in accounts
if str(account.alias).strip()
}
self.tasks = self._filtered_tasks()
self.endResetModel()
def set_filter_mode(self, mode):
self.beginResetModel()
self.filter_mode = mode if mode in {"all", "unmatched"} else "all"
self.tasks = self._filtered_tasks()
self.endResetModel()
def _filtered_tasks(self):
if self.filter_mode == "unmatched":
return [task for task in self.all_tasks if self.is_unmatched(task)]
return list(self.all_tasks)
def rowCount(self, parent=QModelIndex()):
return 0 if parent.isValid() else len(self.tasks)
@@ -154,7 +168,7 @@ if QT_IMPORT_ERROR is None:
return str(task.alias).strip() not in self.account_by_alias
def unmatched_count(self) -> int:
return sum(1 for task in self.tasks if self.is_unmatched(task))
return sum(1 for task in self.all_tasks if self.is_unmatched(task))
def _account_name(self, task) -> str:
account = self.account_by_alias.get(str(task.alias).strip())
@@ -188,6 +202,8 @@ if QT_IMPORT_ERROR is None:
self.db_path = _database_path(db_path, self.config)
self.status_callback = status_callback
self.current_batch_id = None
self.has_import_result = False
self.last_import_stats = None
self.import_button = QPushButton("导入 Excel...")
self.refresh_button = QPushButton("刷新")
@@ -197,6 +213,17 @@ if QT_IMPORT_ERROR is None:
toolbar.addWidget(self.refresh_button)
toolbar.addStretch(1)
self.summary_label = QLabel("未导入任务")
self.match_detail_label = QLabel("")
self.show_all_button = QPushButton("全部")
self.show_unmatched_button = QPushButton("未匹配(0)")
summary_layout = QHBoxLayout()
summary_layout.addWidget(self.summary_label)
summary_layout.addStretch(1)
summary_layout.addWidget(self.show_all_button)
summary_layout.addWidget(self.show_unmatched_button)
self.model = TaskTableModel(self)
self.table = QTableView()
self.table.setModel(self.model)
@@ -211,11 +238,15 @@ if QT_IMPORT_ERROR is None:
layout = QVBoxLayout(self)
layout.setContentsMargins(18, 18, 18, 18)
layout.addLayout(toolbar)
layout.addLayout(summary_layout)
layout.addWidget(self.match_detail_label)
layout.addWidget(self.table, 1)
layout.addWidget(self.empty_label)
self.import_button.clicked.connect(self.import_excel)
self.refresh_button.clicked.connect(self.refresh_tasks)
self.show_all_button.clicked.connect(self.show_all_tasks)
self.show_unmatched_button.clicked.connect(self.show_unmatched_tasks)
self.refresh_tasks()
@@ -245,15 +276,15 @@ if QT_IMPORT_ERROR is None:
except Exception as exc:
self._show_error(exc)
return
if result.get("batch_id"):
self.current_batch_id = result["batch_id"]
self.has_import_result = True
self.last_import_stats = result.get("stats") or {}
self.current_batch_id = result.get("batch_id")
self.refresh_tasks()
stats = result.get("stats") or {}
self._set_status(
"导入完成:有效{valid},无效{invalid},入库{inserted},未匹配{unmatched}".format(
valid=stats.get("valid", 0),
invalid=stats.get("invalid", 0),
inserted=stats.get("inserted", 0),
valid=self.last_import_stats.get("valid", 0),
invalid=self.last_import_stats.get("invalid", 0),
inserted=self.last_import_stats.get("inserted", 0),
unmatched=self.model.unmatched_count(),
)
)
@@ -261,7 +292,10 @@ if QT_IMPORT_ERROR is None:
def refresh_tasks(self, checked=False):
try:
db.init_db(self.db_path)
task_rows = db.list_tasks(batch_id=self.current_batch_id, path=self.db_path)
if self.current_batch_id is None and self.has_import_result:
task_rows = []
else:
task_rows = db.list_tasks(batch_id=self.current_batch_id, path=self.db_path)
account_rows = accounts.list_accounts(path=self.db_path, config=self.config)
except Exception as exc:
self.model.set_tasks([], [])
@@ -269,13 +303,71 @@ if QT_IMPORT_ERROR is None:
self._set_status(f"任务读取失败:{exc}")
return
self.model.set_tasks(task_rows, account_rows)
if task_rows:
unmatched = self.model.unmatched_count()
self.empty_label.setText(
"" if unmatched == 0 else f"{unmatched} 条任务别名未匹配账号,阶段显示为“略过”"
)
else:
self._update_summary(task_rows, account_rows)
self._update_empty_label(len(task_rows))
def show_all_tasks(self, checked=False):
self.model.set_filter_mode("all")
self._update_empty_label(len(self.model.all_tasks))
def show_unmatched_tasks(self, checked=False):
self.model.set_filter_mode("unmatched")
self._update_empty_label(len(self.model.all_tasks))
def _update_summary(self, task_rows, account_rows):
stats = self.last_import_stats or {}
unmatched = self._unmatched_count(task_rows, account_rows)
matched = len(task_rows) - unmatched
files = stats.get("files", 0 if not task_rows else 1)
total = stats.get("total", len(task_rows))
valid = stats.get("valid", len(task_rows))
invalid = stats.get("invalid", 0)
self.summary_label.setText(
f"{files} 文件 · {total} 行 · 有效{valid}/无效{invalid} · 匹配{matched} · 未匹配{unmatched}"
)
self.match_detail_label.setText(self._match_detail(task_rows, account_rows))
self.show_unmatched_button.setText(f"未匹配({unmatched})")
self.show_unmatched_button.setEnabled(unmatched > 0)
if unmatched == 0 and self.model.filter_mode == "unmatched":
self.model.set_filter_mode("all")
def _match_detail(self, task_rows, account_rows):
account_by_alias = {
str(account.alias).strip(): account
for account in account_rows
if str(account.alias).strip()
}
counts = {}
for task in task_rows:
account = account_by_alias.get(str(task.alias).strip())
if account is None:
continue
name = account.account_name or account.alias
counts[name] = counts.get(name, 0) + 1
if not counts:
return "匹配明细:无"
parts = [f"{name}{count}" for name, count in sorted(counts.items())]
return "匹配明细:" + " · ".join(parts)
def _unmatched_count(self, task_rows, account_rows):
aliases = {
str(account.alias).strip()
for account in account_rows
if str(account.alias).strip()
}
return sum(1 for task in task_rows if str(task.alias).strip() not in aliases)
def _update_empty_label(self, total_rows):
if total_rows == 0:
self.empty_label.setText("暂无任务")
return
if self.model.rowCount() == 0 and self.model.filter_mode == "unmatched":
self.empty_label.setText("没有未匹配任务")
return
unmatched = self.model.unmatched_count()
self.empty_label.setText(
"" if unmatched == 0 else f"{unmatched} 条任务别名未匹配账号,阶段显示为“略过”"
)
class AccountDialog(QDialog):