import * as React from 'react' import * as LucideIcons from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Separator } from '@/components/ui/separator' import { fieldHooks, type FieldHookRegistry } from '@/lib/field-hooks' import type { ConfigSchema, FieldSchema } from '@/types/config-schema' import { DynamicField } from './DynamicField' export interface DynamicConfigFormProps { schema: ConfigSchema values: Record onChange: (field: string, value: unknown) => void basePath?: string hooks?: FieldHookRegistry /** 嵌套层级:0 = tab 内容层,1 = section 内容层,2+ = 更深嵌套 */ level?: number advancedVisible?: boolean } function buildFieldPath(basePath: string, fieldName: string) { return basePath ? `${basePath}.${fieldName}` : fieldName } function hasTopLevelAdvancedFields(schema: ConfigSchema) { return schema.fields.some((field) => field.advanced && !schema.nested?.[field.name]) } function resolveSectionTitle(schema: ConfigSchema) { return schema.uiLabel || schema.classDoc || schema.className } function resolveSectionDescription(schema: ConfigSchema, sectionTitle: string) { return schema.classDoc && schema.classDoc !== sectionTitle ? schema.classDoc : undefined } function SectionIcon({ iconName }: { iconName?: string }) { if (!iconName) return null const IconComponent = LucideIcons[iconName as keyof typeof LucideIcons] as | React.ComponentType<{ className?: string }> | undefined if (!IconComponent) return null return } function AdvancedSettingsButton({ active, onClick, }: { active: boolean onClick: () => void }) { return ( ) } function DynamicConfigSection({ basePath, hooks, level, nestedSchema, onChange, sectionDescription, sectionKey, sectionTitle, values, }: { basePath: string hooks: FieldHookRegistry level: number nestedSchema: ConfigSchema onChange: (field: string, value: unknown) => void sectionDescription?: string sectionKey: string sectionTitle: string values: Record }) { const [advancedVisible, setAdvancedVisible] = React.useState(false) const hasAdvanced = hasTopLevelAdvancedFields(nestedSchema) return (
{sectionTitle}
{sectionDescription && ( {sectionDescription} )}
{hasAdvanced && ( setAdvancedVisible((current) => !current)} /> )}
onChange(`${sectionKey}.${field}`, value)} basePath={basePath} hooks={hooks} level={level} advancedVisible={hasAdvanced ? advancedVisible : undefined} />
) } /** * DynamicConfigForm - 动态配置表单组件 * * 根据 ConfigSchema 渲染表单字段,支持: * 1. Hook 系统:通过 FieldHookRegistry 自定义字段渲染 * - replace 模式:完全替换默认渲染 * - wrapper 模式:包装默认渲染(通过 children 传递) * 2. 嵌套 schema:递归渲染 schema.nested 中的子配置 * 3. 高级设置:由栏目标题右侧按钮控制显示 */ export const DynamicConfigForm: React.FC = ({ schema, values, onChange, basePath = '', hooks = fieldHooks, level = 0, advancedVisible, }) => { const [localAdvancedVisible, setLocalAdvancedVisible] = React.useState(false) const resolvedAdvancedVisible = advancedVisible ?? localAdvancedVisible const fieldMap = React.useMemo( () => new Map(schema.fields.map((field) => [field.name, field])), [schema.fields], ) const renderField = (field: FieldSchema) => { const fieldPath = buildFieldPath(basePath, field.name) if (hooks.has(fieldPath)) { const hookEntry = hooks.get(fieldPath) if (!hookEntry) return null const HookComponent = hookEntry.component if (hookEntry.type === 'replace') { return ( onChange(field.name, v)} schema={field} /> ) } return ( onChange(field.name, v)} schema={field} > onChange(field.name, v)} fieldPath={fieldPath} /> ) } return ( onChange(field.name, v)} fieldPath={fieldPath} /> ) } const topLevelFields = schema.fields.filter( (field) => !schema.nested?.[field.name], ) const normalFields = topLevelFields.filter((field) => !field.advanced) const advancedFields = topLevelFields.filter((field) => field.advanced) const visibleFields = resolvedAdvancedVisible ? [...normalFields, ...advancedFields] : normalFields const renderFieldList = (fields: FieldSchema[]) => ( <> {fields.map((field, index) => ( {index > 0 && }
{renderField(field)}
))} ) return (
{topLevelFields.length > 0 && (
{advancedVisible === undefined && advancedFields.length > 0 && (
setLocalAdvancedVisible((current) => !current)} />
)} {renderFieldList(visibleFields)}
)} {schema.nested && Object.entries(schema.nested) .map(([key, nestedSchema]) => { const nestedField = fieldMap.get(key) const nestedFieldPath = buildFieldPath(basePath, key) if (hooks.has(nestedFieldPath)) { const hookEntry = hooks.get(nestedFieldPath) if (!hookEntry) return null const HookComponent = hookEntry.component if (hookEntry.type === 'replace') { return (
onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} />
) } return (
onChange(key, v)} schema={nestedField ?? nestedSchema} nestedSchema={nestedSchema} > ) || {}} onChange={(field, value) => onChange(`${key}.${field}`, value)} basePath={nestedFieldPath} hooks={hooks} level={level + 1} />
) } const sectionTitle = resolveSectionTitle(nestedSchema) const sectionDescription = resolveSectionDescription(nestedSchema, sectionTitle) if (level === 0) { return ( ) || {}} onChange={onChange} basePath={nestedFieldPath} hooks={hooks} level={level + 1} sectionKey={key} sectionTitle={sectionTitle} sectionDescription={sectionDescription} /> ) } return (
{sectionTitle}
{sectionDescription && ( {sectionDescription} )}
) || {}} onChange={(field, value) => onChange(`${key}.${field}`, value)} basePath={nestedFieldPath} hooks={hooks} level={level + 1} />
) })}
) }