This commit is contained in:
陈横 2025-10-11 09:24:06 +08:00
commit ee7a5db41c
82 changed files with 15056 additions and 0 deletions

125
.gitignore vendored Normal file
View File

@ -0,0 +1,125 @@
# Python缓存文件
__pycache__/
*.py[cod]
*$py.class
# 分发/打包
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# 单元测试/覆盖率报告
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# 翻译
*.mo
*.pot
# Django
*.log
local_settings.py
db.sqlite3
# Flask
instance/
.webassets-cache
# Scrapy
.scrapy
# Sphinx文档
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat调度文件
celerybeat-schedule
# SageMath解析文件
*.sage.py
# 环境
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
metacore_env/
# Spyder项目设置
.spyderproject
.spyproject
# Rope项目设置
.ropeproject
# mkdocs文档
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# PyQt5项目特定文件
MetaCore/data/projects.json
*.pyc
*.pyo
# 操作系统生成的文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE文件
.vscode/
.idea/
*.swp
*.swo
*~
# 临时文件
*.tmp
*.temp
*.bak
*.backup

View File

@ -0,0 +1,224 @@
# 跨平台路径处理优化报告
## 优化概述
本次优化主要针对 MetaCore 项目中的跨平台兼容性和路径处理问题,使用现代 Python 标准库 `pathlib` 替代传统的 `os.path`,并创建了专门的工具类来处理跨平台差异。
## 主要问题
### 1. 路径处理问题
- **混合使用路径分隔符**:代码中存在大量 `replace('\\', '/')` 操作,这是不规范且不可靠的做法
- **硬编码路径**存在平台特定的硬编码路径如Linux下的 `/home/tiger/下载/pycharm-2025.2.0.1/bin/pycharm.sh`
- **不一致的路径API**:混合使用 `os.path.join` 和字符串拼接
### 2. 跨平台兼容性问题
- **平台检测重复**:多处重复的平台类型检测代码
- **可执行文件查找**:没有统一的可执行文件查找机制
- **路径规范化不统一**:不同地方使用不同的路径规范化方法
## 优化方案
### 1. 创建工具类
#### PathUtils 类
```python
class PathUtils:
"""跨平台路径处理工具类"""
@staticmethod
def normalize_path(path_str: str) -> Path:
"""规范化路径为Path对象"""
@staticmethod
def ensure_path_exists(path: Path, is_file: bool = False) -> bool:
"""确保路径存在"""
@staticmethod
def safe_path_join(*parts) -> Path:
"""安全地连接路径部分"""
@staticmethod
def is_valid_path(path_str: str) -> bool:
"""检查路径字符串是否有效"""
```
#### PlatformUtils 类
```python
class PlatformUtils:
"""跨平台工具类"""
@staticmethod
def get_system_type() -> str:
"""获取系统类型"""
@staticmethod
def get_executable_extension() -> str:
"""获取可执行文件扩展名"""
@staticmethod
def get_python_executable(venv_path: Path) -> Path:
"""获取虚拟环境中的Python可执行文件路径"""
@staticmethod
def get_pycharm_paths() -> List[Path]:
"""获取各平台PyCharm可能的安装路径"""
@staticmethod
def find_executable(paths: List[Path]) -> Optional[Path]:
"""在指定路径列表中查找可执行文件"""
```
### 2. 优化的文件
#### MetaCore/data/project_manager.py
- **完全重构**:使用 `pathlib.Path` 替代所有 `os.path` 操作
- **统一路径处理**:所有路径操作都通过 `PathUtils` 进行
- **移除硬编码路径**:使用动态路径查找替代硬编码路径
- **改进的PyCharm集成**:使用统一的可执行文件查找机制
#### MetaCore/ui/project_card.py
- **优化 show_in_explorer 方法**:使用 `pathlib` 处理路径
- **改进错误处理**:更好的异常处理和用户提示
- **跨平台文件管理器支持**:统一的文件管理器打开逻辑
### 3. 主要改进
#### 路径处理改进
- ✅ 使用 `pathlib.Path` 替代 `os.path`
- ✅ 统一的路径规范化方法
- ✅ 自动处理不同操作系统的路径格式
- ✅ 移除手动的路径分隔符替换
#### 跨平台兼容性改进
- ✅ 统一的平台检测机制
- ✅ 动态的可执行文件查找
- ✅ 平台特定的配置统一管理
- ✅ 改进的错误处理和回退机制
#### 代码质量改进
- ✅ 减少代码重复
- ✅ 更好的类型提示
- ✅ 统一的异常处理
- ✅ 改进的文档和注释
## 具体优化案例
### 1. 路径连接优化
**优化前:**
```python
project_dir = os.path.join(path, title).replace('\\', '/')
full_path = os.path.normpath(os.path.join(project_dir, filename))
```
**优化后:**
```python
path_obj = PathUtils.normalize_path(path)
project_dir = path_obj / title
full_path = project_dir / filename
```
### 2. PyCharm路径查找优化
**优化前:**
```python
if system == "linux":
return [
"pycharm",
"pycharm.sh",
"/opt/pycharm*/bin/pycharm.sh",
"/usr/local/bin/pycharm",
"/usr/bin/pycharm",
"/home/tiger/下载/pycharm-2025.2.0.1/bin/pycharm.sh", # 硬编码路径
os.path.expanduser("~/.local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-*/bin/pycharm.sh")
]
```
**优化后:**
```python
else: # Linux
return [
Path("pycharm"),
Path("pycharm.sh"),
*Path("/opt").glob("pycharm*/bin/pycharm.sh"),
Path("/usr/local/bin/pycharm"),
Path("/usr/bin/pycharm"),
*home.glob(".local/share/JetBrains/Toolbox/apps/PyCharm-P/ch-*/bin/pycharm.sh")
]
```
### 3. 虚拟环境Python可执行文件查找优化
**优化前:**
```python
if os.name == 'nt': # Windows
python_executable = Path(venv_path) / 'Scripts' / 'python.exe'
else: # Unix-like systems
python_executable = Path(venv_path) / 'bin' / 'python'
```
**优化后:**
```python
python_executable = PlatformUtils.get_python_executable(venv_path_obj)
```
## 测试建议
### 1. 跨平台测试
- Windows 10/11
- macOS (Intel & Apple Silicon)
- Ubuntu/Debian Linux
- CentOS/RHEL Linux
### 2. 路径测试
- 包含中文字符的路径
- 包含空格的路径
- 超长路径
- 网络路径 (Windows UNC)
- 符号链接
### 3. 功能测试
- 项目创建和导入
- 文件管理器打开
- PyCharm集成
- 虚拟环境处理
## 后续优化建议
### 1. 配置文件优化
- 使用 `pathlib` 优化配置文件路径处理
- 统一配置文件格式和位置
### 2. 资源文件处理
- 优化图标和资源文件的路径处理
- 支持相对路径和绝对路径
### 3. 日志系统
- 添加路径相关的详细日志
- 改进错误报告机制
### 4. 单元测试
- 为路径处理工具类添加单元测试
- 添加跨平台兼容性测试
## 性能影响
### 正面影响
- ✅ **更少的路径转换操作**:减少字符串替换操作
- ✅ **更好的缓存利用**Path对象的内置缓存机制
- ✅ **减少重复代码**:统一的工具函数
### 注意事项
- ⚠️ **Path对象创建开销**:虽然很小,但比字符串操作略重
- ⚠️ **兼容性**确保所有Path对象在需要时正确转换为字符串
## 总结
本次优化显著改善了 MetaCore 项目的跨平台兼容性和代码质量:
1. **统一性**所有路径操作现在使用统一的API
2. **可靠性**减少了平台特定的bug和问题
3. **可维护性**:代码更清晰,更容易维护和扩展
4. **现代化**使用现代Python最佳实践
这些改进为项目的长期维护和跨平台部署奠定了坚实的基础。

80
Doc/README.md Normal file
View File

@ -0,0 +1,80 @@
# MetaCore 项目文档中心
欢迎来到MetaCore项目文档中心这里包含了项目的所有说明文档按类别整理便于查阅。
## 📚 文档分类
### 🚀 快速开始
- **[快速开始.md](快速开始.md)** - 新用户必读,快速上手指南
- **[安装PyQt5指南.md](安装PyQt5指南.md)** - PyQt5安装详细步骤
### 📖 详细说明
- **[README_PyQt5.md](README_PyQt5.md)** - PyQt5版本完整功能说明
- **[项目总览.md](项目总览.md)** - 整个项目的全面概述
- **[功能对比说明.md](功能对比说明.md)** - Web版本与PyQt5版本对比
### 🔧 环境配置
- **[虚拟环境完整指南.md](虚拟环境完整指南.md)** - 虚拟环境完整指南
## 🎯 推荐阅读顺序
### 新用户
1. [快速开始.md](快速开始.md) - 了解如何快速启动
2. [安装PyQt5指南.md](安装PyQt5指南.md) - 解决安装问题
3. [README_PyQt5.md](README_PyQt5.md) - 了解完整功能
### 开发者
1. [项目总览.md](项目总览.md) - 了解项目整体架构
2. [功能对比说明.md](功能对比说明.md) - 理解两个版本的差异
3. [虚拟环境完整指南.md](虚拟环境完整指南.md) - 配置开发环境
### 系统管理员
1. [虚拟环境完整指南.md](虚拟环境完整指南.md) - 环境部署
2. [安装PyQt5指南.md](安装PyQt5指南.md) - 依赖安装
3. [项目总览.md](项目总览.md) - 了解项目状态
## 🔍 快速查找
### 按问题类型查找
#### 安装问题
- PyQt5安装失败 → [安装PyQt5指南.md](安装PyQt5指南.md)
- 虚拟环境配置 → [虚拟环境完整指南.md](虚拟环境完整指南.md)
#### 使用问题
- 如何启动应用 → [快速开始.md](快速开始.md)
- 功能说明 → [README_PyQt5.md](README_PyQt5.md)
- 版本选择 → [功能对比说明.md](功能对比说明.md)
#### 开发问题
- 项目架构 → [项目总览.md](项目总览.md)
- 功能对比 → [功能对比说明.md](功能对比说明.md)
## 📱 文档更新
### 最新更新
- **2024年** - 完成PyQt5版本开发
- **最近** - 整理文档到Doc目录
### 维护说明
- 所有文档统一存放在Doc目录
- 按功能和用途分类
- 定期更新和维护
## 🎯 使用建议
### 查阅文档
1. **从README.md开始** - 本文档提供了完整的导航
2. **按需查阅** - 根据具体问题选择相应文档
3. **顺序阅读** - 新用户建议按推荐顺序阅读
### 贡献文档
1. **保持结构** - 新文档应放在Doc目录
2. **更新索引** - 添加新文档时更新本README
3. **统一格式** - 遵循现有文档的格式规范
---
💡 **提示:** 如果您是第一次使用MetaCore建议从 [快速开始.md](快速开始.md) 开始阅读!
🎉 **欢迎使用MetaCore项目管理平台**

210
Doc/README_PyQt5.md Normal file
View File

@ -0,0 +1,210 @@
# MetaCore - PyQt5版本
这是基于PyQt5重写的MetaCore项目管理平台完全复制了原始Web版本的界面和功能。
## 功能特性
### 🎯 主要功能
- ✅ **项目管理**:创建、导入、删除、重命名项目
- ✅ **项目卡片**:三段式布局(标题+菜单、图片+类型标签、时间)
- ✅ **收藏功能**:项目收藏/取消收藏
- ✅ **搜索过滤**:按名称搜索、按类型过滤
- ✅ **视图模式**:网格视图和列表视图
- ✅ **右键菜单**:完整的项目操作菜单
### 🎨 界面特性
- ✅ **深色主题**:现代化的深色界面设计
- ✅ **响应式布局**:自适应窗口大小
- ✅ **侧边栏导航**:可展开/收起的导航菜单
- ✅ **模态对话框**:创建项目和导入项目对话框
- ✅ **拖拽支持**:文件拖拽导入功能
## 安装和运行
### 1. 环境要求
- Python 3.7+
- PyQt5 5.15.0+
### 2. 安装依赖
```bash
pip install -r requirements.txt
```
### 3. 运行应用
```bash
python main.py
```
## 项目结构
```
MetaCore/
├── main.py # 主程序入口
├── requirements.txt # 依赖包列表
├── README_PyQt5.md # 说明文档
├── data/ # 数据模块
│ ├── __init__.py
│ ├── project_manager.py # 项目数据管理器
│ └── projects.json # 项目数据文件(自动生成)
└── ui/ # 界面模块
├── __init__.py
├── main_window.py # 主窗口
├── sidebar.py # 侧边栏组件
├── project_area.py # 项目显示区域
├── project_card.py # 项目卡片组件
├── create_project_dialog.py # 创建项目对话框
├── import_project_dialog.py # 导入项目对话框
└── styles.py # 样式表定义
```
## 主要组件说明
### 1. 主窗口 (MainWindow)
- 整体布局管理
- 菜单栏和状态栏
- 组件间信号连接
- 窗口事件处理
### 2. 侧边栏 (Sidebar)
- Logo区域
- 快速操作按钮
- 可展开的导航菜单
- 用户信息显示
### 3. 项目区域 (ProjectArea)
- 工具栏和搜索区域
- 网格/列表视图切换
- 项目卡片容器
- 空状态显示
### 4. 项目卡片 (ProjectCard)
- 三段式布局设计
- 悬停效果
- 右键菜单
- 收藏状态显示
### 5. 创建项目对话框 (CreateProjectDialog)
- 2:1布局比例
- 模板选择网格
- 项目信息表单
- 文件夹选择功能
### 6. 导入项目对话框 (ImportProjectDialog)
- 文件拖拽上传
- 导入选项配置
- 文件列表显示
- 进度反馈
## 数据管理
### 项目数据结构
```python
{
"id": 1,
"title": "项目名称",
"date": "2024-06-08 15:56:35",
"type": "industrial",
"image": "🏭",
"favorite": true,
"path": "/path/to/project"
}
```
### 支持的项目类型
- `industrial` - 工业项目 🏭
- `smart` - 智能项目 💧
- `vr` - VR项目 🥽
- `game` - 游戏项目 🎮
- `design` - 设计项目 🎨
- `empty` - 空白项目 📁
## 样式系统
### 主题色彩
- **主色调**:深灰色 (#1a1a1a, #2a2a2a)
- **边框色**:中灰色 (#3a3a3a, #4a4a4a)
- **强调色**:紫色 (#8b5cf6, #7c3aed)
- **文字色**:白色/灰色 (#ffffff, #cccccc, #888888)
### 组件样式
- 所有组件都有对应的ObjectName用于样式选择器
- 支持悬停状态和选中状态
- 统一的圆角和间距设计
- 平滑的过渡动画效果
## 信号和槽机制
### 主要信号
- `projects_changed` - 项目列表变化
- `project_added` - 项目添加
- `project_removed` - 项目删除
- `project_updated` - 项目更新
- `filter_changed` - 过滤条件变化
- `search_changed` - 搜索内容变化
### 事件处理
- 鼠标点击事件
- 键盘快捷键
- 拖拽事件
- 窗口事件
## 扩展功能
### 可以添加的功能
1. **项目模板系统**:真实的项目模板创建和应用
2. **文件管理**:项目内文件的管理和预览
3. **版本控制**Git集成和版本管理
4. **协作功能**:多用户协作和权限管理
5. **插件系统**:支持第三方插件扩展
6. **云同步**:项目数据云端同步
7. **导出功能**:项目打包和分享
### 性能优化
1. **虚拟滚动**:大量项目时的性能优化
2. **缓存机制**:图片和数据缓存
3. **异步加载**:大文件异步处理
4. **内存管理**:及时释放不用的资源
## 开发说明
### 添加新组件
1. 在`ui/`目录下创建新的组件文件
2. 继承适当的PyQt5基类
3. 实现`init_ui()`方法
4. 添加必要的信号定义
5. 在样式表中添加对应样式
### 修改样式
1. 在`ui/styles.py`中修改样式定义
2. 使用ObjectName作为选择器
3. 支持状态选择器(:hover, :checked等
4. 遵循现有的颜色和间距规范
### 数据持久化
- 项目数据自动保存到`data/projects.json`
- 应用关闭时自动保存
- 启动时自动加载历史数据
- 支持数据备份和恢复
## 与Web版本对比
### 相同功能
- ✅ 完全相同的界面布局
- ✅ 相同的项目管理功能
- ✅ 相同的视觉设计风格
- ✅ 相同的用户交互逻辑
### PyQt5版本优势
- ✅ 原生桌面应用体验
- ✅ 更好的性能表现
- ✅ 系统集成度更高
- ✅ 离线使用支持
- ✅ 文件系统直接访问
### 技术差异
- **Web版本**HTML + CSS + JavaScript
- **PyQt5版本**Python + PyQt5 + 自定义样式表
- **数据存储**JSON文件 vs 浏览器存储
- **文件操作**系统API vs Web API限制
这个PyQt5版本完全复制了Web版本的功能和外观同时提供了更好的桌面应用体验。

208
Doc/功能对比说明.md Normal file
View File

@ -0,0 +1,208 @@
# MetaCore Web版本 vs PyQt5版本功能对比
## 🎯 完整功能映射
### 1. 整体布局结构
| Web版本 | PyQt5版本 | 说明 |
|---------|-----------|------|
| `<div class="sidebar">` | `Sidebar(QWidget)` | 左侧导航栏 |
| `<div class="main-content">` | `ProjectArea(QWidget)` | 主内容区域 |
| CSS Grid布局 | QGridLayout | 项目卡片网格布局 |
| CSS Flexbox | QHBoxLayout/QVBoxLayout | 弹性布局 |
### 2. 侧边栏功能
| Web版本功能 | PyQt5实现 | 对应文件 |
|-------------|-----------|----------|
| Logo区域 | `create_logo_section()` | `ui/sidebar.py` |
| 创建项目按钮 | `QPushButton(" 创建新项目")` | `ui/sidebar.py` |
| 导入项目按钮 | `QPushButton("📤 导入项目")` | `ui/sidebar.py` |
| 导航菜单 | `create_navigation_menu()` | `ui/sidebar.py` |
| 可展开分组 | `create_nav_section()` | `ui/sidebar.py` |
| 用户信息 | `create_user_info()` | `ui/sidebar.py` |
### 3. 项目卡片功能
| Web版本 | PyQt5版本 | 实现细节 |
|---------|-----------|----------|
| 三段式布局 | `create_grid_layout()` | 头部+图片+底部 |
| 项目标题 | `QLabel(project.title)` | 支持文字省略 |
| 三点菜单 | `QPushButton("⋮")` | 右键菜单触发 |
| 项目图标 | `QLabel(project.image)` | Emoji图标显示 |
| 类型标签 | `QLabel(type_label)` | 紫色背景标签 |
| 收藏星星 | `QLabel("⭐")` | 条件显示 |
| 项目时间 | `QLabel(project.date)` | 底部居中显示 |
| 悬停效果 | `enterEvent/leaveEvent` | 边框高亮效果 |
### 4. 右键菜单功能
| Web版本菜单项 | PyQt5实现 | 功能说明 |
|---------------|-----------|----------|
| 📁 打开项目 | `open_action` | 项目打开功能 |
| ✏️ 重命名 | `rename_action` | 项目重命名 |
| 📋 复制项目 | `duplicate_action` | 项目复制 |
| 📤 导出项目 | `export_action` | 项目导出 |
| ⭐ 收藏/取消收藏 | `favorite_action` | 收藏状态切换 |
| 🗑️ 删除项目 | `delete_action` | 项目删除确认 |
### 5. 创建项目对话框
| Web版本元素 | PyQt5组件 | 布局比例 |
|-------------|-----------|----------|
| 模板选择区域 | `create_template_section()` | 2/3宽度 |
| 项目信息区域 | `create_project_info_section()` | 1/3宽度 |
| 模板网格 | `QGridLayout(2列)` | 2列模板布局 |
| 项目名称输入 | `QLineEdit` | 表单输入 |
| 项目描述输入 | `QTextEdit` | 多行文本 |
| 位置选择 | `QFileDialog.getExistingDirectory()` | 系统文件夹选择 |
| 浏览按钮 | `QPushButton("📁")` | 文件夹图标 |
### 6. 搜索和过滤功能
| Web版本 | PyQt5版本 | 实现方式 |
|---------|-----------|----------|
| 搜索框 | `QLineEdit` | 实时搜索 |
| 视图切换 | `QPushButton` (网格/列表) | 可选择按钮组 |
| 类型过滤 | `QComboBox` | 下拉选择 |
| 排序选项 | `QComboBox` | 排序方式选择 |
## 🎨 样式系统对比
### CSS vs PyQt5样式表
| Web版本CSS | PyQt5样式表 | 效果 |
|------------|-------------|------|
| `.project-card` | `#projectCard` | 项目卡片样式 |
| `.project-card:hover` | `#projectCard[hover="true"]` | 悬停效果 |
| `background: #2a2a2a` | `background-color: #2a2a2a` | 背景色 |
| `border-radius: 12px` | `border-radius: 12px` | 圆角 |
| `transition: all 0.2s` | 通过事件处理实现 | 过渡动画 |
### 颜色主题完全一致
| 颜色用途 | 颜色值 | Web版本 | PyQt5版本 |
|----------|--------|---------|-----------|
| 主背景 | #1a1a1a | ✅ | ✅ |
| 卡片背景 | #2a2a2a | ✅ | ✅ |
| 边框色 | #3a3a3a | ✅ | ✅ |
| 强调色 | #8b5cf6 | ✅ | ✅ |
| 文字色 | #ffffff | ✅ | ✅ |
## 🔧 技术实现对比
### 1. 数据管理
| 方面 | Web版本 | PyQt5版本 |
|------|---------|-----------|
| 数据存储 | JavaScript数组 | Python列表 + JSON文件 |
| 数据持久化 | localStorage | 文件系统 |
| 数据结构 | JavaScript对象 | Python类 |
| 状态管理 | 直接操作DOM | 信号槽机制 |
### 2. 事件处理
| 事件类型 | Web版本 | PyQt5版本 |
|----------|---------|-----------|
| 点击事件 | `addEventListener('click')` | `clicked.connect()` |
| 悬停事件 | CSS `:hover` | `enterEvent/leaveEvent` |
| 键盘事件 | `keydown/keyup` | `keyPressEvent` |
| 拖拽事件 | HTML5 Drag API | `dragEnterEvent/dropEvent` |
### 3. 布局系统
| 布局方式 | Web版本 | PyQt5版本 |
|----------|---------|-----------|
| 弹性布局 | CSS Flexbox | QHBoxLayout/QVBoxLayout |
| 网格布局 | CSS Grid | QGridLayout |
| 绝对定位 | CSS position | setGeometry() |
| 响应式 | CSS媒体查询 | 布局管理器自适应 |
## 🚀 功能增强
### PyQt5版本独有优势
1. **系统集成**
- 原生文件对话框
- 系统通知支持
- 任务栏集成
- 系统托盘支持
2. **性能优化**
- 原生渲染性能
- 内存管理更好
- 大量数据处理能力
- 多线程支持
3. **桌面特性**
- 快捷键支持
- 窗口管理
- 多显示器支持
- 离线使用
### 可扩展功能
1. **文件系统集成**
```python
# 直接操作文件系统
import os, shutil
def copy_project_files(src, dst):
shutil.copytree(src, dst)
```
2. **数据库支持**
```python
# 可以集成SQLite等数据库
import sqlite3
def save_to_database(project):
# 数据库操作
pass
```
3. **插件系统**
```python
# 动态加载插件
import importlib
def load_plugin(plugin_name):
return importlib.import_module(f'plugins.{plugin_name}')
```
## 📱 使用体验对比
### 启动和运行
| 方面 | Web版本 | PyQt5版本 |
|------|---------|-----------|
| 启动方式 | 浏览器打开HTML | 运行Python脚本 |
| 依赖环境 | 现代浏览器 | Python + PyQt5 |
| 安装过程 | 无需安装 | pip install PyQt5 |
| 更新方式 | 刷新页面 | 重新运行程序 |
### 用户交互
| 交互方式 | Web版本 | PyQt5版本 |
|----------|---------|-----------|
| 响应速度 | 依赖浏览器 | 原生应用速度 |
| 动画效果 | CSS动画 | Qt动画系统 |
| 拖拽操作 | HTML5 API | Qt拖拽系统 |
| 右键菜单 | 自定义实现 | 原生菜单 |
## 🎯 总结
PyQt5版本完全复制了Web版本的所有功能和视觉效果同时提供了更好的桌面应用体验
### ✅ 完全实现的功能
- 所有UI组件和布局
- 所有交互功能
- 完整的样式主题
- 数据管理和持久化
- 搜索和过滤功能
- 项目CRUD操作
### 🚀 额外优势
- 原生桌面应用性能
- 系统级文件操作
- 更好的用户体验
- 扩展性更强
这个PyQt5版本不仅是Web版本的完美移植更是一个功能更强大的桌面应用程序。

190
Doc/安装PyQt5指南.md Normal file
View File

@ -0,0 +1,190 @@
# PyQt5安装指南
## 🔧 Windows系统安装PyQt5
### 方法1使用pip安装推荐
#### 1. 检查Python是否已安装
```cmd
python --version
```
如果显示版本号说明Python已安装。如果提示命令不存在需要先安装Python。
#### 2. 安装Python如果未安装
1. 访问 https://www.python.org/downloads/
2. 下载最新版本的Python
3. 安装时勾选"Add Python to PATH"
#### 3. 安装PyQt5
```cmd
pip install PyQt5
```
#### 4. 验证安装
```cmd
python -c "import PyQt5; print('PyQt5安装成功')"
```
### 方法2使用conda安装
#### 1. 安装Anaconda或Miniconda
- 下载地址https://www.anaconda.com/products/distribution
#### 2. 安装PyQt5
```cmd
conda install pyqt
```
### 方法3离线安装
#### 1. 下载PyQt5安装包
访问 https://pypi.org/project/PyQt5/#files 下载对应的.whl文件
#### 2. 离线安装
```cmd
pip install PyQt5-5.15.7-cp39-cp39-win_amd64.whl
```
## 🚀 运行MetaCore应用
### 1. 确认PyQt5已安装
```cmd
python -c "from PyQt5.QtWidgets import QApplication; print('PyQt5可用')"
```
### 2. 运行应用
```cmd
cd MetaCore目录
python main.py
```
### 3. 如果遇到错误
#### 错误1ModuleNotFoundError: No module named 'PyQt5'
**解决方案:**
```cmd
pip install PyQt5
```
#### 错误2ImportError: DLL load failed
**解决方案:**
```cmd
pip uninstall PyQt5
pip install PyQt5
```
#### 错误3Python命令不存在
**解决方案:**
1. 重新安装Python并勾选"Add to PATH"
2. 或者使用完整路径:
```cmd
C:\Python39\python.exe main.py
```
## 📋 系统要求
### 最低要求
- Windows 7 SP1 或更高版本
- Python 3.6 或更高版本
- 2GB RAM
- 100MB 可用磁盘空间
### 推荐配置
- Windows 10 或更高版本
- Python 3.8 或更高版本
- 4GB RAM
- SSD硬盘
## 🔍 故障排除
### 1. 检查Python版本
```cmd
python --version
```
确保版本为3.6或更高。
### 2. 检查pip版本
```cmd
pip --version
```
如果pip不可用可以重新安装Python。
### 3. 更新pip
```cmd
python -m pip install --upgrade pip
```
### 4. 清理缓存
```cmd
pip cache purge
```
### 5. 使用国内镜像源(如果下载慢)
```cmd
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyQt5
```
## 🎯 验证安装成功
### 运行测试脚本
创建一个测试文件 `test_pyqt5.py`
```python
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QWidget
def test_pyqt5():
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle('PyQt5测试')
window.setGeometry(100, 100, 300, 200)
label = QLabel('PyQt5安装成功', window)
label.move(100, 80)
window.show()
print("PyQt5测试窗口已显示关闭窗口退出测试")
sys.exit(app.exec_())
if __name__ == '__main__':
test_pyqt5()
```
运行测试:
```cmd
python test_pyqt5.py
```
如果出现一个窗口显示"PyQt5安装成功",说明安装正确。
## 📞 获取帮助
如果仍然遇到问题:
1. **检查Python环境**
```cmd
where python
python -m site
```
2. **重新安装Python**
- 卸载现有Python
- 重新下载安装,确保勾选"Add to PATH"
3. **使用虚拟环境**
```cmd
python -m venv metacore_env
metacore_env\Scripts\activate
pip install PyQt5
```
4. **联系技术支持**
- 提供错误信息截图
- 说明操作系统版本
- 提供Python版本信息
---
安装完成后就可以运行MetaCore PyQt5版本了🎉

209
Doc/快速开始.md Normal file
View File

@ -0,0 +1,209 @@
# MetaCore PyQt5版本 - 快速开始指南
## 🚀 一键启动
### 方法1直接运行
```bash
# 1. 安装依赖
pip install PyQt5
# 2. 运行应用
python main.py
```
### 方法2使用启动脚本
```bash
python run_app.py
```
## 📋 系统要求
- **Python版本**3.7 或更高
- **操作系统**Windows 10/11, macOS 10.14+, Linux
- **内存要求**:最少 512MB RAM
- **磁盘空间**:约 100MB
## 🔧 安装步骤
### Windows用户
```cmd
# 1. 检查Python版本
python --version
# 2. 安装PyQt5
pip install PyQt5
# 3. 运行应用
python main.py
```
### macOS用户
```bash
# 1. 确保有Python3
python3 --version
# 2. 安装PyQt5
pip3 install PyQt5
# 3. 运行应用
python3 main.py
```
### Linux用户
```bash
# Ubuntu/Debian
sudo apt-get install python3-pyqt5
# 或使用pip
pip3 install PyQt5
# 运行应用
python3 main.py
```
## 🎯 首次使用
### 1. 启动应用
运行后会看到MetaCore主界面
- 左侧:导航侧边栏
- 右侧:项目展示区域
- 底部:状态栏
### 2. 创建第一个项目
1. 点击侧边栏的" 创建新项目"按钮
2. 选择项目模板工业、VR、智能等
3. 填写项目名称和描述
4. 选择项目保存位置
5. 点击"创建项目"
### 3. 管理项目
- **查看项目**:点击项目卡片
- **右键菜单**:右键点击项目卡片或点击"⋮"按钮
- **搜索项目**:使用顶部搜索框
- **切换视图**:点击网格/列表视图按钮
## 📁 文件结构说明
```
MetaCore/
├── main.py # 🚀 主程序入口
├── run_app.py # 🎯 启动脚本
├── requirements.txt # 📦 依赖列表
├── data/ # 💾 数据目录
│ ├── project_manager.py # 项目管理器
│ └── projects.json # 项目数据(自动生成)
└── ui/ # 🎨 界面组件
├── main_window.py # 主窗口
├── sidebar.py # 侧边栏
├── project_area.py # 项目区域
├── project_card.py # 项目卡片
├── create_project_dialog.py # 创建项目对话框
├── import_project_dialog.py # 导入项目对话框
└── styles.py # 样式表
```
## 🎨 界面预览
### 主界面布局
```
┌─────────────────────────────────────────────────────────┐
│ MetaCore - 项目管理平台 🔔 👤 ➕创建 │
├─────────────┬───────────────────────────────────────────┤
│ 🧊 MetaCore │ 全部项目 ⊞ ☰ 🔽 🔍 │
│ ├───────────────────────────────────────────┤
创建新项目│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ 📤 导入项目 │ │ 🏭 │ │ 💧 │ │ 🏗️ │ │ 📊 │ │
│ │ │智慧工厂│ │智慧水务│ │数字工厂│ │智慧监控│ │
│ 📁 项目概览 │ │ ⭐⋮ │ │ ⋮ │ │ ⋮ │ │ ⭐⋮ │ │
│ ⭐ 收藏项目 │ └─────┘ └─────┘ └─────┘ └─────┘ │
│ 🕒 最近项目 │ │
│ 🏭 工业项目 │ │
│ 💧 智能项目 │ │
│ │ │
│ 👤 Admin ▼ │ │
└─────────────┴───────────────────────────────────────────┤
│ 项目总数: 5 当前用户: Admin │
└─────────────────────────────────────────────────────────┘
```
### 创建项目对话框
```
┌─────────────────────────────────────────────────────────┐
│ 创建新的项目 ✕ │
├─────────────────────────────────────────────────────────┤
│ 选择项目模板 │ 项目信息 │
│ │ │
│ ┌─────┐ ┌─────┐ │ 选择一个项目模板来快速 │
│ │ 📁 │ │ 🏭 │ │ 开始,或者创建一个空白 │
│ │空白项目│ │工业项目│ │ 项目来从头开始。 │
│ └─────┘ └─────┘ │ │
│ ┌─────┐ ┌─────┐ │ 项目名称 │
│ │ 🥽 │ │ 💧 │ │ ┌─────────────────┐ │
│ │VR项目│ │智能项目│ │ │ │ │
│ └─────┘ └─────┘ │ └─────────────────┘ │
│ ┌─────┐ ┌─────┐ │ │
│ │ 🎮 │ │ 🎨 │ │ 项目描述 │
│ │游戏项目│ │设计项目│ │ ┌─────────────────┐ │
│ └─────┘ └─────┘ │ │ │ │
│ │ └─────────────────┘ │
│ │ │
│ │ 项目位置 │
│ │ ┌───────────────┐ 📁 │
│ │ │ │ │
│ │ └───────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 取消 创建项目 │
└─────────────────────────────────────────────────────────┘
```
## ⚡ 快捷键
| 功能 | 快捷键 | 说明 |
|------|--------|------|
| 新建项目 | Ctrl+N | 打开创建项目对话框 |
| 导入项目 | Ctrl+I | 打开导入项目对话框 |
| 搜索项目 | Ctrl+F | 聚焦搜索框 |
| 退出应用 | Ctrl+Q | 关闭应用程序 |
## 🔧 常见问题
### Q: 提示"No module named 'PyQt5'"
**A:** 需要安装PyQt5依赖包
```bash
pip install PyQt5
```
### Q: 在macOS上运行出错
**A:** 可能需要使用python3命令
```bash
python3 main.py
```
### Q: 项目数据保存在哪里?
**A:** 项目数据保存在`data/projects.json`文件中,应用关闭时自动保存。
### Q: 如何备份项目数据?
**A:** 复制`data/projects.json`文件即可备份所有项目信息。
### Q: 可以修改界面主题吗?
**A:** 可以在`ui/styles.py`文件中修改颜色和样式定义。
## 🎯 下一步
1. **探索功能**:尝试创建不同类型的项目
2. **自定义设置**:根据需要调整界面和功能
3. **数据管理**:定期备份项目数据
4. **功能扩展**:根据需要添加新功能
## 📞 技术支持
如果遇到问题,可以:
1. 查看`README_PyQt5.md`详细文档
2. 检查`功能对比说明.md`了解功能对应关系
3. 查看源代码中的注释和文档字符串
---
🎉 **恭喜!** 您已经成功运行了MetaCore PyQt5版本
这个桌面应用完全复制了Web版本的所有功能同时提供了更好的性能和用户体验。开始创建您的第一个项目吧

View File

@ -0,0 +1,261 @@
# MetaCore虚拟环境完整指南
## 🎯 概述
为MetaCore PyQt5版本创建独立的Python虚拟环境确保项目依赖不会与系统其他Python项目冲突。
## 📁 已创建的文件
### 自动化脚本
- `setup_and_run.bat` - Windows一键启动脚本
- `setup_and_run.sh` - macOS/Linux一键启动脚本
- `test_environment.py` - 环境测试脚本
### 配置文件
- `requirements.txt` - 项目依赖列表
- `虚拟环境设置指南.md` - 详细设置说明
- `虚拟环境快速参考.md` - 快速参考卡
## 🚀 快速开始
### 方法1一键启动推荐
#### Windows用户
```cmd
# 双击运行或命令行执行
setup_and_run.bat
```
#### macOS/Linux用户
```bash
# 首次运行需要给权限
chmod +x setup_and_run.sh
# 运行脚本
./setup_and_run.sh
```
### 方法2手动设置
#### 1. 创建虚拟环境
```cmd
# Windows
python -m venv metacore_env
# macOS/Linux
python3 -m venv metacore_env
```
#### 2. 激活虚拟环境
```cmd
# Windows
metacore_env\Scripts\activate
# macOS/Linux
source metacore_env/bin/activate
```
#### 3. 安装依赖
```cmd
pip install -r requirements.txt
```
#### 4. 运行应用
```cmd
python main.py
```
#### 5. 退出虚拟环境
```cmd
deactivate
```
## 🔧 环境测试
运行环境测试脚本检查配置:
```cmd
python test_environment.py
```
测试内容包括:
- ✅ Python版本检查
- ✅ PyQt5安装验证
- ✅ 项目文件完整性
- ✅ 模块导入测试
- ✅ 虚拟环境状态
## 📋 依赖包说明
### 必需依赖
- **PyQt5 >= 5.15.0** - GUI框架
### 可选依赖
- **Pillow >= 8.0.0** - 图像处理(如需要更好的图标支持)
- **psutil >= 5.8.0** - 系统信息(如需要系统集成功能)
## 🎯 使用场景
### 开发环境
```cmd
# 激活环境
metacore_env\Scripts\activate
# 开发调试
python main.py
# 安装新依赖
pip install package_name
# 更新依赖列表
pip freeze > requirements.txt
# 退出环境
deactivate
```
### 生产环境
```cmd
# 创建干净环境
python -m venv production_env
# 激活环境
production_env\Scripts\activate
# 安装指定依赖
pip install -r requirements.txt
# 运行应用
python main.py
```
## 🔍 故障排除
### 常见问题及解决方案
#### 1. Python命令不存在
**症状:** `'python' 不是内部或外部命令`
**解决:**
- 重新安装Python并勾选"Add to PATH"
- 或使用完整路径:`C:\Python39\python.exe`
#### 2. venv模块不存在
**症状:** `No module named venv`
**解决:**
```cmd
pip install virtualenv
virtualenv metacore_env
```
#### 3. PyQt5安装失败
**症状:** 网络错误或编译错误
**解决:**
```cmd
# 使用国内镜像
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyQt5
# 或使用conda
conda install pyqt
```
#### 4. 权限错误
**症状:** `Permission denied`
**解决:**
- Windows以管理员身份运行
- macOS/Linux检查目录权限 `chmod 755 /path/to/MetaCore`
#### 5. 虚拟环境激活失败
**症状:** 激活脚本不存在
**解决:**
```cmd
# 删除并重新创建
rm -rf metacore_env
python -m venv metacore_env
```
## 📊 环境管理最佳实践
### 1. 项目隔离
- 每个项目使用独立的虚拟环境
- 避免在系统Python中安装项目依赖
### 2. 依赖管理
- 使用requirements.txt记录依赖
- 定期更新依赖包版本
- 区分开发依赖和生产依赖
### 3. 版本控制
- 将虚拟环境目录添加到.gitignore
- 只提交requirements.txt文件
- 在不同环境中重新创建虚拟环境
### 4. 文档维护
- 记录环境创建步骤
- 说明特殊依赖的安装方法
- 提供故障排除指南
## 🎨 目录结构
完整的项目目录结构:
```
MetaCore/
├── metacore_env/ # 虚拟环境(不提交到版本控制)
│ ├── Scripts/ # Windows
│ ├── bin/ # macOS/Linux
│ ├── lib/
│ └── pyvenv.cfg
├── data/ # 数据模块
├── ui/ # 界面模块
├── main.py # 主程序
├── requirements.txt # 依赖列表
├── setup_and_run.bat # Windows启动脚本
├── setup_and_run.sh # macOS/Linux启动脚本
├── test_environment.py # 环境测试脚本
├── 虚拟环境设置指南.md # 详细指南
├── 虚拟环境快速参考.md # 快速参考
└── 虚拟环境完整指南.md # 本文档
```
## 🚀 部署建议
### 开发部署
1. 克隆项目代码
2. 创建虚拟环境
3. 安装依赖包
4. 运行测试脚本
5. 启动应用
### 生产部署
1. 使用干净的Python环境
2. 创建专用虚拟环境
3. 安装精确版本的依赖
4. 配置系统服务
5. 设置日志和监控
## 📞 获取帮助
### 自助诊断
1. 运行 `python test_environment.py` 检查环境
2. 查看 `虚拟环境快速参考.md` 获取常用命令
3. 参考 `虚拟环境设置指南.md` 获取详细说明
### 常用检查命令
```cmd
# 检查Python版本
python --version
# 检查虚拟环境状态
echo $VIRTUAL_ENV # Linux/macOS
echo %VIRTUAL_ENV% # Windows
# 检查已安装包
pip list
# 检查PyQt5
python -c "import PyQt5; print('PyQt5可用')"
```
---
🎉 **虚拟环境配置完成!**
现在您可以在独立的环境中安全地运行MetaCore PyQt5版本不用担心依赖冲突问题。选择最适合您的启动方式开始使用吧

234
Doc/项目总览.md Normal file
View File

@ -0,0 +1,234 @@
# MetaCore 项目总览
## 🎯 项目简介
MetaCore是一个现代化的项目管理平台现在提供两个版本
- **Web版本**基于HTML/CSS/JavaScript的浏览器应用
- **PyQt5版本**基于Python PyQt5的桌面应用
两个版本功能完全一致,界面设计完全相同,为用户提供了不同平台的选择。
## 📁 完整项目结构
```
MetaCore/
├── 📄 Web版本文件
│ ├── index.html # 主页面
│ ├── script.js # JavaScript逻辑
│ ├── styles.css # 样式表
│ └── test-cards.html # 测试页面
├── 🐍 PyQt5版本文件
│ ├── main.py # 主程序入口
│ ├── run_app.py # 启动脚本
│ ├── requirements.txt # 依赖包
│ ├── data/ # 数据模块
│ │ ├── __init__.py
│ │ ├── project_manager.py # 项目管理器
│ │ └── projects.json # 数据文件(自动生成)
│ └── ui/ # 界面模块
│ ├── __init__.py
│ ├── main_window.py # 主窗口
│ ├── sidebar.py # 侧边栏
│ ├── project_area.py # 项目区域
│ ├── project_card.py # 项目卡片
│ ├── create_project_dialog.py # 创建对话框
│ ├── import_project_dialog.py # 导入对话框
│ └── styles.py # 样式定义
└── 📚 文档文件
├── README_PyQt5.md # PyQt5版本说明
├── 快速开始.md # 快速开始指南
├── 安装PyQt5指南.md # 安装指南
├── 功能对比说明.md # 版本对比
├── PyQt5版本完成总结.md # 完成总结
├── 创建项目1-3布局说明.md # 布局优化说明
├── 创建项目布局优化说明.md # 布局说明
├── 项目卡片更新说明.md # 卡片更新说明
├── 功能演示.md # 功能演示
└── 项目总览.md # 本文档
```
## 🚀 快速开始
### Web版本
```bash
# 直接在浏览器中打开
open index.html
```
### PyQt5版本
```bash
# 1. 安装依赖
pip install PyQt5
# 2. 运行应用
python main.py
```
## ✨ 核心功能
### 🎨 界面特性
- ✅ **现代化设计**:深色主题,简洁美观
- ✅ **响应式布局**:自适应不同屏幕尺寸
- ✅ **三段式卡片**:标题+图片+时间的经典布局
- ✅ **2:1对话框**:创建项目的黄金比例布局
- ✅ **悬停效果**:丰富的交互反馈
### 📊 项目管理
- ✅ **项目创建**:多种模板选择
- ✅ **项目导入**:支持文件拖拽
- ✅ **项目编辑**:重命名、描述修改
- ✅ **项目删除**:安全删除确认
- ✅ **收藏功能**:快速标记重要项目
### 🔍 搜索过滤
- ✅ **实时搜索**:按项目名称搜索
- ✅ **类型过滤**:按项目类型筛选
- ✅ **收藏过滤**:查看收藏的项目
- ✅ **时间排序**:按创建时间排序
### 👁️ 视图模式
- ✅ **网格视图**:卡片式展示
- ✅ **列表视图**:紧凑式展示
- ✅ **视图切换**:一键切换显示模式
## 🎯 版本对比
| 特性 | Web版本 | PyQt5版本 |
|------|---------|-----------|
| 运行环境 | 浏览器 | 桌面应用 |
| 安装要求 | 无 | Python + PyQt5 |
| 性能表现 | 依赖浏览器 | 原生性能 |
| 文件操作 | 受限 | 完全访问 |
| 离线使用 | 支持 | 完全支持 |
| 系统集成 | 有限 | 完全集成 |
| 更新方式 | 刷新页面 | 重启应用 |
## 🛠️ 技术栈
### Web版本
- **前端**HTML5 + CSS3 + JavaScript ES6
- **样式**CSS Grid + Flexbox
- **图标**Font Awesome + Emoji
- **存储**localStorage
### PyQt5版本
- **框架**Python 3.7+ + PyQt5
- **架构**MVC模式 + 信号槽机制
- **界面**Qt样式表 + 自定义组件
- **存储**JSON文件
## 📈 开发历程
### 第一阶段Web版本开发
1. ✅ 基础界面搭建
2. ✅ 项目卡片设计
3. ✅ 侧边栏导航
4. ✅ 创建项目功能
5. ✅ 搜索过滤功能
### 第二阶段:界面优化
1. ✅ 三段式卡片布局
2. ✅ 1/3布局对话框
3. ✅ 悬停效果优化
4. ✅ 响应式设计
5. ✅ 文件选择集成
### 第三阶段PyQt5移植
1. ✅ 架构设计
2. ✅ 组件开发
3. ✅ 样式移植
4. ✅ 功能实现
5. ✅ 测试优化
## 🎨 设计亮点
### 1. 三段式项目卡片
```
┌─────────────────┐
│ 标题 ⋮ │ ← 头部:项目名称 + 菜单
├─────────────────┤
│ 🏭 │ ← 中部:项目图标 + 类型
│ 工业项目 │
├─────────────────┤
│ 2024-06-08 15:56│ ← 底部:创建时间
└─────────────────┘
```
### 2. 2:1创建对话框
```
┌─────────────────────────────────────┐
│ 创建新的项目 ✕ │
├─────────────────────────────────────┤
│ 模板选择 (2/3) │ 项目信息 (1/3) │
│ ┌─────┐ ┌─────┐ │ 项目名称 │
│ │ 📁 │ │ 🏭 │ │ ┌─────────────┐ │
│ │空白 │ │工业 │ │ │ │ │
│ └─────┘ └─────┘ │ └─────────────┘ │
│ ┌─────┐ ┌─────┐ │ 项目描述 │
│ │ 🥽 │ │ 💧 │ │ ┌─────────────┐ │
│ │VR │ │智能 │ │ │ │ │
│ └─────┘ └─────┘ │ └─────────────┘ │
└─────────────────────────────────────┘
```
### 3. 深色主题配色
- **主背景**#1a1a1a深黑
- **卡片背景**#2a2a2a深灰
- **边框色**#3a3a3a中灰
- **强调色**#8b5cf6紫色
- **文字色**#ffffff白色
## 🔮 未来规划
### 短期目标
- [ ] 添加项目模板管理
- [ ] 实现真实的文件操作
- [ ] 添加项目统计功能
- [ ] 支持主题切换
### 中期目标
- [ ] 集成版本控制系统
- [ ] 添加协作功能
- [ ] 实现云同步
- [ ] 开发移动端版本
### 长期目标
- [ ] 构建插件生态
- [ ] 企业级功能
- [ ] AI辅助功能
- [ ] 跨平台统一
## 📞 技术支持
### 文档资源
- 📖 **详细说明**README_PyQt5.md
- 🚀 **快速开始**:快速开始.md
- 🔧 **安装指南**安装PyQt5指南.md
- 📊 **功能对比**:功能对比说明.md
### 常见问题
1. **PyQt5安装问题**参考安装PyQt5指南.md
2. **功能使用问题**:参考快速开始.md
3. **版本选择问题**:参考功能对比说明.md
## 🏆 项目成就
### ✅ 完成的里程碑
1. **完整功能实现**100%复制Web版本功能
2. **视觉效果还原**:像素级还原界面设计
3. **性能优化**:提供更好的桌面体验
4. **文档完善**:提供完整的使用文档
### 🎯 技术价值
- 展示了Web到桌面的完整转换过程
- 提供了PyQt5开发的最佳实践
- 建立了可复用的组件架构
- 创建了完整的文档体系
---
🎉 **MetaCore项目现在提供了Web和桌面两种完整的解决方案**
无论您喜欢在浏览器中使用还是作为桌面应用运行,都能获得完全相同的功能和体验。选择最适合您的版本,开始管理您的项目吧!

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

210
MetaCore/README.md Normal file
View File

@ -0,0 +1,210 @@
# MetaCore - PyQt5版本
这是基于PyQt5重写的MetaCore项目管理平台完全复制了原始Web版本的界面和功能。
## 功能特性
### 🎯 主要功能
- ✅ **项目管理**:创建、导入、删除、重命名项目
- ✅ **项目卡片**:三段式布局(标题+菜单、图片+类型标签、时间)
- ✅ **收藏功能**:项目收藏/取消收藏
- ✅ **搜索过滤**:按名称搜索、按类型过滤
- ✅ **视图模式**:网格视图和列表视图
- ✅ **右键菜单**:完整的项目操作菜单
### 🎨 界面特性
- ✅ **深色主题**:现代化的深色界面设计
- ✅ **响应式布局**:自适应窗口大小
- ✅ **侧边栏导航**:可展开/收起的导航菜单
- ✅ **模态对话框**:创建项目和导入项目对话框
- ✅ **拖拽支持**:文件拖拽导入功能
## 安装和运行
### 1. 环境要求
- Python 3.7+
- PyQt5 5.15.0+
### 2. 安装依赖
```bash
pip install -r requirements.txt
```
### 3. 运行应用
```bash
python main.py
```
## 项目结构
```
MetaCore/
├── main.py # 主程序入口
├── requirements.txt # 依赖包列表
├── README_PyQt5.md # 说明文档
├── data/ # 数据模块
│ ├── __init__.py
│ ├── project_manager.py # 项目数据管理器
│ └── projects.json # 项目数据文件(自动生成)
└── ui/ # 界面模块
├── __init__.py
├── main_window.py # 主窗口
├── sidebar.py # 侧边栏组件
├── project_area.py # 项目显示区域
├── project_card.py # 项目卡片组件
├── create_project_dialog.py # 创建项目对话框
├── import_project_dialog.py # 导入项目对话框
└── styles.py # 样式表定义
```
## 主要组件说明
### 1. 主窗口 (MainWindow)
- 整体布局管理
- 菜单栏和状态栏
- 组件间信号连接
- 窗口事件处理
### 2. 侧边栏 (Sidebar)
- Logo区域
- 快速操作按钮
- 可展开的导航菜单
- 用户信息显示
### 3. 项目区域 (ProjectArea)
- 工具栏和搜索区域
- 网格/列表视图切换
- 项目卡片容器
- 空状态显示
### 4. 项目卡片 (ProjectCard)
- 三段式布局设计
- 悬停效果
- 右键菜单
- 收藏状态显示
### 5. 创建项目对话框 (CreateProjectDialog)
- 2:1布局比例
- 模板选择网格
- 项目信息表单
- 文件夹选择功能
### 6. 导入项目对话框 (ImportProjectDialog)
- 文件拖拽上传
- 导入选项配置
- 文件列表显示
- 进度反馈
## 数据管理
### 项目数据结构
```python
{
"id": 1,
"title": "项目名称",
"date": "2024-06-08 15:56:35",
"type": "industrial",
"image": "🏭",
"favorite": true,
"path": "/path/to/project"
}
```
### 支持的项目类型
- `industrial` - 工业项目 🏭
- `smart` - 智能项目 💧
- `vr` - VR项目 🥽
- `game` - 游戏项目 🎮
- `design` - 设计项目 🎨
- `empty` - 空白项目 📁
## 样式系统
### 主题色彩
- **主色调**:深灰色 (#1a1a1a, #2a2a2a)
- **边框色**:中灰色 (#3a3a3a, #4a4a4a)
- **强调色**:紫色 (#8b5cf6, #7c3aed)
- **文字色**:白色/灰色 (#ffffff, #cccccc, #888888)
### 组件样式
- 所有组件都有对应的ObjectName用于样式选择器
- 支持悬停状态和选中状态
- 统一的圆角和间距设计
- 平滑的过渡动画效果
## 信号和槽机制
### 主要信号
- `projects_changed` - 项目列表变化
- `project_added` - 项目添加
- `project_removed` - 项目删除
- `project_updated` - 项目更新
- `filter_changed` - 过滤条件变化
- `search_changed` - 搜索内容变化
### 事件处理
- 鼠标点击事件
- 键盘快捷键
- 拖拽事件
- 窗口事件
## 扩展功能
### 可以添加的功能
1. **项目模板系统**:真实的项目模板创建和应用
2. **文件管理**:项目内文件的管理和预览
3. **版本控制**Git集成和版本管理
4. **协作功能**:多用户协作和权限管理
5. **插件系统**:支持第三方插件扩展
6. **云同步**:项目数据云端同步
7. **导出功能**:项目打包和分享
### 性能优化
1. **虚拟滚动**:大量项目时的性能优化
2. **缓存机制**:图片和数据缓存
3. **异步加载**:大文件异步处理
4. **内存管理**:及时释放不用的资源
## 开发说明
### 添加新组件
1. 在`ui/`目录下创建新的组件文件
2. 继承适当的PyQt5基类
3. 实现`init_ui()`方法
4. 添加必要的信号定义
5. 在样式表中添加对应样式
### 修改样式
1. 在`ui/styles.py`中修改样式定义
2. 使用ObjectName作为选择器
3. 支持状态选择器(:hover, :checked等
4. 遵循现有的颜色和间距规范
### 数据持久化
- 项目数据自动保存到`data/projects.json`
- 应用关闭时自动保存
- 启动时自动加载历史数据
- 支持数据备份和恢复
## 与Web版本对比
### 相同功能
- ✅ 完全相同的界面布局
- ✅ 相同的项目管理功能
- ✅ 相同的视觉设计风格
- ✅ 相同的用户交互逻辑
### PyQt5版本优势
- ✅ 原生桌面应用体验
- ✅ 更好的性能表现
- ✅ 系统集成度更高
- ✅ 离线使用支持
- ✅ 文件系统直接访问
### 技术差异
- **Web版本**HTML + CSS + JavaScript
- **PyQt5版本**Python + PyQt5 + 自定义样式表
- **数据存储**JSON文件 vs 浏览器存储
- **文件操作**系统API vs Web API限制
这个PyQt5版本完全复制了Web版本的功能和外观同时提供了更好的桌面应用体验。

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1 @@
# 数据模块

File diff suppressed because it is too large Load Diff

67
MetaCore/main.py Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MetaCore - 项目管理平台 (PyQt5版本)
"""
import sys
import json
import os
from datetime import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
# 导入自定义组件
from MetaCore.ui.main_window import MainWindow
from MetaCore.ui.styles import StyleSheet
from MetaCore.data.project_manager import ProjectManager
class MetaCoreApp(QApplication):
"""MetaCore应用程序主类"""
def __init__(self, argv):
super().__init__(argv)
# 设置应用程序信息
self.setApplicationName("MetaCore")
self.setApplicationVersion("1.0.0")
self.setOrganizationName("MetaCore Team")
# 设置应用程序图标(如果图标文件存在)
icon_path = "resources/icons/app_icon.png"
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
# 初始化数据管理器
self.project_manager = ProjectManager()
# 创建主窗口
self.main_window = MainWindow(self.project_manager)
# 应用样式表
self.setStyleSheet(StyleSheet.get_main_style())
# 显示主窗口
self.main_window.show()
def closeEvent(self, event):
"""应用程序关闭事件"""
# 保存项目数据
self.project_manager.save_projects()
event.accept()
def main():
"""主函数"""
app = MetaCoreApp(sys.argv)
# 设置高DPI支持
app.setAttribute(Qt.AA_EnableHighDpiScaling, True)
app.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# 运行应用程序
sys.exit(app.exec_())
if __name__ == "__main__":
main()

24
MetaCore/requirements.txt Normal file
View File

@ -0,0 +1,24 @@
# MetaCore PyQt5版本依赖包
# ================================
# 必需依赖 - 核心GUI框架
PyQt5>=5.15.0
# 必需依赖 - 打包工具(用于构建可执行文件)
PyInstaller>=5.0.0
# 推荐依赖 - 图像处理(更好的图标和图片支持)
Pillow>=8.0.0
# 可选依赖 - 系统信息(系统集成功能)
psutil>=5.8.0
# 开发依赖 - 类型检查(开发时使用)
# typing-extensions>=4.0.0 # Python 3.7兼容性
# 注意以下是Python标准库无需安装
# - sys, os, json, datetime (系统和文件操作)
# - pathlib (路径处理)
# - subprocess (进程管理)
# - platform (平台信息)
# - typing (类型提示)

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
示例脚本 - 演示如何编写脚本
"""
from core.script_system import ScriptBase
class ExampleScript(ScriptBase):
"""示例脚本类"""
def __init__(self):
super().__init__()
self.counter = 0
self.rotation_speed = 30.0 # 度/秒
def start(self):
"""脚本开始时调用"""
self.log("示例脚本开始运行!")
self.log(f"挂载到对象: {self.gameObject.getName()}")
def update(self, dt):
"""每帧更新"""
self.counter += 1
# 每60帧输出一次信息
if self.counter % 60 == 0:
self.log(f"运行了 {self.counter}")
# 让对象旋转
if self.transform:
current_h = self.transform.getH()
new_h = current_h + self.rotation_speed * dt
self.transform.setH(new_h)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("示例脚本被销毁")
def on_enable(self):
"""脚本启用时调用"""
self.log("示例脚本被启用")
def on_disable(self):
"""脚本禁用时调用"""
self.log("示例脚本被禁用")

225
MetaCore/test_open.py Normal file
View File

@ -0,0 +1,225 @@
import subprocess
import os
import sys
from pathlib import Path
def run_cmd_command(command, cwd=None, shell=True, timeout=30):
"""
执行CMD命令的通用方法
Args:
command (str or list): 要执行的命令
cwd (str, optional): 工作目录
shell (bool): 是否使用shell执行命令
timeout (int): 超时时间
Returns:
tuple: (return_code, stdout, stderr)
"""
try:
# 执行命令
result = subprocess.run(
command,
cwd=cwd,
shell=shell,
capture_output=True,
text=True,
timeout=timeout,
encoding='utf-8'
)
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return -1, "", "命令执行超时"
except Exception as e:
return -2, "", f"执行命令时出错: {str(e)}"
def run_cmd_with_venv(venv_path, command, cwd=None, timeout=30):
"""
在指定虚拟环境中执行CMD命令
Args:
venv_path (str): 虚拟环境路径
command (str): 要执行的命令
cwd (str, optional): 工作目录
timeout (int): 超时时间
Returns:
tuple: (return_code, stdout, stderr)
"""
try:
# 激活虚拟环境并执行命令
if os.name == 'nt': # Windows
activate_script = Path(venv_path) / 'Scripts' / 'activate.bat'
full_command = f'"{activate_script}" && {command}'
else: # Unix-like systems
activate_script = Path(venv_path) / 'bin' / 'activate'
full_command = f'source "{activate_script}" && {command}'
return run_cmd_command(full_command, cwd=cwd, timeout=timeout)
except Exception as e:
return -2, "", f"在虚拟环境中执行命令时出错: {str(e)}"
def run_python_in_venv(venv_path, script_path, args=None, cwd=None, timeout=30):
"""
在指定虚拟环境中运行Python脚本
Args:
venv_path (str): 虚拟环境路径
script_path (str): Python脚本路径
args (list, optional): 脚本参数
cwd (str, optional): 工作目录
timeout (int): 超时时间
Returns:
tuple: (return_code, stdout, stderr)
"""
try:
# 获取虚拟环境中的Python解释器
if os.name == 'nt': # Windows
python_executable = Path(venv_path) / 'Scripts' / 'python.exe'
else: # Unix-like systems
python_executable = Path(venv_path) / 'bin' / 'python'
# 构建命令
cmd = [str(python_executable), str(script_path)]
if args:
cmd.extend(args)
# 设置环境变量
env = os.environ.copy()
env['VIRTUAL_ENV'] = str(venv_path)
if os.name == 'nt': # Windows
env['PATH'] = str(Path(venv_path) / 'Scripts') + os.pathsep + env['PATH']
else: # Unix-like
env['PATH'] = str(Path(venv_path) / 'bin') + os.pathsep + env['PATH']
# 执行命令
result = subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout,
encoding='utf-8',
env=env
)
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return -1, "", "命令执行超时"
except Exception as e:
return -2, "", f"在虚拟环境中运行Python脚本时出错: {str(e)}"
def execute_commands_sequentially(commands, cwd=None, timeout=30):
"""
顺序执行多个命令
Args:
commands (list): 命令列表
cwd (str, optional): 工作目录
timeout (int): 超时时间
Returns:
list: 每个命令的执行结果列表
"""
results = []
for i, command in enumerate(commands):
print(f"执行命令 {i + 1}/{len(commands)}: {command}")
return_code, stdout, stderr = run_cmd_command(command, cwd=cwd, timeout=timeout)
results.append({
'command': command,
'return_code': return_code,
'stdout': stdout,
'stderr': stderr
})
# 如果命令执行失败,停止执行后续命令
if return_code != 0:
print(f"命令执行失败,停止执行后续命令: {command}")
break
return results
def run_text():
# 示例1: 执行简单命令
print("=== 示例1: 执行简单命令 ===")
return_code, stdout, stderr = run_cmd_command("dir" if os.name == 'nt' else "ls -la")
print(f"返回码: {return_code}")
print(f"输出: {stdout}")
if stderr:
print(f"错误: {stderr}")
# 示例2: 在虚拟环境中执行命令
print("\n=== 示例2: 在虚拟环境中执行命令 ===")
# return_code, stdout, stderr = run_cmd_with_venv(
# r"C:\path\to\your\venv",
# "pip list"
# )
# print(f"返回码: {return_code}")
# print(f"输出: {stdout}")
# 示例3: 在虚拟环境中运行Python脚本
print("\n=== 示例3: 在虚拟环境中运行Python脚本 ===")
# return_code, stdout, stderr = run_python_in_venv(
# r"C:\path\to\your\venv",
# r"C:\path\to\your\script.py",
# args=["arg1", "arg2"]
# )
# print(f"返回码: {return_code}")
# print(f"输出: {stdout}")
# 示例4: 顺序执行多个命令
print("\n=== 示例4: 顺序执行多个命令 ===")
commands = [
"echo Hello",
"echo World",
"python --version"
]
results = execute_commands_sequentially(commands)
for result in results:
print(f"命令: {result['command']}")
print(f"返回码: {result['return_code']}")
print(f"输出: {result['stdout']}")
if result['stderr']:
print(f"错误: {result['stderr']}")
print("-" * 40)
# 使用示例
if __name__ == "__main__":
venv_path = r"d:\PythonProject\CH_EG\EG\.venv"
script_path = r"d:\PythonProject\CH_EG\EG\Start_Run.py"
print(f"虚拟环境路径: {venv_path}")
print(f"脚本路径: {script_path}")
# 检查路径是否存在
if not os.path.exists(venv_path):
print(f"错误: 虚拟环境路径不存在: {venv_path}")
sys.exit(1)
if not os.path.exists(script_path):
print(f"错误: 脚本文件不存在: {script_path}")
sys.exit(1)
# 在虚拟环境中运行Python脚本
return_code, stdout, stderr = run_python_in_venv(venv_path, script_path)
print(f"返回码: {return_code}")
print(f"标准输出: {stdout}")
print(f"错误输出: {stderr}")
if return_code == 0:
print("脚本执行成功!")
else:
print("脚本执行失败!")

1
MetaCore/ui/__init__.py Normal file
View File

@ -0,0 +1 @@
# UI模块

View File

@ -0,0 +1,460 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
创建项目对话框
"""
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from MetaCore.data.project_manager import ProjectManager
class CreateProjectDialog(QDialog):
"""创建项目对话框"""
def __init__(self, project_manager: ProjectManager, project_settings_page=None, parent=None):
super().__init__(parent)
self.project_manager = project_manager
self.project_settings_page = project_settings_page
self.selected_template = "empty"
self.selected_path = ""
self.init_ui()
self.connect_signals()
# 设置对话框属性
self.setWindowTitle("创建新的项目")
self.setModal(True)
self.setMinimumSize(1000, 725)
self.resize(1000, 725) # 设置默认尺寸
# 设置默认项目位置
self.set_default_location_from_settings()
# 居中显示
self.center_dialog()
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 标题栏 - 使用默认系统标题栏
# self.create_title_bar(layout)
# 主要内容区域
self.create_main_content(layout)
# 底部按钮
self.create_button_area(layout)
def create_main_content(self, layout):
"""创建主要内容"""
content_widget = QWidget()
content_layout = QHBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(0)
# 左侧模板选择区域 (2/3)
self.create_template_section(content_layout)
# 右侧项目信息区域 (1/3)
self.create_project_info_section(content_layout)
layout.addWidget(content_widget)
def create_template_section(self, layout):
"""创建模板选择区域"""
template_widget = QWidget()
template_widget.setObjectName("templateSection")
template_layout = QVBoxLayout(template_widget)
template_layout.setContentsMargins(30, 30, 20, 30)
# 标题
title_label = QLabel("选择项目模板")
title_label.setObjectName("sectionTitle")
template_layout.addWidget(title_label)
# 模板网格
self.create_template_grid(template_layout)
# 设置固定比例 (2/3)
template_widget.setMinimumWidth(600)
layout.addWidget(template_widget, 2)
def create_template_grid(self, layout):
"""创建模板网格"""
# 滚动区域
scroll_area = QScrollArea()
scroll_area.setObjectName("templateScrollArea")
scroll_area.setWidgetResizable(True)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# 模板容器
templates_widget = QWidget()
templates_widget.setObjectName("templatesContainer")
templates_layout = QGridLayout(templates_widget)
templates_layout.setSpacing(20) # 增加间距,适配固定尺寸按钮
templates_layout.setContentsMargins(10, 10, 10, 10) # 添加容器边距
templates_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) # 左上对齐
# 模板数据 - 当前只有空白项目模板,后续可以添加更多模板
templates = [
("empty", "📄", "空白项目模板"),
# 后续可以添加的模板示例:
# ("web", "🌐", "Web应用模板"),
# ("mobile", "📱", "移动应用模板"),
# ("desktop", "🖥️", "桌面应用模板"),
# ("api", "🔌", "API服务模板"),
# ("data", "📊", "数据分析模板"),
# ("game", "🎮", "游戏开发模板"),
]
# 创建模板项
self.template_buttons = []
for i, (template_id, icon, name) in enumerate(templates):
row = i // 3 # 每行最多3个模板为后续扩展做准备
col = i % 3
template_btn = self.create_template_item(template_id, icon, name)
templates_layout.addWidget(template_btn, row, col)
self.template_buttons.append(template_btn)
# 添加弹性空间,确保模板按钮不会被拉伸
templates_layout.setRowStretch(templates_layout.rowCount(), 1)
templates_layout.setColumnStretch(templates_layout.columnCount(), 1)
scroll_area.setWidget(templates_widget)
layout.addWidget(scroll_area)
def create_template_item(self, template_id, icon, name):
"""创建模板项"""
template_btn = QPushButton()
template_btn.setObjectName("templateItem")
template_btn.setCheckable(True)
# 设置固定尺寸,确保所有模板按钮大小一致,便于后续添加多个模板
template_btn.setFixedSize(180, 140)
# 设置默认选中
if template_id == "empty":
template_btn.setChecked(True)
# 布局
btn_layout = QVBoxLayout(template_btn)
btn_layout.setAlignment(Qt.AlignCenter)
btn_layout.setSpacing(12)
btn_layout.setContentsMargins(20, 24, 20, 24)
# 图标
icon_label = QLabel(icon)
icon_label.setObjectName("templateIcon")
icon_label.setAlignment(Qt.AlignCenter)
btn_layout.addWidget(icon_label)
# 名称
name_label = QLabel(name)
name_label.setObjectName("templateName")
name_label.setAlignment(Qt.AlignCenter)
name_label.setWordWrap(True)
btn_layout.addWidget(name_label)
# 点击事件
def on_clicked():
# 取消其他按钮的选中状态
for btn in self.template_buttons:
if btn != template_btn:
btn.setChecked(False)
template_btn.setChecked(True)
self.selected_template = template_id
self.update_template_description(template_id)
template_btn.clicked.connect(on_clicked)
return template_btn
def create_project_info_section(self, layout):
"""创建项目信息区域"""
info_widget = QWidget()
info_widget.setObjectName("projectInfoSection")
info_layout = QVBoxLayout(info_widget)
info_layout.setContentsMargins(20, 30, 30, 30)
# 标题
title_label = QLabel("项目信息")
title_label.setObjectName("sectionTitle")
info_layout.addWidget(title_label)
# 模板描述
self.description_label = QLabel("创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。")
self.description_label.setObjectName("templateDescription")
self.description_label.setWordWrap(True)
info_layout.addWidget(self.description_label)
# 表单
self.create_project_form(info_layout)
info_layout.addStretch()
# 设置固定比例 (1/3),增加最大宽度以容纳更长的错误信息
info_widget.setMaximumWidth(350) # 从300增加到350
layout.addWidget(info_widget, 1)
def create_project_form(self, layout):
"""创建项目表单"""
form_layout = QVBoxLayout()
form_layout.setSpacing(18)
# 项目名称
name_group = QVBoxLayout()
name_group.setSpacing(4) # 减小间距,让错误提示更紧贴
name_label = QLabel("项目名称")
name_label.setObjectName("formLabel")
name_group.addWidget(name_label)
self.name_input = QLineEdit()
self.name_input.setObjectName("formInput")
self.name_input.setPlaceholderText("输入项目名称")
name_group.addWidget(self.name_input)
# 错误提示标签 - 紧贴在输入框下方的小红字
self.name_error_label = QLabel("")
self.name_error_label.setObjectName("errorLabel")
self.name_error_label.setWordWrap(False) # 不换行,保持一行显示
self.name_error_label.setFixedHeight(18) # 固定高度,一行小字
self.name_error_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.name_error_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.name_error_label.hide() # 初始隐藏
name_group.addWidget(self.name_error_label)
form_layout.addLayout(name_group)
# 项目描述
desc_group = QVBoxLayout()
desc_label = QLabel("项目描述")
desc_label.setObjectName("formLabel")
desc_group.addWidget(desc_label)
self.desc_input = QTextEdit()
self.desc_input.setObjectName("formTextArea")
self.desc_input.setPlaceholderText("输入项目描述(可选)")
self.desc_input.setMaximumHeight(70)
desc_group.addWidget(self.desc_input)
form_layout.addLayout(desc_group)
# 项目位置
location_group = QVBoxLayout()
location_label = QLabel("项目位置")
location_label.setObjectName("formLabel")
location_group.addWidget(location_label)
location_layout = QHBoxLayout()
self.location_input = QLineEdit()
self.location_input.setObjectName("formInput")
self.location_input.setPlaceholderText("点击浏览选择位置")
self.location_input.setReadOnly(True)
location_layout.addWidget(self.location_input)
browse_btn = QPushButton("📁")
browse_btn.setObjectName("browseBtn")
browse_btn.setFixedSize(32, 32) # 减小尺寸40x32 -> 32x32
browse_btn.setToolTip("选择项目保存位置")
browse_btn.clicked.connect(self.browse_location)
location_layout.addWidget(browse_btn)
location_group.addLayout(location_layout)
form_layout.addLayout(location_group)
layout.addLayout(form_layout)
def create_button_area(self, layout):
"""创建按钮区域"""
button_widget = QWidget()
button_widget.setObjectName("buttonArea")
button_layout = QHBoxLayout(button_widget)
button_layout.setContentsMargins(30, 20, 30, 30)
button_layout.setSpacing(10) # 设置按钮间距
button_layout.addStretch()
# 取消按钮
cancel_btn = QPushButton("取消")
cancel_btn.setObjectName("secondaryBtn")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
# 创建按钮
self.create_btn = QPushButton("创建项目")
self.create_btn.setObjectName("primaryBtn")
self.create_btn.clicked.connect(self.create_project)
button_layout.addWidget(self.create_btn)
layout.addWidget(button_widget)
def update_template_description(self, template_id):
"""更新模板描述"""
descriptions = {
"empty": "创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。",
}
description = descriptions.get(template_id, "项目模板描述")
self.description_label.setText(description)
def browse_location(self):
"""浏览项目位置"""
# 使用项目设置中的默认位置作为起始目录
if self.project_settings_page:
start_dir = self.project_settings_page.get_default_project_location()
else:
start_dir = os.path.expanduser("~/Documents")
folder = QFileDialog.getExistingDirectory(self, "选择项目保存位置", start_dir)
if folder:
self.selected_path = folder
self.location_input.setText(folder)
# 触发验证
self.validate_input()
def create_project(self):
"""创建项目"""
# 验证输入
name = self.name_input.text().strip()
if not name:
QMessageBox.warning(self, "输入错误", "请输入项目名称")
return
if not self.selected_path:
QMessageBox.warning(self, "输入错误", "请选择项目保存位置")
return
# 使用项目管理器的验证方法进行全面检查
is_valid, error_message = self.project_manager.validate_project_creation(name, self.selected_path)
if not is_valid:
QMessageBox.warning(self, "创建失败", error_message)
return
# 创建项目
description = self.desc_input.toPlainText().strip()
try:
project = self.project_manager.add_project(
name, description, self.selected_template, self.selected_path
)
# 显示详细的成功信息
success_msg = f"""项目创建成功!
项目名称: {name}
项目目录: {project.project_dir}
项目文件结构已创建完成包含
models/ - 模型文件夹
textures/ - 贴图文件夹
scenes/ - 场景文件夹
project.json - 项目配置文件
您可以开始在此基础上开发您的应用程序"""
QMessageBox.information(self, "创建成功", success_msg)
self.accept()
except Exception as e:
QMessageBox.critical(self, "创建失败", f"项目创建失败:{str(e)}")
def get_template_name(self, template_id):
"""获取模板名称"""
template_names = {
"empty": "空白项目模板",
}
return template_names.get(template_id, "空白项目模板")
def connect_signals(self):
"""连接信号"""
# 连接项目名称输入框的文本变化信号
self.name_input.textChanged.connect(self.validate_input)
# 连接路径选择的变化
self.location_input.textChanged.connect(self.validate_input)
def validate_input(self):
"""实时验证输入"""
name = self.name_input.text().strip()
path = self.selected_path
# 重置样式属性
self.name_input.setProperty("error", False)
self.name_input.setProperty("valid", False)
self.create_btn.setEnabled(True)
# 如果名称为空,清空错误提示(用户可能还在输入)
if not name:
self.name_error_label.hide()
self.name_input.style().polish(self.name_input)
return
# 如果路径未选择,不进行完整验证
if not path:
self.name_error_label.hide()
self.name_input.style().polish(self.name_input)
return
# 进行验证
is_valid, error_message = self.project_manager.validate_project_creation(name, path)
if not is_valid:
# 设置错误状态
self.name_input.setProperty("error", True)
self.name_input.setProperty("valid", False)
# 禁用创建按钮
self.create_btn.setEnabled(False)
# 显示错误信息 - 一行小红字
self.name_error_label.setText(error_message)
self.name_error_label.show()
else:
# 设置成功状态
self.name_input.setProperty("error", False)
self.name_input.setProperty("valid", True)
# 隐藏错误信息
self.name_error_label.hide()
# 刷新样式
self.name_input.style().polish(self.name_input)
def set_default_location(self, location: str):
"""设置默认项目位置"""
if location and os.path.exists(location):
self.selected_path = location
self.location_input.setText(location)
# 触发验证
self.validate_input()
def set_default_location_from_settings(self):
"""从项目设置中设置默认项目位置"""
if self.project_settings_page:
default_location = self.project_settings_page.get_default_project_location()
if default_location:
self.set_default_location(default_location)
def center_dialog(self):
"""对话框居中"""
if self.parent():
parent_rect = self.parent().geometry()
x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
self.move(x, y)
else:
# 如果没有父窗口,则在屏幕中央显示
screen = QApplication.desktop().screenGeometry()
x = (screen.width() - self.width()) // 2
y = (screen.height() - self.height()) // 2
self.move(x, y)

238
MetaCore/ui/file_watcher.py Normal file
View File

@ -0,0 +1,238 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文件系统监控器
实时监测目录和文件的变化
"""
import os
from PyQt5.QtCore import QObject, QFileSystemWatcher, pyqtSignal, QTimer
from PyQt5.QtWidgets import QMessageBox
class FileWatcher(QObject):
"""文件系统监控器"""
# 信号定义
directory_changed = pyqtSignal(str) # 目录变化信号
file_changed = pyqtSignal(str) # 文件变化信号
project_added = pyqtSignal(str) # 项目添加信号
project_removed = pyqtSignal(str) # 项目删除信号
def __init__(self, parent=None):
super().__init__(parent)
# 创建文件系统监控器
self.watcher = QFileSystemWatcher()
# 连接信号
self.watcher.directoryChanged.connect(self.on_directory_changed)
self.watcher.fileChanged.connect(self.on_file_changed)
# 监控的路径列表
self.watched_directories = set()
self.watched_files = set()
# 目录内容缓存(用于检测具体变化)
self.directory_contents = {}
# 延迟处理定时器(避免频繁触发)
self.change_timer = QTimer()
self.change_timer.setSingleShot(True)
self.change_timer.timeout.connect(self.process_pending_changes)
self.pending_changes = set()
def add_directory(self, directory_path):
"""
添加目录监控
Args:
directory_path (str): 要监控的目录路径
"""
if not os.path.exists(directory_path):
print(f"警告: 目录不存在 - {directory_path}")
return False
if not os.path.isdir(directory_path):
print(f"警告: 路径不是目录 - {directory_path}")
return False
# 规范化路径
directory_path = os.path.abspath(directory_path)
if directory_path not in self.watched_directories:
# 添加到监控列表
self.watcher.addPath(directory_path)
self.watched_directories.add(directory_path)
# 缓存目录内容
self.cache_directory_contents(directory_path)
print(f"开始监控目录: {directory_path}")
return True
else:
print(f"目录已在监控中: {directory_path}")
return False
def remove_directory(self, directory_path):
"""
移除目录监控
Args:
directory_path (str): 要移除监控的目录路径
"""
directory_path = os.path.abspath(directory_path)
if directory_path in self.watched_directories:
self.watcher.removePath(directory_path)
self.watched_directories.remove(directory_path)
# 清除缓存
if directory_path in self.directory_contents:
del self.directory_contents[directory_path]
print(f"停止监控目录: {directory_path}")
return True
else:
print(f"目录未在监控中: {directory_path}")
return False
def add_file(self, file_path):
"""
添加文件监控
Args:
file_path (str): 要监控的文件路径
"""
if not os.path.exists(file_path):
print(f"警告: 文件不存在 - {file_path}")
return False
if not os.path.isfile(file_path):
print(f"警告: 路径不是文件 - {file_path}")
return False
file_path = os.path.abspath(file_path)
if file_path not in self.watched_files:
self.watcher.addPath(file_path)
self.watched_files.add(file_path)
print(f"开始监控文件: {file_path}")
return True
else:
print(f"文件已在监控中: {file_path}")
return False
def remove_file(self, file_path):
"""
移除文件监控
Args:
file_path (str): 要移除监控的文件路径
"""
file_path = os.path.abspath(file_path)
if file_path in self.watched_files:
self.watcher.removePath(file_path)
self.watched_files.remove(file_path)
print(f"停止监控文件: {file_path}")
return True
else:
print(f"文件未在监控中: {file_path}")
return False
def cache_directory_contents(self, directory_path):
"""缓存目录内容"""
try:
contents = set(os.listdir(directory_path))
self.directory_contents[directory_path] = contents
except OSError as e:
print(f"无法读取目录内容: {directory_path} - {e}")
def on_directory_changed(self, directory_path):
"""目录变化处理"""
print(f"检测到目录变化: {directory_path}")
# 添加到待处理变化列表
self.pending_changes.add(directory_path)
# 启动延迟处理定时器
self.change_timer.start(500) # 500ms延迟
def on_file_changed(self, file_path):
"""文件变化处理"""
print(f"检测到文件变化: {file_path}")
# 立即发出文件变化信号
self.file_changed.emit(file_path)
def process_pending_changes(self):
"""处理待处理的目录变化"""
for directory_path in self.pending_changes:
self.analyze_directory_changes(directory_path)
self.pending_changes.clear()
def analyze_directory_changes(self, directory_path):
"""分析目录具体变化"""
if not os.path.exists(directory_path):
print(f"目录已被删除: {directory_path}")
self.directory_changed.emit(directory_path)
return
try:
# 获取当前目录内容
current_contents = set(os.listdir(directory_path))
# 获取之前缓存的内容
previous_contents = self.directory_contents.get(directory_path, set())
# 检测新增的项目
added_items = current_contents - previous_contents
for item in added_items:
item_path = os.path.join(directory_path, item)
if os.path.isdir(item_path):
print(f"检测到新增目录: {item_path}")
self.project_added.emit(item_path)
else:
print(f"检测到新增文件: {item_path}")
# 检测删除的项目
removed_items = previous_contents - current_contents
for item in removed_items:
item_path = os.path.join(directory_path, item)
print(f"检测到删除项目: {item_path}")
self.project_removed.emit(item_path)
# 更新缓存
self.directory_contents[directory_path] = current_contents
# 发出目录变化信号
if added_items or removed_items:
self.directory_changed.emit(directory_path)
except OSError as e:
print(f"分析目录变化失败: {directory_path} - {e}")
def get_watched_directories(self):
"""获取所有监控的目录"""
return list(self.watched_directories)
def get_watched_files(self):
"""获取所有监控的文件"""
return list(self.watched_files)
def clear_all_watches(self):
"""清除所有监控"""
# 移除所有路径
for path in list(self.watched_directories):
self.remove_directory(path)
for path in list(self.watched_files):
self.remove_file(path)
print("已清除所有文件监控")
def is_watching(self, path):
"""检查是否正在监控指定路径"""
path = os.path.abspath(path)
return path in self.watched_directories or path in self.watched_files

197
MetaCore/ui/icon_manager.py Normal file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
图标管理器
统一管理应用程序中使用的所有图标
"""
import os
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QSize
class IconManager:
"""图标管理器"""
# 图标目录路径
ICONS_DIR = "Resources/Icons"
# 图标文件映射
ICON_FILES = {
# 应用程序图标
'app': 'app_icon.png',
'logo': 'logo.png',
# 操作图标
'create': 'create.png',
'import': 'import.png',
'folder': 'folder.png',
'delete': 'delete.png',
'Refresh': 'Refresh.png',
# 视图图标
'grid_view': 'grid_view.png',
'list_view': 'list_view.png',
# 导航图标
'overview': 'overview.png',
'management': 'management.png',
'resource_category': 'category.png',
'resource_management': 'resource.png',
'project_settings': 'settings.png',
'system_settings': 'system.png',
# 树箭头
'down': 'down.png',
'right': 'right.png',
}
# 图标缓存
_icon_cache = {}
@classmethod
def get_icon(cls, icon_name: str, size: QSize = None) -> QIcon:
"""
获取图标
Args:
icon_name (str): 图标名称
size (QSize, optional): 图标尺寸
Returns:
QIcon: 图标对象如果文件不存在则返回空图标
"""
# 检查缓存
cache_key = f"{icon_name}_{size.width() if size else 'default'}_{size.height() if size else 'default'}"
if cache_key in cls._icon_cache:
return cls._icon_cache[cache_key]
# 获取图标文件路径
icon_path = cls.get_icon_path(icon_name)
if not icon_path or not os.path.exists(icon_path):
# 如果图标文件不存在,返回空图标
icon = QIcon()
else:
# 创建图标
if size:
pixmap = QPixmap(icon_path).scaled(size, aspectRatioMode=1, transformMode=1)
icon = QIcon(pixmap)
else:
icon = QIcon(icon_path)
# 缓存图标
cls._icon_cache[cache_key] = icon
return icon
@classmethod
def get_icon_path(cls, icon_name: str) -> str:
"""
获取图标文件路径
Args:
icon_name (str): 图标名称
Returns:
str: 图标文件的完整路径如果不存在则返回空字符串
"""
if icon_name not in cls.ICON_FILES:
return ""
icon_file = cls.ICON_FILES[icon_name]
icon_path = os.path.join(cls.ICONS_DIR, icon_file)
return icon_path
@classmethod
def get_pixmap(cls, icon_name: str, size: QSize = None) -> QPixmap:
"""
获取图标的 QPixmap 对象
Args:
icon_name (str): 图标名称
size (QSize, optional): 图标尺寸
Returns:
QPixmap: 图标的 QPixmap 对象
"""
icon_path = cls.get_icon_path(icon_name)
if not icon_path or not os.path.exists(icon_path):
return QPixmap()
pixmap = QPixmap(icon_path)
if size:
pixmap = pixmap.scaled(size, aspectRatioMode=1, transformMode=1)
return pixmap
@classmethod
def icon_exists(cls, icon_name: str) -> bool:
"""
检查图标是否存在
Args:
icon_name (str): 图标名称
Returns:
bool: 图标文件是否存在
"""
icon_path = cls.get_icon_path(icon_name)
return icon_path and os.path.exists(icon_path)
@classmethod
def get_project_type_icon(cls, project_type: str) -> QIcon:
"""
根据项目类型获取对应图标
Args:
project_type (str): 项目类型
Returns:
QIcon: 项目类型对应的图标
"""
icon_name = f"project_{project_type}"
if icon_name in cls.ICON_FILES:
return cls.get_icon(icon_name)
else:
# 如果没有对应的项目类型图标,返回默认项目图标
return cls.get_icon('project_empty')
@classmethod
def clear_cache(cls):
"""清空图标缓存"""
cls._icon_cache.clear()
@classmethod
def ensure_icons_directory(cls):
"""确保图标目录存在"""
if not os.path.exists(cls.ICONS_DIR):
os.makedirs(cls.ICONS_DIR, exist_ok=True)
@classmethod
def get_available_icons(cls) -> list:
"""
获取所有可用的图标列表
Returns:
list: 可用图标名称列表
"""
available_icons = []
for icon_name in cls.ICON_FILES:
if cls.icon_exists(icon_name):
available_icons.append(icon_name)
return available_icons
@classmethod
def get_missing_icons(cls) -> list:
"""
获取缺失的图标列表
Returns:
list: 缺失图标名称列表
"""
missing_icons = []
for icon_name in cls.ICON_FILES:
if not cls.icon_exists(icon_name):
missing_icons.append(icon_name)
return missing_icons

View File

@ -0,0 +1,468 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
导入项目对话框
"""
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from MetaCore.data.project_manager import ProjectManager
class ImportProjectDialog(QDialog):
"""导入项目对话框"""
def __init__(self, project_manager: ProjectManager, project_settings_page=None, parent=None):
super().__init__(parent)
self.project_manager = project_manager
self.project_settings_page = project_settings_page
self.selected_files = []
self.init_ui()
self.connect_signals()
# 设置对话框属性
self.setWindowTitle("导入文件夹")
self.setModal(True)
self.setMinimumSize(1000, 725)
self.resize(1000, 725) # 设置默认尺寸
# 居中显示
self.center_dialog()
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 标题栏
# self.create_title_bar(layout)
# 主要内容区域
self.create_main_content(layout)
# 底部按钮
self.create_button_area(layout)
def create_title_bar(self, layout):
"""创建标题栏"""
title_widget = QWidget()
title_widget.setObjectName("dialogTitle")
title_layout = QHBoxLayout(title_widget)
title_layout.setContentsMargins(30, 20, 30, 20)
# 标题
title_label = QLabel("导入项目文件")
title_label.setObjectName("titleLabel")
title_layout.addWidget(title_label)
title_layout.addStretch()
# 关闭按钮
close_btn = QPushButton("")
close_btn.setObjectName("closeBtn")
close_btn.setFixedSize(32, 32)
close_btn.clicked.connect(self.reject)
title_layout.addWidget(close_btn)
layout.addWidget(title_widget)
def create_main_content(self, layout):
"""创建主要内容"""
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(30, 30, 30, 30)
content_layout.setSpacing(20)
# 导入区域
self.create_import_area(content_layout)
# 导入选项
self.create_import_options(content_layout)
layout.addWidget(content_widget)
def create_import_area(self, layout):
"""创建导入区域"""
import_group = QGroupBox("选择文件夹")
import_group.setObjectName("importGroup")
import_layout = QVBoxLayout(import_group)
import_layout.setSpacing(15)
# 拖拽上传区域
self.upload_area = UploadArea()
self.upload_area.files_dropped.connect(self.on_files_dropped)
self.upload_area.select_files_requested.connect(self.select_files)
import_layout.addWidget(self.upload_area)
# 或者选择文件
file_layout = QHBoxLayout()
file_layout.addWidget(QLabel("或者"))
select_btn = QPushButton("点击选择文件夹")
select_btn.setObjectName("selectBtn")
select_btn.clicked.connect(self.select_files)
file_layout.addWidget(select_btn)
file_layout.addStretch()
import_layout.addLayout(file_layout)
# 文件列表
self.file_list = QListWidget()
self.file_list.setObjectName("fileList")
self.file_list.setMaximumHeight(150)
import_layout.addWidget(self.file_list)
layout.addWidget(import_group)
def create_import_options(self, layout):
"""创建导入选项"""
options_widget = QWidget()
options_widget.setObjectName("optionsGroup")
options_layout = QHBoxLayout(options_widget)
# 导入选项
self.extract_checkbox = QCheckBox("导入后打开项目")
self.extract_checkbox.setChecked(True)
options_layout.addWidget(self.extract_checkbox)
options_layout.addStretch()
self.overwrite_checkbox = QCheckBox("还原动态数据源")
options_layout.addWidget(self.overwrite_checkbox)
layout.addWidget(options_widget)
# def create_import_options(self, layout):
# """创建导入选项"""
# options_group = QGroupBox("导入选项")
# options_group.setObjectName("optionsGroup")
# options_layout = QVBoxLayout(options_group)
# options_layout.setSpacing(10)
#
# # 项目名称
# name_layout = QHBoxLayout()
# name_layout.addWidget(QLabel("项目名称:"))
#
# self.name_input = QLineEdit()
# self.name_input.setObjectName("nameInput")
# self.name_input.setPlaceholderText("自动从文件名提取")
# name_layout.addWidget(self.name_input)
#
# options_layout.addLayout(name_layout)
#
# # 导入位置
# location_layout = QHBoxLayout()
# location_layout.addWidget(QLabel("导入位置:"))
#
# self.location_input = QLineEdit()
# self.location_input.setObjectName("locationInput")
# self.location_input.setPlaceholderText("选择导入位置")
# self.location_input.setReadOnly(True)
# location_layout.addWidget(self.location_input)
#
# browse_btn = QPushButton("浏览")
# browse_btn.setObjectName("browseBtn")
# browse_btn.clicked.connect(self.browse_location)
# location_layout.addWidget(browse_btn)
#
# options_layout.addLayout(location_layout)
#
# # 导入选项
# self.extract_checkbox = QCheckBox("解压缩文件")
# self.extract_checkbox.setChecked(True)
# options_layout.addWidget(self.extract_checkbox)
#
# self.overwrite_checkbox = QCheckBox("覆盖已存在的文件")
# options_layout.addWidget(self.overwrite_checkbox)
#
# layout.addWidget(options_group)
def create_button_area(self, layout):
"""创建按钮区域"""
button_widget = QWidget()
button_widget.setObjectName("buttonArea")
button_layout = QHBoxLayout(button_widget)
button_layout.setContentsMargins(30, 20, 30, 30)
button_layout.setSpacing(10) # 与创建项目对话框保持一致
button_layout.addStretch()
# 取消按钮
self.cancel_btn = QPushButton("取消")
self.cancel_btn.setObjectName("secondaryBtn")
self.cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_btn)
# 导入按钮
self.import_btn = QPushButton("导入项目")
self.import_btn.setObjectName("primaryBtn")
self.import_btn.setEnabled(False)
self.import_btn.clicked.connect(self.import_project)
button_layout.addWidget(self.import_btn)
layout.addWidget(button_widget)
def select_files(self):
"""选择文件夹"""
# 使用项目设置中的默认位置作为起始目录
if self.project_settings_page:
start_dir = self.project_settings_page.get_default_project_location()
else:
start_dir = os.path.expanduser("~/Documents")
folder = QFileDialog.getExistingDirectory(
self, "选择文件夹",
start_dir
)
if folder:
self.selected_files = [folder]
self.update_file_list()
self.update_project_name()
def browse_location(self):
"""浏览导入位置"""
# 当前UI简化版本不需要此功能
pass
def on_files_dropped(self, files):
"""文件夹拖拽事件"""
# 只接受文件夹
folders = [f for f in files if os.path.isdir(f)]
if folders:
self.selected_files = folders
self.update_file_list()
self.update_project_name()
else:
QMessageBox.warning(self, "提示", "请拖拽文件夹,不支持单个文件")
def update_file_list(self):
"""更新文件夹列表"""
self.file_list.clear()
for folder_path in self.selected_files:
folder_name = os.path.basename(folder_path)
# 计算文件夹大小和文件数量
try:
total_size = 0
file_count = 0
for root, dirs, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
if os.path.exists(file_path):
total_size += os.path.getsize(file_path)
file_count += 1
size_str = self.format_file_size(total_size)
item_text = f"📁 {folder_name} ({file_count} 个文件, {size_str})"
except Exception:
item_text = f"📁 {folder_name} (无法计算大小)"
self.file_list.addItem(item_text)
# 按钮状态由 update_project_name 方法中的验证逻辑控制
def update_project_name(self):
"""更新项目名称并验证"""
if not self.selected_files:
self.import_btn.setEnabled(False)
return
# 从第一个文件夹名自动提取项目名称
folder_path = self.selected_files[0]
folder_name = os.path.basename(folder_path)
# 进行验证
is_valid, error_message = self.project_manager.validate_project_import(folder_name, folder_path)
if is_valid:
# 验证通过,启用导入按钮
self.import_btn.setEnabled(True)
self.import_btn.setToolTip("")
else:
# 验证失败,禁用导入按钮并显示错误信息
self.import_btn.setEnabled(False)
self.import_btn.setToolTip(f"无法导入: {error_message}")
def format_file_size(self, size):
"""格式化文件大小"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024.0:
return f"{size:.1f} {unit}"
size /= 1024.0
return f"{size:.1f} TB"
def import_project(self):
"""导入项目"""
# 防止重复点击
if not self.import_btn.isEnabled():
return
# 立即禁用按钮,防止重复点击
self.import_btn.setEnabled(False)
try:
# 验证输入
if not self.selected_files:
QMessageBox.warning(self, "输入错误", "请选择要导入的文件夹")
return
# 从第一个文件夹名自动提取项目名称
folder_path = self.selected_files[0]
folder_name = os.path.basename(folder_path)
name = folder_name
# 使用项目管理器的验证方法进行全面检查
is_valid, error_message = self.project_manager.validate_project_import(name, folder_path)
if not is_valid:
QMessageBox.warning(self, "导入失败", error_message)
return
# 导入项目 - 直接使用现有文件夹作为项目目录
project = self.project_manager.import_project(
name, # 项目名称
f"从文件夹 {folder_name} 导入", # 项目描述
"imported", # 项目类型
folder_path # 直接使用原文件夹路径作为项目目录
)
# 处理导入选项
if self.overwrite_checkbox.isChecked():
self.restore_dynamic_data_sources(project)
QMessageBox.information(self, "导入成功", f"项目 \"{name}\" 导入成功!\n文件夹位置: {folder_path}")
# 如果勾选了导入后打开项目,则打开项目
if self.extract_checkbox.isChecked():
self.open_imported_project(project)
self.accept()
except Exception as e:
QMessageBox.critical(self, "导入失败", f"项目导入失败:{str(e)}")
finally:
# 确保在任何情况下都重新启用按钮(如果对话框没有关闭)
if self.isVisible():
self.import_btn.setEnabled(True)
def open_imported_project(self, project):
"""打开导入的项目"""
try:
# 使用项目管理器的验证方法检查项目是否可以打开
is_valid, error_message = self.project_manager.validate_project_open(project.project_dir)
if not is_valid:
QMessageBox.warning(self, "无法打开项目", f"项目无法打开: {error_message}")
return
# 验证通过,显示成功信息
QMessageBox.information(self, "打开项目", f"正在打开项目: {project.title}")
# TODO: 在这里添加实际的项目打开逻辑
except Exception as e:
QMessageBox.critical(self, "错误", f"打开项目时发生错误:\n{str(e)}")
def restore_dynamic_data_sources(self, project):
"""还原动态数据源"""
try:
# TODO: 在这里添加还原动态数据源的逻辑
print(f"还原项目 {project.title} 的动态数据源")
except Exception as e:
QMessageBox.warning(self, "警告", f"还原动态数据源时发生错误:\n{str(e)}")
def connect_signals(self):
"""连接信号"""
# 连接按钮信号
self.cancel_btn.clicked.connect(self.reject)
self.import_btn.clicked.connect(self.import_project)
# 连接文件选择按钮信号
# self.select_files_btn.clicked.connect(self.select_files)
# self.select_folder_btn.clicked.connect(self.select_folder)
def center_dialog(self):
"""对话框居中"""
if self.parent():
parent_rect = self.parent().geometry()
x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
self.move(x, y)
class UploadArea(QWidget):
"""文件上传区域"""
files_dropped = pyqtSignal(list)
select_files_requested = pyqtSignal()
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.init_ui()
def init_ui(self):
"""初始化UI"""
self.setObjectName("uploadArea")
self.setMinimumHeight(150)
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignCenter)
layout.setSpacing(10)
# 上传图标
icon_label = QLabel("📁")
icon_label.setStyleSheet("font-size: 32px; color: #666666;")
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 提示文字
text_label = QLabel("拖拽文件夹到这里,或者")
text_label.setStyleSheet("color: #888888; font-size: 14px;")
text_label.setAlignment(Qt.AlignCenter)
layout.addWidget(text_label)
# 选择按钮
select_btn = QPushButton("点击选择")
select_btn.setObjectName("uploadBtn")
select_btn.clicked.connect(self.select_files_requested.emit)
layout.addWidget(select_btn)
# 支持格式提示
format_label = QLabel("支持任意文件夹")
format_label.setStyleSheet("color: #666666; font-size: 12px;")
format_label.setAlignment(Qt.AlignCenter)
layout.addWidget(format_label)
def dragEnterEvent(self, event):
"""拖拽进入事件"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
self.setProperty("dragOver", True)
self.style().unpolish(self)
self.style().polish(self)
def dragLeaveEvent(self, event):
"""拖拽离开事件"""
self.setProperty("dragOver", False)
self.style().unpolish(self)
self.style().polish(self)
def dropEvent(self, event):
"""拖拽放下事件"""
files = []
for url in event.mimeData().urls():
if url.isLocalFile():
files.append(url.toLocalFile())
if files:
self.files_dropped.emit(files)
self.setProperty("dragOver", False)
self.style().unpolish(self)
self.style().polish(self)

303
MetaCore/ui/main_window.py Normal file
View File

@ -0,0 +1,303 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
主窗口类
"""
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from MetaCore.ui.sidebar import Sidebar
from MetaCore.ui.project_area import ProjectArea
from MetaCore.ui.create_project_dialog import CreateProjectDialog
from MetaCore.ui.import_project_dialog import ImportProjectDialog
from MetaCore.ui.project_settings_page import ProjectSettingsPage
from MetaCore.data.project_manager import ProjectManager
from MetaCore.ui.icon_manager import IconManager
class MainWindow(QMainWindow):
"""主窗口类"""
def __init__(self, project_manager: ProjectManager):
super().__init__()
self.project_manager = project_manager
self.current_filter = "overview"
self.current_search = ""
self.init_ui()
self.connect_signals()
# 设置窗口属性
self.setWindowTitle("MetaCore - 项目管理平台")
self.setWindowIcon(IconManager.get_icon('app'))
self.setMinimumSize(1000, 700)
self.resize(1400, 900)
# 响应式布局标志
self.is_compact_mode = False
# 居中显示
self.center_window()
def init_ui(self):
"""初始化UI"""
# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建主布局
main_layout = QHBoxLayout(central_widget)
# 设置部件内容周围的边距大小分别为左、上、右和下。
# 边距由布局系统使用,并且子类可能使用边距来指定绘制区域(例如,不包括框架)。
# 更改页边距会触发一个 resizeEvent() 事件。
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 创建侧边栏
self.sidebar = Sidebar(self.project_manager)
main_layout.addWidget(self.sidebar)
# 创建项目区域
self.project_area = ProjectArea(self.project_manager)
# 创建项目设置页面
self.project_settings_page = ProjectSettingsPage()
# 创建堆叠窗口部件来管理不同页面
self.stacked_widget = QStackedWidget()
self.stacked_widget.addWidget(self.project_area) # 索引 0: 项目区域
self.stacked_widget.addWidget(self.project_settings_page) # 索引 1: 项目设置页面
main_layout.addWidget(self.stacked_widget)
# 设置布局比例
main_layout.setStretch(0, 0) # 侧边栏固定宽度
main_layout.setStretch(1, 1) # 堆叠窗口部件自适应
# 创建状态栏
# self.create_status_bar()
# 创建菜单栏
# self.create_menu_bar()
def create_status_bar(self):
"""创建状态栏"""
self.status_bar = self.statusBar()
# 项目数量标签
self.project_count_label = QLabel()
self.status_bar.addWidget(self.project_count_label)
# 分隔符
self.status_bar.addPermanentWidget(QLabel("|"))
# 当前用户标签
user_label = QLabel("当前用户: Admin")
self.status_bar.addPermanentWidget(user_label)
# 更新项目数量
self.update_project_count()
def create_menu_bar(self):
"""创建菜单栏"""
menubar = self.menuBar()
# 文件菜单
file_menu = menubar.addMenu('文件(&F)')
# 新建项目
new_action = QAction('新建项目(&N)', self)
new_action.setShortcut('Ctrl+N')
new_action.setStatusTip('创建新项目')
new_action.triggered.connect(self.show_create_project_dialog)
file_menu.addAction(new_action)
# 导入项目
import_action = QAction('导入项目(&I)', self)
import_action.setShortcut('Ctrl+I')
import_action.setStatusTip('导入现有项目')
import_action.triggered.connect(self.show_import_project_dialog)
file_menu.addAction(import_action)
file_menu.addSeparator()
# 退出
exit_action = QAction('退出(&X)', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.setStatusTip('退出应用程序')
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 编辑菜单
edit_menu = menubar.addMenu('编辑(&E)')
# 搜索
search_action = QAction('搜索项目(&S)', self)
search_action.setShortcut('Ctrl+F')
search_action.setStatusTip('搜索项目')
search_action.triggered.connect(self.focus_search)
edit_menu.addAction(search_action)
# 视图菜单
view_menu = menubar.addMenu('视图(&V)')
# 网格视图
grid_action = QAction('网格视图(&G)', self)
grid_action.setCheckable(True)
grid_action.setChecked(True)
grid_action.triggered.connect(lambda: self.project_area.set_view_mode('grid'))
view_menu.addAction(grid_action)
# 列表视图
list_action = QAction('列表视图(&L)', self)
list_action.setCheckable(True)
list_action.triggered.connect(lambda: self.project_area.set_view_mode('list'))
view_menu.addAction(list_action)
# 创建视图模式组
view_group = QActionGroup(self)
view_group.addAction(grid_action)
view_group.addAction(list_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助(&H)')
# 关于
about_action = QAction('关于(&A)', self)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
def connect_signals(self):
"""连接信号"""
# 侧边栏信号
self.sidebar.filter_changed.connect(self.on_filter_changed)
self.sidebar.navigation_changed.connect(self.on_navigation_changed) # 新增导航变化信号
self.sidebar.create_project_requested.connect(self.show_create_project_dialog)
self.sidebar.import_project_requested.connect(self.show_import_project_dialog)
# 项目区域信号
self.project_area.search_changed.connect(self.on_search_changed)
self.project_area.create_project_requested.connect(self.show_create_project_dialog)
self.project_area.import_project_requested.connect(self.show_import_project_dialog)
# 项目设置页面信号
self.project_settings_page.settings_changed.connect(self.on_project_settings_changed)
# 项目管理器信号
self.project_manager.projects_changed.connect(self.update_project_count)
self.project_manager.projects_changed.connect(self.refresh_projects)
def center_window(self):
"""窗口居中"""
screen = QApplication.desktop().screenGeometry()
size = self.geometry()
self.move(
(screen.width() - size.width()) // 2,
(screen.height() - size.height()) // 2
)
def resizeEvent(self, event):
"""窗口大小变化事件"""
super().resizeEvent(event)
# 检查是否需要切换到紧凑模式
width = event.size().width()
should_be_compact = width < 1200
if should_be_compact != self.is_compact_mode:
self.is_compact_mode = should_be_compact
self.update_layout_mode()
def update_layout_mode(self):
"""更新布局模式"""
if self.is_compact_mode:
# 紧凑模式:缩小侧边栏
self.sidebar.setFixedWidth(240)
else:
# 正常模式:标准侧边栏宽度
self.sidebar.setFixedWidth(280)
def on_filter_changed(self, filter_type: str):
"""过滤器改变"""
self.current_filter = filter_type
self.refresh_projects()
def on_navigation_changed(self, section: str, item_name: str, filter_type: str):
"""导航变化处理"""
# 根据filter_type决定显示哪个页面
if filter_type == "project_settings":
# 切换到项目设置页面
self.stacked_widget.setCurrentIndex(1)
else:
# 切换到项目区域
self.stacked_widget.setCurrentIndex(0)
# 更新项目区域的面包屑导航和标题
self.project_area.update_navigation(section, item_name)
def on_search_changed(self, search_text: str):
"""搜索改变"""
self.current_search = search_text
self.refresh_projects()
def on_project_settings_changed(self):
"""项目设置变化处理"""
# 设置已更改,可以在这里处理相关逻辑
# 例如:更新创建项目对话框的默认位置
print("项目设置已更新")
def refresh_projects(self):
"""刷新项目显示"""
if self.current_search:
projects = self.project_manager.search_projects(self.current_search)
else:
projects = self.project_manager.get_projects_by_type(self.current_filter)
self.project_area.update_projects(projects)
def update_project_count(self):
"""更新项目数量"""
count = len(self.project_manager.get_all_projects())
# 如果状态栏存在,更新项目数量显示
if hasattr(self, 'project_count_label'):
self.project_count_label.setText(f"项目总数: {count}")
# 可以在这里添加其他更新逻辑,比如更新窗口标题
self.setWindowTitle(f"MetaCore - 项目管理平台 ({count} 个项目)")
def show_create_project_dialog(self):
"""显示创建项目对话框"""
dialog = CreateProjectDialog(self.project_manager, self.project_settings_page, self)
if dialog.exec_() == QDialog.Accepted:
self.refresh_projects()
def show_import_project_dialog(self):
"""显示导入项目对话框"""
dialog = ImportProjectDialog(self.project_manager, self.project_settings_page, self)
if dialog.exec_() == QDialog.Accepted:
self.refresh_projects()
def focus_search(self):
"""聚焦搜索框"""
self.project_area.focus_search()
def show_about(self):
"""显示关于对话框"""
QMessageBox.about(self, "关于 MetaCore",
"MetaCore 项目管理平台\n\n"
"版本: 1.0.0\n"
"基于 PyQt5 开发\n\n"
"© 2024 MetaCore Team")
def closeEvent(self, event):
"""关闭事件"""
reply = QMessageBox.question(self, '确认退出',
'确定要退出 MetaCore 吗?',
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
# 保存数据
self.project_manager.save_projects()
event.accept()
else:
event.ignore()

441
MetaCore/ui/project_area.py Normal file
View File

@ -0,0 +1,441 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
项目区域组件
"""
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from typing import List
from MetaCore.data.project_manager import ProjectManager, Project
from MetaCore.ui.icon_manager import IconManager
from MetaCore.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.project_manager = project_manager
self.view_mode = "grid" # grid 或 list
self.projects = []
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, 0, 0, 0)
layout.setSpacing(0)
# 顶部工具栏
self.create_toolbar(layout)
# 搜索和过滤区域
self.create_search_filter_area(layout)
# 项目显示区域
self.create_project_display_area(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)
# 面包屑导航容器
breadcrumb_container = QWidget()
breadcrumb_container.setObjectName("breadcrumbContainer")
breadcrumb_layout = QHBoxLayout(breadcrumb_container)
breadcrumb_layout.setContentsMargins(0, 0, 0, 0)
breadcrumb_layout.setSpacing(0)
self.home_label = QLabel("我的项目")
self.home_label.setObjectName("breadcrumbItem")
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)
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.addStretch()
# 搜索输入框
self.search_input = QLineEdit()
self.search_input.setObjectName("searchInput")
self.search_input.setPlaceholderText("搜索项目...")
self.search_input.textChanged.connect(self.search_changed.emit)
self.search_input.setMaximumWidth(250) # 限制最大宽度
header_layout.addWidget(self.search_input)
# 视图控制按钮
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)
# 列表视图按钮
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.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(30, 20, 30, 30)
self.projects_layout.setSpacing(20)
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)
self.current_label.setText(item_name)
# 更新主标题
# 根据不同的导航项设置不同的标题
title_mapping = {
"项目概述": "项目概述",
"项目管理": "项目管理",
"资源分类": "资源分类管理",
"资源管理": "资源管理",
"系统设置": "系统设置",
}
display_title = title_mapping.get(item_name, item_name)
self.title_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
# 更新按钮状态
self.grid_view_btn.setChecked(mode == "grid")
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 # 使用更小的边距
# 固定卡片宽度和间距
card_width = 280 # 固定卡片宽度
spacing = 20 # 卡片间距
# 计算能容纳的列数
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

974
MetaCore/ui/project_card.py Normal file
View File

@ -0,0 +1,974 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
项目卡片组件
"""
import os
import sys
import subprocess
import platform
from pathlib import Path
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from MetaCore.data.project_manager import ProjectManager, Project
from MetaCore.ui.icon_manager import IconManager
from MetaCore.ui.project_settings_page import ProjectSettingsPage
class ImageDisplayWidget(QWidget):
"""
一个专门用于显示带圆角图片的控件
它会自动将图片按比例缩放以覆盖整个区域并进行圆角裁剪
"""
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = QPixmap()
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
def setPixmap(self, pixmap):
self.pixmap = pixmap
self.update() # 触发重绘
# 这是在 ImageDisplayWidget 类内部的方法
def paintEvent(self, event):
if self.pixmap.isNull():
return
# 导入 QSize 以便创建新的尺寸对象
from PyQt5.QtCore import QSize
import math
target_rect = self.rect()
radius = 12.0
# “过缩放”逻辑保持不变,我们仍然需要一张比控件大的图片
overscale_factor = 1.05
original_size = target_rect.size()
larger_width = math.ceil(original_size.width() * overscale_factor)
larger_height = math.ceil(original_size.height() * overscale_factor)
larger_target_size = QSize(larger_width, larger_height)
scaled_pixmap = self.pixmap.scaled(larger_target_size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
# --- 核心修正点在这里 ---
# 1. 计算绘制的起始坐标(x, y),以使 scaled_pixmap 的中心与控件中心对齐
# 由于 scaled_pixmap 比控件大,所以 x 和 y 通常是负数,这是正确的
draw_x = (target_rect.width() - scaled_pixmap.width()) / 2
draw_y = (target_rect.height() - scaled_pixmap.height()) / 2
# 2. 使用QPainter进行绘制
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 3. 创建并设置圆角裁剪路径 (逻辑不变)
path = QPainterPath()
path.addRoundedRect(QRectF(target_rect), radius, radius)
painter.setClipPath(path)
# 4. 使用最简单的 drawPixmap 版本,直接在计算好的坐标点绘制
# 这个版本不会进行任何额外的缩放,完美解决了偏移问题
painter.drawPixmap(int(draw_x), int(draw_y), scaled_pixmap)
# -----------------------
class ProjectCard(QWidget):
"""项目卡片组件"""
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.setObjectName("projectCard")
self.setProperty("status", project.status)
self.setAttribute(Qt.WA_StyledBackground) # 关键:启用样式背景继承
# 设置尺寸和策略
if view_mode == "grid":
self.setFixedSize(280, 240)
else:
self.setFixedHeight(80)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
self.init_ui()
self.connect_signals()
# 确保样式正确应用
self.update_style()
def update_style(self):
"""强制刷新样式"""
self.style().unpolish(self)
self.style().polish(self)
self.update()
def init_ui(self):
"""初始化UI"""
if self.view_mode == "grid":
self.create_grid_layout()
else:
self.create_list_layout()
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)
def create_list_layout(self):
"""创建列表布局"""
layout = QHBoxLayout(self)
layout.setContentsMargins(16, 12, 16, 12)
layout.setSpacing(16)
# 项目图标 - 使用优化的图标显示
self.create_list_project_icon(layout)
# 项目信息
info_layout = QVBoxLayout()
info_layout.setSpacing(4)
# 项目名称
title_layout = QHBoxLayout()
title_label = QLabel(self.project.title)
title_label.setObjectName("projectTitle")
title_layout.addWidget(title_label)
title_layout.addStretch()
info_layout.addLayout(title_layout)
# 项目日期
date_label = QLabel(self.project.date)
date_label.setObjectName("projectDate")
info_layout.addWidget(date_label)
layout.addLayout(info_layout)
layout.addStretch()
# 菜单按钮
self.create_menu_button(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.setSpacing(8)
# 项目标题
title_label = QLabel(self.project.title)
title_label.setObjectName("projectTitle")
title_label.setWordWrap(True)
header_layout.addWidget(title_label, 1) # 添加拉伸因子
# 右侧操作区
actions_layout = QHBoxLayout()
actions_layout.setSpacing(4)
# 根据项目状态显示不同的按钮
if self.project.status == 'pending_delete':
self.menu_btn = QPushButton("")
self.menu_btn.setObjectName("deleteBtn")
self.menu_btn.setFixedSize(24, 24)
self.menu_btn.clicked.connect(self.confirm_delete_project)
self.menu_btn.setToolTip("确认删除项目")
else:
self.menu_btn = QPushButton("")
self.menu_btn.setObjectName("menuBtn")
self.menu_btn.setFixedSize(24, 24)
self.menu_btn.clicked.connect(self.show_context_menu)
self.menu_btn.setToolTip("项目操作菜单")
actions_layout.addWidget(self.menu_btn)
header_layout.addLayout(actions_layout)
layout.addWidget(header_widget)
def create_project_image(self, layout):
"""创建项目图片区域 - 最终版"""
import os
from PyQt5.QtWidgets import QStackedLayout # 导入 QStackedLayout
# image_container 是最外层的容器
image_container = QWidget()
image_container.setObjectName("projectImageContainer")
image_container.setContentsMargins(16, 0, 16, 0)
# image_widget 现在是堆叠布局的容器,它拥有灰色背景和圆角
image_widget = QWidget()
image_widget.setObjectName("projectImage")
# --- 使用 QStackedLayout 来切换两种状态 ---
stacked_layout = QStackedLayout(image_widget)
stacked_layout.setContentsMargins(0, 0, 0, 0)
# 状态一:显示图片 (使用我们自定义的控件)
image_display = ImageDisplayWidget()
# 注意:这里不需要给 image_display 设置 objectName 或样式,因为它只是一个“画布”
# 状态二:显示默认图标 (使用一个标准的 QLabel)
default_icon_label = QLabel("📁")
default_icon_label.setObjectName("projectIcon") # QSS 会应用到这里
default_icon_label.setAlignment(Qt.AlignCenter)
# 将两个状态控件添加到堆叠布局中
stacked_layout.addWidget(image_display) # 索引 0
stacked_layout.addWidget(default_icon_label) # 索引 1
# --- 根据条件切换显示 ---
if hasattr(self.project, 'image') and os.path.exists(self.project.image) and os.path.isfile(self.project.image):
# 如果图片存在...
pixmap = QPixmap(self.project.image)
image_display.setPixmap(pixmap)
stacked_layout.setCurrentIndex(0) # 显示图片控件
else:
# 如果图片不存在...
stacked_layout.setCurrentIndex(1) # 显示默认图标控件
# --- 最终布局 ---
# 我们只需要一个简单的 QVBoxLayout 来容纳 image_widget
container_layout = QVBoxLayout(image_container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(image_widget)
layout.addWidget(image_container, 1)
def create_project_footer(self, layout):
"""创建项目底部"""
footer_widget = QWidget()
footer_widget.setObjectName("projectFooter")
footer_layout = QVBoxLayout(footer_widget)
footer_layout.setContentsMargins(16, 0, 16, 16) # 减少顶部边距
# 项目日期
date_label = QLabel(self.project.date)
date_label.setObjectName("projectDate")
date_label.setAlignment(Qt.AlignCenter)
footer_layout.addWidget(date_label)
layout.addWidget(footer_widget)
def create_menu_button(self, layout):
"""创建菜单按钮(用于列表视图)"""
self.menu_btn = QPushButton("")
self.menu_btn.setObjectName("menuBtn")
self.menu_btn.setFixedSize(32, 32)
self.menu_btn.clicked.connect(self.show_context_menu)
layout.addWidget(self.menu_btn)
def create_list_project_icon(self, layout):
"""创建列表视图中的项目图标"""
import os
from PyQt5.QtWidgets import QStackedLayout
# 图标容器
icon_container = QWidget()
icon_container.setObjectName("listProjectIconContainer")
icon_container.setFixedSize(48, 48) # 列表视图中使用较小的图标
# 图标控件 - 带圆角背景
icon_widget = QWidget()
icon_widget.setObjectName("listProjectIcon")
icon_widget.setProperty("status", self.project.status) # 设置状态属性
# 使用堆叠布局来切换图片和默认图标
stacked_layout = QStackedLayout(icon_widget)
stacked_layout.setContentsMargins(0, 0, 0, 0)
# 状态一:显示图片 (使用自定义的圆角图片控件)
image_display = ListImageDisplayWidget()
# 状态二:显示默认图标
default_icon_label = QLabel("📁")
default_icon_label.setObjectName("listProjectDefaultIcon")
default_icon_label.setAlignment(Qt.AlignCenter)
# 添加到堆叠布局
stacked_layout.addWidget(image_display) # 索引 0
stacked_layout.addWidget(default_icon_label) # 索引 1
# 根据条件切换显示
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)
# 容器布局
container_layout = QVBoxLayout(icon_container)
container_layout.setContentsMargins(0, 0, 0, 0)
container_layout.addWidget(icon_widget)
layout.addWidget(icon_container)
def get_type_label(self):
"""获取项目类型标签"""
type_labels = {
'industrial': '工业',
'smart': '智能',
'vr': 'VR',
'game': '游戏',
'design': '设计'
}
return type_labels.get(self.project.type, '其他')
def show_context_menu(self):
"""显示右键菜单"""
menu = QMenu(self)
# 刷新预览图
if IconManager.icon_exists("Refresh"):
refresh_action = menu.addAction(IconManager.get_icon('Refresh'), "刷新预览图")
else:
refresh_action = menu.addAction("🔄 刷新预览图")
refresh_action.triggered.connect(self.refresh_preview_image)
menu.addSeparator()
# 在资源管理器显示
if IconManager.icon_exists('folder'):
show_in_explorer_action = menu.addAction(IconManager.get_icon('folder'), "在资源管理器显示")
else:
show_in_explorer_action = menu.addAction("📁 在资源管理器显示")
show_in_explorer_action.triggered.connect(self.show_in_explorer)
menu.addSeparator()
# 删除项目
if IconManager.icon_exists('delete'):
delete_action = menu.addAction(IconManager.get_icon('delete'), "移除项目")
else:
delete_action = menu.addAction("🗑️ 移除项目")
delete_action.triggered.connect(self.delete_project)
# 显示菜单
menu.exec_(self.menu_btn.mapToGlobal(self.menu_btn.rect().bottomLeft()))
def open_project(self):
"""打开项目"""
try:
# 优先使用项目目录路径,如果不存在则使用基础路径
project_path = self.project.project_dir if self.project.project_dir else self.project.path
# 使用项目管理器的验证方法进行全面检查
is_valid, error_message = self.project_manager.validate_project_open(project_path)
if not is_valid:
QMessageBox.warning(self, "无法打开项目", f"项目无法打开: {error_message}")
return
# 验证通过,显示成功信息
# QMessageBox.information(self, "打开项目", f"正在打开项目: {self.project.title}")
# 使用question对话框提供明确的确认选项
reply = QMessageBox.question(self, "打开项目",
f"确定要打开项目: {self.project.title}?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes)
if reply == QMessageBox.Yes:
# 用户确认打开项目,继续执行打开逻辑
# TODO: 在这里添加实际的项目打开逻辑
print(f"正在打开项目路径: {project_path},正在启动应用程序: {self.project.title}")
# 连接信号
self.project_manager.pycharm_started.connect(self.on_pycharm_started)
self.project_manager.project_method_called.connect(self.on_project_method_called)
# # 启动PyCharm并调用项目方法
# # 修正参数顺序:
# # 第一个参数要在PyCharm中打开的项目路径EG项目
# # 第二个参数:要传递给项目方法的目标项目路径(当前项目)
success = self.project_manager.run_project_command(
self.project_settings_page.get_default_open_location(),
# "/home/tiger/文档/EG", # project_path - 要在PyCharm中打开的项目
project_path # target_project_path - 要传递给项目方法的目标项目路径
)
if not success:
QMessageBox.warning(
self,
"启动失败",
"无法启动,请检查打开项目路径。"
)
# if success:
# QMessageBox.information(
# self,
# "启动PyCharm",
# f"正在启动PyCharm并准备调用项目方法...\n目标项目: {project_path}"
# )
# else:
# QMessageBox.warning(
# self,
# "启动失败",
# "无法启动PyCharm请检查PyCharm安装。"
# )
else:
# 用户取消操作,直接返回
return
# success = self.project_manager.launch_pycharm_and_open_project(
# "/home/tiger/文档/EG", # project_path - 要在PyCharm中打开的项目
# project_path # target_project_path - 要传递给项目方法的目标项目路径
# )
# if success:
# QMessageBox.information(
# self,
# "启动PyCharm",
# f"正在启动PyCharm并准备调用项目方法...\n目标项目: {project_path}"
# )
# else:
# QMessageBox.warning(
# self,
# "启动失败",
# "无法启动PyCharm请检查PyCharm安装。"
# )
# self.project_manager.open_app_if_not_running(f'{project_path}/project.json', "project.json")
# self.project_manager.open_project_in_pycharm(project_path)
# except Exception as e:
# QMessageBox.critical(self, "错误", f"打开项目时发生错误:\n{str(e)}")
except Exception as e:
QMessageBox.critical(self, "错误", f"启动PyCharm时发生错误:\n{str(e)}")
def on_pycharm_started(self):
"""PyCharm启动完成回调"""
print("PyCharm has started and is ready")
def on_project_method_called(self, target_project_path):
"""项目方法调用完成回调"""
QMessageBox.information(
self,
"操作完成",
f"已成功在PyCharm中打开项目: {target_project_path}"
)
def show_in_explorer(self):
"""在资源管理器中显示项目目录"""
try:
# 获取项目路径并规范化
project_path_str = self.project.project_dir if self.project.project_dir else self.project.path
if not project_path_str:
QMessageBox.warning(self, "路径不存在", "项目路径为空,请检查项目配置。")
return
# 使用pathlib处理路径
project_path = Path(project_path_str).resolve()
print(f"项目ID: {self.project.id}")
print(f"项目标题: {self.project.title}")
print(f"最终使用的路径: '{project_path}'")
print(f"路径是否存在: {project_path.exists()}")
print(f"是否为目录: {project_path.is_dir()}")
# 检查项目路径是否存在
if not project_path.exists():
QMessageBox.warning(self, "路径不存在",
f"项目路径不存在或无效:\n{project_path}\n\n请检查项目是否已被移动或删除。")
return
# 获取操作系统类型
system = platform.system().lower()
if system == "windows":
try:
# Windows使用os.startfile会自动使用系统默认的文件管理器
os.startfile(str(project_path))
except OSError as e:
QMessageBox.critical(self, "打开失败", f"无法打开资源管理器:\n{str(e)}")
elif system == "darwin": # macOS
if project_path.is_file():
# 如果是文件,选中该文件
subprocess.run(['open', '-R', str(project_path)], check=True)
else:
# 如果是目录,直接打开
subprocess.run(['open', str(project_path)], check=True)
else: # Linux和其他Unix系统
try:
# 首先尝试使用nautilusGNOME文件管理器
subprocess.run(['nautilus', str(project_path)], check=True)
except (FileNotFoundError, subprocess.CalledProcessError):
try:
# 尝试使用dolphin (KDE)
subprocess.run(['dolphin', str(project_path)], check=True)
except (FileNotFoundError, subprocess.CalledProcessError):
try:
# 尝试使用thunar (XFCE)
subprocess.run(['thunar', str(project_path)], check=True)
except (FileNotFoundError, subprocess.CalledProcessError):
try:
# 最后尝试使用xdg-open
subprocess.run(['xdg-open', str(project_path)], check=True)
except (FileNotFoundError, subprocess.CalledProcessError):
QMessageBox.warning(self, "无法打开文件管理器",
"系统中没有找到合适的文件管理器。\n"
f"请手动打开路径: {project_path}")
print(f"成功在资源管理器中打开: {project_path}")
except subprocess.CalledProcessError as e:
QMessageBox.critical(self, "打开失败",
f"无法打开资源管理器:\n{str(e)}")
except Exception as e:
QMessageBox.critical(self, "错误",
f"打开资源管理器时发生错误:\n{str(e)}")
def delete_project(self):
"""删除项目"""
reply = QMessageBox.question(self, "确认移除",
f"确定要移除项目 \"{self.project.title}\" 吗?\n此操作不可撤销。",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
self.project_manager.remove_project(self.project.id)
def confirm_delete_project(self):
"""确认删除待删除状态的项目"""
reply = QMessageBox.question(self, "确认删除项目",
f"确定要永久删除项目 \"{self.project.title}\" 吗?\n"
f"此操作不可撤销。\n\n"
f"提示:如果项目目录已恢复,您可以点击项目卡片来恢复项目。",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
self.project_manager.confirm_delete_project(self.project.id)
def update_display(self):
"""更新显示"""
# 更新项目状态属性
self.setProperty("status", self.project.status)
# 重新创建UI以反映更改
for i in reversed(range(self.layout().count())):
child = self.layout().itemAt(i).widget()
if child:
child.setParent(None)
self.init_ui()
# 强制刷新样式
self.style().unpolish(self)
self.style().polish(self)
self.update()
def apply_fallback_styles(self):
"""应用备用样式,确保卡片有正确的外观"""
# 直接设置卡片的内联样式
card_style = """
QWidget#projectCard {
background-color: #4a4a5a;
border: 1px solid #5a5a6a;
border-radius: 16px;
}
QWidget#projectCard:hover {
background-color: #5a5a6a;
border-color: #6a6a7a;
}
"""
self.setStyleSheet(card_style)
def connect_signals(self):
"""连接信号"""
pass
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()
super().mousePressEvent(event)
def try_restore_project(self):
"""尝试恢复待删除状态的项目"""
if self.project_manager.restore_project(self.project.id):
QMessageBox.information(self, "项目已恢复",
f"项目 \"{self.project.title}\" 已成功恢复!")
else:
QMessageBox.information(self, "项目目录不存在",
f"项目 \"{self.project.title}\" 的目录仍然不存在:\n{self.project.project_dir}\n\n"
f"提示:当您恢复项目目录后,系统会自动检测并恢复项目状态,无需手动操作。")
def enterEvent(self, event):
"""鼠标进入事件"""
self.setProperty("hover", True)
self.style().unpolish(self)
self.style().polish(self)
# 强制重绘以确保悬停效果立即生效
self.update()
super().enterEvent(event)
def leaveEvent(self, event):
"""鼠标离开事件"""
self.setProperty("hover", False)
self.style().unpolish(self)
self.style().polish(self)
# 强制重绘以确保悬停效果立即消失
self.update()
super().leaveEvent(event)
def show_success_tooltip(self, message):
"""显示成功提示工具提示"""
# 临时改变菜单按钮的工具提示来显示成功状态
original_tooltip = self.menu_btn.toolTip()
self.menu_btn.setToolTip(f"{message}")
# 使用定时器恢复原状态
QTimer.singleShot(3000, lambda: self.restore_button_state(original_tooltip))
def restore_button_state(self, original_tooltip):
"""恢复按钮原始状态"""
self.menu_btn.setToolTip(original_tooltip)
def refresh_preview_image(self):
"""刷新预览图"""
try:
# 获取项目路径
project_path = self.project.project_dir if self.project.project_dir else self.project.path
if not project_path or not Path(project_path).exists():
QMessageBox.warning(self, "路径不存在", "项目路径不存在,无法生成预览图。")
return
# 显示进度提示
original_tooltip = self.menu_btn.toolTip()
self.menu_btn.setEnabled(False)
self.menu_btn.setToolTip("⏳ 正在生成预览图...")
QApplication.processEvents()
# 生成新的预览图
preview_path = self.generate_project_preview(project_path)
if preview_path and Path(preview_path).exists():
# 清理旧的预览图
self.project_manager.cleanup_old_preview_images(self.project.id)
# 更新项目的图片路径
old_image = self.project.image
self.project.image = preview_path
self.project_manager.update_project(self.project)
# 刷新显示
self.refresh_image_display()
# 显示成功提示(不使用阻塞对话框)
self.show_success_tooltip("预览图已刷新!")
# 如果有旧图片且不同于新图片,尝试删除
if old_image and old_image != preview_path and Path(old_image).exists():
try:
# 检查是否是我们生成的预览图
if "ProjectPreviews" in old_image:
Path(old_image).unlink()
except Exception:
pass # 忽略删除失败
else:
QMessageBox.warning(self, "生成失败",
"无法生成预览图。\n\n可能原因:\n"
"• 项目目录中没有图片文件\n"
"• 图片文件格式不支持\n"
"• 权限不足")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新预览图时发生错误:\n{str(e)}")
finally:
# 恢复按钮状态
self.menu_btn.setEnabled(True)
self.menu_btn.setToolTip(original_tooltip)
def generate_project_preview(self, project_path):
"""生成项目预览图"""
try:
project_path = Path(project_path)
# 查找项目中的图片文件
image_extensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp']
found_images = []
# 搜索常见的图片目录
search_dirs = [
project_path,
project_path / 'images',
project_path / 'img',
project_path / 'assets',
project_path / 'static',
project_path / 'resources',
project_path / 'media',
project_path / 'screenshots',
project_path / 'preview'
]
for search_dir in search_dirs:
if search_dir.exists() and search_dir.is_dir():
for ext in image_extensions:
found_images.extend(search_dir.glob(f'*{ext}'))
found_images.extend(search_dir.glob(f'*{ext.upper()}'))
# 递归搜索子目录(限制深度)
found_images.extend(search_dir.glob(f'*/*{ext}'))
found_images.extend(search_dir.glob(f'*/*{ext.upper()}'))
if not found_images:
return self.create_default_preview(project_path)
# 优先选择特定名称的图片
priority_names = ['preview', 'screenshot', 'main', 'cover', 'thumbnail', 'icon']
selected_image = None
for priority_name in priority_names:
for img_path in found_images:
if priority_name in img_path.stem.lower():
selected_image = img_path
break
if selected_image:
break
# 如果没有找到优先图片,选择第一个
if not selected_image:
selected_image = found_images[0]
# 创建预览图存储目录
preview_dir = Path.cwd() / 'MetaCore' / 'Resources' / 'ProjectPreviews'
preview_dir.mkdir(parents=True, exist_ok=True)
# 生成预览图文件名
preview_filename = f"preview_{self.project.id}_{int(QDateTime.currentMSecsSinceEpoch())}.png"
preview_path = preview_dir / preview_filename
# 处理图片并保存预览图
original_pixmap = QPixmap(str(selected_image))
if not original_pixmap.isNull():
# 缩放到合适的预览尺寸
preview_size = QSize(400, 300)
scaled_pixmap = original_pixmap.scaled(
preview_size,
Qt.KeepAspectRatioByExpanding,
Qt.SmoothTransformation
)
# 裁剪到目标尺寸
if scaled_pixmap.size() != preview_size:
x = (scaled_pixmap.width() - preview_size.width()) // 2
y = (scaled_pixmap.height() - preview_size.height()) // 2
scaled_pixmap = scaled_pixmap.copy(x, y, preview_size.width(), preview_size.height())
# 保存预览图
if scaled_pixmap.save(str(preview_path), 'PNG'):
return str(preview_path)
return self.create_default_preview(project_path)
except Exception as e:
print(f"生成预览图时发生错误: {e}")
return self.create_default_preview(project_path)
def create_default_preview(self, project_path):
"""创建默认预览图"""
try:
# 创建预览图存储目录
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
# 创建一个带有项目信息的默认预览图
pixmap = QPixmap(400, 300)
pixmap.fill(QColor(70, 70, 80)) # 深灰色背景
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing)
# 设置字体
font = QFont("微软雅黑", 16, QFont.Bold)
painter.setFont(font)
painter.setPen(QColor(255, 255, 255))
# 绘制项目名称
title_rect = QRect(20, 100, 360, 40)
painter.drawText(title_rect, Qt.AlignCenter | Qt.TextWordWrap, self.project.title)
# 绘制项目类型
type_font = QFont("微软雅黑", 12)
painter.setFont(type_font)
painter.setPen(QColor(200, 200, 200))
type_rect = QRect(20, 150, 360, 30)
type_text = self.get_type_label()
painter.drawText(type_rect, Qt.AlignCenter, f"项目类型: {type_text}")
# 绘制创建日期
date_rect = QRect(20, 180, 360, 30)
painter.drawText(date_rect, Qt.AlignCenter, f"创建时间: {self.project.date}")
# 绘制文件夹图标
icon_font = QFont("Segoe UI Emoji", 48)
painter.setFont(icon_font)
painter.setPen(QColor(150, 150, 150))
icon_rect = QRect(20, 30, 360, 60)
painter.drawText(icon_rect, Qt.AlignCenter, "📁")
painter.end()
# 保存预览图
if pixmap.save(str(preview_path), 'PNG'):
return str(preview_path)
except Exception as e:
print(f"创建默认预览图时发生错误: {e}")
return None
def refresh_image_display(self):
"""刷新图片显示"""
try:
# 更新网格视图中的图片显示
if self.view_mode == "grid":
self.update_grid_image_display()
else:
self.update_list_image_display()
except Exception as e:
print(f"刷新图片显示时发生错误: {e}")
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
def update_list_image_display(self):
"""更新列表视图中的图片显示"""
# 在列表视图中查找并更新图片显示
for i in range(self.layout().count()):
widget = self.layout().itemAt(i).widget()
if widget and widget.objectName() == "listProjectIconContainer":
# 找到内部的堆叠布局
for j in range(widget.layout().count()):
icon_widget = widget.layout().itemAt(j).widget()
if icon_widget and icon_widget.objectName() == "listProjectIcon":
stacked_layout = icon_widget.layout()
if isinstance(stacked_layout, QStackedLayout):
# 获取图片显示控件
image_display = stacked_layout.widget(0)
if isinstance(image_display, ListImageDisplayWidget):
# 检查是否有新的图片文件
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
class ListImageDisplayWidget(QWidget):
"""
专门用于列表视图的圆角图片显示控件
尺寸较小适合列表视图
"""
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = QPixmap()
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
def setPixmap(self, pixmap):
self.pixmap = pixmap
self.update()
def paintEvent(self, event):
if self.pixmap.isNull():
return
from PyQt5.QtCore import QSize
import math
target_rect = self.rect()
radius = 8.0 # 列表视图使用较小的圆角
# 过缩放因子
overscale_factor = 1.05
original_size = target_rect.size()
larger_width = math.ceil(original_size.width() * overscale_factor)
larger_height = math.ceil(original_size.height() * overscale_factor)
larger_target_size = QSize(larger_width, larger_height)
scaled_pixmap = self.pixmap.scaled(larger_target_size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
# 计算绘制的起始坐标,使图片居中
draw_x = (target_rect.width() - scaled_pixmap.width()) / 2
draw_y = (target_rect.height() - scaled_pixmap.height()) / 2
# 绘制
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# # 创建圆角裁剪路径
# path = QPainterPath()
# path.addRoundedRect(QRectF(target_rect), radius, radius)
# painter.setClipPath(path)
# 绘制图片
painter.drawPixmap(int(draw_x), int(draw_y), scaled_pixmap)

View File

@ -0,0 +1,328 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
项目设置页面组件
这个页面允许用户配置项目相关的设置包括
- 默认项目创建位置
- 项目模板设置
- 项目命名规则
- 其他项目相关配置
"""
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ProjectSettingsPage(QWidget):
"""
项目设置页面
提供项目相关的配置选项主要包括默认项目位置的设置
"""
# 信号定义
settings_changed = pyqtSignal() # 设置发生变化时发出
def __init__(self):
super().__init__()
self.settings = QSettings("MetaCore", "ProjectManager") # 用于保存设置
self.init_ui()
self.load_settings()
def init_ui(self):
"""初始化用户界面"""
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30)
layout.setSpacing(20)
# 页面标题
self.create_page_header(layout)
# 默认项目位置设置
self.create_default_location_section(layout)
self.create_open_location_section(layout)
# 项目创建设置
# self.create_project_creation_section(layout)
# 按钮区域
self.create_button_section(layout)
# 添加弹性空间
layout.addStretch()
def create_page_header(self, layout):
"""创建页面标题"""
header_widget = QWidget()
header_layout = QVBoxLayout(header_widget)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(8)
# 主标题
title_label = QLabel("项目设置")
title_label.setObjectName("pageTitle")
header_layout.addWidget(title_label)
# 副标题
subtitle_label = QLabel("配置项目创建和管理的相关设置")
subtitle_label.setObjectName("pageSubtitle")
header_layout.addWidget(subtitle_label)
layout.addWidget(header_widget)
def create_default_location_section(self, layout):
"""创建默认项目位置设置区域"""
# 分组框
group_box = QGroupBox("默认项目位置")
group_box.setObjectName("settingsGroup")
group_layout = QVBoxLayout(group_box)
group_layout.setSpacing(15)
# 说明文字
desc_label = QLabel("设置新项目的默认创建位置。")
desc_label.setObjectName("settingsDescription")
desc_label.setWordWrap(True)
group_layout.addWidget(desc_label)
# 当前路径显示和选择
path_widget = QWidget()
path_layout = QHBoxLayout(path_widget)
path_layout.setContentsMargins(0, 0, 0, 0)
path_layout.setSpacing(10)
# 路径输入框
self.path_input = QLineEdit()
self.path_input.setObjectName("pathInput")
self.path_input.setPlaceholderText("选择默认项目创建位置...")
self.path_input.setReadOnly(True) # 只读,通过按钮选择
path_layout.addWidget(self.path_input)
# 浏览按钮
self.browse_btn = QPushButton("浏览...")
self.browse_btn.setObjectName("browseBtn")
self.browse_btn.clicked.connect(self.browse_default_location)
path_layout.addWidget(self.browse_btn)
# 重置按钮
self.reset_btn = QPushButton("重置")
self.reset_btn.setObjectName("resetBtn")
self.reset_btn.clicked.connect(self.reset_default_location)
path_layout.addWidget(self.reset_btn)
group_layout.addWidget(path_widget)
layout.addWidget(group_box)
def create_open_location_section(self, layout):
"""创建打开位置设置区域"""
# 分组框
group_box = QGroupBox("打开位置")
group_box.setObjectName("settingsGroup")
group_layout = QVBoxLayout(group_box)
group_layout.setSpacing(15)
# 说明文字
desc_label = QLabel("设置项目文件的默认打开位置。")
desc_label.setObjectName("settingsDescription")
desc_label.setWordWrap(True)
group_layout.addWidget(desc_label)
# 当前路径显示和选择
path_widget = QWidget()
path_layout = QHBoxLayout(path_widget)
path_layout.setContentsMargins(0, 0, 0, 0)
path_layout.setSpacing(10)
# 路径输入框
self.open_path_input = QLineEdit()
self.open_path_input.setObjectName("pathInput")
self.open_path_input.setPlaceholderText("选择默认项目打开位置...")
self.open_path_input.setReadOnly(True) # 只读,通过按钮选择
path_layout.addWidget(self.open_path_input)
# 浏览按钮
self.open_browse_btn = QPushButton("浏览...")
self.open_browse_btn.setObjectName("browseBtn")
self.open_browse_btn.clicked.connect(self.browse_open_location)
path_layout.addWidget(self.open_browse_btn)
# 重置按钮
self.open_reset_btn = QPushButton("重置")
self.open_reset_btn.setObjectName("resetBtn")
self.open_reset_btn.clicked.connect(self.reset_open_location)
path_layout.addWidget(self.open_reset_btn)
group_layout.addWidget(path_widget)
layout.addWidget(group_box)
def browse_open_location(self):
"""浏览选择打开位置"""
current_path = self.open_path_input.text() or self.get_default_projects_path()
folder = QFileDialog.getExistingDirectory(
self,
"选择默认项目打开位置",
current_path
)
if folder:
self.open_path_input.setText(folder)
# 可以根据需要添加保存逻辑
self.save_settings_immediately("default_open_location", self.open_path_input.text())
def reset_open_location(self):
"""重置为默认打开位置"""
default_path = self.get_default_projects_path()
self.open_path_input.setText(default_path)
def create_project_creation_section(self, layout):
"""创建项目创建设置区域"""
# 分组框
group_box = QGroupBox("项目创建设置")
group_box.setObjectName("settingsGroup")
group_layout = QVBoxLayout(group_box)
group_layout.setSpacing(15)
# 自动打开项目文件夹选项
self.auto_open_checkbox = QCheckBox("创建项目后自动打开项目文件夹")
self.auto_open_checkbox.setObjectName("settingsCheckbox")
group_layout.addWidget(self.auto_open_checkbox)
# 创建README文件选项
self.create_readme_checkbox = QCheckBox("自动创建 README.md 文件")
self.create_readme_checkbox.setObjectName("settingsCheckbox")
self.create_readme_checkbox.setChecked(True) # 默认选中
group_layout.addWidget(self.create_readme_checkbox)
# 创建.gitignore文件选项
self.create_gitignore_checkbox = QCheckBox("自动创建 .gitignore 文件")
self.create_gitignore_checkbox.setObjectName("settingsCheckbox")
group_layout.addWidget(self.create_gitignore_checkbox)
layout.addWidget(group_box)
def create_button_section(self, layout):
"""创建按钮区域(已移除,路径选择后自动保存)"""
# 不再需要按钮区域,路径选择后自动保存
pass
def get_default_projects_path(self):
"""获取默认项目目录路径"""
# 获取当前文件所在目录的上级目录(项目根目录)
# current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# return os.path.join(current_dir, "MetaCore_Projects")
current_dir = ''
return current_dir
def browse_default_location(self):
"""浏览选择默认位置"""
current_path = self.path_input.text() or self.get_default_projects_path()
folder = QFileDialog.getExistingDirectory(
self,
"选择默认项目创建位置",
current_path
)
if folder:
self.path_input.setText(folder)
# 路径选择后立即保存设置
self.save_settings_immediately("default_project_location", self.path_input.text())
def reset_default_location(self):
"""重置为默认位置"""
default_path = self.get_default_projects_path()
self.path_input.setText(default_path)
def load_settings(self):
"""加载保存的设置"""
# 加载默认项目位置
default_path = self.settings.value(
"default_project_location",
self.get_default_projects_path()
)
self.path_input.setText(default_path)
default_open_path = self.settings.value(
"default_open_location",
self.get_default_projects_path()
)
self.open_path_input.setText(default_open_path)
# 加载其他设置
# self.auto_open_checkbox.setChecked(
# self.settings.value("auto_open_folder", False, type=bool)
# )
# self.create_readme_checkbox.setChecked(
# self.settings.value("create_readme", True, type=bool)
# )
# self.create_gitignore_checkbox.setChecked(
# self.settings.value("create_gitignore", False, type=bool)
# )
def save_settings_immediately(self, key, value):
"""立即保存设置(路径选择后自动调用)"""
# 保存默认项目位置
self.settings.setValue(key, value)
# 确保设置被保存
self.settings.sync()
# 发出设置变化信号
self.settings_changed.emit()
# 显示简短的成功提示(可选)
# QMessageBox.information(self, "设置已保存", "默认项目位置已更新!")
# def apply_settings(self):
# """应用设置(保留方法以防其他地方调用)"""
# self.save_settings_immediately()
#
# # 显示成功消息
# QMessageBox.information(self, "设置已保存", "项目设置已成功保存!")
#
# def restore_defaults(self):
# """恢复默认设置(已移除按钮,保留方法以防其他地方调用)"""
# reply = QMessageBox.question(
# self,
# "恢复默认设置",
# "确定要恢复所有设置为默认值吗?",
# QMessageBox.Yes | QMessageBox.No,
# QMessageBox.No
# )
#
# if reply == QMessageBox.Yes:
# # 恢复默认值
# self.path_input.setText(self.get_default_projects_path())
#
# # 立即保存恢复的设置
# self.save_settings_immediately()
#
# QMessageBox.information(self, "恢复完成", "已恢复默认设置!")
def get_default_project_location(self):
"""获取默认项目位置"""
return self.settings.value(
"default_project_location",
self.get_default_projects_path()
)
def get_default_open_location(self):
"""获取默认项目位置"""
return self.settings.value(
"default_open_location",
self.get_default_projects_path()
)
# def get_project_creation_settings(self):
# """获取项目创建设置"""
# return {
# 'auto_open_folder': self.settings.value("auto_open_folder", False, type=bool),
# 'create_readme': self.settings.value("create_readme", True, type=bool),
# 'create_gitignore': self.settings.value("create_gitignore", False, type=bool),
# }

283
MetaCore/ui/sidebar.py Normal file
View File

@ -0,0 +1,283 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
侧边栏组件
"""
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from MetaCore.data.project_manager import ProjectManager
from MetaCore.ui.icon_manager import IconManager
class Sidebar(QWidget):
"""侧边栏组件"""
# 信号定义
filter_changed = pyqtSignal(str)
navigation_changed = pyqtSignal(str, str, str) # 新增信号:(section, item_name, filter_type)
create_project_requested = pyqtSignal()
import_project_requested = pyqtSignal()
def __init__(self, project_manager: ProjectManager):
super().__init__()
self.project_manager = project_manager
self.current_filter = "overview"
# 导航项映射filter_type -> (section, item_name)
self.nav_mapping = {
"overview": ("我的项目", "项目概述"),
"management": ("我的项目", "项目管理"),
"resource_category": ("资源管理", "资源分类"),
"resource_management": ("资源管理", "资源管理"),
"project_settings": ("设置中心", "项目设置"),
"system_settings": ("设置中心", "系统设置"),
}
self.init_ui()
self.connect_signals()
# 设置固定宽度
self.setFixedWidth(280)
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Logo区域
self.create_logo_section(layout)
# 快速操作区域
self.create_quick_actions(layout)
# 导航菜单
self.create_navigation_menu(layout)
# 底部用户信息
# layout.addStretch()
# self.create_user_info(layout)
def create_logo_section(self, layout):
"""创建Logo区域"""
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文字
logo_text = QLabel("MetaCore")
logo_text.setObjectName("logoText")
logo_layout.addWidget(logo_text)
logo_layout.addStretch()
layout.addWidget(logo_widget)
def create_quick_actions(self, layout):
"""创建快速操作区域"""
actions_widget = QWidget()
actions_widget.setObjectName("quickActions")
actions_layout = QVBoxLayout(actions_widget)
actions_layout.setContentsMargins(20, 10, 20, 20)
actions_layout.setSpacing(10)
# 创建新项目按钮
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.setObjectName("createBtn")
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("↑ 导入项目")
self.import_btn.setObjectName("sidebarImportBtn")
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):
"""创建导航菜单"""
# 滚动区域
scroll_area = QScrollArea()
scroll_area.setObjectName("navScrollArea") # 设置对象名称
scroll_area.setWidgetResizable(True) # 自适应内容大小
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 水平滚动条
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # 垂直滚动条
# 导航内容
nav_widget = QWidget()
nav_widget.setObjectName("navWidget")
nav_layout = QVBoxLayout(nav_widget)
nav_layout.setContentsMargins(0, 0, 0, 0) # 边距
nav_layout.setSpacing(0) # 间距
# 我的项目分组
self.create_nav_section(nav_layout, "我的项目", [
("📊", "项目概述", "overview"),
("📋", "项目管理", "management"),
])
# 资源管理分组
self.create_nav_section(nav_layout, "资源管理", [
("🏷", "资源分类", "resource_category"),
("📦", "资源管理", "resource_management"),
])
# 设置中心分组
self.create_nav_section(nav_layout, "设置中心", [
("📁", "项目设置", "project_settings"),
("", "系统设置", "system_settings"),
])
nav_layout.addStretch()
scroll_area.setWidget(nav_widget)
layout.addWidget(scroll_area)
def create_nav_section(self, layout, title, items):
"""创建导航分组"""
# 分组标题
section_widget = QWidget()
section_widget.setObjectName("navSection")
section_layout = QVBoxLayout(section_widget)
section_layout.setContentsMargins(0, 0, 0, 0)
section_layout.setSpacing(0)
# 标题按钮(可展开/收起)
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.setObjectName("navSectionTitle")
title_btn.setCheckable(True)
title_btn.setChecked(True)
section_layout.addWidget(title_btn)
# 子项容器
items_widget = QWidget()
items_widget.setObjectName("navItems")
items_layout = QVBoxLayout(items_widget)
items_layout.setContentsMargins(0, 0, 0, 0)
items_layout.setSpacing(0)
# 创建导航项
for icon, text, filter_type in items:
nav_item = self.create_nav_item(icon, text, filter_type)
items_layout.addWidget(nav_item)
section_layout.addWidget(items_widget)
# 展开/收起功能
def toggle_section():
is_expanded = title_btn.isChecked()
items_widget.setVisible(is_expanded)
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}")
title_btn.clicked.connect(toggle_section)
layout.addWidget(section_widget)
def create_nav_item(self, icon, text, filter_type):
"""创建导航项"""
item_btn = QPushButton(f" {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)
# 设置默认选中
if filter_type == "overview":
item_btn.setChecked(True)
# 发送初始导航信号
if filter_type in self.nav_mapping:
section, item_name = self.nav_mapping[filter_type]
# 使用 QTimer.singleShot 确保在UI完全初始化后发送信号
QTimer.singleShot(0, lambda: self.navigation_changed.emit(section, item_name, filter_type))
# 点击事件
def on_clicked():
# 取消其他按钮的选中状态
for btn in self.findChildren(QPushButton):
if btn.objectName() == "navItem" and btn != item_btn:
btn.setChecked(False)
item_btn.setChecked(True)
self.current_filter = filter_type
self.filter_changed.emit(filter_type)
# 发送导航变化信号
if filter_type in self.nav_mapping:
section, item_name = self.nav_mapping[filter_type]
self.navigation_changed.emit(section, item_name, filter_type)
item_btn.clicked.connect(on_clicked)
return item_btn
def create_user_info(self, layout):
"""创建用户信息区域"""
user_widget = QWidget()
user_widget.setObjectName("userInfo")
user_layout = QHBoxLayout(user_widget)
user_layout.setContentsMargins(20, 15, 20, 20)
# 用户头像 - 创建圆形头像
avatar_label = QLabel("U")
avatar_label.setObjectName("userAvatar")
avatar_label.setAlignment(Qt.AlignCenter)
avatar_label.setFixedSize(32, 32)
user_layout.addWidget(avatar_label)
# 用户名
username_label = QLabel("Admin")
username_label.setObjectName("userName")
user_layout.addWidget(username_label)
user_layout.addStretch()
# 下拉箭头
dropdown_label = QLabel("")
dropdown_label.setObjectName("userDropdown")
user_layout.addWidget(dropdown_label)
layout.addWidget(user_widget)
def connect_signals(self):
"""连接信号"""
pass

1705
MetaCore/ui/styles.py Normal file

File diff suppressed because it is too large Load Diff

45
MetaCore/启动应用.bat Normal file
View File

@ -0,0 +1,45 @@
@echo off
echo ========================================
echo MetaCore PyQt5 项目管理平台
echo ========================================
echo.
REM 检查Python是否可用
python --version >nul 2>&1
if errorlevel 1 (
echo [错误] Python未找到请确保Python已正确安装并添加到PATH
pause
exit /b 1
)
echo [信息] Python环境正常
python --version
REM 检查PyQt5是否已安装
python -c "import PyQt5" >nul 2>&1
if errorlevel 1 (
echo [警告] PyQt5未安装正在安装...
python -m pip install PyQt5
if errorlevel 1 (
echo [错误] PyQt5安装失败
pause
exit /b 1
)
echo [成功] PyQt5安装完成
) else (
echo [信息] PyQt5已安装
)
echo.
echo [信息] 正在启动MetaCore应用...
echo ========================================
echo.
REM 启动应用
python main.py
echo.
echo ========================================
echo [信息] MetaCore应用已退出
echo 感谢使用!
pause

78
MetaCore/验证安装.py Normal file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
验证MetaCore安装是否正确
"""
import sys
import os
def main():
print("=" * 50)
print("🧪 MetaCore安装验证")
print("=" * 50)
# 检查Python版本
print(f"🐍 Python版本: {sys.version}")
print(f"📁 Python路径: {sys.executable}")
# 检查PyQt5
try:
import PyQt5
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
print(f"✅ PyQt5版本: {PYQT_VERSION_STR}")
print(f"✅ Qt版本: {QT_VERSION_STR}")
# 测试创建应用
app = QApplication([])
print("✅ PyQt5可以正常创建应用程序")
app.quit()
except ImportError as e:
print(f"❌ PyQt5导入失败: {e}")
return False
# 检查项目文件
required_files = [
'main.py',
'ui/main_window.py',
'data/project_manager.py'
]
print("\n📁 检查项目文件:")
for file_path in required_files:
if os.path.exists(file_path):
print(f"{file_path}")
else:
print(f"{file_path} (缺失)")
return False
# 测试模块导入
print("\n📦 测试模块导入:")
try:
sys.path.insert(0, os.getcwd())
from ui.main_window import MainWindow
from data.project_manager import ProjectManager
print("✅ 所有模块导入成功")
except Exception as e:
print(f"❌ 模块导入失败: {e}")
return False
print("\n" + "=" * 50)
print("🎉 验证完成!")
print("✅ MetaCore安装正确可以正常运行")
print("\n🚀 启动命令:")
print(" python main.py")
print(" 或双击: 启动MetaCore.bat")
print("=" * 50)
return True
if __name__ == "__main__":
success = main()
if not success:
print("\n❌ 验证失败,请检查安装")
input("按Enter键退出...")
else:
input("按Enter键退出...")

139
README.md Normal file
View File

@ -0,0 +1,139 @@
# MetaCore 项目管理平台
现代化的项目管理平台提供Web版本和PyQt5桌面版本。
## 🚀 快速开始
### PyQt5桌面版本推荐
```cmd
# 一键启动
启动MetaCore.bat
# 或进入目录启动
cd MetaCore
启动应用.bat
```
### Web版本
```cmd
# 浏览器打开
web/index.html
```
## 📁 项目结构
```
MetaCore/
├── 🌐 Web版本
│ └── web/ # Web版本目录
│ ├── index.html # Web版本主页面
│ ├── script.js # JavaScript逻辑
│ ├── styles.css # 样式表
│ └── test-cards.html # 测试页面
├── 🖥️ PyQt5桌面版本
│ └── MetaCore/ # 桌面版本目录
│ ├── main.py # 主程序入口
│ ├── 启动应用.bat # 一键启动
│ ├── data/ # 数据模块
│ ├── ui/ # 界面模块
│ └── 验证安装.py # 环境验证工具
├── 📚 文档中心
│ └── Doc/ # 所有文档
│ ├── README.md # 文档索引
│ ├── 快速开始.md # 快速开始指南
│ ├── README_PyQt5.md # PyQt5详细说明
│ ├── 项目总览.md # 项目全面概述
│ ├── 功能对比说明.md # Web vs PyQt5对比
│ ├── 安装PyQt5指南.md # 安装问题解决
│ └── 虚拟环境完整指南.md # 环境配置指南
├── 🧪 测试文件
│ └── tests/ # 测试目录
│ ├── test_*.py # 各种测试文件
│ └── verify_button_fix.py # 验证修复文件
├── 🔧 开发工具
│ └── tools/ # 工具目录
│ ├── build.bat # 构建脚本
│ ├── build_config.py # 构建配置
│ ├── README_打包.md # 打包说明
│ └── 打包指南.md # 打包指南
├── 🚀 快速启动
│ └── 启动MetaCore.bat # 从根目录启动桌面版
└── 🔧 项目配置
├── .gitignore # Git忽略文件
├── data/ # 共享数据目录
└── 项目整理完成报告.md # 项目整理记录
```
## 📚 文档导航
### 🎯 新用户必读
- **[Doc/快速开始.md](Doc/快速开始.md)** - 快速上手指南
- **[Doc/安装PyQt5指南.md](Doc/安装PyQt5指南.md)** - 安装问题解决
### 📖 详细文档
- **[Doc/README.md](Doc/README.md)** - 📚 **文档中心索引**
- **[Doc/README_PyQt5.md](Doc/README_PyQt5.md)** - PyQt5版本完整说明
- **[Doc/项目总览.md](Doc/项目总览.md)** - 项目全面概述
### 🔧 技术文档
- **[Doc/功能对比说明.md](Doc/功能对比说明.md)** - Web vs PyQt5对比
- **[Doc/虚拟环境完整指南.md](Doc/虚拟环境完整指南.md)** - 环境配置指南
## ✨ 功能特性
### 项目管理
- ✅ 创建项目6种模板
- ✅ 导入项目(拖拽支持)
- ✅ 项目搜索和过滤
- ✅ 收藏和管理
### 界面设计
- ✅ 现代化深色主题
- ✅ 三段式项目卡片
- ✅ 2:1创建对话框
- ✅ 响应式布局
### 技术特点
- ✅ Web版本HTML/CSS/JavaScript
- ✅ 桌面版本Python + PyQt5
- ✅ 跨平台支持
- ✅ 数据持久化
## 🔧 系统要求
### PyQt5桌面版本
- Python 3.7+
- PyQt5 5.15.0+
- Windows/macOS/Linux
### Web版本
- 现代浏览器
- 支持HTML5/CSS3
## 🆘 获取帮助
### 常见问题
1. **PyQt5安装问题** → [Doc/安装PyQt5指南.md](Doc/安装PyQt5指南.md)
2. **使用方法** → [Doc/快速开始.md](Doc/快速开始.md)
3. **功能说明** → [Doc/README_PyQt5.md](Doc/README_PyQt5.md)
### 环境验证
```cmd
cd MetaCore
python 验证安装.py
```
### 完整文档
📚 **[Doc/README.md](Doc/README.md)** - 查看完整文档索引
---
🎉 **开始使用MetaCore管理您的项目吧**
💡 **提示:** 推荐使用桌面版本,性能更好,功能更完整。

203
build_app.py Normal file
View File

@ -0,0 +1,203 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MetaCore 应用程序打包脚本
这个脚本用于自动化打包 MetaCore 应用程序为可执行文件
它会
1. 清理之前的构建文件
2. 创建必要的资源目录
3. 使用优化的配置进行打包
4. 验证打包结果
使用方法
python build_app.py
作者: MetaCore Team
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def clean_build_dirs():
"""清理构建目录"""
print("🧹 清理构建目录...")
dirs_to_clean = ['build', 'dist', '__pycache__']
for dir_name in dirs_to_clean:
dir_path = Path(dir_name)
if dir_path.exists():
print(f" 删除: {dir_path}")
shutil.rmtree(dir_path)
# 清理 .pyc 文件
for pyc_file in Path('.').rglob('*.pyc'):
pyc_file.unlink()
print("✅ 清理完成")
def ensure_resources():
"""确保必要的资源目录存在"""
print("📁 检查资源目录...")
resource_dirs = [
Path('MetaCore/Resources/Icons'),
Path('MetaCore/Resources/ProjectPreviews'),
Path('data')
]
for dir_path in resource_dirs:
if not dir_path.exists():
print(f" 创建目录: {dir_path}")
dir_path.mkdir(parents=True, exist_ok=True)
else:
print(f" ✓ 存在: {dir_path}")
print("✅ 资源目录检查完成")
def check_dependencies():
"""检查依赖"""
print("🔍 检查依赖...")
required_packages = ['PyQt5', 'pyinstaller']
missing = []
for package in required_packages:
try:
# 使用pip show来检查包是否安装
result = subprocess.run([sys.executable, '-m', 'pip', 'show', package],
capture_output=True, text=True)
if result.returncode == 0:
print(f"{package}")
else:
missing.append(package)
print(f"{package} - 缺失")
except Exception as e:
print(f" ⚠️ {package} - 检查失败: {e}")
# 如果pip检查失败尝试直接导入
try:
if package == 'PyQt5':
import PyQt5.QtCore
elif package == 'pyinstaller':
import PyInstaller
print(f"{package} (通过导入验证)")
except ImportError:
missing.append(package)
print(f"{package} - 确实缺失")
if missing:
print(f"❌ 缺少依赖: {', '.join(missing)}")
print("请运行: pip install " + ' '.join(missing))
return False
print("✅ 依赖检查完成")
return True
def build_app():
"""构建应用程序"""
print("🔨 开始构建应用程序...")
# 使用自定义的spec文件
spec_file = 'MetaCore.spec'
if not Path(spec_file).exists():
print(f"❌ 找不到 {spec_file} 文件")
return False
try:
# 运行PyInstaller
cmd = [sys.executable, '-m', 'PyInstaller', spec_file, '--clean']
print(f"执行命令: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("✅ 构建成功!")
return True
else:
print("❌ 构建失败:")
print(result.stderr)
return False
except Exception as e:
print(f"❌ 构建过程中发生错误: {e}")
return False
def verify_build():
"""验证构建结果"""
print("🔍 验证构建结果...")
exe_path = Path('dist/MetaCore.exe')
if exe_path.exists():
size_mb = exe_path.stat().st_size / (1024 * 1024)
print(f"✅ 找到可执行文件: {exe_path}")
print(f"📊 文件大小: {size_mb:.1f} MB")
# 检查资源文件是否被正确包含
# 这里可以添加更多的验证逻辑
return True
else:
print("❌ 未找到可执行文件")
return False
def show_results():
"""显示构建结果"""
print("\n" + "="*50)
print("🎉 构建完成!")
print("="*50)
dist_path = Path('dist')
if dist_path.exists():
print(f"📦 输出目录: {dist_path.absolute()}")
for file in dist_path.glob('*'):
if file.is_file():
size_mb = file.stat().st_size / (1024 * 1024)
print(f" 📄 {file.name} ({size_mb:.1f} MB)")
print("\n💡 使用提示:")
print("1. 双击 MetaCore.exe 运行应用程序")
print("2. 可以将 exe 文件分发给其他用户")
print("3. 首次运行可能会慢一些(解压时间)")
def main():
"""主函数"""
print("🚀 MetaCore 应用程序打包工具")
print("="*50)
# 检查是否在项目根目录
if not Path('MetaCore/main.py').exists():
print("❌ 请在项目根目录运行此脚本")
return 1
# 执行构建步骤
try:
clean_build_dirs()
ensure_resources()
# 跳过依赖检查,直接尝试构建
# if not check_dependencies():
# return 1
print("⚠️ 跳过依赖检查,直接尝试构建...")
if not build_app():
return 1
if not verify_build():
return 1
show_results()
return 0
except KeyboardInterrupt:
print("\n⚠️ 用户中断构建")
return 1
except Exception as e:
print(f"\n❌ 构建过程中发生未预期的错误: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())

57
data/projects.json Normal file
View File

@ -0,0 +1,57 @@
[
{
"id": 1,
"title": "智慧工厂",
"date": "2024-06-08 15:56:35",
"type": "industrial",
"image": "🏭",
"path": "",
"project_dir": "",
"description": "",
"status": "normal"
},
{
"id": 2,
"title": "智慧水务",
"date": "2023-01-10 12:09:04",
"type": "smart",
"image": "💧",
"path": "",
"project_dir": "",
"description": "",
"status": "normal"
},
{
"id": 3,
"title": "数字工厂",
"date": "2024-06-07 06:57:46",
"type": "industrial",
"image": "🏗️",
"path": "",
"project_dir": "",
"description": "",
"status": "normal"
},
{
"id": 4,
"title": "智慧监控",
"date": "2024-07-28 17:38:02",
"type": "smart",
"image": "📊",
"path": "",
"project_dir": "",
"description": "",
"status": "normal"
},
{
"id": 5,
"title": "工业设计平台",
"date": "2025-02-28 18:00:05",
"type": "design",
"image": "🎨",
"path": "",
"project_dir": "",
"description": "",
"status": "normal"
}
]

75
quick_build.py Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MetaCore 快速打包脚本
简化版本跳过复杂的检查直接进行打包
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def main():
print("🚀 MetaCore 快速打包")
print("="*30)
# 检查spec文件
spec_file = 'MetaCore.spec'
if not Path(spec_file).exists():
print(f"❌ 找不到 {spec_file} 文件")
print("正在使用默认命令...")
# 使用基本的PyInstaller命令
cmd = [
sys.executable, '-m', 'PyInstaller',
'--onefile',
'--windowed',
'--name=MetaCore',
'--icon=MetaCore/Resources/Icons/app_icon.png',
'MetaCore/main.py'
]
else:
# 使用spec文件
cmd = [sys.executable, '-m', 'PyInstaller', spec_file, '--clean']
print(f"执行命令: {' '.join(cmd)}")
print("开始打包...")
try:
# 运行打包命令
result = subprocess.run(cmd, check=True)
print("✅ 打包完成!")
# 检查结果
exe_path = Path('dist/MetaCore.exe')
if exe_path.exists():
size_mb = exe_path.stat().st_size / (1024 * 1024)
print(f"📦 生成文件: {exe_path}")
print(f"📊 文件大小: {size_mb:.1f} MB")
else:
# 检查是否有main.exe
main_exe = Path('dist/main.exe')
if main_exe.exists():
size_mb = main_exe.stat().st_size / (1024 * 1024)
print(f"📦 生成文件: {main_exe}")
print(f"📊 文件大小: {size_mb:.1f} MB")
print("\n🎉 打包成功可以运行生成的exe文件了。")
except subprocess.CalledProcessError as e:
print(f"❌ 打包失败: {e}")
return 1
except FileNotFoundError:
print("❌ 找不到 PyInstaller请安装: pip install pyinstaller")
return 1
except Exception as e:
print(f"❌ 发生错误: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

11
requirements-minimal.txt Normal file
View File

@ -0,0 +1,11 @@
# MetaCore 最小依赖包
# 仅包含运行应用程序的必需依赖
# GUI框架必需
PyQt5>=5.15.0
# 如果需要打包功能,取消下面的注释
# PyInstaller>=5.0.0
# 如果需要更好的图像支持,取消下面的注释
# Pillow>=8.0.0

57
tests/test_app.py Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试应用程序启动
"""
import sys
import os
def main():
"""主函数"""
print("🚀 测试MetaCore应用程序启动...")
print("=" * 50)
try:
# 导入必要的模块
print("📦 导入模块...")
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.main_window import MainWindow
print("✅ 所有模块导入成功")
# 创建应用程序
print("\n🎯 创建应用程序...")
app = QApplication(sys.argv)
print("✅ QApplication 创建成功")
# 创建项目管理器
print("\n📊 创建项目管理器...")
project_manager = ProjectManager()
print("✅ ProjectManager 创建成功")
# 创建主窗口
print("\n🏠 创建主窗口...")
main_window = MainWindow(project_manager)
print("✅ MainWindow 创建成功")
# 显示窗口
print("\n👁️ 显示窗口...")
main_window.show()
print("✅ 窗口显示成功")
print("\n🎉 应用程序启动成功!")
print("💡 窗口应该已经显示,请检查是否可以正常使用。")
print("⚠️ 关闭窗口来结束测试。")
# 运行应用程序
return app.exec_()
except Exception as e:
print(f"\n❌ 应用程序启动失败: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())

127
tests/test_auto_restore.py Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试自动恢复功能的简单脚本
"""
import sys
import os
import tempfile
import shutil
import time
from datetime import datetime
# 添加MetaCore目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
metacore_dir = os.path.join(current_dir, '..', 'MetaCore')
sys.path.insert(0, metacore_dir)
try:
from data.project_manager import Project, ProjectManager
print("✅ 成功导入项目管理器")
# 创建临时目录
temp_dir = tempfile.mkdtemp(prefix="auto_restore_test_")
print(f"📁 创建临时目录: {temp_dir}")
# 创建项目管理器
pm = ProjectManager()
# 创建测试项目目录
test_project_dir = os.path.join(temp_dir, "自动恢复测试项目")
os.makedirs(test_project_dir, exist_ok=True)
# 创建测试文件
with open(os.path.join(test_project_dir, "test.txt"), 'w', encoding='utf-8') as f:
f.write("这是一个测试文件")
print(f"📂 创建测试项目目录: {test_project_dir}")
# 创建测试项目
test_project = Project(
id=999,
title="自动恢复测试项目",
date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
type="test",
image="🔄",
path=temp_dir,
project_dir=test_project_dir,
description="用于测试自动恢复功能的项目",
status="normal"
)
pm.projects.append(test_project)
print("✅ 创建测试项目")
# 模拟项目目录被删除
print("\n🗑️ 模拟删除项目目录...")
shutil.rmtree(test_project_dir)
print("✅ 项目目录已删除")
# 模拟用户选择保留项目(设置为待删除状态)
test_project.status = "pending_delete"
print("✅ 项目状态设置为 pending_delete")
# 测试检测逻辑
print("\n🔍 测试检测逻辑...")
pm.check_all_projects_existence()
print("✅ 检测逻辑运行完成(应该没有检测到恢复)")
# 恢复项目目录
print("\n📂 恢复项目目录...")
os.makedirs(test_project_dir, exist_ok=True)
with open(os.path.join(test_project_dir, "test.txt"), 'w', encoding='utf-8') as f:
f.write("这是一个恢复的测试文件")
print("✅ 项目目录已恢复")
# 测试自动恢复检测
print("\n🔄 测试自动恢复检测...")
# 创建一个简单的信号接收器来测试
restored_projects = []
def mock_handle_restored(projects):
restored_projects.extend(projects)
print(f"🎉 检测到 {len(projects)} 个项目需要恢复")
for project in projects:
pm.auto_restore_project(project)
print(f"✅ 项目已自动恢复: {project.title}")
# 手动调用检测方法
deleted_projects = []
restored_projects_detected = []
for project in pm.projects:
if project.project_dir:
if project.status == 'pending_delete' and os.path.exists(project.project_dir):
restored_projects_detected.append(project)
if restored_projects_detected:
print(f"🔍 检测到 {len(restored_projects_detected)} 个项目目录已恢复")
mock_handle_restored(restored_projects_detected)
else:
print("❌ 未检测到需要恢复的项目")
# 验证恢复结果
print(f"\n📊 验证结果:")
print(f"项目状态: {test_project.status}")
print(f"目录存在: {os.path.exists(test_project.project_dir)}")
if test_project.status == "normal" and os.path.exists(test_project.project_dir):
print("🎉 自动恢复功能测试成功!")
else:
print("❌ 自动恢复功能测试失败")
# 清理临时目录
print(f"\n🧹 清理临时目录: {temp_dir}")
shutil.rmtree(temp_dir)
print("✅ 清理完成")
except ImportError as e:
print(f"❌ 导入错误:{e}")
sys.exit(1)
except Exception as e:
print(f"❌ 测试失败:{e}")
import traceback
traceback.print_exc()
sys.exit(1)

198
tests/test_card.py Normal file
View File

@ -0,0 +1,198 @@
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel,
QPushButton, QHBoxLayout, QSizePolicy, QFileDialog)
import os
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 1000, 800) # 设置窗口大小
# 使用布局而不是手动定位
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignTop)
for i in range(3): # 减少数量便于查看
card = ProjectCard()
layout.addWidget(card)
class ProjectCard(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("projectCard")
self.setFixedSize(280, 240)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setAttribute(Qt.WA_StyledBackground) # 关键:启用样式背景
self.init_ui()
def init_ui(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)
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.setSpacing(8)
# 项目标题
title_label = QLabel("项目名称")
title_label.setObjectName("projectTitle")
title_label.setWordWrap(True)
header_layout.addWidget(title_label)
# 右侧操作区
actions_layout = QHBoxLayout()
actions_layout.setSpacing(4)
# 菜单按钮
self.menu_btn = QPushButton("")
self.menu_btn.setObjectName("menuBtn")
self.menu_btn.setFixedSize(24, 24)
self.menu_btn.setToolTip("项目操作菜单")
actions_layout.addWidget(self.menu_btn)
header_layout.addLayout(actions_layout)
layout.addWidget(header_widget)
def create_project_image(self, layout):
"""创建项目图片区域"""
image_container = QWidget()
image_container.setObjectName("projectImageContainer")
container_layout = QVBoxLayout(image_container)
container_layout.setContentsMargins(16, 0, 16, 0) # 减少垂直边距
image_widget = QWidget()
image_widget.setObjectName("projectImage")
image_layout = QVBoxLayout(image_widget)
image_layout.setAlignment(Qt.AlignCenter)
image_layout.setContentsMargins(0, 0, 0, 0)
image_layout.setSpacing(8)
# 项目图标
icon_label = QLabel("")
icon_label.setObjectName("projectIcon")
icon_label.setAlignment(Qt.AlignCenter)
image_layout.addWidget(icon_label)
container_layout.addWidget(image_widget)
layout.addWidget(image_container, 1) # 添加拉伸因子
def create_project_footer(self, layout):
"""创建项目底部"""
footer_widget = QWidget()
footer_widget.setObjectName("projectFooter")
footer_layout = QVBoxLayout(footer_widget)
footer_layout.setContentsMargins(16, 8, 16, 16)
# 项目日期
date_label = QLabel("2025年8月6日")
date_label.setObjectName("projectDate")
date_label.setAlignment(Qt.AlignCenter)
footer_layout.addWidget(date_label)
layout.addWidget(footer_widget)
class StyleSheet:
@staticmethod
def get_main_style():
return """
/* 基础卡片样式 - 修复继承问题 */
#projectCard {
background-color: #8b5cf6;
border: 1px solid #5a5a6a;
border-radius: 16px;
}
/* 简化悬停状态 */
#projectCard:hover {
background-color: #7c3aed;
border-color: #6a6a7a;
}
/* 头部样式 - 移除透明背景 */
#projectHeader {
background-color: #8b5cf6;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
#projectTitle {
font-size: 14px;
font-weight: 600;
color: #ffffff;
background-color: transparent;
}
#menuBtn {
background-color: transparent;
border: none;
border-radius: 4px;
color: #ffffff;
font-size: 16px;
}
#menuBtn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
/* 图片区域样式 - 调整尺寸和边距 */
#projectImageContainer {
background-color: transparent;
padding: 0px; /* 减少内边距 */
}
#projectImage {
background-color: #3a3a4a;
border-radius: 12px;
min-height: 100px; /* 减小最小高度 */
max-height: 120px;
}
#projectIcon {
font-size: 36px; /* 减小图标大小 */
color: #ffffff;
padding: 10px;
}
/* 底部样式 - 移除透明背景 */
#projectFooter {
background-color: #8b5cf6;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
#projectDate {
font-size: 11px;
color: #e0e0ff; /* 更亮的颜色提高可读性 */
font-weight: 400;
background-color: transparent;
}
"""
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyleSheet(StyleSheet.get_main_style())
w = MainWindow()
w.show()
app.exec_()

171
tests/test_environment.py Normal file
View File

@ -0,0 +1,171 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
环境测试脚本 - 检查MetaCore运行环境
"""
import sys
import os
import subprocess
def test_python():
"""测试Python环境"""
print("🐍 Python环境检查")
print(f" Python版本: {sys.version}")
print(f" Python路径: {sys.executable}")
print(f" 平台: {sys.platform}")
# 检查Python版本
if sys.version_info < (3, 6):
print(" ❌ Python版本过低需要3.6或更高版本")
return False
else:
print(" ✅ Python版本符合要求")
return True
def test_pyqt5():
"""测试PyQt5"""
print("\n🎨 PyQt5环境检查")
try:
import PyQt5
print(f" PyQt5版本: {PyQt5.Qt.PYQT_VERSION_STR}")
print(f" Qt版本: {PyQt5.Qt.QT_VERSION_STR}")
# 尝试创建应用程序
from PyQt5.QtWidgets import QApplication
app = QApplication([])
print(" ✅ PyQt5可以正常使用")
app.quit()
return True
except ImportError as e:
print(f" ❌ PyQt5未安装: {e}")
print(" 💡 安装命令: pip install PyQt5")
return False
except Exception as e:
print(f" ❌ PyQt5测试失败: {e}")
return False
def test_project_files():
"""测试项目文件"""
print("\n📁 项目文件检查")
required_files = [
'main.py',
'ui/main_window.py',
'ui/sidebar.py',
'ui/project_area.py',
'ui/project_card.py',
'data/project_manager.py'
]
missing_files = []
for file_path in required_files:
if os.path.exists(file_path):
print(f"{file_path}")
else:
print(f"{file_path} (缺失)")
missing_files.append(file_path)
if missing_files:
print(f" ⚠️ 缺失 {len(missing_files)} 个文件")
return False
else:
print(" ✅ 所有必需文件都存在")
return True
def test_imports():
"""测试模块导入"""
print("\n📦 模块导入检查")
modules_to_test = [
('ui.main_window', 'MainWindow'),
('ui.sidebar', 'Sidebar'),
('ui.project_area', 'ProjectArea'),
('ui.project_card', 'ProjectCard'),
('data.project_manager', 'ProjectManager'),
]
import_errors = []
for module_name, class_name in modules_to_test:
try:
module = __import__(module_name, fromlist=[class_name])
getattr(module, class_name)
print(f"{module_name}.{class_name}")
except Exception as e:
print(f"{module_name}.{class_name}: {e}")
import_errors.append((module_name, e))
if import_errors:
print(f" ⚠️ {len(import_errors)} 个模块导入失败")
return False
else:
print(" ✅ 所有模块导入成功")
return True
def test_virtual_env():
"""检查是否在虚拟环境中"""
print("\n🔧 虚拟环境检查")
# 检查是否在虚拟环境中
in_venv = (
hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
)
if in_venv:
print(" ✅ 当前在虚拟环境中")
print(f" 环境路径: {sys.prefix}")
else:
print(" ⚠️ 当前不在虚拟环境中")
print(" 💡 建议使用虚拟环境运行项目")
return True
def main():
"""主测试函数"""
print("=" * 50)
print("🧪 MetaCore环境测试")
print("=" * 50)
tests = [
test_python,
test_pyqt5,
test_project_files,
test_imports,
test_virtual_env
]
results = []
for test_func in tests:
try:
result = test_func()
results.append(result)
except Exception as e:
print(f" ❌ 测试失败: {e}")
results.append(False)
print("\n" + "=" * 50)
print("📊 测试结果汇总")
print("=" * 50)
passed = sum(results)
total = len(results)
if passed == total:
print(f"🎉 所有测试通过 ({passed}/{total})")
print("✅ 环境配置正确可以运行MetaCore")
print("\n🚀 运行命令:")
print(" python main.py")
else:
print(f"⚠️ 部分测试失败 ({passed}/{total})")
print("❌ 请根据上述提示修复问题")
if not any(results[:2]): # Python和PyQt5测试失败
print("\n💡 建议操作:")
print("1. 确保Python 3.6+已安装")
print("2. 安装PyQt5: pip install PyQt5")
print("3. 重新运行测试: python test_environment.py")
if __name__ == "__main__":
main()

View File

164
tests/test_fixes.py Normal file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试修复后的应用程序
"""
import sys
import os
def test_basic_imports():
"""测试基础导入"""
print("🔍 测试基础模块导入...")
try:
# 测试数据管理器
from data.project_manager import ProjectManager
print("✅ ProjectManager 导入成功")
# 测试UI组件
from ui.main_window import MainWindow
print("✅ MainWindow 导入成功")
from ui.import_project_dialog import ImportProjectDialog
print("✅ ImportProjectDialog 导入成功")
return True
except Exception as e:
print(f"❌ 导入失败: {e}")
import traceback
traceback.print_exc()
return False
def test_project_manager():
"""测试项目管理器"""
print("\n🔍 测试项目管理器...")
try:
from data.project_manager import ProjectManager
# 创建项目管理器实例
pm = ProjectManager()
print("✅ ProjectManager 实例创建成功")
# 测试基本功能
projects = pm.get_all_projects()
print(f"✅ 获取项目列表成功,共 {len(projects)} 个项目")
return True
except Exception as e:
print(f"❌ 项目管理器测试失败: {e}")
import traceback
traceback.print_exc()
return False
def test_dialog_creation():
"""测试对话框创建"""
print("\n🔍 测试对话框创建...")
try:
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.import_project_dialog import ImportProjectDialog
# 创建应用程序实例
app = QApplication.instance()
if app is None:
app = QApplication([])
# 创建项目管理器
pm = ProjectManager()
# 创建导入对话框
dialog = ImportProjectDialog(pm)
print("✅ ImportProjectDialog 创建成功")
# 测试对话框的基本属性
print(f"✅ 对话框标题: {dialog.windowTitle()}")
print(f"✅ 对话框大小: {dialog.size().width()}x{dialog.size().height()}")
return True
except Exception as e:
print(f"❌ 对话框创建失败: {e}")
import traceback
traceback.print_exc()
return False
def test_main_window():
"""测试主窗口创建"""
print("\n🔍 测试主窗口创建...")
try:
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.main_window import MainWindow
# 创建应用程序实例
app = QApplication.instance()
if app is None:
app = QApplication([])
# 创建项目管理器
pm = ProjectManager()
# 创建主窗口
window = MainWindow(pm)
print("✅ MainWindow 创建成功")
# 测试窗口的基本属性
print(f"✅ 窗口标题: {window.windowTitle()}")
print(f"✅ 窗口大小: {window.size().width()}x{window.size().height()}")
return True
except Exception as e:
print(f"❌ 主窗口创建失败: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""主函数"""
print("🚀 开始测试修复后的应用程序...")
print("=" * 60)
tests = [
("基础导入", test_basic_imports),
("项目管理器", test_project_manager),
("对话框创建", test_dialog_creation),
("主窗口创建", test_main_window),
]
passed = 0
total = len(tests)
for test_name, test_func in tests:
print(f"\n📋 测试: {test_name}")
print("-" * 40)
if test_func():
passed += 1
print(f"{test_name} 测试通过")
else:
print(f"{test_name} 测试失败")
print("\n" + "=" * 60)
print(f"📊 测试结果: {passed}/{total} 通过")
if passed == total:
print("🎉 所有测试通过!应用程序修复成功。")
print("\n💡 现在可以尝试启动应用程序:")
print(" python main.py")
return True
else:
print("❌ 部分测试失败,需要进一步修复。")
return False
if __name__ == "__main__":
success = main()
print("\n按Enter键退出...")
input()
sys.exit(0 if success else 1)

144
tests/test_folder_import.py Normal file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试文件夹导入功能
"""
import sys
import os
def test_folder_import_dialog():
"""测试文件夹导入对话框"""
print("🔍 测试文件夹导入对话框...")
try:
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.import_project_dialog import ImportProjectDialog
# 创建应用程序实例
app = QApplication.instance()
if app is None:
app = QApplication([])
# 创建项目管理器
project_manager = ProjectManager()
print("✅ ProjectManager 创建成功")
# 创建导入对话框
dialog = ImportProjectDialog(project_manager)
print("✅ ImportProjectDialog 创建成功")
# 检查对话框标题
expected_title = "导入文件夹"
actual_title = dialog.windowTitle()
if actual_title == expected_title:
print(f"✅ 窗口标题正确: {actual_title}")
else:
print(f"❌ 窗口标题错误: 期望 '{expected_title}', 实际 '{actual_title}'")
# 检查UI文本
print("✅ 对话框创建成功UI文本应该已更新为文件夹相关")
# 显示对话框进行手动测试
print("\n💡 对话框将显示,请测试以下功能:")
print(" 1. 检查标题是否为'导入文件夹'")
print(" 2. 检查GroupBox标题是否为'选择文件夹'")
print(" 3. 检查按钮文字是否为'点击选择文件夹'")
print(" 4. 检查提示文字是否为'拖拽文件夹到这里'")
print(" 5. 检查格式提示是否为'支持任意文件夹'")
print(" 6. 测试点击选择按钮是否打开文件夹选择对话框")
print(" 7. 测试拖拽文件夹功能")
print(" 8. 关闭对话框完成测试")
# 显示对话框
dialog.show()
result = dialog.exec_()
if result == dialog.Accepted:
print("✅ 对话框正常关闭(用户点击了导入)")
else:
print("✅ 对话框正常关闭(用户取消了操作)")
return True
except Exception as e:
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
def verify_changes():
"""验证代码修改"""
print("🔍 验证代码修改...")
try:
# 读取修改后的文件
with open('ui/import_project_dialog.py', 'r', encoding='utf-8') as f:
content = f.read()
# 检查关键修改
checks = [
("窗口标题", 'self.setWindowTitle("导入文件夹")'),
("GroupBox标题", 'QGroupBox("选择文件夹")'),
("按钮文字", 'QPushButton("点击选择文件夹")'),
("提示文字", '"拖拽文件夹到这里,或者"'),
("格式提示", '"支持任意文件夹"'),
("文件夹选择", 'QFileDialog.getExistingDirectory'),
("文件夹过滤", 'os.path.isdir(f)'),
]
all_passed = True
for check_name, check_text in checks:
if check_text in content:
print(f"{check_name}: 已修改")
else:
print(f"{check_name}: 未找到修改")
all_passed = False
return all_passed
except Exception as e:
print(f"❌ 验证失败: {e}")
return False
def main():
"""主函数"""
print("🔧 测试导入文件夹功能修改")
print("=" * 60)
# 验证代码修改
code_ok = verify_changes()
if code_ok:
print("\n✅ 代码修改验证通过")
# 测试对话框
dialog_ok = test_folder_import_dialog()
if dialog_ok:
print("\n🎉 所有测试通过!")
print("\n📋 修改总结:")
print("1. ✅ 窗口标题: '导入项目文件''导入文件夹'")
print("2. ✅ GroupBox标题: '选择项目文件''选择文件夹'")
print("3. ✅ 按钮文字: '点击选择文件''点击选择文件夹'")
print("4. ✅ 提示文字: '拖拽文件到这里''拖拽文件夹到这里'")
print("5. ✅ 格式提示: '支持.zip,.rar,.7z格式''支持任意文件夹'")
print("6. ✅ 选择逻辑: 文件选择 → 文件夹选择")
print("7. ✅ 拖拽逻辑: 支持文件夹过滤")
print("8. ✅ 列表显示: 显示文件夹信息和文件数量")
print("9. ✅ 导入逻辑: 直接使用文件夹路径")
print("\n💡 现在可以导入文件夹作为项目了!")
return True
else:
print("\n❌ 对话框测试失败")
return False
else:
print("\n❌ 代码修改验证失败")
return False
if __name__ == "__main__":
success = main()
print("\n按Enter键退出...")
input()
sys.exit(0 if success else 1)

148
tests/test_import_dialog.py Normal file
View File

@ -0,0 +1,148 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试导入对话框功能
"""
import sys
import os
def test_import_dialog():
"""测试导入对话框"""
print("🔍 测试导入对话框功能...")
try:
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.import_project_dialog import ImportProjectDialog
# 创建应用程序实例
app = QApplication.instance()
if app is None:
app = QApplication([])
# 创建项目管理器
project_manager = ProjectManager()
print("✅ ProjectManager 创建成功")
# 创建导入对话框
dialog = ImportProjectDialog(project_manager)
print("✅ ImportProjectDialog 创建成功")
# 测试对话框属性
print(f"✅ 对话框标题: {dialog.windowTitle()}")
print(f"✅ 对话框大小: {dialog.size().width()}x{dialog.size().height()}")
# 测试上传区域
upload_area = dialog.upload_area
print("✅ UploadArea 组件存在")
# 检查信号连接
print("✅ 检查信号连接:")
print(f" - files_dropped 信号: {'已连接' if upload_area.files_dropped.receivers() > 0 else '未连接'}")
print(f" - select_files_requested 信号: {'已连接' if upload_area.select_files_requested.receivers() > 0 else '未连接'}")
# 显示对话框进行手动测试
print("\n💡 对话框将显示,请测试以下功能:")
print(" 1. 点击拖拽区域内的'点击选择'按钮")
print(" 2. 点击下方的'点击选择文件'按钮")
print(" 3. 拖拽文件到拖拽区域")
print(" 4. 关闭对话框完成测试")
# 显示对话框
dialog.show()
result = dialog.exec_()
if result == dialog.Accepted:
print("✅ 对话框正常关闭(用户点击了导入)")
else:
print("✅ 对话框正常关闭(用户取消了操作)")
return True
except Exception as e:
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
def test_upload_area_signals():
"""测试上传区域信号"""
print("\n🔍 测试UploadArea信号...")
try:
from PyQt5.QtWidgets import QApplication
from ui.import_project_dialog import UploadArea
# 创建应用程序实例
app = QApplication.instance()
if app is None:
app = QApplication([])
# 创建上传区域
upload_area = UploadArea()
print("✅ UploadArea 创建成功")
# 测试信号存在
print("✅ 检查信号定义:")
print(f" - files_dropped: {'存在' if hasattr(upload_area, 'files_dropped') else '不存在'}")
print(f" - select_files_requested: {'存在' if hasattr(upload_area, 'select_files_requested') else '不存在'}")
# 测试信号连接
signal_test_result = []
def on_files_dropped(files):
signal_test_result.append(f"files_dropped: {len(files)} 个文件")
def on_select_files_requested():
signal_test_result.append("select_files_requested: 信号触发")
upload_area.files_dropped.connect(on_files_dropped)
upload_area.select_files_requested.connect(on_select_files_requested)
print("✅ 信号连接成功")
# 模拟信号触发
upload_area.select_files_requested.emit()
if signal_test_result:
print(f"✅ 信号测试成功: {signal_test_result}")
else:
print("⚠️ 信号测试未收到响应")
return True
except Exception as e:
print(f"❌ 信号测试失败: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""主函数"""
print("🚀 测试导入对话框按钮修复...")
print("=" * 60)
# 测试信号
signal_success = test_upload_area_signals()
if signal_success:
# 测试完整对话框
dialog_success = test_import_dialog()
if dialog_success:
print("\n🎉 所有测试通过!")
print("💡 '点击选择'按钮现在应该可以正常工作了。")
return True
else:
print("\n❌ 对话框测试失败")
return False
else:
print("\n❌ 信号测试失败")
return False
if __name__ == "__main__":
success = main()
print("\n按Enter键退出...")
input()
sys.exit(0 if success else 1)

94
tests/test_import_fix.py Normal file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试导入修复
"""
import sys
import os
def test_imports():
"""测试模块导入"""
print("🔍 测试模块导入...")
try:
# 测试基础模块导入
from data.project_manager import ProjectManager
print("✅ data.project_manager 导入成功")
from ui.main_window import MainWindow
print("✅ ui.main_window 导入成功")
from ui.import_project_dialog import ImportProjectDialog
print("✅ ui.import_project_dialog 导入成功")
from ui.sidebar import Sidebar
print("✅ ui.sidebar 导入成功")
from ui.project_area import ProjectArea
print("✅ ui.project_area 导入成功")
from ui.project_card import ProjectCard
print("✅ ui.project_card 导入成功")
return True
except Exception as e:
print(f"❌ 导入失败: {e}")
return False
def test_dialog_creation():
"""测试对话框创建"""
print("\n🔍 测试对话框创建...")
try:
from PyQt5.QtWidgets import QApplication
from data.project_manager import ProjectManager
from ui.import_project_dialog import ImportProjectDialog
# 创建应用程序实例(如果不存在)
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
# 创建项目管理器
project_manager = ProjectManager()
print("✅ ProjectManager 创建成功")
# 创建导入对话框
dialog = ImportProjectDialog(project_manager)
print("✅ ImportProjectDialog 创建成功")
return True
except Exception as e:
print(f"❌ 对话框创建失败: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""主函数"""
print("🚀 开始测试导入修复...")
print("=" * 50)
# 测试导入
import_success = test_imports()
if import_success:
# 测试对话框创建
dialog_success = test_dialog_creation()
if dialog_success:
print("\n🎉 所有测试通过!导入问题已修复。")
return True
else:
print("\n❌ 对话框创建测试失败")
return False
else:
print("\n❌ 模块导入测试失败")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)

View File

View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试待删除项目功能
"""
import sys
import os
import tempfile
import shutil
from datetime import datetime
# 添加MetaCore目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
metacore_dir = os.path.join(current_dir, '..', 'MetaCore')
sys.path.insert(0, metacore_dir)
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from data.project_manager import ProjectManager, Project
from ui.project_card import ProjectCard
from ui.styles import StyleSheet
class PendingDeleteTestWindow(QMainWindow):
"""待删除功能测试窗口"""
def __init__(self):
super().__init__()
self.setWindowTitle("待删除项目功能测试")
self.setGeometry(100, 100, 1000, 700)
# 应用深色主题样式
self.setStyleSheet(StyleSheet.get_main_style())
# 创建临时目录用于测试
self.temp_dir = tempfile.mkdtemp(prefix="metacore_test_")
# 创建测试项目
self.create_test_projects()
# 设置UI
self.setup_ui()
def create_test_projects(self):
"""创建测试项目"""
self.project_manager = ProjectManager()
# 创建一个真实的项目目录
self.test_project_dir = os.path.join(self.temp_dir, "测试项目")
os.makedirs(self.test_project_dir, exist_ok=True)
# 创建一些测试文件
with open(os.path.join(self.test_project_dir, "README.md"), 'w', encoding='utf-8') as f:
f.write("# 测试项目\n这是一个用于测试待删除功能的项目。")
# 创建测试项目
test_project = Project(
id=999,
title="测试项目",
date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
type="test",
image="🧪",
path=self.temp_dir,
project_dir=self.test_project_dir,
description="用于测试待删除功能的项目",
status="normal"
)
self.project_manager.projects.append(test_project)
self.test_project = test_project
def setup_ui(self):
"""设置用户界面"""
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(20)
# 标题
title_label = QLabel("待删除项目功能测试")
title_label.setStyleSheet("""
font-size: 24px;
font-weight: bold;
color: #ffffff;
margin-bottom: 10px;
""")
main_layout.addWidget(title_label)
# 说明文字
desc_label = QLabel(
"测试流程:\n"
"1. 点击「删除项目目录」按钮模拟项目目录被删除\n"
"2. 等待5秒系统会检测到项目目录不存在\n"
"3. 在弹出的对话框中点击「No」保留项目\n"
"4. 观察项目卡片变浅,菜单按钮变成叉号\n"
"5. 重要:系统不会再次弹窗提示(已修复)\n"
"6. 点击「恢复项目目录」按钮恢复项目目录\n"
"7. 🆕 系统会自动检测并恢复项目(无需点击)\n"
"8. 或者点击叉号按钮永久删除项目"
)
desc_label.setStyleSheet("""
font-size: 14px;
color: #cccccc;
margin-bottom: 20px;
padding: 15px;
background-color: rgba(60, 60, 60, 0.5);
border-radius: 8px;
""")
main_layout.addWidget(desc_label)
# 控制按钮
button_layout = QHBoxLayout()
self.delete_dir_btn = QPushButton("删除项目目录")
self.delete_dir_btn.setStyleSheet("""
QPushButton {
background-color: #ff6b6b;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #ff5252;
}
""")
self.delete_dir_btn.clicked.connect(self.delete_project_directory)
button_layout.addWidget(self.delete_dir_btn)
self.restore_dir_btn = QPushButton("恢复项目目录")
self.restore_dir_btn.setStyleSheet("""
QPushButton {
background-color: #4caf50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
""")
self.restore_dir_btn.clicked.connect(self.restore_project_directory)
self.restore_dir_btn.setEnabled(False)
button_layout.addWidget(self.restore_dir_btn)
button_layout.addStretch()
main_layout.addLayout(button_layout)
# 项目卡片容器
card_container = QWidget()
card_layout = QHBoxLayout(card_container)
card_layout.setContentsMargins(10, 10, 10, 10)
# 创建项目卡片
self.project_card = ProjectCard(self.test_project, self.project_manager, "grid")
card_layout.addWidget(self.project_card)
card_layout.addStretch()
main_layout.addWidget(card_container)
main_layout.addStretch()
# 状态显示
self.status_label = QLabel("状态:项目目录存在,项目状态正常")
self.status_label.setStyleSheet("""
font-size: 12px;
color: #4caf50;
padding: 10px;
background-color: rgba(76, 175, 80, 0.1);
border-radius: 6px;
border: 1px solid #4caf50;
""")
main_layout.addWidget(self.status_label)
# 连接项目管理器信号
self.project_manager.project_updated.connect(self.on_project_updated)
def delete_project_directory(self):
"""删除项目目录"""
if os.path.exists(self.test_project_dir):
shutil.rmtree(self.test_project_dir)
self.delete_dir_btn.setEnabled(False)
self.restore_dir_btn.setEnabled(True)
self.status_label.setText("状态:项目目录已删除,等待系统检测...")
self.status_label.setStyleSheet("""
font-size: 12px;
color: #ff9800;
padding: 10px;
background-color: rgba(255, 152, 0, 0.1);
border-radius: 6px;
border: 1px solid #ff9800;
""")
def restore_project_directory(self):
"""恢复项目目录"""
if not os.path.exists(self.test_project_dir):
os.makedirs(self.test_project_dir, exist_ok=True)
# 重新创建测试文件
with open(os.path.join(self.test_project_dir, "README.md"), 'w', encoding='utf-8') as f:
f.write("# 测试项目\n这是一个用于测试待删除功能的项目。\n\n项目目录已恢复!")
self.delete_dir_btn.setEnabled(True)
self.restore_dir_btn.setEnabled(False)
self.status_label.setText("状态:项目目录已恢复,等待系统自动检测恢复...")
self.status_label.setStyleSheet("""
font-size: 12px;
color: #4caf50;
padding: 10px;
background-color: rgba(76, 175, 80, 0.1);
border-radius: 6px;
border: 1px solid #4caf50;
""")
def on_project_updated(self, project: Project):
"""项目更新事件"""
if project.id == self.test_project.id:
self.test_project = project
self.project_card.project = project
self.project_card.update_display()
if project.status == 'pending_delete':
self.status_label.setText("状态:项目处于待删除状态(卡片变浅,显示叉号)")
self.status_label.setStyleSheet("""
font-size: 12px;
color: #f44336;
padding: 10px;
background-color: rgba(244, 67, 54, 0.1);
border-radius: 6px;
border: 1px solid #f44336;
""")
elif project.status == 'normal':
self.status_label.setText("状态:项目已恢复正常状态")
self.status_label.setStyleSheet("""
font-size: 12px;
color: #4caf50;
padding: 10px;
background-color: rgba(76, 175, 80, 0.1);
border-radius: 6px;
border: 1px solid #4caf50;
""")
def closeEvent(self, event):
"""窗口关闭事件"""
# 清理临时目录
if os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
event.accept()
def main():
"""主函数"""
app = QApplication(sys.argv)
# 设置应用程序信息
app.setApplicationName("待删除功能测试")
app.setApplicationVersion("1.0.0")
# 创建并显示测试窗口
window = PendingDeleteTestWindow()
window.show()
# 运行应用程序
sys.exit(app.exec_())
if __name__ == "__main__":
main()

View File

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试项目状态功能的简单脚本
"""
import sys
import os
# 添加MetaCore目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
metacore_dir = os.path.join(current_dir, '..', 'MetaCore')
sys.path.insert(0, metacore_dir)
try:
from data.project_manager import Project, ProjectManager
print("✅ 成功导入项目管理器")
# 测试Project类的新status字段
project = Project(
id=1,
title="测试项目",
date="2024-01-01 12:00:00",
type="test",
image="🧪",
path="/test/path",
project_dir="/test/path/测试项目",
description="测试项目描述",
status="normal"
)
print(f"✅ 项目创建成功,状态:{project.status}")
# 测试状态变更
project.status = "pending_delete"
print(f"✅ 状态变更成功:{project.status}")
# 测试序列化
project_dict = project.to_dict()
print(f"✅ 序列化成功包含status字段{'status' in project_dict}")
# 测试反序列化
new_project = Project.from_dict(project_dict)
print(f"✅ 反序列化成功,状态:{new_project.status}")
# 测试项目管理器的新方法
pm = ProjectManager()
print("✅ 项目管理器创建成功")
# 检查新方法是否存在
if hasattr(pm, 'restore_project'):
print("✅ restore_project方法存在")
else:
print("❌ restore_project方法不存在")
if hasattr(pm, 'confirm_delete_project'):
print("✅ confirm_delete_project方法存在")
else:
print("❌ confirm_delete_project方法不存在")
print("\n🎉 所有基本功能测试通过!")
except ImportError as e:
print(f"❌ 导入错误:{e}")
sys.exit(1)
except Exception as e:
print(f"❌ 测试失败:{e}")
sys.exit(1)

62
tests/test_simple.py Normal file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简单测试项目创建功能
"""
import os
import tempfile
import shutil
from data.project_manager import ProjectManager
def test_simple():
"""简单测试"""
print("开始测试项目创建功能...")
# 创建临时目录
temp_dir = tempfile.mkdtemp(prefix="test_")
print(f"测试目录: {temp_dir}")
try:
# 创建项目管理器
pm = ProjectManager()
# 创建测试项目
project = pm.add_project(
title="测试项目",
description="这是一个测试项目",
project_type="industrial",
path=temp_dir
)
print(f"项目创建成功:")
print(f" 名称: {project.title}")
print(f" 类型: {project.type}")
print(f" 目录: {project.project_dir}")
print(f" 描述: {project.description}")
# 检查目录是否存在
if os.path.exists(project.project_dir):
print("✓ 项目目录已创建")
# 列出目录内容
contents = os.listdir(project.project_dir)
print(f"目录内容: {contents}")
else:
print("✗ 项目目录未创建")
except Exception as e:
print(f"错误: {e}")
import traceback
traceback.print_exc()
finally:
# 清理
try:
shutil.rmtree(temp_dir)
print("测试目录已清理")
except:
pass
if __name__ == "__main__":
test_simple()

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
验证按钮修复
"""
import sys
def verify_code_changes():
"""验证代码修改"""
print("🔍 验证代码修改...")
try:
# 读取修改后的文件
with open('ui/import_project_dialog.py', 'r', encoding='utf-8') as f:
content = f.read()
# 检查关键修改
checks = [
("select_files_requested 信号定义", "select_files_requested = pyqtSignal()"),
("按钮点击事件连接", "select_btn.clicked.connect(self.select_files_requested.emit)"),
("信号连接到主对话框", "self.upload_area.select_files_requested.connect(self.select_files)"),
]
all_passed = True
for check_name, check_text in checks:
if check_text in content:
print(f"{check_name}: 已修复")
else:
print(f"{check_name}: 未找到修复")
all_passed = False
return all_passed
except Exception as e:
print(f"❌ 验证失败: {e}")
return False
def verify_import():
"""验证导入"""
print("\n🔍 验证模块导入...")
try:
from ui.import_project_dialog import ImportProjectDialog, UploadArea
print("✅ ImportProjectDialog 导入成功")
print("✅ UploadArea 导入成功")
# 检查信号定义
upload_area = UploadArea()
if hasattr(upload_area, 'select_files_requested'):
print("✅ select_files_requested 信号存在")
else:
print("❌ select_files_requested 信号不存在")
return False
return True
except Exception as e:
print(f"❌ 导入验证失败: {e}")
return False
def main():
"""主函数"""
print("🔧 验证'点击选择'按钮修复")
print("=" * 50)
# 验证代码修改
code_ok = verify_code_changes()
# 验证导入
import_ok = verify_import()
print("\n" + "=" * 50)
if code_ok and import_ok:
print("🎉 修复验证成功!")
print("\n📋 修复内容:")
print("1. ✅ 在 UploadArea 类中添加了 select_files_requested 信号")
print("2. ✅ 连接了'点击选择'按钮的点击事件到信号")
print("3. ✅ 在主对话框中连接了信号到 select_files 方法")
print("\n💡 现在拖拽区域中的'点击选择'按钮应该可以正常工作了!")
print(" 点击按钮会打开文件选择对话框,让用户选择要导入的文件。")
return True
else:
print("❌ 修复验证失败,请检查代码修改。")
return False
if __name__ == "__main__":
success = main()
print("\n按Enter键退出...")
input()
sys.exit(0 if success else 1)

122
tools/README_打包.md Normal file
View File

@ -0,0 +1,122 @@
# MetaCore 打包说明
## 📦 已创建的打包文件
我已经为您创建了多个打包工具,您可以根据环境选择使用:
### 1. 批处理文件Windows 推荐)
- `quick_build.bat` - 快速打包脚本(推荐)
- `build.bat` - 基础打包脚本
### 2. PowerShell 脚本
- `build.ps1` - PowerShell 打包脚本
### 3. Python 脚本
- `build_simple.py` - 简化 Python 打包脚本
- `build_config.py` - 完整配置打包脚本
### 4. 说明文档
- `打包指南.md` - 详细打包指南
- `README_打包.md` - 本文件
## 🚀 快速开始
### 方法一:使用批处理文件(最简单)
1. **右键点击** `quick_build.bat`
2. **选择** "以管理员身份运行"
3. **等待** 打包完成
4. **查看** `dist` 文件夹中的 `MetaCore.exe`
### 方法二:使用命令提示符
1. **打开命令提示符**(按 Win+R输入 cmd
2. **切换到项目目录**
```cmd
cd C:\Users\29381\Desktop\MetaCore
```
3. **运行打包脚本**
```cmd
quick_build.bat
```
### 方法三:手动打包
如果自动脚本不工作,可以手动执行:
1. **安装 PyInstaller**
```cmd
pip install pyinstaller
```
2. **安装项目依赖**
```cmd
pip install PyQt5
```
3. **执行打包命令**
```cmd
pyinstaller --onefile --windowed --name=MetaCore --add-data="MetaCore/data;data" --add-data="MetaCore/ui;ui" --hidden-import=PyQt5.sip MetaCore/main.py
```
## 📁 输出文件
打包成功后,您会得到:
- `dist/MetaCore.exe` - 可执行文件(这是您需要的主文件)
- `build/` - 临时构建文件(可以删除)
- `MetaCore.spec` - PyInstaller 配置文件
## 🎯 使用打包后的程序
1. **复制** `dist/MetaCore.exe` 到任意位置
2. **双击** `MetaCore.exe` 即可运行
3. **无需** 安装 Python 或其他依赖
4. **可以** 分发给其他用户使用
## ⚠️ 常见问题
### Python 未找到
- 确保已安装 Python 3.7+
- 确保 Python 已添加到系统 PATH
- 尝试使用 `py` 命令替代 `python`
### PyInstaller 安装失败
```cmd
python -m pip install --upgrade pip
python -m pip install pyinstaller
```
### 打包失败
1. 检查所有文件是否完整
2. 确保没有其他程序占用文件
3. 以管理员身份运行打包脚本
### 生成的文件过大
- 正常情况下文件大小约 50-100MB
- 这是因为包含了完整的 Python 运行时和 PyQt5 库
## 🔧 高级配置
如需自定义打包配置,可以:
1. **编辑** `MetaCore.spec` 文件
2. **运行**
```cmd
pyinstaller MetaCore.spec
```
## 📞 技术支持
如果遇到问题:
1. 查看 `打包指南.md` 获取详细说明
2. 检查命令行输出的错误信息
3. 确保所有依赖都已正确安装
## 🎉 完成
打包成功后,您就可以:
- ✅ 在任何 Windows 电脑上运行 MetaCore
- ✅ 分发给其他用户使用
- ✅ 无需安装任何依赖
祝您使用愉快!

30
tools/build.bat Normal file
View File

@ -0,0 +1,30 @@
@echo off
chcp 65001 >nul
echo 🚀 MetaCore 打包工具
echo ==================
REM 检查 Python 是否安装
python --version >nul 2>&1
if errorlevel 1 (
echo ❌ 错误: 未找到 Python请先安装 Python 3.7+
pause
exit /b 1
)
echo ✅ Python 环境检查通过
REM 安装打包依赖
echo 📦 安装打包依赖...
pip install pyinstaller
REM 安装项目依赖
echo 📦 安装项目依赖...
pip install -r MetaCore\requirements.txt
REM 运行打包脚本
echo 🔨 开始打包...
python build_config.py
echo.
echo 打包完成!按任意键退出...
pause >nul

243
tools/build_config.py Normal file
View File

@ -0,0 +1,243 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MetaCore 打包配置脚本
使用 PyInstaller MetaCore 打包为可执行文件
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
class MetaCoreBuildConfig:
"""MetaCore 打包配置类"""
def __init__(self):
self.project_root = Path(__file__).parent
self.metacore_dir = self.project_root / "MetaCore"
self.build_dir = self.project_root / "build"
self.dist_dir = self.project_root / "dist"
self.spec_file = self.project_root / "metacore.spec"
def check_dependencies(self):
"""检查打包依赖"""
print("🔍 检查打包依赖...")
# 检查 PyInstaller
try:
import PyInstaller
print(f"✅ PyInstaller 已安装: {PyInstaller.__version__}")
except ImportError:
print("❌ PyInstaller 未安装,正在安装...")
subprocess.run([sys.executable, "-m", "pip", "install", "pyinstaller"], check=True)
print("✅ PyInstaller 安装完成")
# 检查项目依赖
requirements_file = self.metacore_dir / "requirements.txt"
if requirements_file.exists():
print("📦 安装项目依赖...")
subprocess.run([sys.executable, "-m", "pip", "install", "-r", str(requirements_file)], check=True)
print("✅ 项目依赖安装完成")
def clean_build(self):
"""清理构建目录"""
print("🧹 清理构建目录...")
for dir_path in [self.build_dir, self.dist_dir]:
if dir_path.exists():
shutil.rmtree(dir_path)
print(f"✅ 已清理: {dir_path}")
if self.spec_file.exists():
self.spec_file.unlink()
print(f"✅ 已清理: {self.spec_file}")
def create_spec_file(self):
"""创建 PyInstaller 规格文件"""
print("📝 创建 PyInstaller 规格文件...")
spec_content = f'''# -*- mode: python ; coding: utf-8 -*-
import os
from pathlib import Path
# 项目路径
project_root = Path(r"{self.project_root}")
metacore_dir = project_root / "MetaCore"
a = Analysis(
[str(metacore_dir / "main.py")],
pathex=[str(metacore_dir)],
binaries=[],
datas=[
# 包含数据文件
(str(metacore_dir / "data" / "*.json"), "data"),
# 包含资源文件
(str(metacore_dir / "Resources"), "Resources"),
# 包含UI文件
(str(metacore_dir / "ui"), "ui"),
],
hiddenimports=[
'PyQt5.QtCore',
'PyQt5.QtGui',
'PyQt5.QtWidgets',
'PyQt5.sip',
],
hookspath=[],
hooksconfig={{}},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='MetaCore',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # 不显示控制台窗口
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None, # 可以添加图标文件路径
version_file=None,
)
'''
with open(self.spec_file, 'w', encoding='utf-8') as f:
f.write(spec_content)
print(f"✅ 规格文件已创建: {self.spec_file}")
def build_executable(self):
"""构建可执行文件"""
print("🔨 开始构建可执行文件...")
# 切换到项目根目录
original_cwd = os.getcwd()
os.chdir(self.project_root)
try:
# 运行 PyInstaller
cmd = [
sys.executable, "-m", "PyInstaller",
"--clean",
"--noconfirm",
str(self.spec_file)
]
print(f"执行命令: {' '.join(cmd)}")
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print("✅ 构建完成!")
print(f"📁 可执行文件位置: {self.dist_dir / 'MetaCore.exe'}")
except subprocess.CalledProcessError as e:
print(f"❌ 构建失败: {e}")
print(f"错误输出: {e.stderr}")
raise
finally:
os.chdir(original_cwd)
def create_installer_script(self):
"""创建安装脚本"""
print("📦 创建安装脚本...")
installer_script = self.project_root / "create_installer.bat"
script_content = f'''@echo off
echo 正在创建 MetaCore 安装包...
REM 检查是否存在可执行文件
if not exist "dist\\MetaCore.exe" (
echo 错误: 找不到 MetaCore.exe请先运行构建脚本
pause
exit /b 1
)
REM 创建安装包目录
if exist "MetaCore_Installer" rmdir /s /q "MetaCore_Installer"
mkdir "MetaCore_Installer"
REM 复制文件
copy "dist\\MetaCore.exe" "MetaCore_Installer\\"
copy "MetaCore\\README.md" "MetaCore_Installer\\"
REM 创建启动脚本
echo @echo off > "MetaCore_Installer\\启动MetaCore.bat"
echo cd /d "%%~dp0" >> "MetaCore_Installer\\启动MetaCore.bat"
echo start MetaCore.exe >> "MetaCore_Installer\\启动MetaCore.bat"
REM 创建卸载脚本
echo @echo off > "MetaCore_Installer\\卸载.bat"
echo echo 确定要卸载 MetaCore >> "MetaCore_Installer\\卸载.bat"
echo pause >> "MetaCore_Installer\\卸载.bat"
echo del MetaCore.exe >> "MetaCore_Installer\\卸载.bat"
echo del 启动MetaCore.bat >> "MetaCore_Installer\\卸载.bat"
echo del README.md >> "MetaCore_Installer\\卸载.bat"
echo del 卸载.bat >> "MetaCore_Installer\\卸载.bat"
echo 安装包创建完成
echo 📁 安装包位置: MetaCore_Installer
pause
'''
with open(installer_script, 'w', encoding='gbk') as f:
f.write(script_content)
print(f"✅ 安装脚本已创建: {installer_script}")
def build_all(self):
"""执行完整的构建流程"""
print("🚀 开始 MetaCore 打包流程...")
print("=" * 50)
try:
self.check_dependencies()
self.clean_build()
self.create_spec_file()
self.build_executable()
self.create_installer_script()
print("=" * 50)
print("🎉 MetaCore 打包完成!")
print(f"📁 可执行文件: {self.dist_dir / 'MetaCore.exe'}")
print(f"📦 安装脚本: {self.project_root / 'create_installer.bat'}")
print("\n使用说明:")
print("1. 运行 dist/MetaCore.exe 直接启动应用")
print("2. 运行 create_installer.bat 创建安装包")
except Exception as e:
print(f"❌ 打包失败: {e}")
return False
return True
def main():
"""主函数"""
builder = MetaCoreBuildConfig()
success = builder.build_all()
if not success:
sys.exit(1)
if __name__ == "__main__":
main()

98
tools/打包指南.md Normal file
View File

@ -0,0 +1,98 @@
# MetaCore 打包指南
## 环境要求
1. **Python 3.7+** 已安装并添加到系统 PATH
2. **PyQt5** 已安装
3. **PyInstaller** 已安装
## 快速打包步骤
### 方法一:使用批处理文件(推荐)
1. 双击运行 `build.bat`
2. 等待打包完成
3. 在 `dist` 文件夹中找到 `MetaCore.exe`
### 方法二:手动命令行打包
1. 打开命令提示符cmd
2. 切换到项目目录:
```cmd
cd C:\Users\29381\Desktop\MetaCore
```
3. 安装依赖:
```cmd
pip install pyinstaller
pip install PyQt5
```
4. 执行打包命令:
```cmd
pyinstaller --onefile --windowed --name=MetaCore --add-data="MetaCore/data;data" --add-data="MetaCore/ui;ui" --hidden-import=PyQt5.sip MetaCore/main.py
```
### 方法三:使用 Python 脚本
1. 运行打包脚本:
```cmd
python build_simple.py
```
## 打包参数说明
- `--onefile`: 打包成单个可执行文件
- `--windowed`: 不显示控制台窗口
- `--name=MetaCore`: 设置可执行文件名称
- `--add-data`: 添加数据文件到打包中
- `--hidden-import`: 添加隐式导入的模块
## 常见问题解决
### 1. Python 未找到
- 确保 Python 已安装并添加到系统 PATH
- 尝试使用 `py` 命令替代 `python`
### 2. PyInstaller 未安装
```cmd
pip install pyinstaller
```
### 3. PyQt5 未安装
```cmd
pip install PyQt5
```
### 4. 打包失败
- 检查所有依赖是否已安装
- 确保项目文件完整
- 查看错误信息并根据提示解决
## 输出文件
打包成功后,会在以下位置生成文件:
- `dist/MetaCore.exe` - 可执行文件
- `build/` - 临时构建文件(可删除)
- `MetaCore.spec` - PyInstaller 配置文件
## 分发说明
1. 将 `MetaCore.exe` 复制到目标计算机
2. 确保目标计算机有 Windows 10+ 系统
3. 不需要安装 Python 或其他依赖
4. 双击 `MetaCore.exe` 即可运行
## 文件大小优化
如果生成的文件过大,可以尝试:
1. 使用 `--exclude-module` 排除不需要的模块
2. 使用 UPX 压缩(添加 `--upx-dir` 参数)
3. 移除调试信息(添加 `--strip` 参数)
## 高级配置
如需更复杂的打包配置,可以编辑 `MetaCore.spec` 文件,然后运行:
```cmd
pyinstaller MetaCore.spec
```

321
web/index.html Normal file
View File

@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MetaCore - 项目管理平台</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- 侧边栏 -->
<div class="sidebar">
<div class="logo">
<i class="fas fa-cube"></i>
<span>MetaCore</span>
</div>
<nav class="nav-menu">
<!-- 快速操作区 -->
<div class="quick-actions">
<button class="quick-btn create-btn" id="sidebarCreateBtn">
<i class="fas fa-plus"></i>
<span>创建新项目</span>
</button>
<button class="quick-btn import-btn" id="sidebarImportBtn">
<i class="fas fa-upload"></i>
<span>导入项目</span>
</button>
</div>
<div class="nav-section">
<div class="nav-title expandable" data-section="my-projects">
<i class="fas fa-chevron-down expand-icon"></i>
<span>我的项目</span>
</div>
<div class="nav-subsection" id="my-projects">
<div class="nav-item active" data-page="all-projects">
<i class="fas fa-folder"></i>
<span>项目概览</span>
</div>
<div class="nav-item" data-page="favorite-projects">
<i class="fas fa-star"></i>
<span>收藏项目</span>
</div>
<div class="nav-item" data-page="recent-projects">
<i class="fas fa-clock"></i>
<span>最近项目</span>
</div>
<div class="nav-item" data-page="industrial-projects">
<i class="fas fa-industry"></i>
<span>工业项目</span>
</div>
<div class="nav-item" data-page="smart-projects">
<i class="fas fa-microchip"></i>
<span>智能项目</span>
</div>
<div class="nav-item" data-page="vr-projects">
<i class="fas fa-vr-cardboard"></i>
<span>VR项目</span>
</div>
</div>
</div>
<div class="nav-section">
<div class="nav-title expandable" data-section="content-management">
<i class="fas fa-chevron-down expand-icon"></i>
<span>内容管理</span>
</div>
<div class="nav-subsection" id="content-management">
<div class="nav-item" data-page="resource-category">
<i class="fas fa-layer-group"></i>
<span>资源分类</span>
</div>
<div class="nav-item" data-page="category-management">
<i class="fas fa-tags"></i>
<span>分类管理</span>
</div>
<div class="nav-item" data-page="asset-library">
<i class="fas fa-images"></i>
<span>资源库</span>
</div>
<div class="nav-item" data-page="template-library">
<i class="fas fa-puzzle-piece"></i>
<span>模板库</span>
</div>
</div>
</div>
<div class="nav-section">
<div class="nav-title expandable" data-section="settings">
<i class="fas fa-chevron-down expand-icon"></i>
<span>设置中心</span>
</div>
<div class="nav-subsection" id="settings">
<div class="nav-item">
<i class="fas fa-cog"></i>
<span>账户设置</span>
</div>
<div class="nav-item">
<i class="fas fa-users"></i>
<span>系统管理</span>
</div>
<div class="nav-item">
<i class="fas fa-shield-alt"></i>
<span>权限管理</span>
</div>
<div class="nav-item">
<i class="fas fa-database"></i>
<span>数据备份</span>
</div>
</div>
</div>
</nav>
<div class="user-info">
<div class="user-avatar">
<i class="fas fa-user"></i>
</div>
<span>Admin</span>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 顶部导航 -->
<div class="top-nav">
<div class="breadcrumb" id="breadcrumb">
<span>我的项目</span>
<span>/</span>
<span>项目概览</span>
</div>
<div class="top-actions">
<button class="btn-icon">
<i class="fas fa-bell"></i>
</button>
<button class="btn-icon">
<i class="fas fa-user-circle"></i>
</button>
<button class="btn-primary" id="createProjectBtn">
<i class="fas fa-plus"></i>
创建
</button>
</div>
</div>
<!-- 项目列表 -->
<div class="content-area">
<div class="content-header">
<h1>全部项目</h1>
<div class="view-controls">
<button class="view-btn active" data-view="grid">
<i class="fas fa-th"></i>
</button>
<button class="view-btn" data-view="list">
<i class="fas fa-list"></i>
</button>
<button class="view-btn" id="filterBtn">
<i class="fas fa-filter"></i>
</button>
<button class="view-btn" id="searchBtn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<!-- 搜索和过滤区域 -->
<div class="search-filter-area" id="searchFilterArea" style="display: none;">
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索项目名称...">
<i class="fas fa-search"></i>
</div>
<div class="filter-options">
<select id="sortSelect">
<option value="date-desc">按日期降序</option>
<option value="date-asc">按日期升序</option>
<option value="name-asc">按名称升序</option>
<option value="name-desc">按名称降序</option>
</select>
<select id="typeFilter">
<option value="all">所有类型</option>
<option value="industrial">工业项目</option>
<option value="smart">智能项目</option>
<option value="vr">VR项目</option>
<option value="game">游戏项目</option>
<option value="design">设计项目</option>
</select>
</div>
</div>
<div class="projects-grid" id="projectsGrid">
<!-- 项目卡片将通过 JavaScript 动态生成 -->
</div>
</div>
</div>
<!-- 创建项目模态框 -->
<div class="modal" id="createProjectModal">
<div class="modal-content create-project-modal">
<div class="modal-header">
<h2>创建新的项目</h2>
<button class="modal-close" id="closeCreateModal">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body create-project-body">
<!-- 左侧:项目模板 -->
<div class="templates-section">
<h3>选择项目模板</h3>
<div class="project-templates">
<div class="template-item" data-template="empty">
<div class="template-icon">
<i class="fas fa-file"></i>
</div>
<div class="template-name">空白项目模板</div>
</div>
<div class="template-item" data-template="industrial">
<div class="template-icon">
<i class="fas fa-industry"></i>
</div>
<div class="template-name">工业项目</div>
</div>
<div class="template-item" data-template="vr">
<div class="template-icon">
<i class="fas fa-vr-cardboard"></i>
</div>
<div class="template-name">VR虚拟现实</div>
</div>
<div class="template-item" data-template="smart">
<div class="template-icon">
<i class="fas fa-microchip"></i>
</div>
<div class="template-name">智能-可视化大屏</div>
</div>
<div class="template-item" data-template="game">
<div class="template-icon">
<i class="fas fa-gamepad"></i>
</div>
<div class="template-name">游戏开发</div>
</div>
</div>
</div>
<!-- 右侧:项目信息和操作 -->
<div class="project-info-section">
<div class="project-info">
<h3>项目信息</h3>
<div class="template-description" id="templateDescription">
<p>选择一个项目模板来快速开始,或者创建一个空白项目来从头开始。每个模板都包含了相应的基础配置和示例文件。</p>
</div>
<div class="form-group">
<label>项目名称</label>
<input type="text" id="projectName" placeholder="输入项目名称">
</div>
<div class="form-group">
<label>项目描述</label>
<textarea id="projectDescription" placeholder="输入项目描述(可选)" rows="3"></textarea>
</div>
<div class="form-group">
<label>项目位置</label>
<div class="location-input-group">
<input type="text" id="projectLocation" placeholder="点击浏览选择位置" readonly>
<button type="button" class="btn-browse" id="browseLocation" title="选择项目保存位置">
<i class="fas fa-folder-open"></i>
</button>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="project-actions">
<button class="btn-secondary" id="cancelCreate">取消</button>
<button class="btn-primary" id="confirmCreate">创建项目</button>
</div>
</div>
</div>
</div>
</div>
<!-- 导入项目模态框 -->
<div class="modal" id="importProjectModal">
<div class="modal-content">
<div class="modal-header">
<h2>导入项目文件</h2>
<button class="modal-close" id="closeImportModal">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body">
<div class="import-area">
<div class="upload-zone" id="uploadZone">
<i class="fas fa-cloud-upload-alt"></i>
<p>拖拽文件到这里,或者</p>
<button class="btn-upload">点击选择</button>
<input type="file" id="fileInput" accept=".zip,.rar,.7z" style="display: none;">
</div>
<div class="import-options">
<label>
<input type="checkbox" id="importAsNewProject">
导入为新项目
</label>
<label>
<input type="checkbox" id="overwriteExisting">
覆盖现有项目配置
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="cancelImport">取消</button>
<button class="btn-primary" id="confirmImport">导入</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

977
web/script.js Normal file
View File

@ -0,0 +1,977 @@
// 项目数据
const projectsData = [
{
id: 1,
title: "智慧工厂",
date: "2024-06-08 15:56:35",
type: "industrial",
image: "🏭",
favorite: true
},
{
id: 2,
title: "智慧水务",
date: "2023-01-10 12:09:04",
type: "smart",
image: "💧",
favorite: false
},
{
id: 3,
title: "数字工厂",
date: "2024-06-07 06:57:46",
type: "industrial",
image: "🏗️",
favorite: false
},
{
id: 4,
title: "智慧监控",
date: "2024-07-28 17:38:02",
type: "smart",
image: "📊",
favorite: true
},
{
id: 5,
title: "工业设计平台",
date: "2025-02-28 18:00:05",
type: "design",
image: "🎨",
favorite: false
}
];
// DOM 元素
const createProjectBtn = document.getElementById('createProjectBtn');
const createProjectModal = document.getElementById('createProjectModal');
const importProjectModal = document.getElementById('importProjectModal');
const closeCreateModal = document.getElementById('closeCreateModal');
const closeImportModal = document.getElementById('closeImportModal');
const cancelCreate = document.getElementById('cancelCreate');
const cancelImport = document.getElementById('cancelImport');
const confirmCreate = document.getElementById('confirmCreate');
const confirmImport = document.getElementById('confirmImport');
const projectsGrid = document.getElementById('projectsGrid');
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const projectNameInput = document.getElementById('projectName');
// 搜索和过滤相关元素
let searchBtn, filterBtn, searchFilterArea, searchInput, sortSelect, typeFilter;
// 过滤和排序状态
let filteredProjects = [...projectsData];
let currentSearchTerm = '';
let currentSortBy = 'date-desc';
let currentTypeFilter = 'all';
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initializeSearchElements();
renderProjects();
initEventListeners();
});
// 初始化搜索相关元素
function initializeSearchElements() {
searchBtn = document.getElementById('searchBtn');
filterBtn = document.getElementById('filterBtn');
searchFilterArea = document.getElementById('searchFilterArea');
searchInput = document.getElementById('searchInput');
sortSelect = document.getElementById('sortSelect');
typeFilter = document.getElementById('typeFilter');
}
// 渲染项目列表
function renderProjects() {
projectsGrid.innerHTML = '';
// 应用过滤和排序
applyFiltersAndSort();
if (filteredProjects.length === 0) {
projectsGrid.innerHTML = `
<div class="no-projects">
<i class="fas fa-folder-open"></i>
<p>没有找到匹配的项目</p>
</div>
`;
return;
}
filteredProjects.forEach(project => {
const projectCard = document.createElement('div');
projectCard.className = 'project-card';
projectCard.innerHTML = `
<div class="project-header">
<div class="project-title">${project.title}</div>
<div class="project-header-actions">
${project.favorite ? '<i class="fas fa-star favorite-icon" title="已收藏"></i>' : ''}
<div class="project-menu" data-project-id="${project.id}">
<i class="fas fa-ellipsis-v"></i>
</div>
</div>
</div>
<div class="project-image">
${project.image}
<div class="project-type-badge">${getProjectTypeLabel(project.type)}</div>
</div>
<div class="project-footer">
<div class="project-date">${project.date}</div>
</div>
`;
// 项目卡片点击事件(排除菜单按钮)
projectCard.addEventListener('click', (e) => {
if (!e.target.closest('.project-menu')) {
openProject(project.id);
}
});
// 菜单按钮点击事件
const menuBtn = projectCard.querySelector('.project-menu');
menuBtn.addEventListener('click', (e) => {
e.stopPropagation();
showProjectMenu(project.id, menuBtn);
});
projectsGrid.appendChild(projectCard);
});
}
// 应用过滤和排序
function applyFiltersAndSort() {
// 首先应用搜索过滤
filteredProjects = projectsData.filter(project => {
const matchesSearch = currentSearchTerm === '' ||
project.title.toLowerCase().includes(currentSearchTerm.toLowerCase());
const matchesType = currentTypeFilter === 'all' ||
project.type === currentTypeFilter;
return matchesSearch && matchesType;
});
// 然后应用排序
filteredProjects.sort((a, b) => {
switch (currentSortBy) {
case 'date-desc':
return new Date(b.date) - new Date(a.date);
case 'date-asc':
return new Date(a.date) - new Date(b.date);
case 'name-asc':
return a.title.localeCompare(b.title);
case 'name-desc':
return b.title.localeCompare(a.title);
default:
return 0;
}
});
}
// 初始化事件监听器
function initEventListeners() {
// 创建项目按钮 - 顶部和侧边栏
createProjectBtn.addEventListener('click', () => {
showModal(createProjectModal);
});
const sidebarCreateBtn = document.getElementById('sidebarCreateBtn');
const sidebarImportBtn = document.getElementById('sidebarImportBtn');
if (sidebarCreateBtn) {
sidebarCreateBtn.addEventListener('click', () => {
showModal(createProjectModal);
});
}
if (sidebarImportBtn) {
sidebarImportBtn.addEventListener('click', () => {
showModal(importProjectModal);
});
}
// 关闭模态框
closeCreateModal.addEventListener('click', () => {
hideModal(createProjectModal);
});
closeImportModal.addEventListener('click', () => {
hideModal(importProjectModal);
});
// 取消按钮
cancelCreate.addEventListener('click', () => {
hideModal(createProjectModal);
});
cancelImport.addEventListener('click', () => {
hideModal(importProjectModal);
});
// 确认按钮
confirmCreate.addEventListener('click', () => {
createProject();
});
confirmImport.addEventListener('click', () => {
importProject();
});
// 树状结构展开/折叠
initTreeStructure();
// 搜索和过滤功能
initSearchAndFilter();
// 模板选择
const templateItems = document.querySelectorAll('.template-item');
templateItems.forEach(item => {
item.addEventListener('click', () => {
templateItems.forEach(t => t.classList.remove('selected'));
item.classList.add('selected');
updateTemplateDescription(item.dataset.template);
});
});
// 文件上传
uploadZone.addEventListener('click', () => {
fileInput.click();
});
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('dragover');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('dragover');
handleFileUpload(e.dataTransfer.files);
});
fileInput.addEventListener('change', (e) => {
handleFileUpload(e.target.files);
});
// 点击模态框外部关闭
createProjectModal.addEventListener('click', (e) => {
if (e.target === createProjectModal) {
hideModal(createProjectModal);
}
});
importProjectModal.addEventListener('click', (e) => {
if (e.target === importProjectModal) {
hideModal(importProjectModal);
}
});
// 导航菜单项点击
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', () => {
navItems.forEach(nav => nav.classList.remove('active'));
item.classList.add('active');
// 更新面包屑导航
updateBreadcrumb(item);
// 根据页面类型过滤项目
const pageType = item.dataset.page;
if (pageType) {
filterProjectsByPage(pageType);
}
});
});
// 浏览位置按钮
const browseLocationBtn = document.getElementById('browseLocation');
if (browseLocationBtn) {
browseLocationBtn.addEventListener('click', () => {
browseProjectLocation();
});
}
// 视图切换
const viewBtns = document.querySelectorAll('.view-btn[data-view]');
viewBtns.forEach(btn => {
btn.addEventListener('click', () => {
viewBtns.forEach(v => v.classList.remove('active'));
btn.classList.add('active');
const view = btn.dataset.view;
if (view === 'list') {
projectsGrid.style.gridTemplateColumns = '1fr';
} else {
projectsGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(280px, 1fr))';
}
});
});
// 添加导入项目的快捷方式
const importBtn = document.createElement('button');
importBtn.className = 'btn-secondary';
importBtn.innerHTML = '<i class="fas fa-upload"></i> 导入';
importBtn.style.marginLeft = '10px';
importBtn.addEventListener('click', () => {
showModal(importProjectModal);
});
const topActions = document.querySelector('.top-actions');
topActions.insertBefore(importBtn, createProjectBtn);
}
// 显示模态框
function showModal(modal) {
modal.classList.add('show');
document.body.style.overflow = 'hidden';
}
// 隐藏模态框
function hideModal(modal) {
modal.classList.remove('show');
document.body.style.overflow = 'auto';
// 重置表单
if (modal === createProjectModal) {
projectNameInput.value = '';
document.querySelectorAll('.template-item').forEach(item => {
item.classList.remove('selected');
});
}
}
// 创建项目
function createProject() {
const projectName = projectNameInput.value.trim();
const selectedTemplate = document.querySelector('.template-item.selected');
if (!projectName) {
alert('请输入项目名称');
return;
}
if (!selectedTemplate) {
alert('请选择项目模板');
return;
}
const templateType = selectedTemplate.dataset.template;
const templateIcons = {
empty: '📄',
industrial: '🏭',
vr: '🥽',
smart: '📊',
game: '🎮'
};
const newProject = {
id: projectsData.length + 1,
title: projectName,
date: new Date().toLocaleString('zh-CN'),
type: templateType,
image: templateIcons[templateType] || '📄'
};
projectsData.unshift(newProject);
renderProjects();
hideModal(createProjectModal);
// 显示成功消息
showNotification('项目创建成功!', 'success');
}
// 导入项目
function importProject() {
const files = fileInput.files;
if (files.length === 0) {
alert('请选择要导入的文件');
return;
}
// 模拟导入过程
showNotification('正在导入项目...', 'info');
setTimeout(() => {
const newProject = {
id: projectsData.length + 1,
title: `导入的项目_${Date.now()}`,
date: new Date().toLocaleString('zh-CN'),
type: 'imported',
image: '📦'
};
projectsData.unshift(newProject);
renderProjects();
hideModal(importProjectModal);
showNotification('项目导入成功!', 'success');
}, 2000);
}
// 处理文件上传
function handleFileUpload(files) {
if (files.length > 0) {
const file = files[0];
const allowedTypes = ['.zip', '.rar', '.7z'];
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
if (allowedTypes.includes(fileExtension)) {
fileInput.files = files;
uploadZone.innerHTML = `
<i class="fas fa-file-archive"></i>
<p>已选择文件: ${file.name}</p>
<button class="btn-upload">重新选择</button>
`;
} else {
alert('请选择支持的文件格式 (.zip, .rar, .7z)');
}
}
}
// 打开项目
function openProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (project) {
showNotification(`正在打开项目: ${project.title}`, 'info');
// 这里可以添加实际的项目打开逻辑
}
}
// 显示通知
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'};
color: white;
border-radius: 8px;
z-index: 3000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
if (document.body.contains(notification)) {
document.body.removeChild(notification);
}
}, 300);
}, 3000);
}
// 打开项目
function openProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (project) {
showNotification(`正在打开项目: ${project.title}`, 'info');
// 这里可以添加实际的项目打开逻辑
}
}
// 显示通知
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'};
color: white;
border-radius: 8px;
z-index: 3000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
if (document.body.contains(notification)) {
document.body.removeChild(notification);
}
}, 300);
}, 3000);
}
// 更新模板描述
function updateTemplateDescription(templateType) {
const templateDescription = document.getElementById('templateDescription');
if (!templateDescription) return;
const descriptions = {
empty: {
title: '空白项目模板',
description: '从零开始创建项目,适合有经验的开发者。提供基础的项目结构和配置文件,您可以根据需要自由定制。'
},
industrial: {
title: '工业项目模板',
description: '专为工业应用设计的项目模板。包含工业设备监控、数据采集、生产流程管理等常用功能模块和界面组件。'
},
vr: {
title: 'VR虚拟现实模板',
description: '虚拟现实项目模板集成VR交互组件和3D场景管理。适合创建沉浸式体验、虚拟展示和培训应用。'
},
smart: {
title: '智能可视化大屏模板',
description: '智能数据可视化大屏模板,包含丰富的图表组件、实时数据展示和响应式布局。适合创建监控中心和数据分析平台。'
},
game: {
title: '游戏开发模板',
description: '游戏开发项目模板提供游戏引擎集成、资源管理、场景编辑等功能。适合2D/3D游戏和交互应用开发。'
}
};
const template = descriptions[templateType];
if (template) {
templateDescription.innerHTML = `
<h4>${template.title}</h4>
<p>${template.description}</p>
`;
}
}
// 更新面包屑导航
function updateBreadcrumb(navItem) {
const breadcrumb = document.getElementById('breadcrumb');
if (!breadcrumb) return;
const pageNames = {
'all-projects': { section: '我的项目', page: '项目概览' },
'favorite-projects': { section: '我的项目', page: '收藏项目' },
'recent-projects': { section: '我的项目', page: '最近项目' },
'industrial-projects': { section: '我的项目', page: '工业项目' },
'smart-projects': { section: '我的项目', page: '智能项目' },
'vr-projects': { section: '我的项目', page: 'VR项目' },
'resource-category': { section: '内容管理', page: '资源分类' },
'category-management': { section: '内容管理', page: '分类管理' },
'asset-library': { section: '内容管理', page: '资源库' },
'template-library': { section: '内容管理', page: '模板库' }
};
const pageType = navItem.dataset.page;
const pageInfo = pageNames[pageType];
if (pageInfo) {
breadcrumb.innerHTML = `
<span>${pageInfo.section}</span>
<span>/</span>
<span>${pageInfo.page}</span>
`;
}
}
// 根据页面类型过滤项目
function filterProjectsByPage(pageType) {
const contentHeader = document.querySelector('.content-header h1');
switch (pageType) {
case 'all-projects':
currentTypeFilter = 'all';
if (contentHeader) contentHeader.textContent = '全部项目';
break;
case 'favorite-projects':
currentTypeFilter = 'all';
if (contentHeader) contentHeader.textContent = '收藏项目';
// 这里可以添加收藏项目的过滤逻辑
break;
case 'recent-projects':
currentTypeFilter = 'all';
if (contentHeader) contentHeader.textContent = '最近项目';
// 按日期排序显示最近的项目
currentSortBy = 'date-desc';
break;
case 'industrial-projects':
currentTypeFilter = 'industrial';
if (contentHeader) contentHeader.textContent = '工业项目';
break;
case 'smart-projects':
currentTypeFilter = 'smart';
if (contentHeader) contentHeader.textContent = '智能项目';
break;
case 'vr-projects':
currentTypeFilter = 'vr';
if (contentHeader) contentHeader.textContent = 'VR项目';
break;
default:
currentTypeFilter = 'all';
if (contentHeader) contentHeader.textContent = '全部项目';
}
// 更新过滤器选择框
if (typeFilter) {
typeFilter.value = currentTypeFilter;
}
// 重新渲染项目
renderProjects();
}
// 浏览项目位置
function browseProjectLocation() {
const projectLocationInput = document.getElementById('projectLocation');
if (!projectLocationInput) return;
// 尝试调用系统文件选择器
if (window.showDirectoryPicker) {
// 现代浏览器支持的文件系统访问API
showSystemDirectoryPicker(projectLocationInput);
} else if (window.electronAPI) {
// Electron环境
showElectronDirectoryDialog(projectLocationInput);
} else {
// 降级方案:显示自定义文件夹选择对话框
showFallbackFolderDialog(projectLocationInput);
}
}
// 现代浏览器的目录选择器
async function showSystemDirectoryPicker(input) {
try {
const directoryHandle = await window.showDirectoryPicker();
const path = directoryHandle.name;
input.value = path;
showNotification('已选择项目位置: ' + path, 'success');
} catch (err) {
if (err.name !== 'AbortError') {
console.error('目录选择失败:', err);
showFallbackFolderDialog(input);
}
}
}
// Electron环境的目录选择
function showElectronDirectoryDialog(input) {
if (window.electronAPI && window.electronAPI.showOpenDialog) {
window.electronAPI.showOpenDialog({
properties: ['openDirectory'],
title: '选择项目保存位置'
}).then(result => {
if (!result.canceled && result.filePaths.length > 0) {
const selectedPath = result.filePaths[0];
input.value = selectedPath;
showNotification('已选择项目位置: ' + selectedPath, 'success');
}
}).catch(err => {
console.error('目录选择失败:', err);
showFallbackFolderDialog(input);
});
} else {
showFallbackFolderDialog(input);
}
}
// 降级方案:简化的文件夹选择
function showFallbackFolderDialog(input) {
// 创建隐藏的文件输入元素来模拟文件夹选择
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.webkitdirectory = true;
fileInput.style.display = 'none';
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
// 获取第一个文件的路径,然后提取目录路径
const file = e.target.files[0];
const path = file.webkitRelativePath.split('/')[0];
input.value = path || '已选择文件夹';
showNotification('已选择项目位置', 'success');
}
document.body.removeChild(fileInput);
});
document.body.appendChild(fileInput);
fileInput.click();
}
// 显示项目菜单
function showProjectMenu(projectId, menuButton) {
// 移除已存在的菜单
const existingMenu = document.querySelector('.project-context-menu');
if (existingMenu) {
existingMenu.remove();
}
// 获取项目信息
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
// 创建菜单
const menu = document.createElement('div');
menu.className = 'project-context-menu';
menu.innerHTML = `
<div class="menu-item" data-action="open">
<i class="fas fa-folder-open"></i>
<span>打开项目</span>
</div>
<div class="menu-item" data-action="rename">
<i class="fas fa-edit"></i>
<span>重命名</span>
</div>
<div class="menu-item" data-action="duplicate">
<i class="fas fa-copy"></i>
<span>复制项目</span>
</div>
<div class="menu-item" data-action="export">
<i class="fas fa-download"></i>
<span>导出项目</span>
</div>
<div class="menu-divider"></div>
<div class="menu-item" data-action="favorite">
<i class="fas fa-star${project.favorite ? ' favorited' : ''}"></i>
<span>${project.favorite ? '从收藏中移除' : '添加到收藏'}</span>
</div>
<div class="menu-divider"></div>
<div class="menu-item danger" data-action="delete">
<i class="fas fa-trash"></i>
<span>删除项目</span>
</div>
`;
// 定位菜单
const rect = menuButton.getBoundingClientRect();
menu.style.position = 'fixed';
menu.style.top = rect.bottom + 5 + 'px';
menu.style.left = rect.left - 150 + 'px'; // 菜单宽度约150px向左偏移
menu.style.zIndex = '2000';
// 添加到页面
document.body.appendChild(menu);
// 菜单项点击事件
menu.addEventListener('click', (e) => {
const action = e.target.closest('.menu-item')?.dataset.action;
if (action) {
handleProjectAction(projectId, action);
menu.remove();
}
});
// 点击其他地方关闭菜单
setTimeout(() => {
document.addEventListener('click', function closeMenu(e) {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', closeMenu);
}
});
}, 0);
}
// 处理项目操作
function handleProjectAction(projectId, action) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
switch (action) {
case 'open':
openProject(projectId);
break;
case 'rename':
renameProject(projectId);
break;
case 'duplicate':
duplicateProject(projectId);
break;
case 'export':
exportProject(projectId);
break;
case 'favorite':
toggleFavorite(projectId);
break;
case 'delete':
deleteProject(projectId);
break;
}
}
// 重命名项目
function renameProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
const newName = prompt('请输入新的项目名称:', project.title);
if (newName && newName.trim() !== '') {
project.title = newName.trim();
renderProjects();
showNotification('项目重命名成功', 'success');
}
}
// 复制项目
function duplicateProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
const newProject = {
...project,
id: projectsData.length + 1,
title: project.title + ' - 副本',
date: new Date().toLocaleString('zh-CN')
};
projectsData.unshift(newProject);
renderProjects();
showNotification('项目复制成功', 'success');
}
// 导出项目
function exportProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
showNotification(`正在导出项目: ${project.title}`, 'info');
// 这里可以添加实际的导出逻辑
}
// 切换收藏状态
function toggleFavorite(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
project.favorite = !project.favorite;
renderProjects(); // 重新渲染以显示收藏状态变化
showNotification(project.favorite ? '已添加到收藏' : '已从收藏中移除', 'success');
}
// 删除项目
function deleteProject(projectId) {
const project = projectsData.find(p => p.id === projectId);
if (!project) return;
if (confirm(`确定要删除项目 "${project.title}" 吗?此操作不可撤销。`)) {
const index = projectsData.findIndex(p => p.id === projectId);
if (index > -1) {
projectsData.splice(index, 1);
renderProjects();
showNotification('项目删除成功', 'success');
}
}
}
// 获取项目类型标签
function getProjectTypeLabel(type) {
const typeLabels = {
'industrial': '工业',
'smart': '智能',
'vr': 'VR',
'game': '游戏',
'design': '设计'
};
return typeLabels[type] || '其他';
}
// 初始化搜索和过滤功能
function initSearchAndFilter() {
if (!searchBtn || !filterBtn) return;
// 搜索按钮点击
searchBtn.addEventListener('click', () => {
toggleSearchFilterArea();
});
// 过滤按钮点击
filterBtn.addEventListener('click', () => {
toggleSearchFilterArea();
});
// 搜索输入
if (searchInput) {
searchInput.addEventListener('input', (e) => {
currentSearchTerm = e.target.value;
renderProjects();
});
}
// 排序选择
if (sortSelect) {
sortSelect.addEventListener('change', (e) => {
currentSortBy = e.target.value;
renderProjects();
});
}
// 类型过滤
if (typeFilter) {
typeFilter.addEventListener('change', (e) => {
currentTypeFilter = e.target.value;
renderProjects();
});
}
}
// 切换搜索过滤区域显示
function toggleSearchFilterArea() {
if (!searchFilterArea) return;
if (searchFilterArea.style.display === 'none' || searchFilterArea.style.display === '') {
searchFilterArea.style.display = 'flex';
if (searchInput) {
searchInput.focus();
}
} else {
searchFilterArea.style.display = 'none';
}
}
// 初始化树状结构
function initTreeStructure() {
// 主分区展开/折叠
const navTitles = document.querySelectorAll('.nav-title.expandable');
navTitles.forEach(title => {
title.addEventListener('click', () => {
const sectionId = title.dataset.section;
const subsection = document.getElementById(sectionId);
const icon = title.querySelector('.expand-icon');
if (subsection.classList.contains('collapsed')) {
subsection.classList.remove('collapsed');
subsection.style.maxHeight = subsection.scrollHeight + 'px';
icon.style.transform = 'rotate(0deg)';
} else {
subsection.classList.add('collapsed');
subsection.style.maxHeight = '0px';
icon.style.transform = 'rotate(-90deg)';
}
});
});
// 设置初始状态 - 默认展开所有主分区
const allSubsections = document.querySelectorAll('.nav-subsection');
allSubsections.forEach(subsection => {
subsection.style.maxHeight = subsection.scrollHeight + 'px';
});
}
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);

1014
web/styles.css Normal file

File diff suppressed because it is too large Load Diff

339
web/test-cards.html Normal file
View File

@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>项目卡片测试</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body {
background: #1a1a1a;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
max-width: 1200px;
margin: 0 auto;
}
.project-card {
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
}
.project-card:hover {
border-color: #8b5cf6;
transform: translateY(-2px);
}
/* 项目头部 - 名称和菜单 */
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #3a3a3a;
background: #2a2a2a;
}
.project-title {
font-size: 16px;
font-weight: 600;
color: #fff;
line-height: 1.4;
flex: 1;
margin-right: 8px;
margin-bottom: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-menu {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
color: #888;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.project-menu:hover {
background: #3a3a3a;
color: #fff;
}
.project-menu i {
font-size: 14px;
}
.project-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.favorite-icon {
color: #ffd700;
font-size: 14px;
}
.project-type-badge {
position: absolute;
top: 8px;
right: 8px;
background: rgba(139, 92, 246, 0.9);
color: #fff;
padding: 4px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
backdrop-filter: blur(4px);
}
/* 项目图片 */
.project-image {
width: 100%;
height: 160px;
background: #3a3a3a;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #666;
flex: 1;
}
/* 项目底部 - 时间 */
.project-footer {
padding: 12px 16px;
border-top: 1px solid #3a3a3a;
background: #2a2a2a;
}
.project-date {
font-size: 12px;
color: #888;
text-align: center;
margin: 0;
}
/* 项目右键菜单 */
.project-context-menu {
position: fixed;
background: #2a2a2a;
border: 1px solid #3a3a3a;
border-radius: 8px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
min-width: 180px;
padding: 8px 0;
z-index: 2000;
}
.menu-item {
display: flex;
align-items: center;
padding: 10px 16px;
color: #fff;
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 14px;
}
.menu-item:hover {
background: #3a3a3a;
}
.menu-item.danger {
color: #ff6b6b;
}
.menu-item.danger:hover {
background: rgba(255, 107, 107, 0.1);
}
.menu-item i {
width: 16px;
margin-right: 12px;
font-size: 14px;
}
.menu-divider {
height: 1px;
background: #3a3a3a;
margin: 8px 0;
}
</style>
</head>
<body>
<h1>项目卡片布局测试</h1>
<div class="projects-grid" id="projectsGrid">
<!-- 项目卡片将在这里动态生成 -->
</div>
<script>
// 测试数据
const testProjects = [
{
id: 1,
title: "智慧工厂",
date: "2024-06-08 15:56:35",
type: "industrial",
image: "🏭",
favorite: true
},
{
id: 2,
title: "智慧水务",
date: "2023-01-10 12:09:04",
type: "smart",
image: "💧",
favorite: false
},
{
id: 3,
title: "数字工厂",
date: "2024-06-07 06:57:46",
type: "industrial",
image: "🏗️",
favorite: false
}
];
// 渲染项目卡片
function renderTestCards() {
const grid = document.getElementById('projectsGrid');
grid.innerHTML = '';
testProjects.forEach(project => {
const card = document.createElement('div');
card.className = 'project-card';
card.innerHTML = `
<div class="project-header">
<div class="project-title">${project.title}</div>
<div class="project-header-actions">
${project.favorite ? '<i class="fas fa-star favorite-icon" title="已收藏"></i>' : ''}
<div class="project-menu" data-project-id="${project.id}">
<i class="fas fa-ellipsis-v"></i>
</div>
</div>
</div>
<div class="project-image">
${project.image}
<div class="project-type-badge">${getProjectTypeLabel(project.type)}</div>
</div>
<div class="project-footer">
<div class="project-date">${project.date}</div>
</div>
`;
// 菜单点击事件
const menuBtn = card.querySelector('.project-menu');
menuBtn.addEventListener('click', (e) => {
e.stopPropagation();
showTestMenu(project.id, e.target);
});
// 卡片点击事件
card.addEventListener('click', (e) => {
if (!e.target.closest('.project-menu')) {
alert(`打开项目: ${project.title}`);
}
});
grid.appendChild(card);
});
}
// 显示测试菜单
function showTestMenu(projectId, menuButton) {
// 移除已存在的菜单
const existingMenu = document.querySelector('.project-context-menu');
if (existingMenu) {
existingMenu.remove();
}
// 创建菜单
const menu = document.createElement('div');
menu.className = 'project-context-menu';
menu.innerHTML = `
<div class="menu-item" data-action="open">
<i class="fas fa-folder-open"></i>
<span>打开项目</span>
</div>
<div class="menu-item" data-action="rename">
<i class="fas fa-edit"></i>
<span>重命名</span>
</div>
<div class="menu-item" data-action="duplicate">
<i class="fas fa-copy"></i>
<span>复制项目</span>
</div>
<div class="menu-divider"></div>
<div class="menu-item danger" data-action="delete">
<i class="fas fa-trash"></i>
<span>删除项目</span>
</div>
`;
// 定位菜单
const rect = menuButton.getBoundingClientRect();
menu.style.position = 'fixed';
menu.style.top = rect.bottom + 5 + 'px';
menu.style.left = rect.left - 150 + 'px';
document.body.appendChild(menu);
// 菜单项点击事件
menu.addEventListener('click', (e) => {
const action = e.target.closest('.menu-item')?.dataset.action;
if (action) {
alert(`执行操作: ${action} (项目ID: ${projectId})`);
menu.remove();
}
});
// 点击其他地方关闭菜单
setTimeout(() => {
document.addEventListener('click', function closeMenu(e) {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', closeMenu);
}
});
}, 0);
}
// 获取项目类型标签
function getProjectTypeLabel(type) {
const typeLabels = {
'industrial': '工业',
'smart': '智能',
'vr': 'VR',
'game': '游戏',
'design': '设计'
};
return typeLabels[type] || '其他';
}
// 初始化
renderTestCards();
</script>
</body>
</html>

View File

@ -0,0 +1,104 @@
# 刷新预览图功能说明
## 功能概述
在项目卡片的右键菜单中新增了刷新预览图功能(🔄 刷新预览图),用户可以通过菜单选项重新生成项目的预览图。
## 功能特性
### 1. 智能图片搜索
- 自动搜索项目目录中的图片文件
- 支持常见图片格式PNG, JPG, JPEG, GIF, BMP, TIFF, WEBP
- 搜索常见的图片目录:
- 项目根目录
- images/
- img/
- assets/
- static/
- resources/
- media/
- screenshots/
- preview/
### 2. 智能图片选择
- 优先选择特定名称的图片:
- preview预览图
- screenshot截图
- main主图
- cover封面
- thumbnail缩略图
- icon图标
- 如果没有找到优先图片,则选择第一个找到的图片
### 3. 预览图处理
- 自动缩放到400x300像素
- 保持图片比例,使用裁剪确保尺寸一致
- 使用高质量缩放算法
- 生成PNG格式的预览图
### 4. 默认预览图
- 当项目中没有图片时,自动生成包含项目信息的默认预览图
- 显示项目名称、类型和创建时间
- 统一的视觉风格
### 5. 用户体验优化
- 菜单按钮在处理时显示加载状态(⏳)
- 成功后显示确认状态3秒
- 详细的错误提示信息
- 自动清理旧的预览图文件
## 使用方法
1. 点击项目卡片右上角的菜单按钮(⋯)
2. 在弹出菜单中选择"🔄 刷新预览图"
3. 等待处理完成(菜单按钮工具提示会显示⏳状态)
4. 成功后菜单按钮工具提示会短暂显示✅状态
5. 预览图会自动更新显示
## 技术实现
### 文件存储
- 预览图存储在:`MetaCore/Resources/ProjectPreviews/`
- 文件命名规则:`preview_{项目ID}_{时间戳}.png`
- 自动创建目录结构
### 性能优化
- 自动清理项目的旧预览图,只保留最新的一个
- 使用高效的文件搜索算法
- 异步处理,不阻塞界面
### 错误处理
- 路径不存在检查
- 图片格式验证
- 权限问题处理
- 详细的错误信息提示
## 样式设计
刷新功能集成在右键菜单中:
- 菜单项显示:🔄 刷新预览图
- 加载状态:菜单按钮工具提示显示⏳状态
- 成功状态:菜单按钮工具提示显示✅状态
- 与现有菜单风格保持一致
## 测试
可以使用项目根目录下的 `test_refresh_preview.py` 文件来测试这个功能:
```bash
python test_refresh_preview.py
```
## 注意事项
1. 确保项目目录存在且可访问
2. 项目目录中需要包含图片文件才能生成真实的预览图
3. 生成的预览图会保存到应用程序目录,请确保有写入权限
4. 旧的预览图会被自动清理,避免占用过多磁盘空间
## 后续扩展
1. 支持更多图片格式
2. 允许用户手动选择预览图
3. 支持从项目代码生成预览图(如网页截图)
4. 添加预览图编辑功能

49
启动MetaCore.bat Normal file
View File

@ -0,0 +1,49 @@
@echo off
echo ========================================
echo 启动 MetaCore 项目管理平台
echo ========================================
echo.
REM 进入MetaCore目录
cd MetaCore
REM 检查Python是否可用
python --version >nul 2>&1
if errorlevel 1 (
echo [错误] Python未找到请确保Python已正确安装并添加到PATH
pause
exit /b 1
)
echo [信息] Python环境正常
python --version
REM 检查PyQt5是否已安装
python -c "import PyQt5" >nul 2>&1
if errorlevel 1 (
echo [警告] PyQt5未安装正在安装...
python -m pip install PyQt5
if errorlevel 1 (
echo [错误] PyQt5安装失败
pause
exit /b 1
)
echo [成功] PyQt5安装完成
) else (
echo [信息] PyQt5已安装
)
echo.
echo [信息] 正在启动MetaCore应用...
echo ========================================
echo.
REM 启动应用
python main.py
echo.
echo ========================================
echo [信息] MetaCore应用已退出
echo 感谢使用!
cd ..
pause

119
打包说明.md Normal file
View File

@ -0,0 +1,119 @@
# MetaCore 打包说明
## 问题解释
您遇到的情况是正常的PyInstaller 使用 `-F` 参数确实只会生成一个 `main.exe` 文件,这是一个**完整的独立可执行文件**,包含了所有必要的依赖和资源。
## 当前状态
- ✅ **打包成功**:生成的 `main.exe` (37MB) 是完整的应用程序
- ✅ **可以运行**:双击即可启动 MetaCore 应用
- ✅ **包含所有依赖**Python 运行时、PyQt5、所有模块都已打包
## 优化建议
为了获得更好的打包效果,我为您创建了:
### 1. 优化的配置文件 (`MetaCore.spec`)
- 正确包含资源文件(图标、数据等)
- 设置应用图标
- 隐藏控制台窗口
- 排除不必要的模块以减小文件大小
### 2. 自动化打包脚本 (`build_app.py`)
- 自动清理构建目录
- 检查依赖
- 验证构建结果
- 提供详细的构建信息
## 使用新的打包方法
### 方法一:使用自动化脚本(推荐)
```bash
python build_app.py
```
### 方法二:手动使用优化配置
```bash
pyinstaller MetaCore.spec --clean
```
## 预期改进
使用新配置后,您将获得:
1. **更好的用户体验**
- 应用图标显示正确
- 启动时不显示控制台窗口
- 资源文件正确打包
2. **更小的文件大小**
- 排除不必要的模块
- 优化打包配置
3. **更专业的输出**
- 文件名为 `MetaCore.exe` 而不是 `main.exe`
- 包含版本信息和图标
## 打包参数说明
- `-F` (--onefile)打包成单个exe文件
- `-w` (--windowed):不显示控制台窗口
- `--icon`:设置应用程序图标
- `--add-data`:添加资源文件
- `--exclude-module`:排除不需要的模块
## 故障排除
### 如果遇到问题:
1. **权限问题**
```bash
# 不要以管理员身份运行
# PyInstaller 7.0 将禁止管理员运行
```
2. **缺少资源文件**
```bash
# 检查资源目录是否存在
# 使用 MetaCore.spec 配置文件
```
3. **导入错误**
```bash
# 检查 hiddenimports 配置
# 确保所有模块都被正确识别
```
## 分发应用程序
生成的 `MetaCore.exe` 文件可以:
1. **独立运行**:无需安装 Python 或任何依赖
2. **分发给用户**:将单个 exe 文件发送给用户即可
3. **跨系统运行**:在任何 Windows 系统上运行
## 注意事项
1. **首次运行较慢**exe 文件需要解压到临时目录
2. **防病毒软件**:可能会误报,需要添加白名单
3. **文件大小**:单文件打包会比较大,这是正常的
## 高级选项
如果需要更灵活的分发方式,可以考虑:
### 文件夹模式打包
```bash
pyinstaller MetaCore.spec --onedir
```
这会生成一个包含多个文件的文件夹,启动更快但分发稍复杂。
### 添加启动器
可以创建一个小的启动器程序来改善用户体验。
## 总结
您的打包实际上是**成功的**`main.exe` 就是完整的应用程序。使用我提供的优化配置可以获得更好的打包效果和用户体验。

View File

@ -0,0 +1,301 @@
MetaCore 虚拟环境设置指南
================================
目录
----
1. 快速开始
2. 详细步骤
3. 常用命令
4. 故障排除
5. 最佳实践
================================
1. 快速开始
----------
Windows用户
1. 打开命令提示符(cmd)
2. 进入项目目录cd /d D:\PythonProject\MetaCore-startup
3. 创建虚拟环境python -m venv metacore_env
4. 激活环境metacore_env\Scripts\activate
5. 安装依赖pip install -r MetaCore\requirements.txt
6. 运行程序cd MetaCore && python main.py
macOS/Linux用户
1. 打开终端
2. 进入项目目录cd /path/to/MetaCore-startup
3. 创建虚拟环境python3 -m venv metacore_env
4. 激活环境source metacore_env/bin/activate
5. 安装依赖pip install -r MetaCore/requirements.txt
6. 运行程序cd MetaCore && python main.py
================================
2. 详细步骤
----------
步骤1检查Python环境
- 确保Python 3.7+已安装
- 检查命令python --version 或 python3 --version
步骤2创建虚拟环境
Windows
python -m venv metacore_env
macOS/Linux
python3 -m venv metacore_env
步骤3激活虚拟环境
Windows
metacore_env\Scripts\activate
macOS/Linux
source metacore_env/bin/activate
激活成功后,命令提示符前会显示 (metacore_env)
步骤4升级pip推荐
python -m pip install --upgrade pip
步骤5安装项目依赖
pip install -r MetaCore\requirements.txt
主要依赖包:
- PyQt5 >= 5.15.0 (GUI框架必需)
- PyInstaller >= 5.0.0 (打包工具,必需)
- Pillow >= 8.0.0 (图像处理,推荐)
- psutil >= 5.8.0 (系统信息,可选)
标准库模块(无需安装):
- sys, os, json, datetime (系统和文件操作)
- pathlib (路径处理)
- subprocess (进程管理)
- platform (平台信息)
- typing (类型提示)
步骤6验证安装
python -c "import PyQt5; print('PyQt5安装成功')"
步骤7运行应用
cd MetaCore
python main.py
步骤8退出虚拟环境
deactivate
================================
3. 常用命令
----------
环境管理:
- 创建环境python -m venv 环境名
- 激活环境:
Windows: 环境名\Scripts\activate
Linux/Mac: source 环境名/bin/activate
- 退出环境deactivate
- 删除环境:直接删除环境文件夹
包管理:
- 安装包pip install 包名
- 安装指定版本pip install 包名==版本号
- 安装需求文件pip install -r requirements.txt
- 查看已安装包pip list
- 查看包信息pip show 包名
- 卸载包pip uninstall 包名
- 导出需求文件pip freeze > requirements.txt
环境检查:
- 检查Python版本python --version
- 检查pip版本pip --version
- 检查虚拟环境echo $VIRTUAL_ENV (Linux/Mac)
echo %VIRTUAL_ENV% (Windows)
- 检查PyQt5python -c "import PyQt5; print('可用')"
================================
4. 故障排除
----------
问题1python命令不存在
解决:
- 重新安装Python勾选"Add Python to PATH"
- 或使用完整路径C:\Python39\python.exe
问题2venv模块不存在
解决:
- pip install virtualenv
- 使用virtualenv metacore_env
问题3PyQt5安装失败
解决:
- 使用国内镜像:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyQt5
- 或使用清华镜像:
pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ PyQt5
- 或使用阿里云镜像:
pip install -i https://mirrors.aliyun.com/pypi/simple/ PyQt5
问题3.1PyInstaller安装失败
解决:
- 使用国内镜像:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyInstaller
- 或直接安装:
pip install PyInstaller
问题3.2Pillow安装失败
解决:
- 使用国内镜像:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple Pillow
- 或使用预编译版本:
pip install --upgrade Pillow
问题4权限错误
解决:
- Windows以管理员身份运行命令提示符
- Linux/Mac使用sudo或检查目录权限
问题5激活脚本不存在
解决:
- 删除环境文件夹,重新创建
- 检查Python版本是否支持venv模块
问题6导入PyQt5失败
解决:
- 确认在虚拟环境中安装pip list | grep PyQt5
- 重新安装pip uninstall PyQt5 && pip install PyQt5
- 检查Python版本兼容性PyQt5需要Python 3.7+
问题7依赖包版本冲突
解决:
- 查看冲突信息pip check
- 升级pippython -m pip install --upgrade pip
- 清理缓存pip cache purge
- 重新安装pip install --force-reinstall -r MetaCore\requirements.txt
问题8网络连接问题
解决:
- 使用国内镜像源(推荐清华源):
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
- 或临时使用镜像:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r MetaCore\requirements.txt
================================
5. 最佳实践
----------
项目隔离:
- 每个项目使用独立的虚拟环境
- 不在系统Python中安装项目依赖
- 虚拟环境命名要有意义
依赖管理:
- 使用requirements.txt记录依赖
- 定期更新依赖包版本
- 区分开发依赖和生产依赖
版本控制:
- 将虚拟环境目录添加到.gitignore
- 只提交requirements.txt文件
- 在不同机器上重新创建虚拟环境
目录结构:
MetaCore-startup/
├── metacore_env/ # 虚拟环境(不提交版本控制)
├── MetaCore/ # 主程序目录
│ ├── main.py # 程序入口
│ ├── requirements.txt # 依赖列表
│ └── ...
├── Doc/ # 文档目录
├── tests/ # 测试目录
└── README.md # 项目说明
环境变量:
- 激活环境后VIRTUAL_ENV变量指向环境路径
- PATH变量会优先使用虚拟环境中的Python和pip
安全建议:
- 定期更新pippython -m pip install --upgrade pip
- 使用固定版本号避免依赖冲突
- 在生产环境中使用requirements.txt精确安装
================================
快速参考卡
----------
创建并激活环境:
Windows:
python -m venv metacore_env
metacore_env\Scripts\activate
Linux/Mac:
python3 -m venv metacore_env
source metacore_env/bin/activate
安装依赖并运行:
pip install -r MetaCore\requirements.txt
cd MetaCore
python main.py
退出环境:
deactivate
================================
联系方式
--------
如遇到问题,请:
1. 先运行 python MetaCore\验证安装.py 检查环境
2. 查看项目文档Doc\README.md
3. 参考详细指南Doc\虚拟环境完整指南.md
================================
完整依赖包说明
--------------
必需依赖(必须安装):
1. PyQt5 >= 5.15.0
- 作用GUI框架提供窗口、按钮、对话框等界面组件
- 安装pip install PyQt5
- 验证python -c "import PyQt5; print('PyQt5可用')"
2. PyInstaller >= 5.0.0
- 作用将Python程序打包成可执行文件
- 安装pip install PyInstaller
- 验证pyinstaller --version
推荐依赖(建议安装):
3. Pillow >= 8.0.0
- 作用:图像处理库,提供更好的图标和图片支持
- 安装pip install Pillow
- 验证python -c "import PIL; print('Pillow可用')"
可选依赖(按需安装):
4. psutil >= 5.8.0
- 作用:系统信息库,提供系统集成功能
- 安装pip install psutil
- 验证python -c "import psutil; print('psutil可用')"
标准库模块Python内置无需安装
- sys系统相关功能
- os操作系统接口
- jsonJSON数据处理
- datetime日期时间处理
- pathlib路径操作
- subprocess子进程管理
- platform平台信息
- typing类型提示
一键安装所有依赖:
pip install PyQt5 PyInstaller Pillow psutil
或使用requirements.txt
pip install -r MetaCore\requirements.txt
================================
祝您使用愉快!

145
项目整理完成报告.md Normal file
View File

@ -0,0 +1,145 @@
# MetaCore 项目整理完成报告
## 🎉 整理概述
MetaCore项目已完成全面整理项目结构更加清晰文件组织更加合理。本次整理进一步优化了项目结构删除了更多冗余文件。
## ✅ 完成的整理工作
### 1. 目录结构重组
- ✅ **创建web目录**将Web版本文件统一移动到web目录
- ✅ **创建tests目录**:将所有测试文件集中管理
- ✅ **创建tools目录**:将构建脚本和工具文件归类
- ✅ **保留MetaCore目录**PyQt5桌面版本核心代码
- ✅ **保留Doc目录**:文档中心统一管理
### 2. 文件清理
- ✅ **删除重复构建脚本**
- 删除:`build.ps1`, `build_fixed.bat`, `simple_build.bat`, `quick_build.bat`
- 删除:`build_simple.py`, `build_commands.txt`, `build_metacore.bat`
- 保留:`tools/build.bat`, `tools/build_config.py`
- ✅ **删除散布的测试文件**
- 删除:`test_card_fix.py`, `test_explorer_feature.py`, `test_import_project_fix.py`
- 删除:`test_project_creation.py`, `test_project_settings_integration.py`
- 移动:所有`test_*.py`文件到`tests/`目录
- ✅ **删除构建产物**
- 删除:`MetaCore.spec`(构建产物)
- ✅ **删除重复启动脚本**
- 删除:`MetaCore/setup_and_run.bat`, `MetaCore/setup_and_run.sh`, `MetaCore/run_app.py`
- 删除:`MetaCore/quick_build.bat`
- 优化:保留并改进主要启动脚本
### 3. 文档整理
- ✅ **删除重复文档**
- 删除:`文件清理报告.md`, `文档整理报告.md`, `目录整理完成报告.md`
- 删除:`目录整理说明.md`, `项目完成报告.md`
- 删除:`虚拟环境快速参考.md`, `虚拟环境设置指南.md`
- 删除:`创建项目1-3布局说明.md`, `项目创建功能说明.md`
- 删除:`待删除项目功能说明.md`, `自动恢复项目功能说明.md`, `防重复弹窗修复说明.md`
- ✅ **更新文档索引**
- 更新:`Doc/README.md`文档导航
- 移除:已删除文档的引用
- 优化:文档分类和推荐阅读顺序
### 4. 启动脚本优化
- ✅ **根目录启动脚本**
- 改进:`启动MetaCore.bat`使用通用Python命令
- 添加环境检查和PyQt5自动安装
- ✅ **MetaCore目录启动脚本**
- 优化:`MetaCore/启动应用.bat`使用通用Python命令
- 保持:完整的环境检查功能
### 5. 文件移动记录
```
原位置 → 新位置
├── index.html → web/index.html
├── script.js → web/script.js
├── styles.css → web/styles.css
├── test-cards.html → web/test-cards.html
├── test_*.py → tests/test_*.py
├── verify_button_fix.py → tests/verify_button_fix.py
├── build.bat → tools/build.bat
├── build_config.py → tools/build_config.py
├── README_打包.md → tools/README_打包.md
└── 打包指南.md → tools/打包指南.md
```
## 📁 整理后的项目结构
```
MetaCore/
├── web/ # 🌐 Web版本
│ ├── index.html
│ ├── script.js
│ ├── styles.css
│ └── test-cards.html
├── MetaCore/ # 🖥️ PyQt5桌面版本
│ ├── main.py
│ ├── 启动应用.bat
│ ├── 验证安装.py
│ ├── data/
│ └── ui/
├── Doc/ # 📚 文档中心
│ ├── README.md
│ ├── 快速开始.md
│ ├── README_PyQt5.md
│ ├── 项目总览.md
│ ├── 功能对比说明.md
│ ├── 安装PyQt5指南.md
│ └── 虚拟环境完整指南.md
├── tests/ # 🧪 测试文件
│ ├── test_*.py
│ └── verify_button_fix.py
├── tools/ # 🔧 开发工具
│ ├── build.bat
│ ├── build_config.py
│ ├── README_打包.md
│ └── 打包指南.md
├── data/ # 📊 共享数据
├── 启动MetaCore.bat # 🚀 快速启动
├── README.md # 📖 项目说明
└── .gitignore # 🔧 Git配置
```
## 🎯 整理效果
### 优势
1. **结构清晰**:每个目录职责明确,便于维护
2. **文件归类**:相关文件集中管理,减少混乱
3. **文档精简**:删除重复内容,保留核心文档
4. **启动简化**:优化启动脚本,提升用户体验
5. **开发友好**:测试和工具文件分离,便于开发
### 用户体验改进
1. **Web版本**:直接访问`web/index.html`
2. **桌面版本**:运行`启动MetaCore.bat`或进入MetaCore目录
3. **文档查阅**:从`Doc/README.md`开始导航
4. **开发调试**:测试文件集中在`tests/`目录
5. **构建打包**:工具脚本集中在`tools/`目录
## 🔮 后续建议
### 维护建议
1. **保持结构**:新文件按类别放入对应目录
2. **文档更新**:及时更新文档索引
3. **版本控制**使用Git管理代码变更
4. **定期清理**:定期清理临时文件和构建产物
### 扩展方向
1. **移动版本**:可在根目录添加`mobile/`目录
2. **API版本**:可添加`api/`目录用于后端服务
3. **插件系统**:可添加`plugins/`目录用于扩展功能
4. **国际化**:可添加`i18n/`目录用于多语言支持
---
🎉 **项目整理完成!**
现在MetaCore项目结构更加清晰便于使用、开发和维护。选择您需要的版本开始使用吧
💡 **提示**:推荐从`Doc/快速开始.md`开始了解项目使用方法。