feat: 任务列表显示店铺名和执行用时 (#136)

This commit is contained in:
chengma
2026-08-11 11:17:44 +08:00
parent 6d30115c7e
commit e354dddad6
10 changed files with 163 additions and 28 deletions
+13 -1
View File
@@ -4,7 +4,7 @@
``MIGRATIONS`` 末尾增加版本,不能修改已经发布的迁移。
"""
SCHEMA_VERSION = 4
SCHEMA_VERSION = 5
MIGRATION_1 = (
@@ -152,10 +152,22 @@ MIGRATION_4 = (
""",
)
MIGRATION_5 = (
"ALTER TABLE pdd_tasks ADD COLUMN shop_name TEXT",
"""
UPDATE pdd_tasks
SET shop_name = TRIM(json_extract(pdd_data, '$.shop_name'))
WHERE pdd_data IS NOT NULL AND json_valid(pdd_data)
AND json_type(pdd_data, '$.shop_name') = 'text'
AND TRIM(json_extract(pdd_data, '$.shop_name')) <> ''
""",
)
MIGRATIONS = {
1: MIGRATION_1,
2: MIGRATION_2,
3: MIGRATION_3,
4: MIGRATION_4,
5: MIGRATION_5,
}
+27 -12
View File
@@ -67,12 +67,15 @@ class TaskRow:
task_type: str
goods_id: str = ""
title: str = ""
shop_name: str = ""
color: str = ""
size: str = ""
price_cents: Optional[int] = None
price_is_starting: bool = False
quantity: Optional[int] = None
status: str = "待执行"
latest_run_status: str = ""
duration_seconds: Optional[int] = None
updated_at: str = ""
@@ -86,11 +89,13 @@ class TaskTableModel(QAbstractTableModel):
"选择",
"任务类型",
"商品标题",
"店铺名",
"颜色",
"尺码",
"价格",
"数量",
"状态",
"用时",
"更新时间",
)
@@ -133,9 +138,9 @@ class TaskTableModel(QAbstractTableModel):
return self._display_value(task, column)
if role == Qt.TextAlignmentRole:
if column in (0, 1, 7):
if column in (0, 1, 8, 9):
return Qt.AlignCenter
if column in (5, 6):
if column in (6, 7):
return Qt.AlignRight | Qt.AlignVCenter
return Qt.AlignLeft | Qt.AlignVCenter
@@ -367,19 +372,27 @@ class TaskTableModel(QAbstractTableModel):
if column == 2:
return task.title or "尚未获取标题"
if column == 3:
return task.color or "—"
return task.shop_name or "—"
if column == 4:
return task.size or "—"
return task.color or "—"
if column == 5:
return task.size or "—"
if column == 6:
if task.price_cents is None:
return "—"
suffix = " 起" if task.price_is_starting else ""
return f"¥{task.price_cents / 100:.2f}{suffix}"
if column == 6:
return "—" if task.quantity is None else str(task.quantity)
if column == 7:
return task.status
return "—" if task.quantity is None else str(task.quantity)
if column == 8:
return task.status
if column == 9:
if task.latest_run_status == "running":
return "进行中"
if task.duration_seconds is None:
return "—"
return f"{max(0, task.duration_seconds)} 秒"
if column == 10:
return task.updated_at or "—"
return ""
@@ -567,11 +580,13 @@ class PDDTaskPage(QWidget):
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(4, 108)
self.taskTable.setColumnWidth(5, 82)
self.taskTable.setColumnWidth(6, 96)
self.taskTable.setColumnWidth(7, 70)
self.taskTable.setColumnWidth(8, 116)
self.taskTable.setColumnWidth(9, 82)
self.taskTable.setColumnWidth(10, 156)
self.taskTable.viewport().installEventFilter(self)
self.emptyStateCard = CardWidget(self)
+5
View File
@@ -1709,10 +1709,15 @@ def summary_to_row(summary: TaskSummary) -> TaskRow:
task_type=TASK_TYPE_TEXT[summary.task_type],
goods_id=summary.goods_id or "",
title=summary.title or "",
shop_name=summary.shop_name or "",
color=summary.target_color or "",
size=summary.target_size or "",
price_cents=summary.price_cent,
quantity=summary.quantity,
status=TASK_STATUS_TEXT[summary.status],
latest_run_status=(
summary.latest_run_status.value if summary.latest_run_status else ""
),
duration_seconds=summary.duration_seconds,
updated_at=summary.updated_at,
)
+3
View File
@@ -138,6 +138,9 @@ class TaskSummary:
quantity: Optional[int]
status: TaskStatus
updated_at: str
shop_name: Optional[str] = None
latest_run_status: Optional[RunStatus] = None
duration_seconds: Optional[int] = None
@dataclass(frozen=True)
+36 -6
View File
@@ -129,11 +129,20 @@ class TaskRepository:
connection = open_database(self._db_path)
try:
rows = connection.execute(
"SELECT id, remote_task_id, task_type, goods_id, title,"
" target_color, target_size, price_cent, quantity, status, updated_at"
"SELECT pdd_tasks.id, remote_task_id, task_type, goods_id, title,"
" shop_name, target_color, target_size, price_cent, quantity,"
" status, pdd_tasks.updated_at, latest_run.run_status AS latest_run_status,"
" CASE WHEN latest_run.run_status = 'running' THEN NULL"
" WHEN latest_run.started_at IS NOT NULL AND latest_run.finished_at IS NOT NULL"
" THEN MAX(0, CAST(strftime('%s', latest_run.finished_at)"
" - strftime('%s', latest_run.started_at) AS INTEGER))"
" ELSE NULL END AS duration_seconds"
" FROM pdd_tasks"
" LEFT JOIN task_runs AS latest_run ON latest_run.id = ("
" SELECT id FROM task_runs WHERE task_id = pdd_tasks.id"
" ORDER BY attempt_no DESC LIMIT 1)"
f"{where_sql}"
" ORDER BY updated_at DESC, id DESC"
" ORDER BY pdd_tasks.updated_at DESC, pdd_tasks.id DESC"
" LIMIT ? OFFSET ?",
parameters,
).fetchall()
@@ -1098,13 +1107,14 @@ class TaskRepository:
task_cursor = connection.execute(
"UPDATE pdd_tasks SET status = 'result_pending',"
" current_step = 'purchase_order_matched_pending_report',"
" pdd_data = ?,"
" pdd_data = ?, shop_name = ?,"
" price_cent = ?, last_error_code = NULL,"
" last_error_message = NULL, finished_at = ?, updated_at = ?"
" WHERE id = ? AND status = 'manual_review'"
" AND current_step = 'reconcile_purchase'",
(
result_json,
self._summary_shop_name(pdd_data),
self._purchase_result_price(pdd_data),
now,
now,
@@ -1188,11 +1198,12 @@ class TaskRepository:
)
connection.execute(
"UPDATE pdd_tasks SET status = 'result_pending',"
" current_step = 'submit_result', pdd_data = ?,"
" current_step = 'submit_result', pdd_data = ?, shop_name = ?,"
" price_cent = ?, finished_at = ?, updated_at = ?"
" WHERE id = ?",
(
result_json,
self._summary_shop_name(pdd_data),
self._purchase_result_price(pdd_data),
now,
now,
@@ -1373,11 +1384,13 @@ class TaskRepository:
connection.execute(
"UPDATE pdd_tasks SET status = 'result_pending',"
" current_step = 'submit_result', pdd_data = ?, goods_id = ?,"
" title = ?, price_cent = ?, finished_at = ?, updated_at = ?"
" title = ?, shop_name = ?, price_cent = ?,"
" finished_at = ?, updated_at = ?"
" WHERE id = ?",
(
result_json,
pdd_data.get("goods_id"), pdd_data.get("title"),
self._summary_shop_name(pdd_data),
self._summary_price(pdd_data), now, now, task["id"],
),
)
@@ -1834,14 +1847,31 @@ class TaskRepository:
task_type=TaskType(row["task_type"]),
goods_id=row["goods_id"],
title=row["title"],
shop_name=row["shop_name"],
target_color=row["target_color"],
target_size=row["target_size"],
price_cent=row["price_cent"],
quantity=row["quantity"],
status=TaskStatus(row["status"]),
latest_run_status=(
RunStatus(row["latest_run_status"])
if row["latest_run_status"] is not None
else None
),
duration_seconds=row["duration_seconds"],
updated_at=row["updated_at"],
)
@staticmethod
def _summary_shop_name(pdd_data: Dict[str, object]) -> Optional[str]:
"""提取列表需要的店铺名,空值不写入摘要列。"""
value = pdd_data.get("shop_name")
if not isinstance(value, str):
return None
value = value.strip()
return value or None
@staticmethod
def _to_detail(row: sqlite3.Row) -> TaskDetail:
admin_payload = TaskRepository._load_json_object(row["admin_payload"])