fix: table benchmark

This commit is contained in:
dream2023 2024-03-09 17:43:14 +08:00
parent 17549f9f42
commit 4628355194
19 changed files with 12466 additions and 56 deletions

View File

@ -49,6 +49,10 @@ export default defineConfig({
title: 'Application', title: 'Application',
type: 'group', type: 'group',
children: [ children: [
{
title: 'tmp',
link: '/core/data-block/table-performance'
},
{ {
title: 'Application', title: 'Application',
link: '/core/application/application', link: '/core/application/application',

View File

@ -0,0 +1,31 @@
# Table 性能
## Antd Table
### Antd Table + String
<code src="./table-performance/antd-and-string.tsx"></code>
### Antd Table + Component
<code src="./table-performance/antd-and-component.tsx"></code>
### Antd Table + Component + Association
## Antd Table + formily
### Antd Table + formily + String
<code src="./table-performance/formily-and-string.tsx"></code>
### Antd Table + formily + Component
<code src="./table-performance/formily-and-component.tsx"></code>
### Antd Table + formily + Component + Association
## New NocoBase Table
### Full Functional Table
<code src="./table-performance/nocobase-table-and-record-and-collection-field-and-component.tsx"></code>

View File

@ -0,0 +1,75 @@
import { Button, Card, Table } from "antd";
import React, { FC, useContext } from "react";
import tableCollection from './tableCollection.json';
import tableData from './tableData.json';
import { AntdSchemaComponentPlugin, Application, Plugin, useCompile } from "@nocobase/client";
import { SchemaComponentsContext } from "@formily/react";
import { get } from 'lodash';
import MapPlugin from "@nocobase/plugin-map/client";
import FormulaFieldPlugin from "@nocobase/plugin-formula-field/client";
import ChinaRegionPlugin from "@nocobase/plugin-china-region/client";
const AssociationField: FC<{ value: any }> = ({ value }) => {
if (value === undefined || value === null) return null;
const text = JSON.stringify(value)
return <span>{text.slice(0, 20)}{text.length > 20 && '...'}</span>
}
const Demo = () => {
const [show, setShow] = React.useState<boolean>(false);
const [dataSource, setDataSource] = React.useState<any>([]);
const [columns, setColumns] = React.useState<any>([]);
const components = useContext(SchemaComponentsContext);
const compile = useCompile();
return <Card>
{show && <Table scroll={{ x: 'max-content' }} rowKey={'id'} dataSource={dataSource} columns={columns} pagination={false} />}
<Button block onClick={() => {
setShow(true);
setDataSource(tableData.data);
const columns = tableCollection.fields.map((field: any) => {
const Component: any = get(components, field.uiSchema?.['x-component'] || 'Input');
const componentProps = compile(field.uiSchema?.['x-component-props']);
const ReadPretty = Component.ReadPretty ?? Component;
return ({
title: field.name,
dataIndex: field.name,
key: field.key,
render(v) {
return <ReadPretty {...componentProps} value={v} />;
}
})
});
const indexColumn = { title: 'index', key: 'index', render(_1, _2, index) { return index + 1 } }
setColumns([indexColumn, ...columns]);
}}> Table</Button>
</Card>
}
class MyPlugin extends Plugin {
async load() {
this.app.addComponents({
AssociationField,
})
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [AntdSchemaComponentPlugin, MapPlugin, FormulaFieldPlugin, ChinaRegionPlugin, MyPlugin],
})
app.router.add('home', {
path: '/',
Component: Demo,
});
export default app.getRootComponent();

View File

@ -0,0 +1,36 @@
import { Button, Card, Table } from "antd";
import React from "react";
import tableCollection from './tableCollection.json';
import tableData from './tableData.json';
const Demo = () => {
const [show, setShow] = React.useState<boolean>(false);
const [dataSource, setDataSource] = React.useState<any>([]);
const [columns, setColumns] = React.useState<any>([]);
return <Card>
{show && <Table scroll={{ x: 'max-content' }} rowKey={'id'} dataSource={dataSource} columns={columns} pagination={false} />}
<Button block onClick={() => {
setShow(true);
setDataSource(tableData.data);
const columns = tableCollection.fields.map((field: any) => ({
title: field.name,
dataIndex: field.name,
key: field.key,
render(v) {
if (v && typeof v === 'object') {
return JSON.stringify(v);
}
return v;
}
}));
const indexColumn = { title: 'index', key: 'index', render(_1, _2, index) { return index + 1 } }
setColumns([indexColumn, ...columns]);
}}> Table</Button>
</Card>
}
export default Demo;

View File

@ -0,0 +1,117 @@
import React, { FC, useContext } from 'react'
import {
FormItem,
ArrayTable,
} from '@formily/antd-v5'
import { Field, SchemaComponentsContext, useFieldSchema } from '@formily/react'
import { Button, Card } from 'antd'
import tableCollection from './tableCollection.json';
import tableData from './tableData.json';
import { get, merge } from 'lodash'
import { AntdSchemaComponentPlugin, Application, Plugin, SchemaComponent, useCompile } from '@nocobase/client'
import MapPlugin from '@nocobase/plugin-map/client'
import FormulaFieldPlugin from '@nocobase/plugin-formula-field/client'
import ChinaRegionPlugin from '@nocobase/plugin-china-region/client'
const AssociationField: FC<{ value: any }> = ({ value }) => {
if (value === undefined || value === null) return null;
const text = JSON.stringify(value)
return <span>{text.slice(0, 20)}{text.length > 20 && '...'}</span>
}
const fieldsMap = tableCollection.fields.reduce((acc, field) => {
acc[field.name] = field;
return acc;
}, {});
const CollectionField: FC<{ value: any }> = ({ value }) => {
const compile = useCompile();
const components = useContext(SchemaComponentsContext);
const fieldSchema: any = useFieldSchema();
const collectionField = get(fieldsMap, fieldSchema.name);
const Component: any = get(components, collectionField?.uiSchema?.['x-component'] || 'Input');
const componentProps = compile(merge(fieldSchema['x-component-props'], collectionField.uiSchema?.['x-component-props']));
return <Component {...componentProps} value={value} />
}
const Demo = () => {
const [show, setShow] = React.useState<boolean>(false);
const [schema, setSchema] = React.useState<any>(null);
return <Card>
{show && <SchemaComponent schema={schema} />}
<Button block onClick={() => {
setShow(true);
const columns = tableCollection.fields.reduce((acc, field: any) => {
acc[field.name] = {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 200, title: field.name },
properties: {
[field.name]: {
type: 'string',
// pattern: 'readPretty',
'x-pattern': 'readPretty',
'x-component': 'CollectionField',
},
},
}
return acc;
}, {});
setSchema({
type: 'object',
name: 'table',
default: {
array: tableData.data
},
properties: {
array: {
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-component-props': {
rowKey: 'id',
pagination: {
pageSize: 100,
},
scroll: { x: 'max-content' },
},
items: {
type: 'object',
properties: columns,
}
},
},
})
}}> Table</Button>
</Card>
}
class MyPlugin extends Plugin {
async load() {
this.app.addComponents({
AssociationField,
ArrayTable,
FormItem,
CollectionField,
})
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [AntdSchemaComponentPlugin, MapPlugin, ChinaRegionPlugin, FormulaFieldPlugin, MyPlugin],
})
app.router.add('home', {
path: '/',
Component: Demo,
});
export default app.getRootComponent();

View File

@ -0,0 +1,90 @@
import React, { FC, useMemo } from 'react'
import {
FormItem,
ArrayTable,
} from '@formily/antd-v5'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button, Card } from 'antd'
import tableCollection from './tableCollection.json';
import tableData from './tableData.json';
const CollectionField = ({ value }) => {
return <span>{value && typeof value === 'object' ? JSON.stringify(value) : value}</span>;
}
const SchemaField = createSchemaField({
components: {
ArrayTable,
FormItem,
CollectionField,
},
})
const Demo = () => {
const [show, setShow] = React.useState<boolean>(false);
const [schema, setSchema] = React.useState<any>(null);
const form = useMemo(() => createForm(), []);
return <Card>
{show && <FormProvider form={form}>
<SchemaField schema={schema} />
</FormProvider>}
<Button block onClick={() => {
setShow(true);
form.setInitialValues({ array: tableData.data });
const indexColumn = {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 50, title: 'Index', align: 'center' },
properties: {
index: {
type: 'void',
'x-component': 'span',
},
},
}
const columns = tableCollection.fields.reduce((acc, field: any) => {
acc[field.name] = {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 200, title: field.name },
properties: {
[field.name]: {
type: 'string',
'x-component': 'CollectionField',
},
},
}
return acc;
}, { index: indexColumn });
setSchema({
type: 'object',
name: 'table',
properties: {
array: {
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-component-props': {
rowKey: 'id',
pagination: {
pageSize: 100,
},
scroll: { x: 'max-content' },
},
items: {
type: 'object',
properties: columns,
}
},
},
})
}}> Table</Button>
</Card>
}
export default Demo;

View File

@ -0,0 +1,392 @@
import { Button, Drawer, Space, Table, TableProps } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import {
AntdSchemaComponentPlugin,
CardItem,
CollectionField,
ColorPicker,
DataBlockProvider,
FormItem,
Input,
InputNumber,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
SchemaSettings,
SchemaToolbar,
allBuiltInFieldInterfaces,
useCollection,
useCompile,
useDataBlock,
useDataBlockProps,
useDataBlockRequest,
useDataSourceManager,
useSchemaInitializer,
useSchemaInitializerRender,
withDynamicSchemaProps,
} from '@nocobase/client';
import { Application } from '@nocobase/client';
import { uid } from '@formily/shared';
import { ISchema, RecursionField, observer, useField, useFieldSchema } from '@formily/react';
import tableCollection from './tableCollection.json';
import tableData from './tableData.json';
import MockAdapter from 'axios-mock-adapter';
import MapPlugin from '@nocobase/plugin-map/client';
import FormulaFieldPlugin from '@nocobase/plugin-formula-field/client';
const MyTable = withDynamicSchemaProps(Table, { displayName: 'MyTable' });
const TableColumn = observer(() => {
const field = useField<any>();
return <div>{field.title}</div>;
});
function useTableProps(): TableProps<any> {
const { tableProps } = useDataBlockProps();
const { data, loading } = useDataBlockRequest<any[]>();
const dataSource = useMemo(() => data?.data || [], [data]);
const collection = useCollection();
const field = useField<any>();
useEffect(() => {
field.value = dataSource;
}, [dataSource]);
const columns = useMemo(() => {
return collection?.getFields().map((collectionField) => {
const tableFieldSchema = {
name: collectionField.name,
type: 'void',
title: collectionField.uiSchema?.title || collectionField.name,
'x-component': 'TableColumn',
properties: {
[collectionField.name]: {
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
},
},
};
return {
title: <RecursionField name={collectionField.name} schema={tableFieldSchema} onlyRenderSelf />,
dataIndex: collectionField.name,
render(value, record, index) {
return (
<RecursionField basePath={field.address.concat(index)} onlyRenderProperties schema={tableFieldSchema} />
);
},
};
});
}, [collection]);
return {
...tableProps,
pagination: false,
loading,
dataSource,
columns,
};
}
const myTableSettings = new SchemaSettings({
name: 'myTableSettings',
items: [
{
name: 'bordered',
type: 'switch',
useComponentProps() {
const { props: blockSettingsProps, dn } = useDataBlock();
return {
title: 'Bordered',
checked: !!blockSettingsProps.tableProps?.bordered,
onChange: (checked) => {
// 修改 schema
dn.deepMerge({ 'x-decorator-props': { tableProps: { bordered: checked } } });
},
};
},
},
{
type: 'remove',
name: 'remove',
},
],
});
const AddBlockButton = observer(
() => {
const fieldSchema = useFieldSchema();
const { exists, render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
if (!exists) {
console.log('exists', exists);
return null;
}
return render();
},
{ displayName: 'AddBlockButton' },
);
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
function useCollectionMenuItems() {
const dataSourceManager = useDataSourceManager();
const allCollections = dataSourceManager.getAllCollections();
const menus = useMemo(
() =>
allCollections.map((item) => {
const { key, displayName, collections } = item;
return {
name: key,
label: displayName,
type: 'subMenu',
children: collections.map((collection) => {
return {
name: collection.name,
label: collection.title,
collection: collection.name,
dataSource: key,
};
}),
};
}),
[allCollections],
);
return menus;
}
const CreateAction = () => {
const [open, setOpen] = useState(false);
const showDrawer = () => {
setOpen(true);
};
const onClose = () => {
setOpen(false);
};
const compile = useCompile();
const collection = useCollection();
const title = compile(collection.title);
return (
<>
<Button type="primary" onClick={showDrawer}>
Add New
</Button>
<Drawer title={`${title} | Add New`} onClose={onClose} open={open}>
<p>Some contents...</p>
</Drawer>
</>
);
};
const RefreshAction = () => {
const { refresh } = useDataBlockRequest();
return <Button onClick={refresh}>Refresh</Button>;
};
const ActionBar = ({ children }) => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return (
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 'var(--nb-spacing)',
}}
>
<Space>
{children}
{render()}
</Space>
</div>
);
};
const CreateActionInitializer = () => {
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
'x-component': 'CreateAction',
});
};
return <SchemaInitializerItem title={'Add New'} onClick={handleClick}></SchemaInitializerItem>;
};
const RefreshActionInitializer = () => {
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
'x-component': 'RefreshAction',
});
};
return <SchemaInitializerItem title={'Refresh'} onClick={handleClick}></SchemaInitializerItem>;
};
const tableActionInitializers = new SchemaInitializer({
name: 'tableActionInitializers',
title: 'Configure actions',
icon: 'SettingOutlined',
style: {
marginLeft: 8,
},
items: [
{
type: 'item',
name: 'addNew',
Component: CreateActionInitializer,
},
{
type: 'item',
name: 'refresh',
Component: RefreshActionInitializer,
},
],
});
const TableDataBlockInitializer = () => {
const { insert, setVisible } = useSchemaInitializer();
const handleClick = ({ item }) => {
const tableSchema = {
type: 'void',
'x-component': 'CardItem',
'x-settings': 'myTableSettings',
'x-decorator': 'DataBlockProvider',
'x-toolbar': 'MyToolbar',
'x-decorator-props': {
collection: item.collection,
dataSource: item.dataSource,
action: 'list',
tableProps: {
pagination: false,
scroll: { x: 'max-content' },
rowKey: 'id'
},
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-initializer': 'tableActionInitializers',
},
[uid()]: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps',
},
},
};
insert(tableSchema);
setVisible(false);
};
const menuItems = useCollectionMenuItems();
return <SchemaInitializerItem title={'Table'} items={menuItems} onClick={handleClick} />;
};
const MyToolbar = (props) => {
const collection = useCollection();
const compile = useCompile();
return <SchemaToolbar title={`${compile(collection.title)}`} {...props} />;
};
const myInitializer = new SchemaInitializer({
name: 'myInitializer',
title: 'Add Block',
insertPosition: 'beforeEnd',
items: [
{
name: 'table',
Component: TableDataBlockInitializer,
},
],
});
const rootSchema: ISchema = {
type: 'void',
name: 'root',
'x-component': 'Page',
'x-initializer': 'myInitializer',
};
class MyPlugin extends Plugin {
async load() {
this.app.addComponents({ MyTable, TableColumn, MyToolbar, ActionBar, CreateAction, RefreshAction });
this.app.addComponents({
Page,
AddBlockButton,
CardItem,
DataBlockProvider,
InputNumber,
Input,
CollectionField,
ColorPicker,
FormItem,
})
this.app.schemaInitializerManager.add(myInitializer);
this.app.schemaSettingsManager.add(myTableSettings);
this.app.addScopes({ useTableProps });
this.app.schemaInitializerManager.add(tableActionInitializers);
}
}
const Root = () => {
return <SchemaComponent schema={rootSchema}></SchemaComponent>;
};
const app = new Application({
plugins: [AntdSchemaComponentPlugin, MapPlugin, FormulaFieldPlugin, MyPlugin],
components: {
},
router: {
type: 'memory',
initialEntries: ['/'],
},
designable: true,
dataSourceManager: {
collections: [tableCollection as any],
fieldInterfaces: allBuiltInFieldInterfaces,
},
apiClient: {
baseURL: 'http://localhost:8000',
},
});
app.router.add('home', {
path: '/',
Component: Root,
});
const mock = new MockAdapter(app.apiClient.axios);
mock.onGet('tt_pmt_staff:list').reply(200, tableData);
export default app.getRootComponent();

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,47 @@ import {
import { DEFAULT_DATA_SOURCE_KEY, DEFAULT_DATA_SOURCE_TITLE } from '../data-source/data-source/DataSourceManager'; import { DEFAULT_DATA_SOURCE_KEY, DEFAULT_DATA_SOURCE_TITLE } from '../data-source/data-source/DataSourceManager';
import { DataSource } from '../data-source/data-source/DataSource'; import { DataSource } from '../data-source/data-source/DataSource';
export const allBuiltInFieldInterfaces = [
CheckboxFieldInterface,
CheckboxGroupFieldInterface,
ChinaRegionFieldInterface,
CollectionSelectFieldInterface,
ColorFieldInterface,
CreatedAtFieldInterface,
CreatedByFieldInterface,
DatetimeFieldInterface,
EmailFieldInterface,
IconFieldInterface,
IdFieldInterface,
InputFieldInterface,
IntegerFieldInterface,
JsonFieldInterface,
LinkToFieldInterface,
M2MFieldInterface,
M2OFieldInterface,
MarkdownFieldInterface,
MultipleSelectFieldInterface,
NumberFieldInterface,
O2MFieldInterface,
O2OFieldInterface,
OHOFieldInterface,
OBOFieldInterface,
PasswordFieldInterface,
PercentFieldInterface,
PhoneFieldInterface,
RadioGroupFieldInterface,
RichTextFieldInterface,
SelectFieldInterface,
SubTableFieldInterface,
TableoidFieldInterface,
TextareaFieldInterface,
TimeFieldInterface,
UpdatedAtFieldInterface,
UpdatedByFieldInterface,
UrlFieldInterface,
SortFieldInterface,
]
class MainDataSource extends DataSource { class MainDataSource extends DataSource {
async getDataSource() { async getDataSource() {
const service = await this.app.apiClient.request({ const service = await this.app.apiClient.request({
@ -75,9 +116,8 @@ class MainDataSource extends DataSource {
export class CollectionPlugin extends Plugin { export class CollectionPlugin extends Plugin {
async load() { async load() {
this.dataSourceManager.addCollectionMixins([InheritanceCollectionMixin]); this.dataSourceManager.addCollectionMixins([InheritanceCollectionMixin]);
this.addFieldInterfaces();
this.addCollectionTemplates(); this.addCollectionTemplates();
this.addFieldInterfaces(); this.dataSourceManager.addFieldInterfaces(allBuiltInFieldInterfaces);
this.addFieldInterfaceGroups(); this.addFieldInterfaceGroups();
this.dataSourceManager.addDataSource(MainDataSource, { this.dataSourceManager.addDataSource(MainDataSource, {
@ -115,49 +155,6 @@ export class CollectionPlugin extends Plugin {
}); });
} }
addFieldInterfaces() {
this.dataSourceManager.addFieldInterfaces([
CheckboxFieldInterface,
CheckboxGroupFieldInterface,
ChinaRegionFieldInterface,
CollectionSelectFieldInterface,
ColorFieldInterface,
CreatedAtFieldInterface,
CreatedByFieldInterface,
DatetimeFieldInterface,
EmailFieldInterface,
IconFieldInterface,
IdFieldInterface,
InputFieldInterface,
IntegerFieldInterface,
JsonFieldInterface,
LinkToFieldInterface,
M2MFieldInterface,
M2OFieldInterface,
MarkdownFieldInterface,
MultipleSelectFieldInterface,
NumberFieldInterface,
O2MFieldInterface,
O2OFieldInterface,
OHOFieldInterface,
OBOFieldInterface,
PasswordFieldInterface,
PercentFieldInterface,
PhoneFieldInterface,
RadioGroupFieldInterface,
RichTextFieldInterface,
SelectFieldInterface,
SubTableFieldInterface,
TableoidFieldInterface,
TextareaFieldInterface,
TimeFieldInterface,
UpdatedAtFieldInterface,
UpdatedByFieldInterface,
UrlFieldInterface,
SortFieldInterface,
]);
}
addCollectionTemplates() { addCollectionTemplates() {
this.dataSourceManager.addCollectionTemplates([ this.dataSourceManager.addCollectionTemplates([
GeneralCollectionTemplate, GeneralCollectionTemplate,

View File

@ -11,7 +11,7 @@ import { defaultFieldNames } from './defaultFieldNames';
const useDefDataSource = (options) => { const useDefDataSource = (options) => {
const field = useField<ArrayField>(); const field = useField<ArrayField>();
return useRequest(() => Promise.resolve({ data: field.dataSource || [] }), options); return useRequest(() => Promise.resolve({ data: field?.dataSource || [] }), options);
}; };
const useDefLoadData = (props: any) => { const useDefLoadData = (props: any) => {
@ -37,7 +37,9 @@ export const Cascader = connect(
const loadData = useLoadData(props); const loadData = useLoadData(props);
const { loading, run } = useDataSource({ const { loading, run } = useDataSource({
onSuccess(data) { onSuccess(data) {
field.dataSource = data?.data || []; if (field) {
field.dataSource = data?.data || [];
}
}, },
}); });
// 兼容值为 object[] 的情况 // 兼容值为 object[] 的情况
@ -74,7 +76,7 @@ export const Cascader = connect(
<AntdCascader <AntdCascader
loading={loading} loading={loading}
{...others} {...others}
options={field.dataSource} options={field?.dataSource}
loadData={loadData} loadData={loadData}
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect} changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
value={toValue()} value={toValue()}

View File

@ -46,4 +46,8 @@ export const Input: ComposedInput = Object.assign(
}, },
); );
(Input as any).TextArea.ReadPretty = ReadPretty.TextArea;
(Input as any).URL.ReadPretty = ReadPretty.URL;
(Input as any).JSON.ReadPretty = ReadPretty.JSON;
export default Input; export default Input;

View File

@ -19,8 +19,8 @@ export const ReadPretty = observer(
return <div />; return <div />;
} }
const collectionField = useCollectionField(); const collectionField = useCollectionField();
const dataSource = field.dataSource || props.options || collectionField?.uiSchema.enum || []; const dataSource = field?.dataSource || props.options || collectionField?.uiSchema.enum || [];
const currentOptions = getCurrentOptions(field.value, dataSource, fieldNames); const currentOptions = getCurrentOptions(field?.value, dataSource, fieldNames);
return ( return (
<div> <div>

View File

@ -137,7 +137,7 @@ export const useValidator = (validator: (value: any) => string) => {
const field = useField<Field>(); const field = useField<Field>();
useEffect(() => { useEffect(() => {
const dispose = reaction( const dispose = reaction(
() => field.value, () => field?.value,
(value) => { (value) => {
const message = validator(value); const message = validator(value);
field.setFeedback({ field.setFeedback({

View File

@ -4,7 +4,7 @@ import { useAPIClient, useRequest } from '@nocobase/client';
export const useChinaRegionDataSource = (options) => { export const useChinaRegionDataSource = (options) => {
const field = useField<ArrayField>(); const field = useField<ArrayField>();
const maxLevel = field.componentProps.maxLevel; const maxLevel = field?.componentProps.maxLevel;
return useRequest( return useRequest(
{ {
resource: 'chinaRegions', resource: 'chinaRegions',
@ -38,7 +38,7 @@ export const useChinaRegionDataSource = (options) => {
export const useChinaRegionLoadData = () => { export const useChinaRegionLoadData = () => {
const api = useAPIClient(); const api = useAPIClient();
const field = useField<ArrayField>(); const field = useField<ArrayField>();
const maxLevel = field.componentProps.maxLevel; const maxLevel = field?.componentProps?.maxLevel;
return (selectedOptions) => { return (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1]; const targetOption = selectedOptions[selectedOptions.length - 1];
if (targetOption?.children?.length > 0) { if (targetOption?.children?.length > 0) {

View File

@ -31,6 +31,7 @@ function useTargetCollectionField() {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const providedCollection = useCollection_deprecated(); const providedCollection = useCollection_deprecated();
const { getCollection, getCollectionField } = useCollectionManager_deprecated(); const { getCollection, getCollectionField } = useCollectionManager_deprecated();
if (!fieldSchema) return;
const paths = (fieldSchema.name as string).split('.'); const paths = (fieldSchema.name as string).split('.');
let collection: any = providedCollection; let collection: any = providedCollection;
for (let i = 0; i < paths.length - 1; i++) { for (let i = 0; i < paths.length - 1; i++) {
@ -59,10 +60,10 @@ export function Result(props) {
const { evaluate } = (evaluators as Registry<Evaluator>).get(engine); const { evaluate } = (evaluators as Registry<Evaluator>).get(engine);
const formBlockContext = useFormBlockContext(); const formBlockContext = useFormBlockContext();
const field = useField(); const field = useField();
const path: any = field.path.entire; const path: any = field?.path?.entire;
const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); const fieldPath = path?.replace(`.${fieldSchema.name}`, '');
const fieldName = fieldPath.split('.')[0]; const fieldName = fieldPath?.split('.')[0];
const index = parseInt(fieldPath.split('.')?.[1]); const index = parseInt(fieldPath?.split('.')?.[1]);
useEffect(() => { useEffect(() => {
setEditingValue(value); setEditingValue(value);
}, [value]); }, [value]);

View File

@ -1,10 +1,12 @@
import { Plugin } from '@nocobase/client'; import { Plugin } from '@nocobase/client';
import { FormulaFieldProvider } from './FormulaFieldProvider'; import { FormulaFieldProvider } from './FormulaFieldProvider';
import { FormulaFieldInterface } from './interfaces/formula'; import { FormulaFieldInterface } from './interfaces/formula';
import { Formula } from './components';
export class FormulaFieldPlugin extends Plugin { export class FormulaFieldPlugin extends Plugin {
async load() { async load() {
this.app.use(FormulaFieldProvider); this.app.use(FormulaFieldProvider);
this.app.addComponents({ Formula })
this.app.dataSourceManager.addFieldInterfaces([FormulaFieldInterface]); this.app.dataSourceManager.addFieldInterfaces([FormulaFieldInterface]);
} }
} }

View File

@ -23,6 +23,7 @@ const InternalMap = connect((props: MapProps) => {
</div> </div>
); );
}, mapReadPretty(ReadPretty)); }, mapReadPretty(ReadPretty));
InternalMap.displayName = 'Map';
const Map = InternalMap as typeof InternalMap & { const Map = InternalMap as typeof InternalMap & {
Designer: typeof Designer; Designer: typeof Designer;

View File

@ -21,6 +21,7 @@ MapProvider.displayName = 'MapProvider';
export class MapPlugin extends Plugin { export class MapPlugin extends Plugin {
async load() { async load() {
this.app.use(MapProvider); this.app.use(MapProvider);
this.app.addComponents({ Map: Map as any });
this.app.dataSourceManager.addFieldInterfaces(fields); this.app.dataSourceManager.addFieldInterfaces(fields);
this.app.dataSourceManager.addFieldInterfaceGroups({ this.app.dataSourceManager.addFieldInterfaceGroups({