1.更新UI

This commit is contained in:
陈横 2025-10-15 15:21:27 +08:00
parent 76a6ac691d
commit 086301e375
7 changed files with 867 additions and 1843 deletions

View File

@ -13,7 +13,8 @@ class IconManager:
"""图标管理器"""
# 图标目录路径
ICONS_DIR = "Resources/Icons"
# ICONS_DIR = "Resources/Icons"
ICONS_DIR = os.path.join(os.path.dirname(__file__), "..", "Resources", "Icons")
# 图标文件映射
ICON_FILES = {
@ -41,8 +42,13 @@ class IconManager:
'system_settings': 'system.png',
# 树箭头
'down': 'down.png',
'right': 'right.png',
'down': 'solid_down_arrows.png',
'right': 'solid_right_arrows.png',
'search': 'search_icon.png',
'infomation': 'infomation_icon.png',
'project_empty_icon': 'project_empty_icon.png',
}
# 图标缓存
@ -137,6 +143,8 @@ class IconManager:
bool: 图标文件是否存在
"""
icon_path = cls.get_icon_path(icon_name)
print("icon_path", icon_path)
print("os.path.exists(icon_path)", os.path.exists(icon_path))
return icon_path and os.path.exists(icon_path)
@classmethod

View File

@ -28,11 +28,11 @@ class MainWindow(QMainWindow):
self.init_ui()
self.connect_signals()
# 设置窗口属性
# 设置窗口属性 - 匹配 Figma 设计规范
self.setWindowTitle("MetaCore - 项目管理平台")
self.setWindowIcon(IconManager.get_icon('app'))
self.setMinimumSize(1000, 700)
self.resize(1400, 900)
self.setMinimumSize(1200, 800) # 增加最小尺寸,确保良好的用户体验
self.resize(1440, 960) # 调整默认尺寸,匹配现代显示器比例
# 响应式布局标志
self.is_compact_mode = False
@ -201,22 +201,22 @@ class MainWindow(QMainWindow):
"""窗口大小变化事件"""
super().resizeEvent(event)
# 检查是否需要切换到紧凑模式
# 检查是否需要切换到紧凑模式 - 匹配 Figma 响应式断点
width = event.size().width()
should_be_compact = width < 1200
should_be_compact = width < 1400 # 调整断点,匹配现代设计标准
if should_be_compact != self.is_compact_mode:
self.is_compact_mode = should_be_compact
self.update_layout_mode()
def update_layout_mode(self):
"""更新布局模式"""
"""更新布局模式 - 匹配 Figma 响应式设计"""
if self.is_compact_mode:
# 紧凑模式:缩小侧边栏
self.sidebar.setFixedWidth(240)
# 紧凑模式:缩小侧边栏,匹配小屏幕设计
self.sidebar.setFixedWidth(260)
else:
# 正常模式:标准侧边栏宽度
self.sidebar.setFixedWidth(280)
# 正常模式:标准侧边栏宽度,匹配 Figma 设计
self.sidebar.setFixedWidth(300)
def on_filter_changed(self, filter_type: str):
"""过滤器改变"""

View File

@ -23,9 +23,13 @@ class ProjectArea(QWidget):
def __init__(self, project_manager: ProjectManager):
super().__init__()
self.setObjectName("projectArea")
self.setAttribute(Qt.WA_StyledBackground, True)
self.project_manager = project_manager
self.view_mode = "grid" # grid 或 list
self.projects = []
self.grid_view_btn = None
self.list_view_btn = None
self.init_ui()
self.connect_signals()
@ -36,34 +40,40 @@ class ProjectArea(QWidget):
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setContentsMargins(0, 30, 24, 30)
layout.setSpacing(0)
# 主内容容器,方便区分背景颜色
self.content_widget = QWidget()
self.content_widget.setObjectName("projectAreaContent")
self.content_layout = QVBoxLayout(self.content_widget)
self.content_layout.setContentsMargins(0, 0, 0, 0)
self.content_layout.setSpacing(0)
layout.addWidget(self.content_widget)
# 顶部工具栏
self.create_toolbar(layout)
# 搜索和过滤区域
self.create_search_filter_area(layout)
self.create_toolbar(self.content_layout)
# 项目显示区域
self.create_project_display_area(layout)
self.create_project_display_area(self.content_layout)
def create_toolbar(self, layout):
"""创建顶部工具"""
toolbar_widget = QWidget()
toolbar_widget.setObjectName("toolbar")
toolbar_layout = QHBoxLayout(toolbar_widget)
toolbar_layout.setContentsMargins(30, 20, 30, 10)
# 面包屑导航容器
"""创建顶部工具区域"""
header_widget = QWidget()
header_widget.setObjectName("contentHeader")
header_layout = QHBoxLayout(header_widget)
header_layout.setContentsMargins(36, 26, 36, 28)
header_layout.setSpacing(16)
breadcrumb_container = QWidget()
breadcrumb_container.setObjectName("breadcrumbContainer")
breadcrumb_layout = QHBoxLayout(breadcrumb_container)
breadcrumb_layout.setContentsMargins(0, 0, 0, 0)
breadcrumb_layout.setSpacing(0)
breadcrumb_layout.setSpacing(8)
self.home_label = QLabel("我的项目")
self.home_label.setObjectName("breadcrumbItem")
self.home_label.setObjectName("breadcrumbBase")
breadcrumb_layout.addWidget(self.home_label)
self.separator_label = QLabel("/")
@ -74,106 +84,27 @@ class ProjectArea(QWidget):
self.current_label.setObjectName("breadcrumbCurrent")
breadcrumb_layout.addWidget(self.current_label)
breadcrumb_layout.addStretch()
toolbar_layout.addWidget(breadcrumb_container)
# 右侧操作按钮
actions_layout = QHBoxLayout()
# 导入项目按钮
self.import_project_btn = QPushButton("导入项目")
if IconManager.icon_exists('import'):
self.import_project_btn.setIcon(IconManager.get_icon('import'))
else:
self.import_project_btn = QPushButton("↑ 导入项目")
self.import_project_btn.setObjectName("importBtn")
self.import_project_btn.clicked.connect(self.import_project_requested.emit)
actions_layout.addWidget(self.import_project_btn)
# 创建项目按钮
self.create_project_btn = QPushButton("创建")
if IconManager.icon_exists('create'):
self.create_project_btn.setIcon(IconManager.get_icon('create'))
else:
self.create_project_btn = QPushButton("+ 创建")
self.create_project_btn.setObjectName("primaryBtn")
self.create_project_btn.clicked.connect(self.create_project_requested.emit)
actions_layout.addWidget(self.create_project_btn)
toolbar_layout.addLayout(actions_layout)
layout.addWidget(toolbar_widget)
def create_search_filter_area(self, layout):
"""创建搜索和过滤区域"""
# 内容头部 - 调整高度更紧凑
content_header = QWidget()
content_header.setObjectName("contentHeader")
header_layout = QHBoxLayout(content_header)
header_layout.setContentsMargins(30, 12, 30, 12) # 减少上下边距20->12
# 标题
self.title_label = QLabel("项目概述")
self.title_label.setObjectName("contentTitle")
header_layout.addWidget(self.title_label)
header_layout.addWidget(breadcrumb_container)
header_layout.addStretch()
# 搜索输入框
self.search_input = QLineEdit()
self.search_input.setObjectName("searchInput")
self.search_input.setPlaceholderText("搜索项目...")
self.search_input.setClearButtonEnabled(True)
if IconManager.icon_exists('search'):
search_action = self.search_input.addAction(IconManager.get_icon('search', QSize(30, 30)), QLineEdit.TrailingPosition)
self.search_input.setFixedWidth(508)
self.search_input.textChanged.connect(self.search_changed.emit)
self.search_input.setMaximumWidth(250) # 限制最大宽度
header_layout.addWidget(self.search_input)
header_layout.addWidget(self.search_input, 0, Qt.AlignRight)
# 视图控制按钮
view_controls = QHBoxLayout()
# 网格视图按钮
self.grid_view_btn = QPushButton()
if IconManager.icon_exists('grid_view'):
self.grid_view_btn.setIcon(IconManager.get_icon('grid_view', QSize(16, 16)))
self.grid_view_btn.setIconSize(QSize(16, 16))
else:
self.grid_view_btn.setText("")
self.grid_view_btn.setObjectName("viewBtn")
self.grid_view_btn.setCheckable(True)
self.grid_view_btn.setChecked(True)
self.grid_view_btn.setToolTip("网格视图")
self.grid_view_btn.clicked.connect(lambda: self.set_view_mode("grid"))
view_controls.addWidget(self.grid_view_btn)
layout.addWidget(header_widget)
# 列表视图按钮
self.list_view_btn = QPushButton()
if IconManager.icon_exists('list_view'):
self.list_view_btn.setIcon(IconManager.get_icon('list_view', QSize(16, 16)))
self.list_view_btn.setIconSize(QSize(16, 16))
else:
self.list_view_btn.setText("")
self.list_view_btn.setObjectName("viewBtn")
self.list_view_btn.setCheckable(True)
self.list_view_btn.setToolTip("列表视图")
self.list_view_btn.clicked.connect(lambda: self.set_view_mode("list"))
view_controls.addWidget(self.list_view_btn)
# # 过滤按钮
# filter_btn = QPushButton("▼")
# filter_btn.setObjectName("viewBtn")
# filter_btn.setToolTip("过滤选项")
# view_controls.addWidget(filter_btn)
header_layout.addLayout(view_controls)
layout.addWidget(content_header)
def create_project_display_area(self, layout):
"""创建项目显示区域"""
# 滚动区域
self.scroll_area = QScrollArea()
self.scroll_area.setObjectName("projectScrollArea")
self.scroll_area.setFrameShape(QFrame.NoFrame)
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
@ -184,8 +115,8 @@ class ProjectArea(QWidget):
# 网格布局
self.projects_layout = QGridLayout(self.projects_container)
self.projects_layout.setContentsMargins(30, 20, 30, 30)
self.projects_layout.setSpacing(20)
self.projects_layout.setContentsMargins(36, 18, 6, 18)
self.projects_layout.setSpacing(24)
self.projects_layout.setAlignment(Qt.AlignTop) # 设置顶部对齐
# 响应式列数
@ -211,10 +142,8 @@ class ProjectArea(QWidget):
"""更新导航显示"""
# 更新面包屑导航
self.home_label.setText(section)
self.current_label.setText(item_name)
# 更新主标题
# 根据不同的导航项设置不同的标题
# 根据不同的导航项设置显示标题
title_mapping = {
"项目概述": "项目概述",
"项目管理": "项目管理",
@ -224,7 +153,7 @@ class ProjectArea(QWidget):
}
display_title = title_mapping.get(item_name, item_name)
self.title_label.setText(display_title)
self.current_label.setText(display_title)
def refresh_project_display(self):
"""刷新项目显示"""
@ -290,8 +219,10 @@ class ProjectArea(QWidget):
self.view_mode = mode
# 更新按钮状态
self.grid_view_btn.setChecked(mode == "grid")
self.list_view_btn.setChecked(mode == "list")
if self.grid_view_btn:
self.grid_view_btn.setChecked(mode == "grid")
if self.list_view_btn:
self.list_view_btn.setChecked(mode == "list")
# 刷新显示
self.refresh_project_display()
@ -348,9 +279,9 @@ class ProjectArea(QWidget):
if available_width <= 0:
available_width = width - 20 # 使用更小的边距
# 固定卡片宽度和间距
card_width = 280 # 固定卡片宽度
spacing = 20 # 卡片间距
# 固定卡片宽度和间距 - 匹配 Figma 设计
card_width = 280 # 固定卡片宽度,匹配当前卡片尺寸
spacing = 24 # 卡片间距,匹配更新后的间距
# 计算能容纳的列数
if available_width < card_width:

View File

@ -41,7 +41,7 @@ class ImageDisplayWidget(QWidget):
import math
target_rect = self.rect()
radius = 12.0
radius = 5.0
# “过缩放”逻辑保持不变,我们仍然需要一张比控件大的图片
overscale_factor = 1.05
@ -76,23 +76,26 @@ class ImageDisplayWidget(QWidget):
class ProjectCard(QWidget):
"""项目卡片组件"""
CORNER_RADIUS = 5
def __init__(self, project: Project, project_manager: ProjectManager, view_mode: str = "grid"):
super().__init__()
self.project = project
self.project_manager = project_manager
self.view_mode = view_mode
self.project_settings_page = ProjectSettingsPage()
self.project_size_text = self._compute_project_size_text()
# 设置卡片对象名称和属性
self.setObjectName("projectCard")
self.setProperty("status", project.status)
self.setAttribute(Qt.WA_StyledBackground) # 关键:启用样式背景继承
# 设置尺寸和策略
# 设置尺寸和策略 - 匹配 Figma 设计规范
if view_mode == "grid":
self.setFixedSize(280, 240)
self.setFixedSize(276, 191)
else:
self.setFixedHeight(80)
self.setFixedHeight(88) # 调整列表视图高度
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
self.init_ui()
@ -100,6 +103,7 @@ class ProjectCard(QWidget):
# 确保样式正确应用
self.update_style()
self.apply_corner_mask()
def update_style(self):
"""强制刷新样式"""
@ -107,6 +111,86 @@ class ProjectCard(QWidget):
self.style().polish(self)
self.update()
def apply_corner_mask(self):
"""为整个卡片应用统一的圆角裁剪"""
rect = self.rect()
if rect.isNull():
return
path = QPainterPath()
path.addRoundedRect(QRectF(rect), self.CORNER_RADIUS, self.CORNER_RADIUS)
fill_polygon = path.toFillPolygon()
if fill_polygon.isEmpty():
return
self.setMask(QRegion(fill_polygon.toPolygon()))
def _compute_project_size_text(self):
"""根据项目路径计算大小文本"""
path = self._resolve_project_path()
if not path:
return "N/A"
try:
if not path.exists():
return "N/A"
except OSError:
return "N/A"
size_bytes = self._calculate_path_size(path)
return self._format_size(size_bytes)
def _resolve_project_path(self):
project_path = self.project.project_dir if self.project.project_dir else self.project.path
if not project_path:
return None
if isinstance(project_path, Path):
candidate = project_path
else:
candidate = Path(str(project_path))
if candidate == Path():
return None
try:
return candidate if candidate.is_absolute() else candidate.resolve()
except OSError:
return candidate
@staticmethod
def _calculate_path_size(path: Path) -> int:
try:
if path.is_file():
return path.stat().st_size
except OSError:
return 0
total = 0
stack = [path]
while stack:
current = stack.pop()
try:
with os.scandir(current) as entries:
for entry in entries:
try:
if entry.is_symlink():
continue
if entry.is_file(follow_symlinks=False):
total += entry.stat(follow_symlinks=False).st_size
elif entry.is_dir(follow_symlinks=False):
stack.append(Path(entry.path))
except OSError:
continue
except OSError:
continue
return total
@staticmethod
def _format_size(size_bytes: int) -> str:
if size_bytes <= 0:
return "0B"
units = ["B", "KB", "MB", "GB", "TB"]
size = float(size_bytes)
for unit in units:
if size < 1024 or unit == units[-1]:
if unit == "B":
return f"{int(size)}{unit}"
return f"{size:.2f}{unit}"
size /= 1024
return f"{int(size_bytes)}B"
def init_ui(self):
"""初始化UI"""
if self.view_mode == "grid":
@ -114,20 +198,28 @@ class ProjectCard(QWidget):
else:
self.create_list_layout()
def resizeEvent(self, event):
super().resizeEvent(event)
self.apply_corner_mask()
def create_grid_layout(self):
"""创建网格布局"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 项目头部
self.create_project_header(layout)
# 项目图片
self.create_project_image(layout)
# 项目底部
self.create_project_footer(layout)
"""创建网格布局 - 根据Figma设计优化"""
# 使用绝对定位的方式来实现图片背景和覆盖层效果
self.setStyleSheet("""
QWidget#projectCard {
border-radius: 5px;
overflow: hidden;
}
""")
# 创建背景图片层
self.create_background_image()
# 创建右上角按钮
self.create_overlay_button()
# 创建底部信息覆盖层
self.create_bottom_overlay()
def create_list_layout(self):
"""创建列表布局"""
@ -162,19 +254,122 @@ class ProjectCard(QWidget):
# 菜单按钮
self.create_menu_button(layout)
def create_background_image(self):
"""创建背景图片层"""
# 背景图片标签,填充整个卡片
self.background_label = QLabel(self)
self.background_label.setGeometry(0, 0, 276, 191)
self.background_label.setScaledContents(True)
self.background_label.setAlignment(Qt.AlignCenter)
# 检查是否有项目图片
if hasattr(self.project, 'image') and self.project.image and os.path.exists(self.project.image):
pixmap = QPixmap(self.project.image)
if not pixmap.isNull():
# 缩放图片以填充整个区域,保持宽高比
scaled_pixmap = pixmap.scaled(276, 191, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
self.background_label.setPixmap(scaled_pixmap)
else:
self.set_default_background()
else:
self.set_default_background()
def set_default_background(self):
"""设置默认背景"""
# 使用project_empty_icon作为默认背景
if IconManager.icon_exists('project_empty_icon'):
empty_pixmap = IconManager.get_pixmap('project_empty_icon', QSize(276, 191))
if not empty_pixmap.isNull():
self.background_label.setPixmap(empty_pixmap)
return
# 如果图标不存在,创建一个默认的灰色背景
default_pixmap = QPixmap(276, 191)
default_pixmap.fill(QColor(66, 67, 71)) # #424347 - Figma中的颜色
# 在中心绘制文件夹图标
painter = QPainter(default_pixmap)
painter.setRenderHint(QPainter.Antialiasing)
# 绘制文件夹图标
font = QFont("Segoe UI Emoji", 48)
painter.setFont(font)
painter.setPen(QColor(212, 212, 212)) # #d4d4d4
painter.drawText(default_pixmap.rect(), Qt.AlignCenter, "📁")
painter.end()
self.background_label.setPixmap(default_pixmap)
def create_overlay_button(self):
"""创建右上角覆盖按钮"""
# 根据项目状态显示不同的按钮
if self.project.status == 'pending_delete':
self.overlay_btn = QPushButton("", self)
self.overlay_btn.setObjectName("overlayDeleteBtn")
self.overlay_btn.clicked.connect(self.confirm_delete_project)
self.overlay_btn.setToolTip("确认删除项目")
else:
self.overlay_btn = QPushButton(self)
if IconManager.icon_exists('infomation'):
self.overlay_btn.setIcon(IconManager.get_icon('infomation', QSize(16, 16)))
self.overlay_btn.setIconSize(QSize(16, 16))
else:
self.overlay_btn.setText("")
self.overlay_btn.setObjectName("overlayInfoBtn")
self.overlay_btn.clicked.connect(self.show_context_menu)
# self.overlay_btn.setToolTip("项目信息")
# 设置按钮位置和大小
self.overlay_btn.setGeometry(248, 8, 20, 20)
def create_bottom_overlay(self):
"""创建底部信息覆盖层"""
# 底部覆盖层容器
self.bottom_overlay = QWidget(self)
self.bottom_overlay.setObjectName("bottomOverlay")
self.bottom_overlay.setGeometry(0, 138, 276, 53)
# 底部布局
overlay_layout = QHBoxLayout(self.bottom_overlay)
overlay_layout.setContentsMargins(20, 8, 20, 8)
overlay_layout.setSpacing(8)
# 项目标题
title_label = QLabel(self.project.title)
title_label.setObjectName("overlayProjectTitle")
overlay_layout.addWidget(title_label)
overlay_layout.addStretch()
# 右侧信息区域
info_layout = QVBoxLayout()
info_layout.setSpacing(2)
info_layout.setContentsMargins(0, 0, 0, 0)
# 项目日期
date_label = QLabel(self.project.date)
date_label.setObjectName("overlayProjectDate")
date_label.setAlignment(Qt.AlignRight)
info_layout.addWidget(date_label)
# 项目大小
# size_label = QLabel("52.00KB") # 这里可以根据实际项目大小动态设置
size_label = QLabel(self.project_size_text)
size_label.setObjectName("overlayProjectSize")
size_label.setAlignment(Qt.AlignRight)
info_layout.addWidget(size_label)
overlay_layout.addLayout(info_layout)
def create_project_header(self, layout):
"""创建项目头部"""
header_widget = QWidget()
header_widget.setObjectName("projectHeader")
header_layout = QHBoxLayout(header_widget)
header_layout.setContentsMargins(16, 16, 16, 8)
header_layout.setContentsMargins(8, 8, 8, 8)
header_layout.setSpacing(8)
# 项目标题
title_label = QLabel(self.project.title)
title_label.setObjectName("projectTitle")
title_label.setWordWrap(True)
header_layout.addWidget(title_label, 1) # 添加拉伸因子
header_layout.addStretch()
# 右侧操作区
actions_layout = QHBoxLayout()
@ -184,15 +379,20 @@ class ProjectCard(QWidget):
if self.project.status == 'pending_delete':
self.menu_btn = QPushButton("")
self.menu_btn.setObjectName("deleteBtn")
self.menu_btn.setFixedSize(24, 24)
self.menu_btn.setFixedSize(20, 20)
self.menu_btn.clicked.connect(self.confirm_delete_project)
self.menu_btn.setToolTip("确认删除项目")
else:
self.menu_btn = QPushButton("")
self.menu_btn = QPushButton()
if IconManager.icon_exists('infomation'):
self.menu_btn.setIcon(IconManager.get_icon('infomation', QSize(20, 20)))
self.menu_btn.setIconSize(QSize(20, 20))
else:
self.menu_btn.setText("")
self.menu_btn.setObjectName("menuBtn")
self.menu_btn.setFixedSize(24, 24)
self.menu_btn.setFixedSize(20, 20)
self.menu_btn.clicked.connect(self.show_context_menu)
self.menu_btn.setToolTip("项目操作菜单")
self.menu_btn.setToolTip("项目信息")
actions_layout.addWidget(self.menu_btn)
header_layout.addLayout(actions_layout)
@ -251,14 +451,36 @@ class ProjectCard(QWidget):
"""创建项目底部"""
footer_widget = QWidget()
footer_widget.setObjectName("projectFooter")
footer_layout = QVBoxLayout(footer_widget)
footer_layout.setContentsMargins(16, 0, 16, 16) # 减少顶部边距
footer_layout = QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(20, 0, 20, 0)
footer_layout.setSpacing(0)
# 项目标题(左侧)
title_label = QLabel(self.project.title)
title_label.setObjectName("projectTitle")
footer_layout.addWidget(title_label)
footer_layout.addStretch()
# 项目信息(右侧)
info_layout = QVBoxLayout()
info_layout.setSpacing(0)
info_layout.setContentsMargins(0, 0, 0, 0)
# 项目日期
date_label = QLabel(self.project.date)
date_label.setObjectName("projectDate")
date_label.setAlignment(Qt.AlignCenter)
footer_layout.addWidget(date_label)
date_label.setAlignment(Qt.AlignRight)
info_layout.addWidget(date_label)
# 项目大小
# size_label = QLabel("52.00KB") # 这里可以根据实际项目大小动态设置
size_label = QLabel(self.project_size_text)
size_label.setObjectName("projectDate")
size_label.setAlignment(Qt.AlignRight)
info_layout.addWidget(size_label)
footer_layout.addLayout(info_layout)
layout.addWidget(footer_widget)
@ -362,7 +584,14 @@ class ProjectCard(QWidget):
delete_action.triggered.connect(self.delete_project)
# 显示菜单
menu.exec_(self.menu_btn.mapToGlobal(self.menu_btn.rect().bottomLeft()))
# 根据视图模式选择正确的按钮来定位菜单
if self.view_mode == "grid" and hasattr(self, 'overlay_btn'):
menu.exec_(self.overlay_btn.mapToGlobal(self.overlay_btn.rect().bottomLeft()))
elif hasattr(self, 'menu_btn'):
menu.exec_(self.menu_btn.mapToGlobal(self.menu_btn.rect().bottomLeft()))
else:
# 如果没有按钮,使用鼠标位置
menu.exec_(QCursor.pos())
def open_project(self):
"""打开项目"""
@ -569,6 +798,7 @@ class ProjectCard(QWidget):
if child:
child.setParent(None)
self.project_size_text = self._compute_project_size_text()
self.init_ui()
# 强制刷新样式
@ -599,14 +829,24 @@ class ProjectCard(QWidget):
def mousePressEvent(self, event):
"""鼠标点击事件"""
if event.button() == Qt.LeftButton:
# 检查是否点击了菜单按钮
if not self.menu_btn.geometry().contains(event.pos()):
if self.project.status == 'pending_delete':
# 待删除状态的项目,尝试恢复
self.try_restore_project()
else:
# 正常状态的项目,打开项目
self.open_project()
# 检查是否点击了覆盖按钮(新的网格布局)或菜单按钮(列表布局)
if self.view_mode == "grid":
if hasattr(self, 'overlay_btn') and not self.overlay_btn.geometry().contains(event.pos()):
if self.project.status == 'pending_delete':
# 待删除状态的项目,尝试恢复
self.try_restore_project()
else:
# 正常状态的项目,打开项目
self.open_project()
else:
# 列表视图的原有逻辑
if hasattr(self, 'menu_btn') and not self.menu_btn.geometry().contains(event.pos()):
if self.project.status == 'pending_delete':
# 待删除状态的项目,尝试恢复
self.try_restore_project()
else:
# 正常状态的项目,打开项目
self.open_project()
super().mousePressEvent(event)
def try_restore_project(self):
@ -639,20 +879,37 @@ class ProjectCard(QWidget):
def show_success_tooltip(self, message):
"""显示成功提示工具提示"""
# 临时改变菜单按钮的工具提示来显示成功状态
original_tooltip = self.menu_btn.toolTip()
# 根据视图模式选择正确的按钮
target_btn = None
if self.view_mode == "grid" and hasattr(self, 'overlay_btn'):
target_btn = self.overlay_btn
elif hasattr(self, 'menu_btn'):
target_btn = self.menu_btn
self.menu_btn.setToolTip(f"{message}")
# 使用定时器恢复原状态
QTimer.singleShot(3000, lambda: self.restore_button_state(original_tooltip))
if target_btn:
# 临时改变按钮的工具提示来显示成功状态
original_tooltip = target_btn.toolTip()
target_btn.setToolTip(f"{message}")
# 使用定时器恢复原状态
QTimer.singleShot(3000, lambda: self.restore_button_state(target_btn, original_tooltip))
def restore_button_state(self, original_tooltip):
def restore_button_state(self, button, original_tooltip):
"""恢复按钮原始状态"""
self.menu_btn.setToolTip(original_tooltip)
if button:
button.setToolTip(original_tooltip)
def refresh_preview_image(self):
"""刷新预览图"""
# 确定要操作的按钮
target_btn = None
if self.view_mode == "grid" and hasattr(self, 'overlay_btn'):
target_btn = self.overlay_btn
elif hasattr(self, 'menu_btn'):
target_btn = self.menu_btn
original_tooltip = ""
try:
# 获取项目路径
project_path = self.project.project_dir if self.project.project_dir else self.project.path
@ -661,9 +918,10 @@ class ProjectCard(QWidget):
return
# 显示进度提示
original_tooltip = self.menu_btn.toolTip()
self.menu_btn.setEnabled(False)
self.menu_btn.setToolTip("⏳ 正在生成预览图...")
if target_btn:
original_tooltip = target_btn.toolTip()
target_btn.setEnabled(False)
target_btn.setToolTip("⏳ 正在生成预览图...")
QApplication.processEvents()
# 生成新的预览图
@ -693,18 +951,33 @@ class ProjectCard(QWidget):
except Exception:
pass # 忽略删除失败
else:
# 生成预览图失败使用project_empty_icon作为默认图片
self.project.image = None
self.project_manager.update_project(self.project)
# 刷新显示以显示默认图标
self.refresh_image_display()
QMessageBox.warning(self, "生成失败",
"无法生成预览图。\n\n可能原因:\n"
"无法生成预览图,已使用默认图标\n\n可能原因:\n"
"• 项目目录中没有图片文件\n"
"• 图片文件格式不支持\n"
"• 权限不足")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新预览图时发生错误:\n{str(e)}")
# 发生异常时也使用project_empty_icon作为默认图片
self.project.image = None
self.project_manager.update_project(self.project)
# 刷新显示以显示默认图标
self.refresh_image_display()
QMessageBox.critical(self, "错误", f"刷新预览图时发生错误,已使用默认图标:\n{str(e)}")
finally:
# 恢复按钮状态
self.menu_btn.setEnabled(True)
self.menu_btn.setToolTip(original_tooltip)
if target_btn:
target_btn.setEnabled(True)
target_btn.setToolTip(original_tooltip)
def generate_project_preview(self, project_path):
"""生成项目预览图"""
@ -792,13 +1065,29 @@ class ProjectCard(QWidget):
return self.create_default_preview(project_path)
def create_default_preview(self, project_path):
"""创建默认预览图"""
"""创建默认预览图 - 使用project_empty_icon"""
try:
# 创建预览图存储目录
# 首先尝试使用project_empty_icon
if IconManager.icon_exists('project_empty_icon'):
# 创建预览图存储目录
preview_dir = Path.cwd() / 'MetaCore' / 'Resources' / 'ProjectPreviews'
preview_dir.mkdir(parents=True, exist_ok=True)
# 生成默认预览图文件名
preview_filename = f"default_preview_{self.project.id}.png"
preview_path = preview_dir / preview_filename
# 获取project_empty_icon并缩放到预览尺寸
empty_icon = IconManager.get_pixmap('project_empty_icon', QSize(400, 300))
if not empty_icon.isNull():
# 保存预览图
if empty_icon.save(str(preview_path), 'PNG'):
return str(preview_path)
# 如果project_empty_icon不存在创建带项目信息的默认预览图
preview_dir = Path.cwd() / 'MetaCore' / 'Resources' / 'ProjectPreviews'
preview_dir.mkdir(parents=True, exist_ok=True)
# 生成默认预览图文件名
preview_filename = f"default_preview_{self.project.id}.png"
preview_path = preview_dir / preview_filename
@ -861,34 +1150,21 @@ class ProjectCard(QWidget):
def update_grid_image_display(self):
"""更新网格视图中的图片显示"""
# 找到图片容器
for i in range(self.layout().count()):
widget = self.layout().itemAt(i).widget()
if widget and widget.objectName() == "projectImageContainer":
# 找到内部的堆叠布局
for j in range(widget.layout().count()):
image_widget = widget.layout().itemAt(j).widget()
if image_widget and image_widget.objectName() == "projectImage":
stacked_layout = image_widget.layout()
if isinstance(stacked_layout, QStackedLayout):
# 获取图片显示控件
image_display = stacked_layout.widget(0)
if isinstance(image_display, ImageDisplayWidget):
# 检查是否有新的图片文件
if (hasattr(self.project, 'image') and
self.project.image and
os.path.exists(self.project.image) and
os.path.isfile(self.project.image)):
# 重新加载图片
pixmap = QPixmap(self.project.image)
if not pixmap.isNull():
image_display.setPixmap(pixmap)
stacked_layout.setCurrentIndex(0)
else:
stacked_layout.setCurrentIndex(1)
else:
stacked_layout.setCurrentIndex(1)
break
if self.view_mode == "grid" and hasattr(self, 'background_label'):
# 检查是否有新的图片文件
if (hasattr(self.project, 'image') and
self.project.image and
os.path.exists(self.project.image) and
os.path.isfile(self.project.image)):
# 重新加载图片
pixmap = QPixmap(self.project.image)
if not pixmap.isNull():
scaled_pixmap = pixmap.scaled(276, 191, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
self.background_label.setPixmap(scaled_pixmap)
else:
self.set_default_background()
else:
self.set_default_background()
def update_list_image_display(self):
"""更新列表视图中的图片显示"""
@ -945,7 +1221,7 @@ class ListImageDisplayWidget(QWidget):
import math
target_rect = self.rect()
radius = 8.0 # 列表视图使用较小的圆角
radius = 5.0 # 统一使用5px圆角
# 过缩放因子
overscale_factor = 1.05
@ -971,4 +1247,3 @@ class ListImageDisplayWidget(QWidget):
# 绘制图片
painter.drawPixmap(int(draw_x), int(draw_y), scaled_pixmap)

View File

@ -24,6 +24,8 @@ class Sidebar(QWidget):
super().__init__()
self.project_manager = project_manager
self.current_filter = "overview"
self.setObjectName("sidebar")
self.setAttribute(Qt.WA_StyledBackground, True)
# 导航项映射filter_type -> (section, item_name)
self.nav_mapping = {
@ -38,8 +40,8 @@ class Sidebar(QWidget):
self.init_ui()
self.connect_signals()
# 设置固定宽度
self.setFixedWidth(280)
# 设置固定宽度 - 匹配 Figma 设计规范
self.setFixedWidth(313)
def init_ui(self):
"""初始化UI"""
@ -65,23 +67,26 @@ class Sidebar(QWidget):
logo_widget = QWidget()
logo_widget.setObjectName("logoWidget")
logo_layout = QHBoxLayout(logo_widget)
logo_layout.setContentsMargins(20, 20, 20, 20)
# Logo图标 - 使用更专业的图标
logo_icon = QLabel()
if IconManager.icon_exists('logo'):
logo_icon.setPixmap(IconManager.get_pixmap('logo', QSize(24, 24)))
else:
logo_icon.setText("") # 备选文字图标
logo_icon.setObjectName("logoIcon")
logo_layout.addWidget(logo_icon)
logo_layout.setContentsMargins(37, 32, 46, 24)
logo_layout.setSpacing(28)
logo_layout.setAlignment(Qt.AlignCenter)
logo_icon = QLabel()
logo_icon.setObjectName("logoIcon")
logo_icon.setAlignment(Qt.AlignCenter)
logo_icon.setFixedSize(80, 80)
logo_icon.setScaledContents(True)
if IconManager.icon_exists('logo'):
logo_icon.setPixmap(IconManager.get_pixmap('logo', QSize(80, 80)))
else:
logo_icon.setText("")
logo_layout.addWidget(logo_icon, alignment=Qt.AlignCenter)
# Logo文字
logo_text = QLabel("MetaCore")
logo_text.setObjectName("logoText")
logo_layout.addWidget(logo_text)
logo_layout.addStretch()
logo_text.setAlignment(Qt.AlignCenter)
logo_layout.addWidget(logo_text, 0, Qt.AlignBottom)
layout.addWidget(logo_widget)
def create_quick_actions(self, layout):
@ -89,31 +94,33 @@ class Sidebar(QWidget):
actions_widget = QWidget()
actions_widget.setObjectName("quickActions")
actions_layout = QVBoxLayout(actions_widget)
actions_layout.setContentsMargins(20, 10, 20, 20)
actions_layout.setSpacing(10)
actions_layout.setContentsMargins(37, 24, 46, 24)
actions_layout.setSpacing(12)
# 创建新项目按钮
self.create_btn = QPushButton("创建新项目")
if IconManager.icon_exists('create'):
self.create_btn.setIcon(IconManager.get_icon('create'))
else:
self.create_btn = QPushButton("+ 创建新项目")
self.create_btn = QPushButton("新建项目")
# if IconManager.icon_exists('create'):
# self.create_btn.setIcon(IconManager.get_icon('create'))
# self.create_btn.setIconSize(QSize(18, 18))
self.create_btn.setObjectName("createBtn")
self.create_btn.setCursor(Qt.PointingHandCursor)
self.create_btn.setMinimumHeight(36)
self.create_btn.setMaximumHeight(36)
self.create_btn.clicked.connect(self.create_project_requested.emit)
actions_layout.addWidget(self.create_btn)
# 导入项目按钮
self.import_btn = QPushButton("导入项目")
if IconManager.icon_exists('import'):
self.import_btn.setIcon(IconManager.get_icon('import'))
else:
self.import_btn = QPushButton("↑ 导入项目")
# if IconManager.icon_exists('import'):
# self.import_btn.setIcon(IconManager.get_icon('import'))
# self.import_btn.setIconSize(QSize(18, 18))
self.import_btn.setObjectName("sidebarImportBtn")
self.import_btn.setCursor(Qt.PointingHandCursor)
self.import_btn.setMinimumHeight(36)
self.import_btn.setMaximumHeight(36)
self.import_btn.clicked.connect(self.import_project_requested.emit)
actions_layout.addWidget(self.import_btn)
layout.addWidget(actions_widget)
def create_navigation_menu(self, layout):
"""创建导航菜单"""
# 滚动区域
@ -127,8 +134,8 @@ class Sidebar(QWidget):
nav_widget = QWidget()
nav_widget.setObjectName("navWidget")
nav_layout = QVBoxLayout(nav_widget)
nav_layout.setContentsMargins(0, 0, 0, 0) # 边距
nav_layout.setSpacing(0) # 间距
nav_layout.setContentsMargins(0, 10, 0, 0) # 边距上10px
nav_layout.setSpacing(10) # 分组间距10px
# 我的项目分组
self.create_nav_section(nav_layout, "我的项目", [
@ -162,11 +169,11 @@ class Sidebar(QWidget):
section_layout.setSpacing(0)
# 标题按钮(可展开/收起)
title_btn = QPushButton(f"{title}")
title_btn = QPushButton(f" {title}") # 默认有空格
if IconManager.icon_exists('down'):
title_btn.setIcon(IconManager.get_icon('down'))
else:
title_btn = QPushButton(f"{title}")
title_btn.setText(f"{title}")
title_btn.setObjectName("navSectionTitle")
title_btn.setCheckable(True)
@ -177,8 +184,8 @@ class Sidebar(QWidget):
items_widget = QWidget()
items_widget.setObjectName("navItems")
items_layout = QVBoxLayout(items_widget)
items_layout.setContentsMargins(0, 0, 0, 0)
items_layout.setSpacing(0)
items_layout.setContentsMargins(0, 10, 0, 0)
items_layout.setSpacing(10)
# 创建导航项
for icon, text, filter_type in items:
@ -194,13 +201,13 @@ class Sidebar(QWidget):
if is_expanded:
if IconManager.icon_exists('down'):
title_btn.setIcon(IconManager.get_icon('down'))
title_btn.setText(f" {title}")
# 文本已经在初始化时设置了空格,不需要重新设置
else:
title_btn.setText(f"{title}")
else:
if IconManager.icon_exists('right'):
title_btn.setIcon(IconManager.get_icon('right'))
title_btn.setText(f" {title}")
# 文本已经在初始化时设置了空格,不需要重新设置
else:
title_btn.setText(f"{title}")
@ -209,15 +216,16 @@ class Sidebar(QWidget):
def create_nav_item(self, icon, text, filter_type):
"""创建导航项"""
item_btn = QPushButton(f" {text}") # 移除文字图标,只保留空格用于间距
item_btn = QPushButton(text) # 直接使用文本,不显示图标
# 设置真实图标
if IconManager.icon_exists(filter_type):
item_btn.setIcon(IconManager.get_icon(filter_type, QSize(16, 16)))
item_btn.setIconSize(QSize(16, 16))
else:
# 如果图标不存在,使用原来的文字图标
item_btn.setText(f"{icon} {text}")
# 隐藏图标,不设置任何图标
# 注释掉图标相关代码
# if IconManager.icon_exists(filter_type):
# item_btn.setIcon(IconManager.get_icon(filter_type, QSize(16, 16)))
# item_btn.setIconSize(QSize(16, 16))
# else:
# # 如果图标不存在,使用原来的文字图标
# item_btn.setText(f"{icon} {text}")
item_btn.setObjectName("navItem")
item_btn.setCheckable(True)

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,21 @@
[
{
"id": 6,
"title": "XNWX",
"date": "2025-10-11 12:15:15",
"id": 2,
"title": "ab",
"date": "2025-10-14 18:23:06",
"type": "imported",
"image": "C:\\Users\\29381\\Desktop\\XNWX\\XNWX.png",
"image": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\Resources\\ProjectPreviews\\default_preview_2.png",
"path": "C:\\Users\\29381\\Desktop",
"project_dir": "C:\\Users\\29381\\Desktop\\ab",
"description": "从文件夹 ab 导入",
"status": "normal"
},
{
"id": 1,
"title": "XNWX",
"date": "2025-10-14 18:05:51",
"type": "imported",
"image": "C:\\Users\\29381\\Desktop\\MetaCore-startup\\MetaCore\\Resources\\ProjectPreviews\\preview_1_1760491477496.png",
"path": "C:\\Users\\29381\\Desktop",
"project_dir": "C:\\Users\\29381\\Desktop\\XNWX",
"description": "从文件夹 XNWX 导入",