mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 10:46:54 +00:00
feat(client): add settings form
This commit is contained in:
parent
6608901596
commit
0c83c16391
@ -16,6 +16,7 @@ export * from './record-provider';
|
||||
export * from './route-switch';
|
||||
export * from './schema-component';
|
||||
export * from './schema-initializer';
|
||||
export * from './settings-form';
|
||||
export * from './system-settings';
|
||||
export * from './user';
|
||||
|
||||
|
269
packages/client/src/settings-form/SettingsForm.tsx
Normal file
269
packages/client/src/settings-form/SettingsForm.tsx
Normal file
@ -0,0 +1,269 @@
|
||||
import { FormButtonGroup, FormDialog, FormDrawer, FormItem, FormLayout, Reset, Submit } from '@formily/antd';
|
||||
import { createForm, Field, ObjectField, onFormValuesChange } from '@formily/core';
|
||||
import {
|
||||
FieldContext,
|
||||
FormContext,
|
||||
observer,
|
||||
RecursionField,
|
||||
Schema,
|
||||
SchemaOptionsContext,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
useForm
|
||||
} from '@formily/react';
|
||||
import { Dropdown, Menu, Modal, Select, Switch } from 'antd';
|
||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { SchemaComponentOptions, useAttach, useDesignable } from '..';
|
||||
|
||||
export interface SettingsFormContextProps {
|
||||
field?: Field;
|
||||
fieldSchema?: Schema;
|
||||
dropdownVisible?: boolean;
|
||||
setDropdownVisible?: (v: boolean) => void;
|
||||
dn?: any;
|
||||
}
|
||||
|
||||
export const SettingsFormContext = createContext<SettingsFormContextProps>(null);
|
||||
|
||||
export const useSettingsFormContext = () => {
|
||||
return useContext(SettingsFormContext);
|
||||
};
|
||||
|
||||
export const SettingsForm: any = observer((props: any) => {
|
||||
const dn = useDesignable();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const [dropdownVisible, setDropdownVisible] = useState(false);
|
||||
const settingsFormSchema = useMemo(() => new Schema(props.schema), []);
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
initialValues: fieldSchema.toJSON(),
|
||||
effects(form) {
|
||||
onFormValuesChange((form) => {
|
||||
dn.patch(form.values);
|
||||
console.log('form.values', form.values);
|
||||
});
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
|
||||
return (
|
||||
<SettingsFormContext.Provider value={{ dn, field, fieldSchema, dropdownVisible, setDropdownVisible }}>
|
||||
<SchemaComponentOptions components={{ SettingsForm }}>
|
||||
<FieldContext.Provider value={null}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FieldContext.Provider value={f}>
|
||||
<Dropdown
|
||||
visible={dropdownVisible}
|
||||
onVisibleChange={(visible) => setDropdownVisible(visible)}
|
||||
overlayStyle={{ width: 200 }}
|
||||
overlay={
|
||||
<Menu>
|
||||
{settingsFormSchema.mapProperties((s, key) => {
|
||||
return <RecursionField name={key} schema={s} />;
|
||||
})}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<a>配置</a>
|
||||
</Dropdown>
|
||||
</FieldContext.Provider>
|
||||
</FormContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
</SchemaComponentOptions>
|
||||
</SettingsFormContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
SettingsForm.Divider = () => {
|
||||
return <Menu.Divider />;
|
||||
};
|
||||
|
||||
SettingsForm.Remove = (props) => {
|
||||
const field = useField();
|
||||
const { dn, setDropdownVisible } = useSettingsFormContext();
|
||||
return (
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
setDropdownVisible(false);
|
||||
Modal.confirm({
|
||||
title: 'Are you sure delete this task?',
|
||||
content: 'Some descriptions',
|
||||
okText: 'Yes',
|
||||
okType: 'danger',
|
||||
cancelText: 'No',
|
||||
...props.confirm,
|
||||
onOk() {
|
||||
dn.remove();
|
||||
console.log('OK');
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{field.title}
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
||||
|
||||
SettingsForm.Switch = observer(() => {
|
||||
const field = useField<Field>();
|
||||
return (
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
field.value = !field.value;
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{field.title} <Switch checked={!!field.value} />
|
||||
</div>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
SettingsForm.Select = observer((props) => {
|
||||
const field = useField<Field>();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Menu.Item onClick={() => !open && setOpen(true)}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{field.title}
|
||||
<Select
|
||||
open={open}
|
||||
onDropdownVisibleChange={(open) => setOpen(open)}
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
field.value = value;
|
||||
}}
|
||||
value={field.value}
|
||||
options={field.dataSource}
|
||||
style={{ width: '60%' }}
|
||||
size={'small'}
|
||||
bordered={false}
|
||||
/>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
SettingsForm.Modal = () => {
|
||||
const form = useForm();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { setDropdownVisible } = useSettingsFormContext();
|
||||
return (
|
||||
<Menu.Item
|
||||
style={{ width: 200 }}
|
||||
onClick={async () => {
|
||||
setDropdownVisible(false);
|
||||
const values = await FormDialog('Title', () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components, FormItem }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<RecursionField schema={fieldSchema} onlyRenderProperties />
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}).open({
|
||||
initialValues: fieldSchema.type !== 'void' ? field.value : form.values,
|
||||
});
|
||||
if (fieldSchema.type !== 'void') {
|
||||
form.setValues(
|
||||
{
|
||||
[fieldSchema.name]: values,
|
||||
},
|
||||
'deepMerge',
|
||||
);
|
||||
} else {
|
||||
form.setValues(values);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{field.title}
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
||||
|
||||
SettingsForm.Drawer = () => {
|
||||
const form = useForm();
|
||||
const field = useField<ObjectField>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { setDropdownVisible } = useSettingsFormContext();
|
||||
return (
|
||||
<Menu.Item
|
||||
style={{ width: 200 }}
|
||||
onClick={async () => {
|
||||
setDropdownVisible(false);
|
||||
const values = await FormDrawer('Pop-up form', () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components, FormItem }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<RecursionField schema={fieldSchema} onlyRenderProperties />
|
||||
<FormDrawer.Footer>
|
||||
<FormButtonGroup align="right">
|
||||
<Reset>Reset</Reset>
|
||||
<Submit
|
||||
onSubmit={() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</Submit>
|
||||
</FormButtonGroup>
|
||||
</FormDrawer.Footer>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}).open({
|
||||
initialValues: fieldSchema.type !== 'void' ? field.value : form.values,
|
||||
});
|
||||
if (fieldSchema.type !== 'void') {
|
||||
form.setValues(
|
||||
{
|
||||
[fieldSchema.name]: values,
|
||||
},
|
||||
'deepMerge',
|
||||
);
|
||||
} else {
|
||||
form.setValues(values);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{field.title}
|
||||
</Menu.Item>
|
||||
);
|
||||
};
|
||||
|
||||
SettingsForm.SubMenu = () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<Menu.SubMenu title={field.title}>
|
||||
{fieldSchema.mapProperties((schema, key) => {
|
||||
return <RecursionField name={key} schema={schema} />;
|
||||
})}
|
||||
</Menu.SubMenu>
|
||||
);
|
||||
};
|
||||
|
||||
SettingsForm.ItemGroup = () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<Menu.ItemGroup title={field.title}>
|
||||
{fieldSchema.mapProperties((schema, key) => {
|
||||
return <RecursionField name={key} schema={schema} />;
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
);
|
||||
};
|
133
packages/client/src/settings-form/demos/demo1.tsx
Normal file
133
packages/client/src/settings-form/demos/demo1.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { ISchema, observer, useFieldSchema } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, SchemaComponent, SchemaComponentProvider, SettingsForm } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'x-component-props.switch': {
|
||||
title: 'Switch',
|
||||
'x-component': 'SettingsForm.Switch',
|
||||
},
|
||||
'x-component-props.select': {
|
||||
title: 'Select',
|
||||
'x-component': 'SettingsForm.Select',
|
||||
enum: [
|
||||
{ label: 'Option1', value: 'option1' },
|
||||
{ label: 'Option2', value: 'option2' },
|
||||
{ label: 'Option3', value: 'option3' },
|
||||
],
|
||||
},
|
||||
modal: {
|
||||
type: 'void',
|
||||
title: 'Open Modal',
|
||||
'x-component': 'SettingsForm.Modal',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
'x-component-props.title': {
|
||||
title: '标题',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: 'Open Drawer',
|
||||
'x-component': 'SettingsForm.Drawer',
|
||||
properties: {
|
||||
'x-component-props.title': {
|
||||
title: '标题',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
group: {
|
||||
type: 'void',
|
||||
title: 'ItemGroup',
|
||||
'x-component': 'SettingsForm.ItemGroup',
|
||||
properties: {
|
||||
'x-component-props': {
|
||||
type: 'object',
|
||||
title: 'Open Modal',
|
||||
'x-component': 'SettingsForm.Modal',
|
||||
properties: {
|
||||
title: {
|
||||
title: '标题',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
submenu: {
|
||||
type: 'void',
|
||||
title: 'SubMenu',
|
||||
'x-component': 'SettingsForm.SubMenu',
|
||||
properties: {
|
||||
'x-component-props': {
|
||||
type: 'object',
|
||||
title: 'Open Modal',
|
||||
'x-component': 'SettingsForm.Modal',
|
||||
properties: {
|
||||
title: {
|
||||
title: '标题',
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
divider: {
|
||||
'x-component': 'SettingsForm.Divider',
|
||||
},
|
||||
remove: {
|
||||
title: 'Delete',
|
||||
'x-component': 'SettingsForm.Remove',
|
||||
'x-component-props': {
|
||||
confirm: {
|
||||
title: 'Are you sure delete this task?',
|
||||
content: 'Some descriptions',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Hello = observer((props: any) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<div>
|
||||
<pre>{JSON.stringify(props, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(fieldSchema.toJSON(), null, 2)}</pre>
|
||||
<SettingsForm schema={schema} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Hello }}>
|
||||
<AntdSchemaComponentProvider>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
'x-component': 'Hello',
|
||||
'x-component-props': {
|
||||
title: 'abc',
|
||||
switch: true,
|
||||
select: 'option1',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</AntdSchemaComponentProvider>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
@ -5,4 +5,6 @@ group:
|
||||
path: /client
|
||||
---
|
||||
|
||||
# SettingsForm <Badge>待定</Badge>
|
||||
# SettingsForm
|
||||
|
||||
<code src="./demos/demo1.tsx" />
|
||||
|
1
packages/client/src/settings-form/index.ts
Normal file
1
packages/client/src/settings-form/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './SettingsForm';
|
Loading…
Reference in New Issue
Block a user