feat: filter component finish

This commit is contained in:
Semmy 2022-02-27 01:49:56 +08:00
parent 6f2069e918
commit 968a7ec80f
10 changed files with 240 additions and 149 deletions

View File

@ -16,6 +16,7 @@ export const useCollectionField = () => {
} }
const resourceName = `${ctx?.collectionName || collection?.name}.${ctx.name}`; const resourceName = `${ctx?.collectionName || collection?.name}.${ctx.name}`;
const resource = api?.resource(resourceName, record[ctx.sourceKey]); const resource = api?.resource(resourceName, record[ctx.sourceKey]);
debugger;
return { return {
...ctx, ...ctx,
uiSchema: compile(ctx.uiSchema), uiSchema: compile(ctx.uiSchema),

View File

@ -1,14 +1,12 @@
import { FormButtonGroup, Submit } from '@formily/antd';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { FormProvider, observer, useFieldSchema } from '@formily/react'; import { observer, useFieldSchema } from '@formily/react';
import { Button, Popover } from 'antd';
import flatten from 'flat'; import flatten from 'flat';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaComponent } from '../../core'; import { SchemaComponent } from '../../core';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { useCompile } from '../../hooks/useCompile'; import { useCompile } from '../../hooks/useCompile';
import { IconPicker } from '../icon-picker'; import { FilterContext } from '../filter/context';
export const Filter = observer((props: any) => { export const Filter = observer((props: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -25,45 +23,19 @@ export const Filter = observer((props: any) => {
properties[p.name] = p; properties[p.name] = p;
}); });
return ( return (
<Popover <FilterContext.Provider value={{ visible, setVisible }}>
trigger={['click']} <SchemaComponent
placement={'bottomLeft'} schema={{
visible={visible} type: 'void',
onVisibleChange={setVisible} properties: {
content={ filter: {
<div> type: 'object',
<FormProvider form={form}> title: t('Filter'),
<SchemaComponent 'x-component': 'Filter.Action',
schema={{ },
type: 'object', },
properties: { }}
filter: { />
type: 'object', </FilterContext.Provider>
'x-component': 'Filter',
properties,
},
},
}}
/>
<FormButtonGroup align={'right'}>
<Submit
onSubmit={() => {
const { filter } = form.values;
console.log('Table.Filter', form.values);
setVisible(false);
}}
>
{t('Submit')}
</Submit>
</FormButtonGroup>
</FormProvider>
</div>
}
>
<Button icon={<IconPicker type={icon} />}>
{count > 0 ? t('{{count}} filter items', { count }) : compile(filedSchema.title)}
<DesignableBar />
</Button>
</Popover>
); );
}); });

View File

@ -7,6 +7,7 @@ import { uid } from '@formily/shared';
import { import {
AntdSchemaComponentProvider, AntdSchemaComponentProvider,
CollectionField, CollectionField,
CollectionManagerProvider,
CollectionProvider, CollectionProvider,
SchemaComponent, SchemaComponent,
SchemaComponentProvider, SchemaComponentProvider,
@ -56,6 +57,13 @@ const schema = {
'x-action': 'calendar:viewSelect', 'x-action': 'calendar:viewSelect',
'x-align': 'right', 'x-align': 'right',
}, },
filter: {
type: 'void',
title: '过滤',
'x-align': 'right',
'x-component': 'Calendar.Filter',
'x-action': 'calendar:filter',
},
}, },
}, },
event: { event: {
@ -98,9 +106,9 @@ const collection = {
type: 'string', type: 'string',
name: 'id', name: 'id',
interface: 'input', interface: 'input',
title: 'id',
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: 'id',
'x-component': 'Input', 'x-component': 'Input',
}, },
}, },
@ -108,9 +116,9 @@ const collection = {
type: 'string', type: 'string',
name: 'title', name: 'title',
interface: 'input', interface: 'input',
title: '标题',
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: '标题',
'x-component': 'Input', 'x-component': 'Input',
}, },
}, },
@ -118,9 +126,9 @@ const collection = {
type: 'string', type: 'string',
name: 'start', name: 'start',
interface: 'datetime', interface: 'datetime',
title: '开始时间',
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: '开始时间',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': { 'x-component-props': {
dateFormat: 'YYYY-MM-DD', dateFormat: 'YYYY-MM-DD',
@ -131,9 +139,9 @@ const collection = {
type: 'string', type: 'string',
name: 'end', name: 'end',
interface: 'datetime', interface: 'datetime',
title: '结束时间',
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: '结束时间',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': { 'x-component-props': {
dateFormat: 'YYYY-MM-DD', dateFormat: 'YYYY-MM-DD',
@ -144,14 +152,16 @@ const collection = {
}; };
export default () => { export default () => {
return ( return (
<CollectionProvider collection={collection}> <CollectionManagerProvider>
<SchemaComponentProvider designable={true} components={{ CollectionField }}> <CollectionProvider collection={collection}>
<SchemaInitializerProvider> <SchemaComponentProvider designable={true} components={{ CollectionField }}>
<AntdSchemaComponentProvider> <SchemaInitializerProvider>
<SchemaComponent schema={schema} /> <AntdSchemaComponentProvider>
</AntdSchemaComponentProvider> <SchemaComponent schema={schema} />
</SchemaInitializerProvider> </AntdSchemaComponentProvider>
</SchemaComponentProvider> </SchemaInitializerProvider>
</CollectionProvider> </SchemaComponentProvider>
</CollectionProvider>
</CollectionManagerProvider>
); );
}; };

View File

@ -2,33 +2,33 @@ import { get } from 'lodash';
import { i18n } from '../../../i18n'; import { i18n } from '../../../i18n';
export const fieldsToFilterColumns = (fields: any[], options: any = {}) => { export const fieldsToFilterColumns = (fields: any[], options: any = {}) => {
// const { fieldNames = [] } = options; // const { fieldNames = [] } = options;
// const properties = {}; // const properties = {};
// fields.forEach((field, index) => { // fields.forEach((field, index) => {
// if (fieldNames?.length && !fieldNames.includes(field.name)) { // if (fieldNames?.length && !fieldNames.includes(field.name)) {
// return; // return;
// } // }
// const fieldOption = interfaces.get(field.interface); // const fieldOption = interfaces.get(field.interface);
// if (!fieldOption?.operations) { // if (!fieldOption?.operations) {
// return; // return;
// } // }
// properties[`column${index}`] = { // properties[`column${index}`] = {
// type: 'void', // type: 'void',
// title: field?.uiSchema?.title, // title: field?.uiSchema?.title,
// 'x-component': 'Filter.Column', // 'x-component': 'Filter.Column',
// 'x-component-props': { // 'x-component-props': {
// operations: fieldOption.operations, // operations: fieldOption.operations,
// },
// properties: {
// [field.name]: {
// ...field.uiSchema,
// 'x-decorator': 'FormilyFormItem',
// title: null,
// }, // },
// properties: { // },
// [field.name]: { // };
// ...field.uiSchema, // });
// 'x-decorator': 'FormilyFormItem', // return properties;
// title: null,
// },
// },
// };
// });
// return properties;
}; };
export const toEvents = (data: any[], fieldNames: any) => { export const toEvents = (data: any[], fieldNames: any) => {

View File

@ -0,0 +1,86 @@
import { FilterOutlined } from '@ant-design/icons';
import { FormButtonGroup, Submit } from '@formily/antd';
import { createForm } from '@formily/core';
import { FieldContext, FormContext, observer, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import { Button, Popover } from 'antd';
import flatten from 'flat';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { SchemaComponent } from '../../core';
import { useDesignable } from '../../hooks';
import { useCompile } from '../../hooks/useCompile';
import { useFilterContext } from './hooks';
export const FilterAction = observer((props: any) => {
const { t } = useTranslation();
const compile = useCompile();
const { DesignableBar } = useDesignable();
const filedSchema = useFieldSchema();
const form = useMemo(() => createForm(), []);
const { visible, setVisible } = useFilterContext();
const obj = flatten(form.values.filter || {});
const count = Object.values(obj).filter((i) => (Array.isArray(i) ? i.length : i)).length;
const { fields } = useCollection();
const { getInterface } = useCollectionManager();
const properties = {};
fields?.forEach((field) => {
const { operators } = getInterface(field.interface);
properties[uid()] = {
type: 'void',
title: field?.uiSchema?.title ?? field.title,
'x-component': 'Filter.Column',
'x-component-props': {
operations: [...operators],
},
properties: {
[field.name ?? uid()]: { ...field?.uiSchema },
},
};
});
const schema = {
type: 'void',
properties: {
filter: {
type: 'array',
'x-component': 'Filter',
properties,
},
},
};
return (
<Popover
trigger={['click']}
placement={'bottomLeft'}
visible={visible}
onVisibleChange={setVisible}
content={
<div>
<FieldContext.Provider value={null}>
<FormContext.Provider value={form}>
<SchemaComponent schema={schema} name="filter" />
<FormButtonGroup align={'right'}>
<Submit
onSubmit={() => {
const { filter } = form.values;
console.log('Table.Filter====', form.values);
debugger;
setVisible(false);
}}
>
{t('Submit')}
</Submit>
</FormButtonGroup>
</FormContext.Provider>
</FieldContext.Provider>
</div>
}
>
<Button icon={<FilterOutlined />}>
{count > 0 ? t('{{count}} filter items', { count }) : compile(filedSchema.title)}
<DesignableBar />
</Button>
</Popover>
);
});

View File

@ -0,0 +1,61 @@
import { createForm, onFieldValueChange } from '@formily/core';
import type { ISchema, Schema } from '@formily/react';
import { connect } from '@formily/react';
import deepmerge from 'deepmerge';
import React, { useMemo } from 'react';
import { SchemaComponent } from '../../core';
interface DynamicValueProps {
value?: any;
onChange?: any;
schema?: Schema;
operation?: any;
}
export const FilterDynamicValue = connect((props: DynamicValueProps) => {
const { onChange, value, operation } = props;
const fieldName = Object.keys(props?.schema?.properties || {}).shift() ?? 'value';
const fieldSchema = Object.values(props?.schema?.properties || {}).shift();
const form = useMemo(
() =>
createForm({
initialValues: {
[fieldName]: value,
},
effects(form) {
onFieldValueChange(fieldName, (field) => {
onChange(field.value);
});
},
}),
[],
);
const extra: ISchema = deepmerge(
{
required: false,
'x-read-pretty': false,
name: fieldName,
default: value,
'x-decorator': 'FormilyFormItem',
'x-decorator-props': {
asterisk: true,
feedbackLayout: 'none',
},
'x-component-props': {
style: {
minWidth: '150px',
},
},
},
operation?.schema || {},
);
const schema = {
type: 'object',
properties: {
[fieldName]: deepmerge(fieldSchema, extra, {
arrayMerge: (target, source) => source,
}) as any,
},
};
return <SchemaComponent schema={schema} />;
});

View File

@ -1,14 +1,15 @@
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { createForm, onFieldValueChange } from '@formily/core'; import { connect, mapProps, mapReadPretty } from '@formily/react';
import { connect, FieldContext, FormContext, ISchema, mapProps, mapReadPretty, Schema } from '@formily/react'; import React from 'react';
import deepmerge from 'deepmerge'; import { FilterAction } from './Filter.Action';
import React, { useMemo } from 'react'; import { FilterDynamicValue } from './Filter.DynamicValue';
import { SchemaComponent } from '../../core';
import { FilterGroup } from './FilterGroup'; import { FilterGroup } from './FilterGroup';
import './style.less'; import './style.less';
export const Filter: any = connect( export const Filter: any = connect(
(props) => <FilterGroup bordered={false} {...props} />, (props) => {
return <FilterGroup bordered={false} {...props} />;
},
mapProps((props, field) => { mapProps((props, field) => {
return { return {
...props, ...props,
@ -20,65 +21,7 @@ export const Filter: any = connect(
}), }),
); );
interface DynamicValueProps { Filter.DynamicValue = FilterDynamicValue;
value?: any; Filter.Action = FilterAction;
onChange?: any;
schema?: Schema;
operation?: any;
}
Filter.DynamicValue = connect((props: DynamicValueProps) => {
const { onChange, value, operation } = props;
const fieldName = Object.keys(props?.schema?.properties || {}).shift() ?? 'value';
const fieldSchema = Object.values(props?.schema?.properties || {}).shift();
const form = useMemo(
() =>
createForm({
initialValues: {
[fieldName]: value,
},
effects(form) {
onFieldValueChange(fieldName, (field) => {
onChange(field.value);
});
},
}),
[],
);
const extra: ISchema = deepmerge(
{
required: false,
'x-read-pretty': false,
name: fieldName,
default: value,
'x-decorator': 'FormilyFormItem',
'x-decorator-props': {
asterisk: true,
feedbackLayout: 'none',
},
'x-component-props': {
style: {
minWidth: '150px',
},
},
},
operation?.schema || {},
);
const schema = {
type: 'object',
properties: {
[fieldName]: deepmerge(fieldSchema, extra, {
arrayMerge: (target, source) => source,
}) as any,
},
};
return (
<FieldContext.Provider value={null}>
<FormContext.Provider value={form}>
<SchemaComponent schema={schema} />
</FormContext.Provider>
</FieldContext.Provider>
);
});
export default Filter; export default Filter;

View File

@ -0,0 +1,8 @@
import { createContext } from 'react';
export const FilterContext = createContext<FilterContextProps>({});
export interface FilterContextProps {
visible?: boolean;
setVisible?: (v: boolean) => void;
}

View File

@ -0,0 +1,9 @@
import { useContext } from 'react';
import { FilterContext } from './context';
export const useFilterContext = () => {
const ctx = useContext(FilterContext);
return {
...ctx,
};
};

View File

@ -65,6 +65,7 @@ export const IconPicker = connect(
mapProps((props, field) => { mapProps((props, field) => {
return { return {
...props, ...props,
value: props.type,
suffix: <span>{field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffix}</span>, suffix: <span>{field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffix}</span>,
}; };
}), }),