From 20680d3bc708cc78c9799bf3f3b3f9caca02a48c Mon Sep 17 00:00:00 2001 From: chenos <chenlinxh@gmail.com> Date: Thu, 7 Apr 2022 17:34:29 +0800 Subject: [PATCH] feat: improve filter component --- .../antd/filter/DynamicComponent.tsx | 6 + .../antd/filter/Filter.Action.Designer.tsx | 75 ++++++---- .../antd/filter/FilterAction.tsx | 7 +- .../antd/filter/FilterGroup.tsx | 129 +++++++++--------- .../schema-component/antd/filter/context.ts | 1 + .../antd/filter/useFilterActionProps.ts | 43 +++++- .../schema-component/antd/filter/useValues.ts | 7 +- .../src/schema-settings/SchemaSettings.tsx | 12 +- 8 files changed, 183 insertions(+), 97 deletions(-) diff --git a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx index 61e00673b..d8a6cad3f 100644 --- a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx +++ b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx @@ -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, diff --git a/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx index bfe4b4d08..912f2f8e5 100644 --- a/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx +++ b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx @@ -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 diff --git a/packages/client/src/schema-component/antd/filter/FilterAction.tsx b/packages/client/src/schema-component/antd/filter/FilterAction.tsx index c56e4f820..2269d6528 100644 --- a/packages/client/src/schema-component/antd/filter/FilterAction.tsx +++ b/packages/client/src/schema-component/antd/filter/FilterAction.tsx @@ -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); }} > diff --git a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx index b2ded2510..bc053bf9f 100644 --- a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx +++ b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx @@ -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> ); }); diff --git a/packages/client/src/schema-component/antd/filter/context.ts b/packages/client/src/schema-component/antd/filter/context.ts index 87f36caee..ccb5733f1 100644 --- a/packages/client/src/schema-component/antd/filter/context.ts +++ b/packages/client/src/schema-component/antd/filter/context.ts @@ -11,3 +11,4 @@ export interface FilterContextProps { export const RemoveConditionContext = createContext(null); export const FilterContext = createContext<FilterContextProps>(null); +export const FilterLogicContext = createContext(null); \ No newline at end of file diff --git a/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts b/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts index 02f181a8f..04870a70c 100644 --- a/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts +++ b/packages/client/src/schema-component/antd/filter/useFilterActionProps.ts @@ -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'); }, }; }; diff --git a/packages/client/src/schema-component/antd/filter/useValues.ts b/packages/client/src/schema-component/antd/filter/useValues.ts index 9a0cee894..2ddf9d4f5 100644 --- a/packages/client/src/schema-component/antd/filter/useValues.ts +++ b/packages/client/src/schema-component/antd/filter/useValues.ts @@ -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); }, diff --git a/packages/client/src/schema-settings/SchemaSettings.tsx b/packages/client/src/schema-settings/SchemaSettings.tsx index 86c287d07..cc5dd51b4 100644 --- a/packages/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/client/src/schema-settings/SchemaSettings.tsx @@ -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> );