This commit is contained in:
root 2025-07-11 01:09:20 +08:00
parent e377ae46ba
commit aaf96c5aed
6 changed files with 1082 additions and 1 deletions

View 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()

View File

@ -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<ProjectInfo, String> {
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
}

View File

@ -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");

View File

@ -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() {
<Route path="/templates" element={<TemplateManagePage />} />
<Route path="/templates/:templateId" element={<TemplateDetailPage />} />
<Route path="/resource-categories" element={<ResourceCategoryPage />} />
<Route path="/projects" element={<ProjectManagePage />} />
<Route path="/kv-test" element={<KVTestPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>

View 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

View 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'
}
}
}
}