feat: 增加任务批量勾选和重新上报 (#89)

This commit is contained in:
chengma
2026-08-10 10:56:48 +08:00
parent d017062d82
commit 34ff39a200
8 changed files with 734 additions and 60 deletions
+223 -46
View File
@@ -56,7 +56,8 @@ from qfluentwidgets import (
PAGE_SIZE = 50
DETAIL_COLUMN = 8
CHECK_COLUMN = 0
DETAIL_COLUMN = 9
REMOTE_TASK_ID_ROLE = Qt.UserRole + 1
@@ -81,8 +82,10 @@ class TaskTableModel(QAbstractTableModel):
"""PDD 任务表格模型,只保存已经从数据库读取的轻量行。"""
loadMoreRequested = pyqtSignal(int, int)
checkedTaskIdsChanged = pyqtSignal(object)
HEADERS = (
"选择",
"任务类型",
"商品标题",
"颜色",
@@ -100,6 +103,7 @@ class TaskTableModel(QAbstractTableModel):
self._has_more = False
self._loading = False
self._placeholder_row_count = 1
self._checked_task_ids: set[str] = set()
def rowCount(self, parent=QModelIndex()) -> int:
if parent.isValid():
@@ -121,18 +125,25 @@ class TaskTableModel(QAbstractTableModel):
task = self._rows[index.row()]
column = index.column()
if role == Qt.CheckStateRole and column == CHECK_COLUMN:
return (
Qt.Checked
if task.remote_task_id in self._checked_task_ids
else Qt.Unchecked
)
if role == Qt.DisplayRole:
return self._display_value(task, column)
if role == Qt.TextAlignmentRole:
if column in (0, 6, 8):
if column in (0, 1, 7, 9):
return Qt.AlignCenter
if column in (4, 5):
if column in (5, 6):
return Qt.AlignRight | Qt.AlignVCenter
return Qt.AlignLeft | Qt.AlignVCenter
if role == Qt.ToolTipRole:
if column == 1:
if column == 2:
return task.title or "尚未获取标题"
if column == DETAIL_COLUMN:
return f"查看任务 {task.remote_task_id} 的详情"
@@ -145,7 +156,30 @@ class TaskTableModel(QAbstractTableModel):
def flags(self, index):
if not index.isValid() or index.row() >= len(self._rows):
return Qt.NoItemFlags
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == CHECK_COLUMN:
flags |= Qt.ItemIsUserCheckable
return flags
def setData(self, index, value, role=Qt.EditRole):
"""切换任务勾选状态;业务身份始终使用远端任务编号。"""
if (
not index.isValid()
or index.column() != CHECK_COLUMN
or index.row() >= len(self._rows)
or role != Qt.CheckStateRole
):
return False
task_id = self._rows[index.row()].remote_task_id
if value == Qt.Checked:
self._checked_task_ids.add(task_id)
else:
self._checked_task_ids.discard(task_id)
self.dataChanged.emit(index, index, [Qt.CheckStateRole])
self.checkedTaskIdsChanged.emit(self.checked_task_ids())
return True
def canFetchMore(self, parent=QModelIndex()) -> bool:
"""告诉 Qt 数据库中是否还有下一页。"""
@@ -172,6 +206,7 @@ class TaskTableModel(QAbstractTableModel):
self._has_more = False
self._loading = False
self.endResetModel()
self._prune_checked_task_ids()
def reset_for_reload(self) -> None:
"""清空旧结果,准备从数据库重新读取第一页。"""
@@ -192,6 +227,8 @@ class TaskTableModel(QAbstractTableModel):
self._has_more = has_more
self._loading = False
self.endResetModel()
if not has_more:
self._prune_checked_task_ids()
return
if page:
@@ -201,6 +238,8 @@ class TaskTableModel(QAbstractTableModel):
self.endInsertRows()
self._has_more = has_more
self._loading = False
if not has_more:
self._prune_checked_task_ids()
def finish_loading(self) -> None:
"""查询失败时停止继续请求,等待用户刷新或重新搜索。"""
@@ -231,6 +270,54 @@ class TaskTableModel(QAbstractTableModel):
return len(self._rows)
def checked_task_ids(self) -> tuple[str, ...]:
"""按当前列表顺序返回已勾选的稳定任务编号。"""
loaded_order = [
task.remote_task_id
for task in self._rows
if task.remote_task_id in self._checked_task_ids
]
loaded = set(loaded_order)
return tuple(loaded_order) + tuple(sorted(self._checked_task_ids - loaded))
def clear_checked_tasks(self) -> None:
"""清空全部勾选,并只刷新复选框列。"""
if not self._checked_task_ids:
return
self._checked_task_ids.clear()
if self._rows:
self.dataChanged.emit(
self.index(0, CHECK_COLUMN),
self.index(len(self._rows) - 1, CHECK_COLUMN),
[Qt.CheckStateRole],
)
self.checkedTaskIdsChanged.emit(())
def uncheck_tasks(self, remote_task_ids: Iterable[str]) -> None:
"""取消指定任务的勾选,供批量操作成功后更新界面。"""
removed = self._checked_task_ids.intersection(remote_task_ids)
if not removed:
return
self._checked_task_ids.difference_update(removed)
for row, task in enumerate(self._rows):
if task.remote_task_id in removed:
index = self.index(row, CHECK_COLUMN)
self.dataChanged.emit(index, index, [Qt.CheckStateRole])
self.checkedTaskIdsChanged.emit(self.checked_task_ids())
def _prune_checked_task_ids(self) -> None:
"""完整加载结束后移除本地已不存在的勾选。"""
loaded = {task.remote_task_id for task in self._rows}
removed = self._checked_task_ids - loaded
if not removed:
return
self._checked_task_ids.intersection_update(loaded)
self.checkedTaskIdsChanged.emit(self.checked_task_ids())
def set_placeholder_row_count(self, count: int) -> None:
"""设置无数据时显示的禁用空行数量。"""
@@ -248,24 +335,26 @@ class TaskTableModel(QAbstractTableModel):
@staticmethod
def _display_value(task: TaskRow, column: int) -> str:
if column == 0:
return task.task_type
if column == CHECK_COLUMN:
return ""
if column == 1:
return task.title or "尚未获取标题"
return task.task_type
if column == 2:
return task.color or "—"
return task.title or "尚未获取标题"
if column == 3:
return task.size or "—"
return task.color or "—"
if column == 4:
return task.size or "—"
if column == 5:
if task.price_cents is None:
return "—"
suffix = " 起" if task.price_is_starting else ""
return f"¥{task.price_cents / 100:.2f}{suffix}"
if column == 5:
return "—" if task.quantity is None else str(task.quantity)
if column == 6:
return task.status
return "—" if task.quantity is None else str(task.quantity)
if column == 7:
return task.status
if column == 8:
return task.updated_at or "—"
return "详情"
@@ -278,6 +367,7 @@ class PDDTaskPage(QWidget):
refreshRequested = pyqtSignal()
rerunRequested = pyqtSignal(str)
rerunCancelRequested = pyqtSignal()
resubmitRequested = pyqtSignal(object)
openSettingsRequested = pyqtSignal()
detailRequested = pyqtSignal(str)
@@ -286,6 +376,7 @@ class PDDTaskPage(QWidget):
self.setObjectName("pddTaskPage")
self._rerun_state = "idle"
self._resubmit_running = False
self.taskModel = TaskTableModel(self)
self._build_command_area()
self._build_content_area()
@@ -301,11 +392,13 @@ class PDDTaskPage(QWidget):
self.autoFetchButton.setMinimumWidth(132)
self.autoFetchButton.setAccessibleName("开始自动获取 PDD 任务")
self.taskTypeLabel = CaptionLabel("任务类型", self)
self.taskTypeCombo = ComboBox(self)
self.taskTypeCombo.addItems(("全部", "采集", "采购"))
self.taskTypeCombo.setMinimumWidth(100)
self.taskTypeCombo.setAccessibleName("任务类型")
self.statusLabelCaption = CaptionLabel("任务状态", self)
self.statusCombo = ComboBox(self)
self.statusCombo.addItems(
("全部", "待执行", "执行中", "已完成", "失败", "需要人工处理")
@@ -313,6 +406,7 @@ class PDDTaskPage(QWidget):
self.statusCombo.setMinimumWidth(130)
self.statusCombo.setAccessibleName("任务状态")
self.keywordLabel = CaptionLabel("关键词", self)
self.keywordInput = SearchLineEdit(self)
self.keywordInput.setPlaceholderText("任务编号、商品编号或商品标题")
self.keywordInput.setClearButtonEnabled(True)
@@ -331,22 +425,68 @@ class PDDTaskPage(QWidget):
self.rerunButton.setAccessibleName("重新执行当前选中的采集任务")
self.rerunButton.setEnabled(False)
self.resubmitButton = PushButton(FIF.SEND, "重新上报", self)
self.resubmitButton.setAccessibleName("重新上报勾选任务的已有结果")
self.resubmitButton.setEnabled(False)
self.commandCard = CardWidget(self)
commandLayout = QGridLayout(self.commandCard)
commandLayout.setContentsMargins(20, 14, 20, 14)
commandLayout.setHorizontalSpacing(12)
commandLayout.addWidget(self.autoFetchButton, 0, 0)
commandLayout.addWidget(CaptionLabel("任务类型", self.commandCard), 0, 1)
commandLayout.addWidget(self.taskTypeCombo, 0, 2)
commandLayout.addWidget(CaptionLabel("任务状态", self.commandCard), 0, 3)
commandLayout.addWidget(self.statusCombo, 0, 4)
commandLayout.addWidget(CaptionLabel("关键词", self.commandCard), 0, 5)
commandLayout.addWidget(self.keywordInput, 0, 6)
commandLayout.addWidget(self.searchButton, 0, 7)
commandLayout.setColumnStretch(6, 4)
commandLayout.setColumnStretch(8, 1)
commandLayout.addWidget(self.refreshButton, 0, 9)
commandLayout.addWidget(self.rerunButton, 0, 10)
self.checkedCountLabel = CaptionLabel("已选 0 条", self.commandCard)
self.checkedCountLabel.setAccessibleName("已勾选任务数量")
self.commandLayout = QGridLayout(self.commandCard)
self.commandLayout.setContentsMargins(20, 14, 20, 14)
self.commandLayout.setHorizontalSpacing(12)
self.commandLayout.setVerticalSpacing(10)
self._compact_commands = False
# 先使用安全的双行布局,窗口真正变宽后再切换为单行。
self._update_command_layout(compact=True)
def _update_command_layout(self, compact: bool) -> None:
"""在窄窗口把搜索区换到第二行,不重建任何控件。"""
if compact == self._compact_commands and self.commandLayout.count():
return
self._compact_commands = compact
for column in range(13):
self.commandLayout.setColumnStretch(column, 0)
if compact:
positions = (
(self.autoFetchButton, 0, 0, 1, 1),
(self.taskTypeLabel, 0, 1, 1, 1),
(self.taskTypeCombo, 0, 2, 1, 1),
(self.statusLabelCaption, 0, 3, 1, 1),
(self.statusCombo, 0, 4, 1, 1),
(self.checkedCountLabel, 0, 6, 1, 1),
(self.rerunButton, 0, 7, 1, 1),
(self.resubmitButton, 0, 8, 1, 1),
(self.keywordLabel, 1, 0, 1, 1),
(self.keywordInput, 1, 1, 1, 5),
(self.searchButton, 1, 6, 1, 1),
(self.refreshButton, 1, 7, 1, 1),
)
self.commandLayout.setColumnStretch(5, 1)
else:
positions = (
(self.autoFetchButton, 0, 0, 1, 1),
(self.taskTypeLabel, 0, 1, 1, 1),
(self.taskTypeCombo, 0, 2, 1, 1),
(self.statusLabelCaption, 0, 3, 1, 1),
(self.statusCombo, 0, 4, 1, 1),
(self.keywordLabel, 0, 5, 1, 1),
(self.keywordInput, 0, 6, 1, 1),
(self.searchButton, 0, 7, 1, 1),
(self.refreshButton, 0, 8, 1, 1),
(self.checkedCountLabel, 0, 10, 1, 1),
(self.rerunButton, 0, 11, 1, 1),
(self.resubmitButton, 0, 12, 1, 1),
)
self.commandLayout.setColumnStretch(6, 4)
self.commandLayout.setColumnStretch(9, 1)
for widget, row, column, row_span, column_span in positions:
self.commandLayout.addWidget(
widget, row, column, row_span, column_span
)
def _build_content_area(self) -> None:
self.taskTable = TableView(self)
@@ -362,16 +502,17 @@ class PDDTaskPage(QWidget):
header = self.taskTable.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Interactive)
header.setSectionResizeMode(1, QHeaderView.Stretch)
header.setSectionResizeMode(2, QHeaderView.Stretch)
header.setMinimumSectionSize(64)
self.taskTable.setColumnWidth(0, 82)
self.taskTable.setColumnWidth(2, 108)
self.taskTable.setColumnWidth(3, 82)
self.taskTable.setColumnWidth(4, 96)
self.taskTable.setColumnWidth(5, 70)
self.taskTable.setColumnWidth(6, 116)
self.taskTable.setColumnWidth(7, 156)
self.taskTable.setColumnWidth(8, 72)
self.taskTable.setColumnWidth(0, 64)
self.taskTable.setColumnWidth(1, 82)
self.taskTable.setColumnWidth(3, 108)
self.taskTable.setColumnWidth(4, 82)
self.taskTable.setColumnWidth(5, 96)
self.taskTable.setColumnWidth(6, 70)
self.taskTable.setColumnWidth(7, 116)
self.taskTable.setColumnWidth(8, 156)
self.taskTable.setColumnWidth(9, 72)
self.taskTable.viewport().installEventFilter(self)
self.emptyStateCard = CardWidget(self)
@@ -420,12 +561,11 @@ class PDDTaskPage(QWidget):
self.keywordInput.returnPressed.connect(self._apply_filters)
self.refreshButton.clicked.connect(self.refreshRequested.emit)
self.rerunButton.clicked.connect(self._request_rerun)
self.resubmitButton.clicked.connect(self._request_resubmit)
self.clearFiltersButton.clicked.connect(self.clear_filters)
self.taskTable.clicked.connect(self._on_table_clicked)
self.taskTable.activated.connect(self._on_table_activated)
self.taskTable.selectionModel().selectionChanged.connect(
self._update_rerun_button
)
self.taskModel.checkedTaskIdsChanged.connect(self._update_checked_actions)
self.findShortcut = QShortcut(QKeySequence.Find, self)
self.findShortcut.activated.connect(self.keywordInput.setFocus)
@@ -435,6 +575,12 @@ class PDDTaskPage(QWidget):
self._update_placeholder_rows()
return super().eventFilter(watched, event)
def resizeEvent(self, event) -> None:
"""按内容区宽度切换顶部命令区的单行或双行布局。"""
self._update_command_layout(compact=event.size().width() < 1180)
super().resizeEvent(event)
def set_tasks(self, tasks: Iterable[TaskRow]) -> None:
"""显示任务数据,并尽量恢复原来选中的稳定任务编号。"""
@@ -516,6 +662,12 @@ class PDDTaskPage(QWidget):
self._rerun_state = "running" if running else "idle"
self._update_rerun_button()
def set_resubmit_running(self, running: bool) -> None:
"""设置重新上报忙碌状态并防止重复提交。"""
self._resubmit_running = running
self._update_checked_actions(self.taskModel.checked_task_ids())
def set_rerun_cancelling(self) -> None:
"""显示取消请求已发出,防止用户重复点击。"""
@@ -528,33 +680,58 @@ class PDDTaskPage(QWidget):
return
if self._rerun_state != "idle":
return
task_id = self.current_task_id()
if task_id:
self.rerunRequested.emit(task_id)
task_ids = self.taskModel.checked_task_ids()
if len(task_ids) == 1:
self.rerunRequested.emit(task_ids[0])
def _request_resubmit(self) -> None:
if self._resubmit_running:
return
task_ids = self.taskModel.checked_task_ids()
if task_ids:
self.resubmitRequested.emit(task_ids)
def _update_rerun_button(self) -> None:
if self._rerun_state == "running":
self.rerunButton.setText("停止重新采集")
self.rerunButton.setAccessibleName("停止当前重新采集")
self.rerunButton.setEnabled(True)
self.resubmitButton.setEnabled(False)
self.autoFetchButton.setEnabled(False)
return
if self._rerun_state == "cancelling":
self.rerunButton.setText("正在停止…")
self.rerunButton.setAccessibleName("正在停止重新采集")
self.rerunButton.setEnabled(False)
self.resubmitButton.setEnabled(False)
self.autoFetchButton.setEnabled(False)
return
self.rerunButton.setText("重新执行")
self.rerunButton.setAccessibleName("重新执行当前选中的采集任务")
self.autoFetchButton.setEnabled(True)
has_selection = self.taskTable.selectionModel().hasSelection()
self.autoFetchButton.setEnabled(not self._resubmit_running)
self.rerunButton.setEnabled(
has_selection and bool(self.current_task_id())
not self._resubmit_running
and len(self.taskModel.checked_task_ids()) == 1
)
self.resubmitButton.setEnabled(
not self._resubmit_running
and bool(self.taskModel.checked_task_ids())
)
def _update_checked_actions(self, task_ids=None) -> None:
"""同步勾选数量和批量命令启用状态。"""
checked = tuple(task_ids or ())
self.checkedCountLabel.setText(f"已选 {len(checked)} 条")
self.resubmitButton.setText(
"正在上报…" if self._resubmit_running else "重新上报"
)
self.resubmitButton.setEnabled(bool(checked) and not self._resubmit_running)
self._update_rerun_button()
def _apply_filters(self) -> None:
self.taskModel.clear_checked_tasks()
filters = {
"task_type": self.taskTypeCombo.currentText(),
"status": self.statusCombo.currentText(),
+195 -1
View File
@@ -50,6 +50,7 @@ from .purchase_reconcile_service import PurchaseReconcileFactory
from .selected_android_device_service import SelectedAndroidDeviceService
from .settings_repository import SettingsRepository
from .task_models import (
OutboxStatus,
TaskFilters,
TaskStatus,
TaskSummary,
@@ -211,6 +212,82 @@ class ClaimTaskWorker(QObject):
self.completed.emit()
class ResultResubmitWorker(QObject):
"""在后台逐条重发既有结果 Outbox,不执行任何手机操作。"""
progress = pyqtSignal(int, int, str)
finished = pyqtSignal(object, object, object)
completed = pyqtSignal()
def __init__(
self,
gateway: AdminGateway,
repository: TaskRepository,
task_ids: tuple[str, ...],
) -> None:
super().__init__()
self._gateway = gateway
self._repository = repository
self._task_ids = task_ids
self._cancelled = False
def cancel(self) -> None:
"""当前网络请求结束后停止处理后续任务。"""
self._cancelled = True
@pyqtSlot()
def run(self) -> None:
succeeded: list[str] = []
failed: list[str] = []
skipped: list[str] = []
total = len(self._task_ids)
try:
for current, task_id in enumerate(self._task_ids, 1):
if self._cancelled:
break
self.progress.emit(current, total, task_id)
try:
event = self._repository.latest_result_outbox(task_id)
except Exception:
skipped.append(task_id)
continue
if event is None or event.status is OutboxStatus.SENDING:
skipped.append(task_id)
continue
self._repository.mark_outbox_sending(event.id)
try:
receipt = self._gateway.submit_result(
task_id,
event.idempotency_key,
event.payload_json,
)
if not receipt.accepted:
raise AdminGatewayError(
"ADMIN_RESULT_NOT_ACCEPTED",
"Admin 未确认接收任务结果",
False,
)
except AdminGatewayError as exc:
if exc.retryable:
self._repository.mark_outbox_retry(event.id, str(exc))
else:
self._repository.mark_outbox_failed(event.id, str(exc))
failed.append(task_id)
continue
except Exception as exc:
self._repository.mark_outbox_retry(event.id, str(exc))
failed.append(task_id)
continue
self._repository.mark_outbox_sent(event.id)
succeeded.append(task_id)
self.finished.emit(succeeded, failed, skipped)
finally:
self.completed.emit()
class PDDTaskPageEvent(QObject):
"""把 PDD 页面只读操作连接到本地任务 Repository。"""
@@ -242,6 +319,9 @@ class PDDTaskPageEvent(QObject):
self._claim_feedback: Optional[InfoBar] = None
self._device_feedback: Optional[InfoBar] = None
self._auto_fetch_running = False
self._resubmit_busy = False
self._resubmit_thread: Optional[QThread] = None
self._resubmit_worker: Optional[ResultResubmitWorker] = None
self._stop_requested = False
self._cycle_next_delay_ms: Optional[int] = None
self._stop_status = "自动获取:已停止 · 当前没有执行中的任务"
@@ -295,6 +375,7 @@ class PDDTaskPageEvent(QObject):
page.refreshRequested.connect(self.refresh_tasks)
page.rerunRequested.connect(self.request_rerun)
page.rerunCancelRequested.connect(self.request_cancel_rerun)
page.resubmitRequested.connect(self.request_resubmit)
page.autoFetchRequested.connect(self._request_claim_task)
page.detailRequested.connect(self.show_task_detail)
page.taskModel.loadMoreRequested.connect(self._load_page)
@@ -330,7 +411,7 @@ class PDDTaskPageEvent(QObject):
if self._closing or not task_id:
return
if self._auto_fetch_running or self._claim_busy:
if self._auto_fetch_running or self._claim_busy or self._resubmit_busy:
self._show_rerun_warning(
"暂时不能重新执行",
"自动获取或其他采集正在运行,请停止并等待当前任务结束。",
@@ -386,6 +467,100 @@ class PDDTaskPageEvent(QObject):
self._start_rerun_worker(task_id)
@pyqtSlot(object)
def request_resubmit(self, task_ids) -> None:
"""确认后在后台重发勾选任务的原始结果 Outbox。"""
stable_ids = tuple(
dict.fromkeys(str(value) for value in task_ids if value)
)
if self._closing or not stable_ids:
return
if self._auto_fetch_running or self._claim_busy or self._resubmit_busy:
self._show_rerun_warning(
"暂时不能重新上报",
"自动获取、重新执行或另一批上报正在运行,请等待当前操作结束。",
)
return
if self._claim_gateway_error or self._claim_gateway is None:
self._show_rerun_warning(
"重新上报不可用",
self._claim_gateway_error or "Admin 提交服务未初始化",
)
return
dialog = MessageBox(
f"重新上报 {len(stable_ids)} 条任务结果?",
"只会重新提交本地已经保存的原始结果,"
"不会重新采集、采购或操作 Android 手机,也不会修改本地结果。",
self._page.window(),
)
dialog.yesButton.setText(f"上报 {len(stable_ids)} 条")
dialog.cancelButton.setText("暂不上报")
dialog.cancelButton.setFocus()
if not dialog.exec():
return
self._start_resubmit_worker(stable_ids)
def _start_resubmit_worker(self, task_ids: tuple[str, ...]) -> None:
"""启动只处理指定结果 Outbox 的工作线程。"""
assert self._claim_gateway is not None
self._resubmit_busy = True
self._page.set_resubmit_running(True)
self._page.set_engine_status(
f"正在准备重新上报 {len(task_ids)} 条任务结果…"
)
thread = QThread(self)
worker = ResultResubmitWorker(
self._claim_gateway,
self._repository,
task_ids,
)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.progress.connect(self._on_resubmit_progress)
worker.finished.connect(self._on_resubmit_finished)
worker.completed.connect(thread.quit)
worker.completed.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.finished.connect(self._on_resubmit_thread_finished)
self._resubmit_thread = thread
self._resubmit_worker = worker
thread.start()
@pyqtSlot(int, int, str)
def _on_resubmit_progress(self, current: int, total: int, task_id: str) -> None:
if not self._closing:
self._page.set_engine_status(
f"正在重新上报 {current}/{total}:{task_id}"
)
@pyqtSlot(object, object, object)
def _on_resubmit_finished(self, succeeded, failed, skipped) -> None:
if self._closing:
return
succeeded_ids = tuple(succeeded)
self._page.taskModel.uncheck_tasks(succeeded_ids)
message = (
f"重新上报完成:成功 {len(succeeded_ids)} 条,"
f"失败 {len(failed)} 条,跳过 {len(skipped)} 条"
)
self._page.set_engine_status(message)
if failed or skipped:
self._show_claim_error("重新上报未全部完成", message)
self._reload()
@pyqtSlot()
def _on_resubmit_thread_finished(self) -> None:
self._resubmit_worker = None
self._resubmit_thread = None
self._resubmit_busy = False
if not self._closing:
self._page.set_resubmit_running(False)
def _start_rerun_worker(self, task_id: str) -> None:
"""启动只处理指定任务的工作线程。"""
@@ -557,6 +732,12 @@ class PDDTaskPageEvent(QObject):
if self._closing:
return
if self._resubmit_busy:
self._show_rerun_warning(
"暂时不能获取任务",
"任务结果正在重新上报,请等待当前操作结束。",
)
return
if self._auto_fetch_running:
self._request_stop_auto_fetch()
return
@@ -1045,6 +1226,19 @@ class PDDTaskPageEvent(QObject):
# 避免窗口销毁时出现 "QThread destroyed while running"。
thread.wait(60_000)
resubmit_worker = self._resubmit_worker
resubmit_thread = self._resubmit_thread
if resubmit_worker is not None:
try:
resubmit_worker.cancel()
resubmit_worker.progress.disconnect(self._on_resubmit_progress)
resubmit_worker.finished.disconnect(self._on_resubmit_finished)
except (TypeError, RuntimeError):
pass
if resubmit_thread is not None and resubmit_thread.isRunning():
resubmit_thread.quit()
resubmit_thread.wait(60_000)
def summary_to_row(summary: TaskSummary) -> TaskRow:
"""把领域摘要转换成只供表格显示的轻量行。"""
+19
View File
@@ -998,6 +998,25 @@ class TaskRepository:
connection.close()
return self._to_outbox(row) if row is not None else None
def latest_result_outbox(
self, remote_task_id: str
) -> Optional[OutboxEventRecord]:
"""读取任务最新的成功结果事件,不返回失败上报事件。"""
connection = open_database(self._db_path)
try:
row = connection.execute(
"SELECT o.* FROM outbox_events o"
" JOIN pdd_tasks t ON t.id = o.task_id"
" WHERE t.remote_task_id = ?"
" AND o.event_type IN ('collect_result', 'purchase_result')"
" ORDER BY o.id DESC LIMIT 1",
(remote_task_id,),
).fetchone()
finally:
connection.close()
return self._to_outbox(row) if row is not None else None
def get_outbox_event(self, event_id: int) -> Optional[OutboxEventRecord]:
connection = open_database(self._db_path)
try: