fix(collection-manager): no refresh after override the field (#4022)

* refactor: code improve

* fix: bug
This commit is contained in:
katherinehhh 2024-04-12 17:57:38 +08:00 committed by GitHub
parent a18dab363a
commit 1658415402
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 122 additions and 676 deletions

View File

@ -89,39 +89,6 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
}; };
}; };
const useOverridingCollectionField = () => {
const form = useForm();
const { refreshCM } = useCollectionManager_deprecated();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource } = useResourceContext();
return {
async run() {
await form.submit();
const values = cloneDeep(form.values);
const data = omit(values, [
'key',
'uiSchemaUid',
'collectionName',
'autoCreateReverseField',
'uiSchema.x-uid',
'reverseField',
'reverseKey',
'parentKey',
// 'reverseField.key',
// 'reverseField.uiSchemaUid',
]);
await resource.create({
values: data,
});
ctx.setVisible(false);
await form.reset();
refresh();
await refreshCM();
},
};
};
export const OverridingCollectionField = (props) => { export const OverridingCollectionField = (props) => {
const record = useRecord(); const record = useRecord();
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
@ -135,7 +102,15 @@ const getIsOverriding = (currentFields, record) => {
return flag; return flag;
}; };
export const OverridingFieldAction = (props) => { export const OverridingFieldAction = (props) => {
const { scope, getContainer, item: record, parentItem: parentRecord, children, currentCollection } = props; const {
scope,
getContainer,
item: record,
parentItem: parentRecord,
children,
currentCollection,
handleRefresh,
} = props;
const { target, through } = record; const { target, through } = record;
const { getInterface, getCurrentCollectionFields, getChildrenCollections, collections } = const { getInterface, getCurrentCollectionFields, getChildrenCollections, collections } =
useCollectionManager_deprecated(); useCollectionManager_deprecated();
@ -163,6 +138,39 @@ export const OverridingFieldAction = (props) => {
}; };
}); });
}, []); }, []);
const useOverridingCollectionField = () => {
const form = useForm();
const { refresh } = useResourceActionContext();
const { refreshCM } = useCollectionManager_deprecated();
const ctx = useActionContext();
const { resource } = useResourceContext();
return {
async run() {
await form.submit();
const values = cloneDeep(form.values);
const data = omit(values, [
'key',
'uiSchemaUid',
'collectionName',
'autoCreateReverseField',
'uiSchema.x-uid',
'reverseField',
'reverseKey',
'parentKey',
// 'reverseField.key',
// 'reverseField.uiSchemaUid',
]);
await resource.create({
values: data,
});
await form.reset();
await refreshCM();
await refresh();
handleRefresh?.();
ctx.setVisible(false);
},
};
};
return ( return (
<RecordProvider record={{ ...record, collectionName: parentRecord.name }} parent={parentRecord}> <RecordProvider record={{ ...record, collectionName: parentRecord.name }} parent={parentRecord}>
<ActionContextProvider value={{ visible, setVisible }}> <ActionContextProvider value={{ visible, setVisible }}>
@ -213,7 +221,6 @@ export const OverridingFieldAction = (props) => {
isOverride: true, isOverride: true,
targetScope: { target: getFilterCollections(target), through: getFilterCollections(through) }, targetScope: { target: getFilterCollections(target), through: getFilterCollections(through) },
collections: currentCollections, collections: currentCollections,
...scope, ...scope,
}} }}
/> />

View File

@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { createForm, Field } from '@formily/core'; import { createForm, Field } from '@formily/core';
import { FieldContext, FormContext, useField } from '@formily/react'; import { FieldContext, FormContext, useField } from '@formily/react';
import { Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd'; import { Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd';
import React, { useContext, useMemo } from 'react'; import React, { useContext, useMemo, createContext, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Action, Action,
@ -30,7 +30,24 @@ import {
} from '@nocobase/client'; } from '@nocobase/client';
import { collection } from './schemas/collectionFields'; import { collection } from './schemas/collectionFields';
const resourceActionProps = {
association: {
sourceKey: 'name',
targetKey: 'name',
},
collection,
request: {
resource: 'collections.fields',
action: 'list',
params: {
paginate: false,
filter: {
$or: [{ 'interface.$not': null }, { 'options.source.$notEmpty': true }],
},
sort: ['sort'],
},
},
};
const indentStyle = css` const indentStyle = css`
.ant-table { .ant-table {
margin-left: -16px !important; margin-left: -16px !important;
@ -60,6 +77,7 @@ const tableContainer = css`
`; `;
const titlePrompt = 'Default title for each record'; const titlePrompt = 'Default title for each record';
const RefreshContext = createContext(null);
const CurrentFields = (props) => { const CurrentFields = (props) => {
const compile = useCompile(); const compile = useCompile();
@ -238,9 +256,11 @@ const InheritFields = (props) => {
dataIndex: 'actions', dataIndex: 'actions',
title: t('Actions'), title: t('Actions'),
render: function Render(_, record) { render: function Render(_, record) {
const { handleRefresh } = useContext(RefreshContext);
const overrideProps = { const overrideProps = {
type: 'primary', type: 'primary',
currentCollection: name, currentCollection: name,
handleRefresh,
}; };
const viewCollectionProps = { const viewCollectionProps = {
type: 'primary', type: 'primary',
@ -383,25 +403,6 @@ export const CollectionFields = () => {
.filter(Boolean), .filter(Boolean),
); );
const resourceActionProps = {
association: {
sourceKey: 'name',
targetKey: 'name',
},
collection,
request: {
resource: 'collections.fields',
action: 'list',
params: {
paginate: false,
filter: {
$or: [{ 'interface.$not': null }, { 'options.source.$notEmpty': true }],
},
sort: ['sort'],
},
},
};
const deleteProps = useMemo( const deleteProps = useMemo(
() => ({ () => ({
useAction: useBulkDestroyActionAndRefreshCM, useAction: useBulkDestroyActionAndRefreshCM,
@ -417,58 +418,65 @@ export const CollectionFields = () => {
); );
const addProps = { type: 'primary', database }; const addProps = { type: 'primary', database };
const syncProps = { type: 'primary' }; const syncProps = { type: 'primary' };
const [refresh, setRefresh] = useState(false);
const handleRefresh = () => {
setRefresh(!refresh);
};
return ( return (
<ResourceActionProvider {...resourceActionProps}> <RefreshContext.Provider value={{ refresh, handleRefresh }}>
<FormContext.Provider value={form}> <ResourceActionProvider {...resourceActionProps}>
<FieldContext.Provider value={f}> <FormContext.Provider value={form}>
<Space <FieldContext.Provider value={f}>
align={'end'} <Space
className={css` align={'end'}
justify-content: flex-end; className={css`
display: flex; justify-content: flex-end;
margin-bottom: 16px; display: flex;
`} margin-bottom: 16px;
> `}
<Action {...deleteProps} /> >
<SyncFieldsAction {...syncProps} /> <Action {...deleteProps} />
<SyncSQLFieldsAction refreshCMList={refreshAsync} /> <SyncFieldsAction {...syncProps} />
<SchemaComponent <SyncSQLFieldsAction refreshCMList={refreshAsync} />
schema={{ <SchemaComponent
type: 'object', schema={{
properties: { type: 'object',
...targetTemplate.configureActions, properties: {
}, ...targetTemplate.configureActions,
},
}}
/>
<AddCollectionField {...addProps} />
</Space>
<Table
rowKey={'key'}
columns={columns}
dataSource={dataSource.filter((d) => d.fields.length)}
pagination={false}
className={tableContainer}
expandable={{
defaultExpandAllRows: true,
defaultExpandedRowKeys: dataSource.map((d) => d.key),
expandedRowRender: (record) =>
record.inherit ? (
<InheritFields
fields={record.fields}
collectionResource={collectionResource}
refreshAsync={refreshAsync}
/>
) : (
<CurrentFields
fields={record.fields}
collectionResource={collectionResource}
refreshAsync={refreshAsync}
/>
),
}} }}
/> />
<AddCollectionField {...addProps} /> </FieldContext.Provider>
</Space> </FormContext.Provider>
<Table </ResourceActionProvider>
rowKey={'key'} </RefreshContext.Provider>
columns={columns}
dataSource={dataSource.filter((d) => d.fields.length)}
pagination={false}
className={tableContainer}
expandable={{
defaultExpandAllRows: true,
defaultExpandedRowKeys: dataSource.map((d) => d.key),
expandedRowRender: (record) =>
record.inherit ? (
<InheritFields
fields={record.fields}
collectionResource={collectionResource}
refreshAsync={refreshAsync}
/>
) : (
<CurrentFields
fields={record.fields}
collectionResource={collectionResource}
refreshAsync={refreshAsync}
/>
),
}}
/>
</FieldContext.Provider>
</FormContext.Provider>
</ResourceActionProvider>
); );
}; };

View File

@ -1,72 +0,0 @@
import { createForm, Field } from '@formily/core';
import { FieldContext, FormContext, observer, useField, useFieldSchema } from '@formily/react';
import { useAttach } from '@formily/react/esm/hooks/useAttach';
import { Options, Result } from 'ahooks/es/useRequest/src/types';
import { TableProps } from 'antd';
import React, { useMemo } from 'react';
import { AsyncDataProvider, useAsyncData, useRequest } from '@nocobase/client';
import { CollectionFieldsTableArray } from './CollectionFieldsTableArray';
type TableVoidProps = TableProps<any> & {
request?: any;
useSelectedRowKeys?: any;
useDataSource?: (
options?: Options<any, any> & { uid?: string },
props?: any,
) => Result<any, any> & { state?: any; setState?: any };
};
const useDefSelectedRowKeys = () => {
const result = useAsyncData();
return [result?.state?.selectedRowKeys, (selectedRowKeys) => result?.setState?.({ selectedRowKeys })];
};
const useDef = (options, props) => {
const { request, dataSource } = props;
const result = useRequest(request(props), { ...options, manual: true });
if (request) {
result.run();
return result;
} else {
return Promise.resolve({
data: dataSource,
});
}
};
export const CollectionFieldsTable: React.FC<TableVoidProps> = observer(
(props) => {
const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props;
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const form = useMemo(() => createForm(), []);
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
const result = useDataSource(
{
uid: fieldSchema['x-uid'],
onSuccess(data) {
form.setValues({
[fieldSchema.name]: data?.data,
});
},
},
props,
);
return (
<AsyncDataProvider value={result}>
<FormContext.Provider value={form}>
<FieldContext.Provider value={f}>
<CollectionFieldsTableArray
{...props}
rowKey={rowKey}
loading={result?.['loading']}
useSelectedRowKeys={useSelectedRowKeys}
pagination={false}
/>
</FieldContext.Provider>
</FormContext.Provider>
</AsyncDataProvider>
);
},
{ displayName: 'CollectionFieldsTable' },
);

View File

@ -1,283 +0,0 @@
import { css } from '@emotion/css';
import { ArrayField, Field } from '@formily/core';
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
import {
RecordIndexProvider,
RecordProvider,
SchemaComponent,
useCollectionManager_deprecated,
useCompile,
useRecord,
useRequest,
useSchemaInitializerRender,
} from '@nocobase/client';
import { Table, TableColumnProps } from 'antd';
import { default as classNames } from 'classnames';
import { findIndex } from 'lodash';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { overridingSchema } from '../Configuration/schemas/collectionFields';
const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1;
};
export const components = {
body: {
row: (props) => {
return <tr {...props} />;
},
cell: (props) => (
<td
{...props}
className={classNames(
props.className,
css`
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,
)}
/>
),
},
};
const useDef = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
return [selectedRowKeys, setSelectedRowKeys];
};
const useDefDataSource = (options, props) => {
const field = useField<Field>();
return useRequest(() => {
return Promise.resolve({
data: field.value,
});
}, options);
};
const groupColumns = [
{
dataIndex: 'name',
key: 'name',
},
];
type CategorizeKey = 'primaryAndForeignKey' | 'relation' | 'systemInfo' | 'basic';
const CategorizeKeyNameMap = new Map<CategorizeKey, string>([
['primaryAndForeignKey', 'PK & FK fields'],
['relation', 'Association fields'],
['systemInfo', 'System fields'],
['basic', 'General fields'],
]);
interface CategorizeDataItem {
key: CategorizeKey;
name: string;
data: Array<any>;
}
export const CollectionFieldsTableArray: React.FC<any> = observer(
(props) => {
const sortKeyArr: Array<CategorizeKey> = ['primaryAndForeignKey', 'relation', 'basic', 'systemInfo'];
const field = useField<ArrayField>();
const recordData = useRecord();
const { name } = recordData;
const { t } = useTranslation();
const compile = useCompile();
const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields, getInheritedFields } =
useCollectionManager_deprecated();
const {
showIndex = true,
useSelectedRowKeys = useDef,
useDataSource = useDefDataSource,
onChange,
...others
} = props;
const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys();
const [categorizeData, setCategorizeData] = useState<Array<CategorizeDataItem>>([]);
const [expandedKeys, setExpendedKeys] = useState(selectedRowKeys);
const inherits = getInheritCollections(name);
useDataSource({
onSuccess(data) {
field.value = data?.data || [];
const tmpData: Array<CategorizeDataItem> = [];
const categorizeMap = new Map<CategorizeKey, any>();
const addCategorizeVal = (categorizeKey: CategorizeKey, val) => {
let fieldArr = categorizeMap.get(categorizeKey);
if (!fieldArr) {
fieldArr = [];
}
fieldArr.push(val);
categorizeMap.set(categorizeKey, fieldArr);
};
field.value.forEach((item) => {
const itemInterface = getInterface(item?.interface);
if (item?.primaryKey || item.isForeignKey) {
addCategorizeVal('primaryAndForeignKey', item);
return;
}
const group = itemInterface?.group as CategorizeKey;
switch (group) {
case 'systemInfo':
case 'relation':
addCategorizeVal(group, item);
break;
default:
addCategorizeVal('basic', item);
}
});
if (inherits) {
inherits.forEach((v: any) => {
sortKeyArr.push(v);
const parentCollection = getCollection(v);
parentCollection.fields.map((k) => {
if (k.interface) {
addCategorizeVal(v, new Proxy(k, {}));
field.value.push(new Proxy(k, {}));
}
});
});
}
sortKeyArr.forEach((key) => {
if (categorizeMap.get(key)?.length > 0) {
const parentCollection = getCollection(key);
tmpData.push({
key,
name:
t(CategorizeKeyNameMap.get(key)) ||
t(`Parent collection fields`) + `(${compile(parentCollection.title)})`,
data: categorizeMap.get(key),
});
}
});
setExpendedKeys(sortKeyArr);
setCategorizeData(tmpData);
},
});
const useTableColumns = () => {
const schema = useFieldSchema();
const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']);
const columns = schema
.reduceProperties((buf, s) => {
if (isColumnComponent(s)) {
return buf.concat([s]);
}
}, [])
.map((s: Schema) => {
return {
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
dataIndex: s.name,
key: s.name,
render: (v, record) => {
const index = findIndex(field.value, record);
return (
<RecordIndexProvider index={index}>
<RecordProvider record={record} parent={recordData}>
<RecursionField schema={s} name={index} onlyRenderProperties />
</RecordProvider>
</RecordIndexProvider>
);
},
} as TableColumnProps<any>;
});
if (!exists) {
return columns;
}
return columns.concat({
title: render(),
dataIndex: 'TABLE_COLUMN_INITIALIZER',
key: 'TABLE_COLUMN_INITIALIZER',
});
};
const ExpandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => {
const columns = useTableColumns();
if (!props.loading) {
if (inherits.includes(record.key)) {
columns.pop();
columns.push({
title: <RecursionField name={'column4'} schema={overridingSchema as Schema} onlyRenderSelf />,
dataIndex: 'column4',
key: 'column4',
render: (v, record) => {
const index = findIndex(field.value, record);
return (
<RecordIndexProvider index={index}>
<RecordProvider record={record} parent={recordData}>
<SchemaComponent
scope={{ currentCollection: name }}
schema={overridingSchema as Schema}
name={index}
onlyRenderProperties
/>
</RecordProvider>
</RecordIndexProvider>
);
},
});
}
const restProps = {
rowSelection:
props.rowSelection && !inherits.includes(record.key)
? {
type: 'checkbox',
selectedRowKeys,
onChange(selectedRowKeys: any[]) {
setSelectedRowKeys(selectedRowKeys);
},
...props.rowSelection,
}
: undefined,
};
return (
<Table
{...others}
{...restProps}
components={components}
showHeader={true}
columns={columns}
dataSource={record.data}
pagination={false}
/>
);
}
};
return (
<div
className={css`
.ant-table {
overflow-x: auto;
overflow-y: hidden;
}
`}
>
<Table
showHeader={false}
loading={props?.loading}
columns={groupColumns}
dataSource={categorizeData}
pagination={false}
expandable={{
expandedRowRender: ExpandedRowRender,
expandedRowKeys: expandedKeys,
}}
onExpand={(expanded, record) => {
let keys = [];
if (expanded) {
keys = expandedKeys.concat([record.key]);
} else {
keys = expandedKeys.filter((v) => {
return v !== record.key;
});
}
setExpendedKeys(keys);
}}
/>
</div>
);
},
{ displayName: 'CollectionFieldsTableArray' },
);

View File

@ -3,7 +3,7 @@ import { action } from '@formily/reactive';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CollectionFieldsTable } from '.'; // import { CollectionFieldsTable } from '.';
import { import {
useAPIClient, useAPIClient,
useCurrentAppInfo, useCurrentAppInfo,
@ -191,7 +191,6 @@ export const ConfigurationTable = () => {
EditSubFieldAction, EditSubFieldAction,
FieldSummary, FieldSummary,
TemplateSummay: TemplateSummary, TemplateSummay: TemplateSummary,
CollectionFieldsTable,
CollectionFields, CollectionFields,
}} }}
scope={{ scope={{

View File

@ -1,6 +1,5 @@
import { registerValidateFormats } from '@formily/core'; import { registerValidateFormats } from '@formily/core';
export * from './ConfigurationTable'; export * from './ConfigurationTable';
export * from './CollectionFieldsTable';
export * from './schemas/collections'; export * from './schemas/collections';
export * from './ConfigurationTabs'; export * from './ConfigurationTabs';
export * from './AddCategoryAction'; export * from './AddCategoryAction';

View File

@ -65,215 +65,3 @@ export const collection: CollectionOptions = {
}, },
], ],
}; };
export const collectionFieldSchema: ISchema = {
type: 'void',
'x-collection-field': 'collections.fields',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
association: {
sourceKey: 'name',
targetKey: 'name',
},
collection,
request: {
resource: 'collections.fields',
action: 'list',
params: {
paginate: false,
filter: {
$or: [{ 'interface.$not': null }, { 'options.source.$notEmpty': true }],
},
sort: ['sort'],
// appends: ['uiSchema'],
},
},
},
properties: {
summary: {
type: 'void',
'x-component': 'FieldSummary',
},
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
delete: {
type: 'void',
title: '{{ t("Delete") }}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ cm.useBulkDestroyActionAndRefreshCM }}',
confirm: {
title: "{{t('Delete record')}}",
content: "{{t('Are you sure you want to delete it?')}}",
},
},
},
syncfromDatabase: {
type: 'void',
title: '{{ t("Sync from database") }}',
'x-component': 'SyncFieldsAction',
'x-component-props': {
type: 'primary',
},
},
syncSQL: {
type: 'void',
title: '{{ t("Sync from database") }}',
'x-component': 'SyncSQLFieldsAction',
'x-component-props': {
type: 'primary',
},
},
create: {
type: 'void',
title: '{{ t("Add new") }}',
'x-component': 'AddCollectionField',
'x-component-props': {
type: 'primary',
},
},
},
},
table: {
type: 'void',
'x-uid': 'input',
'x-component': 'CollectionFieldsTable',
'x-component-props': {
rowKey: 'name',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ cm.useDataSourceFromRAC }}',
},
properties: {
column1: {
type: 'void',
title: '{{ t("Field display name") }}',
'x-component': 'Table.Column',
properties: {
'uiSchema.title': {
type: 'number',
'x-component': 'Input',
'x-read-pretty': true,
},
},
},
column2: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
name: {
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column3: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
title: '{{t("Field interface")}}',
properties: {
interface: {
'x-component': CollectionFieldInterfaceTag,
'x-read-pretty': true,
},
},
},
column4: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
title: '{{ t("Description") }}',
properties: {
interface: {
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column5: {
type: 'void',
title: '{{ t("Actions") }}',
'x-component': 'Table.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
update: {
type: 'void',
title: '{{ t("Edit") }}',
'x-component': 'EditCollectionField',
'x-component-props': {
role: 'button',
'aria-label': '{{ "edit-button-" + $record.name }}',
type: 'primary',
},
},
delete: {
type: 'void',
title: '{{ t("Delete") }}',
'x-disabled': '{{cm.useDeleteButtonDisabled()}}',
'x-component': 'Action.Link',
'x-component-props': {
confirm: {
title: "{{t('Delete record')}}",
content: "{{t('Are you sure you want to delete it?')}}",
},
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
},
},
},
},
},
},
},
},
},
};
export const overridingSchema: ISchema = {
type: 'void',
title: '{{ t("Actions") }}',
'x-component': 'Table.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
overriding: {
type: 'void',
title: '{{ t("Overriding") }}',
'x-component': 'OverridingCollectionField',
'x-component-props': {
type: 'primary',
currentCollection: '{{ currentCollection }}',
},
},
view: {
type: 'void',
title: '{{ t("View") }}',
'x-component': 'ViewCollectionField',
'x-component-props': {
type: 'primary',
},
},
},
},
},
};