feat(collect): show and filter product status

This commit is contained in:
chengma
2026-07-20 10:16:25 +08:00
parent c81a24d173
commit a92f1c86f4
6 changed files with 199 additions and 12 deletions
+30 -4
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from .. import ai, appconfig, diagnostics
from .. import ai, appconfig, diagnostics, product_status
from ..collect_skip import skipped_stage_text
from .widgets import *
@@ -10,7 +10,7 @@ from .widgets import *
class TaskTableModel(QAbstractTableModel):
"""Table model for task rows shared by workflow tabs."""
HEADERS = ["账号", "别名", "商品ID", "阶段"]
HEADERS = ["账号", "别名", "商品ID", "阶段", "商品状态"]
STAGE_TEXT = {
"imported": "待采集",
@@ -89,9 +89,14 @@ class TaskTableModel(QAbstractTableModel):
task = self.tasks[index.row()]
if role == Qt.DisplayRole:
return self._display_value(task, index.column())
if role == Qt.ForegroundRole and index.column() == 3:
return self._stage_color(task)
if role == Qt.ForegroundRole:
if index.column() == 3:
return self._stage_color(task)
if index.column() == 4:
return self._product_status_color(task)
if role == Qt.ToolTipRole:
if index.column() == 4:
return self._product_status_tooltip(task)
error = getattr(task, "last_error", "") or ""
if self.is_unmatched(task) or getattr(task, "status", "") == "skipped":
if error:
@@ -154,12 +159,33 @@ class TaskTableModel(QAbstractTableModel):
return _qcolor(COLOR_SUCCESS)
return _qcolor(COLOR_PENDING)
def _product_status_color(self, task):
status = product_status.raw_status(getattr(task, "product_status", None))
if status == product_status.STATUS_NORMAL:
return _qcolor(COLOR_SUCCESS)
if status == product_status.STATUS_UNLISTED:
return _qcolor(COLOR_DANGER)
if status == product_status.STATUS_REVIEWING:
return _qcolor(COLOR_WARNING)
return _qcolor(COLOR_MUTED)
def _product_status_tooltip(self, task):
note = str(getattr(task, "product_status_note", "") or "").strip()
detected_at = str(getattr(task, "product_status_at", "") or "").strip()
lines = [note] if note else []
if detected_at:
lines.append(f"检测时间:{detected_at}")
if not lines and product_status.raw_status(getattr(task, "product_status", None)) == product_status.STATUS_UNCHECKED:
lines.append("尚未检测商品状态")
return "\n".join(lines) or None
def _display_value(self, task, column):
values = [
self._account_name(task),
task.alias,
task.item_id,
self._stage_text(task),
product_status.display_status(getattr(task, "product_status", None)),
]
return values[column] if 0 <= column < len(values) else None
+19 -1
View File
@@ -114,6 +114,10 @@ class CollectTab(QWidget):
self.status_filter.setObjectName("collectStatusFilter")
for label, value in self.STATUS_FILTERS:
self.status_filter.addItem(label, value)
self.product_status_filter = QComboBox()
self.product_status_filter.setObjectName("collectProductStatusFilter")
for label, value in product_status.PRODUCT_STATUS_FILTER_ITEMS:
self.product_status_filter.addItem(label, value)
self.delete_batch_button = QPushButton("删除批次")
self.delete_batch_button.setObjectName("deleteBatchButton")
self.delete_batch_button.setStyleSheet(_danger_outline_button_style("deleteBatchButton"))
@@ -134,7 +138,9 @@ class CollectTab(QWidget):
filter_layout.addWidget(self.shop_filter, 1)
filter_layout.addWidget(QLabel("商品ID"))
filter_layout.addWidget(self.item_filter, 1)
filter_layout.addWidget(QLabel("状态"))
filter_layout.addWidget(QLabel("商品状态"))
filter_layout.addWidget(self.product_status_filter, 1)
filter_layout.addWidget(QLabel("处理状态"))
filter_layout.addWidget(self.status_filter, 1)
filter_layout.addWidget(self.delete_batch_button)
@@ -173,6 +179,8 @@ class CollectTab(QWidget):
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
for column in (3, 4):
self.table.horizontalHeader().setSectionResizeMode(column, QHeaderView.ResizeToContents)
self.table.verticalHeader().setVisible(False)
self.run_log_view = QPlainTextEdit()
@@ -208,6 +216,7 @@ class CollectTab(QWidget):
self.batch_filter.currentIndexChanged.connect(self.refresh_tasks)
self.shop_filter.currentIndexChanged.connect(self.refresh_tasks)
self.item_filter.textChanged.connect(self.refresh_tasks)
self.product_status_filter.currentIndexChanged.connect(self.refresh_tasks)
self.status_filter.currentIndexChanged.connect(self.refresh_tasks)
self.delete_batch_button.clicked.connect(self.delete_current_batch)
self.collect_button.clicked.connect(self.collect_old_data)
@@ -556,6 +565,7 @@ class CollectTab(QWidget):
selected_batch = self.batch_filter.currentData()
selected_shop = self.shop_filter.currentData()
selected_status = self.status_filter.currentData() or "all"
selected_product_status = self.product_status_filter.currentData() or "all"
item_query = self.item_filter.text().strip()
if self.current_batch_id and self.batch_filter.findData(self.current_batch_id) < 0:
selected_batch = self.current_batch_id
@@ -570,6 +580,7 @@ class CollectTab(QWidget):
task for task in task_rows
if self._matches_shop(task, selected_shop)
and self._matches_item(task, item_query)
and self._matches_product_status(task, selected_product_status)
and self._matches_status(task, selected_status, account_rows)
]
except Exception as exc:
@@ -638,6 +649,11 @@ class CollectTab(QWidget):
return True
return item_query in str(getattr(task, "item_id", ""))
def _matches_product_status(self, task, selected_product_status):
if selected_product_status in (None, "all"):
return True
return product_status.raw_status(getattr(task, "product_status", None)) == selected_product_status
def _matches_status(self, task, selected_status, account_rows):
if selected_status in (None, "all"):
return True
@@ -837,6 +853,7 @@ class CollectTab(QWidget):
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)
self.item_filter.setEnabled(not running)
self.product_status_filter.setEnabled(not running)
self.status_filter.setEnabled(not running)
self._update_delete_batch_button()
@@ -848,6 +865,7 @@ class CollectTab(QWidget):
self.batch_filter.setEnabled(not running)
self.shop_filter.setEnabled(not running)
self.item_filter.setEnabled(not running)
self.product_status_filter.setEnabled(not running)
self.status_filter.setEnabled(not running)
self._update_delete_batch_button()
+32
View File
@@ -11,6 +11,7 @@ STATUS_NORMAL = "normal"
STATUS_UNLISTED = "unlisted"
STATUS_REVIEWING = "reviewing"
STATUS_UNKNOWN = "unknown"
STATUS_UNCHECKED = "unchecked"
VALID_PRODUCT_STATUSES = frozenset(
{
@@ -28,6 +29,23 @@ PRODUCT_STATUS_LABELS = {
STATUS_UNKNOWN: "状态未知",
}
PRODUCT_STATUS_FILTER_ITEMS = (
("全部商品状态", "all"),
("架上商品", STATUS_NORMAL),
("未上架", STATUS_UNLISTED),
("审核中", STATUS_REVIEWING),
("状态未知", STATUS_UNKNOWN),
("待检测", STATUS_UNCHECKED),
)
PRODUCT_STATUS_DISPLAY_LABELS = {
STATUS_NORMAL: "架上商品",
STATUS_UNLISTED: "未上架",
STATUS_REVIEWING: "审核中",
STATUS_UNKNOWN: "状态未知",
STATUS_UNCHECKED: "待检测",
}
SCOPE_NORMAL_ONLY = "normal_only"
SCOPE_ALL = "all"
@@ -53,6 +71,20 @@ def status_label(value) -> str:
return PRODUCT_STATUS_LABELS[normalize_status(value)]
def display_status(value) -> str:
"""Return the UI label without treating an empty snapshot as unknown."""
return PRODUCT_STATUS_DISPLAY_LABELS[raw_status(value)]
def raw_status(value) -> str:
"""Return a UI filter status while keeping blank snapshots distinguishable."""
if not str(value or "").strip():
return STATUS_UNCHECKED
return normalize_status(value)
def is_normal(value) -> bool:
return normalize_status(value) == STATUS_NORMAL