147 lines
5.7 KiB
TypeScript
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;
|