fix
This commit is contained in:
307
python_core/services/project_manager.py
Normal file
307
python_core/services/project_manager.py
Normal file
@@ -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()
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::{command, AppHandle};
|
||||||
|
use crate::python_executor::execute_python_command;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ProjectInfo {
|
pub struct ProjectInfo {
|
||||||
@@ -75,3 +77,122 @@ pub async fn load_project(project_path: String) -> Result<ProjectInfo, String> {
|
|||||||
|
|
||||||
Ok(project_info)
|
Ok(project_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 项目管理相关结构体和命令
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CreateProjectRequest {
|
||||||
|
pub name: String,
|
||||||
|
pub local_directory: String,
|
||||||
|
pub product_name: Option<String>,
|
||||||
|
pub product_image: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct UpdateProjectRequest {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub local_directory: Option<String>,
|
||||||
|
pub product_name: Option<String>,
|
||||||
|
pub product_image: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所有项目
|
||||||
|
#[command]
|
||||||
|
pub async fn get_all_projects(app: AppHandle) -> Result<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,14 @@ pub fn run() {
|
|||||||
commands::create_resource_category,
|
commands::create_resource_category,
|
||||||
commands::update_resource_category,
|
commands::update_resource_category,
|
||||||
commands::delete_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!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import SettingsPage from './pages/SettingsPage'
|
|||||||
import TemplateManagePage from './pages/TemplateManagePage'
|
import TemplateManagePage from './pages/TemplateManagePage'
|
||||||
import TemplateDetailPage from './pages/TemplateDetailPage'
|
import TemplateDetailPage from './pages/TemplateDetailPage'
|
||||||
import ResourceCategoryPage from './pages/ResourceCategoryPage'
|
import ResourceCategoryPage from './pages/ResourceCategoryPage'
|
||||||
|
import ProjectManagePage from './pages/ProjectManagePage'
|
||||||
import KVTestPage from './pages/KVTestPage'
|
import KVTestPage from './pages/KVTestPage'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -20,6 +21,7 @@ function App() {
|
|||||||
<Route path="/templates" element={<TemplateManagePage />} />
|
<Route path="/templates" element={<TemplateManagePage />} />
|
||||||
<Route path="/templates/:templateId" element={<TemplateDetailPage />} />
|
<Route path="/templates/:templateId" element={<TemplateDetailPage />} />
|
||||||
<Route path="/resource-categories" element={<ResourceCategoryPage />} />
|
<Route path="/resource-categories" element={<ResourceCategoryPage />} />
|
||||||
|
<Route path="/projects" element={<ProjectManagePage />} />
|
||||||
<Route path="/kv-test" element={<KVTestPage />} />
|
<Route path="/kv-test" element={<KVTestPage />} />
|
||||||
<Route path="/settings" element={<SettingsPage />} />
|
<Route path="/settings" element={<SettingsPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
395
src/pages/ProjectManagePage.tsx
Normal file
395
src/pages/ProjectManagePage.tsx
Normal file
@@ -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<Project[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
|
const [editingProject, setEditingProject] = useState<Project | null>(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 (
|
||||||
|
<div className="flex items-center justify-center h-64">
|
||||||
|
<div className="text-gray-500">加载中...</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6">
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">项目管理</h1>
|
||||||
|
<p className="text-gray-600 mt-2">管理项目信息,包括项目目录、商品信息等</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 搜索和创建按钮 */}
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<div className="relative flex-1 max-w-md">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索项目..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowCreateForm(true)
|
||||||
|
setEditingProject(null)
|
||||||
|
setFormData({ name: '', local_directory: '', product_name: '', product_image: '' })
|
||||||
|
}}
|
||||||
|
className="ml-4 flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
<Plus size={20} className="mr-2" />
|
||||||
|
新建项目
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 项目列表 */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{filteredProjects.map((project) => (
|
||||||
|
<div key={project.id} className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||||
|
{/* 项目标题 */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900">{project.name}</h3>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleOpenDirectory(project.id)}
|
||||||
|
className="p-2 text-gray-400 hover:text-green-600 transition-colors"
|
||||||
|
title="打开目录"
|
||||||
|
>
|
||||||
|
<FolderOpen size={16} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => startEdit(project)}
|
||||||
|
className="p-2 text-gray-400 hover:text-blue-600 transition-colors"
|
||||||
|
title="编辑"
|
||||||
|
>
|
||||||
|
<Edit size={16} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteProject(project.id)}
|
||||||
|
className="p-2 text-gray-400 hover:text-red-600 transition-colors"
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 项目信息 */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* 本地目录 */}
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-600">本地目录:</p>
|
||||||
|
<p className="text-sm text-gray-900 truncate" title={project.local_directory}>
|
||||||
|
{project.local_directory}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品信息 */}
|
||||||
|
{project.product_name && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-600">商品名:</p>
|
||||||
|
<p className="text-sm text-gray-900">{project.product_name}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 商品图片 */}
|
||||||
|
{project.product_image && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-600">商品图片:</p>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Package size={16} className="text-gray-400" />
|
||||||
|
<p className="text-sm text-gray-900 truncate flex-1" title={project.product_image}>
|
||||||
|
{project.product_image.split('/').pop()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建时间 */}
|
||||||
|
<div className="text-xs text-gray-400 pt-2 border-t border-gray-100">
|
||||||
|
创建于 {new Date(project.created_at).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 空状态 */}
|
||||||
|
{filteredProjects.length === 0 && (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-gray-400 mb-4">
|
||||||
|
{searchTerm ? '没有找到匹配的项目' : '暂无项目'}
|
||||||
|
</div>
|
||||||
|
{!searchTerm && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowCreateForm(true)}
|
||||||
|
className="text-blue-600 hover:text-blue-700"
|
||||||
|
>
|
||||||
|
创建第一个项目
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建/编辑表单弹窗 */}
|
||||||
|
{(showCreateForm || editingProject) && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">
|
||||||
|
{/* 表单标题 */}
|
||||||
|
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900">
|
||||||
|
{editingProject ? '编辑项目' : '新建项目'}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={cancelEdit}
|
||||||
|
className="text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
<X size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表单内容 */}
|
||||||
|
<div className="p-6 space-y-4">
|
||||||
|
{/* 项目名 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
项目名 *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 本地目录 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
本地目录 *
|
||||||
|
</label>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.local_directory}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={selectDirectory}
|
||||||
|
className="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||||
|
>
|
||||||
|
<FolderOpen size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品名 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
商品名
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.product_name}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 商品图片 */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
商品图片
|
||||||
|
</label>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.product_image}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={selectImage}
|
||||||
|
className="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||||
|
>
|
||||||
|
<Package size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 表单按钮 */}
|
||||||
|
<div className="flex items-center justify-end space-x-3 p-6 border-t border-gray-200">
|
||||||
|
<button
|
||||||
|
onClick={cancelEdit}
|
||||||
|
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={editingProject ? handleUpdateProject : handleCreateProject}
|
||||||
|
disabled={!formData.name.trim() || !formData.local_directory.trim()}
|
||||||
|
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Save size={16} className="mr-2" />
|
||||||
|
{editingProject ? '保存' : '创建'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectManagePage
|
||||||
249
src/services/projectService.ts
Normal file
249
src/services/projectService.ts
Normal file
@@ -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<T> {
|
||||||
|
status: boolean
|
||||||
|
data?: T
|
||||||
|
msg?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectService {
|
||||||
|
/**
|
||||||
|
* 获取所有项目
|
||||||
|
*/
|
||||||
|
static async getAllProjects(): Promise<ApiResponse<Project[]>> {
|
||||||
|
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<Project[]>
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('Failed to parse JSON response:', parseError)
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<Project[]>
|
||||||
|
} 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<ApiResponse<Project>> {
|
||||||
|
try {
|
||||||
|
const result = await invoke('get_project_by_id', { projectId })
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
return parsed as ApiResponse<Project>
|
||||||
|
} catch (parseError) {
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<Project>
|
||||||
|
} 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<ApiResponse<Project>> {
|
||||||
|
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<Project>
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('Failed to parse JSON response:', parseError)
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<Project>
|
||||||
|
} 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<ApiResponse<Project>> {
|
||||||
|
try {
|
||||||
|
const result = await invoke('update_project', { projectId, request })
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
return parsed as ApiResponse<Project>
|
||||||
|
} catch (parseError) {
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<Project>
|
||||||
|
} 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<ApiResponse<boolean>> {
|
||||||
|
try {
|
||||||
|
const result = await invoke('delete_project', { projectId })
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
return parsed as ApiResponse<boolean>
|
||||||
|
} catch (parseError) {
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<boolean>
|
||||||
|
} 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<ApiResponse<Project[]>> {
|
||||||
|
try {
|
||||||
|
const result = await invoke('search_projects', { keyword })
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
return parsed as ApiResponse<Project[]>
|
||||||
|
} catch (parseError) {
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<Project[]>
|
||||||
|
} 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<ApiResponse<boolean>> {
|
||||||
|
try {
|
||||||
|
const result = await invoke('open_project_directory', { projectId })
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
return parsed as ApiResponse<boolean>
|
||||||
|
} catch (parseError) {
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: `Invalid JSON response: ${result}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as ApiResponse<boolean>
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open project directory:', error)
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
msg: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user