From aaf96c5aed9eb5051faed9eef619d55a0ba52444 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 Jul 2025 01:09:20 +0800 Subject: [PATCH] fix --- python_core/services/project_manager.py | 307 ++++++++++++++++++ src-tauri/src/commands/project.rs | 121 ++++++++ src-tauri/src/lib.rs | 9 +- src/App.tsx | 2 + src/pages/ProjectManagePage.tsx | 395 ++++++++++++++++++++++++ src/services/projectService.ts | 249 +++++++++++++++ 6 files changed, 1082 insertions(+), 1 deletion(-) create mode 100644 python_core/services/project_manager.py create mode 100644 src/pages/ProjectManagePage.tsx create mode 100644 src/services/projectService.ts diff --git a/python_core/services/project_manager.py b/python_core/services/project_manager.py new file mode 100644 index 0000000..ff875d2 --- /dev/null +++ b/python_core/services/project_manager.py @@ -0,0 +1,307 @@ +""" +项目管理服务 +管理项目信息,包括项目名、本地目录、分类文件夹初始化、商品信息等 +""" + +import json +import uuid +import os +import shutil +from pathlib import Path +from typing import List, Dict, Optional +from dataclasses import dataclass, asdict +from datetime import datetime + +from python_core.config import settings +from python_core.utils.logger import logger +from python_core.utils.jsonrpc import create_response_handler +from python_core.services.resource_category_manager import resource_category_manager + + +@dataclass +class Project: + """项目数据结构""" + id: str + name: str # 项目名 + local_directory: str # 本地目录路径 + product_name: str # 商品名 + product_image: str # 商品图片路径 + created_at: str + updated_at: str + is_active: bool = True + + +class ProjectManager: + """项目管理器""" + + def __init__(self): + self.cache_dir = settings.temp_dir / "cache" + self.cache_dir.mkdir(parents=True, exist_ok=True) + + # 项目数据文件 + self.projects_file = self.cache_dir / "projects.json" + self.projects = self._load_projects() + + def _load_projects(self) -> List[Project]: + """加载项目数据""" + if self.projects_file.exists(): + try: + with open(self.projects_file, 'r', encoding='utf-8') as f: + data = json.load(f) + return [Project(**item) for item in data] + except Exception as e: + logger.error(f"Failed to load projects: {e}") + return [] + else: + return [] + + def _save_projects(self, projects: List[Project] = None): + """保存项目数据""" + if projects is None: + projects = self.projects + + try: + data = [asdict(project) for project in projects] + with open(self.projects_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + logger.info(f"Projects saved to {self.projects_file}") + except Exception as e: + logger.error(f"Failed to save projects: {e}") + raise + + def _initialize_category_folders(self, project_directory: str) -> bool: + """为项目初始化分类文件夹""" + try: + project_path = Path(project_directory) + if not project_path.exists(): + project_path.mkdir(parents=True, exist_ok=True) + + # 获取所有启用的分类 + active_categories = resource_category_manager.get_all_categories() + + for category in active_categories: + if category.get('is_active', True): # 只为启用的分类创建文件夹 + category_folder = project_path / category['title'] + category_folder.mkdir(exist_ok=True) + logger.info(f"Created category folder: {category_folder}") + + return True + except Exception as e: + logger.error(f"Failed to initialize category folders: {e}") + return False + + def get_all_projects(self) -> List[Dict]: + """获取所有项目""" + return [asdict(project) for project in self.projects if project.is_active] + + def get_project_by_id(self, project_id: str) -> Optional[Dict]: + """根据ID获取项目""" + for project in self.projects: + if project.id == project_id and project.is_active: + return asdict(project) + return None + + def create_project(self, name: str, local_directory: str, product_name: str = "", + product_image: str = "") -> Dict: + """创建新项目""" + now = datetime.now().isoformat() + + # 验证目录路径 + if not os.path.isabs(local_directory): + raise ValueError("Local directory must be an absolute path") + + new_project = Project( + id=str(uuid.uuid4()), + name=name, + local_directory=local_directory, + product_name=product_name, + product_image=product_image, + created_at=now, + updated_at=now + ) + + # 初始化分类文件夹 + if not self._initialize_category_folders(local_directory): + raise RuntimeError("Failed to initialize category folders") + + self.projects.append(new_project) + self._save_projects() + + logger.info(f"Created new project: {name}") + return asdict(new_project) + + def update_project(self, project_id: str, name: str = None, local_directory: str = None, + product_name: str = None, product_image: str = None) -> Optional[Dict]: + """更新项目""" + for i, project in enumerate(self.projects): + if project.id == project_id and project.is_active: + if name is not None: + project.name = name + if local_directory is not None: + # 验证新目录路径 + if not os.path.isabs(local_directory): + raise ValueError("Local directory must be an absolute path") + project.local_directory = local_directory + if product_name is not None: + project.product_name = product_name + if product_image is not None: + project.product_image = product_image + project.updated_at = datetime.now().isoformat() + + self.projects[i] = project + self._save_projects() + + logger.info(f"Updated project: {project_id}") + return asdict(project) + return None + + def delete_project(self, project_id: str) -> bool: + """删除项目(软删除)""" + for i, project in enumerate(self.projects): + if project.id == project_id and project.is_active: + project.is_active = False + project.updated_at = datetime.now().isoformat() + + self.projects[i] = project + self._save_projects() + + logger.info(f"Deleted project: {project_id}") + return True + return False + + def search_projects(self, keyword: str) -> List[Dict]: + """搜索项目""" + keyword = keyword.lower() + results = [] + + for project in self.projects: + if (project.is_active and + (keyword in project.name.lower() or + keyword in project.product_name.lower())): + results.append(asdict(project)) + + return results + + def open_project_directory(self, project_id: str) -> bool: + """打开项目目录""" + project = self.get_project_by_id(project_id) + if project and os.path.exists(project['local_directory']): + try: + import subprocess + import platform + + directory = project['local_directory'] + system = platform.system() + + if system == "Windows": + subprocess.run(['explorer', directory]) + elif system == "Darwin": # macOS + subprocess.run(['open', directory]) + else: # Linux + subprocess.run(['xdg-open', directory]) + + logger.info(f"Opened project directory: {directory}") + return True + except Exception as e: + logger.error(f"Failed to open directory: {e}") + return False + return False + + +# 全局实例 +project_manager = ProjectManager() + + +def main(): + """命令行接口 - 使用JSON-RPC协议""" + import sys + import json + + # 创建响应处理器 + rpc = create_response_handler() + + if len(sys.argv) < 2: + rpc.error("INVALID_REQUEST", "No command specified") + return + + command = sys.argv[1] + + try: + if command == "get_all_projects": + projects = project_manager.get_all_projects() + rpc.success(projects) + + elif command == "get_project_by_id": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Project ID required") + return + project_id = sys.argv[2] + project = project_manager.get_project_by_id(project_id) + if project: + rpc.success(project) + else: + rpc.error("NOT_FOUND", "Project not found") + + elif command == "create_project": + if len(sys.argv) < 4: + rpc.error("INVALID_REQUEST", "Name and directory required") + return + name = sys.argv[2] + local_directory = sys.argv[3] + product_name = sys.argv[4] if len(sys.argv) > 4 else "" + product_image = sys.argv[5] if len(sys.argv) > 5 else "" + result = project_manager.create_project(name, local_directory, product_name, product_image) + rpc.success(result) + + elif command == "update_project": + if len(sys.argv) < 4: + rpc.error("INVALID_REQUEST", "Project ID and update data required") + return + project_id = sys.argv[2] + update_data = json.loads(sys.argv[3]) + result = project_manager.update_project( + project_id, + update_data.get('name'), + update_data.get('local_directory'), + update_data.get('product_name'), + update_data.get('product_image') + ) + if result: + rpc.success(result) + else: + rpc.error("NOT_FOUND", "Project not found") + + elif command == "delete_project": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Project ID required") + return + project_id = sys.argv[2] + success = project_manager.delete_project(project_id) + rpc.success(success) + + elif command == "search_projects": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Search keyword required") + return + keyword = sys.argv[2] + results = project_manager.search_projects(keyword) + rpc.success(results) + + elif command == "open_project_directory": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Project ID required") + return + project_id = sys.argv[2] + success = project_manager.open_project_directory(project_id) + rpc.success(success) + + else: + rpc.error("INVALID_REQUEST", f"Unknown command: {command}") + + except Exception as e: + logger.error(f"Command execution failed: {e}") + rpc.error("INTERNAL_ERROR", str(e)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src-tauri/src/commands/project.rs b/src-tauri/src/commands/project.rs index 8918be1..acdddfd 100644 --- a/src-tauri/src/commands/project.rs +++ b/src-tauri/src/commands/project.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +use tauri::{command, AppHandle}; +use crate::python_executor::execute_python_command; #[derive(Debug, Serialize, Deserialize)] pub struct ProjectInfo { @@ -75,3 +77,122 @@ pub async fn load_project(project_path: String) -> Result { Ok(project_info) } + +// 项目管理相关结构体和命令 + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateProjectRequest { + pub name: String, + pub local_directory: String, + pub product_name: Option, + pub product_image: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateProjectRequest { + pub name: Option, + pub local_directory: Option, + pub product_name: Option, + pub product_image: Option, +} + +/// 获取所有项目 +#[command] +pub async fn get_all_projects(app: AppHandle) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "get_all_projects".to_string(), + ]; + + execute_python_command(app, &args, None).await +} + +/// 根据ID获取项目 +#[command] +pub async fn get_project_by_id(app: AppHandle, project_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "get_project_by_id".to_string(), + project_id, + ]; + + execute_python_command(app, &args, None).await +} + +/// 创建新项目 +#[command] +pub async fn create_project(app: AppHandle, request: CreateProjectRequest) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "create_project".to_string(), + request.name, + request.local_directory, + request.product_name.unwrap_or_default(), + request.product_image.unwrap_or_default(), + ]; + + execute_python_command(app, &args, None).await +} + +/// 更新项目 +#[command] +pub async fn update_project( + app: AppHandle, + project_id: String, + request: UpdateProjectRequest, +) -> Result { + let request_json = serde_json::to_string(&request) + .map_err(|e| format!("Failed to serialize request: {}", e))?; + + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "update_project".to_string(), + project_id, + request_json, + ]; + + execute_python_command(app, &args, None).await +} + +/// 删除项目 +#[command] +pub async fn delete_project(app: AppHandle, project_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "delete_project".to_string(), + project_id, + ]; + + execute_python_command(app, &args, None).await +} + +/// 搜索项目 +#[command] +pub async fn search_projects(app: AppHandle, keyword: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "search_projects".to_string(), + keyword, + ]; + + execute_python_command(app, &args, None).await +} + +/// 打开项目目录 +#[command] +pub async fn open_project_directory(app: AppHandle, project_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.project_manager".to_string(), + "open_project_directory".to_string(), + project_id, + ]; + + execute_python_command(app, &args, None).await +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index de567c4..800a245 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -48,7 +48,14 @@ pub fn run() { commands::create_resource_category, commands::update_resource_category, commands::delete_resource_category, - commands::search_resource_categories + commands::search_resource_categories, + commands::get_all_projects, + commands::get_project_by_id, + commands::create_project, + commands::update_project, + commands::delete_project, + commands::search_projects, + commands::open_project_directory ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.tsx b/src/App.tsx index 9509446..76f4213 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import SettingsPage from './pages/SettingsPage' import TemplateManagePage from './pages/TemplateManagePage' import TemplateDetailPage from './pages/TemplateDetailPage' import ResourceCategoryPage from './pages/ResourceCategoryPage' +import ProjectManagePage from './pages/ProjectManagePage' import KVTestPage from './pages/KVTestPage' function App() { @@ -20,6 +21,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/pages/ProjectManagePage.tsx b/src/pages/ProjectManagePage.tsx new file mode 100644 index 0000000..0f9f1e4 --- /dev/null +++ b/src/pages/ProjectManagePage.tsx @@ -0,0 +1,395 @@ +import React, { useState, useEffect } from 'react' +import { Plus, Edit, Trash2, Search, Save, X, FolderOpen, Package } from 'lucide-react' +import { ProjectService, Project } from '../services/projectService' + +const ProjectManagePage: React.FC = () => { + const [projects, setProjects] = useState([]) + const [loading, setLoading] = useState(true) + const [searchTerm, setSearchTerm] = useState('') + const [editingProject, setEditingProject] = useState(null) + const [showCreateForm, setShowCreateForm] = useState(false) + const [formData, setFormData] = useState({ + name: '', + local_directory: '', + product_name: '', + product_image: '' + }) + + useEffect(() => { + loadProjects() + }, []) + + const loadProjects = async () => { + try { + setLoading(true) + const response = await ProjectService.getAllProjects() + console.log(`loadProjects`, response) + if (response.status && response.data) { + setProjects(response.data) + } else { + console.error('Failed to load projects:', response.msg) + } + } catch (error) { + console.error('Failed to load projects:', error) + } finally { + setLoading(false) + } + } + + const handleCreateProject = async () => { + try { + const response = await ProjectService.createProject(formData) + console.log(`handleCreateProject`, response) + if (response.status && response.data) { + setProjects([...projects, response.data]) + setShowCreateForm(false) + setFormData({ name: '', local_directory: '', product_name: '', product_image: '' }) + } else { + console.error('创建失败:', response.msg || '未知错误') + } + } catch (error) { + console.error('Failed to create project:', error) + } + } + + const handleUpdateProject = async () => { + if (!editingProject) return + + try { + const response = await ProjectService.updateProject(editingProject.id, formData) + if (response.status && response.data) { + const updatedProjects = projects.map(proj => + proj.id === editingProject.id ? response.data! : proj + ) + setProjects(updatedProjects) + setEditingProject(null) + setFormData({ name: '', local_directory: '', product_name: '', product_image: '' }) + } else { + console.error('更新失败:', response.msg || '未知错误') + } + } catch (error) { + console.error('Failed to update project:', error) + } + } + + const handleDeleteProject = async (projectId: string) => { + if (!confirm('确定要删除这个项目吗?')) return + + try { + const response = await ProjectService.deleteProject(projectId) + if (response.status) { + setProjects(projects.filter(proj => proj.id !== projectId)) + } else { + console.error('删除失败:', response.msg || '未知错误') + } + } catch (error) { + console.error('Failed to delete project:', error) + } + } + + const handleOpenDirectory = async (projectId: string) => { + try { + const response = await ProjectService.openProjectDirectory(projectId) + if (!response.status) { + console.error('打开目录失败:', response.msg || '未知错误') + } + } catch (error) { + console.error('Failed to open directory:', error) + } + } + + const selectDirectory = async () => { + try { + // TODO: 使用Tauri的文件选择器 + const directory = prompt('请输入项目目录路径:') + if (directory) { + setFormData({ ...formData, local_directory: directory }) + } + } catch (error) { + console.error('Failed to select directory:', error) + } + } + + const selectImage = async () => { + try { + // TODO: 使用Tauri的文件选择器 + const imagePath = prompt('请输入商品图片路径:') + if (imagePath) { + setFormData({ ...formData, product_image: imagePath }) + } + } catch (error) { + console.error('Failed to select image:', error) + } + } + + const startEdit = (project: Project) => { + setEditingProject(project) + setFormData({ + name: project.name, + local_directory: project.local_directory, + product_name: project.product_name, + product_image: project.product_image + }) + setShowCreateForm(false) + } + + const cancelEdit = () => { + setEditingProject(null) + setShowCreateForm(false) + setFormData({ name: '', local_directory: '', product_name: '', product_image: '' }) + } + + const filteredProjects = projects.filter(project => + project.name.toLowerCase().includes(searchTerm.toLowerCase()) || + project.product_name.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + if (loading) { + return ( +
+
加载中...
+
+ ) + } + + return ( +
+ {/* 页面标题 */} +
+

项目管理

+

管理项目信息,包括项目目录、商品信息等

+
+ + {/* 搜索和创建按钮 */} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full" + /> +
+ + +
+ + {/* 项目列表 */} +
+ {filteredProjects.map((project) => ( +
+ {/* 项目标题 */} +
+

{project.name}

+ +
+ + + +
+
+ + {/* 项目信息 */} +
+ {/* 本地目录 */} +
+

本地目录:

+

+ {project.local_directory} +

+
+ + {/* 商品信息 */} + {project.product_name && ( +
+

商品名:

+

{project.product_name}

+
+ )} + + {/* 商品图片 */} + {project.product_image && ( +
+

商品图片:

+
+ +

+ {project.product_image.split('/').pop()} +

+
+
+ )} + + {/* 创建时间 */} +
+ 创建于 {new Date(project.created_at).toLocaleDateString()} +
+
+
+ ))} +
+ + {/* 空状态 */} + {filteredProjects.length === 0 && ( +
+
+ {searchTerm ? '没有找到匹配的项目' : '暂无项目'} +
+ {!searchTerm && ( + + )} +
+ )} + + {/* 创建/编辑表单弹窗 */} + {(showCreateForm || editingProject) && ( +
+
+ {/* 表单标题 */} +
+

+ {editingProject ? '编辑项目' : '新建项目'} +

+ +
+ + {/* 表单内容 */} +
+ {/* 项目名 */} +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="请输入项目名称" + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+ + {/* 本地目录 */} +
+ +
+ setFormData({ ...formData, local_directory: e.target.value })} + placeholder="请选择或输入项目目录路径" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + +
+
+ + {/* 商品名 */} +
+ + setFormData({ ...formData, product_name: e.target.value })} + placeholder="请输入商品名称" + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+ + {/* 商品图片 */} +
+ +
+ setFormData({ ...formData, product_image: e.target.value })} + placeholder="请选择或输入商品图片路径" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + +
+
+
+ + {/* 表单按钮 */} +
+ + +
+
+
+ )} +
+ ) +} + +export default ProjectManagePage diff --git a/src/services/projectService.ts b/src/services/projectService.ts new file mode 100644 index 0000000..222b007 --- /dev/null +++ b/src/services/projectService.ts @@ -0,0 +1,249 @@ +import { invoke } from '@tauri-apps/api/core' + +export interface Project { + id: string + name: string + local_directory: string + product_name: string + product_image: string + created_at: string + updated_at: string + is_active: boolean +} + +export interface CreateProjectRequest { + name: string + local_directory: string + product_name?: string + product_image?: string +} + +export interface UpdateProjectRequest { + name?: string + local_directory?: string + product_name?: string + product_image?: string +} + +export interface ApiResponse { + status: boolean + data?: T + msg?: string +} + +export class ProjectService { + /** + * 获取所有项目 + */ + static async getAllProjects(): Promise> { + try { + console.log('Calling get_all_projects...') + const result = await invoke('get_all_projects') + console.log('Raw result from Tauri:', result) + + // 如果result是字符串,尝试解析JSON + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + console.log('Parsed result:', parsed) + return parsed as ApiResponse + } catch (parseError) { + console.error('Failed to parse JSON response:', parseError) + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to get all projects:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 根据ID获取项目 + */ + static async getProjectById(projectId: string): Promise> { + try { + const result = await invoke('get_project_by_id', { projectId }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to get project by id:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 创建新项目 + */ + static async createProject(request: CreateProjectRequest): Promise> { + try { + console.log('Calling create_project with:', request) + const result = await invoke('create_project', { request }) + console.log('Raw result from Tauri:', result) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + console.log('Parsed result:', parsed) + return parsed as ApiResponse + } catch (parseError) { + console.error('Failed to parse JSON response:', parseError) + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to create project:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 更新项目 + */ + static async updateProject( + projectId: string, + request: UpdateProjectRequest + ): Promise> { + try { + const result = await invoke('update_project', { projectId, request }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to update project:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 删除项目 + */ + static async deleteProject(projectId: string): Promise> { + try { + const result = await invoke('delete_project', { projectId }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to delete project:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 搜索项目 + */ + static async searchProjects(keyword: string): Promise> { + try { + const result = await invoke('search_projects', { keyword }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to search projects:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 打开项目目录 + */ + static async openProjectDirectory(projectId: string): Promise> { + try { + const result = await invoke('open_project_directory', { projectId }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to open project directory:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } +}