feat: 简化字段卡片交互 - 点击直接打开编辑弹窗
- 移除字段卡片选中样式 - 点击字段直接打开编辑弹窗 - 简化 FieldItem 组件,移除 isSelected 属性 - 简化 FieldList 接口,onSelect 改为 onEdit fix: 优化字段编辑弹窗 - 去除弹窗描述文字 - 字段名称增加字符计数 (x/20) - 字段描述增加字符计数 (x/50) - 保持 placeholder 提示 fix: 修复弹窗顶部间距问题 - DialogContent 添加 pt-6 避开关闭按钮 - 内容区域移除顶部 padding,改为 pb-6 fix: 增加弹窗顶部间距 (pt-6 → pt-8) fix: 优化弹窗尺寸和按钮间距 - 弹窗最大宽度 360→420px 适配标签页 - 增加按钮 padding (px-2→px-3, py-1→py-1.5) - 添加按钮增加 padding (h-8→h-9, px-3)
This commit is contained in:
@@ -73,9 +73,12 @@ export default function FieldEditor({ field, onChange }: FieldEditorProps) {
|
|||||||
{/* 基础配置 */}
|
{/* 基础配置 */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<div className="flex items-center justify-between">
|
||||||
{t('testDataGenerator_fieldName')}
|
<label className="text-sm font-medium text-foreground">
|
||||||
</label>
|
{t('testDataGenerator_fieldName')}
|
||||||
|
</label>
|
||||||
|
<span className="text-xs text-muted-foreground">{field.name.length}/20</span>
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
value={field.name}
|
value={field.name}
|
||||||
onChange={(e) => handleNameChange(e.target.value)}
|
onChange={(e) => handleNameChange(e.target.value)}
|
||||||
@@ -86,9 +89,14 @@ export default function FieldEditor({ field, onChange }: FieldEditorProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<div className="flex items-center justify-between">
|
||||||
{t('testDataGenerator_fieldDescription')}
|
<label className="text-sm font-medium text-foreground">
|
||||||
</label>
|
{t('testDataGenerator_fieldDescription')}
|
||||||
|
</label>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{(field.description || '').length}/50
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
value={field.description || ''}
|
value={field.description || ''}
|
||||||
onChange={(e) => handleDescriptionChange(e.target.value)}
|
onChange={(e) => handleDescriptionChange(e.target.value)}
|
||||||
@@ -132,7 +140,7 @@ export default function FieldEditor({ field, onChange }: FieldEditorProps) {
|
|||||||
<button
|
<button
|
||||||
key={preset.value}
|
key={preset.value}
|
||||||
onClick={() => handleNullRateChange(preset.value)}
|
onClick={() => handleNullRateChange(preset.value)}
|
||||||
className={`px-2 py-1 text-xs rounded-md transition-colors ${
|
className={`px-3 py-1.5 text-xs rounded-md transition-colors ${
|
||||||
field.nullRate === preset.value
|
field.nullRate === preset.value
|
||||||
? 'bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'bg-muted text-muted-foreground hover:bg-muted/80'
|
: 'bg-muted text-muted-foreground hover:bg-muted/80'
|
||||||
|
|||||||
@@ -11,20 +11,15 @@ import type { FieldConfig } from '@/types/testDataGenerator';
|
|||||||
interface FieldItemProps {
|
interface FieldItemProps {
|
||||||
field: FieldConfig;
|
field: FieldConfig;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
isSelected: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FieldItem({ field, onClick, isSelected }: FieldItemProps) {
|
export default function FieldItem({ field, onClick }: FieldItemProps) {
|
||||||
const { t } = useI18n('testDataGenerator');
|
const { t } = useI18n('testDataGenerator');
|
||||||
const generator = getGeneratorById(field.generatorId);
|
const generator = getGeneratorById(field.generatorId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex-1 min-w-0 cursor-pointer rounded-md transition-all ${
|
className="flex-1 min-w-0 cursor-pointer rounded-md transition-all 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
|
<div
|
||||||
|
|||||||
@@ -44,21 +44,18 @@ interface FieldListProps {
|
|||||||
onUpdate: (index: number, field: FieldConfig) => void;
|
onUpdate: (index: number, field: FieldConfig) => void;
|
||||||
onRemove: (index: number) => void;
|
onRemove: (index: number) => void;
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
onSelect: (index: number) => void;
|
onEdit: (index: number) => void;
|
||||||
selectedIndex: number | null;
|
|
||||||
onReorder: (oldIndex: number, newIndex: number) => void;
|
onReorder: (oldIndex: number, newIndex: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 可排序的字段项 */
|
/** 可排序的字段项 */
|
||||||
function SortableFieldItem({
|
function SortableFieldItem({
|
||||||
field,
|
field,
|
||||||
isSelected,
|
onEdit,
|
||||||
onSelect,
|
|
||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
field: FieldConfig;
|
field: FieldConfig;
|
||||||
isSelected: boolean;
|
onEdit: () => void;
|
||||||
onSelect: () => void;
|
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
@@ -78,10 +75,8 @@ function SortableFieldItem({
|
|||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={`group relative rounded-lg border transition-all ${
|
className={`group relative rounded-lg border transition-all ${
|
||||||
isDragging ? 'border-primary shadow-lg' : ''
|
isDragging
|
||||||
} ${
|
? 'border-primary shadow-lg'
|
||||||
isSelected
|
|
||||||
? 'border-primary bg-primary/8 shadow-sm ring-1 ring-primary/20'
|
|
||||||
: 'border-border hover:border-muted-foreground/30 hover:shadow-sm'
|
: 'border-border hover:border-muted-foreground/30 hover:shadow-sm'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -96,7 +91,7 @@ function SortableFieldItem({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<FieldItem field={field} onClick={onSelect} isSelected={isSelected} />
|
<FieldItem field={field} onClick={onEdit} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -117,8 +112,7 @@ export default function FieldList({
|
|||||||
onUpdate: _onUpdate,
|
onUpdate: _onUpdate,
|
||||||
onRemove,
|
onRemove,
|
||||||
onAdd,
|
onAdd,
|
||||||
onSelect,
|
onEdit,
|
||||||
selectedIndex,
|
|
||||||
onReorder,
|
onReorder,
|
||||||
}: FieldListProps) {
|
}: FieldListProps) {
|
||||||
const { t } = useI18n('testDataGenerator');
|
const { t } = useI18n('testDataGenerator');
|
||||||
@@ -135,14 +129,6 @@ export default function FieldList({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 点击已选中的字段时取消选中
|
|
||||||
const handleToggleSelect = useCallback(
|
|
||||||
(index: number) => {
|
|
||||||
onSelect(selectedIndex === index ? -1 : index);
|
|
||||||
},
|
|
||||||
[selectedIndex, onSelect],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDragStart = (event: DragStartEvent) => {
|
const handleDragStart = (event: DragStartEvent) => {
|
||||||
setActiveId(String(event.active.id));
|
setActiveId(String(event.active.id));
|
||||||
};
|
};
|
||||||
@@ -230,7 +216,7 @@ export default function FieldList({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
disabled={isMaxReached}
|
disabled={isMaxReached}
|
||||||
className="h-8 gap-1.5"
|
className="h-9 gap-1.5 px-3"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
{t('testDataGenerator_addField')}
|
{t('testDataGenerator_addField')}
|
||||||
@@ -284,8 +270,7 @@ export default function FieldList({
|
|||||||
>
|
>
|
||||||
<SortableFieldItem
|
<SortableFieldItem
|
||||||
field={field}
|
field={field}
|
||||||
isSelected={selectedIndex === realIndex}
|
onEdit={() => onEdit(realIndex)}
|
||||||
onSelect={() => handleToggleSelect(realIndex)}
|
|
||||||
onRemove={() => onRemove(realIndex)}
|
onRemove={() => onRemove(realIndex)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,8 +285,7 @@ export default function FieldList({
|
|||||||
<SortableFieldItem
|
<SortableFieldItem
|
||||||
key={field.id}
|
key={field.id}
|
||||||
field={field}
|
field={field}
|
||||||
isSelected={selectedIndex === index}
|
onEdit={() => onEdit(index)}
|
||||||
onSelect={() => handleToggleSelect(index)}
|
|
||||||
onRemove={() => onRemove(index)}
|
onRemove={() => onRemove(index)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -317,7 +301,7 @@ export default function FieldList({
|
|||||||
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<FieldItem field={activeField} isSelected={false} onClick={() => {}} />
|
<FieldItem field={activeField} onClick={() => {}} />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 w-8 shrink-0" />
|
<div className="h-8 w-8 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useGenerator } from './hooks/useGenerator';
|
import { useGenerator } from './hooks/useGenerator';
|
||||||
import { getGeneratorById } from '@/lib/generators';
|
import { getGeneratorById } from '@/lib/generators';
|
||||||
import type { FieldConfig, GenerateResult } from '@/types/testDataGenerator';
|
import type { FieldConfig, GenerateResult } from '@/types/testDataGenerator';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogDescription } from '@/components/ui/dialog';
|
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||||
|
|
||||||
// 生成唯一 ID 的辅助函数
|
// 生成唯一 ID 的辅助函数
|
||||||
function generateId(): string {
|
function generateId(): string {
|
||||||
@@ -227,8 +227,7 @@ export default function TestDataGeneratorPage() {
|
|||||||
onUpdate={handleUpdateField}
|
onUpdate={handleUpdateField}
|
||||||
onRemove={handleRemoveField}
|
onRemove={handleRemoveField}
|
||||||
onAdd={handleAddField}
|
onAdd={handleAddField}
|
||||||
onSelect={handleOpenEditor}
|
onEdit={handleOpenEditor}
|
||||||
selectedIndex={selectedIndex}
|
|
||||||
onReorder={handleReorder}
|
onReorder={handleReorder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -303,11 +302,8 @@ export default function TestDataGeneratorPage() {
|
|||||||
|
|
||||||
{/* 字段编辑弹窗 */}
|
{/* 字段编辑弹窗 */}
|
||||||
<Dialog open={isEditorOpen} onOpenChange={setIsEditorOpen}>
|
<Dialog open={isEditorOpen} onOpenChange={setIsEditorOpen}>
|
||||||
<DialogContent className="w-[calc(100vw-2rem)] max-w-[360px] max-h-[calc(100vh-4rem)] overflow-hidden p-0">
|
<DialogContent className="w-[calc(100vw-4rem)] max-w-[420px] max-h-[calc(100vh-4rem)] overflow-hidden p-0 pt-8">
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<div className="overflow-y-auto max-h-[calc(100vh-6rem)] px-6 pb-6">
|
||||||
<DialogDescription>{t('testDataGenerator_selectFieldToEdit')}</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="overflow-y-auto max-h-[calc(100vh-8rem)] px-6 pb-6">
|
|
||||||
{selectedField && selectedIndex !== null && (
|
{selectedField && selectedIndex !== null && (
|
||||||
<FieldEditor
|
<FieldEditor
|
||||||
field={selectedField}
|
field={selectedField}
|
||||||
|
|||||||
Reference in New Issue
Block a user