feat: association filter (#1274)
* feat: association filter * feat: association filter update * feat: association filter remove unused designer * feat: feat: association filter lowercase fix * feat: feat: association filter lowercase fix * feat: feat: association filter configure field fix * feat: association field remove AssociationFieldsFilter.Designer * feat: association field fixed layout * feat: associate filter multiple to simple * feat: association field "title" to "id" * feat: associaion filter interface limit * feat: association filter move to association-filter folder * feat: association filter change style * fix: card item error * fix: add RenderChildrenWithAssociationFilter * feat: association-filter fix style * feat: associate filter fix filter params * feat: assocition filter layout fix * feat: association filter change schema * feat: association filter rename * feat: association filter rename * feat: association filter break layout fix * feat: association filter fix table layout * feat: association filter fix ActionBar style * feat: association filter fix ActionBar style * feat: association filter bug fix * feat: association filter change valueKey * feat: association filter remove collectionFieldKey * feat: improve code * feat: association filter style fix * feat: association filter custom title * feat: association filter max height overscroll * feat: association filter add linkTo & i18n * feat: association filter ellipsis; * feat: association fields add linkTo Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
8668a18359
commit
a69074ead4
@ -1,12 +1,22 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
import template from 'lodash/template';
|
import template from 'lodash/template';
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ACLCollectionProvider, TableFieldResource, useAPIClient, useRecord, WithoutTableFieldResource } from '../';
|
import {
|
||||||
|
ACLCollectionProvider,
|
||||||
|
TableFieldResource,
|
||||||
|
useAPIClient,
|
||||||
|
useDesignable,
|
||||||
|
useRecord,
|
||||||
|
WithoutTableFieldResource,
|
||||||
|
} from '../';
|
||||||
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
||||||
import { useRecordIndex } from '../record-provider';
|
import { useRecordIndex } from '../record-provider';
|
||||||
|
import { SharedFilterProvider } from './SharedFilterProvider';
|
||||||
|
|
||||||
export const BlockResourceContext = createContext(null);
|
export const BlockResourceContext = createContext(null);
|
||||||
export const BlockAssociationContext = createContext(null);
|
export const BlockAssociationContext = createContext(null);
|
||||||
@ -143,6 +153,53 @@ export const useBlockRequestContext = () => {
|
|||||||
return useContext(BlockRequestContext);
|
return useContext(BlockRequestContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const RenderChildrenWithAssociationFilter: React.FC<any> = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { findComponent } = useDesignable();
|
||||||
|
const field = useField();
|
||||||
|
const Component = findComponent(field.component?.[0]) || React.Fragment;
|
||||||
|
const associationFilterSchema = fieldSchema.reduceProperties((buf, s) => {
|
||||||
|
if (s['x-component'] === 'AssociationFilter') {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
if (associationFilterSchema) {
|
||||||
|
return (
|
||||||
|
<Component {...field.componentProps}>
|
||||||
|
<Row gutter={16} wrap={false}>
|
||||||
|
<Col
|
||||||
|
className={css`
|
||||||
|
width: 200px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<RecursionField
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(s) => s['x-component'] === 'AssociationFilter'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
className={css`
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<RecursionField
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
filterProperties={(s) => s['x-component'] !== 'AssociationFilter'}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return props.children;
|
||||||
|
};
|
||||||
|
|
||||||
export const BlockProvider = (props) => {
|
export const BlockProvider = (props) => {
|
||||||
const { collection, association } = props;
|
const { collection, association } = props;
|
||||||
const resource = useResource(props);
|
const resource = useResource(props);
|
||||||
@ -150,7 +207,9 @@ export const BlockProvider = (props) => {
|
|||||||
<MaybeCollectionProvider collection={collection}>
|
<MaybeCollectionProvider collection={collection}>
|
||||||
<BlockAssociationContext.Provider value={association}>
|
<BlockAssociationContext.Provider value={association}>
|
||||||
<BlockResourceContext.Provider value={resource}>
|
<BlockResourceContext.Provider value={resource}>
|
||||||
<BlockRequestProvider {...props}>{props.children}</BlockRequestProvider>
|
<BlockRequestProvider {...props}>
|
||||||
|
<SharedFilterProvider {...props} />
|
||||||
|
</BlockRequestProvider>
|
||||||
</BlockResourceContext.Provider>
|
</BlockResourceContext.Provider>
|
||||||
</BlockAssociationContext.Provider>
|
</BlockAssociationContext.Provider>
|
||||||
</MaybeCollectionProvider>
|
</MaybeCollectionProvider>
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import React, { createContext, FC, useState } from 'react';
|
||||||
|
|
||||||
|
export enum SHARED_FILTER_CONDITION {
|
||||||
|
AND = '$and',
|
||||||
|
OR = '$or',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SharedFilter = {
|
||||||
|
[K in SHARED_FILTER_CONDITION]?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SharedFilterStore = Record<string, SharedFilter>;
|
||||||
|
|
||||||
|
export type SharedFilterContextValue = {
|
||||||
|
sharedFilterStore: SharedFilter;
|
||||||
|
setSharedFilterStore: (filterStore: SharedFilterStore) => void;
|
||||||
|
getFilterParams: (filterStore?: SharedFilterStore) => any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SharedFilterContext = createContext<SharedFilterContextValue>({
|
||||||
|
sharedFilterStore: {},
|
||||||
|
setSharedFilterStore: undefined!,
|
||||||
|
getFilterParams: undefined!,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const concatFilter = (f1: SharedFilter, f2: SharedFilter): SharedFilter => {
|
||||||
|
const newAnd = [f1.$and, f2.$and].filter((i) => i);
|
||||||
|
const newOr = [f1.$or, f2.$or].filter((i) => i);
|
||||||
|
const newFilter: SharedFilter = {};
|
||||||
|
newAnd.length && (newFilter.$and = newAnd);
|
||||||
|
newOr.length && (newFilter.$or = newOr);
|
||||||
|
return newFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SharedFilterProvider: FC<{ params?: any }> = (props) => {
|
||||||
|
const [sharedFilterStore, setSharedFilterStoreUnwrap] = useState<Record<string, SharedFilter>>({});
|
||||||
|
|
||||||
|
const setSharedFilterStore = (associationFilter: Record<string, SharedFilter>) => {
|
||||||
|
setSharedFilterStoreUnwrap(associationFilter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilterParams = (filterStore?: SharedFilterStore) => {
|
||||||
|
const newAssociationFilterList = Object.entries(filterStore ?? sharedFilterStore).map(([key, filter]) => filter);
|
||||||
|
const newAssociationFilter = newAssociationFilterList.length
|
||||||
|
? {
|
||||||
|
$and: newAssociationFilterList,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return newAssociationFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SharedFilterContext.Provider
|
||||||
|
value={{
|
||||||
|
sharedFilterStore,
|
||||||
|
setSharedFilterStore,
|
||||||
|
getFilterParams,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SharedFilterContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -3,8 +3,9 @@ import { FormContext, Schema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useCollectionManager } from '../collection-manager';
|
import { useCollectionManager } from '../collection-manager';
|
||||||
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
import { useFixedSchema } from '../schema-component';
|
import { useFixedSchema } from '../schema-component';
|
||||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
|
||||||
|
|
||||||
export const TableBlockContext = createContext<any>({});
|
export const TableBlockContext = createContext<any>({});
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ const InternalTableBlockProvider = (props) => {
|
|||||||
rowKey,
|
rowKey,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
<RenderChildrenWithAssociationFilter {...props} />
|
||||||
</TableBlockContext.Provider>
|
</TableBlockContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { Schema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import React, { createContext, useContext, useEffect } from 'react';
|
import React, { createContext, useContext, useEffect } from 'react';
|
||||||
import { useCollectionManager } from '../collection-manager';
|
import { useCollectionManager } from '../collection-manager';
|
||||||
import { RecordProvider, useRecord } from '../record-provider';
|
import { RecordProvider, useRecord } from '../record-provider';
|
||||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
import { useFormBlockContext } from './FormBlockProvider';
|
import { useFormBlockContext } from './FormBlockProvider';
|
||||||
|
|
||||||
export const TableSelectorContext = createContext<any>({});
|
export const TableSelectorContext = createContext<any>({});
|
||||||
@ -27,7 +27,7 @@ const InternalTableSelectorProvider = (props) => {
|
|||||||
rowKey,
|
rowKey,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
<RenderChildrenWithAssociationFilter {...props} />
|
||||||
</TableSelectorContext.Provider>
|
</TableSelectorContext.Provider>
|
||||||
</RecordProvider>
|
</RecordProvider>
|
||||||
);
|
);
|
||||||
@ -59,7 +59,6 @@ export const TableSelectorProvider = (props) => {
|
|||||||
// const value = ctx.form.query(collectionFieldSchema?.name).value();
|
// const value = ctx.form.query(collectionFieldSchema?.name).value();
|
||||||
const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
|
const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
|
||||||
|
|
||||||
console.log('TableSelectorProvider', collectionFieldSchema, collectionField, record);
|
|
||||||
const params = { ...props.params };
|
const params = { ...props.params };
|
||||||
const appends = useAssociationNames(props.collection);
|
const appends = useAssociationNames(props.collection);
|
||||||
if (props.dragSort) {
|
if (props.dragSort) {
|
||||||
|
@ -223,7 +223,7 @@ export const overridingSchema: ISchema = {
|
|||||||
'x-component': 'OverridingCollectionField',
|
'x-component': 'OverridingCollectionField',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
'currentCollection': '{{ currentCollection }}',
|
currentCollection: '{{ currentCollection }}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
|
@ -133,6 +133,7 @@ export default {
|
|||||||
"Override": "Override",
|
"Override": "Override",
|
||||||
"Override field": "Override field",
|
"Override field": "Override field",
|
||||||
"Configure fields of {{title}}": "Configure fields of {{title}}",
|
"Configure fields of {{title}}": "Configure fields of {{title}}",
|
||||||
|
"Association fields filter": "Association fields filter",
|
||||||
"PK & FK fields": "PK & FK fields",
|
"PK & FK fields": "PK & FK fields",
|
||||||
"Association fields": "Association fields",
|
"Association fields": "Association fields",
|
||||||
"Parent collection fields": "Parent collection fields",
|
"Parent collection fields": "Parent collection fields",
|
||||||
@ -273,6 +274,7 @@ export default {
|
|||||||
"Allow uploading multiple files": "Allow uploading multiple files",
|
"Allow uploading multiple files": "Allow uploading multiple files",
|
||||||
"Configure calendar": "Configure calendar",
|
"Configure calendar": "Configure calendar",
|
||||||
"Title field": "Title field",
|
"Title field": "Title field",
|
||||||
|
"Custom title": "Custom title",
|
||||||
"Start date field": "Start date field",
|
"Start date field": "Start date field",
|
||||||
"End date field": "End date field",
|
"End date field": "End date field",
|
||||||
"Navigate": "Navigate",
|
"Navigate": "Navigate",
|
||||||
|
@ -138,6 +138,7 @@ export default {
|
|||||||
"Override": "重写",
|
"Override": "重写",
|
||||||
"Override field": "重写字段",
|
"Override field": "重写字段",
|
||||||
"Configure fields of {{title}}": "「{{title}}」的字段配置",
|
"Configure fields of {{title}}": "「{{title}}」的字段配置",
|
||||||
|
"Association fields filter": "关系筛选",
|
||||||
"PK & FK fields": "主外键字段",
|
"PK & FK fields": "主外键字段",
|
||||||
"Association fields": "关系字段",
|
"Association fields": "关系字段",
|
||||||
"System fields": "系统字段",
|
"System fields": "系统字段",
|
||||||
@ -326,6 +327,7 @@ export default {
|
|||||||
|
|
||||||
"Configure calendar": "配置日历",
|
"Configure calendar": "配置日历",
|
||||||
"Title field": "标题字段",
|
"Title field": "标题字段",
|
||||||
|
"Custom title": "自定义标题",
|
||||||
"Show lunar": "展示农历",
|
"Show lunar": "展示农历",
|
||||||
"Start date field": "开始日期字段",
|
"Start date field": "开始日期字段",
|
||||||
"End date field": "结束日期字段",
|
"End date field": "结束日期字段",
|
||||||
|
@ -4,11 +4,13 @@ import { Space } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSchemaInitializer } from '../../../schema-initializer';
|
import { useSchemaInitializer } from '../../../schema-initializer';
|
||||||
import { DndContext } from '../../common';
|
import { DndContext } from '../../common';
|
||||||
|
import { useDesignable } from '../../hooks';
|
||||||
|
|
||||||
export const ActionBar = observer((props: any) => {
|
export const ActionBar = observer((props: any) => {
|
||||||
const { layout = 'tow-columns', style, ...others } = props;
|
const { layout = 'tow-columns', style, ...others } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
|
const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
|
||||||
|
const { designable } = useDesignable();
|
||||||
if (layout === 'one-column') {
|
if (layout === 'one-column') {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', ...style }} {...others}>
|
<div style={{ display: 'flex', ...style }} {...others}>
|
||||||
@ -25,16 +27,21 @@ export const ActionBar = observer((props: any) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const hasActions = Object.keys(fieldSchema.properties ?? {}).length > 0;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={
|
||||||
display: 'flex',
|
!designable && !hasActions
|
||||||
justifyContent: 'space-between',
|
? undefined
|
||||||
alignItems: 'center',
|
: {
|
||||||
overflowX: 'auto',
|
display: 'flex',
|
||||||
flexShrink: 0,
|
justifyContent: 'space-between',
|
||||||
...style,
|
alignItems: 'center',
|
||||||
}}
|
overflowX: 'auto',
|
||||||
|
flexShrink: 0,
|
||||||
|
...style,
|
||||||
|
}
|
||||||
|
}
|
||||||
{...others}
|
{...others}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Schema, useFieldSchema } from '@formily/react';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAPIClient } from '../../../api-client';
|
||||||
|
import { createDesignable, SchemaComponentContext, useDesignable } from '../..';
|
||||||
|
import { ActionInitializer } from '../../../schema-initializer/items/ActionInitializer';
|
||||||
|
|
||||||
|
export const ActionBarAssociationFilterAction = (props) => {
|
||||||
|
const { refresh } = useContext(SchemaComponentContext);
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dn = createDesignable({ t, api, refresh, current: fieldSchema });
|
||||||
|
dn.loadAPIClientEvents();
|
||||||
|
|
||||||
|
const handleInsert = (s: Schema) => {
|
||||||
|
dn.insertBeforeBegin(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'void',
|
||||||
|
'x-action': 'associateFilter',
|
||||||
|
'x-initializer': 'AssociationFilter.Initializer',
|
||||||
|
'x-component': 'AssociationFilter',
|
||||||
|
properties: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newProps = {
|
||||||
|
...props,
|
||||||
|
insert: handleInsert,
|
||||||
|
wrap: (s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ActionInitializer {...newProps} schema={schema} />;
|
||||||
|
};
|
@ -0,0 +1,61 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollection } from '../../../collection-manager';
|
||||||
|
import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer';
|
||||||
|
|
||||||
|
export const AssociationFilterInitializer = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { fields } = useCollection();
|
||||||
|
|
||||||
|
const associatedFields = fields.filter((field) =>
|
||||||
|
['o2o', 'oho', 'obo', 'm2o', 'createdBy', 'updatedBy', 'o2m', 'm2m', 'linkTo'].includes(field.interface),
|
||||||
|
);
|
||||||
|
|
||||||
|
const items: SchemaInitializerItemOptions[] = associatedFields.map((field) => ({
|
||||||
|
type: 'item',
|
||||||
|
key: field.key,
|
||||||
|
title: field.uiSchema.title,
|
||||||
|
component: 'AssociationFilterDesignerDisplayField',
|
||||||
|
schema: {
|
||||||
|
name: field.name,
|
||||||
|
title: field.uiSchema.title,
|
||||||
|
type: 'void',
|
||||||
|
'x-designer': 'AssociationFilter.Item.Designer',
|
||||||
|
'x-component': 'AssociationFilter.Item',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: field.targetKey || 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const associatedFieldGroup: SchemaInitializerItemOptions = {
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: t('Association fields'),
|
||||||
|
children: items,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dividerItem: SchemaInitializerItemOptions = {
|
||||||
|
type: 'divider',
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteItem: SchemaInitializerItemOptions = {
|
||||||
|
type: 'item',
|
||||||
|
title: t('Delete'),
|
||||||
|
component: 'AssociationFilterDesignerDelete',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Button
|
||||||
|
className={css`
|
||||||
|
margin-top: 16px;
|
||||||
|
`}
|
||||||
|
icon={'SettingOutlined'}
|
||||||
|
title={t('Configure fields')}
|
||||||
|
items={[associatedFieldGroup, dividerItem, deleteItem]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,92 @@
|
|||||||
|
import { ISchema, useFieldSchema } from '@formily/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
|
import { useCompile, useDesignable } from '../../hooks';
|
||||||
|
import { AssociationFilter } from './AssociationFilter';
|
||||||
|
|
||||||
|
export const AssociationFilterItemDesigner = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const collectionField = AssociationFilter.useAssociationField();
|
||||||
|
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
|
||||||
|
const targetFields = getCollectionFields(collectionField.target) ?? [];
|
||||||
|
|
||||||
|
const options = targetFields
|
||||||
|
.filter(
|
||||||
|
(field) => field?.interface && ['id', 'input', 'phone', 'email', 'integer', 'number'].includes(field?.interface),
|
||||||
|
)
|
||||||
|
.map((field) => ({
|
||||||
|
value: field?.name,
|
||||||
|
label: compile(field?.uiSchema?.title) || field?.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onTitleFieldChange = (label) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
const fieldNames = {
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner {...props} disableInitializer={true}>
|
||||||
|
<SchemaSettings.ModalItem
|
||||||
|
title={t('Custom title')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('Custom title'),
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
default: fieldSchema?.title,
|
||||||
|
description: `${t('Original title: ')}${collectionField?.uiSchema?.title || fieldSchema?.title}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={({ title }) => {
|
||||||
|
if (title) {
|
||||||
|
// field.title = title;
|
||||||
|
fieldSchema.title = title;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
title: fieldSchema.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="title-field"
|
||||||
|
title={t('Title field')}
|
||||||
|
options={options}
|
||||||
|
value={fieldSchema['x-component-props']?.fieldNames?.label}
|
||||||
|
onChange={onTitleFieldChange}
|
||||||
|
/>
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,276 @@
|
|||||||
|
import { CloseOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { Col, Collapse, Input, Row, Tree } from 'antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import React, { ChangeEvent, MouseEvent, useContext, useState } from 'react';
|
||||||
|
import { useRequest } from '../../../api-client';
|
||||||
|
import { useBlockRequestContext } from '../../../block-provider';
|
||||||
|
import { SharedFilterContext } from '../../../block-provider/SharedFilterProvider';
|
||||||
|
import { SortableItem } from '../../common';
|
||||||
|
import { useCompile, useDesigner } from '../../hooks';
|
||||||
|
import { AssociationFilter } from './AssociationFilter';
|
||||||
|
|
||||||
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
|
export const AssociationFilterItem = (props) => {
|
||||||
|
const collectionField = AssociationFilter.useAssociationField();
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const Designer = useDesigner();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { service } = useBlockRequestContext();
|
||||||
|
const { setSharedFilterStore, sharedFilterStore, getFilterParams } = useContext(SharedFilterContext);
|
||||||
|
const [searchVisible, setSearchVisible] = useState(false);
|
||||||
|
|
||||||
|
const collectionFieldName = collectionField.name;
|
||||||
|
|
||||||
|
const valueKey = collectionField?.targetKey || 'id';
|
||||||
|
const labelKey = fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||||
|
|
||||||
|
const fieldNames = {
|
||||||
|
title: labelKey || valueKey,
|
||||||
|
key: valueKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, params, loading, run } = useRequest(
|
||||||
|
{
|
||||||
|
resource: collectionField.target,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
fields: [labelKey, valueKey],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refreshDeps: [labelKey, valueKey],
|
||||||
|
debounceWait: 300,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const treeData = data?.data || [];
|
||||||
|
|
||||||
|
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||||
|
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const onExpand = (expandedKeysValue: React.Key[]) => {
|
||||||
|
setExpandedKeys(expandedKeysValue);
|
||||||
|
setAutoExpandParent(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelect = (selectedKeysValue: React.Key[]) => {
|
||||||
|
setSelectedKeys(selectedKeysValue);
|
||||||
|
|
||||||
|
const orList = selectedKeysValue.map((item) => ({
|
||||||
|
[collectionFieldName]: {
|
||||||
|
[valueKey]: {
|
||||||
|
$eq: item,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newFilter =
|
||||||
|
orList.length > 0
|
||||||
|
? {
|
||||||
|
$or: orList,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const newAssociationFilterStore = {
|
||||||
|
...sharedFilterStore,
|
||||||
|
[collectionFieldName]: newFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSharedFilterStore(newAssociationFilterStore);
|
||||||
|
|
||||||
|
const paramFilter = getFilterParams(newAssociationFilterStore);
|
||||||
|
|
||||||
|
service.run({ ...service.params?.[0], page: 1, filter: paramFilter });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchToggle = (e: MouseEvent) => {
|
||||||
|
const filter = params?.[0]?.filter;
|
||||||
|
if (searchVisible || filter) {
|
||||||
|
run({
|
||||||
|
...params?.[0],
|
||||||
|
filter: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSearchVisible(!searchVisible);
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClick = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchInput = (e: ChangeEvent<any>) => {
|
||||||
|
run({
|
||||||
|
...params?.[0],
|
||||||
|
filter: {
|
||||||
|
[`${labelKey}.$includes`]: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = fieldSchema.title ?? collectionField.uiSchema?.title;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableItem
|
||||||
|
className={cls(
|
||||||
|
'nb-block-item',
|
||||||
|
props.className,
|
||||||
|
css`
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
> .general-schema-designer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.nb-form-item:hover {
|
||||||
|
> .general-schema-designer {
|
||||||
|
background: rgba(241, 139, 98, 0.06) !important;
|
||||||
|
border: 0 !important;
|
||||||
|
top: -5px !important;
|
||||||
|
bottom: -5px !important;
|
||||||
|
left: -5px !important;
|
||||||
|
right: -5px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .general-schema-designer {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: none;
|
||||||
|
border: 2px solid rgba(241, 139, 98, 0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
> .general-schema-designer-icons {
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
top: 2px;
|
||||||
|
line-height: 16px;
|
||||||
|
pointer-events: all;
|
||||||
|
.ant-space-item {
|
||||||
|
background-color: #f18b62;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
padding-left: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Designer />
|
||||||
|
<Collapse
|
||||||
|
defaultActiveKey={[collectionField.uiSchemaUid]}
|
||||||
|
ghost
|
||||||
|
expandIcon={searchVisible ? () => null : undefined}
|
||||||
|
>
|
||||||
|
<Panel
|
||||||
|
className={css`
|
||||||
|
& .ant-collapse-content-box {
|
||||||
|
padding: 0 8px !important;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
& .ant-collapse-header {
|
||||||
|
padding: 10px !important;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
header={
|
||||||
|
<Row
|
||||||
|
className={css`
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
height: 22px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
${searchVisible ? 'border-bottom: 1px solid #dcdcdc;' : ''}
|
||||||
|
`}
|
||||||
|
gutter={5}
|
||||||
|
>
|
||||||
|
<Col
|
||||||
|
title={compile(title)}
|
||||||
|
className={css`
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{searchVisible ? (
|
||||||
|
<Input
|
||||||
|
bordered={false}
|
||||||
|
autoFocus
|
||||||
|
placeholder="Search..."
|
||||||
|
className={css`
|
||||||
|
outline: none;
|
||||||
|
background: #fafafa;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
height: 20px;
|
||||||
|
padding: 4px;
|
||||||
|
&::placeholder {
|
||||||
|
color: #dcdcdc;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
onClick={handleSearchClick}
|
||||||
|
onChange={handleSearchInput}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
compile(title)
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
className={css`
|
||||||
|
flex: 0 0 auto;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{searchVisible ? (
|
||||||
|
<CloseOutlined
|
||||||
|
className={css`
|
||||||
|
color: #aeaeae !important;
|
||||||
|
font-size: 11px;
|
||||||
|
`}
|
||||||
|
onClick={handleSearchToggle}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SearchOutlined
|
||||||
|
className={css`
|
||||||
|
color: #aeaeae !important;
|
||||||
|
`}
|
||||||
|
onClick={handleSearchToggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
key={collectionField.uiSchemaUid}
|
||||||
|
>
|
||||||
|
<Tree
|
||||||
|
style={{ padding: '16px 0' }}
|
||||||
|
onExpand={onExpand}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
|
autoExpandParent={autoExpandParent}
|
||||||
|
treeData={treeData}
|
||||||
|
onSelect={onSelect}
|
||||||
|
fieldNames={fieldNames}
|
||||||
|
titleRender={(node) => compile(node[labelKey])}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
blockNode
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
</Collapse>
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,86 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import { useCollection } from '../../../collection-manager';
|
||||||
|
import { useSchemaInitializer } from '../../../schema-initializer';
|
||||||
|
import { SortableItem } from '../../common';
|
||||||
|
import { useDesigner } from '../../hooks';
|
||||||
|
import { AssociationFilterInitializer } from './AssociationFilter.Initializer';
|
||||||
|
import { AssociationFilterItem } from './AssociationFilter.Item';
|
||||||
|
import { AssociationFilterItemDesigner } from './AssociationFilter.Item.Designer';
|
||||||
|
|
||||||
|
export const AssociationFilter = (props) => {
|
||||||
|
const Designer = useDesigner();
|
||||||
|
const filedSchema = useFieldSchema();
|
||||||
|
|
||||||
|
const { exists, render } = useSchemaInitializer(filedSchema['x-initializer']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableItem
|
||||||
|
className={cls(
|
||||||
|
'nb-block-item',
|
||||||
|
props.className,
|
||||||
|
css`
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
> .general-schema-designer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.nb-form-item:hover {
|
||||||
|
> .general-schema-designer {
|
||||||
|
background: rgba(241, 139, 98, 0.06) !important;
|
||||||
|
border: 0 !important;
|
||||||
|
top: -5px !important;
|
||||||
|
bottom: -5px !important;
|
||||||
|
left: -5px !important;
|
||||||
|
right: -5px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .general-schema-designer {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: none;
|
||||||
|
border: 2px solid rgba(241, 139, 98, 0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
> .general-schema-designer-icons {
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
top: 2px;
|
||||||
|
line-height: 16px;
|
||||||
|
pointer-events: all;
|
||||||
|
.ant-space-item {
|
||||||
|
background-color: #f18b62;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
padding-left: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Designer />
|
||||||
|
{props.children}
|
||||||
|
{render()}
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AssociationFilter.Initializer = AssociationFilterInitializer;
|
||||||
|
AssociationFilter.Item = AssociationFilterItem as typeof AssociationFilterItem & {
|
||||||
|
Designer: typeof AssociationFilterItemDesigner;
|
||||||
|
};
|
||||||
|
AssociationFilter.Item.Designer = AssociationFilterItemDesigner;
|
||||||
|
|
||||||
|
AssociationFilter.useAssociationField = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
return React.useMemo(() => getField(fieldSchema.name as any), [fieldSchema.name]);
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { SchemaInitializer } from '../../../schema-initializer/SchemaInitializer';
|
||||||
|
import { createDesignable, SchemaComponentContext } from '../..';
|
||||||
|
import { useAPIClient } from '../../../api-client';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const AssociationFilterDesignerDelete = (props) => {
|
||||||
|
const { refresh } = useContext(SchemaComponentContext);
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dn = createDesignable({ t, api, refresh, current: fieldSchema });
|
||||||
|
dn.loadAPIClientEvents();
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
dn.remove(fieldSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Item onClick={handleClick}>
|
||||||
|
<div>{props.title}</div>
|
||||||
|
</SchemaInitializer.Item>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { merge } from '@formily/shared';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { SchemaInitializer } from '../../../schema-initializer';
|
||||||
|
import { useCurrentSchema } from '../../../schema-initializer/utils';
|
||||||
|
|
||||||
|
export const AssociationFilterDesignerDisplayField = (props) => {
|
||||||
|
const { schema, item, insert } = props;
|
||||||
|
const { exists, remove } = useCurrentSchema(schema.name, 'name', item.find, item.remove);
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.SwitchItem
|
||||||
|
checked={exists}
|
||||||
|
title={item.title}
|
||||||
|
onClick={() => {
|
||||||
|
if (exists) {
|
||||||
|
return remove();
|
||||||
|
}
|
||||||
|
const s = merge(schema || {}, item.schema || {});
|
||||||
|
item?.schemaInitialize?.(s);
|
||||||
|
insert(s);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -5,12 +5,13 @@ import { useSchemaTemplate } from '../../../schema-templates';
|
|||||||
import { BlockItem } from '../block-item';
|
import { BlockItem } from '../block-item';
|
||||||
|
|
||||||
export const CardItem: React.FC = (props) => {
|
export const CardItem: React.FC = (props) => {
|
||||||
|
const { children, ...restProps } = props;
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const templateKey = fieldSchema['x-template-key'];
|
const templateKey = fieldSchema['x-template-key'];
|
||||||
return templateKey && !template ? null : (
|
return templateKey && !template ? null : (
|
||||||
<BlockItem className={'noco-card-item'}>
|
<BlockItem className={'noco-card-item'}>
|
||||||
<Card style={{ marginBottom: 24 }} bordered={false} {...props}>
|
<Card style={{ marginBottom: 24 }} bordered={false} {...restProps}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Card>
|
</Card>
|
||||||
</BlockItem>
|
</BlockItem>
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { AntdSchemaComponentProvider, Filter, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import {
|
||||||
|
AntdSchemaComponentProvider,
|
||||||
|
SharedFilter,
|
||||||
|
Input,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const schema: any = {
|
const schema: any = {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ISchema, useForm } from '@formily/react';
|
import { ISchema, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
AntdSchemaComponentProvider,
|
AntdSchemaComponentProvider,
|
||||||
Filter,
|
SharedFilter,
|
||||||
Input,
|
Input,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
useActionContext,
|
useActionContext,
|
||||||
useRequest
|
useRequest,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
AntdSchemaComponentProvider,
|
AntdSchemaComponentProvider,
|
||||||
Filter,
|
SharedFilter,
|
||||||
Input,
|
Input,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
Select
|
Select,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
@ -2,13 +2,13 @@ import {
|
|||||||
AntdSchemaComponentProvider,
|
AntdSchemaComponentProvider,
|
||||||
CollectionManagerProvider,
|
CollectionManagerProvider,
|
||||||
CollectionProvider,
|
CollectionProvider,
|
||||||
Filter,
|
SharedFilter,
|
||||||
Input,
|
Input,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useFilterOptions
|
useFilterOptions,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export * from './Filter';
|
export * from './Filter';
|
||||||
export * from './useFilterActionProps';
|
export * from './useFilterActionProps';
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import flat from 'flat';
|
import flat from 'flat';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useBlockRequestContext } from '../../../block-provider';
|
import { useBlockRequestContext } from '../../../block-provider';
|
||||||
|
import { concatFilter, SharedFilterContext } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
|
||||||
export const useFilterOptions = (collectionName: string) => {
|
export const useFilterOptions = (collectionName: string) => {
|
||||||
@ -69,7 +71,10 @@ export const useFilterFieldOptions = (fields) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isEmpty = (obj) => {
|
const isEmpty = (obj) => {
|
||||||
return obj && Object.keys(obj).length === 0 && Object.getPrototypeOf(obj) === Object.prototype;
|
return (
|
||||||
|
(Array.isArray(obj) && obj.length === 0) ||
|
||||||
|
(obj && Object.keys(obj).length === 0 && Object.getPrototypeOf(obj) === Object.prototype)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeNullCondition = (filter) => {
|
export const removeNullCondition = (filter) => {
|
||||||
@ -77,26 +82,13 @@ export const removeNullCondition = (filter) => {
|
|||||||
const values = {};
|
const values = {};
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
const value = items[key];
|
const value = items[key];
|
||||||
if (value !== null && !isEmpty(value)) {
|
if (value != null && !isEmpty(value)) {
|
||||||
values[key] = value;
|
values[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flat.unflatten(values);
|
return flat.unflatten(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mergeFilter = (filter1, filter2) => {
|
|
||||||
if (filter1 && filter2) {
|
|
||||||
return { $and: [filter1, filter2] };
|
|
||||||
}
|
|
||||||
if (!filter1 && filter2) {
|
|
||||||
return filter2;
|
|
||||||
}
|
|
||||||
if (filter1 && !filter2) {
|
|
||||||
return filter1;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFilterActionProps = () => {
|
export const useFilterActionProps = () => {
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const options = useFilterOptions(name);
|
const options = useFilterOptions(name);
|
||||||
@ -107,6 +99,7 @@ export const useFilterActionProps = () => {
|
|||||||
export const useFilterFieldProps = ({ options, service, params }) => {
|
export const useFilterFieldProps = ({ options, service, params }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
|
const { sharedFilterStore, setSharedFilterStore, getFilterParams } = useContext(SharedFilterContext);
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
onSubmit(values) {
|
onSubmit(values) {
|
||||||
@ -114,7 +107,17 @@ export const useFilterFieldProps = ({ options, service, params }) => {
|
|||||||
const defaultFilter = removeNullCondition(params.filter);
|
const defaultFilter = removeNullCondition(params.filter);
|
||||||
// filter parameter for the filter action
|
// filter parameter for the filter action
|
||||||
const filter = removeNullCondition(values?.filter);
|
const filter = removeNullCondition(values?.filter);
|
||||||
service.run({ ...service.params?.[0], page: 1, filter: mergeFilter(defaultFilter, filter) });
|
|
||||||
|
const newSharedFilterStore = {
|
||||||
|
...sharedFilterStore,
|
||||||
|
ActionBar: concatFilter(defaultFilter, filter),
|
||||||
|
};
|
||||||
|
|
||||||
|
setSharedFilterStore(newSharedFilterStore);
|
||||||
|
|
||||||
|
const paramFilter = getFilterParams(newSharedFilterStore);
|
||||||
|
|
||||||
|
service.run({ ...service.params?.[0], page: 1, filter: paramFilter });
|
||||||
const items = filter?.$and || filter?.$or;
|
const items = filter?.$and || filter?.$or;
|
||||||
if (items?.length) {
|
if (items?.length) {
|
||||||
field.title = t('{{count}} filter items', { count: items?.length || 0 });
|
field.title = t('{{count}} filter items', { count: items?.length || 0 });
|
||||||
|
@ -19,7 +19,7 @@ export const SortableProvider = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Sortable = (props: any) => {
|
export const Sortable = (props: any) => {
|
||||||
const { component, style, children, ...others } = props;
|
const { component, style, children, openMode, ...others } = props;
|
||||||
const { droppable } = useContext(SortableContext);
|
const { droppable } = useContext(SortableContext);
|
||||||
const { isOver, setNodeRef } = droppable;
|
const { isOver, setNodeRef } = droppable;
|
||||||
const droppableStyle = { ...style };
|
const droppableStyle = { ...style };
|
||||||
|
@ -113,7 +113,7 @@ export class Designable {
|
|||||||
}
|
}
|
||||||
const updateColumnSize = (parent: Schema) => {
|
const updateColumnSize = (parent: Schema) => {
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
const len = Object.values(parent.properties).length;
|
const len = Object.values(parent.properties).length;
|
||||||
const schemas = [];
|
const schemas = [];
|
||||||
|
@ -2,6 +2,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { isPlainObj } from '@formily/shared';
|
import { isPlainObj } from '@formily/shared';
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext } from 'react';
|
||||||
import { SchemaComponentOptions } from '../schema-component';
|
import { SchemaComponentOptions } from '../schema-component';
|
||||||
|
import get from 'lodash/get';
|
||||||
import * as globals from './buttons';
|
import * as globals from './buttons';
|
||||||
import * as initializerComponents from './components';
|
import * as initializerComponents from './components';
|
||||||
import * as items from './items';
|
import * as items from './items';
|
||||||
@ -25,7 +26,7 @@ export const useSchemaInitializer = (name: string, props = {}) => {
|
|||||||
return { exists: false, render: (props?: any) => render(null) };
|
return { exists: false, render: (props?: any) => render(null) };
|
||||||
}
|
}
|
||||||
|
|
||||||
const initializer = initializers?.[name || fieldSchema?.['x-initializer']];
|
const initializer = get(initializers, name || fieldSchema?.['x-initializer']);
|
||||||
const initializerProps = { ...props, ...fieldSchema?.['x-initializer-props'] };
|
const initializerProps = { ...props, ...fieldSchema?.['x-initializer-props'] };
|
||||||
|
|
||||||
if (!initializer) {
|
if (!initializer) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ISchema, Schema } from '@formily/react';
|
||||||
|
|
||||||
// 表格操作配置
|
// 表格操作配置
|
||||||
export const TableActionInitializers = {
|
export const TableActionInitializers = {
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
@ -52,6 +54,23 @@ export const TableActionInitializers = {
|
|||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'item',
|
||||||
|
title: "{{t('Association fields filter')}}",
|
||||||
|
component: 'ActionBarAssociationFilterAction',
|
||||||
|
schema: {
|
||||||
|
'x-align': 'left',
|
||||||
|
},
|
||||||
|
find: (schema: Schema) => {
|
||||||
|
const resultSchema = Object.entries(schema.parent.properties).find(
|
||||||
|
([, value]) => value['x-component'] === 'AssociationFilter',
|
||||||
|
)?.[1];
|
||||||
|
return resultSchema;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'subMenu',
|
type: 'subMenu',
|
||||||
title: '{{t("Customize")}}',
|
title: '{{t("Customize")}}',
|
||||||
|
@ -19,3 +19,5 @@ export * from './TableActionInitializers';
|
|||||||
export * from './TableColumnInitializers';
|
export * from './TableColumnInitializers';
|
||||||
export * from './TableSelectorInitializers';
|
export * from './TableSelectorInitializers';
|
||||||
export * from './TabPaneInitializers';
|
export * from './TabPaneInitializers';
|
||||||
|
// association filter
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilter';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { merge } from '@formily/shared';
|
import { merge } from '@formily/shared';
|
||||||
|
|
||||||
import { SchemaInitializer } from "..";
|
import { SchemaInitializer } from '..';
|
||||||
import { useCurrentSchema } from '../utils';
|
import { useCurrentSchema } from '../utils';
|
||||||
|
|
||||||
export const InitializerWithSwitch = (props) => {
|
export const InitializerWithSwitch = (props) => {
|
||||||
|
@ -37,3 +37,8 @@ export * from './TableSelectorInitializer';
|
|||||||
export * from './UpdateActionInitializer';
|
export * from './UpdateActionInitializer';
|
||||||
export * from './UpdateSubmitActionInitializer';
|
export * from './UpdateSubmitActionInitializer';
|
||||||
export * from './ViewActionInitializer';
|
export * from './ViewActionInitializer';
|
||||||
|
// association filter
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilter';
|
||||||
|
export * from '../../schema-component/antd/association-filter/ActionBarAssociationFilterAction';
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDisplayField';
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDelete';
|
||||||
|
Loading…
Reference in New Issue
Block a user