mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 04:05:45 +00:00
fix: table benchmark
This commit is contained in:
parent
17549f9f42
commit
4628355194
@ -49,6 +49,10 @@ export default defineConfig({
|
||||
title: 'Application',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
title: 'tmp',
|
||||
link: '/core/data-block/table-performance'
|
||||
},
|
||||
{
|
||||
title: 'Application',
|
||||
link: '/core/application/application',
|
||||
|
@ -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>
|
@ -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();
|
@ -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;
|
@ -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();
|
@ -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;
|
@ -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
File diff suppressed because it is too large
Load Diff
@ -51,72 +51,7 @@ import {
|
||||
import { DEFAULT_DATA_SOURCE_KEY, DEFAULT_DATA_SOURCE_TITLE } from '../data-source/data-source/DataSourceManager';
|
||||
import { DataSource } from '../data-source/data-source/DataSource';
|
||||
|
||||
class MainDataSource extends DataSource {
|
||||
async getDataSource() {
|
||||
const service = await this.app.apiClient.request({
|
||||
resource: 'collections',
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
appends: ['fields', 'category'],
|
||||
filter: {
|
||||
// inherit: false,
|
||||
},
|
||||
sort: ['sort'],
|
||||
},
|
||||
});
|
||||
const collections = service?.data?.data || [];
|
||||
return {
|
||||
collections,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionPlugin extends Plugin {
|
||||
async load() {
|
||||
this.dataSourceManager.addCollectionMixins([InheritanceCollectionMixin]);
|
||||
this.addFieldInterfaces();
|
||||
this.addCollectionTemplates();
|
||||
this.addFieldInterfaces();
|
||||
this.addFieldInterfaceGroups();
|
||||
|
||||
this.dataSourceManager.addDataSource(MainDataSource, {
|
||||
key: DEFAULT_DATA_SOURCE_KEY,
|
||||
displayName: DEFAULT_DATA_SOURCE_TITLE,
|
||||
});
|
||||
}
|
||||
|
||||
addFieldInterfaceGroups() {
|
||||
this.dataSourceManager.addFieldInterfaceGroups({
|
||||
basic: {
|
||||
label: '{{t("Basic")}}',
|
||||
},
|
||||
choices: {
|
||||
label: '{{t("Choices")}}',
|
||||
},
|
||||
media: {
|
||||
label: '{{t("Media")}}',
|
||||
},
|
||||
datetime: {
|
||||
label: '{{t("Date & Time")}}',
|
||||
},
|
||||
relation: {
|
||||
label: '{{t("Relation")}}',
|
||||
},
|
||||
advanced: {
|
||||
label: '{{t("Advanced type")}}',
|
||||
},
|
||||
systemInfo: {
|
||||
label: '{{t("System info")}}',
|
||||
},
|
||||
others: {
|
||||
label: '{{t("Others")}}',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addFieldInterfaces() {
|
||||
this.dataSourceManager.addFieldInterfaces([
|
||||
export const allBuiltInFieldInterfaces = [
|
||||
CheckboxFieldInterface,
|
||||
CheckboxGroupFieldInterface,
|
||||
ChinaRegionFieldInterface,
|
||||
@ -155,7 +90,69 @@ export class CollectionPlugin extends Plugin {
|
||||
UpdatedByFieldInterface,
|
||||
UrlFieldInterface,
|
||||
SortFieldInterface,
|
||||
]);
|
||||
]
|
||||
|
||||
class MainDataSource extends DataSource {
|
||||
async getDataSource() {
|
||||
const service = await this.app.apiClient.request({
|
||||
resource: 'collections',
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
appends: ['fields', 'category'],
|
||||
filter: {
|
||||
// inherit: false,
|
||||
},
|
||||
sort: ['sort'],
|
||||
},
|
||||
});
|
||||
const collections = service?.data?.data || [];
|
||||
return {
|
||||
collections,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionPlugin extends Plugin {
|
||||
async load() {
|
||||
this.dataSourceManager.addCollectionMixins([InheritanceCollectionMixin]);
|
||||
this.addCollectionTemplates();
|
||||
this.dataSourceManager.addFieldInterfaces(allBuiltInFieldInterfaces);
|
||||
this.addFieldInterfaceGroups();
|
||||
|
||||
this.dataSourceManager.addDataSource(MainDataSource, {
|
||||
key: DEFAULT_DATA_SOURCE_KEY,
|
||||
displayName: DEFAULT_DATA_SOURCE_TITLE,
|
||||
});
|
||||
}
|
||||
|
||||
addFieldInterfaceGroups() {
|
||||
this.dataSourceManager.addFieldInterfaceGroups({
|
||||
basic: {
|
||||
label: '{{t("Basic")}}',
|
||||
},
|
||||
choices: {
|
||||
label: '{{t("Choices")}}',
|
||||
},
|
||||
media: {
|
||||
label: '{{t("Media")}}',
|
||||
},
|
||||
datetime: {
|
||||
label: '{{t("Date & Time")}}',
|
||||
},
|
||||
relation: {
|
||||
label: '{{t("Relation")}}',
|
||||
},
|
||||
advanced: {
|
||||
label: '{{t("Advanced type")}}',
|
||||
},
|
||||
systemInfo: {
|
||||
label: '{{t("System info")}}',
|
||||
},
|
||||
others: {
|
||||
label: '{{t("Others")}}',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addCollectionTemplates() {
|
||||
|
@ -11,7 +11,7 @@ import { defaultFieldNames } from './defaultFieldNames';
|
||||
|
||||
const useDefDataSource = (options) => {
|
||||
const field = useField<ArrayField>();
|
||||
return useRequest(() => Promise.resolve({ data: field.dataSource || [] }), options);
|
||||
return useRequest(() => Promise.resolve({ data: field?.dataSource || [] }), options);
|
||||
};
|
||||
|
||||
const useDefLoadData = (props: any) => {
|
||||
@ -37,7 +37,9 @@ export const Cascader = connect(
|
||||
const loadData = useLoadData(props);
|
||||
const { loading, run } = useDataSource({
|
||||
onSuccess(data) {
|
||||
if (field) {
|
||||
field.dataSource = data?.data || [];
|
||||
}
|
||||
},
|
||||
});
|
||||
// 兼容值为 object[] 的情况
|
||||
@ -74,7 +76,7 @@ export const Cascader = connect(
|
||||
<AntdCascader
|
||||
loading={loading}
|
||||
{...others}
|
||||
options={field.dataSource}
|
||||
options={field?.dataSource}
|
||||
loadData={loadData}
|
||||
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
|
||||
value={toValue()}
|
||||
|
@ -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;
|
||||
|
@ -19,8 +19,8 @@ export const ReadPretty = observer(
|
||||
return <div />;
|
||||
}
|
||||
const collectionField = useCollectionField();
|
||||
const dataSource = field.dataSource || props.options || collectionField?.uiSchema.enum || [];
|
||||
const currentOptions = getCurrentOptions(field.value, dataSource, fieldNames);
|
||||
const dataSource = field?.dataSource || props.options || collectionField?.uiSchema.enum || [];
|
||||
const currentOptions = getCurrentOptions(field?.value, dataSource, fieldNames);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -137,7 +137,7 @@ export const useValidator = (validator: (value: any) => string) => {
|
||||
const field = useField<Field>();
|
||||
useEffect(() => {
|
||||
const dispose = reaction(
|
||||
() => field.value,
|
||||
() => field?.value,
|
||||
(value) => {
|
||||
const message = validator(value);
|
||||
field.setFeedback({
|
||||
|
@ -4,7 +4,7 @@ import { useAPIClient, useRequest } from '@nocobase/client';
|
||||
|
||||
export const useChinaRegionDataSource = (options) => {
|
||||
const field = useField<ArrayField>();
|
||||
const maxLevel = field.componentProps.maxLevel;
|
||||
const maxLevel = field?.componentProps.maxLevel;
|
||||
return useRequest(
|
||||
{
|
||||
resource: 'chinaRegions',
|
||||
@ -38,7 +38,7 @@ export const useChinaRegionDataSource = (options) => {
|
||||
export const useChinaRegionLoadData = () => {
|
||||
const api = useAPIClient();
|
||||
const field = useField<ArrayField>();
|
||||
const maxLevel = field.componentProps.maxLevel;
|
||||
const maxLevel = field?.componentProps?.maxLevel;
|
||||
return (selectedOptions) => {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
if (targetOption?.children?.length > 0) {
|
||||
|
@ -31,6 +31,7 @@ function useTargetCollectionField() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const providedCollection = useCollection_deprecated();
|
||||
const { getCollection, getCollectionField } = useCollectionManager_deprecated();
|
||||
if (!fieldSchema) return;
|
||||
const paths = (fieldSchema.name as string).split('.');
|
||||
let collection: any = providedCollection;
|
||||
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 formBlockContext = useFormBlockContext();
|
||||
const field = useField();
|
||||
const path: any = field.path.entire;
|
||||
const path: any = field?.path?.entire;
|
||||
const fieldPath = path?.replace(`.${fieldSchema.name}`, '');
|
||||
const fieldName = fieldPath.split('.')[0];
|
||||
const index = parseInt(fieldPath.split('.')?.[1]);
|
||||
const fieldName = fieldPath?.split('.')[0];
|
||||
const index = parseInt(fieldPath?.split('.')?.[1]);
|
||||
useEffect(() => {
|
||||
setEditingValue(value);
|
||||
}, [value]);
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { FormulaFieldProvider } from './FormulaFieldProvider';
|
||||
import { FormulaFieldInterface } from './interfaces/formula';
|
||||
import { Formula } from './components';
|
||||
|
||||
export class FormulaFieldPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.use(FormulaFieldProvider);
|
||||
this.app.addComponents({ Formula })
|
||||
this.app.dataSourceManager.addFieldInterfaces([FormulaFieldInterface]);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ const InternalMap = connect((props: MapProps) => {
|
||||
</div>
|
||||
);
|
||||
}, mapReadPretty(ReadPretty));
|
||||
InternalMap.displayName = 'Map';
|
||||
|
||||
const Map = InternalMap as typeof InternalMap & {
|
||||
Designer: typeof Designer;
|
||||
|
@ -21,6 +21,7 @@ MapProvider.displayName = 'MapProvider';
|
||||
export class MapPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.use(MapProvider);
|
||||
this.app.addComponents({ Map: Map as any });
|
||||
|
||||
this.app.dataSourceManager.addFieldInterfaces(fields);
|
||||
this.app.dataSourceManager.addFieldInterfaceGroups({
|
||||
|
Loading…
Reference in New Issue
Block a user