mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-30 14:17:14 +00:00
feat: switch dynamic to shadcn
This commit is contained in:
@@ -161,7 +161,7 @@ export default function BotForm({
|
|||||||
|
|
||||||
function onEditMode() {
|
function onEditMode() {
|
||||||
console.log('onEditMode', form.getValues());
|
console.log('onEditMode', form.getValues());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
|
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>
|
<FormLabel>平台/适配器选择<span className="text-red-500">*</span></FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
field.onChange(value);
|
field.onChange(value);
|
||||||
handleAdapterSelect(value);
|
handleAdapterSelect(value);
|
||||||
}}
|
}}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger className="w-[180px]">
|
||||||
@@ -376,8 +376,8 @@ export default function BotForm({
|
|||||||
|
|
||||||
{form.watch('adapter') && (
|
{form.watch('adapter') && (
|
||||||
<div className="flex items-start gap-3 p-4 rounded-lg border">
|
<div className="flex items-start gap-3 p-4 rounded-lg border">
|
||||||
<img
|
<img
|
||||||
src={adapterIconList[form.watch('adapter')]}
|
src={adapterIconList[form.watch('adapter')]}
|
||||||
alt="adapter icon"
|
alt="adapter icon"
|
||||||
className="w-12 h-12"
|
className="w-12 h-12"
|
||||||
/>
|
/>
|
||||||
@@ -392,21 +392,41 @@ export default function BotForm({
|
|||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
|
||||||
{!initBotId && (
|
<div className="flex justify-end gap-2">
|
||||||
<Button type="submit">提交</Button>
|
{!initBotId && (
|
||||||
)}
|
<Button type="submit" onClick={form.handleSubmit(onDynamicFormSubmit)}>提交</Button>
|
||||||
{initBotId && (
|
)}
|
||||||
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
|
{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>
|
||||||
)}
|
</div>
|
||||||
<Button type="button" onClick={() => onFormCancel()}>
|
</div>
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -154,22 +154,24 @@ export default function BotConfigPage() {
|
|||||||
</Spin> */}
|
</Spin> */}
|
||||||
|
|
||||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
<DialogContent className="w-[700px] p-6">
|
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader className="px-6 pt-6 pb-4">
|
||||||
<DialogTitle>{isEditForm ? '编辑机器人' : '创建机器人'}</DialogTitle>
|
<DialogTitle>{isEditForm ? '编辑机器人' : '创建机器人'}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<BotForm
|
<div className="flex-1 overflow-y-auto px-6">
|
||||||
initBotId={nowSelectedBotCard?.id}
|
<BotForm
|
||||||
onFormSubmit={() => {
|
initBotId={nowSelectedBotCard?.id}
|
||||||
getBotList();
|
onFormSubmit={() => {
|
||||||
setModalOpen(false);
|
getBotList();
|
||||||
}}
|
setModalOpen(false);
|
||||||
onFormCancel={() => setModalOpen(false)}
|
}}
|
||||||
onBotDeleted={() => {
|
onFormCancel={() => setModalOpen(false)}
|
||||||
getBotList();
|
onBotDeleted={() => {
|
||||||
setModalOpen(false);
|
getBotList();
|
||||||
}}
|
setModalOpen(false);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,139 @@
|
|||||||
import { IDynamicFormItemConfig } from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
|
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 DynamicFormItemComponent from '@/app/home/components/dynamic-form/DynamicFormItemComponent';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function DynamicFormComponent({
|
export default function DynamicFormComponent({
|
||||||
form,
|
|
||||||
itemConfigList,
|
itemConfigList,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
initialValues,
|
||||||
}: {
|
}: {
|
||||||
form: FormInstance<object>;
|
|
||||||
itemConfigList: IDynamicFormItemConfig[];
|
itemConfigList: IDynamicFormItemConfig[];
|
||||||
onSubmit?: (val: object) => unknown;
|
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 (
|
return (
|
||||||
<Form form={form} onFinish={onSubmit} layout={'vertical'}>
|
<Form {...form}>
|
||||||
{itemConfigList.map((config) => (
|
<div className="space-y-4">
|
||||||
<DynamicFormItemComponent key={config.id} config={config} />
|
{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>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,81 @@
|
|||||||
import { Form, Input, InputNumber, Select, Switch } from 'antd';
|
// import { Form, Input, InputNumber, Select, Switch } from 'antd';
|
||||||
import {
|
import {
|
||||||
DynamicFormItemType,
|
DynamicFormItemType,
|
||||||
IDynamicFormItemConfig,
|
IDynamicFormItemConfig,
|
||||||
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
|
} 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({
|
export default function DynamicFormItemComponent({
|
||||||
config,
|
config,
|
||||||
|
field,
|
||||||
}: {
|
}: {
|
||||||
config: IDynamicFormItemConfig;
|
config: IDynamicFormItemConfig;
|
||||||
|
field: ControllerRenderProps<any, any>;
|
||||||
}) {
|
}) {
|
||||||
return (
|
switch (config.type) {
|
||||||
<Form.Item
|
case DynamicFormItemType.INT:
|
||||||
label={config.label.zh_CN}
|
case DynamicFormItemType.FLOAT:
|
||||||
name={config.name}
|
return (
|
||||||
rules={[{ required: config.required, message: '该项为必填项哦~' }]}
|
<Input
|
||||||
initialValue={config.default}
|
type="number"
|
||||||
>
|
{...field}
|
||||||
{config.type === DynamicFormItemType.INT && <InputNumber />}
|
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 && (
|
case DynamicFormItemType.STRING_ARRAY:
|
||||||
<Select options={[]} />
|
return (
|
||||||
)}
|
<Select
|
||||||
</Form.Item>
|
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} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ export interface IDynamicFormItemLabel {
|
|||||||
|
|
||||||
export enum DynamicFormItemType {
|
export enum DynamicFormItemType {
|
||||||
INT = 'integer',
|
INT = 'integer',
|
||||||
STRING = 'string',
|
FLOAT = 'float',
|
||||||
BOOLEAN = 'boolean',
|
BOOLEAN = 'boolean',
|
||||||
|
STRING = 'string',
|
||||||
STRING_ARRAY = 'array[string]',
|
STRING_ARRAY = 'array[string]',
|
||||||
|
SELECT = 'select',
|
||||||
UNKNOWN = 'unknown',
|
UNKNOWN = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user