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

View File

@ -1,16 +1,66 @@
import { ISchema, useField, useFieldSchema } from '@formily/react';
import React from 'react';
import { useDesignable } from '../..';
import { useCollection, useCollectionManager } from '../../../collection-manager';
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) => {
const initialValue = {};
const field = useField();
const fieldSchema = useFieldSchema();
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 (
<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
title={'编辑'}
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.Remove
removeParentsIfNoChildren

View File

@ -17,7 +17,7 @@ export const FilterAction = observer((props: any) => {
const [visible, setVisible] = useState(false);
const { designable, dn } = useDesignable();
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);
return (
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
@ -58,7 +58,7 @@ export const FilterAction = observer((props: any) => {
<Button
onClick={async () => {
await form.reset();
onReset?.();
onReset?.(form.values);
field.title = t('Filter');
setVisible(false);
}}
@ -72,9 +72,6 @@ export const FilterAction = observer((props: any) => {
e.preventDefault();
e.stopPropagation();
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);
}}
>

View File

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

View File

@ -11,3 +11,4 @@ export interface FilterContextProps {
export const RemoveConditionContext = createContext(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 { useCollection, useCollectionManager } from '../../../collection-manager';
export const useFilterOptions = (collectionName: string) => {
const fieldSchema = useFieldSchema();
const fieldNames = fieldSchema?.['x-component-props']?.fieldNames || [];
const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName);
const field2option = (field, nochildren) => {
if (fieldNames.length && !nochildren && !fieldNames.includes(field.name)) {
return;
}
if (!field.interface) {
return;
}
@ -46,15 +55,45 @@ export const useFilterOptions = (collectionName: string) => {
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 = () => {
const { name } = useCollection();
const options = useFilterOptions(name);
const { service } = useBlockRequestContext();
const field = useField<Field>();
const { t } = useTranslation();
return {
options,
onSubmit({ filter }) {
console.log('filter', filter)
onSubmit(values) {
const filter = removeNullCondition(values?.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 get from 'lodash/get';
import { useContext, useEffect } from 'react';
import { FilterContext } from './context';
import { FilterContext, FilterLogicContext } from './context';
// import { useValues } from './useValues';
const findOption = (dataIndex = [], options) => {
@ -21,6 +21,7 @@ const findOption = (dataIndex = [], options) => {
export const useValues = () => {
const field = useField<any>();
const logic = useContext(FilterLogicContext);
const { options } = useContext(FilterContext);
const data2value = () => {
field.value = flat.unflatten({
@ -55,7 +56,7 @@ export const useValues = () => {
};
useEffect(() => {
value2data();
}, []);
}, [logic]);
return {
fields: options,
...field.data,
@ -75,7 +76,7 @@ export const useValues = () => {
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
field.data.operator = operator;
field.data.schema = merge(field.data.schema, operator.schema);
field.data.value = null;
field.data.value = operator.noValue ? true : null;
data2value();
console.log('setOperator', field.data);
},

View File

@ -200,6 +200,10 @@ SchemaSettings.Item = (props) => {
);
};
SchemaSettings.ItemGroup = (props) => {
return <Menu.ItemGroup {...props} />;
};
SchemaSettings.SubMenu = (props) => {
return <Menu.SubMenu {...props} />;
};
@ -245,7 +249,13 @@ SchemaSettings.SelectItem = (props) => {
<SchemaSettings.Item {...others}>
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
{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>
</SchemaSettings.Item>
);