feat: 优化应用详情和公告详情窗口的UI设计
重构应用详情窗口和公告详情窗口的UI布局,使用Fluent Design风格组件 添加卡片式布局和滚动区域,改进视觉层次和用户体验 更新主页应用列表为卡片式展示,增加点击查看详情功能
This commit is contained in:
@@ -6,7 +6,7 @@ LeonApp GUI - 基于PyQt5和Fluent Design的App Store API图形界面工具
|
||||
"""
|
||||
|
||||
# APP版本号
|
||||
APP_VERSION = "Beta 0.2"
|
||||
APP_VERSION = "Beta 0.3"
|
||||
|
||||
import sys
|
||||
import json
|
||||
@@ -18,17 +18,18 @@ import markdown
|
||||
from enum import Enum
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QTableWidgetItem, QTableWidget, QTextEdit, QFrame, QHeaderView
|
||||
QTableWidgetItem, QTableWidget, QTextEdit, QFrame, QHeaderView, QLabel
|
||||
)
|
||||
from PyQt5.QtGui import QTextOption
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QThread
|
||||
from qfluentwidgets import (
|
||||
CardWidget, TitleLabel, SubtitleLabel, CaptionLabel, PushButton,
|
||||
CardWidget, TitleLabel, SubtitleLabel, CaptionLabel, BodyLabel, PushButton,
|
||||
PrimaryPushButton, LineEdit, ComboBox, ProgressBar, TableWidget,
|
||||
ScrollArea, InfoBar, InfoBarPosition, NavigationInterface, NavigationItemPosition,
|
||||
FluentWindow, FluentIcon
|
||||
FluentWindow, FluentIcon, SimpleCardWidget
|
||||
)
|
||||
from qfluentwidgets import FluentTranslator
|
||||
from app_detail_window import AppDetailWindow
|
||||
|
||||
class APIClient:
|
||||
"""API客户端类,处理与API的通信"""
|
||||
@@ -122,14 +123,43 @@ class HomepageTab(QWidget):
|
||||
apps_layout = QVBoxLayout(self.latest_apps_card)
|
||||
apps_layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
# 创建最新应用表格
|
||||
self.latest_apps_table = TableWidget()
|
||||
self.latest_apps_table.setColumnCount(4)
|
||||
self.latest_apps_table.setHorizontalHeaderLabels(["ID", "应用名称", "版本", "添加时间"])
|
||||
self.latest_apps_table.horizontalHeader().setSectionResizeMode(1, 3)
|
||||
self.latest_apps_table.cellDoubleClicked.connect(self.show_app_detail)
|
||||
self.latest_apps_table.setMaximumHeight(250)
|
||||
apps_layout.addWidget(self.latest_apps_table)
|
||||
# 创建水平滚动区域来放置应用卡片 - 设置为隐形样式
|
||||
self.apps_scroll_area = ScrollArea()
|
||||
self.apps_scroll_area.setWidgetResizable(True)
|
||||
self.apps_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.apps_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
|
||||
# 设置隐形样式
|
||||
self.apps_scroll_area.setStyleSheet("""
|
||||
QScrollArea {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
QScrollArea > QWidget > QWidget {
|
||||
background-color: transparent;
|
||||
}
|
||||
QScrollBar:horizontal {
|
||||
height: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
QScrollBar::handle:horizontal {
|
||||
background: transparent;
|
||||
min-width: 0px;
|
||||
}
|
||||
""")
|
||||
|
||||
# 创建水平布局来放置卡片
|
||||
self.apps_container = QWidget()
|
||||
self.apps_layout = QHBoxLayout(self.apps_container)
|
||||
self.apps_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.apps_layout.setSpacing(15)
|
||||
|
||||
# 设置滚动区域的内容
|
||||
self.apps_scroll_area.setWidget(self.apps_container)
|
||||
self.apps_scroll_area.setMaximumHeight(1000)
|
||||
self.apps_scroll_area.setMaximumWidth(1000)
|
||||
self.apps_scroll_area.setMinimumWidth(1000)
|
||||
apps_layout.addWidget(self.apps_scroll_area)
|
||||
|
||||
|
||||
|
||||
@@ -152,11 +182,30 @@ class HomepageTab(QWidget):
|
||||
self.latest_announcements_list_layout = QVBoxLayout(self.latest_announcements_list)
|
||||
self.latest_announcements_list_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# 创建滚动区域放置公告列表
|
||||
# 创建滚动区域放置公告列表 - 设置为隐形样式
|
||||
scroll_area = ScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
|
||||
# 设置隐形样式
|
||||
scroll_area.setStyleSheet("""
|
||||
QScrollArea {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
QScrollArea > QWidget > QWidget {
|
||||
background-color: transparent;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
width: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background: transparent;
|
||||
min-height: 0px;
|
||||
}
|
||||
""")
|
||||
scroll_area.setWidget(self.latest_announcements_list)
|
||||
scroll_area.setMaximumHeight(250)
|
||||
scroll_area.setMaximumHeight(300)
|
||||
announcements_layout.addWidget(scroll_area)
|
||||
|
||||
|
||||
@@ -199,25 +248,118 @@ class HomepageTab(QWidget):
|
||||
|
||||
def on_latest_apps_loaded(self, data):
|
||||
"""最新应用加载完成处理"""
|
||||
# 清空表格
|
||||
self.latest_apps_table.setRowCount(0)
|
||||
# 清空现有卡片
|
||||
while self.apps_layout.count() > 0:
|
||||
item = self.apps_layout.takeAt(0)
|
||||
widget = item.widget()
|
||||
if widget:
|
||||
widget.deleteLater()
|
||||
|
||||
if data and isinstance(data, dict) and 'apps' in data and isinstance(data['apps'], list):
|
||||
# 填充表格
|
||||
# 为每个应用创建卡片
|
||||
index = 0
|
||||
for app in data['apps']:
|
||||
if not isinstance(app, dict):
|
||||
continue
|
||||
|
||||
# 只显示前三个应用
|
||||
if index >= 3:
|
||||
break
|
||||
index += 1
|
||||
|
||||
row_pos = self.latest_apps_table.rowCount()
|
||||
self.latest_apps_table.insertRow(row_pos)
|
||||
# 创建卡片 - 微软商店风格
|
||||
app_card = CardWidget()
|
||||
app_card.setFixedWidth(160) # 固定卡片宽度
|
||||
app_card.setFixedHeight(220) # 固定卡片高度
|
||||
# 使用样式表设置圆角并移除边框
|
||||
app_card.setStyleSheet("""
|
||||
QWidget {
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
}
|
||||
""")
|
||||
|
||||
self.latest_apps_table.setItem(row_pos, 0, QTableWidgetItem(str(app.get('id', ''))))
|
||||
self.latest_apps_table.setItem(row_pos, 1, QTableWidgetItem(app.get('name', '')))
|
||||
self.latest_apps_table.setItem(row_pos, 2, QTableWidgetItem(app.get('version', '')))
|
||||
self.latest_apps_table.setItem(row_pos, 3, QTableWidgetItem(app.get('created_at', '')))
|
||||
# 创建卡片内容布局
|
||||
card_layout = QVBoxLayout(app_card)
|
||||
card_layout.setContentsMargins(12, 12, 12, 12)
|
||||
card_layout.setSpacing(8) # 调整间距
|
||||
|
||||
# 添加应用ID(隐藏)
|
||||
app_id_label = QLabel(str(app.get('id', '')))
|
||||
app_id_label.setVisible(False)
|
||||
card_layout.addWidget(app_id_label)
|
||||
|
||||
# 添加应用图标占位区 - 微软商店风格的图标位置
|
||||
icon_widget = QWidget()
|
||||
icon_widget.setFixedSize(100, 100)
|
||||
icon_layout = QVBoxLayout(icon_widget)
|
||||
icon_layout.setAlignment(Qt.AlignCenter)
|
||||
|
||||
# 创建图标占位符(实际应用中可以替换为真实图标)
|
||||
placeholder_label = QLabel()
|
||||
placeholder_label.setFixedSize(80, 80)
|
||||
placeholder_label.setStyleSheet("""
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 12px;
|
||||
font-size: 32px;
|
||||
color: #666;
|
||||
""")
|
||||
placeholder_label.setAlignment(Qt.AlignCenter)
|
||||
# 使用应用名称的第一个字符作为图标占位符
|
||||
first_char = app.get('name', 'A')[0].upper() if app.get('name') else 'A'
|
||||
placeholder_label.setText(first_char)
|
||||
|
||||
icon_layout.addWidget(placeholder_label)
|
||||
card_layout.addWidget(icon_widget, alignment=Qt.AlignCenter)
|
||||
|
||||
# 添加应用名称 - 限制为2行
|
||||
name_label = SubtitleLabel(app.get('name', '无名称'))
|
||||
name_label.setWordWrap(True)
|
||||
name_label.setMaximumHeight(40) # 限制高度,最多显示2行
|
||||
name_label.setStyleSheet("""
|
||||
QLabel {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
""")
|
||||
card_layout.addWidget(name_label)
|
||||
|
||||
# 添加应用版本和添加时间(合并显示)
|
||||
version = app.get('version', '未知')
|
||||
created_at = app.get('created_at', '未知')
|
||||
# 简化时间显示
|
||||
if created_at != '未知':
|
||||
try:
|
||||
# 假设时间格式为ISO格式,提取日期部分
|
||||
if 'T' in created_at:
|
||||
created_at = created_at.split('T')[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
info_label = CaptionLabel(f"版本 {version} · {created_at}")
|
||||
info_label.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
card_layout.addWidget(info_label)
|
||||
|
||||
# 添加获取按钮(模拟微软商店的获取按钮)- 使用PrimaryPushButton获得标准的蓝色按钮
|
||||
get_button = PrimaryPushButton("查看详情")
|
||||
get_button.setFixedHeight(32)
|
||||
|
||||
get_button.clicked.connect(lambda checked, app_id=app.get('id', ''): self.show_app_detail(app_id))
|
||||
card_layout.addWidget(get_button)
|
||||
|
||||
# 添加点击事件到整个卡片
|
||||
app_card.mousePressEvent = lambda event, app_id=app.get('id', ''): self.show_app_detail(app_id)
|
||||
|
||||
# 将卡片添加到水平布局
|
||||
self.apps_layout.addWidget(app_card)
|
||||
|
||||
# 自动调整列宽
|
||||
self.latest_apps_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
# 添加一个占位符,确保卡片不会被拉伸
|
||||
self.apps_layout.addStretch()
|
||||
|
||||
# 检查是否还有其他数据正在加载
|
||||
if not self.apps_worker.isRunning() and not self.announcements_worker.isRunning():
|
||||
@@ -290,12 +432,11 @@ class HomepageTab(QWidget):
|
||||
if not self.apps_worker.isRunning() and not self.announcements_worker.isRunning():
|
||||
self.hide_progress()
|
||||
|
||||
def show_app_detail(self, row, column):
|
||||
def show_app_detail(self, app_id):
|
||||
"""显示应用详情"""
|
||||
app_id = self.latest_apps_table.item(row, 0).text()
|
||||
# 调用父窗口的show_app_detail方法
|
||||
if hasattr(self.parent(), 'show_app_detail'):
|
||||
self.parent().show_app_detail(app_id)
|
||||
# 直接创建并显示应用详情窗口
|
||||
detail_window = AppDetailWindow(self.api_client, app_id, self)
|
||||
detail_window.show()
|
||||
|
||||
def show_announcement_detail(self, announcement_id):
|
||||
"""显示公告详情方法已被移除,现在主页直接显示最新公告内容"""
|
||||
@@ -1552,12 +1693,22 @@ class AppDetailWindow(QMainWindow):
|
||||
description_label = SubtitleLabel("描述")
|
||||
self.info_layout.addWidget(description_label)
|
||||
|
||||
# 使用QTextEdit替代CaptionLabel以支持富文本显示
|
||||
description_text = QTextEdit()
|
||||
# 使用QFluentWidgets的TextEdit支持Markdown显示
|
||||
from qfluentwidgets import TextEdit
|
||||
description_text = TextEdit()
|
||||
description_text.setReadOnly(True)
|
||||
description_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||
description_text.setLineWrapMode(QTextEdit.WidgetWidth)
|
||||
description_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
# 设置Fluent风格
|
||||
description_text.setStyleSheet("""
|
||||
TextEdit {
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
# 尝试将markdown格式的描述转换为HTML
|
||||
app_description = data.get('description', '暂无描述')
|
||||
@@ -1982,6 +2133,18 @@ class AnnouncementDetailWindow(QMainWindow):
|
||||
self.title = title
|
||||
self.created_at = created_at
|
||||
|
||||
# 设置窗口属性
|
||||
self.setWindowTitle(f"公告详情 - {title}")
|
||||
self.resize(700, 550)
|
||||
self.setObjectName("AnnouncementDetailWindow")
|
||||
|
||||
# 添加全局样式
|
||||
self.setStyleSheet("""
|
||||
#AnnouncementDetailWindow {
|
||||
background-color: #F2F3F5;
|
||||
}
|
||||
""")
|
||||
|
||||
# 将Markdown内容转换为HTML
|
||||
try:
|
||||
import markdown
|
||||
@@ -1992,8 +2155,6 @@ class AnnouncementDetailWindow(QMainWindow):
|
||||
print(f"Markdown转换失败: {str(e)}")
|
||||
|
||||
self.content = content
|
||||
self.setWindowTitle(f"公告详情 - {title}")
|
||||
self.resize(600, 500)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
@@ -2005,15 +2166,66 @@ class AnnouncementDetailWindow(QMainWindow):
|
||||
# 创建主布局
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
main_layout.setSpacing(12)
|
||||
|
||||
# 创建标题
|
||||
title_label = TitleLabel(f"{self.title}")
|
||||
main_layout.addWidget(title_label)
|
||||
self.title_label = TitleLabel(f"{self.title}")
|
||||
self.title_label.setObjectName("AnnouncementTitle")
|
||||
main_layout.addWidget(self.title_label)
|
||||
|
||||
# 创建滚动区域 - 使用QFluentWidgets的ScrollArea
|
||||
self.scroll_area = ScrollArea()
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setFrameShape(0)
|
||||
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
|
||||
# 设置滚动区域样式
|
||||
self.scroll_area.setStyleSheet("""
|
||||
ScrollArea {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
width: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background: rgba(142, 142, 147, 0.3);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background: rgba(142, 142, 147, 0.5);
|
||||
}
|
||||
""")
|
||||
|
||||
# 创建滚动内容部件
|
||||
self.scroll_content = QWidget()
|
||||
self.scroll_layout = QVBoxLayout(self.scroll_content)
|
||||
self.scroll_layout.setContentsMargins(0, 0, 0, 20)
|
||||
self.scroll_layout.setSpacing(16)
|
||||
|
||||
# 添加滚动区域到主布局
|
||||
self.scroll_area.setWidget(self.scroll_content)
|
||||
main_layout.addWidget(self.scroll_area)
|
||||
|
||||
# 创建信息卡片
|
||||
self.info_card = CardWidget()
|
||||
self.info_layout = QVBoxLayout(self.info_card)
|
||||
main_layout.addWidget(self.info_card)
|
||||
self.create_info_card()
|
||||
|
||||
# 创建内容卡片
|
||||
self.create_content_card()
|
||||
|
||||
# 添加关闭按钮
|
||||
self.create_close_button(main_layout)
|
||||
|
||||
def create_info_card(self):
|
||||
"""创建公告信息卡片"""
|
||||
info_card = SimpleCardWidget()
|
||||
info_card.setObjectName("InfoCard")
|
||||
|
||||
card_layout = QHBoxLayout(info_card)
|
||||
card_layout.setContentsMargins(16, 12, 16, 12)
|
||||
card_layout.setSpacing(20)
|
||||
|
||||
# 添加公告信息
|
||||
info_items = [
|
||||
@@ -2022,33 +2234,68 @@ class AnnouncementDetailWindow(QMainWindow):
|
||||
]
|
||||
|
||||
for label_text, value_text in info_items:
|
||||
label = CaptionLabel(f"{label_text}: {value_text}")
|
||||
self.info_layout.addWidget(label)
|
||||
info_layout = QVBoxLayout()
|
||||
label = CaptionLabel(label_text)
|
||||
value = BodyLabel(value_text)
|
||||
|
||||
info_layout.addWidget(label)
|
||||
info_layout.addWidget(value)
|
||||
card_layout.addLayout(info_layout)
|
||||
|
||||
# 添加分隔线
|
||||
separator = QWidget()
|
||||
separator.setMinimumHeight(10)
|
||||
self.info_layout.addWidget(separator)
|
||||
card_layout.addStretch()
|
||||
self.scroll_layout.addWidget(info_card)
|
||||
|
||||
def create_content_card(self):
|
||||
"""创建公告内容卡片"""
|
||||
content_card = CardWidget()
|
||||
content_card.setObjectName("ContentCard")
|
||||
|
||||
card_layout = QVBoxLayout(content_card)
|
||||
card_layout.setContentsMargins(16, 16, 16, 16)
|
||||
card_layout.setSpacing(12)
|
||||
|
||||
# 添加内容标题
|
||||
content_title = SubtitleLabel("公告内容")
|
||||
self.info_layout.addWidget(content_title)
|
||||
card_layout.addWidget(content_title)
|
||||
|
||||
# 添加内容文本框,支持Markdown渲染
|
||||
self.content_text = QTextEdit()
|
||||
from qfluentwidgets import TextEdit
|
||||
self.content_text = TextEdit()
|
||||
self.content_text.setHtml(self.content_html)
|
||||
self.content_text.setReadOnly(True)
|
||||
self.content_text.setMinimumHeight(200)
|
||||
self.content_text.setMinimumHeight(250)
|
||||
self.content_text.setLineWrapMode(QTextEdit.WidgetWidth)
|
||||
self.content_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||
self.info_layout.addWidget(self.content_text)
|
||||
|
||||
# 添加关闭按钮
|
||||
button_layout = QHBoxLayout()
|
||||
# 设置Fluent风格样式
|
||||
self.content_text.setStyleSheet("""
|
||||
TextEdit {
|
||||
background-color: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
""")
|
||||
|
||||
card_layout.addWidget(self.content_text)
|
||||
self.scroll_layout.addWidget(content_card)
|
||||
|
||||
def create_close_button(self, parent_layout):
|
||||
"""创建关闭按钮"""
|
||||
button_card = SimpleCardWidget()
|
||||
button_card.setObjectName("ButtonCard")
|
||||
button_card.setMinimumHeight(80)
|
||||
|
||||
button_layout = QHBoxLayout(button_card)
|
||||
button_layout.setContentsMargins(16, 16, 16, 16)
|
||||
button_layout.addStretch()
|
||||
|
||||
close_button = PushButton("关闭")
|
||||
close_button.setFixedWidth(100)
|
||||
close_button.clicked.connect(self.close)
|
||||
button_layout.addWidget(close_button, alignment=Qt.AlignCenter)
|
||||
main_layout.addLayout(button_layout)
|
||||
button_layout.addWidget(close_button)
|
||||
|
||||
parent_layout.addWidget(button_card)
|
||||
|
||||
class DeveloperInfoWindow(QMainWindow):
|
||||
"""开发者信息窗口"""
|
||||
|
||||
Reference in New Issue
Block a user