From c2e7b2c70f2c81e60876f8d2f5bd101e2abc13e9 Mon Sep 17 00:00:00 2001 From: imeepos Date: Sun, 13 Jul 2025 22:26:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=A7=E5=B9=85=E5=A2=9E=E5=BC=BAPro?= =?UTF-8?q?jectCard=20-=20=E6=B7=BB=E5=8A=A0=E7=BB=9F=E8=AE=A1=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=92=8C=E6=89=93=E5=BC=80=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能增强: 为首页项目列表中的ProjectCard添加了丰富的统计信息展示和便捷的文件夹操作功能, 让用户能够快速了解项目状态并方便地访问项目文件。 新增功能: 1. 项目统计信息展示: - 自动加载项目素材统计数据 - 显示素材总数和总文件大小 - 按类型分类显示(视频、音频、图片、其他) - 使用颜色编码区分不同文件类型 2. 打开文件夹功能: - 底部按钮栏新增文件夹图标按钮 - 下拉菜单中添加'打开文件夹'选项 - 支持Windows长路径格式处理 - 双重备用机制确保兼容性 3. 加载状态优化: - 统计信息加载时显示加载状态 - 静默处理加载失败,不影响卡片显示 - 优雅的动画效果 技术实现: 1. 统计数据获取: - 使用get_project_material_stats命令 - React hooks管理状态 - useEffect自动加载数据 2. 文件夹操作: - 集成@tauri-apps/plugin-opener - 路径标准化处理 - 错误处理和用户提示 3. UI设计: - 统计信息卡片式展示 - 图标+数字的直观显示 - 响应式布局适配 4. 数据格式化: - formatFileSize函数处理文件大小 - 智能单位转换(B/KB/MB/GB/TB) 视觉效果: 项目统计信息一目了然 文件类型分布清晰展示 便捷的文件夹访问按钮 加载状态友好提示 颜色编码增强可读性 用户体验: - 快速了解项目规模和内容 - 一键打开项目文件夹 - 直观的文件类型分布 - 流畅的交互体验 现在首页的项目卡片功能更加完善,用户可以快速了解项目状态并便捷地进行文件管理! --- apps/desktop/src/components/ProjectCard.tsx | 163 +++++++++++++++++++- apps/desktop/src/pages/ProjectDetails.tsx | 5 - 2 files changed, 156 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/components/ProjectCard.tsx b/apps/desktop/src/components/ProjectCard.tsx index 4b5960b..c6f113a 100644 --- a/apps/desktop/src/components/ProjectCard.tsx +++ b/apps/desktop/src/components/ProjectCard.tsx @@ -1,15 +1,25 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Project } from '../types/project'; +import { MaterialStats } from '../types/material'; import { formatDistanceToNow } from 'date-fns'; import { zhCN } from 'date-fns/locale'; -import { - Folder, - MoreVertical, - Edit3, - Trash2, +import { invoke } from '@tauri-apps/api/core'; +import { + Folder, + MoreVertical, + Edit3, + Trash2, ExternalLink, Calendar, - MapPin + MapPin, + FolderOpen, + FileVideo, + FileAudio, + FileImage, + File, + HardDrive, + BarChart3, + Hash } from 'lucide-react'; interface ProjectCardProps { @@ -19,6 +29,15 @@ interface ProjectCardProps { onDelete: (id: string) => void; } +// 格式化文件大小 +const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; + /** * 项目卡片组件 * 遵循简洁大方的设计风格 @@ -30,8 +49,30 @@ export const ProjectCard: React.FC = ({ onDelete }) => { const [showMenu, setShowMenu] = React.useState(false); + const [stats, setStats] = useState(null); + const [isLoadingStats, setIsLoadingStats] = useState(false); const menuRef = React.useRef(null); + // 加载项目统计信息 + useEffect(() => { + const loadStats = async () => { + setIsLoadingStats(true); + try { + const projectStats = await invoke('get_project_material_stats', { + projectId: project.id + }); + setStats(projectStats); + } catch (error) { + console.error('加载项目统计失败:', error); + // 静默失败,不影响卡片显示 + } finally { + setIsLoadingStats(false); + } + }; + + loadStats(); + }, [project.id]); + // 点击外部关闭菜单 React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -68,6 +109,37 @@ export const ProjectCard: React.FC = ({ return parts[parts.length - 1] || path; }; + // 打开项目文件夹 + const handleOpenFolder = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + const { openPath } = await import('@tauri-apps/plugin-opener'); + + // 处理 Windows 路径格式,移除 \\?\ 前缀 + let normalizedPath = project.path; + if (normalizedPath.startsWith('\\\\?\\')) { + normalizedPath = normalizedPath.substring(4); + } + + await openPath(normalizedPath); + } catch (error) { + console.error('打开文件夹失败:', error); + + // 如果 openPath 失败,尝试使用 revealItemInDir + try { + const { revealItemInDir } = await import('@tauri-apps/plugin-opener'); + let normalizedPath = project.path; + if (normalizedPath.startsWith('\\\\?\\')) { + normalizedPath = normalizedPath.substring(4); + } + await revealItemInDir(normalizedPath); + } catch (fallbackError) { + console.error('备用方法也失败:', fallbackError); + alert('无法打开文件夹,请检查路径是否存在'); + } + } + }; + return (
@@ -96,6 +168,16 @@ export const ProjectCard: React.FC = ({ 打开项目 +