mixvideo-v2/apps/desktop/src/pages/Tools.tsx

255 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useMemo } from 'react';
import {
Wrench,
Search,
Grid,
List,
Star,
Sparkles
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { CardGrid } from '../components/CardGrid';
import ToolCard from '../components/ToolCard';
import { TOOLS_DATA, TOOL_CATEGORIES, searchTools } from '../data/tools';
import { Tool, ToolCategory } from '../types/tool';
/**
* 便捷小工具页面
* 遵循 Tauri 开发规范和 UI/UX 设计标准
* 使用卡片列表展示工具,点击进入详情页
*/
const Tools: React.FC = () => {
const navigate = useNavigate();
// 工具列表状态
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<ToolCategory | null>(null);
const [showNewOnly, setShowNewOnly] = useState(false);
const [showPopularOnly, setShowPopularOnly] = useState(false);
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
// 根据筛选条件过滤工具列表
const filteredTools = useMemo(() => {
let result = [...TOOLS_DATA];
// 搜索过滤
if (searchQuery) {
result = searchTools(searchQuery);
}
// 分类过滤
if (selectedCategory) {
result = result.filter(tool => tool.category === selectedCategory);
}
// 新功能过滤
if (showNewOnly) {
result = result.filter(tool => tool.isNew);
}
// 热门工具过滤
if (showPopularOnly) {
result = result.filter(tool => tool.isPopular);
}
return result;
}, [searchQuery, selectedCategory, showNewOnly, showPopularOnly]);
// 处理工具卡片点击
const handleToolClick = (tool: Tool) => {
navigate(tool.route);
};
// 清除所有筛选条件
const clearFilters = () => {
setSearchQuery('');
setSelectedCategory(null);
setShowNewOnly(false);
setShowPopularOnly(false);
};
return (
<div className="space-y-6">
{/* 页面标题 */}
<div className="page-header flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg hover:shadow-xl transition-all duration-300">
<Wrench className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-gray-900 to-purple-600 bg-clip-text text-transparent">便</h1>
<p className="text-gray-600 text-lg"></p>
</div>
</div>
{/* 视图切换 */}
<div className="flex items-center gap-2">
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'grid'
? 'bg-primary-100 text-primary-600'
: 'text-gray-400 hover:text-gray-600'
}`}
>
<Grid className="w-5 h-5" />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded-lg transition-colors ${
viewMode === 'list'
? 'bg-primary-100 text-primary-600'
: 'text-gray-400 hover:text-gray-600'
}`}
>
<List className="w-5 h-5" />
</button>
</div>
</div>
{/* 搜索和筛选区域 */}
<div className="card p-6 animate-fade-in">
<div className="flex flex-col lg:flex-row gap-4">
{/* 搜索框 */}
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="搜索工具名称、描述或标签..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all duration-200 bg-gray-50 hover:bg-white"
/>
</div>
</div>
{/* 分类筛选 */}
<div className="flex flex-wrap gap-2">
<button
onClick={() => setSelectedCategory(null)}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 ${
selectedCategory === null
? 'bg-primary-500 text-white shadow-md hover:bg-primary-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:shadow-sm'
}`}
>
</button>
{TOOL_CATEGORIES.map((category) => (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 ${
selectedCategory === category.id
? 'bg-primary-500 text-white shadow-md hover:bg-primary-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:shadow-sm'
}`}
>
{category.name}
</button>
))}
</div>
{/* 快速筛选 */}
<div className="flex gap-2">
<button
onClick={() => setShowNewOnly(!showNewOnly)}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 ${
showNewOnly
? 'bg-green-500 text-white shadow-md hover:bg-green-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:shadow-sm'
}`}
>
<Sparkles className="w-4 h-4" />
</button>
<button
onClick={() => setShowPopularOnly(!showPopularOnly)}
className={`flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200 ${
showPopularOnly
? 'bg-yellow-500 text-white shadow-md hover:bg-yellow-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 hover:shadow-sm'
}`}
>
<Star className="w-4 h-4" />
</button>
</div>
</div>
{/* 筛选结果统计 */}
<div className="mt-6 pt-4 border-t border-gray-100 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-900"> {filteredTools.length} </span>
{filteredTools.length !== TOOLS_DATA.length && (
<span className="text-xs text-gray-500"> {TOOLS_DATA.length} </span>
)}
</div>
{(searchQuery || selectedCategory || showNewOnly || showPopularOnly) && (
<button
onClick={clearFilters}
className="px-3 py-1.5 text-sm font-medium text-primary-600 hover:text-primary-700 hover:bg-primary-50 rounded-lg transition-all duration-200"
>
</button>
)}
</div>
</div>
{/* 工具卡片列表 */}
<div className="animate-fade-in">
<CardGrid
items={filteredTools}
renderCard={(tool) => (
<ToolCard
key={tool.id}
tool={tool}
onClick={handleToolClick}
config={{
showStatus: true,
showCategory: true,
showTags: false,
showVersion: true,
size: 'medium',
enableHover: true
}}
/>
)}
loading={false}
searchable={false}
sortable={false}
viewModes={[viewMode]}
defaultViewMode={viewMode}
gridCols={{
sm: 1,
md: 2,
lg: 3,
xl: 3,
'2xl': 4
}}
gap={6}
emptyText="没有找到匹配的工具"
emptyComponent={
<div className="text-center py-16">
<div className="w-24 h-24 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center mx-auto mb-6">
<Wrench className="w-12 h-12 text-gray-400" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-3"></h3>
<p className="text-gray-600 mb-6 max-w-md mx-auto"></p>
<button
onClick={clearFilters}
className="px-6 py-3 bg-primary-600 text-white rounded-xl hover:bg-primary-700 transition-all duration-200 shadow-md hover:shadow-lg font-medium"
>
</button>
</div>
}
/>
</div>
</div>
);
};
export default Tools;