Files
mixvideo-v2/apps/desktop/src/components/ProjectList.tsx
imeepos 42c5dcef8e feat: 实现项目管理功能 v0.1.1
新功能:
- 项目创建:支持项目名称和本地路径绑定
- 项目列表:简洁大方的卡片式布局展示
- 项目编辑:支持项目信息修改
- 项目删除:支持项目软删除
- 路径选择:集成系统文件夹选择对话框
- 路径验证:实时验证项目路径有效性

 架构设计:
- 遵循 Tauri 开发规范的四层架构设计
- 基础设施层:数据库管理、文件系统操作
- 数据访问层:项目仓库模式、SQLite 集成
- 业务逻辑层:项目服务、数据验证
- 表示层:Tauri 命令、前端组件

 UI/UX:
- 使用 Tailwind CSS 实现简洁大方的设计风格
- 响应式布局适配不同屏幕尺寸
- 流畅的动画效果和交互反馈
- 完整的错误处理和用户提示

 技术栈:
- 后端:Rust + Tauri + SQLite + 四层架构
- 前端:React + TypeScript + Tailwind CSS + Zustand
- 测试:Rust 单元测试 + Vitest 前端测试
- 工具:pnpm 包管理 + 类型安全保证

 质量保证:
- Rust 单元测试覆盖核心业务逻辑
- 前端组件测试覆盖主要 UI 组件
- TypeScript 严格模式确保类型安全
- 遵循开发规范的代码质量标准

 核心特性:
- 项目管理:创建、查看、编辑、删除项目
- 路径管理:自动验证、绝对路径转换
- 数据持久化:SQLite 本地数据库存储
- 状态管理:Zustand 响应式状态管理
- 错误处理:完整的错误捕获和用户反馈
2025-07-13 18:46:58 +08:00

145 lines
4.0 KiB
TypeScript

import React, { useEffect } from 'react';
import { useProjectStore } from '../store/projectStore';
import { useUIStore } from '../store/uiStore';
import { ProjectCard } from './ProjectCard';
import { EmptyState } from './EmptyState';
import { LoadingSpinner } from './LoadingSpinner';
import { ErrorMessage } from './ErrorMessage';
import { Plus } from 'lucide-react';
/**
* 项目列表组件
* 遵循 Tauri 开发规范的组件设计模式
*/
export const ProjectList: React.FC = () => {
const {
projects,
isLoading,
error,
loadProjects,
deleteProject,
setCurrentProject,
clearError
} = useProjectStore();
const {
openCreateProjectModal,
openEditProjectModal
} = useUIStore();
// 组件挂载时加载项目
useEffect(() => {
loadProjects();
}, [loadProjects]);
// 处理项目打开
const handleProjectOpen = (project: any) => {
setCurrentProject(project);
// TODO: 导航到项目详情页面
console.log('打开项目:', project);
};
// 处理项目编辑
const handleProjectEdit = (project: any) => {
openEditProjectModal(project);
};
// 处理项目删除
const handleProjectDelete = async (id: string) => {
if (window.confirm('确定要删除这个项目吗?此操作不可撤销。')) {
try {
await deleteProject(id);
} catch (error) {
console.error('删除项目失败:', error);
}
}
};
// 错误状态
if (error) {
return (
<div className="w-full animate-fade-in">
<div className="flex items-center justify-between mb-8 pb-4 border-b border-gray-200">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<button
className="btn btn-primary"
onClick={openCreateProjectModal}
>
<Plus size={20} />
</button>
</div>
<ErrorMessage
message={error}
onRetry={loadProjects}
onDismiss={clearError}
/>
</div>
);
}
// 加载状态
if (isLoading && projects.length === 0) {
return (
<div className="w-full animate-fade-in">
<div className="flex items-center justify-between mb-8 pb-4 border-b border-gray-200">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<button
className="btn btn-primary"
onClick={openCreateProjectModal}
>
<Plus size={20} />
</button>
</div>
<div className="flex items-center justify-center py-16">
<LoadingSpinner text="加载项目中..." />
</div>
</div>
);
}
return (
<div className="w-full">
<div className="flex items-center justify-between mb-8 pb-4 border-b border-gray-200">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<button
className="btn btn-primary"
onClick={openCreateProjectModal}
disabled={isLoading}
>
<Plus size={20} />
</button>
</div>
{projects.length === 0 ? (
<EmptyState
title="还没有项目"
description="创建您的第一个项目开始使用 MixVideo"
actionText="新建项目"
onAction={openCreateProjectModal}
/>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{projects.map((project) => (
<ProjectCard
key={project.id}
project={project}
onOpen={handleProjectOpen}
onEdit={handleProjectEdit}
onDelete={handleProjectDelete}
/>
))}
</div>
)}
{isLoading && projects.length > 0 && (
<div className="fixed inset-0 bg-black bg-opacity-20 flex items-center justify-center z-40">
<LoadingSpinner size="small" />
</div>
)}
</div>
);
};