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):
"model": item.model,
"createdAt": item.created_at.strftime("%Y-%m-%d %H:%M:%S"),
}
@Slot(str, result=str)
def thumbnailPath(self, timestamp: str) -> str:
"""返回该 timestamp 缩略图本地路径(按需生成)。源图缺失时返回 ""。"""
item = self._history.load_history_item_fast(timestamp)
if item is None or not item.generated_image_path.exists():
return ""
thumb = self._history.get_or_create_thumbnail(item.generated_image_path)
if thumb is None:
# 缩略图生成失败时回退到原图(QML Image 读 PNG 没问题)
return str(item.generated_image_path)
return str(thumb)
......
......@@ -85,6 +85,19 @@ class HistoryListModel(QAbstractListModel):
return 0
return len(self._timestamps)
def roleNames(self):
"""暴露 Qt 内置 roles 给 QML(默认 QML 只能 model.display)。
QListView (旧 QWidget UI) 用 int role 不受影响;QML ListView delegate
现在能用 model.timestamp / model.toolTip 等访问。
"""
return {
Qt.DisplayRole: b"display",
Qt.DecorationRole: b"decoration",
Qt.ToolTipRole: b"toolTip",
Qt.UserRole: b"timestamp",
}
def flags(self, index: QModelIndex):
if not index.isValid():
return Qt.NoItemFlags
......
......@@ -54,7 +54,11 @@ class AppState(QObject):
self._auth = auth_bridge
# 兼容现有 QML:env QML_AUTO_LOGIN=1 时强制 loggedIn=True
self._poc_force_login = os.environ.get("QML_AUTO_LOGIN", "") == "1"
self._current_tab = 0
# 调试便捷:env QML_DEBUG_TAB=0/1/2 控制启动时默认 tab
try:
self._current_tab = int(os.environ.get("QML_DEBUG_TAB", "0"))
except ValueError:
self._current_tab = 0
self._auth.loggedInChanged.connect(self.loggedInChanged.emit)
@Property(bool, notify=loggedInChanged)
......
......@@ -107,17 +107,7 @@ Rectangle {
}
}
// 历史记录 — 占位
Item {
Text {
anchors.centerIn: parent
text: "历史记录 tab 内容\n(QML PoC 暂未实现)"
color: App.Theme.textTertiary
font.family: App.Theme.fontFamily
font.pointSize: App.Theme.fontLg
horizontalAlignment: Text.AlignHCenter
}
}
HistoryTab {}
}
}
......