mixvideo-v2/apps/desktop/src/components/ModelSearch.tsx

250 lines
9.3 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 from 'react';
import { ModelStatus, Gender, ModelSortBy, SortOrder } from '../types/model';
import { MagnifyingGlassIcon, FunnelIcon, ArrowUpIcon, ArrowDownIcon } from '@heroicons/react/24/outline';
import { CustomSelect } from './CustomSelect'
interface ModelSearchProps {
searchQuery: string;
onSearchChange: (query: string) => void;
statusFilter: ModelStatus | 'all';
onStatusFilterChange: (status: ModelStatus | 'all') => void;
genderFilter: Gender | 'all';
onGenderFilterChange: (gender: Gender | 'all') => void;
sortBy: ModelSortBy;
onSortByChange: (sortBy: ModelSortBy) => void;
sortOrder: SortOrder;
onSortOrderChange: (order: SortOrder) => void;
}
const ModelSearch: React.FC<ModelSearchProps> = ({
searchQuery,
onSearchChange,
statusFilter,
onStatusFilterChange,
genderFilter,
onGenderFilterChange,
sortBy,
onSortByChange,
sortOrder,
onSortOrderChange
}) => {
const getStatusText = (status: ModelStatus | 'all') => {
switch (status) {
case 'all':
return '全部状态';
case ModelStatus.Active:
return '活跃';
case ModelStatus.Inactive:
return '不活跃';
case ModelStatus.Retired:
return '退役';
case ModelStatus.Suspended:
return '暂停';
default:
return '未知';
}
};
const getGenderText = (gender: Gender | 'all') => {
switch (gender) {
case 'all':
return '全部性别';
case Gender.Male:
return '男';
case Gender.Female:
return '女';
case Gender.Other:
return '其他';
default:
return '未知';
}
};
const getSortByText = (sortBy: ModelSortBy) => {
switch (sortBy) {
case ModelSortBy.Name:
return '姓名';
case ModelSortBy.CreatedAt:
return '创建时间';
case ModelSortBy.UpdatedAt:
return '更新时间';
case ModelSortBy.Rating:
return '评分';
case ModelSortBy.Age:
return '年龄';
case ModelSortBy.Height:
return '身高';
default:
return '创建时间';
}
};
return (
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 animate-fade-in">
<div className="flex flex-col lg:flex-row gap-4">
{/* 搜索框 */}
<div className="flex-1">
<div className="relative">
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="搜索模特姓名、艺名或标签..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="w-full pl-10 pr-3 py-2 border border-gray-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 text-sm text-gray-900 hover:border-gray-300"
/>
</div>
</div>
{/* 过滤器 */}
<div className="flex flex-wrap gap-3">
{/* 状态过滤 */}
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5 text-xs font-medium text-gray-700">
<FunnelIcon className="h-3.5 w-3.5 text-gray-500" />
</div>
<CustomSelect
value={statusFilter}
onChange={(value) => onStatusFilterChange(value as ModelStatus | 'all')}
options={[
{ value: 'all', label: '全部状态' },
{ value: ModelStatus.Active, label: '活跃' },
{ value: ModelStatus.Inactive, label: '不活跃' },
{ value: ModelStatus.Retired, label: '退役' },
{ value: ModelStatus.Suspended, label: '暂停' }
]}
className="min-w-[100px]"
/>
</div>
{/* 性别过滤 */}
<div className="flex items-center gap-2">
<div className="text-xs font-medium text-gray-700"></div>
<CustomSelect
value={genderFilter}
onChange={(value) => onGenderFilterChange(value as Gender | 'all')}
options={[
{ value: 'all', label: '全部性别' },
{ value: Gender.Male, label: '男' },
{ value: Gender.Female, label: '女' },
{ value: Gender.Other, label: '其他' }
]}
className="min-w-[80px]"
/>
</div>
{/* 排序选择 */}
<div className="flex items-center gap-2">
<div className="text-xs font-medium text-gray-700"></div>
<div className="flex items-center gap-1.5">
<CustomSelect
value={sortBy}
onChange={(value) => onSortByChange(value as ModelSortBy)}
options={[
{ value: ModelSortBy.CreatedAt, label: '创建时间' },
{ value: ModelSortBy.UpdatedAt, label: '更新时间' },
{ value: ModelSortBy.Name, label: '姓名' },
{ value: ModelSortBy.Rating, label: '评分' },
{ value: ModelSortBy.Age, label: '年龄' },
{ value: ModelSortBy.Height, label: '身高' }
]}
className="min-w-[100px]"
/>
{/* 排序方向 */}
<button
onClick={() => onSortOrderChange(sortOrder === SortOrder.Asc ? SortOrder.Desc : SortOrder.Asc)}
className="flex items-center justify-center w-8 h-8 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200"
title={sortOrder === SortOrder.Asc ? '当前升序,点击切换为降序' : '当前降序,点击切换为升序'}
>
{sortOrder === SortOrder.Asc ? (
<ArrowUpIcon className="h-4 w-4 text-gray-600" />
) : (
<ArrowDownIcon className="h-4 w-4 text-gray-600" />
)}
</button>
</div>
</div>
</div>
</div>
{/* 活动过滤器显示 */}
{(searchQuery || statusFilter !== 'all' || genderFilter !== 'all') && (
<div className="mt-4 pt-3 border-t border-gray-100">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-medium text-gray-700">:</span>
{searchQuery && (
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-primary-50 text-primary-700 text-xs rounded-md border border-primary-200">
<MagnifyingGlassIcon className="h-3 w-3" />
: {searchQuery}
<button
onClick={() => onSearchChange('')}
className="ml-0.5 text-primary-600 hover:text-primary-800 transition-colors"
title="清除搜索"
>
×
</button>
</span>
)}
{statusFilter !== 'all' && (
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-green-50 text-green-700 text-xs rounded-md border border-green-200">
<FunnelIcon className="h-3 w-3" />
: {getStatusText(statusFilter)}
<button
onClick={() => onStatusFilterChange('all')}
className="ml-0.5 text-green-600 hover:text-green-800 transition-colors"
title="清除状态筛选"
>
×
</button>
</span>
)}
{genderFilter !== 'all' && (
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-purple-50 text-purple-700 text-xs rounded-md border border-purple-200">
: {getGenderText(genderFilter)}
<button
onClick={() => onGenderFilterChange('all')}
className="ml-0.5 text-purple-600 hover:text-purple-800 transition-colors"
title="清除性别筛选"
>
×
</button>
</span>
)}
{/* 清除所有过滤器 */}
<button
onClick={() => {
onSearchChange('');
onStatusFilterChange('all');
onGenderFilterChange('all');
}}
className="inline-flex items-center gap-1.5 px-3 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-200 rounded-md hover:bg-gray-50 hover:border-gray-300 transition-all duration-200"
>
</button>
</div>
</div>
)}
{/* 排序信息显示 */}
<div className="mt-3 pt-2 border-t border-gray-100">
<div className="flex items-center gap-1.5 text-xs text-gray-500">
<span>:</span>
<span className="font-medium text-gray-700">
{getSortByText(sortBy)} {sortOrder === SortOrder.Asc ? '升序' : '降序'}
</span>
</div>
</div>
</div>
);
};
export default ModelSearch;