glam-web/src/pages/TryOnPage/components/ClothingCard.tsx

147 lines
5.7 KiB
TypeScript

import { BgPaddingMultiSelect, type BgPaddingMultiValue } from '@/components/block/BgPaddingMultiSelect';
import CascaderMultiSelect from '@/components/block/CascaderMultiSelect';
import { Button } from '@/components/ui/button';
import { Card, CardAction, CardContent, CardHeader } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { X } from 'lucide-react';
import React from 'react';
interface Tag {
sex: string;
category: string;
size: string;
material: string;
color: string;
}
interface TagOptions {
sexOptions: any[];
categoryOptions: any[];
sizeOptions: any[];
materialOptions: any[];
colorOptions: any[];
}
interface ClothingCardProps {
imageUrl: string;
tag: Tag;
options: TagOptions;
idx: number;
onTagChange: (level: keyof Tag, value: string) => void;
onRemove: () => void;
errors?: Partial<Record<keyof Tag, string>>;
scenesOptions?: any[];
scenes?: string[][];
onScenesChange?: (scenes: string[][]) => void;
fileName?: string;
bgMode: 'custom' | 'scene';
onChangeBgMode: (bgMode: 'custom' | 'scene') => void;
paddingList?: BgPaddingMultiValue;
onPaddingListChange?: (paddingList: BgPaddingMultiValue) => void;
}
const LABELS = ['性别', '类别', '尺寸', '材质', '颜色'];
const LEVELS: (keyof Tag)[] = ['sex', 'category', 'size', 'material', 'color'];
const ClothingCard: React.FC<ClothingCardProps> = ({
imageUrl,
tag,
options,
onTagChange,
onRemove,
errors = {},
scenesOptions = [],
scenes = [],
onScenesChange,
fileName,
bgMode,
onChangeBgMode,
paddingList,
onPaddingListChange,
}) => {
return (
<Card className='relative md:flex-row gap-6 group hover:shadow-lg transition-shadow'>
<CardHeader className='p-0'>
<CardAction>
<Button
type='button'
size='icon'
variant='destructive'
onClick={onRemove}
className='absolute -top-3 -right-2 w-6 h-6 p-0 rounded-full shadow transition-colors opacity-0 group-hover:opacity-100'
title='删除'
>
<X size={18} />
</Button>
</CardAction>
</CardHeader>
<CardContent className='flex items-center w-full gap-6'>
<div className='flex flex-col items-center'>
<img src={imageUrl} alt='预览' className='w-[200px] aspect-[9/16] object-cover rounded border max-h-72' />
{fileName && <div className='mt-2 text-xs text-muted-foreground break-all max-w-[200px]'>{fileName}</div>}
</div>
<div className='flex-1 flex flex-col gap-6'>
{LEVELS.map((level, lidx) => {
let selectOptions: any[] = [];
if (level === 'sex') selectOptions = options.sexOptions;
else if (level === 'category') selectOptions = options.categoryOptions;
else if (level === 'size') selectOptions = options.sizeOptions;
else if (level === 'material') selectOptions = options.materialOptions;
else if (level === 'color') selectOptions = options.colorOptions;
return (
<div key={level} className='w-full flex flex-row items-center gap-2'>
<label className='w-16 text-right flex-shrink-0'>{LABELS[lidx]}</label>
<div className='flex-1'>
<Select value={tag[level]} onValueChange={v => onTagChange(level, v)} disabled={selectOptions.length === 0}>
<SelectTrigger className='w-full'>
<SelectValue placeholder={`请选择${LABELS[lidx]}`} />
</SelectTrigger>
<SelectContent>
{selectOptions.map((item: any) => (
<SelectItem key={item.title} value={item.title}>
{item.title}
</SelectItem>
))}
</SelectContent>
</Select>
{errors[level] && <div className='text-destructive text-xs mt-1'>{errors[level]}</div>}
</div>
</div>
);
})}
<div className='flex-1 flex flex-col gap-6'>
{/* 场景级联多选/自定义上传切换 */}
<div className='w-full flex flex-row items-start gap-4'>
<label className='w-16 text-right flex-shrink-0'></label>
<div className='flex flex-col gap-2 min-h-40 w-full overflow-y-auto'>
<div className='flex items-center gap-2 h-6'>
<Switch
id='custom-mode-switch'
checked={bgMode === 'custom'}
onCheckedChange={() => onChangeBgMode(bgMode === 'custom' ? 'scene' : 'custom')}
/>
<Label htmlFor='custom-mode-switch'>{bgMode === 'custom' ? '自定义垫图' : '内置场景'}</Label>
</div>
<div className='w-full flex flex-row items-center gap-2'>
<div className='flex-1'>
{bgMode === 'scene' ? (
<CascaderMultiSelect options={scenesOptions} value={scenes || []} onChange={onScenesChange || (() => {})} />
) : (
<BgPaddingMultiSelect value={paddingList || []} onChange={onPaddingListChange || (() => {})} />
)}
</div>
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
};
export default ClothingCard;