diff --git a/client/src/pdd_ui.py b/client/src/pdd_ui.py index e6332fb..bc1fc40 100644 --- a/client/src/pdd_ui.py +++ b/client/src/pdd_ui.py @@ -28,7 +28,7 @@ from dataclasses import dataclass from typing import Iterable, Optional -from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal +from PyQt5.QtCore import QAbstractTableModel, QEvent, QModelIndex, Qt, pyqtSignal from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QAbstractItemView, @@ -36,7 +36,6 @@ from PyQt5.QtWidgets import ( QHeaderView, QHBoxLayout, QShortcut, - QStackedWidget, QVBoxLayout, QWidget, ) @@ -102,9 +101,12 @@ class TaskTableModel(QAbstractTableModel): self._keyword = "" self._sort_column = 7 self._sort_order = Qt.DescendingOrder + self._placeholder_row_count = 1 def rowCount(self, parent=QModelIndex()) -> int: - return 0 if parent.isValid() else len(self._rows) + if parent.isValid(): + return 0 + return len(self._rows) or self._placeholder_row_count def columnCount(self, parent=QModelIndex()) -> int: return 0 if parent.isValid() else len(self.HEADERS) @@ -143,7 +145,7 @@ class TaskTableModel(QAbstractTableModel): return None def flags(self, index): - if not index.isValid(): + if not index.isValid() or index.row() >= len(self._rows): return Qt.NoItemFlags return Qt.ItemIsEnabled | Qt.ItemIsSelectable @@ -214,6 +216,26 @@ class TaskTableModel(QAbstractTableModel): def filtered_count(self) -> int: return len(self._filtered_rows) + def data_row_count(self) -> int: + """返回真实任务行数,不包含填满空表格的占位行。""" + + return len(self._rows) + + def set_placeholder_row_count(self, count: int) -> None: + """设置无数据时显示的禁用空行数量。""" + + count = max(1, count) + if count == self._placeholder_row_count: + return + + if self._rows: + self._placeholder_row_count = count + return + + self.beginResetModel() + 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) @@ -372,35 +394,30 @@ class PDDTaskPage(QWidget): self.taskTable.setColumnWidth(7, 156) self.taskTable.setColumnWidth(8, 72) self.taskTable.sortByColumn(7, Qt.DescendingOrder) + self.taskTable.viewport().installEventFilter(self) - self.emptyPage = QWidget(self) - emptyLayout = QVBoxLayout(self.emptyPage) - emptyLayout.setContentsMargins(32, 32, 32, 32) - emptyLayout.addStretch(1) - self.emptyTitleLabel = SubtitleLabel("还没有领取过任务", self.emptyPage) - self.emptyTitleLabel.setAlignment(Qt.AlignCenter) + self.emptyStateCard = CardWidget(self) + emptyLayout = QHBoxLayout(self.emptyStateCard) + emptyLayout.setContentsMargins(16, 10, 16, 10) + + emptyTextLayout = QVBoxLayout() + emptyTextLayout.setSpacing(2) + self.emptyTitleLabel = SubtitleLabel( + "还没有领取过任务", self.emptyStateCard + ) self.emptyMessageLabel = BodyLabel( - "点击“开始自动获取”开始领取任务。", self.emptyPage + "点击“开始自动获取”开始领取任务。", self.emptyStateCard ) - self.emptyMessageLabel.setAlignment(Qt.AlignCenter) self.emptyMessageLabel.setWordWrap(True) - self.clearFiltersButton = PushButton("清除筛选条件", self.emptyPage) + emptyTextLayout.addWidget(self.emptyTitleLabel) + emptyTextLayout.addWidget(self.emptyMessageLabel) + + self.clearFiltersButton = PushButton( + "清除筛选条件", self.emptyStateCard + ) self.clearFiltersButton.setAccessibleName("清除全部任务筛选条件") - - clearButtonLayout = QHBoxLayout() - clearButtonLayout.addStretch(1) - clearButtonLayout.addWidget(self.clearFiltersButton) - clearButtonLayout.addStretch(1) - - emptyLayout.addWidget(self.emptyTitleLabel) - emptyLayout.addWidget(self.emptyMessageLabel) - emptyLayout.addSpacing(8) - emptyLayout.addLayout(clearButtonLayout) - emptyLayout.addStretch(1) - - self.contentStack = QStackedWidget(self) - self.contentStack.addWidget(self.taskTable) - self.contentStack.addWidget(self.emptyPage) + emptyLayout.addLayout(emptyTextLayout, 1) + emptyLayout.addWidget(self.clearFiltersButton) def _build_status_area(self) -> None: self.statusCard = CardWidget(self) @@ -419,7 +436,8 @@ class PDDTaskPage(QWidget): layout.setSpacing(16) layout.addWidget(TitleLabel("PDD 任务", self)) layout.addWidget(self.commandCard) - layout.addWidget(self.contentStack, 1) + layout.addWidget(self.emptyStateCard) + layout.addWidget(self.taskTable, 1) layout.addWidget(self.statusCard) def _connect_ui_events(self) -> None: @@ -434,6 +452,11 @@ class PDDTaskPage(QWidget): self.findShortcut = QShortcut(QKeySequence.Find, self) self.findShortcut.activated.connect(self.keywordInput.setFocus) + def eventFilter(self, watched, event): + if watched is self.taskTable.viewport() and event.type() == QEvent.Resize: + self._update_placeholder_rows() + return super().eventFilter(watched, event) + def set_tasks(self, tasks: Iterable[TaskRow]) -> None: """显示任务数据,并尽量恢复原来选中的稳定任务编号。""" @@ -537,8 +560,8 @@ class PDDTaskPage(QWidget): ) def _update_empty_state(self) -> None: - if self.taskModel.rowCount() > 0: - self.contentStack.setCurrentWidget(self.taskTable) + if self.taskModel.data_row_count() > 0: + self.emptyStateCard.hide() return if self._has_active_filters(): @@ -561,4 +584,13 @@ class PDDTaskPage(QWidget): self.emptyTitleLabel.setText(title) self.emptyMessageLabel.setText(message) self.clearFiltersButton.setVisible(show_clear_button) - self.contentStack.setCurrentWidget(self.emptyPage) + self.emptyStateCard.show() + self._update_placeholder_rows() + + def _update_placeholder_rows(self) -> None: + """用不可交互的空行填满无数据时的表格可视区域。""" + + row_height = max(1, self.taskTable.verticalHeader().defaultSectionSize()) + viewport_height = max(row_height, self.taskTable.viewport().height()) + row_count = max(1, viewport_height // row_height) + self.taskModel.set_placeholder_row_count(row_count)