feat: improve filter component
This commit is contained in:
parent
53900c1e9e
commit
20680d3bc7
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -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>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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);
|
@ -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');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user