feat: 任务详情字段支持双击复制 (#145)

This commit is contained in:
chengma
2026-08-11 11:32:37 +08:00
parent eb5494cdf1
commit 07c5a85df7
3 changed files with 144 additions and 12 deletions
+98 -11
View File
@@ -7,9 +7,10 @@
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Mapping
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QCloseEvent, QKeySequence
from PyQt5.QtWidgets import (
QApplication,
QGridLayout,
QHBoxLayout,
QShortcut,
@@ -80,6 +81,63 @@ CURRENT_STEP_TEXT = {
}
class _CopyableLabelMixin:
"""让只读 Label 支持双击或 Ctrl+C 复制完整字段值。"""
copied = pyqtSignal(str, str)
def _init_copyable(self, field_name: str) -> None:
self._copy_field_name = field_name
self.setTextInteractionFlags(
Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard
)
self.setFocusPolicy(Qt.StrongFocus)
self.setCursor(Qt.IBeamCursor)
self.setToolTip(f"双击或按 Ctrl+C 复制{field_name}")
self.setAccessibleDescription(
f"可选择文本;双击或按 Ctrl+C 复制完整{field_name}"
)
def copy_full_text(self) -> None:
"""复制完整可见内容,并通知详情窗口显示反馈。"""
value = self.text()
QApplication.clipboard().setText(value)
self.copied.emit(self._copy_field_name, value)
def mouseDoubleClickEvent(self, event) -> None:
if event.button() == Qt.LeftButton:
self.copy_full_text()
event.accept()
return
super().mouseDoubleClickEvent(event)
def keyPressEvent(self, event) -> None:
if event.matches(QKeySequence.Copy):
self.copy_full_text()
event.accept()
return
super().keyPressEvent(event)
class CopyableBodyLabel(_CopyableLabelMixin, BodyLabel):
"""保持 BodyLabel 外观的可复制字段值。"""
def __init__(self, text: str, field_name: str, parent=None):
super().__init__(parent)
self.setText(text)
self._init_copyable(field_name)
class CopyableStrongBodyLabel(_CopyableLabelMixin, StrongBodyLabel):
"""保持强调文字外观的可复制字段值。"""
def __init__(self, text: str, field_name: str, parent=None):
super().__init__(parent)
self.setText(text)
self._init_copyable(field_name)
@dataclass(frozen=True)
class ColorPriceView:
"""一个颜色及其采集价格。"""
@@ -258,7 +316,7 @@ def build_task_detail_view_data(detail: TaskDetail) -> TaskDetailViewData:
class _ColorCard(CardWidget):
"""详情中的只读颜色卡片。"""
def __init__(self, color: ColorPriceView, parent=None):
def __init__(self, color: ColorPriceView, on_copied, parent=None):
super().__init__(parent)
self.setFocusPolicy(Qt.NoFocus)
self.setCursor(Qt.ArrowCursor)
@@ -267,8 +325,10 @@ class _ColorCard(CardWidget):
layout = QVBoxLayout(self)
layout.setContentsMargins(14, 10, 14, 10)
layout.setSpacing(3)
name = BodyLabel(color.text, self)
price = StrongBodyLabel(color.price_text, self)
name = CopyableBodyLabel(color.text, "颜色", self)
price = CopyableStrongBodyLabel(color.price_text, "颜色价格", self)
name.copied.connect(on_copied)
price.copied.connect(on_copied)
layout.addWidget(name)
layout.addWidget(price)
notes = []
@@ -284,13 +344,15 @@ class _ColorCard(CardWidget):
class _SizeCard(CardWidget):
"""详情中的只读尺码卡片。"""
def __init__(self, size: str, parent=None):
def __init__(self, size: str, on_copied, parent=None):
super().__init__(parent)
self.setFocusPolicy(Qt.NoFocus)
self.setCursor(Qt.ArrowCursor)
layout = QHBoxLayout(self)
layout.setContentsMargins(14, 8, 14, 8)
layout.addWidget(BodyLabel(size, self))
value = CopyableBodyLabel(size, "尺码", self)
value.copied.connect(on_copied)
layout.addWidget(value)
self.setAccessibleName(f"尺码 {size}")
@@ -304,6 +366,10 @@ class TaskDetailWindow(QWidget):
self.data = build_task_detail_view_data(detail)
self.colorItems: List[_ColorCard] = []
self.sizeItems: List[_SizeCard] = []
self._copy_feedback_timer = QTimer(self)
self._copy_feedback_timer.setSingleShot(True)
self._copy_feedback_timer.setInterval(2_000)
self._copy_feedback_timer.timeout.connect(self._reset_copy_feedback)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setObjectName("taskDetailWindow")
self.setWindowTitle(f"任务详情 · {self.data.task_id}")
@@ -347,7 +413,11 @@ class TaskDetailWindow(QWidget):
error_layout = QVBoxLayout(error_card)
error_layout.setContentsMargins(16, 14, 16, 14)
error_layout.addWidget(SubtitleLabel("最近错误", error_card))
error_layout.addWidget(BodyLabel(self.data.last_error, error_card))
error = self._copyable_label(
self.data.last_error, "最近错误", error_card
)
error.setWordWrap(True)
error_layout.addWidget(error)
content_layout.addWidget(error_card)
content_layout.addStretch(1)
scroll.setWidget(content)
@@ -355,6 +425,9 @@ class TaskDetailWindow(QWidget):
buttons = QHBoxLayout()
buttons.setContentsMargins(24, 12, 24, 0)
self.copyStatusLabel = CaptionLabel("双击字段值可复制", self)
self.copyStatusLabel.setAccessibleName("任务详情复制状态")
buttons.addWidget(self.copyStatusLabel)
buttons.addStretch(1)
close_button = PushButton("关闭", self)
close_button.setAccessibleName("关闭任务详情")
@@ -379,7 +452,7 @@ class TaskDetailWindow(QWidget):
)
for row, (label, value) in enumerate(rows):
name = CaptionLabel(label, card)
text = BodyLabel(value, card)
text = self._copyable_label(value, label, card)
text.setWordWrap(True)
layout.addWidget(name, row, 0, Qt.AlignTop)
layout.addWidget(text, row, 1)
@@ -405,12 +478,12 @@ class TaskDetailWindow(QWidget):
flow.setVerticalSpacing(8)
if title == "颜色":
for value in values:
item = _ColorCard(value, flow_host)
item = _ColorCard(value, self._on_field_copied, flow_host)
self.colorItems.append(item)
flow.addWidget(item)
else:
for value in values:
item = _SizeCard(value, flow_host)
item = _SizeCard(value, self._on_field_copied, flow_host)
self.sizeItems.append(item)
flow.addWidget(item)
layout.addWidget(flow_host)
@@ -431,10 +504,24 @@ class TaskDetailWindow(QWidget):
layout.addWidget(SubtitleLabel("采集信息", card), 0, 0, 1, 2)
for row, (label, value) in enumerate(values, start=1):
layout.addWidget(CaptionLabel(label, card), row, 0)
layout.addWidget(BodyLabel(value, card), row, 1)
layout.addWidget(self._copyable_label(value, label, card), row, 1)
layout.setColumnStretch(1, 1)
return card
def _copyable_label(
self, value: str, field_name: str, parent: QWidget
) -> CopyableBodyLabel:
label = CopyableBodyLabel(value, field_name, parent)
label.copied.connect(self._on_field_copied)
return label
def _on_field_copied(self, field_name: str, _value: str) -> None:
self.copyStatusLabel.setText(f"已复制:{field_name}")
self._copy_feedback_timer.start()
def _reset_copy_feedback(self) -> None:
self.copyStatusLabel.setText("双击字段值可复制")
def closeEvent(self, event: QCloseEvent) -> None:
self.closed.emit(self.data.task_id)
super().closeEvent(event)