feat: switch dynamic to shadcn

This commit is contained in:
Junyan Qin
2025-05-08 11:28:52 +08:00
parent 9e24e240d8
commit 4604f70a57
5 changed files with 248 additions and 55 deletions

View File

@@ -161,7 +161,7 @@ export default function BotForm({
function onEditMode() {
console.log('onEditMode', form.getValues());
}
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
@@ -347,11 +347,11 @@ export default function BotForm({
<FormLabel>/<span className="text-red-500">*</span></FormLabel>
<FormControl>
<div className="relative">
<Select
<Select
onValueChange={(value) => {
field.onChange(value);
handleAdapterSelect(value);
}}
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
@@ -376,8 +376,8 @@ export default function BotForm({
{form.watch('adapter') && (
<div className="flex items-start gap-3 p-4 rounded-lg border">
<img
src={adapterIconList[form.watch('adapter')]}
<img
src={adapterIconList[form.watch('adapter')]}
alt="adapter icon"
className="w-12 h-12"
/>
@@ -392,21 +392,41 @@ export default function BotForm({
</div>
)}
{showDynamicForm && dynamicFormConfigList.length > 0 && (
<div className="space-y-4">
<div className="text-lg font-medium"></div>
<DynamicFormComponent
itemConfigList={dynamicFormConfigList}
initialValues={form.watch('adapter_config')}
onSubmit={(values) => {
form.setValue('adapter_config', values);
}}
/>
</div>
)}
</div>
<DialogFooter>
{!initBotId && (
<Button type="submit"></Button>
)}
{initBotId && (
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
<div className="flex justify-end gap-2">
{!initBotId && (
<Button type="submit" onClick={form.handleSubmit(onDynamicFormSubmit)}></Button>
)}
{initBotId && (
<>
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
</Button>
<Button type="button" onClick={form.handleSubmit(onDynamicFormSubmit)}>
</Button>
</>
)}
<Button type="button" onClick={() => onFormCancel()}>
</Button>
)}
<Button type="button" onClick={() => onFormCancel()}>
</Button>
</DialogFooter>
</div>
</div>
</form>
</Form>
</div>

View File

@@ -154,22 +154,24 @@ export default function BotConfigPage() {
</Spin> */}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[700px] p-6">
<DialogHeader>
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
<DialogHeader className="px-6 pt-6 pb-4">
<DialogTitle>{isEditForm ? '编辑机器人' : '创建机器人'}</DialogTitle>
</DialogHeader>
<BotForm
initBotId={nowSelectedBotCard?.id}
onFormSubmit={() => {
getBotList();
setModalOpen(false);
}}
onFormCancel={() => setModalOpen(false)}
onBotDeleted={() => {
getBotList();
setModalOpen(false);
}}
/>
<div className="flex-1 overflow-y-auto px-6">
<BotForm
initBotId={nowSelectedBotCard?.id}
onFormSubmit={() => {
getBotList();
setModalOpen(false);
}}
onFormCancel={() => setModalOpen(false)}
onBotDeleted={() => {
getBotList();
setModalOpen(false);
}}
/>
</div>
</DialogContent>
</Dialog>

View File

@@ -1,21 +1,139 @@
import { IDynamicFormItemConfig } from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
import { Form, FormInstance } from 'antd';
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent';
import { useEffect } from 'react';
export default function DynamicFormComponent({
form,
itemConfigList,
onSubmit,
initialValues,
}: {
form: FormInstance<object>;
itemConfigList: IDynamicFormItemConfig[];
onSubmit?: (val: object) => unknown;
initialValues?: Record<string, any>;
}) {
// 根据 itemConfigList 动态生成 zod schema
const formSchema = z.object(
itemConfigList.reduce((acc, item) => {
let fieldSchema;
switch (item.type) {
case 'integer':
fieldSchema = z.number();
break;
case 'float':
fieldSchema = z.number();
break;
case 'boolean':
fieldSchema = z.boolean();
break;
case 'string':
fieldSchema = z.string();
break;
case 'array[string]':
fieldSchema = z.array(z.string());
break;
case 'select':
fieldSchema = z.string();
break;
default:
fieldSchema = z.string();
}
if (item.required && (fieldSchema instanceof z.ZodString || fieldSchema instanceof z.ZodArray)) {
fieldSchema = fieldSchema.min(1, { message: '此字段为必填项' });
}
return {
...acc,
[item.name]: fieldSchema,
};
}, {} as Record<string, z.ZodTypeAny>)
);
type FormValues = z.infer<typeof formSchema>;
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: itemConfigList.reduce((acc, item) => {
// 优先使用 initialValues如果没有则使用默认值
const value = initialValues?.[item.name] ?? item.default;
return {
...acc,
[item.name]: value,
};
}, {} as FormValues),
});
// 当 initialValues 变化时更新表单值
useEffect(() => {
console.log('initialValues', initialValues);
if (initialValues) {
// 合并默认值和初始值
const mergedValues = itemConfigList.reduce((acc, item) => {
acc[item.name] = initialValues[item.name] ?? item.default;
return acc;
}, {} as Record<string, any>);
Object.entries(mergedValues).forEach(([key, value]) => {
form.setValue(key as keyof FormValues, value);
});
}
}, [initialValues, form, itemConfigList]);
// 监听表单值变化
useEffect(() => {
const subscription = form.watch((value) => {
// 获取完整的表单值,确保包含所有默认值
const formValues = form.getValues();
console.log('formValues', formValues);
const finalValues = itemConfigList.reduce((acc, item) => {
acc[item.name] = formValues[item.name] ?? item.default;
return acc;
}, {} as Record<string, any>);
console.log('finalValues', finalValues);
onSubmit?.(finalValues);
});
return () => subscription.unsubscribe();
}, [form, onSubmit, itemConfigList]);
return (
<Form form={form} onFinish={onSubmit} layout={'vertical'}>
{itemConfigList.map((config) => (
<DynamicFormItemComponent key={config.id} config={config} />
))}
<Form {...form}>
<div className="space-y-4">
{itemConfigList.map((config) => (
<FormField
key={config.id}
control={form.control}
name={config.name as keyof FormValues}
render={({ field }) => (
<FormItem>
<FormLabel>{config.label.zh_CN} {config.required && <span className="text-red-500">*</span>}</FormLabel>
<FormControl>
<DynamicFormItemComponent
config={config}
field={field}
/>
</FormControl>
{config.description && (
<p className="text-sm text-muted-foreground">
{config.description.zh_CN}
</p>
)}
<FormMessage />
</FormItem>
)}
/>
))}
</div>
</Form>
);
}

View File

@@ -1,30 +1,81 @@
import { Form, Input, InputNumber, Select, Switch } from 'antd';
// import { Form, Input, InputNumber, Select, Switch } from 'antd';
import {
DynamicFormItemType,
IDynamicFormItemConfig,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { ControllerRenderProps } from "react-hook-form";
export default function DynamicFormItemComponent({
config,
field,
}: {
config: IDynamicFormItemConfig;
field: ControllerRenderProps<any, any>;
}) {
return (
<Form.Item
label={config.label.zh_CN}
name={config.name}
rules={[{ required: config.required, message: '该项为必填项哦~' }]}
initialValue={config.default}
>
{config.type === DynamicFormItemType.INT && <InputNumber />}
switch (config.type) {
case DynamicFormItemType.INT:
case DynamicFormItemType.FLOAT:
return (
<Input
type="number"
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
);
{config.type === DynamicFormItemType.STRING && <Input />}
case DynamicFormItemType.STRING:
return <Input {...field} />;
{config.type === DynamicFormItemType.BOOLEAN && <Switch defaultChecked />}
case DynamicFormItemType.BOOLEAN:
return (
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
);
{config.type === DynamicFormItemType.STRING_ARRAY && (
<Select options={[]} />
)}
</Form.Item>
);
case DynamicFormItemType.STRING_ARRAY:
return (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger>
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{/* 这里需要根据实际情况添加选项 */}
<SelectItem value="option1">1</SelectItem>
<SelectItem value="option2">2</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
);
case DynamicFormItemType.SELECT:
return (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger>
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{/* 这里需要根据实际情况添加选项 */}
<SelectItem value="option1">1</SelectItem>
<SelectItem value="option2">2</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
);
default:
return <Input {...field} />;
}
}

View File

@@ -35,9 +35,11 @@ export interface IDynamicFormItemLabel {
export enum DynamicFormItemType {
INT = 'integer',
STRING = 'string',
FLOAT = 'float',
BOOLEAN = 'boolean',
STRING = 'string',
STRING_ARRAY = 'array[string]',
SELECT = 'select',
UNKNOWN = 'unknown',
}