MetaCore-startup/MetaCore/ui/project_area.py
2025-10-17 16:56:28 +08:00

373 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
项目区域组件
"""
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from typing import List
from data.project_manager import ProjectManager, Project
from ui.icon_manager import IconManager
from ui.project_card import ProjectCard
class ProjectArea(QWidget):
"""项目区域组件"""
# 信号定义
search_changed = pyqtSignal(str)
create_project_requested = pyqtSignal()
import_project_requested = pyqtSignal()
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()
# 初始加载项目
self.update_projects(self.project_manager.get_all_projects())
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout(self)
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(self.content_layout)
# 项目显示区域
self.create_project_display_area(self.content_layout)
def create_toolbar(self, layout):
"""创建顶部工具区域"""
header_widget = QWidget()
header_widget.setObjectName("contentHeader")
header_layout = QHBoxLayout(header_widget)
header_layout.setContentsMargins(36, 26, 36, 24)
header_layout.setSpacing(0)
breadcrumb_container = QWidget()
breadcrumb_container.setObjectName("breadcrumbContainer")
breadcrumb_layout = QHBoxLayout(breadcrumb_container)
breadcrumb_layout.setContentsMargins(0, 0, 0, 0)
breadcrumb_layout.setSpacing(8)
self.home_label = QLabel("我的项目")
self.home_label.setObjectName("breadcrumbBase")
breadcrumb_layout.addWidget(self.home_label)
self.separator_label = QLabel("/")
self.separator_label.setObjectName("breadcrumbSeparator")
breadcrumb_layout.addWidget(self.separator_label)
self.current_label = QLabel("项目概述")
self.current_label.setObjectName("breadcrumbCurrent")
breadcrumb_layout.addWidget(self.current_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)
header_layout.addWidget(self.search_input, 0, Qt.AlignRight)
layout.addWidget(header_widget)
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)
# 项目容器
self.projects_container = QWidget()
self.projects_container.setObjectName("projectsContainer")
# 网格布局
self.projects_layout = QGridLayout(self.projects_container)
self.projects_layout.setContentsMargins(36, 24, 6, 18)
self.projects_layout.setSpacing(24)
self.projects_layout.setAlignment(Qt.AlignTop) # 设置顶部对齐
# 响应式列数
self.grid_columns = 4 # 默认列数
self.update_grid_columns()
self.scroll_area.setWidget(self.projects_container)
layout.addWidget(self.scroll_area)
def connect_signals(self):
"""连接信号"""
# 项目管理器信号
self.project_manager.project_added.connect(self.on_project_added)
self.project_manager.project_removed.connect(self.on_project_removed)
self.project_manager.project_updated.connect(self.on_project_updated)
def update_projects(self, projects: List[Project]):
"""更新项目显示"""
self.projects = projects
self.refresh_project_display()
def update_navigation(self, section: str, item_name: str):
"""更新导航显示"""
# 更新面包屑导航
self.home_label.setText(section)
# 根据不同的导航项设置显示标题
title_mapping = {
"项目概述": "项目概述",
"项目管理": "项目管理",
"资源分类": "资源分类管理",
"资源管理": "资源管理",
"系统设置": "系统设置",
}
display_title = title_mapping.get(item_name, item_name)
self.current_label.setText(display_title)
def refresh_project_display(self):
"""刷新项目显示"""
# 清除现有项目卡片
for i in reversed(range(self.projects_layout.count())):
child = self.projects_layout.itemAt(i).widget()
if child:
child.setParent(None)
if not self.projects:
# 显示空状态
self.show_empty_state()
else:
# 添加项目卡片
if self.view_mode == "grid":
self.show_grid_view()
else:
self.show_list_view()
# 强制更新布局和几何信息
self._update_layout_geometry()
def show_grid_view(self):
"""显示网格视图"""
# 使用响应式列数
columns = self.grid_columns
for i, project in enumerate(self.projects):
row = i // columns
col = i % columns
project_card = ProjectCard(project, self.project_manager)
self.projects_layout.addWidget(project_card, row, col, Qt.AlignTop) # 顶部对齐
def show_list_view(self):
"""显示列表视图"""
for i, project in enumerate(self.projects):
project_card = ProjectCard(project, self.project_manager, view_mode="list")
self.projects_layout.addWidget(project_card, i, 0, Qt.AlignTop) # 顶部对齐
def show_empty_state(self):
"""显示空状态"""
empty_widget = QWidget()
empty_layout = QVBoxLayout(empty_widget)
empty_layout.setAlignment(Qt.AlignCenter)
# 空状态图标
empty_icon = QLabel("📁")
empty_icon.setStyleSheet("font-size: 48px; color: #666666;")
empty_icon.setAlignment(Qt.AlignCenter)
empty_layout.addWidget(empty_icon)
# 空状态文字
empty_text = QLabel("没有找到匹配的项目")
empty_text.setStyleSheet("font-size: 16px; color: #888888; margin-top: 10px;")
empty_text.setAlignment(Qt.AlignCenter)
empty_layout.addWidget(empty_text)
self.projects_layout.addWidget(empty_widget, 0, 0, 1, 4)
def set_view_mode(self, mode: str):
"""设置视图模式"""
self.view_mode = mode
# 更新按钮状态
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()
def focus_search(self):
"""聚焦搜索框"""
self.search_input.setFocus()
def on_project_added(self, project: Project):
"""项目添加事件"""
# 如果当前显示的是项目概述或项目管理,则添加新项目
if hasattr(self, 'current_filter') and self.current_filter in ["overview", "management"]:
self.projects.insert(0, project)
self.refresh_project_display()
def on_project_removed(self, project_id: int):
"""项目删除事件"""
self.projects = [p for p in self.projects if p.id != project_id]
self.refresh_project_display()
def on_project_updated(self, project: Project):
"""项目更新事件"""
for i, p in enumerate(self.projects):
if p.id == project.id:
self.projects[i] = project
break
self.refresh_project_display()
def update_grid_columns(self):
"""更新网格列数 - 针对固定大小卡片优化"""
# 获取可用宽度,尝试多种方式确保准确性
width = 0
if hasattr(self, 'scroll_area') and self.scroll_area.width() > 0:
# 优先使用滚动区域的宽度
width = self.scroll_area.viewport().width()
elif hasattr(self, 'projects_container') and self.projects_container.width() > 0:
# 备选:使用项目容器的宽度
width = self.projects_container.width()
elif hasattr(self, 'scroll_area'):
# 最后备选:使用滚动区域的宽度
width = self.scroll_area.width()
# 确保宽度有效
if width <= 0:
# 如果仍然无效,使用默认列数
if not hasattr(self, 'grid_columns'):
self.grid_columns = 1
return False
# 固定卡片大小的计算280px宽度20px间距
available_width = width - 60 # 减去左右边距
# 确保可用宽度为正数
if available_width <= 0:
available_width = width - 20 # 使用更小的边距
# 固定卡片宽度和间距 - 匹配 Figma 设计
card_width = 280 # 固定卡片宽度,匹配当前卡片尺寸
spacing = 24 # 卡片间距,匹配更新后的间距
# 计算能容纳的列数
if available_width < card_width:
columns = 1
else:
# 计算能容纳的列数:(可用宽度 + 间距) / (卡片宽度 + 间距)
columns = (available_width + spacing) // (card_width + spacing)
columns = max(1, columns)
# 限制最大列数,避免过多列
columns = min(columns, 6)
# 检查列数是否发生变化
old_columns = getattr(self, 'grid_columns', 4)
if old_columns != columns:
self.grid_columns = columns
return True # 返回True表示列数发生了变化
else:
self.grid_columns = columns
return False
def resizeEvent(self, event):
"""窗口大小变化事件"""
super().resizeEvent(event)
# 立即处理缩小情况,延迟处理放大情况
if hasattr(self, '_last_width'):
current_width = event.size().width()
is_shrinking = current_width < self._last_width
if is_shrinking:
# 缩小时立即处理,确保响应性
self._handle_resize()
self._last_width = current_width
return
# 延迟处理,避免频繁调用(主要用于放大情况)
if hasattr(self, '_resize_timer'):
self._resize_timer.stop()
self._resize_timer = QTimer()
self._resize_timer.setSingleShot(True)
self._resize_timer.timeout.connect(self._handle_resize)
self._resize_timer.start(50) # 减少延迟100ms->50ms
# 记录当前宽度
self._last_width = event.size().width()
def _handle_resize(self):
"""处理窗口大小变化"""
if hasattr(self, 'projects') and self.view_mode == "grid":
# 强制更新滚动区域几何信息
if hasattr(self, 'scroll_area'):
self.scroll_area.updateGeometry()
QApplication.processEvents()
# 更新列数
columns_changed = self.update_grid_columns()
if columns_changed:
# 列数变化,完全重新布局
self.refresh_project_display()
else:
# 列数没变,但仍需要更新布局以适应新的宽度
self._update_layout_geometry()
# 强制重新计算卡片大小
self._update_card_sizes()
def _update_layout_geometry(self):
"""更新布局几何信息"""
if hasattr(self, 'projects_container'):
# 强制更新布局
self.projects_container.updateGeometry()
self.projects_layout.update()
# 确保滚动区域正确更新
if hasattr(self, 'scroll_area'):
self.scroll_area.updateGeometry()
# 强制重新计算滚动区域大小
QApplication.processEvents()
self.scroll_area.ensureVisible(0, 0)
def _update_card_sizes(self):
"""更新项目卡片大小 - 固定大小卡片无需动态调整"""
# 由于卡片现在是固定大小(280x240),不再需要动态调整
# 这个方法保留为空,以保持接口兼容性
pass