MaiBot/dashboard/src/components/dynamic-form/DynamicConfigForm.tsx

115 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<string, unknown>
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<DynamicConfigFormProps> = ({
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 (
<HookComponent
fieldPath={fieldPath}
value={values[field.name]}
onChange={(v) => onChange(field.name, v)}
/>
)
} else {
// wrapper 模式:包装默认渲染
return (
<HookComponent
fieldPath={fieldPath}
value={values[field.name]}
onChange={(v) => onChange(field.name, v)}
>
<DynamicField
schema={field}
value={values[field.name]}
onChange={(v) => onChange(field.name, v)}
fieldPath={fieldPath}
/>
</HookComponent>
)
}
}
// 无 Hook使用默认渲染
return (
<DynamicField
schema={field}
value={values[field.name]}
onChange={(v) => onChange(field.name, v)}
fieldPath={fieldPath}
/>
)
}
return (
<div className="space-y-4">
{/* 渲染顶层字段 */}
{schema.fields.map((field) => (
<div key={field.name}>{renderField(field)}</div>
))}
{/* 渲染嵌套 schema */}
{schema.nested &&
Object.entries(schema.nested).map(([key, nestedSchema]) => (
<div key={key} className="mt-6 space-y-4">
{/* 嵌套 schema 标题 */}
<div className="border-b pb-2">
<h3 className="text-lg font-semibold">{nestedSchema.className}</h3>
{nestedSchema.classDoc && (
<p className="text-sm text-muted-foreground">{nestedSchema.classDoc}</p>
)}
</div>
{/* 递归渲染嵌套表单 */}
<DynamicConfigForm
schema={nestedSchema}
values={(values[key] as Record<string, unknown>) || {}}
onChange={(field, value) => onChange(`${key}.${field}`, value)}
hooks={hooks}
/>
</div>
))}
</div>
)
}