From 449e2f470c1c93213f3268181019dedf4e1263a2 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Tue, 17 Feb 2026 17:20:50 +0800 Subject: [PATCH] feat(dashboard): create DynamicConfigForm component with Hook integration - Accept schema, values, onChange, hooks props - Check for field hooks, use if present (replace/wrapper modes) - Fall back to DynamicField for non-hooked fields - Support nested schema rendering with recursive calls - Export DynamicConfigForm and DynamicField from index Groups with Task 5. Completes Task 6 of webui-config-visualization-refactor plan. --- .../dynamic-form/DynamicConfigForm.tsx | 114 ++++++++++++++++++ .../src/components/dynamic-form/index.ts | 2 + 2 files changed, 116 insertions(+) create mode 100644 dashboard/src/components/dynamic-form/DynamicConfigForm.tsx create mode 100644 dashboard/src/components/dynamic-form/index.ts diff --git a/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx new file mode 100644 index 00000000..cd04b78f --- /dev/null +++ b/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx @@ -0,0 +1,114 @@ +import * as React from 'react' + +import type { ConfigSchema, FieldSchema } from '@/types/config-schema' +import { fieldHooks, type FieldHookRegistry } from '@/lib/field-hooks' + +import { DynamicField } from './DynamicField' + +export interface DynamicConfigFormProps { + schema: ConfigSchema + values: Record + onChange: (field: string, value: unknown) => void + hooks?: FieldHookRegistry +} + +/** + * DynamicConfigForm - 动态配置表单组件 + * + * 根据 ConfigSchema 渲染表单字段,支持: + * 1. Hook 系统:通过 FieldHookRegistry 自定义字段渲染 + * - replace 模式:完全替换默认渲染 + * - wrapper 模式:包装默认渲染(通过 children 传递) + * 2. 嵌套 schema:递归渲染 schema.nested 中的子配置 + * 3. 默认渲染:使用 DynamicField 组件 + */ +export const DynamicConfigForm: React.FC = ({ + schema, + values, + onChange, + hooks = fieldHooks, // 默认使用全局单例 +}) => { + /** + * 渲染单个字段 + * 检查是否有注册的 Hook,根据 Hook 类型选择渲染方式 + */ + const renderField = (field: FieldSchema) => { + const fieldPath = field.name + + // 检查是否有注册的 Hook + if (hooks.has(fieldPath)) { + const hookEntry = hooks.get(fieldPath) + if (!hookEntry) return null // Type guard(理论上不会发生) + + const HookComponent = hookEntry.component + + if (hookEntry.type === 'replace') { + // replace 模式:完全替换默认渲染 + return ( + onChange(field.name, v)} + /> + ) + } else { + // wrapper 模式:包装默认渲染 + return ( + onChange(field.name, v)} + > + onChange(field.name, v)} + fieldPath={fieldPath} + /> + + ) + } + } + + // 无 Hook,使用默认渲染 + return ( + onChange(field.name, v)} + fieldPath={fieldPath} + /> + ) + } + + return ( +
+ {/* 渲染顶层字段 */} + {schema.fields.map((field) => ( +
{renderField(field)}
+ ))} + + {/* 渲染嵌套 schema */} + {schema.nested && + Object.entries(schema.nested).map(([key, nestedSchema]) => ( +
+ {/* 嵌套 schema 标题 */} +
+

{nestedSchema.className}

+ {nestedSchema.classDoc && ( +

{nestedSchema.classDoc}

+ )} +
+ + {/* 递归渲染嵌套表单 */} + ) || {}} + onChange={(field, value) => onChange(`${key}.${field}`, value)} + hooks={hooks} + /> +
+ ))} +
+ ) +} diff --git a/dashboard/src/components/dynamic-form/index.ts b/dashboard/src/components/dynamic-form/index.ts new file mode 100644 index 00000000..aba05893 --- /dev/null +++ b/dashboard/src/components/dynamic-form/index.ts @@ -0,0 +1,2 @@ +export { DynamicConfigForm } from './DynamicConfigForm' +export { DynamicField } from './DynamicField'