From 2962a9534162dc88b5c06a00c0bdc2686232654f Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Tue, 17 Feb 2026 17:14:41 +0800 Subject: [PATCH] feat(dashboard): create DynamicField renderer component - Render fields based on x-widget or type - Support slider, switch, textarea, select, custom widgets - Include label, icon, description rendering - Placeholder for unsupported types (array, object) Completes Task 5 of webui-config-visualization-refactor plan. --- .../components/dynamic-form/DynamicField.tsx | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 dashboard/src/components/dynamic-form/DynamicField.tsx diff --git a/dashboard/src/components/dynamic-form/DynamicField.tsx b/dashboard/src/components/dynamic-form/DynamicField.tsx new file mode 100644 index 00000000..de9d550c --- /dev/null +++ b/dashboard/src/components/dynamic-form/DynamicField.tsx @@ -0,0 +1,246 @@ +import * as React from "react" +import * as LucideIcons from "lucide-react" + +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Slider } from "@/components/ui/slider" +import { Switch } from "@/components/ui/switch" +import { Textarea } from "@/components/ui/textarea" +import type { FieldSchema } from "@/types/config-schema" + +export interface DynamicFieldProps { + schema: FieldSchema + value: unknown + onChange: (value: unknown) => void + // eslint-disable-next-line @typescript-eslint/no-unused-vars + fieldPath?: string // 用于 Hook 系统(未来使用) +} + +/** + * DynamicField - 根据字段类型和 x-widget 渲染对应的 shadcn/ui 组件 + * + * 渲染逻辑: + * 1. x-widget 优先:如果 schema 有 x-widget,使用对应组件 + * 2. type 回退:如果没有 x-widget,根据 type 选择默认组件 + */ +export const DynamicField: React.FC = ({ + schema, + value, + onChange, +}) => { + /** + * 渲染字段图标 + */ + const renderIcon = () => { + if (!schema['x-icon']) return null + + const IconComponent = (LucideIcons as any)[schema['x-icon']] + if (!IconComponent) return null + + return + } + + /** + * 根据 x-widget 或 type 选择并渲染对应的输入组件 + */ + const renderInputComponent = () => { + const widget = schema['x-widget'] + const type = schema.type + + // x-widget 优先 + if (widget) { + switch (widget) { + case 'slider': + return renderSlider() + case 'switch': + return renderSwitch() + case 'textarea': + return renderTextarea() + case 'select': + return renderSelect() + case 'custom': + return ( +
+ Custom field requires Hook +
+ ) + default: + // 未知的 x-widget,回退到 type + break + } + } + + // type 回退 + switch (type) { + case 'boolean': + return renderSwitch() + case 'number': + case 'integer': + return renderNumberInput() + case 'string': + return renderTextInput() + case 'select': + return renderSelect() + case 'array': + return ( +
+ Array fields not yet supported +
+ ) + case 'object': + return ( +
+ Object fields not yet supported +
+ ) + case 'textarea': + return renderTextarea() + default: + return ( +
+ Unknown field type: {type} +
+ ) + } + } + + /** + * 渲染 Switch 组件(用于 boolean 类型) + */ + const renderSwitch = () => { + const checked = Boolean(value) + return ( + onChange(checked)} + /> + ) + } + + /** + * 渲染 Slider 组件(用于 number 类型 + x-widget: slider) + */ + const renderSlider = () => { + const numValue = typeof value === 'number' ? value : (schema.default as number ?? 0) + const min = schema.minValue ?? 0 + const max = schema.maxValue ?? 100 + const step = schema.step ?? 1 + + return ( +
+ onChange(values[0])} + min={min} + max={max} + step={step} + /> +
+ {min} + {numValue} + {max} +
+
+ ) + } + + /** + * 渲染 Input[type="number"] 组件(用于 number/integer 类型) + */ + const renderNumberInput = () => { + const numValue = typeof value === 'number' ? value : (schema.default as number ?? 0) + const min = schema.minValue + const max = schema.maxValue + const step = schema.step ?? (schema.type === 'integer' ? 1 : 0.1) + + return ( + onChange(parseFloat(e.target.value) || 0)} + min={min} + max={max} + step={step} + /> + ) + } + + /** + * 渲染 Input[type="text"] 组件(用于 string 类型) + */ + const renderTextInput = () => { + const strValue = typeof value === 'string' ? value : (schema.default as string ?? '') + return ( + onChange(e.target.value)} + /> + ) + } + + /** + * 渲染 Textarea 组件(用于 textarea 类型或 x-widget: textarea) + */ + const renderTextarea = () => { + const strValue = typeof value === 'string' ? value : (schema.default as string ?? '') + return ( +