feat: 完成Tab①导入汇总栏
在 CollectTab 增加导入汇总栏,展示文件数、解析行数、有效/无效、匹配与未匹配数量。 TaskTableModel 支持全部/未匹配过滤,未匹配按钮可筛出别名未匹配账号的任务。 补充 GUI 单测覆盖汇总文本、匹配明细和未匹配筛选;同步任务看板、API 合约、当前状态与 progress。
This commit is contained in:
+107
-15
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user