feat: 字段列表优化 - 输入限制、选中样式、取消选中

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