cabdf6a1 by 柴进

feat(qml): task #16 历史记录 tab 完整重做(ListView + 详情面板)

core/history.py:
  HistoryListModel.roleNames() 暴露 4 个 Qt 内置 role 给 QML:
  display / decoration / toolTip / timestamp (UserRole)
  旧 QListView 用 int role 不受影响

bridges/history.py:
  Slot thumbnailPath(timestamp) → str
  返回 thumb.jpg 本地路径(按需用 PIL 生成 240px JPEG),缩略图缺失回退原图

qml_poc/qml/HistoryTab.qml (新):
  - 左侧 340px 卡片:列表 (history.model)
    * delegate 76px 高:60×60 缩略图 + timestamp + prompt 双行摘要
    * 选中态 accent 蓝边框 + accentSubtle 浅蓝底
    * hover 提示完整 toolTip
  - 右侧详情卡片:
    * timestamp + createdAt + "在文件管理器中打开" + "删除"
    * 360px 大图(双击系统查看器打开)
    * GridLayout 4 列元信息(宽高比/尺寸/模型)
    * prompt ScrollView(长文本可滚)
  - Component.onCompleted + onCountChanged 自动选中第一条
  - itemRemoved 信号 → 选中失效时回到空态

qml_poc/main_qml.py:
  AppState 加 QML_DEBUG_TAB env var (0/1/2) 控制启动 tab 索引,方便 PoC 调试

MainWindow.qml: 历史记录占位 Item → HistoryTab {}

视觉验证:QML_DEBUG_TAB=2 直接进历史 tab,2 条已存历史正确渲染(缩略图 +
详情大图 + 元信息 + prompt 都对),UI 无回归。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7cf6c18e
...@@ -83,3 +83,15 @@ class HistoryBridge(QObject): ...@@ -83,3 +83,15 @@ class HistoryBridge(QObject):
83 "model": item.model, 83 "model": item.model,
84 "createdAt": item.created_at.strftime("%Y-%m-%d %H:%M:%S"), 84 "createdAt": item.created_at.strftime("%Y-%m-%d %H:%M:%S"),
85 } 85 }
86
87 @Slot(str, result=str)
88 def thumbnailPath(self, timestamp: str) -> str:
89 """返回该 timestamp 缩略图本地路径(按需生成)。源图缺失时返回 ""。"""
90 item = self._history.load_history_item_fast(timestamp)
91 if item is None or not item.generated_image_path.exists():
92 return ""
93 thumb = self._history.get_or_create_thumbnail(item.generated_image_path)
94 if thumb is None:
95 # 缩略图生成失败时回退到原图(QML Image 读 PNG 没问题)
96 return str(item.generated_image_path)
97 return str(thumb)
......
...@@ -85,6 +85,19 @@ class HistoryListModel(QAbstractListModel): ...@@ -85,6 +85,19 @@ class HistoryListModel(QAbstractListModel):
85 return 0 85 return 0
86 return len(self._timestamps) 86 return len(self._timestamps)
87 87
88 def roleNames(self):
89 """暴露 Qt 内置 roles 给 QML(默认 QML 只能 model.display)。
90
91 QListView (旧 QWidget UI) 用 int role 不受影响;QML ListView delegate
92 现在能用 model.timestamp / model.toolTip 等访问。
93 """
94 return {
95 Qt.DisplayRole: b"display",
96 Qt.DecorationRole: b"decoration",
97 Qt.ToolTipRole: b"toolTip",
98 Qt.UserRole: b"timestamp",
99 }
100
88 def flags(self, index: QModelIndex): 101 def flags(self, index: QModelIndex):
89 if not index.isValid(): 102 if not index.isValid():
90 return Qt.NoItemFlags 103 return Qt.NoItemFlags
......
...@@ -54,6 +54,10 @@ class AppState(QObject): ...@@ -54,6 +54,10 @@ class AppState(QObject):
54 self._auth = auth_bridge 54 self._auth = auth_bridge
55 # 兼容现有 QML:env QML_AUTO_LOGIN=1 时强制 loggedIn=True 55 # 兼容现有 QML:env QML_AUTO_LOGIN=1 时强制 loggedIn=True
56 self._poc_force_login = os.environ.get("QML_AUTO_LOGIN", "") == "1" 56 self._poc_force_login = os.environ.get("QML_AUTO_LOGIN", "") == "1"
57 # 调试便捷:env QML_DEBUG_TAB=0/1/2 控制启动时默认 tab
58 try:
59 self._current_tab = int(os.environ.get("QML_DEBUG_TAB", "0"))
60 except ValueError:
57 self._current_tab = 0 61 self._current_tab = 0
58 self._auth.loggedInChanged.connect(self.loggedInChanged.emit) 62 self._auth.loggedInChanged.connect(self.loggedInChanged.emit)
59 63
......
...@@ -107,17 +107,7 @@ Rectangle { ...@@ -107,17 +107,7 @@ Rectangle {
107 } 107 }
108 } 108 }
109 109
110 // 历史记录 — 占位 110 HistoryTab {}
111 Item {
112 Text {
113 anchors.centerIn: parent
114 text: "历史记录 tab 内容\n(QML PoC 暂未实现)"
115 color: App.Theme.textTertiary
116 font.family: App.Theme.fontFamily
117 font.pointSize: App.Theme.fontLg
118 horizontalAlignment: Text.AlignHCenter
119 }
120 }
121 } 111 }
122 } 112 }
123 113
......