refactor(DataBlock): table block (#3748)
* refactor: remove useless code * chore: remove useless code * feat: add createTableBlockSchema * refactor: use createTableBlockUISchema * refactor: extract useTableBlockParams * refactor: extract useTableBlockSourceId * refactor: compat * refactor: fix typo in createTableBlockUISchema file * refactor: should not get collection on getting association in UISchema * refactor: use x-use-component-props instead of useProps * chore: fix unit tests * fix: fix errors * refactor: refactor data block source ID hooks
This commit is contained in:
parent
0269a1ff7d
commit
1c32983c00
@ -59,31 +59,6 @@ export const useBlockResource = () => {
|
|||||||
return useContext(BlockResourceContext) || resource;
|
return useContext(BlockResourceContext) || resource;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UseResourceProps {
|
|
||||||
resource: any;
|
|
||||||
association?: any;
|
|
||||||
useSourceId?: any;
|
|
||||||
collection?: any;
|
|
||||||
dataSource?: any;
|
|
||||||
block?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useAssociation = (props) => {
|
|
||||||
const { association } = props;
|
|
||||||
const { getCollectionField } = useCollectionManager_deprecated();
|
|
||||||
if (typeof association === 'string') {
|
|
||||||
return getCollectionField(association);
|
|
||||||
} else if (association?.collectionName && association?.name) {
|
|
||||||
return getCollectionField(`${association?.collectionName}.${association?.name}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const useActionParams = (props) => {
|
|
||||||
const { useParams } = props;
|
|
||||||
const params = useParams?.() || {};
|
|
||||||
return { ...props.params, ...params };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MaybeCollectionProvider = (props) => {
|
export const MaybeCollectionProvider = (props) => {
|
||||||
const { collection } = props;
|
const { collection } = props;
|
||||||
return collection ? (
|
return collection ? (
|
||||||
@ -218,6 +193,22 @@ export const useBlockContext = () => {
|
|||||||
return useContext(BlockContext);
|
return useContext(BlockContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于兼容旧版本 Schema
|
||||||
|
*/
|
||||||
|
const useCompatDataBlockSourceId = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
// 如果存在 x-use-decorator-props,说明是新版 Schema
|
||||||
|
if (fieldSchema['x-use-decorator-props']) {
|
||||||
|
return props.sourceId;
|
||||||
|
} else {
|
||||||
|
// 是否存在 x-use-decorator-props 是固定不变的,所以这里可以使用 hooks
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
return useDataBlockSourceId(props);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use `DataBlockProvider` instead
|
* @deprecated use `DataBlockProvider` instead
|
||||||
*/
|
*/
|
||||||
@ -236,8 +227,11 @@ export const BlockProvider = (props: {
|
|||||||
useParams?: any;
|
useParams?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const { name, dataSource, association, useParams, parentRecord } = props;
|
const { name, dataSource, association, useParams, parentRecord } = props;
|
||||||
const sourceId = useDataBlockSourceId({ association });
|
const sourceId = useCompatDataBlockSourceId(props);
|
||||||
|
|
||||||
|
// 新版(1.0)已弃用 useParams,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
const paramsFromHook = useParams?.();
|
const paramsFromHook = useParams?.();
|
||||||
|
|
||||||
const { getAssociationAppends } = useAssociationNames(dataSource);
|
const { getAssociationAppends } = useAssociationNames(dataSource);
|
||||||
const { appends, updateAssociationValues } = getAssociationAppends();
|
const { appends, updateAssociationValues } = getAssociationAppends();
|
||||||
const params = useMemo(() => {
|
const params = useMemo(() => {
|
||||||
|
@ -10,11 +10,13 @@ import { DetailsBlockProvider, useDetailsBlockProps } from './DetailsBlockProvid
|
|||||||
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
||||||
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
||||||
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
||||||
import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider';
|
import { TableBlockProvider } from './TableBlockProvider';
|
||||||
|
import { useTableBlockProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockProps';
|
||||||
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
||||||
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
||||||
import * as bp from './hooks';
|
import * as bp from './hooks';
|
||||||
import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
|
import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
|
||||||
|
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||||
|
|
||||||
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
|
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
|
||||||
export const BlockSchemaComponentProvider: React.FC = (props) => {
|
export const BlockSchemaComponentProvider: React.FC = (props) => {
|
||||||
@ -41,6 +43,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
|
useTableBlockDecoratorProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -84,6 +87,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
|
useTableBlockDecoratorProps,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ArrayField, createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
import { FormContext, useField, useFieldSchema } from '@formily/react';
|
import { FormContext, useField, useFieldSchema } from '@formily/react';
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||||
import { useCollectionManager_deprecated } from '../collection-manager';
|
import { useCollectionManager_deprecated } from '../collection-manager';
|
||||||
import { useFilterBlock } from '../filter-provider/FilterProvider';
|
import { FixedBlockWrapper, SchemaComponentOptions } from '../schema-component';
|
||||||
import { mergeFilter } from '../filter-provider/utils';
|
|
||||||
import { FixedBlockWrapper, SchemaComponentOptions, removeNullCondition } from '../schema-component';
|
|
||||||
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
import { findFilterTargets, useParsedFilter } from './hooks';
|
import { useParsedFilter } from './hooks';
|
||||||
|
import { useTableBlockParams } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||||
|
import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
export const TableBlockContext = createContext<any>({});
|
export const TableBlockContext = createContext<any>({});
|
||||||
TableBlockContext.displayName = 'TableBlockContext';
|
TableBlockContext.displayName = 'TableBlockContext';
|
||||||
@ -74,16 +74,37 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableBlockProvider = (props) => {
|
/**
|
||||||
const resourceName = props.resource;
|
* 用于兼容旧版本的 schema,当不需要兼容时可直接移除该方法
|
||||||
const params = useMemo(() => ({ ...props.params }), [props.params]);
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const useTableBlockParamsCompat = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
let params;
|
||||||
|
// 1. 新版本的 schema 存在 x-use-decorator-props 属性
|
||||||
|
if (fieldSchema['x-use-decorator-props']) {
|
||||||
|
params = props.params;
|
||||||
|
} else {
|
||||||
|
// 2. 旧版本的 schema 不存在 x-use-decorator-props 属性
|
||||||
|
// 因为 schema 中是否存在 x-use-decorator-props 是固定不变的,所以这里可以使用 hooks
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
params = useTableBlockParams(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
|
const resourceName = props.resource || props.association;
|
||||||
|
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getCollection, getCollectionField } = useCollectionManager_deprecated(props.dataSource);
|
const { getCollection, getCollectionField } = useCollectionManager_deprecated(props.dataSource);
|
||||||
const collection = getCollection(props.collection, props.dataSource);
|
const collection = getCollection(props.collection, props.dataSource);
|
||||||
const { treeTable, dragSortBy } = fieldSchema?.['x-decorator-props'] || {};
|
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
|
||||||
if (props.dragSort && dragSortBy) {
|
const params = useTableBlockParamsCompat(props);
|
||||||
params['sort'] = dragSortBy;
|
|
||||||
}
|
|
||||||
let childrenColumnName = 'children';
|
let childrenColumnName = 'children';
|
||||||
if (collection?.tree && treeTable !== false) {
|
if (collection?.tree && treeTable !== false) {
|
||||||
if (resourceName?.includes('.')) {
|
if (resourceName?.includes('.')) {
|
||||||
@ -101,140 +122,18 @@ export const TableBlockProvider = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const form = useMemo(() => createForm(), [treeTable]);
|
const form = useMemo(() => createForm(), [treeTable]);
|
||||||
const { filter: parsedFilter } = useParsedFilter({
|
|
||||||
filterOption: params?.filter,
|
|
||||||
});
|
|
||||||
const paramsWithFilter = useMemo(() => {
|
|
||||||
return {
|
|
||||||
...params,
|
|
||||||
filter: parsedFilter,
|
|
||||||
};
|
|
||||||
}, [parsedFilter, params]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ treeTable }}>
|
<SchemaComponentOptions scope={{ treeTable }}>
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
<BlockProvider name={props.name || 'table'} {...props} params={paramsWithFilter} runWhenParamsChanged>
|
<BlockProvider name={props.name || 'table'} {...props} params={params} runWhenParamsChanged>
|
||||||
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={paramsWithFilter} />
|
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export const useTableBlockContext = () => {
|
export const useTableBlockContext = () => {
|
||||||
return useContext(TableBlockContext);
|
return useContext(TableBlockContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableBlockProps = () => {
|
|
||||||
const field = useField<ArrayField>();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const ctx = useTableBlockContext();
|
|
||||||
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
|
||||||
const { getDataBlocks } = useFilterBlock();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ctx?.service?.loading) {
|
|
||||||
field.value = [];
|
|
||||||
field.value = ctx?.service?.data?.data;
|
|
||||||
field?.setInitialValue(ctx?.service?.data?.data);
|
|
||||||
field.data = field.data || {};
|
|
||||||
field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
|
|
||||||
field.componentProps.pagination = field.componentProps.pagination || {};
|
|
||||||
field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize;
|
|
||||||
field.componentProps.pagination.total = ctx?.service?.data?.meta?.count;
|
|
||||||
field.componentProps.pagination.current = ctx?.service?.data?.meta?.page;
|
|
||||||
}
|
|
||||||
}, [ctx?.service?.data, ctx?.service?.loading]); // 这里如果依赖了 ctx?.field?.data?.selectedRowKeys 的话,会导致这个问题:
|
|
||||||
return {
|
|
||||||
childrenColumnName: ctx.childrenColumnName,
|
|
||||||
loading: ctx?.service?.loading,
|
|
||||||
showIndex: ctx.showIndex,
|
|
||||||
dragSort: ctx.dragSort && ctx.dragSortBy,
|
|
||||||
rowKey: ctx.rowKey || 'id',
|
|
||||||
pagination:
|
|
||||||
ctx?.params?.paginate !== false
|
|
||||||
? {
|
|
||||||
defaultCurrent: ctx?.params?.page || 1,
|
|
||||||
defaultPageSize: ctx?.params?.pageSize,
|
|
||||||
}
|
|
||||||
: false,
|
|
||||||
onRowSelectionChange(selectedRowKeys) {
|
|
||||||
ctx.field.data = ctx?.field?.data || {};
|
|
||||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
|
||||||
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
|
||||||
},
|
|
||||||
async onRowDragEnd({ from, to }) {
|
|
||||||
await ctx.resource.move({
|
|
||||||
sourceId: from[ctx.rowKey || 'id'],
|
|
||||||
targetId: to[ctx.rowKey || 'id'],
|
|
||||||
sortField: ctx.dragSort && ctx.dragSortBy,
|
|
||||||
});
|
|
||||||
ctx.service.refresh();
|
|
||||||
},
|
|
||||||
onChange({ current, pageSize }, filters, sorter) {
|
|
||||||
const sort = sorter.order ? (sorter.order === `ascend` ? [sorter.field] : [`-${sorter.field}`]) : globalSort;
|
|
||||||
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
|
||||||
},
|
|
||||||
onClickRow(record, setSelectedRow, selectedRow) {
|
|
||||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
|
||||||
const dataBlocks = getDataBlocks();
|
|
||||||
|
|
||||||
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
|
|
||||||
if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
|
|
||||||
// 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
|
|
||||||
// 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
|
|
||||||
setSelectedRow((prev) => (prev.length ? [] : prev));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = [record[ctx.rowKey]];
|
|
||||||
|
|
||||||
dataBlocks.forEach((block) => {
|
|
||||||
const target = targets.find((target) => target.uid === block.uid);
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
const param = block.service.params?.[0] || {};
|
|
||||||
// 保留原有的 filter
|
|
||||||
const storedFilter = block.service.params?.[1]?.filters || {};
|
|
||||||
|
|
||||||
if (selectedRow.includes(record[ctx.rowKey])) {
|
|
||||||
if (block.dataLoadingMode === 'manual') {
|
|
||||||
return block.clearData();
|
|
||||||
}
|
|
||||||
delete storedFilter[uid];
|
|
||||||
} else {
|
|
||||||
storedFilter[uid] = {
|
|
||||||
$and: [
|
|
||||||
{
|
|
||||||
[target.field || ctx.rowKey]: {
|
|
||||||
[target.field ? '$in' : '$eq']: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedFilter = mergeFilter([
|
|
||||||
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
|
||||||
block.defaultFilter,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return block.doFilter(
|
|
||||||
{
|
|
||||||
...param,
|
|
||||||
page: 1,
|
|
||||||
filter: mergedFilter,
|
|
||||||
},
|
|
||||||
{ filters: storedFilter },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新表格的选中状态
|
|
||||||
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
|
|
||||||
},
|
|
||||||
onExpand(expanded, record) {
|
|
||||||
ctx?.field.onExpandClick?.(expanded, record);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import { useCollection, useCollectionManager, useCollectionParentRecordData, useCollectionRecordData } from '../..';
|
import { useCollection, useCollectionManager, useCollectionParentRecordData, useCollectionRecordData } from '../..';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,9 +2,9 @@ import { TableOutlined } from '@ant-design/icons';
|
|||||||
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application/schema-initializer/context';
|
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application/schema-initializer/context';
|
||||||
import { useCollectionManager_deprecated } from '../../../../collection-manager/hooks/useCollectionManager_deprecated';
|
import { useCollectionManager_deprecated } from '../../../../collection-manager/hooks/useCollectionManager_deprecated';
|
||||||
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
||||||
import { createTableBlockSchema } from '../../../../schema-initializer/utils';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
|
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
|
||||||
|
import { createTableBlockUISchema } from './createTableBlockUISchema';
|
||||||
|
|
||||||
export const TableBlockInitializer = ({
|
export const TableBlockInitializer = ({
|
||||||
filterCollections,
|
filterCollections,
|
||||||
@ -42,8 +42,8 @@ export const TableBlockInitializer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const collection = getCollection(item.name, item.dataSource);
|
const collection = getCollection(item.name, item.dataSource);
|
||||||
const schema = createTableBlockSchema({
|
const schema = createTableBlockUISchema({
|
||||||
collection: item.name,
|
collectionName: item.name,
|
||||||
dataSource: item.dataSource,
|
dataSource: item.dataSource,
|
||||||
rowKey: collection.filterTargetKey || 'id',
|
rowKey: collection.filterTargetKey || 'id',
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import { createTableBlockUISchema } from '../createTableBlockUISchema';
|
||||||
|
|
||||||
|
vi.mock('@formily/shared', () => {
|
||||||
|
return {
|
||||||
|
uid: () => 'mocked-uid',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTableBLockSchemaV2', () => {
|
||||||
|
it('should create a default table block schema with minimum options', () => {
|
||||||
|
const options = { dataSource: 'abc', collectionName: 'users', association: 'users.roles', rowKey: 'rowKey' };
|
||||||
|
const schema = createTableBlockUISchema(options);
|
||||||
|
|
||||||
|
expect(schema).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"actions": {
|
||||||
|
"properties": {},
|
||||||
|
"type": "void",
|
||||||
|
"x-component": "ActionBar",
|
||||||
|
"x-component-props": {
|
||||||
|
"style": {
|
||||||
|
"marginBottom": "var(--nb-spacing)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-initializer": "table:configureActions",
|
||||||
|
},
|
||||||
|
"mocked-uid": {
|
||||||
|
"properties": {
|
||||||
|
"actions": {
|
||||||
|
"properties": {
|
||||||
|
"mocked-uid": {
|
||||||
|
"type": "void",
|
||||||
|
"x-component": "Space",
|
||||||
|
"x-component-props": {
|
||||||
|
"split": "|",
|
||||||
|
},
|
||||||
|
"x-decorator": "DndContext",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "{{ t("Actions") }}",
|
||||||
|
"type": "void",
|
||||||
|
"x-action-column": "actions",
|
||||||
|
"x-component": "TableV2.Column",
|
||||||
|
"x-decorator": "TableV2.Column.ActionBar",
|
||||||
|
"x-designer": "TableV2.ActionColumnDesigner",
|
||||||
|
"x-initializer": "table:configureItemActions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"x-component": "TableV2",
|
||||||
|
"x-component-props": {
|
||||||
|
"rowKey": "id",
|
||||||
|
"rowSelection": {
|
||||||
|
"type": "checkbox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-initializer": "table:configureColumns",
|
||||||
|
"x-use-component-props": "useTableBlockProps",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "void",
|
||||||
|
"x-acl-action": "users:list",
|
||||||
|
"x-component": "CardItem",
|
||||||
|
"x-decorator": "TableBlockProvider",
|
||||||
|
"x-decorator-props": {
|
||||||
|
"action": "list",
|
||||||
|
"association": "users.roles",
|
||||||
|
"collection": "users",
|
||||||
|
"dataSource": "abc",
|
||||||
|
"dragSort": false,
|
||||||
|
"params": {
|
||||||
|
"pageSize": 20,
|
||||||
|
},
|
||||||
|
"rowKey": "rowKey",
|
||||||
|
"showIndex": true,
|
||||||
|
},
|
||||||
|
"x-filter-targets": [],
|
||||||
|
"x-settings": "blockSettings:table",
|
||||||
|
"x-toolbar": "BlockSchemaToolbar",
|
||||||
|
"x-use-decorator-props": "useTableBlockDecoratorProps",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,84 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
|
||||||
|
export const createTableBlockUISchema = (options: {
|
||||||
|
dataSource: string;
|
||||||
|
collectionName?: string;
|
||||||
|
rowKey?: string;
|
||||||
|
association?: string;
|
||||||
|
}): ISchema => {
|
||||||
|
const { collectionName, dataSource, rowKey, association } = options;
|
||||||
|
|
||||||
|
if (!dataSource) {
|
||||||
|
throw new Error('dataSource is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': `${collectionName}:list`,
|
||||||
|
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: collectionName,
|
||||||
|
association,
|
||||||
|
dataSource,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey,
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'table:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
[uid()]: {
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'table:configureColumns',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'table:configureItemActions',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { useParsedFilter } from '../../../../../block-provider/hooks/useParsedFilter';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
|
||||||
|
|
||||||
|
export const useTableBlockDecoratorProps = (props) => {
|
||||||
|
const params = useTableBlockParams(props);
|
||||||
|
const sourceId = useTableBlockSourceId(props);
|
||||||
|
|
||||||
|
return {
|
||||||
|
params,
|
||||||
|
sourceId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTableBlockParams(props) {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { filter: parsedFilter } = useParsedFilter({
|
||||||
|
filterOption: props.params?.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const params = props.params || {};
|
||||||
|
|
||||||
|
// 1. sort
|
||||||
|
const { dragSortBy } = fieldSchema?.['x-decorator-props'] || {};
|
||||||
|
if (props.dragSort && dragSortBy) {
|
||||||
|
params['sort'] = dragSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. filter
|
||||||
|
const paramsWithFilter = {
|
||||||
|
...params,
|
||||||
|
filter: parsedFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
return paramsWithFilter;
|
||||||
|
}, [fieldSchema, parsedFilter, props.dragSort, props.params]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTableBlockSourceId(props) {
|
||||||
|
let sourceId: string | undefined;
|
||||||
|
|
||||||
|
// 因为 association 是固定不变的,所以在条件中使用 hooks 是安全的
|
||||||
|
if (props.association) {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
sourceId = useDataBlockSourceId({ association: props.association });
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceId;
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFilterBlock } from '../../../../../filter-provider/FilterProvider';
|
||||||
|
import { mergeFilter } from '../../../../../filter-provider/utils';
|
||||||
|
import { removeNullCondition } from '../../../../../schema-component';
|
||||||
|
import { findFilterTargets } from '../../../../../block-provider/hooks';
|
||||||
|
import { useTableBlockContext } from '../../../../../block-provider/TableBlockProvider';
|
||||||
|
|
||||||
|
export const useTableBlockProps = () => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const ctx = useTableBlockContext();
|
||||||
|
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ctx?.service?.loading) {
|
||||||
|
field.value = [];
|
||||||
|
field.value = ctx?.service?.data?.data;
|
||||||
|
field?.setInitialValue(ctx?.service?.data?.data);
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
|
||||||
|
field.componentProps.pagination = field.componentProps.pagination || {};
|
||||||
|
field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize;
|
||||||
|
field.componentProps.pagination.total = ctx?.service?.data?.meta?.count;
|
||||||
|
field.componentProps.pagination.current = ctx?.service?.data?.meta?.page;
|
||||||
|
}
|
||||||
|
}, [ctx?.service?.data, ctx?.service?.loading]); // 这里如果依赖了 ctx?.field?.data?.selectedRowKeys 的话,会导致这个问题:
|
||||||
|
return {
|
||||||
|
childrenColumnName: ctx.childrenColumnName,
|
||||||
|
loading: ctx?.service?.loading,
|
||||||
|
showIndex: ctx.showIndex,
|
||||||
|
dragSort: ctx.dragSort && ctx.dragSortBy,
|
||||||
|
rowKey: ctx.rowKey || 'id',
|
||||||
|
pagination:
|
||||||
|
ctx?.params?.paginate !== false
|
||||||
|
? {
|
||||||
|
defaultCurrent: ctx?.params?.page || 1,
|
||||||
|
defaultPageSize: ctx?.params?.pageSize,
|
||||||
|
}
|
||||||
|
: false,
|
||||||
|
onRowSelectionChange(selectedRowKeys) {
|
||||||
|
ctx.field.data = ctx?.field?.data || {};
|
||||||
|
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||||
|
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
||||||
|
},
|
||||||
|
async onRowDragEnd({ from, to }) {
|
||||||
|
await ctx.resource.move({
|
||||||
|
sourceId: from[ctx.rowKey || 'id'],
|
||||||
|
targetId: to[ctx.rowKey || 'id'],
|
||||||
|
sortField: ctx.dragSort && ctx.dragSortBy,
|
||||||
|
});
|
||||||
|
ctx.service.refresh();
|
||||||
|
},
|
||||||
|
onChange({ current, pageSize }, filters, sorter) {
|
||||||
|
const sort = sorter.order ? (sorter.order === `ascend` ? [sorter.field] : [`-${sorter.field}`]) : globalSort;
|
||||||
|
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
||||||
|
},
|
||||||
|
onClickRow(record, setSelectedRow, selectedRow) {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
const dataBlocks = getDataBlocks();
|
||||||
|
|
||||||
|
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
|
||||||
|
if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
|
||||||
|
// 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
|
||||||
|
// 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
|
||||||
|
setSelectedRow((prev) => (prev.length ? [] : prev));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = [record[ctx.rowKey]];
|
||||||
|
|
||||||
|
dataBlocks.forEach((block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
if (selectedRow.includes(record[ctx.rowKey])) {
|
||||||
|
if (block.dataLoadingMode === 'manual') {
|
||||||
|
return block.clearData();
|
||||||
|
}
|
||||||
|
delete storedFilter[uid];
|
||||||
|
} else {
|
||||||
|
storedFilter[uid] = {
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
[target.field || ctx.rowKey]: {
|
||||||
|
[target.field ? '$in' : '$eq']: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedFilter = mergeFilter([
|
||||||
|
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
||||||
|
block.defaultFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新表格的选中状态
|
||||||
|
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
|
||||||
|
},
|
||||||
|
onExpand(expanded, record) {
|
||||||
|
ctx?.field.onExpandClick?.(expanded, record);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -5,3 +5,5 @@ export * from './TableColumnSchemaToolbar';
|
|||||||
export * from './tableBlockSettings';
|
export * from './tableBlockSettings';
|
||||||
export * from './tableColumnSettings';
|
export * from './tableColumnSettings';
|
||||||
export * from './TableColumnInitializers';
|
export * from './TableColumnInitializers';
|
||||||
|
export * from './createTableBlockUISchema';
|
||||||
|
export * from './hooks/useTableBlockDecoratorProps';
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
useTableBlockContext,
|
useTableBlockContext,
|
||||||
useTableSelectorContext,
|
useTableSelectorContext,
|
||||||
} from '../../../';
|
} from '../../../';
|
||||||
|
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
||||||
import { useToken } from '../__builtins__';
|
import { useToken } from '../__builtins__';
|
||||||
import { SubFormProvider } from '../association-field/hooks';
|
import { SubFormProvider } from '../association-field/hooks';
|
||||||
@ -211,7 +212,8 @@ const usePaginationProps = (pagination1, pagination2) => {
|
|||||||
return result.total <= result.pageSize ? false : result;
|
return result.total <= result.pageSize ? false : result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Table: any = observer(
|
export const Table: any = withDynamicSchemaProps(
|
||||||
|
observer(
|
||||||
(props: {
|
(props: {
|
||||||
useProps?: () => any;
|
useProps?: () => any;
|
||||||
onChange?: (pagination, filters, sorter, extra) => void;
|
onChange?: (pagination, filters, sorter, extra) => void;
|
||||||
@ -229,7 +231,10 @@ export const Table: any = observer(
|
|||||||
}) => {
|
}) => {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
||||||
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
|
|
||||||
|
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
|
const { pagination: pagination2, ...others2 } = useProps?.() || {};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dragSort = false,
|
dragSort = false,
|
||||||
showIndex = true,
|
showIndex = true,
|
||||||
@ -239,6 +244,7 @@ export const Table: any = observer(
|
|||||||
rowKey,
|
rowKey,
|
||||||
required,
|
required,
|
||||||
onExpand,
|
onExpand,
|
||||||
|
onClickRow,
|
||||||
...others
|
...others
|
||||||
} = { ...others1, ...others2 } as any;
|
} = { ...others1, ...others2 } as any;
|
||||||
const field = useArrayField(others);
|
const field = useArrayField(others);
|
||||||
@ -591,5 +597,6 @@ export const Table: any = observer(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
{ displayName: 'Table' },
|
{ displayName: 'Table' },
|
||||||
);
|
);
|
||||||
|
@ -3,8 +3,9 @@ import { TableOutlined } from '@ant-design/icons';
|
|||||||
|
|
||||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||||
import { createTableBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
import { useRecordCollectionDataSourceItems } from '../utils';
|
||||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
||||||
|
import { createTableBlockUISchema } from '../../modules/blocks/data-blocks/table/createTableBlockUISchema';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@ -17,7 +18,7 @@ export const RecordAssociationBlockInitializer = () => {
|
|||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
const field = itemConfig.field;
|
const field = itemConfig.field;
|
||||||
const collection = getCollection(field.target);
|
const collection = getCollection(field.target);
|
||||||
const resource = `${field.collectionName}.${field.name}`;
|
const association = `${field.collectionName}.${field.name}`;
|
||||||
return (
|
return (
|
||||||
<SchemaInitializerItem
|
<SchemaInitializerItem
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
@ -28,17 +29,15 @@ export const RecordAssociationBlockInitializer = () => {
|
|||||||
insert(s);
|
insert(s);
|
||||||
} else {
|
} else {
|
||||||
insert(
|
insert(
|
||||||
createTableBlockSchema({
|
createTableBlockUISchema({
|
||||||
rowKey: collection.filterTargetKey,
|
rowKey: collection.filterTargetKey,
|
||||||
collection: field.target,
|
|
||||||
dataSource: collection.dataSource,
|
dataSource: collection.dataSource,
|
||||||
resource,
|
association: association,
|
||||||
association: resource,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
items={useRecordCollectionDataSourceItems('Table', itemConfig, field.target, resource)}
|
items={useRecordCollectionDataSourceItems('Table', itemConfig, field.target, association)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -53,9 +52,8 @@ export function useCreateAssociationTableBlock() {
|
|||||||
const collection = getCollection(field.target);
|
const collection = getCollection(field.target);
|
||||||
|
|
||||||
insert(
|
insert(
|
||||||
createTableBlockSchema({
|
createTableBlockUISchema({
|
||||||
rowKey: collection.filterTargetKey,
|
rowKey: collection.filterTargetKey,
|
||||||
collection: field.target,
|
|
||||||
dataSource: collection.dataSource,
|
dataSource: collection.dataSource,
|
||||||
association: `${field.collectionName}.${field.name}`,
|
association: `${field.collectionName}.${field.name}`,
|
||||||
}),
|
}),
|
||||||
|
@ -1181,8 +1181,6 @@ export const createFormBlockSchema = (options) => {
|
|||||||
resource: resourceName,
|
resource: resourceName,
|
||||||
collection,
|
collection,
|
||||||
association,
|
association,
|
||||||
// action: 'get',
|
|
||||||
// useParams: '{{ useParamsFromRecord }}',
|
|
||||||
},
|
},
|
||||||
'x-toolbar': 'BlockSchemaToolbar',
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
...(settings ? { 'x-settings': settings } : { 'x-designer': designer }),
|
...(settings ? { 'x-settings': settings } : { 'x-designer': designer }),
|
||||||
@ -1358,6 +1356,12 @@ export const createReadPrettyFormBlockSchema = (options) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 已弃用,可以使用 createTableBlockUISchema 替换
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const createTableBlockSchema = (options) => {
|
export const createTableBlockSchema = (options) => {
|
||||||
const {
|
const {
|
||||||
collection,
|
collection,
|
||||||
|
@ -1,28 +1,16 @@
|
|||||||
import { TableOutlined } from '@ant-design/icons';
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import {
|
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
createTableBlockSchema,
|
|
||||||
SchemaInitializerItem,
|
|
||||||
useSchemaInitializer,
|
|
||||||
useSchemaInitializerItem,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { createAuditLogsBlockSchema } from './createAuditLogsBlockSchema';
|
||||||
|
|
||||||
export const AuditLogsBlockInitializer = () => {
|
export const AuditLogsBlockInitializer = () => {
|
||||||
const { insert } = useSchemaInitializer();
|
const { insert } = useSchemaInitializer();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
|
||||||
const schema = createTableBlockSchema({
|
const schema = createAuditLogsBlockSchema();
|
||||||
collection: 'auditLogs',
|
|
||||||
rowKey: 'id',
|
|
||||||
tableActionInitializers: 'auditLogsTable:configureActions',
|
|
||||||
tableColumnInitializers: 'auditLogsTable:configureColumns',
|
|
||||||
tableActionColumnInitializers: 'auditLogsTable:configureItemActions',
|
|
||||||
tableBlockProvider: 'AuditLogsBlockProvider',
|
|
||||||
disableTemplate: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializerItem
|
<SchemaInitializerItem
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { createAuditLogsBlockSchema } from '../createAuditLogsBlockSchema';
|
||||||
|
|
||||||
|
describe('createAuditLogsBlockSchema', () => {
|
||||||
|
let schema: ISchema;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
schema = createAuditLogsBlockSchema();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a valid schema object', () => {
|
||||||
|
expect(schema).toBeDefined();
|
||||||
|
expect(typeof schema).toBe('object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have specific top-level properties with expected values', () => {
|
||||||
|
expect(schema).toMatchObject({
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'AuditLogsBlockProvider',
|
||||||
|
'x-acl-action': 'auditLogs:list',
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': expect.arrayContaining([]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('x-decorator-props section', () => {
|
||||||
|
it('should have a collection named "auditLogs"', () => {
|
||||||
|
expect(schema['x-decorator-props'].collection).toEqual('auditLogs');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a list action', () => {
|
||||||
|
expect(schema['x-decorator-props'].action).toEqual('list');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set pageSize to 20', () => {
|
||||||
|
expect(schema['x-decorator-props'].params.pageSize).toEqual(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains expected boolean flags', () => {
|
||||||
|
expect(schema['x-decorator-props'].showIndex).toBeTruthy();
|
||||||
|
expect(schema['x-decorator-props'].dragSort).toBeFalsy();
|
||||||
|
expect(schema['x-decorator-props'].disableTemplate).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actions property inside schema.properties', () => {
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
actions = schema.properties.actions;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined and have a specific structure', () => {
|
||||||
|
expect(actions).toBeDefined();
|
||||||
|
expect(actions.type).toBe('void');
|
||||||
|
expect(actions['x-initializer']).toBe('auditLogsTable:configureActions');
|
||||||
|
expect(actions['x-component']).toBe('ActionBar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has x-component-props with expected style', () => {
|
||||||
|
expect(actions['x-component-props'].style.marginBottom).toBe('var(--nb-spacing)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('properties should include dynamically keyed objects', () => {
|
||||||
|
it('keyed objects should match the expected structure for tables', () => {
|
||||||
|
const propertyKeys = Object.keys(schema.properties).filter((key) => key !== 'actions');
|
||||||
|
propertyKeys.forEach((key) => {
|
||||||
|
expect(schema.properties[key].type).toBe('array');
|
||||||
|
expect(schema.properties[key]['x-component']).toBe('TableV2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,71 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { uid } from '@nocobase/utils/client';
|
||||||
|
|
||||||
|
export function createAuditLogsBlockSchema(): ISchema {
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'AuditLogsBlockProvider',
|
||||||
|
'x-acl-action': `auditLogs:list`,
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'auditLogs',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
disableTemplate: true,
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'auditLogsTable:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
[uid()]: {
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'auditLogsTable:configureColumns',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'auditLogsTable:configureItemActions',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user