feat(client): connect PDD task list to SQLite (#9)
This commit is contained in:
+68
-89
@@ -78,7 +78,9 @@ class TaskRow:
|
||||
|
||||
|
||||
class TaskTableModel(QAbstractTableModel):
|
||||
"""PDD 任务表格模型,当前使用内存数据模拟 Repository 查询结果。"""
|
||||
"""PDD 任务表格模型,只保存已经从数据库读取的轻量行。"""
|
||||
|
||||
loadMoreRequested = pyqtSignal(int, int)
|
||||
|
||||
HEADERS = (
|
||||
"任务类型",
|
||||
@@ -94,14 +96,9 @@ class TaskTableModel(QAbstractTableModel):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._source_rows: list[TaskRow] = []
|
||||
self._filtered_rows: list[TaskRow] = []
|
||||
self._rows: list[TaskRow] = []
|
||||
self._task_type = "全部"
|
||||
self._status = "全部"
|
||||
self._keyword = ""
|
||||
self._sort_column = 7
|
||||
self._sort_order = Qt.DescendingOrder
|
||||
self._has_more = False
|
||||
self._loading = False
|
||||
self._placeholder_row_count = 1
|
||||
|
||||
def rowCount(self, parent=QModelIndex()) -> int:
|
||||
@@ -151,54 +148,66 @@ class TaskTableModel(QAbstractTableModel):
|
||||
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
|
||||
def canFetchMore(self, parent=QModelIndex()) -> bool:
|
||||
"""告诉 Qt 内存结果中是否还有下一页。"""
|
||||
"""告诉 Qt 数据库中是否还有下一页。"""
|
||||
|
||||
return not parent.isValid() and len(self._rows) < len(self._filtered_rows)
|
||||
return not parent.isValid() and self._has_more and not self._loading
|
||||
|
||||
def fetchMore(self, parent=QModelIndex()) -> None:
|
||||
"""每次再展示最多 50 行,模拟后续 Repository 的分页行为。"""
|
||||
"""请求事件层从数据库读取下一页。"""
|
||||
|
||||
if parent.isValid() or not self.canFetchMore(parent):
|
||||
return
|
||||
|
||||
start = len(self._rows)
|
||||
end = min(start + PAGE_SIZE, len(self._filtered_rows))
|
||||
self.beginInsertRows(QModelIndex(), start, end - 1)
|
||||
self._rows.extend(self._filtered_rows[start:end])
|
||||
self.endInsertRows()
|
||||
self._loading = True
|
||||
self.loadMoreRequested.emit(len(self._rows), PAGE_SIZE)
|
||||
|
||||
def sort(self, column, order=Qt.AscendingOrder) -> None:
|
||||
if not 0 <= column < len(self.HEADERS):
|
||||
return
|
||||
|
||||
loaded_count = max(PAGE_SIZE, len(self._rows))
|
||||
self.beginResetModel()
|
||||
self._sort_column = column
|
||||
self._sort_order = order
|
||||
self._sort_filtered_rows()
|
||||
self._rows = self._filtered_rows[:loaded_count]
|
||||
self.endResetModel()
|
||||
"""列表顺序由 Repository 固定为更新时间倒序。"""
|
||||
|
||||
def set_tasks(self, tasks: Iterable[TaskRow]) -> None:
|
||||
"""替换内存任务列表;后续由 Repository 查询结果调用。"""
|
||||
"""替换全部任务;保留给简单测试和静态预览使用。"""
|
||||
|
||||
self.beginResetModel()
|
||||
self._source_rows = list(tasks)
|
||||
self._rebuild_filtered_rows()
|
||||
self._rows = self._filtered_rows[:PAGE_SIZE]
|
||||
self._rows = list(tasks)
|
||||
self._has_more = False
|
||||
self._loading = False
|
||||
self.endResetModel()
|
||||
|
||||
def apply_filters(self, task_type: str, status: str, keyword: str) -> None:
|
||||
"""按类型、状态和关键词同时筛选当前内存任务。"""
|
||||
def reset_for_reload(self) -> None:
|
||||
"""清空旧结果,准备从数据库重新读取第一页。"""
|
||||
|
||||
self.beginResetModel()
|
||||
self._task_type = task_type
|
||||
self._status = status
|
||||
self._keyword = keyword.strip().casefold()
|
||||
self._rebuild_filtered_rows()
|
||||
self._rows = self._filtered_rows[:PAGE_SIZE]
|
||||
self._rows = []
|
||||
self._has_more = True
|
||||
self._loading = False
|
||||
self.endResetModel()
|
||||
|
||||
def append_tasks(self, tasks: Iterable[TaskRow], has_more: bool) -> None:
|
||||
"""追加一页任务,并结束本次加载状态。"""
|
||||
|
||||
page = list(tasks)
|
||||
if not self._rows:
|
||||
self.beginResetModel()
|
||||
self._rows = page
|
||||
self._has_more = has_more
|
||||
self._loading = False
|
||||
self.endResetModel()
|
||||
return
|
||||
|
||||
if page:
|
||||
start = len(self._rows)
|
||||
self.beginInsertRows(QModelIndex(), start, start + len(page) - 1)
|
||||
self._rows.extend(page)
|
||||
self.endInsertRows()
|
||||
self._has_more = has_more
|
||||
self._loading = False
|
||||
|
||||
def finish_loading(self) -> None:
|
||||
"""查询失败时停止继续请求,等待用户刷新或重新搜索。"""
|
||||
|
||||
self._has_more = False
|
||||
self._loading = False
|
||||
|
||||
def row_at(self, row: int) -> Optional[TaskRow]:
|
||||
"""根据当前可见行获取任务,越界时返回 None。"""
|
||||
|
||||
@@ -215,7 +224,7 @@ class TaskTableModel(QAbstractTableModel):
|
||||
return -1
|
||||
|
||||
def filtered_count(self) -> int:
|
||||
return len(self._filtered_rows)
|
||||
return len(self._rows)
|
||||
|
||||
def data_row_count(self) -> int:
|
||||
"""返回真实任务行数,不包含填满空表格的占位行。"""
|
||||
@@ -237,48 +246,6 @@ class TaskTableModel(QAbstractTableModel):
|
||||
self._placeholder_row_count = count
|
||||
self.endResetModel()
|
||||
|
||||
def _rebuild_filtered_rows(self) -> None:
|
||||
self._filtered_rows = [
|
||||
task for task in self._source_rows if self._matches_filters(task)
|
||||
]
|
||||
self._sort_filtered_rows()
|
||||
|
||||
def _matches_filters(self, task: TaskRow) -> bool:
|
||||
if self._task_type != "全部" and task.task_type != self._task_type:
|
||||
return False
|
||||
if self._status != "全部" and task.status != self._status:
|
||||
return False
|
||||
if not self._keyword:
|
||||
return True
|
||||
|
||||
searchable_text = " ".join(
|
||||
(task.remote_task_id, task.goods_id, task.title)
|
||||
).casefold()
|
||||
return self._keyword in searchable_text
|
||||
|
||||
def _sort_filtered_rows(self) -> None:
|
||||
reverse = self._sort_order == Qt.DescendingOrder
|
||||
self._filtered_rows.sort(
|
||||
key=lambda task: self._sort_value(task, self._sort_column),
|
||||
reverse=reverse,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _sort_value(task: TaskRow, column: int):
|
||||
values = (
|
||||
task.task_type,
|
||||
task.title,
|
||||
task.color,
|
||||
task.size,
|
||||
task.price_cents if task.price_cents is not None else -1,
|
||||
task.quantity if task.quantity is not None else -1,
|
||||
task.status,
|
||||
task.updated_at,
|
||||
task.remote_task_id,
|
||||
)
|
||||
value = values[column]
|
||||
return value.casefold() if isinstance(value, str) else value
|
||||
|
||||
@staticmethod
|
||||
def _display_value(task: TaskRow, column: int) -> str:
|
||||
if column == 0:
|
||||
@@ -381,7 +348,7 @@ class PDDTaskPage(QWidget):
|
||||
self.taskTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||
self.taskTable.setAlternatingRowColors(True)
|
||||
self.taskTable.setWordWrap(False)
|
||||
self.taskTable.setSortingEnabled(True)
|
||||
self.taskTable.setSortingEnabled(False)
|
||||
self.taskTable.verticalHeader().setVisible(False)
|
||||
|
||||
header = self.taskTable.horizontalHeader()
|
||||
@@ -396,7 +363,6 @@ class PDDTaskPage(QWidget):
|
||||
self.taskTable.setColumnWidth(6, 116)
|
||||
self.taskTable.setColumnWidth(7, 156)
|
||||
self.taskTable.setColumnWidth(8, 72)
|
||||
self.taskTable.sortByColumn(7, Qt.DescendingOrder)
|
||||
self.taskTable.viewport().installEventFilter(self)
|
||||
|
||||
self.emptyStateCard = CardWidget(self)
|
||||
@@ -464,6 +430,24 @@ class PDDTaskPage(QWidget):
|
||||
self._update_empty_state()
|
||||
self._restore_current_task(current_task_id)
|
||||
|
||||
def begin_task_reload(self) -> None:
|
||||
"""清空旧结果并显示本地数据库加载状态。"""
|
||||
|
||||
self._task_id_before_reload = self.current_task_id()
|
||||
self.taskModel.reset_for_reload()
|
||||
self.set_loading()
|
||||
|
||||
def append_task_page(
|
||||
self, tasks: Iterable[TaskRow], has_more: bool
|
||||
) -> None:
|
||||
"""把事件层读取的一页任务追加到表格。"""
|
||||
|
||||
self.taskModel.append_tasks(tasks, has_more)
|
||||
self._update_empty_state()
|
||||
task_id = getattr(self, "_task_id_before_reload", "")
|
||||
self._restore_current_task(task_id)
|
||||
self._task_id_before_reload = ""
|
||||
|
||||
def set_loading(self) -> None:
|
||||
"""显示正在读取本地任务的状态。"""
|
||||
|
||||
@@ -472,6 +456,7 @@ class PDDTaskPage(QWidget):
|
||||
def set_load_error(self, message: str) -> None:
|
||||
"""显示本地任务加载失败状态,不清除当前筛选条件。"""
|
||||
|
||||
self.taskModel.finish_loading()
|
||||
self._show_empty_state(
|
||||
"任务加载失败",
|
||||
message or "无法读取本地任务,请检查日志后重试。",
|
||||
@@ -498,7 +483,7 @@ class PDDTaskPage(QWidget):
|
||||
self.statusLabel.setText(text or "自动获取:已停止 · 当前没有执行中的任务")
|
||||
|
||||
def clear_filters(self) -> None:
|
||||
"""清除全部搜索条件并恢复当前内存任务列表。"""
|
||||
"""清除全部搜索条件并重新查询本地任务。"""
|
||||
|
||||
self.taskTypeCombo.setCurrentIndex(0)
|
||||
self.statusCombo.setCurrentIndex(0)
|
||||
@@ -513,17 +498,11 @@ class PDDTaskPage(QWidget):
|
||||
return task.remote_task_id if task else ""
|
||||
|
||||
def _apply_filters(self) -> None:
|
||||
current_task_id = self.current_task_id()
|
||||
filters = {
|
||||
"task_type": self.taskTypeCombo.currentText(),
|
||||
"status": self.statusCombo.currentText(),
|
||||
"keyword": self.keywordInput.text().strip(),
|
||||
}
|
||||
self.taskModel.apply_filters(
|
||||
filters["task_type"], filters["status"], filters["keyword"]
|
||||
)
|
||||
self._update_empty_state()
|
||||
self._restore_current_task(current_task_id)
|
||||
self.searchRequested.emit(filters)
|
||||
|
||||
def _on_table_clicked(self, index: QModelIndex) -> None:
|
||||
|
||||
Reference in New Issue
Block a user