feat: improve filter component

This commit is contained in:
chenos 2022-04-07 17:34:29 +08:00
parent 53900c1e9e
commit 20680d3bc7
8 changed files with 183 additions and 97 deletions

View File

@ -1,5 +1,6 @@
import { createForm, onFieldValueChange } from '@formily/core'; import { createForm, onFieldValueChange } from '@formily/core';
import { FieldContext, FormContext } from '@formily/react'; import { FieldContext, FormContext } from '@formily/react';
import { merge } from '@formily/shared';
import React, { useContext, useMemo } from 'react'; import React, { useContext, useMemo } from 'react';
import { SchemaComponent } from '../../core'; import { SchemaComponent } from '../../core';
import { useComponent } from '../../hooks'; import { useComponent } from '../../hooks';
@ -27,6 +28,11 @@ export const DynamicComponent = (props) => {
schema={{ schema={{
'x-component': 'Input', 'x-component': 'Input',
...props.schema, ...props.schema,
'x-component-props': merge(props?.schema?.['x-component-props'] || {}, {
style: {
minWidth: 150,
},
}),
name: 'value', name: 'value',
'x-read-pretty': false, 'x-read-pretty': false,
'x-validator': undefined, 'x-validator': undefined,

View File

@ -1,16 +1,66 @@
import { ISchema, useField, useFieldSchema } from '@formily/react'; import { ISchema, useField, useFieldSchema } from '@formily/react';
import React from 'react'; import React from 'react';
import { useDesignable } from '../..'; import { useDesignable } from '../..';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
export const useFilterableFields = (collectionName: string) => {
const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName);
return fields?.filter?.((field) => {
if (!field.interface) {
return false;
}
const fieldInterface = getInterface(field.interface);
if (!fieldInterface.filterable) {
return false;
}
return true;
});
};
export const FilterActionDesigner = (props) => { export const FilterActionDesigner = (props) => {
const initialValue = {};
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { dn } = useDesignable(); const { dn } = useDesignable();
const isPopupAction = ['create', 'update', 'view'].includes(fieldSchema['x-action'] || ''); const { name } = useCollection();
const fields = useFilterableFields(name);
const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
return ( return (
<GeneralSchemaDesigner {...props}> <GeneralSchemaDesigner {...props}>
<SchemaSettings.ItemGroup title={'可筛选字段'}>
{fields.map((field) => {
const checked = fieldNames.includes(field.name);
return (
<SchemaSettings.SwitchItem
checked={checked}
title={field?.uiSchema?.title}
onChange={(value) => {
fieldSchema['x-component-props'] = fieldSchema?.['x-component-props'] || {};
const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
console.log('fieldNames.checked', value)
if (value) {
fieldNames.push(field.name);
} else {
const index = fieldNames.indexOf(field.name);
fieldNames.splice(index, 1);
}
fieldSchema['x-component-props'].fieldNames = fieldNames;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-component-props': {
...fieldSchema['x-component-props'],
},
},
});
dn.refresh();
}}
/>
);
})}
</SchemaSettings.ItemGroup>
<SchemaSettings.Divider />
<SchemaSettings.ModalItem <SchemaSettings.ModalItem
title={'编辑'} title={'编辑'}
schema={ schema={
@ -57,27 +107,6 @@ export const FilterActionDesigner = (props) => {
} }
}} }}
/> />
{isPopupAction && (
<SchemaSettings.SelectItem
title={'打开方式'}
options={[
{ label: '抽屉', value: 'drawer' },
{ label: '对话框', value: 'modal' },
]}
value={field.componentProps.openMode}
onChange={(value) => {
field.componentProps.openMode = value;
fieldSchema['x-component-props']['openMode'] = value;
dn.emit('patch', {
schema: {
'x-uid': fieldSchema['x-uid'],
'x-component-props': fieldSchema['x-component-props'],
},
});
dn.refresh();
}}
/>
)}
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren

View File

@ -17,7 +17,7 @@ export const FilterAction = observer((props: any) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { designable, dn } = useDesignable(); const { designable, dn } = useDesignable();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const form = useMemo<Form>(() => props.form || createForm(), [visible]); const form = useMemo<Form>(() => props.form || createForm(), []);
const { options, onSubmit, onReset, ...others } = useProps(props); const { options, onSubmit, onReset, ...others } = useProps(props);
return ( return (
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}> <FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
@ -58,7 +58,7 @@ export const FilterAction = observer((props: any) => {
<Button <Button
onClick={async () => { onClick={async () => {
await form.reset(); await form.reset();
onReset?.(); onReset?.(form.values);
field.title = t('Filter'); field.title = t('Filter');
setVisible(false); setVisible(false);
}} }}
@ -72,9 +72,6 @@ export const FilterAction = observer((props: any) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onSubmit?.(form.values); onSubmit?.(form.values);
const items = form.values?.filter?.$and || form.values?.filter?.$or;
console.log('form.values.filter', form.values, items);
field.title = t('{{count}} filter items', { count: items?.length || 0 });
setVisible(false); setVisible(false);
}} }}
> >

View File

@ -4,7 +4,7 @@ import { ArrayField, connect, useField } from '@formily/react';
import { Select, Space } from 'antd'; import { Select, Space } from 'antd';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { RemoveConditionContext } from './context'; import { FilterLogicContext, RemoveConditionContext } from './context';
import { FilterItems } from './FilterItems'; import { FilterItems } from './FilterItems';
export const FilterGroup = connect((props) => { export const FilterGroup = connect((props) => {
@ -16,73 +16,76 @@ export const FilterGroup = connect((props) => {
const setLogic = (value) => { const setLogic = (value) => {
const obj = field.value || {}; const obj = field.value || {};
field.value = { field.value = {
[value]: obj[logic] || [], [value]: [...(obj[logic] || [])],
}; };
}; };
console.log('logic', logic);
return ( return (
<div <FilterLogicContext.Provider value={logic}>
style={{ <div
position: 'relative', style={{
border: '1px dashed #dedede', position: 'relative',
padding: 14, border: '1px dashed #dedede',
marginBottom: 8, padding: 14,
}} marginBottom: 8,
> }}
{remove && ( >
<CloseCircleOutlined {remove && (
style={{ <CloseCircleOutlined
position: 'absolute', style={{
right: 10, position: 'absolute',
}} right: 10,
onClick={() => remove()} }}
/> onClick={() => remove()}
)} />
<div style={{ marginBottom: 8 }}> )}
<Trans> <div style={{ marginBottom: 8 }}>
{'Meet '} <Trans>
<Select {'Meet '}
value={logic} <Select
onChange={(value) => { value={logic}
setLogic(value); onChange={(value) => {
setLogic(value);
}}
>
<Select.Option value={'$and'}>All</Select.Option>
<Select.Option value={'$or'}>Any</Select.Option>
</Select>
{' conditions in the group'}
</Trans>
</div>
<div>
<ArrayField name={logic} component={[FilterItems]} />
</div>
<Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
<a
onClick={() => {
const value = field.value || {};
const items = value[logic] || [];
items.push({});
field.value = {
[logic]: items,
};
}} }}
> >
<Select.Option value={'$and'}>All</Select.Option> {t('Add condition')}
<Select.Option value={'$or'}>Any</Select.Option> </a>
</Select> <a
{' conditions in the group'} onClick={() => {
</Trans> const value = field.value || {};
const items = value[logic] || [];
items.push({
$and: [{}],
});
field.value = {
[logic]: items,
};
}}
>
{t('Add condition group')}
</a>
</Space>
</div> </div>
<div> </FilterLogicContext.Provider>
<ArrayField name={logic} component={[FilterItems]} />
</div>
<Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
<a
onClick={() => {
const value = field.value || {};
const items = value[logic] || [];
items.push({});
field.value = {
[logic]: items,
};
}}
>
{t('Add condition')}
</a>
<a
onClick={() => {
const value = field.value || {};
const items = value[logic] || [];
items.push({
$and: [{}],
});
field.value = {
[logic]: items,
};
}}
>
{t('Add condition group')}
</a>
</Space>
</div>
); );
}); });

View File

@ -11,3 +11,4 @@ export interface FilterContextProps {
export const RemoveConditionContext = createContext(null); export const RemoveConditionContext = createContext(null);
export const FilterContext = createContext<FilterContextProps>(null); export const FilterContext = createContext<FilterContextProps>(null);
export const FilterLogicContext = createContext(null);

View File

@ -1,10 +1,19 @@
import { Field } from '@formily/core';
import { useField, useFieldSchema } from '@formily/react';
import flat from 'flat';
import { useTranslation } from 'react-i18next';
import { useBlockRequestContext } from '../../../block-provider'; import { useBlockRequestContext } from '../../../block-provider';
import { useCollection, useCollectionManager } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
export const useFilterOptions = (collectionName: string) => { export const useFilterOptions = (collectionName: string) => {
const fieldSchema = useFieldSchema();
const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
const { getCollectionFields, getInterface } = useCollectionManager(); const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName); const fields = getCollectionFields(collectionName);
const field2option = (field, nochildren) => { const field2option = (field, nochildren) => {
if (fieldNames.length && !nochildren && !fieldNames.includes(field.name)) {
return;
}
if (!field.interface) { if (!field.interface) {
return; return;
} }
@ -46,15 +55,45 @@ export const useFilterOptions = (collectionName: string) => {
return getOptions(fields); return getOptions(fields);
}; };
const isEmpty = (obj) => {
return obj && Object.keys(obj).length === 0 && Object.getPrototypeOf(obj) === Object.prototype;
};
const removeNullCondition = (filter) => {
const items = flat(filter);
console.log('filter', items);
const values = {};
for (const key in items) {
const value = items[key];
if (value !== null && !isEmpty(value)) {
values[key] = value;
}
}
return flat.unflatten(values);
};
export const useFilterActionProps = () => { export const useFilterActionProps = () => {
const { name } = useCollection(); const { name } = useCollection();
const options = useFilterOptions(name); const options = useFilterOptions(name);
const { service } = useBlockRequestContext(); const { service } = useBlockRequestContext();
const field = useField<Field>();
const { t } = useTranslation();
return { return {
options, options,
onSubmit({ filter }) { onSubmit(values) {
console.log('filter', filter) const filter = removeNullCondition(values?.filter);
service.run({ ...service.params?.[0], filter }); service.run({ ...service.params?.[0], filter });
const items = filter?.$and || filter?.$or;
if (items?.length) {
field.title = t('{{count}} filter items', { count: items?.length || 0 });
} else {
field.title = t('Filter');
}
},
onReset(values) {
const filter = removeNullCondition(values?.filter);
service.run({ ...service.params?.[0], filter });
field.title = t('Filter');
}, },
}; };
}; };

View File

@ -3,7 +3,7 @@ import { merge } from '@formily/shared';
import flat from 'flat'; import flat from 'flat';
import get from 'lodash/get'; import get from 'lodash/get';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { FilterContext } from './context'; import { FilterContext, FilterLogicContext } from './context';
// import { useValues } from './useValues'; // import { useValues } from './useValues';
const findOption = (dataIndex = [], options) => { const findOption = (dataIndex = [], options) => {
@ -21,6 +21,7 @@ const findOption = (dataIndex = [], options) => {
export const useValues = () => { export const useValues = () => {
const field = useField<any>(); const field = useField<any>();
const logic = useContext(FilterLogicContext);
const { options } = useContext(FilterContext); const { options } = useContext(FilterContext);
const data2value = () => { const data2value = () => {
field.value = flat.unflatten({ field.value = flat.unflatten({
@ -55,7 +56,7 @@ export const useValues = () => {
}; };
useEffect(() => { useEffect(() => {
value2data(); value2data();
}, []); }, [logic]);
return { return {
fields: options, fields: options,
...field.data, ...field.data,
@ -75,7 +76,7 @@ export const useValues = () => {
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue); const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
field.data.operator = operator; field.data.operator = operator;
field.data.schema = merge(field.data.schema, operator.schema); field.data.schema = merge(field.data.schema, operator.schema);
field.data.value = null; field.data.value = operator.noValue ? true : null;
data2value(); data2value();
console.log('setOperator', field.data); console.log('setOperator', field.data);
}, },

View File

@ -200,6 +200,10 @@ SchemaSettings.Item = (props) => {
); );
}; };
SchemaSettings.ItemGroup = (props) => {
return <Menu.ItemGroup {...props} />;
};
SchemaSettings.SubMenu = (props) => { SchemaSettings.SubMenu = (props) => {
return <Menu.SubMenu {...props} />; return <Menu.SubMenu {...props} />;
}; };
@ -245,7 +249,13 @@ SchemaSettings.SelectItem = (props) => {
<SchemaSettings.Item {...others}> <SchemaSettings.Item {...others}>
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}> <div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
{title} {title}
<Select bordered={false} defaultValue={value} onChange={onChange} options={options} style={{ textAlign: 'right', minWidth: 100 }} /> <Select
bordered={false}
defaultValue={value}
onChange={onChange}
options={options}
style={{ textAlign: 'right', minWidth: 100 }}
/>
</div> </div>
</SchemaSettings.Item> </SchemaSettings.Item>
); );