"use client" import { useState, useCallback } from "react" import { Plus, Trash2, ChevronRight, ChevronDown } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Switch } from "@/components/ui/switch" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" // 生成唯一 ID function generateId(): string { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID() } return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 11)}` } type ValueType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null' interface TreeNode { id: string key: string value: unknown type: ValueType expanded?: boolean children?: TreeNode[] } interface NestedKeyValueEditorProps { value: Record onChange: (value: Record) => void placeholder?: string } // 推断值的类型 function inferType(value: unknown): ValueType { if (value === null) return 'null' if (Array.isArray(value)) return 'array' if (typeof value === 'object') return 'object' if (typeof value === 'boolean') return 'boolean' if (typeof value === 'number') return 'number' return 'string' } // 将 Record 转换为树节点数组 function recordToTree(record: Record): TreeNode[] { return Object.entries(record).map(([key, value]) => { const type = inferType(value) const node: TreeNode = { id: generateId(), key, value, type, expanded: true, } if (type === 'object' && value && typeof value === 'object') { node.children = recordToTree(value as Record) } else if (type === 'array' && Array.isArray(value)) { node.children = value.map((item, index) => { const itemType = inferType(item) const childNode: TreeNode = { id: generateId(), key: String(index), value: item, type: itemType, expanded: true, } if (itemType === 'object' && item && typeof item === 'object') { childNode.children = recordToTree(item as Record) } else if (itemType === 'array' && Array.isArray(item)) { childNode.children = item.map((subItem, subIndex) => ({ id: generateId(), key: String(subIndex), value: subItem, type: inferType(subItem), expanded: true, })) } return childNode }) } return node }) } // 将树节点数组转换为 Record function treeToRecord(nodes: TreeNode[]): Record { const record: Record = {} for (const node of nodes) { if (!node.key.trim()) continue if (node.type === 'object' && node.children) { record[node.key] = treeToRecord(node.children) } else if (node.type === 'array' && node.children) { record[node.key] = node.children.map(child => { if (child.type === 'object' && child.children) { return treeToRecord(child.children) } else if (child.type === 'array' && child.children) { return child.children.map(c => c.value) } return child.value }) } else if (node.type === 'null') { record[node.key] = null } else { record[node.key] = node.value } } return record } // 转换简单值 function convertSimpleValue(value: string, type: ValueType): unknown { switch (type) { case 'boolean': return value === 'true' case 'number': { const num = parseFloat(value) return isNaN(num) ? 0 : num } case 'null': return null default: return value } } // 树节点组件 function TreeNodeItem({ node, level, onUpdate, onRemove, onAddChild, onToggleExpand, }: { node: TreeNode level: number onUpdate: (id: string, field: 'key' | 'value' | 'type', value: unknown) => void onRemove: (id: string) => void onAddChild: (parentId: string) => void onToggleExpand: (id: string) => void }) { const isContainer = node.type === 'object' || node.type === 'array' const hasChildren = node.children && node.children.length > 0 return (
{/* 展开/折叠按钮 */} {/* 键名 */} onUpdate(node.id, 'key', e.target.value)} placeholder="key" className="h-8 text-sm" /> {/* 值(仅简单类型显示) */} {!isContainer && ( <> {node.type === 'boolean' ? (
onUpdate(node.id, 'value', checked)} /> {node.value ? 'true' : 'false'}
) : node.type === 'null' ? (
null
) : ( onUpdate(node.id, 'value', e.target.value)} placeholder="value" className="h-8 text-sm" step={node.type === 'number' ? 'any' : undefined} /> )} )} {/* 类型选择 */} {/* 操作按钮 */}
{isContainer && ( )}
{/* 子节点 */} {isContainer && node.expanded && node.children && node.children.length > 0 && (
{node.children.map((child) => ( ))}
)}
) } export function NestedKeyValueEditor({ value, onChange, placeholder = "添加参数...", }: NestedKeyValueEditorProps) { const [nodes, setNodes] = useState(() => recordToTree(value || {})) // 同步到父组件 const syncToParent = useCallback( (newNodes: TreeNode[]) => { setNodes(newNodes) onChange(treeToRecord(newNodes)) }, [onChange] ) // 添加根节点 const addRootNode = useCallback(() => { const newNode: TreeNode = { id: generateId(), key: '', value: '', type: 'string', expanded: false, } syncToParent([...nodes, newNode]) }, [nodes, syncToParent]) // 更新节点 const updateNode = useCallback( (id: string, field: 'key' | 'value' | 'type', newValue: unknown) => { const updateRecursive = (nodes: TreeNode[]): TreeNode[] => { return nodes.map((node) => { if (node.id === id) { if (field === 'type') { const newType = newValue as ValueType if (newType === 'object') { return { ...node, type: newType, value: {}, children: [] } } else if (newType === 'array') { return { ...node, type: newType, value: [], children: [] } } else if (newType === 'null') { return { ...node, type: newType, value: null } } else { const converted = convertSimpleValue(String(node.value), newType) return { ...node, type: newType, value: converted, children: undefined } } } else if (field === 'value') { const converted = convertSimpleValue(String(newValue), node.type) return { ...node, value: converted } } else { return { ...node, [field]: String(newValue) } } } if (node.children) { return { ...node, children: updateRecursive(node.children) } } return node }) } syncToParent(updateRecursive(nodes)) }, [nodes, syncToParent] ) // 删除节点 const removeNode = useCallback( (id: string) => { const removeRecursive = (nodes: TreeNode[]): TreeNode[] => { return nodes .filter((node) => node.id !== id) .map((node) => { if (node.children) { return { ...node, children: removeRecursive(node.children) } } return node }) } syncToParent(removeRecursive(nodes)) }, [nodes, syncToParent] ) // 添加子节点 const addChildNode = useCallback( (parentId: string) => { const addRecursive = (nodes: TreeNode[]): TreeNode[] => { return nodes.map((node) => { if (node.id === parentId) { const newChild: TreeNode = { id: generateId(), key: node.type === 'array' ? String(node.children?.length || 0) : '', value: '', type: 'string', expanded: true, } return { ...node, children: [...(node.children || []), newChild], } } if (node.children) { return { ...node, children: addRecursive(node.children) } } return node }) } syncToParent(addRecursive(nodes)) }, [nodes, syncToParent] ) // 切换展开/折叠 const toggleExpand = useCallback( (id: string) => { const toggleRecursive = (nodes: TreeNode[]): TreeNode[] => { return nodes.map((node) => { if (node.id === id) { return { ...node, expanded: !node.expanded } } if (node.children) { return { ...node, children: toggleRecursive(node.children) } } return node }) } setNodes(toggleRecursive(nodes)) }, [nodes] ) return (
{/* 顶部工具栏 */}
{nodes.length} 个参数
{/* 内容区域 */}
{nodes.length === 0 ? (
{placeholder}
) : (
{/* 表头 */}
键名 类型
{nodes.map((node) => ( ))}
)}
) }