feat: association-filter-improve (#1606)

* feat: snapshot-i18n fix

* feat: association-filter-improve

add data scope for association filter
add default sort for association filter

* feat(association-filter-improve): close association-filter filter fix

* feat(association-filter-improve): refresh on table refresh

* feat(association-filter-improve): refresh on table refresh

* feat(association-filter-improve): remove field filter fixed

* feat(association-filter-improve): filter fix

* fix: missing props

* refactor: re design

* fix: data scope not work

* feat: support sorting rule

* feat: support configure default collapse

* fix: multiple association item will not work

* fix: ts error

* fix: ts error

---------

Co-authored-by: anuoua <anuoua@gmail.com>
This commit is contained in:
Dunqing 2023-03-30 09:00:56 +08:00 committed by GitHub
parent 920aab949a
commit 0853915f23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 212 additions and 67 deletions

View File

@ -802,6 +802,7 @@ export const useAssociationFilterProps = () => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const valueKey = collectionField?.targetKey || 'id'; const valueKey = collectionField?.targetKey || 'id';
const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey; const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
const field = useField()
const collectionFieldName = collectionField.name; const collectionFieldName = collectionField.name;
const { data, params, run } = useRequest( const { data, params, run } = useRequest(
{ {
@ -811,18 +812,19 @@ export const useAssociationFilterProps = () => {
fields: [labelKey, valueKey], fields: [labelKey, valueKey],
pageSize: 200, pageSize: 200,
page: 1, page: 1,
...field.componentProps?.params
}, },
}, },
{ {
refreshDeps: [labelKey, valueKey], refreshDeps: [labelKey, valueKey, JSON.stringify(field.componentProps?.params || {})],
debounceWait: 300, debounceWait: 300,
}, },
); );
const list = data?.data || []; const list = data?.data || [];
const onSelected = (value) => { const onSelected = (value) => {
const filters = service.params?.[1]?.filters || {}; const filters = service.params?.[1]?.filters || {};
if (value.length) { if (value.length) {
filters[`af.${collectionFieldName}`] = { filters[`af.${collectionFieldName}`] = {
[`${collectionFieldName}.${valueKey}.$in`]: value, [`${collectionFieldName}.${valueKey}.$in`]: value,
@ -830,7 +832,6 @@ export const useAssociationFilterProps = () => {
} else { } else {
delete filters[`af.${collectionFieldName}`]; delete filters[`af.${collectionFieldName}`];
} }
service.run( service.run(
{ {
...service.params?.[0], ...service.params?.[0],
@ -969,6 +970,7 @@ export const useAssociationFilterBlockProps = () => {
}); });
}; };
return { return {
/** 渲染 Collapse 的列表数据 */ /** 渲染 Collapse 的列表数据 */
list, list,

View File

@ -115,6 +115,7 @@ export default {
'Collapse all':'Collapse all', 'Collapse all':'Collapse all',
'Expand all':'Expand all', 'Expand all':'Expand all',
'Expand/Collapse':'Expand/Collapse', 'Expand/Collapse':'Expand/Collapse',
'Default collapse':'Default collapse',
"Tree table":"Tree table", "Tree table":"Tree table",
"Custom field display name": "Custom field display name", "Custom field display name": "Custom field display name",
"Display fields": "Display collection fields", "Display fields": "Display collection fields",

View File

@ -63,6 +63,7 @@ export default {
'Collapse all':'全部收起', 'Collapse all':'全部收起',
'Expand all':'全部展开', 'Expand all':'全部展开',
'Expand/Collapse':'展开/折叠', 'Expand/Collapse':'展开/折叠',
'Default collapse':'默认展开',
'Tree collection': '树结构表', 'Tree collection': '树结构表',
"Tree table":"树表格", "Tree table":"树表格",
'Parent ID': '父记录ID', 'Parent ID': '父记录ID',

View File

@ -4,6 +4,8 @@ import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../api-client'; import { useAPIClient } from '../../../api-client';
import { createDesignable, SchemaComponentContext, useDesignable } from '../..'; import { createDesignable, SchemaComponentContext, useDesignable } from '../..';
import { ActionInitializer } from '../../../schema-initializer/items/ActionInitializer'; import { ActionInitializer } from '../../../schema-initializer/items/ActionInitializer';
import { useBlockRequestContext } from '../../../block-provider';
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
export const ActionBarAssociationFilterAction = (props) => { export const ActionBarAssociationFilterAction = (props) => {
const { refresh } = useContext(SchemaComponentContext); const { refresh } = useContext(SchemaComponentContext);
@ -11,12 +13,22 @@ export const ActionBarAssociationFilterAction = (props) => {
const api = useAPIClient(); const api = useAPIClient();
const { t } = useTranslation(); const { t } = useTranslation();
const dn = createDesignable({ t, api, refresh, current: fieldSchema }); const dn = createDesignable({ t, api, refresh, current: fieldSchema });
const { service, props: blockProps } = useBlockRequestContext();
dn.loadAPIClientEvents(); dn.loadAPIClientEvents();
const handleInsert = (s: Schema) => { const handleInsert = (s: Schema) => {
dn.insertBeforeBegin(s); dn.insertBeforeBegin(s);
}; };
const handleRemove = (schema, remove) => {
remove(schema);
service.run({
...service.params?.[0],
filter: mergeFilter([blockProps?.params?.filter]),
});
};
const schema = { const schema = {
type: 'void', type: 'void',
'x-action': 'associateFilter', 'x-action': 'associateFilter',
@ -28,6 +40,7 @@ export const ActionBarAssociationFilterAction = (props) => {
const newProps = { const newProps = {
...props, ...props,
insert: handleInsert, insert: handleInsert,
remove: handleRemove,
wrap: (s) => s, wrap: (s) => s,
}; };

View File

@ -1,44 +0,0 @@
import { useFieldSchema } from '@formily/react';
import React, { createContext } from 'react';
import { useRequest } from '../../../api-client';
import { AssociationFilter } from './AssociationFilter';
export const useAssociationFieldService = (props) => {
const collectionField = AssociationFilter.useAssociationField();
const fieldSchema = useFieldSchema();
const valueKey = collectionField?.targetKey || 'id';
const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
const service = useRequest(
{
resource: collectionField.target,
action: 'list',
params: {
fields: [labelKey, valueKey],
pageSize: 200,
page: 1,
...props.params,
},
},
{
refreshDeps: [labelKey, valueKey],
debounceWait: 300,
},
);
return service;
};
export type AssociationItemContextValue = {
service: ReturnType<typeof useRequest>;
};
export const AssociationItemContext = createContext<AssociationItemContextValue>({ service: undefined! });
export const AssociationItemDecorator: React.FC = (props) => {
const service = useAssociationFieldService(props);
return <AssociationItemContext.Provider value={{ service }}>{props.children}</AssociationItemContext.Provider>;
};

View File

@ -1,21 +1,33 @@
import { ISchema, useFieldSchema } from '@formily/react'; import { ISchema, useField, useFieldSchema } from '@formily/react';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCollectionManager } from '../../../collection-manager'; import {
useCollection,
useCollectionFilterOptions,
useCollectionManager,
useSortFields,
} from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useCompile, useDesignable } from '../../hooks'; import { useCompile, useDesignable } from '../../hooks';
import { AssociationFilter } from './AssociationFilter'; import _ from 'lodash';
export const AssociationFilterItemDesigner = (props) => { export const AssociationFilterItemDesigner = (props) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const field = useField();
const { t } = useTranslation(); const { t } = useTranslation();
const collectionField = AssociationFilter.useAssociationField(); const { getCollectionJoinField } = useCollectionManager();
const { getField } = useCollection();
const collectionField = getField(fieldSchema['name']!) || getCollectionJoinField(fieldSchema['x-collection-field']);
const filterEnum = collectionField?.target ? useCollectionFilterOptions(collectionField?.target) : [];
const defaultFilter = fieldSchema?.['x-component-props']?.params?.filter || {};
const { getCollectionFields } = useCollectionManager(); const { getCollectionFields } = useCollectionManager();
const compile = useCompile(); const compile = useCompile();
const { dn } = useDesignable(); const { dn } = useDesignable();
const targetFields = getCollectionFields(collectionField.target) ?? []; const targetFields = collectionField?.target ? getCollectionFields(collectionField?.target) : [];
const options = targetFields const options = targetFields
.filter( .filter(
@ -42,6 +54,20 @@ export const AssociationFilterItemDesigner = (props) => {
dn.refresh(); dn.refresh();
}; };
const sortFields = useSortFields(collectionField?.target);
const defaultSort = fieldSchema?.['x-component-props']?.params?.sort || [];
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {
field: item.substring(1),
direction: 'desc',
}
: {
field: item,
direction: 'asc',
};
});
return ( return (
<GeneralSchemaDesigner {...props} disableInitializer={true}> <GeneralSchemaDesigner {...props} disableInitializer={true}>
<SchemaSettings.ModalItem <SchemaSettings.ModalItem
@ -75,6 +101,67 @@ export const AssociationFilterItemDesigner = (props) => {
dn.refresh(); dn.refresh();
}} }}
/> />
<SchemaSettings.SwitchItem
title={t('Default collapse')}
checked={field.componentProps.defaultCollapse}
onChange={(v) => {
field.componentProps.defaultCollapse = v;
fieldSchema['x-component-props']['defaultCollapse'] = v;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-component-props': fieldSchema['x-component-props'],
},
});
dn.refresh();
}}
/>
<SchemaSettings.ModalItem
title={t('Set the data scope')}
schema={
{
type: 'object',
title: t('Set the data scope'),
properties: {
filter: {
default: defaultFilter,
enum: filterEnum,
'x-component': 'Filter',
'x-component-props': {},
},
},
} as ISchema
}
onSubmit={({ filter }) => {
_.set(field.componentProps, 'params', {
...field.componentProps?.params,
filter,
});
fieldSchema['x-component-props']['params'] = field.componentProps.params;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-component-props': fieldSchema['x-component-props'],
},
});
}}
/>
<SchemaSettings.DefaultSortingRules
sort={sort}
sortFields={sortFields}
onSubmit={({ sort }) => {
_.set(field.componentProps, 'params', {
...field.componentProps?.params,
sort: sort.map((item) => {
return item.direction === 'desc' ? `-${item.field}` : item.field;
}),
});
fieldSchema['x-component-props']['params'] = field.componentProps.params;
dn.emit('patch', {
schema: fieldSchema,
});
}}
/>
<SchemaSettings.SelectItem <SchemaSettings.SelectItem
key="title-field" key="title-field"
title={t('Title field')} title={t('Title field')}

View File

@ -3,10 +3,9 @@ import { css } from '@emotion/css';
import { useFieldSchema } from '@formily/react'; import { useFieldSchema } from '@formily/react';
import { Col, Collapse, Input, Row, Tree } from 'antd'; import { Col, Collapse, Input, Row, Tree } from 'antd';
import cls from 'classnames'; import cls from 'classnames';
import React, { ChangeEvent, MouseEvent, useState } from 'react'; import React, { ChangeEvent, MouseEvent, useMemo, useState } from 'react';
import { useAssociationFilterProps } from '../../../block-provider/hooks';
import { SortableItem } from '../../common'; import { SortableItem } from '../../common';
import { useCompile, useDesigner } from '../../hooks'; import { useCompile, useDesigner, useProps } from '../../hooks';
import { AssociationFilter } from './AssociationFilter'; import { AssociationFilter } from './AssociationFilter';
const { Panel } = Collapse; const { Panel } = Collapse;
@ -15,11 +14,9 @@ export const AssociationFilterItem = (props) => {
const collectionField = AssociationFilter.useAssociationField(); const collectionField = AssociationFilter.useAssociationField();
// 把一些可定制的状态通过 hook 提取出去了,为了兼容之前添加的 Table 区块,这里加了个默认值 // 把一些可定制的状态通过 hook 提取出去了,为了兼容之前添加的 Table 区块,这里加了个默认值
const { useProps = useAssociationFilterProps } = props;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const Designer = useDesigner(); const Designer = useDesigner();
const compile = useCompile(); const compile = useCompile();
const { const {
list, list,
onSelected, onSelected,
@ -28,10 +25,12 @@ export const AssociationFilterItem = (props) => {
run, run,
valueKey: _valueKey, valueKey: _valueKey,
labelKey: _labelKey, labelKey: _labelKey,
} = useProps(); defaultCollapse,
} = useProps(props);
const [searchVisible, setSearchVisible] = useState(false); const [searchVisible, setSearchVisible] = useState(false);
const defaultActiveKeyCollapse = useMemo<React.Key[]>(() => (defaultCollapse ? [collectionField.name] : []), []);
const valueKey = _valueKey || collectionField?.targetKey || 'id'; const valueKey = _valueKey || collectionField?.targetKey || 'id';
const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey; const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
@ -131,11 +130,7 @@ export const AssociationFilterItem = (props) => {
)} )}
> >
<Designer /> <Designer />
<Collapse <Collapse defaultActiveKey={defaultActiveKeyCollapse} ghost expandIcon={searchVisible ? () => null : undefined}>
defaultActiveKey={[collectionField.uiSchemaUid]}
ghost
expandIcon={searchVisible ? () => null : undefined}
>
<Panel <Panel
className={css` className={css`
& .ant-collapse-content-box { & .ant-collapse-content-box {
@ -216,7 +211,7 @@ export const AssociationFilterItem = (props) => {
</Col> </Col>
</Row> </Row>
} }
key={collectionField.uiSchemaUid} key={defaultActiveKeyCollapse[0]}
> >
<Tree <Tree
style={{ padding: '16px 0' }} style={{ padding: '16px 0' }}

View File

@ -5,8 +5,13 @@ import { SchemaInitializer } from '..';
import { useCurrentSchema } from '../utils'; import { useCurrentSchema } from '../utils';
export const InitializerWithSwitch = (props) => { export const InitializerWithSwitch = (props) => {
const { type, schema, item, insert } = props; const { type, schema, item, insert, remove: passInRemove } = props;
const { exists, remove } = useCurrentSchema(schema?.[type] || item?.schema?.[type], type, item.find, item.remove); const { exists, remove } = useCurrentSchema(
schema?.[type] || item?.schema?.[type],
type,
item.find,
passInRemove ?? item.remove,
);
return ( return (
<SchemaInitializer.SwitchItem <SchemaInitializer.SwitchItem
checked={exists} checked={exists}

View File

@ -1,5 +1,6 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { FormDialog, FormItem, FormLayout, Input, ArrayCollapse, ArrayItems } from '@formily/antd'; import { ArrayItems } from '@formily/antd';
import { FormDialog, FormItem, FormLayout, Input, ArrayCollapse } from '@formily/antd';
import { createForm, Field, GeneralField } from '@formily/core'; import { createForm, Field, GeneralField } from '@formily/core';
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react'; import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
import _ from 'lodash'; import _ from 'lodash';
@ -853,6 +854,90 @@ SchemaSettings.BlockTitleItem = () => {
); );
}; };
SchemaSettings.DefaultSortingRules = (props) => {
const { sort, sortFields, onSubmit } = props;
const { t } = useTranslation();
return (
<SchemaSettings.ModalItem
title={t('Set default sorting rules')}
components={{ ArrayItems }}
schema={
{
type: 'object',
title: t('Set default sorting rules'),
properties: {
sort: {
type: 'array',
default: sort,
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
field: {
type: 'string',
enum: sortFields,
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 260,
},
},
},
direction: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
optionType: 'button',
},
enum: [
{
label: t('ASC'),
value: 'asc',
},
{
label: t('DESC'),
value: 'desc',
},
],
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: t('Add sort field'),
'x-component': 'ArrayItems.Addition',
},
},
},
},
} as ISchema
}
onSubmit={onSubmit}
/>
);
};
SchemaSettings.LinkageRules = (props) => { SchemaSettings.LinkageRules = (props) => {
const { collectionName } = props; const { collectionName } = props;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();