init
125
.gitignore
vendored
Normal 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
|
||||
224
CROSS_PLATFORM_OPTIMIZATION.md
Normal 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
@ -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
@ -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
@ -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
@ -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. 如果遇到错误
|
||||
|
||||
#### 错误1:ModuleNotFoundError: No module named 'PyQt5'
|
||||
**解决方案:**
|
||||
```cmd
|
||||
pip install PyQt5
|
||||
```
|
||||
|
||||
#### 错误2:ImportError: DLL load failed
|
||||
**解决方案:**
|
||||
```cmd
|
||||
pip uninstall PyQt5
|
||||
pip install PyQt5
|
||||
```
|
||||
|
||||
#### 错误3:Python命令不存在
|
||||
**解决方案:**
|
||||
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
@ -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版本的所有功能,同时提供了更好的性能和用户体验。开始创建您的第一个项目吧!
|
||||
261
Doc/虚拟环境完整指南.md
Normal 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
@ -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和桌面两种完整的解决方案!**
|
||||
|
||||
无论您喜欢在浏览器中使用还是作为桌面应用运行,都能获得完全相同的功能和体验。选择最适合您的版本,开始管理您的项目吧!
|
||||
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 89 KiB |
210
MetaCore/README.md
Normal 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版本的功能和外观,同时提供了更好的桌面应用体验。
|
||||
BIN
MetaCore/Resources/Icons/Refresh.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
MetaCore/Resources/Icons/app_icon.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
MetaCore/Resources/Icons/category.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
MetaCore/Resources/Icons/create.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
MetaCore/Resources/Icons/delete.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
MetaCore/Resources/Icons/down.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
MetaCore/Resources/Icons/folder.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
MetaCore/Resources/Icons/grid_view.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
MetaCore/Resources/Icons/import.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
MetaCore/Resources/Icons/list_view.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
MetaCore/Resources/Icons/logo.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
MetaCore/Resources/Icons/management.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
MetaCore/Resources/Icons/overview.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
MetaCore/Resources/Icons/resource.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
MetaCore/Resources/Icons/right.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
MetaCore/Resources/Icons/settings.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
MetaCore/Resources/Icons/system_settings.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
1
MetaCore/data/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# 数据模块
|
||||
1271
MetaCore/data/project_manager.py
Normal file
67
MetaCore/main.py
Normal 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
@ -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 (类型提示)
|
||||
47
MetaCore/scripts/example_script.py
Normal 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
@ -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
@ -0,0 +1 @@
|
||||
# UI模块
|
||||
460
MetaCore/ui/create_project_dialog.py
Normal 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
@ -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
@ -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
|
||||
468
MetaCore/ui/import_project_dialog.py
Normal 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
@ -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
@ -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
@ -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:
|
||||
# 首先尝试使用nautilus(GNOME文件管理器)
|
||||
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)
|
||||
|
||||
328
MetaCore/ui/project_settings_page.py
Normal 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
@ -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
45
MetaCore/启动应用.bat
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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()
|
||||
0
tests/test_explorer_feature.py
Normal file
164
tests/test_fixes.py
Normal 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
@ -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
@ -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
@ -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)
|
||||
0
tests/test_import_project_fix.py
Normal file
278
tests/test_pending_delete_feature.py
Normal 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()
|
||||
0
tests/test_project_creation.py
Normal file
0
tests/test_project_settings_integration.py
Normal file
68
tests/test_project_status.py
Normal 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
@ -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()
|
||||
91
tests/verify_button_fix.py
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
339
web/test-cards.html
Normal 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>
|
||||
104
刷新预览图功能说明.md
Normal 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
@ -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
@ -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` 就是完整的应用程序。使用我提供的优化配置可以获得更好的打包效果和用户体验。
|
||||
301
虚拟环境设置指南.txt
Normal 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)
|
||||
- 检查PyQt5:python -c "import PyQt5; print('可用')"
|
||||
|
||||
================================
|
||||
|
||||
4. 故障排除
|
||||
----------
|
||||
|
||||
问题1:python命令不存在
|
||||
解决:
|
||||
- 重新安装Python,勾选"Add Python to PATH"
|
||||
- 或使用完整路径:C:\Python39\python.exe
|
||||
|
||||
问题2:venv模块不存在
|
||||
解决:
|
||||
- pip install virtualenv
|
||||
- 使用:virtualenv metacore_env
|
||||
|
||||
问题3:PyQt5安装失败
|
||||
解决:
|
||||
- 使用国内镜像:
|
||||
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.1:PyInstaller安装失败
|
||||
解决:
|
||||
- 使用国内镜像:
|
||||
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyInstaller
|
||||
- 或直接安装:
|
||||
pip install PyInstaller
|
||||
|
||||
问题3.2:Pillow安装失败
|
||||
解决:
|
||||
- 使用国内镜像:
|
||||
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
|
||||
- 升级pip:python -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
|
||||
|
||||
安全建议:
|
||||
- 定期更新pip:python -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:操作系统接口
|
||||
- json:JSON数据处理
|
||||
- datetime:日期时间处理
|
||||
- pathlib:路径操作
|
||||
- subprocess:子进程管理
|
||||
- platform:平台信息
|
||||
- typing:类型提示
|
||||
|
||||
一键安装所有依赖:
|
||||
pip install PyQt5 PyInstaller Pillow psutil
|
||||
|
||||
或使用requirements.txt:
|
||||
pip install -r MetaCore\requirements.txt
|
||||
|
||||
================================
|
||||
|
||||
祝您使用愉快!
|
||||
145
项目整理完成报告.md
Normal 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`开始了解项目使用方法。
|
||||