feat(client): FormItem.Designer & TableColumn. Designer

This commit is contained in:
chenos 2022-03-05 22:51:23 +08:00
parent 2c38bf139f
commit c9957dadab
10 changed files with 314 additions and 44 deletions

View File

@ -1,5 +1,6 @@
import { Field } from '@formily/core'; import { Field } from '@formily/core';
import { connect, useField, useFieldSchema } from '@formily/react'; import { connect, useField, useFieldSchema } from '@formily/react';
import { merge } from '@formily/shared';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useCompile, useComponent } from '..'; import { useCompile, useComponent } from '..';
import { CollectionFieldProvider } from './CollectionFieldProvider'; import { CollectionFieldProvider } from './CollectionFieldProvider';
@ -33,7 +34,9 @@ const InternalField: React.FC = (props) => {
setRequired(); setRequired();
// @ts-ignore // @ts-ignore
field.dataSource = uiSchema.enum; field.dataSource = uiSchema.enum;
field.component = [component, uiSchema['x-component-props']]; const originalProps = compile(uiSchema['x-component-props']);
const componentProps = field.componentProps;
field.component = [component, merge(originalProps, componentProps)];
}, [uiSchema?.title, uiSchema?.description, uiSchema?.required]); }, [uiSchema?.title, uiSchema?.description, uiSchema?.required]);
if (!uiSchema) { if (!uiSchema) {
return null; return null;

View File

@ -30,7 +30,7 @@ export const useCancelFilterAction = () => {
export const useCollectionFilterOptions = (collectionName: string) => { export const useCollectionFilterOptions = (collectionName: string) => {
const { getCollectionFields, getInterface } = useCollectionManager(); const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName); const fields = getCollectionFields(collectionName);
const field2option = (field) => { const field2option = (field, nochildren) => {
if (!field.interface) { if (!field.interface) {
return; return;
} }
@ -45,21 +45,24 @@ export const useCollectionFilterOptions = (collectionName: string) => {
schema: field?.uiSchema, schema: field?.uiSchema,
operators: operators || [], operators: operators || [],
}; };
if (nochildren) {
return option;
}
if (children?.length) { if (children?.length) {
option['children'] = children; option['children'] = children;
} }
if (nested) { if (nested) {
const targetFields = getCollectionFields(field.target); const targetFields = getCollectionFields(field.target);
const options = getOptions(targetFields); const options = getOptions(targetFields, true);
option['children'] = option['children'] || []; option['children'] = option['children'] || [];
option['children'].push(...options); option['children'].push(...options);
} }
return option; return option;
}; };
const getOptions = (fields) => { const getOptions = (fields, nochildren = false) => {
const options = []; const options = [];
fields.forEach((field) => { fields.forEach((field) => {
const option = field2option(field); const option = field2option(field, nochildren);
if (option) { if (option) {
options.push(option); options.push(option);
} }

View File

@ -32,4 +32,6 @@ export interface CollectionFieldOptions {
collectionName?: string; collectionName?: string;
sourceKey?: string; // association field sourceKey?: string; // association field
uiSchema?: ISchema; uiSchema?: ISchema;
target?: string;
[key: string]: any;
} }

View File

@ -26,7 +26,7 @@ export const Filter: any = observer((props: any) => {
<FilterContext.Provider value={{ field, fieldSchema, dynamicComponent, options: field.dataSource || [] }}> <FilterContext.Provider value={{ field, fieldSchema, dynamicComponent, options: field.dataSource || [] }}>
<FilterGroup {...props} /> <FilterGroup {...props} />
</FilterContext.Provider> </FilterContext.Provider>
<pre>{JSON.stringify(field.value, null, 2)}</pre> {/* <pre>{JSON.stringify(field.value, null, 2)}</pre> */}
</div> </div>
); );
}); });

View File

@ -1,5 +1,10 @@
import { FormItem as Item } from '@formily/antd'; import { FormItem as Item } from '@formily/antd';
import { Field } from '@formily/core';
import { ISchema, useField, useFieldSchema, useForm } from '@formily/react';
import React from 'react'; import React from 'react';
import { useActionContext } from '..';
import { useCompile, useDesignable } from '../..';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { BlockItem } from '../block-item'; import { BlockItem } from '../block-item';
@ -12,8 +17,144 @@ export const FormItem: any = (props) => {
}; };
FormItem.Designer = () => { FormItem.Designer = () => {
const { getCollectionFields } = useCollectionManager();
const { getField } = useCollection();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const { dn, reset, refresh } = useDesignable();
const compile = useCompile();
const collectionField = getField(fieldSchema['name']);
const originalTitle = collectionField?.uiSchema?.title;
const targetFields = collectionField.target ? getCollectionFields(collectionField.target) : [];
const initialValue = {
title: field.title === originalTitle ? undefined : field.title,
};
if (!field.readPretty) {
initialValue['required'] = field.required;
}
const options = targetFields
.filter((field) => !field?.target && field.type !== 'boolean')
.map((field) => ({
value: field?.name,
label: compile(field?.uiSchema?.title) || field?.name,
}));
return ( return (
<GeneralSchemaDesigner> <GeneralSchemaDesigner>
<SchemaSettings.PopupItem
title={'编辑'}
schema={
{
title: '编辑字段',
'x-component': 'Action.Modal',
'x-decorator': 'Form',
'x-decorator-props': {
initialValue,
},
type: 'void',
properties: {
title: {
'x-decorator': 'FormItem',
'x-component': 'Input',
title: '字段标题',
'x-component-props': {},
description: `原字段标题:${collectionField?.uiSchema?.title}`,
},
required: {
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '必填',
'x-hidden': field.readPretty,
},
footer: {
type: 'void',
'x-component': 'Action.Modal.Footer',
properties: {
cancel: {
type: 'void',
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': {
useAction() {
const ctx = useActionContext();
return {
async run() {
ctx.setVisible(false);
},
};
},
},
},
submit: {
type: 'void',
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction() {
const form = useForm();
const ctx = useActionContext();
return {
async run() {
const { title, required, label } = form.values;
const schema = {
['x-uid']: fieldSchema['x-uid'],
};
if (title) {
field.title = title;
fieldSchema['title'] = title;
schema['title'] = title;
}
field.required = required;
fieldSchema['required'] = required;
schema['required'] = required;
ctx.setVisible(false);
dn.emit('patch', {
schema,
});
// dn.refresh();
reset();
refresh();
},
};
},
},
},
},
},
},
} as ISchema
}
/>
{collectionField?.target && (
<SchemaSettings.SelectItem
title={'标题字段'}
options={options}
value={field?.componentProps?.fieldNames?.label}
onChange={(label) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
};
const fieldNames = {
...field.componentProps.fieldNames,
label,
};
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
field.componentProps.fieldNames = fieldNames;
schema['x-component-props'] = {
fieldNames,
};
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
)}
<SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren
breakRemoveOn={{ breakRemoveOn={{

View File

@ -16,7 +16,6 @@ import { toArr } from '@formily/shared';
import { Button, Drawer, Select, Space } from 'antd'; import { Button, Drawer, Select, Space } from 'antd';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCollectionField } from '../../../collection-manager';
import { RecordProvider } from '../../../record-provider'; import { RecordProvider } from '../../../record-provider';
import { useAttach } from '../../hooks/useAttach'; import { useAttach } from '../../hooks/useAttach';
import { ActionContext, useActionContext } from '../action'; import { ActionContext, useActionContext } from '../action';
@ -85,7 +84,7 @@ const InputRecordPicker: React.FC<any> = (props) => {
<Select <Select
size={props.size} size={props.size}
mode={multiple ? 'multiple' : props.mode} mode={multiple ? 'multiple' : props.mode}
fieldNames={fieldNames} // fieldNames={fieldNames}
onClick={() => { onClick={() => {
setVisible(true); setVisible(true);
}} }}
@ -193,7 +192,6 @@ RecordPicker.SelectedItem = () => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const fieldNames = ctx.field.componentProps.fieldNames; const fieldNames = ctx.field.componentProps.fieldNames;
const { target } = useCollectionField();
return ( return (
<ActionContext.Provider value={{ visible, setVisible }}> <ActionContext.Provider value={{ visible, setVisible }}>
<a style={{ cursor: 'pointer' }} onClick={() => setVisible(true)}> <a style={{ cursor: 'pointer' }} onClick={() => setVisible(true)}>

View File

@ -17,13 +17,13 @@ export const useColumnSchema = () => {
return {}; return {};
} }
const collectionField = getField(fieldSchema.name); const collectionField = getField(fieldSchema.name);
return { columnSchema, fieldSchema, uiSchema: compile(collectionField?.uiSchema) }; return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) };
}; };
export const TableColumnDecorator = (props) => { export const TableColumnDecorator = (props) => {
const Designer = useDesigner(); const Designer = useDesigner();
const field = useField(); const field = useField();
const { fieldSchema, uiSchema } = useColumnSchema(); const { fieldSchema, uiSchema, collectionField } = useColumnSchema();
const { refresh } = useDesignable(); const { refresh } = useDesignable();
const compile = useCompile(); const compile = useCompile();
useLayoutEffect(() => { useLayoutEffect(() => {
@ -37,10 +37,9 @@ export const TableColumnDecorator = (props) => {
field.title = uiSchema?.title; field.title = uiSchema?.title;
} }
}, [uiSchema?.title]); }, [uiSchema?.title]);
console.log('field.title', uiSchema?.title, field.title);
return ( return (
<SortableItem className={designerCss}> <SortableItem className={designerCss}>
<Designer /> <Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField}/>
{/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */} {/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */}
{field.title || compile(uiSchema?.title)} {field.title || compile(uiSchema?.title)}
{/* <div {/* <div

View File

@ -1,28 +1,137 @@
import { useField, useFieldSchema } from '@formily/react'; import { ISchema, useField, useFieldSchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import React from 'react'; import React from 'react';
import { useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useDesignable } from '../../hooks'; import { useCompile, useDesignable } from '../../hooks';
import { useActionContext } from '../action';
export const TableColumnDeigner = () => { const useLabelFields = (collectionName?: any) => {
if (!collectionName) {
return [];
}
const compile = useCompile();
const { getCollectionFields } = useCollectionManager();
const targetFields = getCollectionFields(collectionName);
return targetFields
?.filter((field) => !field?.target && field.type !== 'boolean')
?.map((field) => {
return {
value: field.name,
label: compile(field?.uiSchema?.title || field.name),
};
});
};
export const TableColumnDeigner = (props) => {
const { uiSchema, fieldSchema, collectionField } = props;
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const columnSchema = useFieldSchema();
const { dn } = useDesignable(); const { dn } = useDesignable();
const initialValue = {
title: columnSchema?.title,
};
const options = useLabelFields(collectionField.target);
return ( return (
<GeneralSchemaDesigner> <GeneralSchemaDesigner>
<SchemaSettings.Item <SchemaSettings.PopupItem
title={'编辑'} title={'编辑'}
onClick={() => { schema={
field.title = uid(); {
fieldSchema.title = uid(); title: '编辑字段',
'x-component': 'Action.Modal',
'x-decorator': 'Form',
'x-decorator-props': {
initialValue,
},
type: 'void',
properties: {
title: {
'x-decorator': 'FormItem',
'x-component': 'Input',
title: '字段标题',
'x-component-props': {},
description: `原字段标题:${collectionField?.uiSchema?.title}`,
},
footer: {
type: 'void',
'x-component': 'Action.Modal.Footer',
properties: {
cancel: {
type: 'void',
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': {
useAction() {
const ctx = useActionContext();
return {
async run() {
ctx.setVisible(false);
},
};
},
},
},
submit: {
type: 'void',
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction() {
const form = useForm();
const ctx = useActionContext();
return {
async run() {
const { title } = form.values;
if (title) {
field.title = title;
columnSchema.title = title;
dn.emit('patch', {
schema: {
'x-uid': columnSchema['x-uid'],
title: columnSchema.title,
},
});
}
ctx.setVisible(false);
dn.refresh();
},
};
},
},
},
},
},
},
} as ISchema
}
/>
{collectionField?.target && (
<SchemaSettings.SelectItem
title={'标题字段'}
options={options}
value={fieldSchema?.['x-component-props']?.['fieldNames']?.['label']}
onChange={(label) => {
const fieldNames = {
...fieldSchema['x-component-props']['fieldNames'],
label,
};
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
field.query(`.*.${fieldSchema.name}`).take((f) => {
f.componentProps.fieldNames = fieldNames;
});
dn.emit('patch', { dn.emit('patch', {
schema: { schema: {
'x-uid': fieldSchema['x-uid'], 'x-uid': fieldSchema['x-uid'],
title: fieldSchema.title, 'x-component-props': {
fieldNames,
},
}, },
}); });
dn.refresh();
}} }}
/> />
)}
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren

View File

@ -17,6 +17,7 @@ export const AddNewActionInitializer = (props) => {
type: 'void', type: 'void',
title: '{{ t("Add new") }}', title: '{{ t("Add new") }}',
'x-action': 'create', 'x-action': 'create',
'x-designer': 'Action.Designer',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
type: 'primary', type: 'primary',

View File

@ -1,7 +1,7 @@
import { GeneralField } from '@formily/core'; import { GeneralField } from '@formily/core';
import { ISchema, Schema } from '@formily/react'; import { ISchema, Schema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { Dropdown, Menu, MenuItemProps, Modal } from 'antd'; import { Dropdown, Menu, MenuItemProps, Modal, Select } from 'antd';
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ActionContext, Designable, SchemaComponent } from '..'; import { ActionContext, Designable, SchemaComponent } from '..';
@ -17,6 +17,8 @@ interface SchemaSettingsContextProps {
dn?: Designable; dn?: Designable;
field?: GeneralField; field?: GeneralField;
fieldSchema?: Schema; fieldSchema?: Schema;
setVisible?: any;
visible?: any;
} }
const SchemaSettingsContext = createContext<SchemaSettingsContextProps>(null); const SchemaSettingsContext = createContext<SchemaSettingsContextProps>(null);
@ -42,23 +44,36 @@ interface SchemaSettingsProviderProps {
dn?: Designable; dn?: Designable;
field?: GeneralField; field?: GeneralField;
fieldSchema?: Schema; fieldSchema?: Schema;
setVisible?: any;
visible?: any;
} }
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => { export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
const { dn, field, fieldSchema, children } = props; const { visible, setVisible, dn, field, fieldSchema, children } = props;
return <SchemaSettingsContext.Provider value={{ dn, field, fieldSchema }}>{children}</SchemaSettingsContext.Provider>; return (
<SchemaSettingsContext.Provider value={{ visible, setVisible, dn, field, fieldSchema }}>
{children}
</SchemaSettingsContext.Provider>
);
}; };
export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNested = (props) => { export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNested = (props) => {
const { title, dn, field, fieldSchema } = props; const { title, dn, field, fieldSchema } = props;
const [visible, setVisible] = useState(false);
const DropdownMenu = ( const DropdownMenu = (
<Dropdown overlay={<Menu>{props.children}</Menu>}> <Dropdown
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={<Menu>{props.children}</Menu>}
>
{typeof title === 'string' ? <span>{title}</span> : title} {typeof title === 'string' ? <span>{title}</span> : title}
</Dropdown> </Dropdown>
); );
if (dn) { if (dn) {
return ( return (
<SchemaSettingsProvider dn={dn} field={field} fieldSchema={fieldSchema}> <SchemaSettingsProvider visible={visible} setVisible={setVisible} dn={dn} field={field} fieldSchema={fieldSchema}>
{DropdownMenu} {DropdownMenu}
</SchemaSettingsProvider> </SchemaSettingsProvider>
); );
@ -121,21 +136,29 @@ SchemaSettings.Remove = (props: any) => {
}; };
SchemaSettings.SelectItem = (props) => { SchemaSettings.SelectItem = (props) => {
return null; const { title, options, value, onChange, ...others } = props;
return (
<SchemaSettings.Item {...others}>
{title}
<Select bordered={false} defaultValue={value} onChange={onChange} options={options} style={{ minWidth: 100 }} />
</SchemaSettings.Item>
);
}; };
SchemaSettings.SwitchItem = (props) => { SchemaSettings.SwitchItem = (props) => {
return null; return null;
}; };
SchemaSettings.Popup = (props) => { SchemaSettings.PopupItem = (props) => {
const { schema, ...others } = props; const { schema, ...others } = props;
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const ctx = useContext(SchemaSettingsContext);
return ( return (
<ActionContext.Provider value={{ visible, setVisible }}> <ActionContext.Provider value={{ visible, setVisible }}>
<SchemaSettings.Item <SchemaSettings.Item
{...others} {...others}
onClick={() => { onClick={() => {
ctx.setVisible(false);
setVisible(true); setVisible(true);
}} }}
> >
@ -150,12 +173,3 @@ SchemaSettings.Popup = (props) => {
</ActionContext.Provider> </ActionContext.Provider>
); );
}; };
SchemaSettings.DrawerItem = (props) => {
const [visible, setVisible] = useState(false);
return (
<ActionContext.Provider value={{ visible, setVisible }}>
<SchemaSettings.Item {...props}>{props.children || props.title}</SchemaSettings.Item>
</ActionContext.Provider>
);
};