feat: 字段列表优化 - 输入限制、选中样式、取消选中
- FieldEditor: 字段名称maxLength=20,描述maxLength=50 - FieldItem: 统一有无描述时的垂直居中布局,选中左侧竖线+背景高亮 - FieldList: 点击已选中字段可取消选中,选中态加shadow和ring效果 - index: 支持selectedIndex=-1取消选中
This commit is contained in:
@@ -80,6 +80,7 @@ export default function FieldEditor({ field, onChange }: FieldEditorProps) {
|
|||||||
value={field.name}
|
value={field.name}
|
||||||
onChange={(e) => handleNameChange(e.target.value)}
|
onChange={(e) => handleNameChange(e.target.value)}
|
||||||
placeholder={t('testDataGenerator_fieldNamePlaceholder')}
|
placeholder={t('testDataGenerator_fieldNamePlaceholder')}
|
||||||
|
maxLength={20}
|
||||||
className="h-9"
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,6 +93,7 @@ export default function FieldEditor({ field, onChange }: FieldEditorProps) {
|
|||||||
value={field.description || ''}
|
value={field.description || ''}
|
||||||
onChange={(e) => handleDescriptionChange(e.target.value)}
|
onChange={(e) => handleDescriptionChange(e.target.value)}
|
||||||
placeholder={t('testDataGenerator_fieldDescriptionPlaceholder')}
|
placeholder={t('testDataGenerator_fieldDescriptionPlaceholder')}
|
||||||
|
maxLength={50}
|
||||||
className="h-9"
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* 字段项组件
|
* 字段项组件
|
||||||
* 展示单个字段的基本信息
|
* 展示单个字段的基本信息,适配固定高度卡片
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useI18n } from '@/utils/chromeI18n';
|
import { useI18n } from '@/utils/chromeI18n';
|
||||||
@@ -20,30 +20,45 @@ export default function FieldItem({ field, onClick, isSelected }: FieldItemProps
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex-1 cursor-pointer p-2 rounded-md transition-colors ${
|
className={`flex-1 min-w-0 cursor-pointer rounded-md transition-all ${
|
||||||
isSelected ? 'bg-primary/10' : 'hover:bg-muted/50'
|
isSelected
|
||||||
|
? 'bg-primary/10 pl-2 border-l-2 border-l-primary'
|
||||||
|
: 'hover:bg-muted/50 border-l-2 border-l-transparent'
|
||||||
}`}
|
}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div
|
||||||
<span className="font-medium text-sm text-foreground truncate">{field.name}</span>
|
className={`h-full ${field.description ? 'flex flex-col justify-center' : 'flex items-center'}`}
|
||||||
<Badge variant="secondary" className="text-xs">
|
>
|
||||||
{generator?.name || field.generatorId}
|
{/* 第一行:字段名 + 标签 */}
|
||||||
</Badge>
|
<div className="flex items-center gap-1.5 overflow-hidden">
|
||||||
{!field.required && (
|
<span className="font-medium text-sm text-foreground truncate shrink-0 max-w-[140px]">
|
||||||
<Badge variant="outline" className="text-xs text-muted-foreground">
|
{field.name}
|
||||||
{field.nullRate}%
|
</span>
|
||||||
</Badge>
|
<Badge variant="secondary" className="text-[10px] shrink-0 px-1 py-0">
|
||||||
)}
|
{generator?.name || field.generatorId}
|
||||||
{field.unique && (
|
|
||||||
<Badge variant="outline" className="text-xs text-blue-500">
|
|
||||||
{t('testDataGenerator_unique')}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{!field.required && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[10px] shrink-0 px-1 py-0 text-muted-foreground"
|
||||||
|
>
|
||||||
|
{field.nullRate}%
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{field.unique && (
|
||||||
|
<Badge variant="outline" className="text-[10px] shrink-0 px-1 py-0 text-blue-500">
|
||||||
|
{t('testDataGenerator_unique')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* 第二行:描述 */}
|
||||||
|
{field.description && (
|
||||||
|
<p className="text-[11px] text-muted-foreground mt-0.5 truncate leading-tight">
|
||||||
|
{field.description}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{field.description && (
|
|
||||||
<p className="text-xs text-muted-foreground mt-1 truncate">{field.description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ function SortableFieldItem({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={`group relative rounded-lg border transition-colors ${
|
className={`group relative rounded-lg border transition-all ${
|
||||||
isDragging ? 'border-primary shadow-lg' : ''
|
isDragging ? 'border-primary shadow-lg' : ''
|
||||||
} ${
|
} ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary bg-primary/5'
|
? 'border-primary bg-primary/8 shadow-sm ring-1 ring-primary/20'
|
||||||
: 'border-border hover:border-muted-foreground/30'
|
: 'border-border hover:border-muted-foreground/30 hover:shadow-sm'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 h-full px-3 py-2">
|
<div className="flex items-center gap-2 h-full px-3 py-2">
|
||||||
@@ -131,6 +131,14 @@ export default function FieldList({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 点击已选中的字段时取消选中
|
||||||
|
const handleToggleSelect = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
onSelect(selectedIndex === index ? -1 : index);
|
||||||
|
},
|
||||||
|
[selectedIndex, onSelect],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDragEnd = (event: DragEndEvent) => {
|
const handleDragEnd = (event: DragEndEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
if (!over || active.id === over.id) return;
|
if (!over || active.id === over.id) return;
|
||||||
@@ -232,7 +240,7 @@ export default function FieldList({
|
|||||||
<SortableFieldItem
|
<SortableFieldItem
|
||||||
field={field}
|
field={field}
|
||||||
isSelected={selectedIndex === realIndex}
|
isSelected={selectedIndex === realIndex}
|
||||||
onSelect={() => onSelect(realIndex)}
|
onSelect={() => handleToggleSelect(realIndex)}
|
||||||
onRemove={() => onRemove(realIndex)}
|
onRemove={() => onRemove(realIndex)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,7 +256,7 @@ export default function FieldList({
|
|||||||
key={field.id}
|
key={field.id}
|
||||||
field={field}
|
field={field}
|
||||||
isSelected={selectedIndex === index}
|
isSelected={selectedIndex === index}
|
||||||
onSelect={() => onSelect(index)}
|
onSelect={() => handleToggleSelect(index)}
|
||||||
onRemove={() => onRemove(index)}
|
onRemove={() => onRemove(index)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ export default function TestDataGeneratorPage() {
|
|||||||
generate(fields, count, format === 'csv');
|
generate(fields, count, format === 'csv');
|
||||||
}, [fields, count, format, generate]);
|
}, [fields, count, format, generate]);
|
||||||
|
|
||||||
// 获取选中的字段
|
// 获取选中的字段(-1 或 null 表示未选中)
|
||||||
const selectedField = selectedIndex !== null ? fields[selectedIndex] : null;
|
const selectedField = selectedIndex !== null && selectedIndex >= 0 ? fields[selectedIndex] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20">
|
<div className="min-h-screen bg-background text-foreground antialiased selection:bg-primary/20">
|
||||||
|
|||||||
Reference in New Issue
Block a user