refactor: new schema initializer and schema settings (#2802)

* fix: form

* refactor: schema-initializer

* fix: bug

* refactor: schema initializer

* refactor: rename

* fix: delete SchemaInitializerProvider

* refactor: props `insert` to hooks `useSchemaInitializerV2`

* fix: bug

* refactor: delete `SchemaInitializer.Button`

* refactor: delete old SchemaInitializer

* fix: bug

* fix: workflow

* fix: docs

* fix: bug

* fix: bug

* feat: style

* fix: remove v2

* fix: visible

* fix: bug

* fix: item hook

* feat: item hook

* fix: add search DataBlockInitializer

* fix: build bug

* fix: style bug

* fix: style bug

* fix: test bug

* fix: test bug

* fix: rerender bug

* fix: remove menu select

* fix: bug

* chore: add aria-label for SchemaInitializerButton

* refactor: rename name to camel case

* fix: menu height bug

* fix: build errors

* fix: build errors

* fix: bug

* fix: bug

* fix: performance

* test: add test for header

* fix: sidebar is not refresh (T-2422)

* feat(e2e): support to add group page and link page

* chore: make sure the page is configurable when using page.goto

* test: add tests for menu initializer

* fix: imporve  code

* chore: fix build error

* chore: optimize locator of menu item

* refactor: rename testid for select

* test: make tests passing

* fix: make tests passing

* chore: upgrade vitest to v0.34.6

* chore: increase timeout of e2e

* feat: core

* fix: revert schema initializer demos

* test: menu, page tabs, page grid, table column

* fix: schema button interface

* feat: refactor: page tab settings

* feat: page settings

* fix: dumirc

* fix: export CSSVariableProvider

* feat: lazy render

* fix: form-item

* fix: general schema desinger

* feat: filter form item settings

* refactor: form-v2 schema settings

* refactor: form-v1 schema settings

* refactor: action schema settings

* fix: action bug

* fix: form-item bug

* fix: types error

* docs: schema settings  doc

* docs: schema settings

* feat: schema setting  item add name

* fix: visible lazy render bug

* fix: revert form item filter

* fix: test bug

* fix: test JSON.parse bug

* fix: test bug

* fix: improve styling

* fix: styling

* fix: cleanup

* fix: token.borderRadiusSM

* fix: bug

* test: add tests

* fix: style bug

* fix: add chart performance

* feat: add SchemaDesignerContext

* fix: bug

* fix: test bug

* style: create record action style improve

* fix: make test passing

* chore: mack tests passing

* chore: make tests passing

* test: fix tests

* style: style revert

* fix: bug

* fix: data selector

* fix: fix tests

* fix: fix tests

* fix: delete PluginManagerContext

* refactor: improve router and add SchemaComponentProvider & CSSVariableProvider to MainComponent

* fix: add dn and field builtin to SchemaSettingWrapper

* feat: update docs

* refactor: application providers

* fix: test bug

* fix: fix tests

* chore: make test passing

* feat: update docs

* chore: rename collection name

* feat: update docs

* chore: skip weird test

* fix: blockInitializers media to otherBlocks

* fix: cancel to skip test

* fix: bug

* test: add test

* refactor: migrate to small files

* test: add tests for form block settings

* chore: format

* fix: add chart scroll bug

* refactor: action designer improve

* refactor: formitem designer schemaSetting

* feat: schemaSettingsManager and schemaInitializerManager addItem and removeItem

* test: add tests for color field in creating block

* test: add tests for email field in creating block

* test: make tests passing

* perf: reduce fields number

* fix: sub menu bug

* test: add tests basic in editing form

* test: add tests basic in details form

* fix: improve code

* test: make tests passing

* test(plugin-mock-collections): add color for enum options

* refactor: improve code

* fix: bug

* fix: bug

* refactor: convert parameters to destructured object

* test: add tests choices

* test: add tests media

* test: add tests for datetime in creating form

* feat(plugin-mock-collection): generate faker time

* test: add tests for datetime in editing form

* test: add tests for datetime in details form

* fix: bug

* feat: improve code

* test: add tests for relation fields

* fix: rename SchemaSettings

* fix: type bug

* refactor: useDesinger()

* fix: bug

* fix: bug

* fix: build tip

* fix: designableState

* fix: bug

* fix: designable

* fix: designable

* test: add tests for relation fields

* test: add tests for relation fields

* test: add tests for relation fields

* feat: client api doc

* test: add tests for relation fields

* test: avoid errors

* test: make tests passing

* fix: bug

* test: make tests passing

* test: add tests for advanced fields

* test: increase e2e timeout-minutes to 60

* fix: bug

* fix: improve code

* feat: add schema initailizer component  demos

* test: make tests passing

* fix: schema settings demos

* feat: shallowMerge & deepMerge

* test: reduce number of tests

* test: make tests passing

* feat: updates

* fix: add Initializer Internal

* demos:  useSchemaSettingsRender

* test: make tests passing

* test: make tests passing

* fix: improve docs

* fix: bug

* chore: upgrade dumi theme

* test: make tests passing

* test: add tests for linkage rules

* test: add test for form data templates

* test: add tests for default value

* test: reduce number of tests

* fix: dn.deepMerge

* fix: bug

* fix: bug

* fix: toolbar

* fix: docs ssr

* test: add tests for system fields

* test: add tests for actions

* fix:  bug

* test: add tests for lazy loading of variables

* test: make testing more stable

* fix: update docs

* fix: bug

---------

Co-authored-by: Rain <958414905@qq.com>
Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
jack zhang 2023-12-04 14:56:46 +08:00 committed by GitHub
parent 69abfc9832
commit bf1a19426a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
678 changed files with 53962 additions and 7769 deletions

View File

@ -30,6 +30,12 @@ export default defineConfig({
alias: {
...umiConfig.alias,
},
ssr: {
},
exportStatic: {
ignorePreRenderError: true
},
cacheDirectoryPath: `node_modules/.docs-${lang}-cache`,
outputPath: `./docs/dist/${lang}`,
resolve: {

View File

@ -23,8 +23,10 @@ jobs:
with:
node-version: "18"
- run: yarn install
- name: Build
run: yarn doc build core/client
- name: Build zh-CN
run: yarn doc build core/client --lang=zh-CN
- name: Build en-US
run: yarn doc build core/client --lang=en-US
- name: Set tags
id: set-tags
run: |

View File

@ -76,4 +76,4 @@ jobs:
DB_UNDERSCORED: ${{ matrix.underscored }}
DB_SCHEMA: ${{ matrix.schema }}
COLLECTION_MANAGER_SCHEMA: ${{ matrix.collection_schema }}
timeout-minutes: 40
timeout-minutes: 60

View File

@ -206,14 +206,14 @@ type ConditionFunc = (ctx: any) => Promise<boolean> | boolean;
**Detailed Information**
* resourceName - Name of the resource
* actionNames - Name of the resource action
* actionNames - Name of the resource action
* condition? - Configuration of the validity condition
* Pass in a `string` to use a condition that is already defined; Use the `acl.allowManager.registerCondition` method to register a condition.
```typescript
acl.allowManager.registerAllowCondition('superUser', async () => {
return ctx.state.user?.id === 1;
});
// Open permissions of the users:list with validity condition superUser
acl.allow('users', 'list', 'superUser');
```

View File

@ -35,7 +35,6 @@ Add Providers, build-in Providers are:
- SystemSettingsProvider
- PluginManagerProvider
- SchemaComponentProvider
- SchemaInitializerProvider
- BlockSchemaComponentProvider
- AntdSchemaComponentProvider
- ACLProvider

View File

@ -17,11 +17,3 @@ Used for the initialization of various schemas. Newly added schema can be insert
},
}
```
The core of SchemaInitializer includes `<SchemaInitializer.Button />` and `<SchemaInitializer.Item />` the two components. `<SchemaInitializer.Button />` is used to create the dropdown menu button of schema, and the options of the dropdown menu is `<SchemaInitializer.Item/>`.
### `<SchemaInitializerProvider />`
### `<SchemaInitializer.Button />`
### `<SchemaInitializer.Item/>`

View File

@ -10,7 +10,6 @@ Most of the extensions for the NocoBase client are provided as Providers.
- SystemSettingsProvider
- PluginManagerProvider
- SchemaComponentProvider
- SchemaInitializerProvider
- BlockSchemaComponentProvider
- AntdSchemaComponentProvider
- DocumentTitleProvider

View File

@ -25,15 +25,11 @@
```tsx |pure
import React, { useContext } from 'react';
import { SchemaInitializerContext } from '@nocobase/client';
import { Plugin } from '@nocobase/client';
export default React.memo((props) => {
const items = useContext(SchemaInitializerContext);
const BlockInitializers = {};
return (
<SchemaInitializerContext.Provider value={{ ...items, BlockInitializers }}>
{props.children}
</SchemaInitializerContext.Provider>
);
});
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add('BlockInitializers', BlockInitializers)
}
}
```

View File

@ -35,7 +35,6 @@ const app = new Application({
- SystemSettingsProvider
- PluginManagerProvider
- SchemaComponentProvider
- SchemaInitializerProvider
- BlockSchemaComponentProvider
- AntdSchemaComponentProvider
- ACLProvider

View File

@ -17,11 +17,3 @@
},
}
```
SchemaInitializer 的核心包括 `<SchemaInitializer.Button />``<SchemaInitializer.Item />` 两个组件。`<SchemaInitializer.Button />` 用于创建 Schema 的下拉菜单按钮,下拉菜单的菜单项为 `<SchemaInitializer.Item/>`
### `<SchemaInitializerProvider />`
### `<SchemaInitializer.Button />`
### `<SchemaInitializer.Item/>`

View File

@ -10,7 +10,6 @@ NocoBase 客户端的扩展大多以 Provider 的形式提供。
- SystemSettingsProvider
- PluginManagerProvider
- SchemaComponentProvider
- SchemaInitializerProvider
- BlockSchemaComponentProvider
- AntdSchemaComponentProvider
- DocumentTitleProvider

View File

@ -23,17 +23,13 @@
## 替换
```tsx |pure
```tsx | pure
import React, { useContext } from 'react';
import { SchemaInitializerContext } from '@nocobase/client';
import { Plugin } from '@nocobase/client';
export default React.memo((props) => {
const items = useContext(SchemaInitializerContext);
const BlockInitializers = {};
return (
<SchemaInitializerContext.Provider value={{ ...items, BlockInitializers }}>
{props.children}
</SchemaInitializerContext.Provider>
);
});
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add('BlockInitializers', BlockInitializers)
}
}
```

View File

@ -0,0 +1,278 @@
# v0.15
## 不兼容的变化
### SchemaInitializer 的注册和实现变更
#### 定义方式变更
以前 `SchemaInitializer` 支持 2 种定义方式,分别为对象和组件。例如:
```tsx
const BlockInitializers = {
title: '{{t("Add block")}}',
icon: 'PlusOutlined',
items: [
// ...
],
// ...
}
```
```tsx
const BlockInitializers = () => {
return <SchemaInitializer.Button
title={'{{t("Add block")}}'}
icon={'PlusOutlined'}
items={[
// ...
]}
// ...
/>
}
```
现在仅支持 `new SchemaInitializer()` 的实例。例如:
```tsx
const blockInitializers = new SchemaInitializer({
name: 'BlockInitializers', // 名称,和原来保持一致
title: '{{t("Add block")}}',
icon: 'PlusOutlined',
items: [
// ...
],
// ...
});
```
#### 参数变更
整体来说,`new SchemaInitializer()` 的参数参考了之前对象定义方式,但又有新的变更。具体如下:
- 新增 `name` 必填参数,用于 `x-initializer` 的值。
- 新增 `Component` 参数,用于定制化渲染的按钮。默认为 `SchemaInitializerButton`
- 新增 `componentProps`、`style` 用于配置 `Component` 的属性和样式。
- 新增 `ItemsComponent` 参数,用于定制化渲染的列表。默认为 `SchemaInitializerItems`
- 新增 `itemsComponentProps`、`itemsComponentStyle` 用于配置 `ItemsComponent` 的属性和样式。
- 新增 `popover` 参数,用于配置是否显示 `popover` 效果。
- 新增 `useInsert` 参数,用于当 `insert` 函数需要使用 hooks 时。
- 更改 将 `dropdown` 参数改为了 `popoverProps`,使用 `Popover` 代替了 `Dropdown`
- items 参数变更
- 新增 `useChildren` 函数,用于动态控制子项。
- 更改 `visible` 参数改为了 `useVisible` 函数,用于动态控制是否显示。
- 更改 将 `component` 参数改为了 `Component`,用于列表项的渲染。
- 更改 将 `key` 参数改为了 `name`,用于列表项的唯一标识。
案例1
```diff
- export const BlockInitializers = {
+ export const blockInitializers = new SchemaInitializer({
+ name: 'BlockInitializers',
'data-testid': 'add-block-button-in-page',
title: '{{t("Add block")}}',
icon: 'PlusOutlined',
wrap: gridRowColWrap,
items: [
{
- key: 'dataBlocks',
+ name: 'data-blocks',
type: 'itemGroup',
title: '{{t("Data blocks")}}',
children: [
{
- key: 'table',
+ name: 'table',
- type: 'item', // 当有 Component 参数时,就不需要此了
title: '{{t("Table")}}',
- component: TableBlockInitializer,
+ Component: TableBlockInitializer,
},
{
key: 'form',
type: 'item',
title: '{{t("Form")}}',
component: FormBlockInitializer,
}
],
},
],
});
```
案例2
原来是组件定义的方式:
```tsx
export const BulkEditFormItemInitializers = (props: any) => {
const { t } = useTranslation();
const { insertPosition, component } = props;
const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Form' });
return (
<SchemaInitializer.Button
data-testid="configure-fields-button-of-bulk-edit-form-item"
wrap={gridRowColWrap}
icon={'SettingOutlined'}
items={[
{
type: 'itemGroup',
title: t('Display fields'),
children: useCustomBulkEditFormItemInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: t('Add text'),
component: BlockItemInitializer,
},
]}
insertPosition={insertPosition}
component={component}
title={component ? null : t('Configure fields')}
/>
);
};
```
现在需要改为 `new SchemaInitializer()` 的方式:
```tsx
const bulkEditFormItemInitializers = new SchemaInitializer({
name: 'BulkEditFormItemInitializers',
'data-testid': 'configure-fields-button-of-bulk-edit-form-item',
wrap: gridRowColWrap,
icon: 'SettingOutlined',
// 原 insertPosition 和 component 是透传的,这里不用管,也是透传的
items: [
{
type: 'itemGroup',
title: t('Display fields'),
name: 'display-fields', // 记得加上 name
useChildren: useCustomBulkEditFormItemInitializerFields, // 使用到了 useChildren
},
{
type: 'divider',
},
{
title: t('Add text'),
name: 'add-text',
Component: BlockItemInitializer, // component 替换为 Component
},
]
});
```
关于参数的具体说明可以参考 `SchemaInitializer` 的类型定义,以及 [SchemaInitializer 文档](https://client.docs.nocobase.com/client/schema-initializer)。
#### 实现原理变更
以前是将所有 `items` 转为 `Menu` 组件的 items JSON 对象,最后渲染成 Menu 列表。
现在默认情况下仅仅是渲染 `items` 列表项的 `Component` 组件,至于 `Component` 组件内部如何渲染取决于自身,最后也不会拼接成一个 JSON 对象。
具体说明参考 `SchemaInitializer` 的 [Nested items 示例](https://client.docs.nocobase.com/client/schema-initializer#nested-items)。
#### 列表中的组件获取参数方式变更
之前是通过 `props` 获取 `insert` 函数,现在需要通过 `useSchemaInitializer()` 获取。例如:
```diff
const FormBlockInitializer = (props) => {
- const { insert } = props;
+ const { insert } = useSchemaInitializer();
// ...
}
export const blockInitializers = new SchemaInitializer({
name: 'BlockInitializers',
items: [
{
name: 'form',
Component: FormBlockInitializer
}
]
});
```
#### 注册方式变更
以前是通过 `SchemaInitializerProvider` 进行注册。例如:
```tsx
<SchemaInitializerProvider
initializers={{ BlockInitializers }}
components={{ ManualActionDesigner }}
></SchemaInitializerProvider>
```
现在需要改为插件的方式。例如:
```tsx
import { Plugin } from '@nocobase/client';
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(blockInitializers);
this.app.addComponents({ ManualActionDesigner });
}
}
```
#### 修改方式变更
以前是通过 `SchemaInitializerContext` 获取到全部的 `Initializers` 然后进行增删改。例如下面代码是为了往 `BlockInitializers` 中的 `media` 下添加 `Hello`
```tsx
const items = useContext<any>(SchemaInitializerContext);
const mediaItems = items.BlockInitializers.items.find((item) => item.key === 'media');
if (process.env.NODE_ENV !== 'production' && !mediaItems) {
throw new Error('media block initializer not found');
}
const children = mediaItems.children;
if (!children.find((item) => item.key === 'hello')) {
children.push({
key: 'hello',
type: 'item',
title: '{{t("Hello block")}}',
component: HelloBlockInitializer,
});
}
```
新的方式则通过插件的方式更简洁的进行修改。例如:
```tsx
class MyPlugin extends Plugin {
async load() {
// 获取 BlockInitializers
const blockInitializers = this.app.schemaInitializerManager.get('BlockInitializers');
// 添加 Hello
blockInitializers.add('media.hello', {
title: '{{t("Hello block")}}',
Component: HelloBlockInitializer,
})
}
}
```
#### 使用方式变更
之前使用 `useSchemaInitializer` 的方式进行渲染,现在需要改为 `useSchemaInitializerRender`,并且参数需要增加 `x-initializer-props`。例如:
```diff
- const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
+ const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
render();
render({ style: { marginLeft: 8 } })
```
更多说明可以参考 [SchemaInitializer 文档](https://client.docs.nocobase.com/client/schema-initializer)。

View File

@ -86,7 +86,7 @@
"axios": "^0.26.1",
"commander": "^9.2.0",
"dumi": "^2.2.0",
"dumi-theme-nocobase": "^0.2.14",
"dumi-theme-nocobase": "^0.2.18",
"eslint-plugin-jest-dom": "^5.0.1",
"eslint-plugin-testing-library": "^5.11.0",
"execa": "^5.1.1",
@ -103,7 +103,7 @@
"ts-jest": "^29.1.1",
"typescript": "5.1.3",
"vite": "^4.4.9",
"vitest": "^0.34.3"
"vitest": "^0.34.6"
},
"volta": {
"node": "18.14.2",

View File

@ -24,7 +24,13 @@ export async function build(pkgs: string[]) {
const packages = getPackages(pkgs);
if (packages.length === 0) {
console.error(chalk.red(`[@nocobase/build]: '${pkgs.join(', ')}' not match any packages.`));
let msg = '';
if (pkgs.length) {
msg = `'${pkgs.join(', ')}' did not match any packages`
} else {
msg = 'No package matched'
}
console.warn(chalk.yellow(`[@nocobase/build]: ${msg}`));
return;
}

View File

@ -3,19 +3,29 @@ import { defineConfig } from 'dumi';
import { defineThemeConfig } from 'dumi-theme-nocobase';
const umiConfig = getUmiConfig();
process.env.DOC_LANG = process.env.DOC_LANG || 'zh-CN';
const lang = process.env.DOC_LANG;
console.log('process.env.DOC_LANG', lang);
export default defineConfig({
hash: true,
alias: {
...umiConfig.alias,
},
// ssr: {},
// exportStatic: {
// ignorePreRenderError: true
// },
cacheDirectoryPath: `node_modules/.docs-client-${lang}-cache`,
outputPath: `./dist/${lang}`,
resolve: {
atomDirs: [
{ type: 'api', dir: 'src' },
{ type: 'api', dir: 'src/schema-component/antd' },
{ type: 'api', dir: 'src/route-switch/antd' },
],
docDirs: [`./docs/${lang}`]
},
locales: [
{ id: 'en-US', name: 'English' },
{ id: 'zh-CN', name: '中文' },
],
themeConfig: defineThemeConfig({
title: 'NocoBase',
logo: 'https://www.nocobase.com/images/logo.png',
@ -25,92 +35,338 @@ export default defineConfig({
nav: [
{
title: 'API',
link: '/apis/application',
link: '/core/application/application',
},
// {
// title: 'UI Schema',
// link: '/ui-schema',
// },
],
sidebarEnhance: {
'/apis': [
'/core': [
{
title: 'Core',
title: 'Application',
type: 'group',
children: [
{
title: 'Application',
children: [
{
title: 'Application',
link: '/apis/application',
},
{
title: 'APIClient',
link: '/apis/api-client',
},
{
title: 'PluginSettingsManager',
link: '#',
},
],
link: '/core/application/application',
},
{
title: 'UI schema designer',
children: [
{
title: 'SchemaComponent',
link: '#',
},
{
title: 'SchemaInitializer',
link: '#',
},
{
title: 'SchemaSettings',
link: '#',
},
{
title: 'DNDContext & DragHandler',
link: '#',
},
],
title: 'Plugin',
link: '/core/application/plugin',
},
{
title: 'Collection Manager',
link: '#',
title: 'PluginManager',
link: '/core/application/plugin-manager',
},
{
title: 'BlockProvider',
link: '#',
title: 'RouterManager',
link: '/core/application/router-manager',
},
{
title: 'RecordProvider',
link: '#',
title: 'PluginSettingsManager',
link: '/core/application/plugin-settings-manager',
},
],
},
{
title: 'React components',
title: 'UI Schema',
type: 'group',
children: [
{
title: 'Board',
link: '#',
title: 'SchemaComponent',
link: '/core/ui-schema/schema-component',
},
{
title: 'Icon',
link: '#',
title: 'Designable',
link: '/core/ui-schema/designable',
},
],
},
{
title: 'Schema components',
type: 'group',
children: [
{
title: 'Input',
link: '#',
title: 'SchemaInitializer',
link: '/core/ui-schema/schema-initializer',
},
{
title: 'SchemaInitializerManager',
link: '/core/ui-schema/schema-initializer-manager',
},
{
title: 'SchemaSettings',
link: '/core/ui-schema/schema-settings',
},
{
title: 'SchemaSettingsManager',
link: '/core/ui-schema/schema-settings-manager',
},
{
title: 'SchemaToolbar',
link: '/core/ui-schema/schema-toolbar',
},
],
},
],
// '/ui-schema': [
// {
// title: 'Overview',
// link: '/ui-schema',
// },
// {
// title: 'Globals',
// type: 'group',
// children: [
// {
// title: 'Menu',
// link: '/ui-schema/globals/menu',
// },
// {
// title: 'Page',
// link: '/ui-schema/globals/page',
// },
// {
// title: 'Tabs',
// link: '/ui-schema/globals/tabs',
// },
// ],
// },
// {
// title: 'Blocks',
// type: 'group',
// children: [
// {
// title: 'Overview',
// link: '/ui-schema/blocks',
// },
// {
// title: 'Data blocks',
// children: [
// {
// title: 'Overview',
// link: '/ui-schema/blocks/data',
// },
// {
// title: 'Table',
// link: '/ui-schema/blocks/data/table',
// },
// {
// title: 'Form',
// link: '/ui-schema/blocks/data/form',
// },
// {
// title: 'Form(Read pretty)',
// link: '/ui-schema/blocks/data/form-read-pretty',
// },
// {
// title: 'Details',
// link: '/ui-schema/blocks/data/details',
// },
// {
// title: 'List',
// link: '/ui-schema/blocks/data/list',
// },
// {
// title: 'Grid Card',
// link: '/ui-schema/blocks/data/grid-card',
// },
// {
// title: 'Calendar',
// link: '/ui-schema/blocks/data/calendar',
// },
// {
// title: 'Kanban',
// link: '/ui-schema/blocks/data/kanban',
// },
// {
// title: 'Map',
// link: '/ui-schema/blocks/data/map',
// },
// {
// title: 'Gantt',
// link: '/ui-schema/blocks/data/gantt',
// },
// {
// title: 'Charts',
// link: '/ui-schema/blocks/data/charts',
// },
// ],
// },
// {
// title: 'Filter blocks',
// children: [
// {
// title: 'Collapse',
// link: '/ui-schema/blocks/filter/collapse',
// },
// {
// title: 'Form',
// link: '/ui-schema/blocks/filter/form',
// },
// ],
// },
// {
// title: 'Other blocks',
// children: [
// {
// title: 'iframe',
// link: '/ui-schema/blocks/others/iframe',
// },
// {
// title: 'Markdown',
// link: '/ui-schema/blocks/others/markdown',
// },
// {
// title: 'Workflow todos',
// link: '/ui-schema/blocks/others/workflow-todo',
// },
// ],
// },
// ],
// },
// {
// title: 'Fields',
// type: 'group',
// children: [
// {
// title: 'Overview',
// link: '/ui-schema/fields',
// },
// {
// title: 'FormItem',
// link: '/ui-schema/fields/form-item',
// },
// {
// title: 'TableColumn',
// link: '/ui-schema/fields/table-column',
// },
// {
// title: 'Association',
// children: [
// {
// title: 'Title',
// link: '/ui-schema/fields/association-components/title',
// },
// {
// title: 'Tag',
// link: '/ui-schema/fields/association-components/tag',
// },
// {
// title: 'Select',
// link: '/ui-schema/fields/association-components/select',
// },
// {
// title: 'RecordPicker',
// link: '/ui-schema/fields/association-components/record-picker',
// },
// {
// title: 'Cascader',
// link: '/ui-schema/fields/association-components/cascader-select',
// },
// {
// title: 'Sub-form',
// link: '/ui-schema/fields/association-components/sub-form',
// },
// {
// title: 'Sub-form(Popover)',
// link: '/ui-schema/fields/association-components/sub-form-popover',
// },
// {
// title: 'Sub-details',
// link: '/ui-schema/fields/association-components/sub-details',
// },
// {
// title: 'Sub-table',
// link: '/ui-schema/fields/association-components/cascader-select',
// },
// {
// title: 'File manager',
// link: '/ui-schema/fields/association-components/file-manager',
// },
// ],
// },
// ],
// },
// {
// title: 'Actions',
// type: 'group',
// children: [
// {
// title: 'Overview',
// link: '/ui-schema/actions',
// },
// {
// title: 'Add new',
// link: '/ui-schema/actions/add-new',
// },
// {
// title: 'View',
// link: '/ui-schema/actions/view',
// },
// {
// title: 'Edit',
// link: '/ui-schema/actions/edit',
// },
// {
// title: 'Delete',
// link: '/ui-schema/actions/delete',
// },
// {
// title: 'Submit',
// link: '/ui-schema/actions/submit',
// },
// {
// title: 'Filter',
// link: '/ui-schema/actions/filter',
// },
// {
// title: 'Refresh',
// link: '/ui-schema/actions/refresh',
// },
// {
// title: 'Print',
// link: '/ui-schema/actions/print',
// },
// {
// title: 'Duplicate',
// link: '/ui-schema/actions/duplicate',
// },
// {
// title: 'Export',
// link: '/ui-schema/actions/export',
// },
// {
// title: 'Import',
// link: '/ui-schema/actions/import',
// },
// {
// title: 'Bulk update',
// link: '/ui-schema/actions/bulk-update',
// },
// {
// title: 'Bulk edit',
// link: '/ui-schema/actions/bulk-edit',
// },
// {
// title: 'Add record(任意表)',
// link: '/ui-schema/actions/add-record',
// },
// {
// title: 'Update record',
// link: '/ui-schema/actions/update-record',
// },
// {
// title: 'Save record',
// link: '/ui-schema/actions/save-record',
// },
// {
// title: 'Custom request',
// link: '/ui-schema/actions/custom-request',
// },
// {
// title: 'Submit to workflow',
// link: '/ui-schema/actions/submit-to-workflow',
// },
// ],
// },
// ],
},
}),
});

View File

@ -1,3 +1,4 @@
/node_modules
/src
/.dumi
/.dumi
/docs

View File

@ -1,5 +0,0 @@
---
sidebar: false
---
<code src="../src/application/demos/demo3.tsx"></code>

View File

@ -0,0 +1,369 @@
# Application
## new Application(options)
创建一个 NocoBase 应用。
- 类型
```tsx | pure
export interface ApplicationOptions {
apiClient?: APIClientOptions;
ws?: WebSocketClientOptions | boolean;
i18n?: i18next;
providers?: (ComponentType | ComponentAndProps)[];
plugins?: PluginType[];
components?: Record<string, ComponentType>;
scopes?: Record<string, any>;
router?: RouterOptions;
schemaSettings?: SchemaSetting[];
schemaInitializers?: SchemaInitializer[];
loadRemotePlugins?: boolean;
}
```
- 详细信息
- apiClientAPI 请求实例,具体说明请参见:[https://docs.nocobase.com/api/sdk](https://docs.nocobase.com/api/sdk)
- i18n国际化具体请参考[https://www.i18next.com/overview/api#createinstance](https://www.i18next.com/overview/api#createinstance)
- providers上下文
- components全局组件
- scopes全局 scopes
- router配置路由具体请参考[RouterManager](/core/application/router-manager)
- pluginSettings: [PluginSettingsManager](/core/application/plugin-settings-manager)
- schemaSettingsSchema 设置工具,具体参考:[SchemaSettingsManager](/core/ui-schema/schema-initializer-manager)
- schemaInitializersSchema 添加工具,具体参考:[SchemaInitializerManager](/core/ui-schema/schema-initializer-manager)
- loadRemotePlugins用于控制是否加载远程插件默认为 `false`,即不加载远程插件(方便单测和 DEMO 环境)。
- 示例
```tsx
/**
* defaultShowCode: true
*/
import { Application, Plugin } from '@nocobase/client';
const ProviderDemo = ({ children }) => {
return <div>
<div>hello world</div>
<div style={{ marginTop: 10 }}>{children}</div>
</div>
}
class MyPlugin extends Plugin {
async load(){
this.app.router.add('home', {
path: '/',
Component: () => <div>home page</div>
})
}
}
const app = new Application({
providers: [ProviderDemo],
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/'],
}
});
export default app.getRootComponent();
```
## 实例属性
### app.i18n
```tsx | pure
class Application {
i18n: i18next;
}
```
详细介绍,请参考:[i18next](https://www.i18next.com/overview/api#createinstance)
### app.apiClient
```tsx | pure
class Application {
apiClient: APIClient;
}
```
详细介绍,请参考:[APIClient](https://docs.nocobase.com/api/sdk)
### app.router
详细介绍,请参考:[RouterManager](/core/application/router-manager)
### app.pluginSettingsManager
详细介绍,请参考:[PluginSettingsManager](/core/application/plugin-settings-manager)
### app.schemaSettingsManager
详细介绍,请参考:[SchemaSettingsManager](/core/ui-schema/schema-initializer-manager)
### app.schemaInitializerManager
详细介绍,请参考:[SchemaInitializerManager](/core/ui-schema/schema-initializer-manager)
## 实例方法
### app.getRootComponent()
获取应用的根组件。
- 类型
```tsx | pure
class Application {
getRootComponent(): React.FC
}
```
- 示例
```tsx | pure
import { Application } from '@nocobase/client';
const app = new Application();
const App = app.getRootComponent();
```
### app.mount()
将应用实例挂载在一个容器元素中。
- 类型
```tsx | pure
class Application {
mount(containerOrSelector: Element | ShadowRoot | string): ReactDOM.Root
}
```
- 示例
```tsx | pure
import { Application } from '@nocobase/client';
const app = new Application();
app.mount('#root');
```
### app.addProvider()
添加 `Provider` 上下文。
- 类型
```tsx | pure
class Application {
addProvider<T = any>(component: ComponentType, props?: T): void;
}
```
- 详细信息
第一个参数是组件,第二个参数是组件的参数。注意 `Provider` 一定要渲染 `children`
- 示例
```tsx | pure
// 场景1第三方库或者自己创建的 Context
const MyContext = createContext({});
app.addProvider(MyContext.provider, { value: { color: 'red' } });
```
```tsx
import { createContext, useContext } from 'react';
import { Application, Plugin } from '@nocobase/client';
const MyContext = createContext();
const HomePage = () => {
const { color } = useContext(MyContext) || {};
return <div style={{ color }}>home page</div>
}
class MyPlugin extends Plugin {
async load(){
this.app.addProvider(MyContext.Provider, { value: { color: 'red' } });
this.app.router.add('home', {
path: '/',
Component: HomePage
})
}
}
const app = new Application({
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/'],
}
});
export default app.getRootComponent();
```
```tsx | pure
// 场景2自定义的组件注意 children
const GlobalDemo = ({ name, children }) => {
return <div>
<div>hello, { name }</div>
<div>{ children }</div>
</div>
}
app.addProvider(GlobalDemo, { name: 'nocobase' });
```
```tsx
import { Application, Plugin } from '@nocobase/client';
const GlobalDemo = ({ name, children }) => {
return <div>
<div>hello, { name }</div>
<div>{ children }</div>
</div>
}
class MyPlugin extends Plugin {
async load(){
this.app.addProvider(GlobalDemo, { name: 'nocobase' });
this.app.router.add('home', {
path: '/',
Component: () => <div>home page</div>
})
}
}
const app = new Application({
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/'],
}
});
export default app.getRootComponent();
```
### app.addProviders()
添加多个 `Provider` 上下文。
- 类型
```tsx | pure
class Application {
addProviders(providers: (ComponentType | [ComponentType, any])[]): void;
}
```
- 详细信息
一次添加多个 `Provider`
- 示例
```tsx | pure
app.addProviders([[MyContext.provider, { value: { color: 'red' } }], [GlobalDemo, { name: 'nocobase' }]])
```
### app.addComponents()
添加全局组件。
全局组件可以使用在 [RouterManager](/core/application/router-manager) 和 [UI Schema](/core/ui-schema/schema-component)上。
- 类型
```tsx | pure
class Application {
addComponents(components: Record<string, ComponentType>): void;
}
```
- 示例
```tsx | pure
app.addComponents({ Demo, Foo, Bar })
```
### app.addScopes()
添加全局的 scope。
全局组 scope 可以 [UI Schema](/core/ui-schema/schema-component) 上。
- 类型
```tsx | pure
class Application {
addScopes(scopes: Record<string, any>): void;
}
```
- 示例
```tsx | pure
function useSomeThing() {}
const anyVar = '';
app.addScopes({ useSomeThing, anyVar })
```
## Hooks
### useApp()
获取当前应用的实例。
- 类型
```tsx | pure
const useApp: () => Application
```
- 示例
```tsx | pure
const Demo = () => {
const app = useApp();
return <div>{ JSON.stringify(app.router.getRouters()) }</div>
}
```
```tsx
import { Application, Plugin, useApp } from '@nocobase/client';
const HomePage = () => {
const app = useApp();
return <div>{ JSON.stringify(app.router.getRoutes()) }</div>
}
class MyPlugin extends Plugin {
async load(){
this.app.router.add('home', {
path: '/',
Component: HomePage
})
}
}
const app = new Application({
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/'],
}
});
export default app.getRootComponent();
```

View File

@ -0,0 +1,122 @@
# PluginManager
用于管理插件。
```tsx | pure
class PluginManager {
add<T = any>(plugin: typeof Plugin, opts?: PluginOptions<T>): Promise<void>
get<T extends typeof Plugin>(PluginClass: T): InstanceType<T>;
get<T extends {}>(name: string): T;
}
```
## 实例方法
### pluginManager.add()
将插件添加到应用中。
- 类型
```tsx | pure
class PluginManager {
add<T = any>(plugin: typeof Plugin, opts?: PluginOptions<T>): Promise<void>
}
```
- 详细信息
第一个参数是插件类,第二个则是实例化时传递的参数。前面已经讲过会在添加插件后,立即调用 `afterAdd` 钩子函数,所以返回的是 `Promise<void>`
对于远程组件而言,会自动传递一个 `name` 参数。
- 示例
```tsx | pure
class MyPluginA extends Plugin {
async load() {
console.log('options', this.options)
console.log('app', this.app);
console.log('router', this.app.router, this.router);
}
}
class MyPluginB extends Plugin {
// 需要在 afterAdd 执行添加的方法
async afterAdd() {
// 通过 `app.pluginManager.add()` 添加插件时,第一个参数是插件类,第二个参数是实例化时传递的参数
this.app.pluginManager.add(MyPluginA, { name: 'MyPluginA', hello: 'world' })
}
}
const app = new Application({
plugins: [MyPluginB],
});
```
### pluginManager.get()
获取插件实例。
- 类型
```tsx | pure
class PluginManager {
get<T extends typeof Plugin>(PluginClass: T): InstanceType<T>;
get<T extends {}>(name: string): T;
}
```
- 详细信息
可以通过 Class 获取插件示例,如果在插件注册的时候有 name也可以通过字符串的 name 获取。
如果是远程插件,会自动传入 name值为 package 的 name。
- 示例
```tsx | pure
import MyPluginA from 'xxx';
class MyPluginB extends Plugin {
async load() {
// 方式1通过 Class 获取
const myPluginA = this.app.pluginManager.get(MyPluginA);
// 方式2通过 name 获取(添加的时候要传递 name 参数)
const myPluginA = this.app.pluginManager.get('MyPluginA');
}
}
```
## Hooks
获取插件实例,等同于 `pluginManager.get()`
### usePlugin()
```tsx | pure
function usePlugin<T extends typeof Plugin>(plugin: T): InstanceType<T>;
function usePlugin<T extends {}>(name: string): T;
```
- 详细信息
可以通过 Class 获取插件示例,如果在插件注册的时候有 name也可以通过字符串的 name 获取。
- 示例
```tsx | pure
import { usePlugin } from '@nocobase/client';
const Demo = () => {
// 通过 Class 获取
const myPlugin = usePlugin(MyPlugin);
// 通过 name 获取(添加的时候要传递 name 参数)
const myPlugin = usePlugin('MyPlugin');
return <div></div>
}
```

View File

@ -0,0 +1,269 @@
# PluginSettingsManager
![](../static/plugin-settings.jpg)
用于管理插件配置页面,其底层对应着 [RouterManager](/core/application/router-manager)。
```tsx | pure
interface PluginSettingOptionsType {
title: string;
/**
* @default `Outlet`
*/
Component?: ComponentType<T> | string;
icon?: string;
/**
* sort, the smaller the number, the higher the priority
* @default 0
*/
sort?: number;
aclSnippet?: string;
}
interface PluginSettingsPageType {
label?: string;
title: string;
key: string;
icon: any;
path: string;
sort?: number;
name?: string;
isAllow?: boolean;
topLevelName?: string;
aclSnippet: string;
children?: PluginSettingsPageType[];
}
class PluginSettingsManager {
add(name: string, options: PluginSettingOptionsType): void
get(name: string, filterAuth?: boolean): PluginSettingsPageType;
getList(filterAuth?: boolean): PluginSettingsPageType[]
has(name: string): boolean;
remove(name: string): void;
getRouteName(name: string): string
getRoutePath(name: string): string;
hasAuth(name: string): boolean;
}
```
## 实例方法
### pluginSettingsManager.add()
添加插件配置页。
- 类型
```tsx | pure
class PluginSettingsManager {
add(name: string, options: PluginSettingOptionsType): void
}
```
- 详细解释
第一个参数 `name`,是路由唯一标识,用于后续的删改查,并且 `name` 支持 `.` 用于分割层级,不过需要注意当使用 `.` 分层的时候,父级要使用 [Outlet](https://reactrouter.com/en/main/components/outlet),让子元素能正常渲染。
第二个参数中 `Component` 支持组件形式和字符串形式,如果是字符串组件,要先通过 [app.addComponents](/core/application/application#appaddcomponents) 进行注册,具体参考 [RouterManager](/core/application/router-manager)。
- 示例
单层级配置。
```tsx | pure
const HelloSettingPage = () => {
return <div>hello setting page</div>
}
class MyPlugin extends Plugin {
async load() {
this.app.pluginSettingsManager.add('hello', {
title: 'Hello', // menu title and page title
icon: 'ApiOutlined', // menu icon
Component: HelloSettingPage
})
}
}
```
多层级配置。
```tsx | pure
// 多层级配置页
class MyPlugin extends Plugin {
async load() {
this.app.pluginSettingsManager.add('hello', {
title: 'HelloWorld',
icon: '',
// Component: Outlet, 默认为 react-router-dom 的 Outlet 组件,可自定义
})
this.app.pluginSettingsManager.add('hello.demo1', {
title: 'Demo1 Page',
Component: () => <div>Demo1 Page Content</div>
})
this.app.pluginSettingsManager.add('hello.demo2', {
title: 'Demo2 Page',
Component: () => <div>Demo2 Page Content</div>
})
}
}
```
### pluginSettingsManager.get()
获取配置信息。
- 类型
```tsx | pure
class PluginSettingsManager {
get(name: string, filterAuth?: boolean): PluginSettingsPageType;
}
```
- 详细解释
第一个是在添加时的 name 参数,第二个参数是是否在获取的时候进行权限过滤。
- 示例
在组件中获取。
```tsx | pure
const Demo = () => {
const app = useApp();
const helloSettingPage = this.app.pluginSettingsManager.get('hello');
}
```
在插件中获取。
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const helloSettingPage = this.app.pluginSettingsManager.get('hello')
const helloSettingPage = this.app.pluginSettingsManager.get('hello', false);
const mobileAppConfigPage = this.app.pluginSettingsManager.get('mobile.app')
}
}
```
### pluginSettingsManager.getList()
获取插件配置页列表。
- 类型
```tsx | pure
class PluginSettingsManager {
getList(filterAuth?: boolean): PluginSettingsPageType[]
}
```
- 详细解释
`filterAuth` 默认值为 `true`,即进行权限过滤。
- 示例
```tsx | pure
const Demo = () => {
const app = useApp();
const settings = app.pluginSettingsManager.getList();
const settings = app.pluginSettingsManager.getList(false);
}
```
### pluginSettingsManager.has()
判断是否存在,内部已进行权限过滤。
- 类型
```tsx | pure
class PluginSettingsManager {
has(name: string): boolean;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.pluginSettingsManager.has('hello');
}
}
```
### pluginSettingsManager.remove()
移除配置。
```tsx | pure
class PluginSettingsManager {
remove(name: string): void;
}
```
### pluginSettingsManager.getRouteName()
获取对应路由的名称。
- 类型
```tsx | pure
class PluginSettingsManager {
getRouteName(name: string): string
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const helloRouteName = this.pluginSettingsManager.getRouteName('hello'); // admin.settings.hello
}
}
```
### pluginSettingsManager.getRoutePath()
获取插件配置对应的页面路径。
- 类型
```tsx | pure
class PluginSettingsManager {
getRoutePath(name: string): string;
}
```
- 示例
```tsx | pure
const Demo = () => {
const navigate = useNavigate();
const app = useApp();
const helloSettingPath = app.pluginSettingsManager.getRoutePath('hello');
return <div onClick={()=> navigate(helloSettingPath)}>
go to hello setting page
</div>
}
```
### pluginSettingsManager.hasAuth()
单独判断是否权限。
```tsx | pure
class PluginSettingsManager {
hasAuth(name: string): boolean;
}
```

View File

@ -0,0 +1,85 @@
# Plugin
Plugin 基类。
- 类型
```tsx | pure
class Plugin<T = any> {
constructor(
protected options: T,
protected app: Application,
) {
this.options = options;
this.app = app;
}
get pluginManager() {
return this.app.pluginManager;
}
get router() {
return this.app.router;
}
get pluginSettingsManager() {
return this.app.pluginSettingsManager;
}
get schemaInitializerManager() {
return this.app.schemaInitializerManager;
}
get schemaSettingsManager() {
return this.app.schemaSettingsManager;
}
async afterAdd() {}
async beforeLoad() {}
async load() {}
}
```
- 详细信息
- 构造函数
- `options` 插件的添加有两种方式,一种方式从插件列表中远程加载出来,另一种方式是通过 [PluginManager](/core/application/plugin-manager) 添加
- 远程加载:`options` 会被自动注入 `{ name: 'npm package.name' }`
- PluginManager `options` 由用户自己传递
- `app`:此参数是自动注入的,是应用实例
- 快捷访问:基类提供了对 `app` 部分方法和属性的快捷访问
- `pluginManager`
- `router`
- `pluginSettingsManager`
- `schemaSettingsManager`
- `schemaInitializerManager`
- 声明周期
- `afterAdd`:插件被添加后立即执行
- `beforeLoad`:执行渲染时执行,在 `afterAdd` 之后,`load` 之前
- `load`:最后执行
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async afterAdd() {
console.log('afterAdd')
}
async beforeLoad() {
console.log('beforeLoad')
}
async load() {
console.log('load')
// 可以访问应用实例
console.log(this.app)
// 访问应用实例内容
console.log(this.app.router, this.router);
}
}
```

View File

@ -0,0 +1,386 @@
# RouterManager
用于管理路由。
```tsx | pure
import { ComponentType } from 'react';
import { RouteObject } from 'react-router-dom';
interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
Component?: ComponentType<T> | string;
}
class RouterManager {
add(name: string, route: RouteType): void;
getRoutes(): Record<string, RouteType>;
getRoutesTree(): RouteObject[];
get(name: string): RouteType;
has(name: string): boolean;
remove(name: string): void;
setType(type: 'browser' | 'memory' | 'hash'): void;
setBasename(basename: string): void;
}
```
## 实例方法
### router.add()
添加一条路由。
- 类型
```tsx | pure
class RouterManager {
add(name: string, route: RouteType): void
}
```
- 详情
第一个参数 `name`,是路由唯一标识,用于后续的删改查,并且 `name` 支持 `.` 用于分割层级,不过需要注意当使用 `.` 分层的时候,父级要使用 [Outlet](https://reactrouter.com/en/main/components/outlet),让子元素能正常渲染。
第二个参数 `RouteType``Component` 支持组件形式和字符串形式,如果是字符串组件,要先通过 [app.addComponents](/core/application/application#appaddcomponents) 进行注册。
- 示例
单层级路由。
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.router.add('home', {
path: '/',
Component: () => <div>home page</div>
})
this.app.router.add('login', {
path: '/login',
element: <div>login page</div>
})
}
}
```
```tsx
import { useNavigate } from 'react-router-dom';
import { Plugin, Application } from '@nocobase/client';
const HomePage = () => {
const navigate = useNavigate();
return <div>
<div>home page</div>
<button onClick={() => navigate('/login')}>GO To LoginPage</button>
</div>
}
const LoginPage = () => {
const navigate = useNavigate();
return <div>
<div>login page</div>
<button onClick={() => navigate('/')}>GO To HomePage</button>
</div>
}
class MyPlugin extends Plugin {
async load() {
this.app.router.add('home', {
path: '/',
Component: HomePage
})
this.app.router.add('login', {
path: '/login',
Component: LoginPage
})
}
}
const app = new Application({
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/'],
}
});
export default app.getRootComponent();
```
多层级路由。
```tsx | pure
import { Plugin } from '@nocobase/client';
import { Outlet } from 'react-router-dom';
const AdminLayout = () =>{
return <div>
<div>This is admin layout</div>
<Outlet />
</div>
}
const AdminSettings = () => {
return <div>This is admin settings page</div>
}
class MyPlugin extends Plugin {
async load() {
this.app.router.add('admin', {
path: '/admin',
Component: AdminLayout
})
this.app.router.add('admin.settings', {
path: '/admin/settings',
Component: AdminSettings ,
})
}
}
```
```tsx
import { useNavigate, Outlet } from 'react-router-dom';
import { Plugin, Application } from '@nocobase/client';
const AdminLayout = () =>{
return <div>
<div>This is admin layout</div>
<Outlet />
</div>
}
const AdminSettings = () => {
return <div>This is admin settings page</div>
}
class MyPlugin extends Plugin {
async load() {
this.app.router.add('admin', {
path: '/admin',
Component: AdminLayout
})
this.app.router.add('admin.settings', {
path: '/admin/settings',
Component: AdminSettings ,
})
}
}
const app = new Application({
plugins: [MyPlugin],
router: {
type: 'memory',
initialEntries: ['/admin/settings'],
}
});
export default app.getRootComponent();
```
`Component` 参数为字符串。
```tsx | pure
const LoginPage = () => {
return <div>login page</div>
}
class MyPlugin extends Plugin {
async load() {
// 通过 app.addComponents 进行注册
this.app.addComponents({ LoginPage })
this.app.router.add('login', {
path: '/login',
Component: 'LoginPage', // 这里可以使用字符串了
})
}
}
```
### router.getRoutes()
获取路由列表。
- 类型
```tsx | pure
class RouterManager {
getRoutes(): Record<string, RouteType>
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
console.log(this.app.router.getRoutes());
}
}
```
![](../static/CxjJbp0pcogYn6xviVyc6QT8nUg.png)
### router.getRoutesTree()
获取用于 [useRoutes()](https://reactrouter.com/hooks/use-routes) 的数据。
- 类型
```tsx | pure
class RouterManager {
getRoutesTree(): RouteObject[]
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const routes = this.app.router.getRoutesTree();
}
}
```
### router.get()
获取单个路由配置。
- 类型
```tsx | pure
class RouterManager {
get(name: string): RouteType
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const adminRoute = this.app.router.get('admin')
const adminSettings = this.app.router.get('admin.settings')
}
}
```
### router.has()
判断是否添加过路由。
- 类型
```tsx | pure
class RouterManager {
has(name: string): boolean;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const hasAdminRoute = this.app.router.has('admin')
const hasAdminSettings = this.app.router.has('admin.settings')
}
}
```
### router.remove()
移除路由配置。
- 类型
```tsx | pure
class RouterManager {
remove(name: string): void;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.router.remove('admin')
this.app.router.remove('admin.settings')
}
}
```
### router.setType()
设置路由类型,默认为 `browser`
- 类型
```tsx | pure
class RouterManager {
setType(type: 'browser' | 'memory' | 'hash'): void;
}
```
- 详细解释
- browser: [BrowserRouter](https://reactrouter.com/en/main/router-components/browser-router)
- memory: [MemoryRouter](https://reactrouter.com/en/main/router-components/hash-router)
- hash: [HashRouter](https://reactrouter.com/en/main/router-components/memory-router)
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.router.setType('hash')
}
}
```
### router.setBasename()
设置 [basename](https://reactrouter.com/en/main/router-components/browser-router#basename)。
- 类型
```tsx | pure
class RouterManager {
setBasename(basename: string): void;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.router.setBasename('/')
}
}
```
## Hooks
### useRouter()
获取当前路由的实例,等同于 `app.router`
- 类型
```tsx | pure
const useRouter: () => RouterManager
```
- 示例
```tsx | pure
import { useRouter } from '@nocobase/client';
const Demo = () => {
const router = useRouter();
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@ -0,0 +1,122 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Field } from '@formily/core';
const Hello = observer(() => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
function Demo() {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
title: itemConfig.title,
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick} />;
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
// 插入位置
insertPosition: 'beforeEnd',
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
},
{
name: 'b',
title: 'Item B',
Component: Demo,
},
],
});
const AddBlockButton = observer(() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return render();
});
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return (
<div>
<SchemaComponent
components={{ Page, Hello, AddBlockButton }}
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
></SchemaComponent>
</div>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
this.app.router.add('root', {
path: '/',
Component: Root,
});
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
designable: true,
});
export default app.getRootComponent();

View File

@ -0,0 +1,38 @@
import { ApplicationOptions, Grid, Plugin, SchemaComponent } from '@nocobase/client';
import React from 'react';
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'MyInitializer',
properties: {},
}}
/>
</div>
);
};
class PluginHello extends Plugin {
async load() {
this.app.addComponents({ Grid });
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
const appOptions: ApplicationOptions = {
router: {
type: 'memory',
},
designable: true,
plugins: [PluginHello],
};
export { appOptions };

View File

@ -0,0 +1,130 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Field } from '@formily/core';
import { Avatar } from 'antd';
const Hello = observer(() => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
function Demo() {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
title: itemConfig.title,
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick} />;
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
insertPosition: 'beforeEnd',
Component: (props: any) => (
<Avatar style={{ cursor: 'pointer' }} {...props}>
C
</Avatar>
),
componentProps: {
size: 'large',
},
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
},
{
name: 'b',
title: 'Item B',
Component: Demo,
},
],
});
const AddBlockButton = observer(() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return render();
});
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return (
<div>
<SchemaComponent
components={{ Page, Hello, AddBlockButton }}
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
></SchemaComponent>
</div>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
this.app.router.add('root', {
path: '/',
Component: Root,
});
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
designable: true,
});
export default app.getRootComponent();

View File

@ -0,0 +1,57 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaInitializer } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'A Group Title',
type: 'itemGroup',
children: [
{
name: 'a1',
type: 'item',
title: 'A1',
},
{
name: 'a2',
type: 'item',
title: 'A2',
},
],
},
{
name: 'd',
type: 'divider',
},
{
name: 'b',
title: 'B Group Title',
type: 'itemGroup',
children: [
{
name: 'a1',
type: 'item',
title: 'B1',
},
{
name: 'a2',
type: 'item',
title: 'B2',
},
],
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,71 @@
import { Application, SchemaInitializer, SchemaInitializerItemGroup } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
import React from 'react';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'A Group Title',
type: 'itemGroup',
children: [
{
name: 'a1',
type: 'item',
title: 'A1',
},
{
name: 'a2',
type: 'item',
title: 'A2',
},
],
},
{
name: 'b',
title: 'B Group Title',
type: 'itemGroup',
divider: true, // 渲染分割线
useChildren() {
// 动态子元素
return [
{
name: 'b1',
type: 'item',
title: 'B1',
},
{
name: 'b2',
type: 'item',
title: 'B2',
},
];
},
},
{
name: 'c',
Component: () => {
return (
<SchemaInitializerItemGroup title="C Group Title">
{[
{
name: 'c1',
type: 'item',
title: 'C1',
},
]}
</SchemaInitializerItemGroup>
);
},
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,88 @@
/**
* defaultShowCode: true
*/
import {
Grid,
SchemaInitializer,
useSchemaInitializer,
SchemaInitializerItem,
CardItem,
Application,
} from '@nocobase/client';
import React from 'react';
import { appOptions } from './schema-initializer-common';
import { useFieldSchema } from '@formily/react';
const Demo = () => {
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={'Demo-组件方式定义'} onClick={handleClick}></SchemaInitializerItem>;
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
wrap: Grid.wrap,
items: [
{
name: 'demo1',
Component: Demo,
},
{
name: 'demo2',
type: 'item',
useComponentProps() {
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
};
return {
title: 'type 方式定义',
onClick: handleClick,
};
},
},
{
name: 'demo3',
title: 'with items',
type: 'item',
onClick(args) {
console.log(args);
},
items: [
{
label: 'aaa',
value: 'aaa',
},
{
label: 'bbb',
value: 'bbb',
},
],
},
],
});
const Hello = () => {
const schema = useFieldSchema();
return <h1>Hello, world! {schema.name}</h1>;
};
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
components: { CardItem, Hello },
});
export default app.getRootComponent();

View File

@ -0,0 +1,53 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaInitializer } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'A subMenu',
type: 'subMenu',
children: [
{
name: 'a1',
type: 'item',
title: 'A1',
},
{
name: 'a2',
type: 'item',
title: 'A2',
},
],
},
{
name: 'b',
title: 'B subMenu',
type: 'subMenu',
children: [
{
name: 'a1',
type: 'item',
title: 'B1',
},
{
name: 'a2',
type: 'item',
title: 'B2',
},
],
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,53 @@
/**
* defaultShowCode: true
*/
import { useFieldSchema } from '@formily/react';
import { Application, Grid, SchemaInitializer, SchemaInitializerSelect, useDesignable } from '@nocobase/client';
import React from 'react';
import { appOptions } from './schema-initializer-common';
const OpenModeSelect = () => {
const fieldSchema = useFieldSchema();
const openModeValue = fieldSchema?.['x-component-props']?.['openMode'] || 'drawer';
const { patch } = useDesignable();
const handleChange = (value) => {
// 修改当前节点的 Schema 的属性
patch({
'x-component-props': {
openMode: value,
},
});
};
return (
<SchemaInitializerSelect
title={'Open mode'}
options={[
{ label: 'Drawer', value: 'drawer' },
{ label: 'Dialog', value: 'modal' },
]}
value={openModeValue}
onChange={handleChange}
/>
);
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
wrap: Grid.wrap,
items: [
{
name: 'openMode',
Component: OpenModeSelect,
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,88 @@
/**
* defaultShowCode: true
*/
import {
Grid,
SchemaInitializer,
Application,
SchemaInitializerSwitch,
useCurrentSchema,
useSchemaInitializer,
Action,
} from '@nocobase/client';
import React from 'react';
import { appOptions } from './schema-initializer-common';
const actionKey = 'x-action';
const schema = {
type: 'void',
[actionKey]: 'create',
title: "{{t('Add new')}}",
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
};
const AddNewButton = () => {
// 判断是否已插入
const { exists, remove } = useCurrentSchema(schema[actionKey], actionKey);
const { insert } = useSchemaInitializer();
return (
<SchemaInitializerSwitch
checked={exists}
title={'Add new - Component方式'}
onClick={() => {
// 如果已插入,则移除
if (exists) {
return remove();
}
// 新插入子节点
insert(schema);
}}
/>
);
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Configure actions',
wrap: Grid.wrap,
items: [
{
name: 'Add New',
Component: AddNewButton,
},
{
name: 'Add New2',
type: 'switch',
useComponentProps() {
const { exists, remove } = useCurrentSchema(schema[actionKey], actionKey);
const { insert } = useSchemaInitializer();
return {
checked: exists,
title: 'Add new - type 方式',
onClick() {
// 如果已插入,则移除
if (exists) {
return remove();
}
// 新插入子节点
insert(schema);
},
};
},
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
components: { Action },
});
export default app.getRootComponent();

View File

@ -0,0 +1,33 @@
/**
* defaultShowCode: true
*/
import React, { ReactNode } from 'react';
import { Application, SchemaInitializer, SchemaInitializerItem, useSchemaInitializerItem } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
import { TableOutlined } from '@ant-design/icons';
const Demo = () => {
const { name, foo, icon } = useSchemaInitializerItem<{ name: string; foo: string; icon: ReactNode }>();
return <SchemaInitializerItem icon={icon} title={`${name} - ${foo}`} />;
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
foo: 'bar',
icon: <TableOutlined />,
Component: Demo,
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,143 @@
import React, { FC } from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerChild,
SchemaInitializerItem,
SchemaInitializerItemsProps,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Field } from '@formily/core';
import { ButtonProps, ListProps, List, Card } from 'antd';
const Hello = observer(() => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
function Demo() {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
title: itemConfig.title,
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick} />;
}
const CustomListGridMenu: FC<SchemaInitializerItemsProps<ButtonProps, ListProps<any>>> = (props) => {
const { items, options, ...others } = props;
return (
<List
{...others}
style={{ marginTop: 20 }}
dataSource={items}
grid={{ gutter: 16, column: 2 }}
renderItem={(item) => (
<List.Item style={{ minWidth: 100, textAlign: 'center' }}>
<SchemaInitializerChild {...item} />
</List.Item>
)}
></List>
);
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
// 插入位置
insertPosition: 'beforeEnd',
ItemsComponent: CustomListGridMenu,
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
},
{
name: 'b',
title: 'Item B',
Component: Demo,
},
],
});
const AddBlockButton = observer(() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return render();
});
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return (
<div>
<SchemaComponent
components={{ Page, Hello, AddBlockButton }}
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
></SchemaComponent>
</div>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
this.app.router.add('root', {
path: '/',
Component: Root,
});
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
designable: true,
});
export default app.getRootComponent();

View File

@ -0,0 +1,61 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaInitializer, SchemaInitializerItem } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
type: 'itemGroup',
title: '静态 children',
children: [
{
name: 'a1',
type: 'item',
title: 'A 1',
onClick: () => {
alert('a-1');
},
},
{
name: 'a2',
type: 'item',
title: 'A 2',
},
],
},
{
name: 'a',
type: 'itemGroup',
title: '动态 children',
useChildren() {
return [
{
name: 'a1',
type: 'item',
title: 'A 1',
onClick: () => {
alert('a-1');
},
},
{
name: 'a2',
type: 'item',
title: 'A 2',
},
];
},
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,34 @@
/**
* defaultShowCode: true
*/
import React from 'react';
import { Application, SchemaInitializer, SchemaInitializerItem } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const Demo = () => {
// 最终渲染 `SchemaInitializerItem`
return <SchemaInitializerItem title="Component Demo" />;
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
Component: Demo, // 通过 Component 定义
},
{
name: 'b',
type: 'item', // 通过 `type` 定义,底层对应着 `SchemaInitializerItem` 组件
title: 'type Demo',
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,40 @@
/**
* defaultShowCode: true
*/
import React from 'react';
import { Application, SchemaInitializer, SchemaInitializerItem } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const CommonDemo = (props) => {
return <SchemaInitializerItem title={props.title} />;
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
Component: CommonDemo,
componentProps: {
title: 'componentProps',
},
},
{
name: 'b',
Component: CommonDemo,
useComponentProps() {
return {
title: 'useComponentProps',
};
},
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,32 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaInitializer } from '@nocobase/client';
import { appOptions } from './schema-initializer-common';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
type: 'item',
title: 'Demo A',
},
{
name: 'b',
type: 'item',
title: 'Demo B',
useVisible() {
return false;
},
},
],
});
const app = new Application({
...appOptions,
schemaInitializers: [myInitializer],
});
export default app.getRootComponent();

View File

@ -0,0 +1,111 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
useDesignable,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Field } from '@formily/core';
import { Button } from 'antd';
const Hello = observer(() => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
const MyInitializerComponent = () => {
const { insertBeforeEnd } = useDesignable();
return (
<Button
onClick={() =>
insertBeforeEnd({
type: 'void',
title: Math.random(),
'x-component': 'Hello',
})
}
>
Add block
</Button>
);
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
popover: false,
Component: MyInitializerComponent,
});
const AddBlockButton = observer(() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return render();
});
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return (
<div>
<SchemaComponent
components={{ Page, Hello, AddBlockButton }}
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
></SchemaComponent>
</div>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
this.app.router.add('root', {
path: '/',
Component: Root,
});
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
designable: true,
});
export default app.getRootComponent();

View File

@ -0,0 +1,127 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Field } from '@formily/core';
const Hello = observer(() => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
function Demo() {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
title: itemConfig.title,
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick} />;
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
// 插入位置
insertPosition: 'beforeEnd',
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
},
{
name: 'b',
title: 'Item B',
Component: Demo,
},
],
});
const AddBlockButton = observer(() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return (
<div>
<div>{render()}</div>
<div>{render({ style: { color: 'red' } })}</div>
</div>
);
});
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return (
<div>
<SchemaComponent
components={{ Page, Hello, AddBlockButton }}
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
></SchemaComponent>
</div>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
this.app.router.add('root', {
path: '/',
Component: Root,
});
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
designable: true,
});
export default app.getRootComponent();

View File

@ -0,0 +1,48 @@
import { ApplicationOptions, CardItem, Grid, Plugin, SchemaComponent } from '@nocobase/client';
import React from 'react';
const Hello = () => {
return <h1>Hello, world!</h1>;
};
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-component': 'Grid',
properties: {
hello: Grid.wrap({
type: 'void',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
}),
},
}}
/>
</div>
);
};
class PluginHello extends Plugin {
async load() {
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
const appOptions: ApplicationOptions = {
router: {
type: 'memory',
},
designable: true,
components: { Grid, CardItem, Hello },
plugins: [PluginHello],
};
export { appOptions };

View File

@ -0,0 +1,66 @@
/**
* defaultShowCode: true
*/
import React from 'react';
import {
Application,
FormItem,
Input,
SchemaSettings,
SchemaSettingsActionModalItem,
useDesignable,
} from '@nocobase/client';
import { appOptions } from './schema-settings-common';
import { ISchema, useField } from '@formily/react';
export const SchemaSettingsBlockTitleItem = function BlockTitleItem() {
const filed = useField();
const { patch } = useDesignable();
return (
<SchemaSettingsActionModalItem
title={'Edit block title'}
schema={
{
type: 'object',
title: 'Edit block title',
properties: {
title: {
title: 'Block title',
type: 'string',
default: filed.decoratorProps?.title,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
} as ISchema
}
onSubmit={({ title }) => {
patch({
'x-decorator-props': {
title,
},
});
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'blockTitle',
Component: SchemaSettingsBlockTitleItem,
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
app.addComponents({ FormItem, Input });
export default app.getRootComponent();

View File

@ -0,0 +1,68 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'itemGroup',
componentProps: {
title: 'group A',
},
children: [
{
name: 'a1',
type: 'item',
componentProps: {
title: 'A1',
},
},
{
name: 'a2',
type: 'item',
componentProps: {
title: 'A2',
},
},
],
},
{
name: 'divider',
type: 'divider',
},
{
name: 'b',
type: 'itemGroup',
componentProps: {
title: 'group B',
},
children: [
{
name: 'b1',
type: 'item',
componentProps: {
title: 'B1',
},
},
{
name: 'b2',
type: 'item',
componentProps: {
title: 'B2',
},
},
],
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,64 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'itemGroup',
componentProps: {
title: 'group A',
},
children: [
{
name: 'a1',
type: 'item',
componentProps: {
title: 'A1',
},
},
{
name: 'a2',
type: 'item',
componentProps: {
title: 'A2',
},
},
],
},
{
name: 'b',
type: 'itemGroup',
componentProps: {
title: 'group B',
},
children: [
{
name: 'b1',
type: 'item',
componentProps: {
title: 'B1',
},
},
{
name: 'b2',
type: 'item',
componentProps: {
title: 'B2',
},
},
],
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,76 @@
/**
* defaultShowCode: true
*/
import React, { FC, useState } from 'react';
import { Application, SchemaSettings, SchemaSettingsItem, useDesignable } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
import { observer, useField } from '@formily/react';
import { Button, Input, Space } from 'antd';
const MarkdownEdit = () => {
const field = useField();
return (
<SchemaSettingsItem
title="Edit markdown"
onClick={() => {
field.editable = true;
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'markdown',
Component: MarkdownEdit,
},
],
});
const Hello: FC<{ content?: string }> = observer((props) => {
const field = useField();
const { content } = props;
const [inputVal, setInputVal] = useState(content);
const { patch } = useDesignable();
return field.editable ? (
<Space>
<Input placeholder="input value" value={inputVal} onChange={(e) => setInputVal(e.target.value)} />
<Space>
<Button
onClick={() => {
field.editable = false;
setInputVal(content);
}}
>
Cancel
</Button>
<Button
type="primary"
onClick={() => {
field.editable = false;
patch({
'x-component-props': {
content: inputVal,
},
});
}}
>
Submit
</Button>
</Space>
</Space>
) : (
<h1>{content}</h1>
);
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
app.addComponents({ Hello });
export default app.getRootComponent();

View File

@ -0,0 +1,66 @@
/**
* defaultShowCode: true
*/
import { ISchema } from '@formily/react';
import {
Application,
FormItem,
Input,
SchemaSettings,
SchemaSettingsModalItem,
useSchemaSettings,
} from '@nocobase/client';
import React from 'react';
import { appOptions } from './schema-settings-common';
export const SchemaSettingsBlockTitleItem = function BlockTitleItem() {
const { dn } = useSchemaSettings();
return (
<SchemaSettingsModalItem
title={'Edit block title'}
schema={
{
type: 'object',
title: 'Edit block title',
properties: {
title: {
title: 'Block title',
type: 'string',
default: dn.getSchemaAttribute('x-decorator-props.title'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-compile-omitted': ['default'],
},
},
} as ISchema
}
onSubmit={({ title }) => {
dn.deepMerge({
'x-decorator-props': {
title,
},
});
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'blockTitle',
Component: SchemaSettingsBlockTitleItem,
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
app.addComponents({ FormItem, Input });
export default app.getRootComponent();

View File

@ -0,0 +1,28 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn(s) {
return s['x-component'] === 'Grid'; // 其顶级是 Grid这一层级不能删
},
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,82 @@
/**
* defaultShowCode: true
*/
import React, { FC } from 'react';
import { Application, SchemaSettings, SchemaSettingsSelectItem, useDesignable } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
import { observer, useField } from '@formily/react';
import { Table } from 'antd';
const PageSize = () => {
const { patch } = useDesignable();
const filed = useField();
return (
<SchemaSettingsSelectItem
title={'Page Size'}
options={[
{ label: '5', value: 5 },
{ label: '10', value: 10 },
{ label: '15', value: 15 },
]}
value={filed.componentProps?.pageSize || 5}
onChange={(v) => {
patch({
'x-component-props': {
pageSize: v,
},
});
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'pageSize',
Component: PageSize,
},
],
});
const data = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
const Hello: FC<{ pageSize: number }> = observer((props) => {
return (
<Table
columns={[
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
]}
pagination={{ pageSize: props.pageSize || 5, total: data.length }}
dataSource={data}
/>
);
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
app.addComponents({ Hello });
export default app.getRootComponent();

View File

@ -0,0 +1,64 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'subMenu',
componentProps: {
title: 'Sub Menu A',
},
children: [
{
name: 'a1',
type: 'item',
componentProps: {
title: 'A1',
},
},
{
name: 'a2',
type: 'item',
componentProps: {
title: 'A2',
},
},
],
},
{
name: 'b',
type: 'subMenu',
componentProps: {
title: 'Sub Menu B',
},
children: [
{
name: 'b1',
type: 'item',
componentProps: {
title: 'B1',
},
},
{
name: 'b2',
type: 'item',
componentProps: {
title: 'B2',
},
},
],
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,74 @@
/**
* defaultShowCode: true
*/
import React, { FC } from 'react';
import { Application, SchemaSettings, SchemaSettingsSwitchItem, useDesignable } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
import { observer, useField } from '@formily/react';
import { Form, Input } from 'antd';
const FormItemRequired = () => {
const { patch } = useDesignable();
const filed = useField();
return (
<SchemaSettingsSwitchItem
title={'Required - 组件方式'}
checked={!!filed.componentProps?.required}
onChange={(v) => {
patch({
'x-component-props': {
required: v,
},
});
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'required',
Component: FormItemRequired,
},
{
name: 'required2',
type: 'switch',
useComponentProps() {
const { patch } = useDesignable();
const filed = useField();
return {
title: 'Required - type 方式',
checked: !!filed.componentProps?.required,
onChange(v) {
patch({
'x-component-props': {
required: v,
},
});
},
};
},
},
],
});
const Hello: FC<{ required: boolean }> = observer((props) => {
return (
<Form>
<Form.Item name="note" label="Note" rules={[{ required: props.required }]}>
<Input />
</Form.Item>
</Form>
);
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
app.addComponents({ Hello });
export default app.getRootComponent();

View File

@ -0,0 +1,68 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'itemGroup',
componentProps: {
title: '静态 children',
},
children: [
{
name: 'a1',
type: 'item',
componentProps: {
title: 'A 1',
onClick: () => {
alert('a-1');
},
},
},
{
name: 'a2',
type: 'item',
componentProps: {
title: 'A 2',
},
},
],
},
{
name: 'a',
type: 'itemGroup',
componentProps: {
title: '动态 children',
},
useChildren() {
return [
{
name: 'a1',
type: 'item',
title: 'A 1',
onClick: () => {
alert('a-1');
},
},
{
name: 'a2',
type: 'item',
title: 'A 2',
},
];
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,35 @@
/**
* defaultShowCode: true
*/
import React from 'react';
import { Application, SchemaSettings, SchemaSettingsItem } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const Demo = () => {
// 最终渲染 `SchemaInitializerItem`
return <SchemaSettingsItem title="Component Demo" />;
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
Component: Demo, // 通过 Component 定义
},
{
name: 'b',
type: 'item', // 通过 `type` 定义,底层对应着 `SchemaInitializerItem` 组件
componentProps: {
title: 'type Demo',
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,39 @@
/**
* defaultShowCode: true
*/
import React from 'react';
import { Application, SchemaSettings, SchemaSettingsItem } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const CommonDemo = (props) => {
return <SchemaSettingsItem title={props.title} />;
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
Component: CommonDemo,
componentProps: {
title: 'componentProps',
},
},
{
name: 'b',
Component: CommonDemo,
useComponentProps() {
return {
title: 'useComponentProps',
};
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,35 @@
/**
* defaultShowCode: true
*/
import { Application, SchemaSettings } from '@nocobase/client';
import { appOptions } from './schema-settings-common';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'item',
componentProps: {
title: 'Demo A',
},
},
{
name: 'b',
type: 'item',
componentProps: {
title: 'Demo B',
},
useVisible() {
return false;
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,151 @@
import { ISchema, useField, useFieldSchema } from '@formily/react';
import {
Application,
CardItem,
FormItem,
Grid,
Input,
Plugin,
SchemaComponent,
SchemaSettings,
SchemaSettingsModalItem,
createDesignable,
useAPIClient,
useSchemaComponentContext,
useSchemaSettings,
useSchemaSettingsRender,
} from '@nocobase/client';
import React, { useMemo } from 'react';
class PluginHello extends Plugin {
async load() {
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
export const SchemaSettingsBlockTitleItem = function BlockTitleItem() {
const { dn } = useSchemaSettings();
console.log(dn.current);
return (
<SchemaSettingsModalItem
title={'Edit block title'}
schema={
{
type: 'object',
title: 'Edit block title',
properties: {
title: {
title: 'Block title',
type: 'string',
default: dn.getSchemaAttribute('x-decorator-props.title'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-compile-omitted': ['default'],
},
},
} as ISchema
}
onSubmit={({ title }) => {
dn.shallowMerge({
'x-decorator-props': {
title,
},
});
}}
/>
);
};
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'blockTitle',
Component: SchemaSettingsBlockTitleItem,
},
],
});
const Hello = (props) => {
return (
<h1>
Hello, world! <Demo />
{props.children}
</h1>
);
};
const Demo = () => {
const fieldSchema = useFieldSchema();
const field = useField();
const api = useAPIClient();
const { refresh } = useSchemaComponentContext();
const dn = useMemo(
() =>
createDesignable({
current: fieldSchema.parent,
model: field.parent,
api,
refresh,
}),
[],
);
const { render, exists } = useSchemaSettingsRender(fieldSchema['x-settings'], {
fieldSchema: dn.current,
field: dn.model,
dn,
});
return (
<div>
<div>{render()}</div>
<div>{render({ style: { color: 'red' } })}</div>
</div>
);
};
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-decorator': 'CardItem',
'x-decorator-props': {
title: 'aaa',
},
properties: {
hello1: {
type: 'void',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
},
hello2: {
type: 'void',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
},
},
}}
/>
</div>
);
};
const app = new Application({
schemaSettings: [mySettings],
router: {
type: 'memory',
},
designable: true,
components: { FormItem, Input, Grid, CardItem, Hello },
plugins: [PluginHello],
});
export default app.getRootComponent();

View File

@ -0,0 +1,791 @@
# Designable
## Designable
对 Schema 节点进行增、删、改操作,并且提供了事件触发机制,用于将数据同步到服务端。
```tsx | pure
interface Options {
current: Schema;
api?: APIClient;
onSuccess?: any;
refresh?: () => void;
t?: any;
}
interface InsertAdjacentOptions {
wrap?: (s: ISchema) => ISchema;
removeParentsIfNoChildren?: boolean;
breakRemoveOn?: ISchema | BreakFn;
onSuccess?: any;
}
class Designable {
constructor(options: Options ) { }
loadAPIClientEvents(): void;
on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', listener: any): void
emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', ...args: any[]): Promise<any>
refresh(): void
recursiveRemoveIfNoChildren(schema?: Schema, options?: RecursiveRemoveOptions): Schema;
remove(schema?: Schema, options?: RemoveOptions): Promise<any>
removeWithoutEmit(schema?: Schema, options?: RemoveOptions): Schema
insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void | Promise<any>
insertBeforeBeginOrAfterEnd(schema: ISchema, options?: InsertAdjacentOptions): void
insertBeforeBegin(schema: ISchema, options?: InsertAdjacentOptions): void
insertAfterBegin(schema: ISchema, options?: InsertAdjacentOptions): void
insertBeforeEnd(schema: ISchema, options?: InsertAdjacentOptions): Promise<any>
insertAfterEnd(schema: ISchema, options?: InsertAdjacentOptions): void
}
```
### 构造函数
- 参数讲解
- `current`:需要操作的 Schema 节点
- `api`:用于发起后端请求的 [APIClient](https://docs.nocobase.com/api/sdk) 实例
- `onSuccess`:后端接口请求成功后的回调
- `refresh`:用于更新节点后,刷新页面
- `t``useTranslation()` 的返回值
- 示例
```tsx | pure
const schema = new Schema({
type: 'void',
name: 'hello',
'x-component': 'div',
})
const dn = new Designable({ current: schema });
```
### Schema 操作方法
```tsx | pure
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
}
}
}
}
});
const b = schema.b;
const dn = createDesignable({
current: b,
})
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
#### remove
移除当前节点
```tsx | pure
dn.remove();
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
dn.remove();
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
```diff
{
type: 'void',
name: 'a',
properties: {
- b: {
- type: 'void',
- properties: {
- c: {
- type: 'void',
- }
- }
- }
}
}
```
#### insertBeforeBegin
在当前节点的前面插入,并会触发 `insertAdjacent` 事件。
```tsx | pure
dn.insertBeforeBegin({
type: 'void',
name: 'd',
});
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
dn.insertBeforeBegin({
type: 'void',
name: 'd',
});
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
```diff
{
type: 'void',
name: 'a',
properties: {
+ d: {
+ type: 'void',
+ },
b: {
type: 'void',
properties: {
c: {
type: 'void',
}
}
}
}
}
```
#### insertAfterBegin
在当前节点的前面插入,并会触发 `insertAdjacent` 事件。
```tsx | pure
dn.insertAfterBegin({
type: 'void',
name: 'd',
});
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
dn.insertAfterBegin({
type: 'void',
name: 'd',
});
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
```diff
{
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
+ d: {
+ type: 'void',
+ },
c: {
type: 'void',
}
}
}
}
}
```
#### insertBeforeEnd
在当前节点的前面插入,并会触发 `insertAdjacent` 事件。
```tsx | pure
dn.insertBeforeEnd({
type: 'void',
name: 'd',
});
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
dn.insertBeforeEnd({
type: 'void',
name: 'd',
});
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
```diff
{
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
+ d: {
+ type: 'void',
+ },
}
}
}
}
```
#### insertAfterEnd
在当前节点的前面插入,并会触发 `insertAdjacent` 事件。
```tsx | pure
dn.insertAfterEnd({
type: 'void',
name: 'd',
});
console.log(schema.toJSON());
```
```tsx
import React from 'react';
import { Schema } from '@formily/json-schema';
import { createDesignable } from '@nocobase/client';
const schema = new Schema({
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
},
},
},
},
});
const b = schema.properties['b'];
const dn = createDesignable({
current: b,
});
dn.insertAfterEnd({
type: 'void',
name: 'd',
});
export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
```
```diff
{
type: 'void',
name: 'a',
properties: {
b: {
type: 'void',
properties: {
c: {
type: 'void',
}
}
},
+ d: {
+ type: 'void',
+ },
}
}
```
#### insertAdjacent
根据第一个参数决定插入的位置,是前面四个方法的封装。
```tsx | pure
class Designable {
insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void | Promise<any>
}
```
### 事件监听和 API 请求
- `on` :添加事件监听的基础方法
- `loadAPIClientEvents`:调用 `on` 方法添加对 `insertAdjacent`、`patch`、`batchPatch`、`remove` 的事件的监听,主要功能是将变更的 Schema 更新到服务端
- `emit`:是根据事件名称,调用之前注册过的方法,具体是由前面讲过的 *插入操作和删除操作* 触发
`loadAPIClientEvents()` 并非在初始化时调用,需要手动调用,换而言之,如果不调用 `dn.loadAPIClientEvents()`,则不会将更新发送到服务端,主要是简化在单测或者 DEMO 环境对服务端的 Mock。
## 工具函数
### createDesignable()
`new Designable()` 的简单封装。
```tsx | pure
function createDesignable(options: CreateDesignableProps) {
return new Designable(options);
}
```
```tsx | pure
const dn = createDesignable({ current: schema });
```
## Hooks
### useFieldSchema()
用户获取当前节点 Schema JSON 对象,更多信息请参考 [formily useFieldSchema()](https://react.formilyjs.org/api/hooks/use-field-schema)。
- 类型
```tsx | pure
import { Schema } from '@formily/json-schema';
const useFieldSchema: () => Schema;
```
- 示例
```tsx
/**
* defaultShowCode: true
*/
import React from 'react';
import { useFieldSchema } from '@formily/react';
import { Application, Plugin, SchemaComponent } from '@nocobase/client';
const Demo = ({ children }) => {
const fieldSchema = useFieldSchema();
return <div style={{ border: '1px solid red' }}>
<pre>{ JSON.stringify(fieldSchema, null, 2)}</pre>
<div style={{ paddingLeft: 20 }}>{children}</div>
</div>
}
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Demo', // 这里是 Demo 组件
'properties': {
'world': {
'type': 'void',
'x-component': 'Demo', // 这里也是 Demo 组件
},
}
}
const Root = () => {
return <SchemaComponent components={{ Demo }} schema={schema} />
}
const app = new Application({
providers: [Root]
})
export default app.getRootComponent();
```
### useField()
获取当前节点 Schema 实例,更多信息请参考 [formily useField()](https://react.formilyjs.org/api/hooks/use-field)
- 类型
```tsx | pure
import { GeneralField } from '@formily/core';
const useField: <T = GeneralField>() => T;
```
- 示例
```tsx | pure
const Demo = () => {
const field = useField();
console.log('field', field);
return <div></div>
}
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Demo', // 这里是 Demo 组件
'properties': {
'world': {
'type': 'void',
'x-component': 'Demo', // 这里也是 Demo 组件
},
}
}
const Root = () => {
return <SchemaComponent components={{ Hello }} schema={schema} />
}
```
### useDesignable()
对当前 Schema 节点的修改操作。
- 类型
```tsx | pure
interface InsertAdjacentOptions {
wrap?: (s: ISchema) => ISchema;
removeParentsIfNoChildren?: boolean;
breakRemoveOn?: ISchema | BreakFn;
onSuccess?: any;
}
type Position = 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
function useDesignable(): {
dn: Designable;
designable: boolean;
reset: () => void;
refresh: () => void;
setDesignable: (value: boolean) => void;
findComponent(component: any): any;
patch: (key: ISchema | string, value?: any) => void
on(name: "error" | "insertAdjacent" | "remove" | "patch" | "batchPatch", listener: any): void
remove(schema?: any, options?: RemoveOptions): void
insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void
insertBeforeBegin(schema: ISchema): void;
insertAfterBegin(schema: ISchema): void;
insertBeforeEnd(schema: ISchema): void;
insertAfterEnd(schema: ISchema): void;
}
```
- 详细解释
- designable、reset、refresh、setDesignable这些值继承自 [SchemaComponentContext](https://www.baidu.com)
- dn`Designable` 的实例
- findComponent用于查找 Schema 中字符串对应真正的组件,如果组件未注册则返回 `null`
- remove内部调用的是 `dn.remove` 方法
- on内部调用的是 `dn.on` 方法
- insertAdjacent插入新的 Schema 节点,内部调用的是 `dn.insertAdjacent` 方法
- position插入位置
- schema新的 Schema 节点
- Options
- wrap对 Schema 的二次处理的回调函数
- removeParentsIfNoChildren当没有子元素时删除父元素
- breakRemoveOn停止删除的判断回调
- onSuccess插入成功的回调
- insertBeforeBegin内部调用的是 `dn.insertBeforeBegin` 方法
- insertAfterBegin内部调用的是 `dn.insertAfterBegin` 方法
- insertBeforeEnd内部调用的是 `dn.insertBeforeEnd` 方法
- insertAfterEnd内部调用的是 `dn.insertAfterEnd` 方法
- 示例
插入节点。
```tsx
import React from 'react';
import {
SchemaComponentProvider,
SchemaComponent,
useDesignable,
} from '@nocobase/client';
import { observer, Schema, useFieldSchema } from '@formily/react';
import { Button, Space } from 'antd';
import { uid } from '@formily/shared';
const Hello = observer(
(props) => {
const { insertAdjacent } = useDesignable();
const fieldSchema = useFieldSchema();
return (
<div>
<h1>{fieldSchema.name}</h1>
<Space>
<Button
onClick={() => {
insertAdjacent('beforeBegin', {
'x-component': 'Hello',
});
}}
>
before begin
</Button>
<Button
onClick={() => {
insertAdjacent('afterBegin', {
'x-component': 'Hello',
});
}}
>
after begin
</Button>
<Button
onClick={() => {
insertAdjacent('beforeEnd', {
'x-component': 'Hello',
});
}}
>
before end
</Button>
<Button
onClick={() => {
insertAdjacent('afterEnd', {
'x-component': 'Hello',
});
}}
>
after end
</Button>
</Space>
<div style={{ margin: 50 }}>{props.children}</div>
</div>
);
},
{ displayName: 'Hello' },
);
const Page = observer(
(props) => {
return <div>{props.children}</div>;
},
{ displayName: 'Page' },
);
export default () => {
return (
<SchemaComponentProvider components={{ Page, Hello }}>
<SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
properties: {
hello1: {
type: 'void',
'x-component': 'Hello',
},
},
}}
/>
</SchemaComponentProvider>
);
};
```
部分更新。
```tsx
import React from 'react';
import {
SchemaComponentProvider,
SchemaComponent,
useDesignable,
} from '@nocobase/client';
import { observer, Schema, useField, useFieldSchema } from '@formily/react';
import { FormItem } from '@formily/antd-v5';
import { Button } from 'antd';
import { uid } from '@formily/shared';
const Hello = observer(
(props) => {
const fieldSchema = useFieldSchema();
const field = useField();
const { dn } = useDesignable();
return (
<div>
<h1>{field.title}</h1>
{ JSON.stringify(props) }
<br/>
{ JSON.stringify(field.componentProps) }
<br/>
{ JSON.stringify(field.decoratorProps) }
<br/>
{ JSON.stringify(fieldSchema.toJSON()) }
<br/>
<Button onClick={() => {
dn.shallowMerge({
title: uid(),
'x-component-props': {a: uid(), },
'x-decorator-props': {b: uid(), },
});
}}>shallowMerge</Button>
<Button onClick={() => {
dn.deepMerge({
title: uid(),
'x-component-props': {c: uid() },
'x-decorator-props': {d: uid(), },
});
}}>deepMerge</Button>
</div>
);
},
{ displayName: 'Hello' },
);
const Page = observer(
(props) => {
return <div>{props.children}</div>;
},
{ displayName: 'Page' },
);
export default () => {
return (
<SchemaComponentProvider components={{ FormItem, Page, Hello }}>
<SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
properties: {
hello1: {
type: 'string',
title: 'Title 1',
'x-decorator': 'FormItem',
'x-component': 'Hello',
},
},
}}
/>
</SchemaComponentProvider>
);
};
```

View File

@ -0,0 +1,208 @@
# SchemaComponent
## Context
### SchemaComponentContext
```tsx | pure
interface SchemaComponentContext {
scope?: any;
components?: SchemaReactComponents;
refresh?: () => void;
reset?: () => void;
designable?: boolean;
setDesignable?: (value: boolean) => void;
}
```
Schema 渲染的上下文。
- `scope`Schema 中变量的映射
- `components`: Schema 中组件的映射
- `refresh`:触发 React 重新渲染的工具函数
- `reset`:重置整个 Schema 节点
- `designable`:是否显示设计器,默认 `false`
- `setDesignable`:用于切换 `designable` 的值
## Hooks
### useSchemaComponentContext()
用于获取 `SchemaComponentContext` 的值,是 `useContext(SchemaComponentContext)` 的封装。
## 组件
### SchemaComponentProvider
其是对 `SchemaComponentContext.Provider` 和 [FormProvider ](https://react.formilyjs.org/api/components/form-provider)的封装,并内置在 `Application` 中,并且会将 `app.components``app.scopes` 传递过去,所以一般情况下 *不需要关注* 此组件。
- props
```tsx | pure
interface SchemaComponentProviderProps {
designable?: boolean;
form?: Form;
scope?: any;
components?: SchemaReactComponents;
}
```
- 详细解释
- `designable``SchemaComponentContext` 中 `designable` 的默认值
- `form`NocoBase 的 Schema 能力是基于 formily 的 `FormProvider` 提供的form 是其参数,默认为 `createForm()`
- `scope`Schema 中所用到的变量,会通过 `SchemaComponentContext` 进行传递
- `components`Schema 中所用到的组件,会通过 `SchemaComponentContext` 进行传递
### SchemaComponent
用于渲染 Schema此组件必须和 `SchemaComponentProvider` 一起使用,因为 `SchemaComponentProvider` 提供了 [FormProvider](https://react.formilyjs.org/api/components/form-provider) 作为渲染 Schema 的根节点。
- Props
```tsx | pure
type SchemaComponentProps = (ISchemaFieldProps | IRecursionFieldProps) & {
memoized?: boolean;
components?: SchemaReactComponents;
scope?: any;
}
```
- 详细解释
- `memoized`:当为 `true` 时,会对每层的 Schema 使用 `useMemo()` 进行处理
- `components`:同 `SchemaComponentProvider``components`
- `scope`: 同 `SchemaComponentProvider``components`
## 综合示例
结合 `SchemaComponentProvider``useSchemaComponentContext()``SchemaComponent`
```tsx
/**
* defaultShowCode: true
*/
import { SchemaComponentProvider, useSchemaComponentContext, SchemaComponent, } from '@nocobase/client';
const Hello = () => {
const { designable, setDesignable } = useSchemaComponentContext();
return <div>
<div style={{ padding: 20, border: designable ? '1px solid red' : undefined }} contentEditable={designable}>hello world</div>
<button onClick={() => setDesignable(!designable) }>change designable</button>
</div>;
}
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Hello',
}
const Demo = () => {
return <SchemaComponent schema={schema} />
}
const Root = () => {
return <SchemaComponentProvider components={{ Hello }}>
<Demo />
</SchemaComponentProvider>
}
export default Root;
```
使用 `new Application()` 的方式,其内置了 `SchemaComponentProvider` ,我们可以如下操作:
```tsx
/**
* defaultShowCode: true
*/
import { Application, Plugin, useSchemaComponentContext, SchemaComponent } from '@nocobase/client';
const Hello = () => {
const { designable, setDesignable } = useSchemaComponentContext();
return <div>
<div style={{ padding: 20, border: designable ? '1px solid red' : undefined }} contentEditable={designable}>hello world</div>
<button onClick={() => setDesignable(!designable) }>change designable</button>
</div>;
}
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Hello',
}
const HomePage = () => {
return <SchemaComponent components={{ Hello }} schema={schema} />
}
class MyPlugin extends Plugin {
async load() {
this.app.addComponents({ Hello });
this.app.router.add('home', {
path: '/',
Component: HomePage,
})
}
}
const app = new Application({
router: {
type: 'memory',
initialEntries: ['/'],
},
plugins: [MyPlugin],
})
export default app.getRootComponent();
```
### SchemaComponentOptions
在应用中,会有很多层级的嵌套,每一层都可能提供自己的组件和 scope此组件就是为了层层传递 Schema 所需的 `components``scope` 的。
- props
```tsx | pure
interface SchemaComponentOptionsProps {
scope?: any;
components?: SchemaReactComponents;
}
```
- 示例
```tsx
/**
* defaultShowCode: true
*/
import { SchemaComponentProvider, useSchemaComponentContext, SchemaComponent, SchemaComponentOptions } from '@nocobase/client';
const World = () => {
return <div>world</div>
}
const Hello = ({ children }) => {
return <div>
<div>hello</div>
<SchemaComponentOptions components={{ World }}>{ children }</SchemaComponentOptions>
</div>;
}
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'void',
'x-component': 'World',
},
},
}
const Root = () => {
return <SchemaComponentProvider components={{ Hello }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
}
export default Root;
```

View File

@ -0,0 +1,186 @@
# SchemaInitializerManager
## 实例方法
### schemaInitializerManager.add()
添加 SchemaInitializer 实例。
- 类型
```tsx | pure
class SchemaInitializerManager {
add<P1 = any, P2 = any>(...schemaInitializerList: SchemaInitializer<P1, P2>[]): void
}
```
- 示例
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add block',
items: [
{
name: 'demo',
type: 'item',
title: 'Demo'
}
],
});
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.add(myInitializer);
}
}
```
### schemaInitializerManager.get()
获取一个 SchemaInitializer 实例。
- 类型
```tsx | pure
class SchemaInitializerManager {
get<P1 = ButtonProps, P2 = {}>(name: string): SchemaInitializer<P1, P2> | undefined
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const myInitializer = this.app.schemaInitializerManager.get('MyInitializer');
}
}
```
### schemaInitializerManager.getAll()
获取所有的 SchemaInitializer 实例。
- 类型
```tsx | pure
class SchemaInitializerManager {
getAll(): Record<string, SchemaInitializer<any, any>>
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const list = this.app.schemaInitializerManager.getAll();
}
}
```
### app.schemaInitializerManager.has()
判断是否有存在某个 SchemaInitializer 实例。
- 类型
```tsx | pure
class SchemaInitializerManager {
has(name: string): boolean
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const hasMyInitializer = this.app.schemaInitializerManager.has('MyInitializer');
}
}
```
### schemaInitializerManager.remove()
移除 SchemaInitializer 实例。
- 类型
```tsx | pure
class SchemaInitializerManager {
remove(name: string): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.schemaInitializerManager.remove('MyInitializer');
}
}
```
### schemaInitializerManager.addItem()
添加 SchemaInitializer 实例的 Item 项,其和直接 schemaInitializer.add() 方法的区别是,可以确保在实例存在时才会添加。
- 类型
```tsx | pure
class SchemaInitializerManager {
addItem(schemaInitializerName: string, itemName: string, data: Omit<SchemaInitializerItemType, 'name'>): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
// 方式1先获取再添加子项需要确保已注册
const myInitializer = this.app.schemaInitializerManager.get('MyInitializer');
if (myInitializer) {
myInitializer.add('b', { type: 'item', title: 'B' })
}
// 方式2通过 addItem内部确保在 MyInitializer 注册时才会添加
this.app.schemaInitializerManager.addItem('MyInitializer', 'b', {
type: 'item',
title: 'B'
})
}
}
```
### schemaInitializerManager.removeItem()
移除 实例的 Item 项,其和直接 schemaInitializer.remove() 方法的区别是,可以确保在实例存在时才会移除。
- 类型
```tsx | pure
class SchemaInitializerManager {
removeItem(schemaInitializerName: string, itemName: string): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
// 方式1先获取再删除子项需要确保已注册
const myInitializer = this.app.schemaInitializerManager.get('MyInitializer');
if (myInitializer) {
myInitializer.remove('a')
}
// 方式2通过 addItem内部确保在 MyInitializer 注册时才会移除
this.app.schemaInitializerManager.remove('MyInitializer', 'a')
}
}
```

View File

@ -0,0 +1,704 @@
# SchemaInitializer
## new SchemaInitializer(options)
```tsx | pure
interface SchemaInitializerOptions<P1 = ButtonProps, P2 = {}> {
Component?: ComponentType<P1>;
componentProps?: P1;
style?: React.CSSProperties;
title?: string;
icon?: ReactNode;
items?: SchemaInitializerItemType[];
ItemsComponent?: ComponentType<P2>;
itemsComponentProps?: P2;
itemsComponentStyle?: React.CSSProperties;
insertPosition?: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
designable?: boolean;
wrap?: (s: ISchema) => ISchema;
onSuccess?: (data: any) => void;
insert?: InsertType;
useInsert?: () => InsertType;
popover?: boolean;
popoverProps?: PopoverProps;
}
class SchemaInitializer<P1 = ButtonProps, P2 = {}> {
constructor(options: SchemaInitializerOptions<P1, P2> & { name: string }): SchemaInitializer<P1, P2>;
add(name: string, item: Omit<SchemaInitializerItemType, 'name'>): void
get(nestedName: string): SchemaInitializerItemType | undefined
remove(nestedName: string): void
}
```
### 详细解释
![](../static/KTUWb69kioUg8bxYTAMc2ReDnRg.png)
- name唯一标识必填
- Component 相关
- Component触发组件默认是 `Button` 组件
- componentProps: 组件属性,默认是 `ButtonProps`
- title 按钮的文本
- icon按钮的 icon 属性
- style组件的样式
- Items 相关
- items列表项配置
- ItemsComponent默认是渲染成一个列表的形式可通过此参数自定义 items
- itemsComponentProps`ItemsComponent` 的属性
- itemsComponentStyle`ItemsComponent` 的样式
- popover 组件相关
- popover是否使用 popover默认为 `true`
- popoverPropspopover 的属性
- Schema 操作相关
- insertPosition插入位置参考[useDesignable()](/core/ui-schema/designable#usedesignable)
- designable是否显示设计模式参考[useDesignable()](/core/ui-schema/designable#usedesignable)
- wrap对 Schema 的二次处理,参考:[useDesignable()](/core/ui-schema/designable#usedesignable)
- onSuccessSchema 更新到服务端后的回调,参考:[useDesignable()](/core/ui-schema/designable#usedesignable)
- insert自定义 Schema 插入逻辑,默认为 [useDesignable()](/core/ui-schema/designable#usedesignable) 的 `insertAdjacent`
- useInsert当自定义插入 Schema 的逻辑需要用到 Hooks 时,可以使用此参数
### 示例
#### 基础用法
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
// 插入位置
insertPosition: 'beforeEnd',
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
},
],
});
```
<code src="./demos/schema-initializer-basic.tsx"></code>
#### 定制化 `Component`
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
Component: (props) => (
<Avatar style={{ cursor: 'pointer' }} {...props}>
C
</Avatar>
),
componentProps: {
size: 'large',
},
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
}
],
});
```
<code src="./demos/schema-initializer-component.tsx"></code>
#### 不使用 Popover
关于 `useDesignable()` 的说明请参考 [useDesignable](/core/ui-schema/designable#usedesignable)。
```tsx | pure
const schema = {
type: 'void',
title: Math.random(),
'x-component': 'Hello',
};
const MyInitializerComponent = () => {
const { insertBeforeEnd } = useDesignable();
return <Button onClick={() => insertBeforeEnd(schema)}>Add block</Button>
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add block',
popover: false,
Component: MyInitializerComponent,
});
```
<code src="./demos/schema-initializer-popover.tsx"></code>
#### 定制化 Items
```tsx | pure
const CustomListGridMenu: FC<SchemaInitializerItemsProps<ButtonProps, ListProps<any>>> = (props) => {
const { items, options, ...others } = props;
return (
<List
{...others}
style={{ marginTop: 20 }}
dataSource={items}
grid={{ gutter: 16, column: 2 }}
renderItem={(item) => (
<List.Item style={{ minWidth: 100, textAlign: 'center' }}>
<SchemaInitializerChild {...item} />
</List.Item>
)}
></List>
);
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
ItemsComponent: CustomListGridMenu,
items: [
{
name: 'a',
title: 'Item A',
Component: Demo,
}
],
});
```
<code src="./demos/schema-initializer-items.tsx"></code>
## options.items 配置详解
### 类型
```tsx | pure
interface SchemaInitializerComponentCommonProps {
title?: string;
schema?: ISchema;
style?: React.CSSProperties;
className?: string;
}
interface SchemaInitializerItemBaseType<T = {}> extends SchemaInitializerComponentCommonProps {
name: string;
sort?: number;
type?: string;
Component?: string | ComponentType<T>;
componentProps?: Omit<T, 'children'>;
useComponentProps?: () => Omit<T, 'children'>;
useVisible?: () => boolean;
children?: SchemaInitializerItemType[];
useChildren?: () => SchemaInitializerItemType[];
[index: string]: any;
}
```
### 两种定义方式:`Component` 和 `type`
- 通过 `Component` 定义
```tsx | pure
const Demo = () => {
// 最终渲染 `SchemaInitializerItem`
return <SchemaInitializerItem title='Demo' />
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
items: [
{
name: 'a',
Component: Demo, // 通过 Component 定义
}
],
});
```
- 通过 `type` 定义
NocoBase 内置了一些常用的 `type`,例如 `type: 'item'`,相当于 `Component: SchemaInitializerItem`
更多内置类型,请参考:[内置组件和类型](/core/ui-schema/schema-initializer#%E5%86%85%E7%BD%AE%E7%BB%84%E4%BB%B6%E5%92%8C%E7%B1%BB%E5%9E%8B)
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
items: [
{
name: 'a',
type: 'item',
title: 'Demo'
}
],
});
```
<code src="./demos/schema-initializer-options-item-define.tsx"></code>
### `children` 和动态方式 `useChildren`
对于某些组件而言是有子列表项的,例如 `type: 'itemGroup'`,那么我们使用 children 属性,同时考虑到某些场景下 children 是动态的,需要从 Hooks 里面获取,那么就可以通过 `useChildren` 来定义。
<code src="./demos/schema-initializer-options-item-children.tsx"></code>
### 动态显示隐藏 `useVisible`
<code src="./demos/schema-initializer-options-item-visible.tsx"></code>
### 组件属性 `componentProps` 和动态属性 `useComponentProps`
对于一些通用组件,我们可以通过 `componentProps` 来定义组件属性,同时考虑到某些场景下组件属性是动态的,需要从 Hooks 里面获取,那么就可以通过 `useComponentProps` 来定义。
当然也可以不使用这两个属性,直接封装成一个组件,然后通过 `Component` 来定义。
<code src="./demos/schema-initializer-options-item-props.tsx"></code>
### 公共属性和组件属性
```tsx | pure
{
name: 'demo',
title: 'Demo',
foo: 'bar',
Component: Demo,
componentProps: {
zzz: 'xxx',
},
}
```
从上面的示例中我么看到,从配置项中获取组件组件所需的数据有两个方式:
- 组件属性:通过 `componentProps` 来定义,例如 `zzz: 'xxx'`
- 公共属性:将属性直接定义在配置项上,例如 `foo: 'bar'`、`name`、`title`
在获取上
- `componentProps` 定义的数据会被传递给组件的 `props`
- 直接定义在配置项上的数据会则需要通过 [useSchemaInitializerItem()](/core/ui-schema/schema-initializer#useschemainitializeritem) 获取
```tsx | pure
const Demo = (props) => {
console.log(props); // { zzz: 'xxx' }
const { foo } = useSchemaInitializerItem(); // { foo: 'bar' }
}
```
## 实例方法
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'item a',
type: 'itemGroup',
children: [
{
name: 'a1',
title: 'item a1',
}
],
},
],
});
```
```tsx
import { SchemaInitializer, Application, useSchemaInitializerRender } from '@nocobase/client';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'item a',
type: 'itemGroup',
children: [
{
name: 'a1',
title: 'item a1',
type: 'item',
}
],
},
],
});
const Root = () => {
const { render } = useSchemaInitializerRender('MyInitializer');
return render();
}
const app = new Application({
schemaInitializers: [myInitializer],
providers: [Root],
designable: true,
});
export default app.getRootComponent();
```
### schemaInitializer.add()
用于新增 Item另一种添加方式参考 [schemaInitializerManager.addItem()](/core/ui-schema/schema-initializer-manager#schemainitializermanageradditem);
- 类型
```tsx | pure
class SchemaInitializer {
add(name: string, item: Omit<SchemaInitializerItemType, 'name'>): void
}
```
- 参数说明
第一个参数是 name作为唯一标识用于增删改查并且 `name` 支持 `.` 用于分割层级。
- 示例
```tsx | pure
myInitializer.add('b', {
type: 'item',
title: 'item b',
})
myInitializer.add('a.a2', {
type: 'item',
title: 'item a2',
})
```
```tsx
import { SchemaInitializer, Application, useSchemaInitializerRender } from '@nocobase/client';
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'item a',
type: 'itemGroup',
children: [
{
name: 'a1',
title: 'item a1',
type: 'item',
}
],
},
],
});
myInitializer.add('b', {
type: 'item',
title: 'item b',
})
myInitializer.add('a.a2', {
type: 'item',
title: 'item a2',
})
const Root = () => {
const { render } = useSchemaInitializerRender('MyInitializer');
return render();
}
const app = new Application({
schemaInitializers: [myInitializer],
providers: [Root],
designable: true,
});
export default app.getRootComponent();
```
### schemaInitializer.get()
- 类型
```tsx | pure
class SchemaInitializer {
get(nestedName: string): SchemaInitializerItemType | undefined
}
```
- 示例
```tsx | pure
const itemA = myInitializer.get('a')
const itemA1 = myInitializer.add('a.a1')
```
### schemaInitializer.remove()
另一种移除方式参考 [schemaInitializerManager.addItem()](/core/ui-schema/schema-initializer-manager#schemainitializermanagerremoveitem);
- 类型
```tsx | pure
class SchemaInitializer {
remove(nestedName: string): void
}
```
- 示例
```tsx | pure
myInitializer.remove('a.a1')
myInitializer.remove('a')
```
## Hooks
### useSchemaInitializer()
用于获取 `SchemaInitializer` 上下文内容。
- 类型
```tsx | pure
export type InsertType = (s: ISchema) => void;
const useSchemaInitializer: () => {
insert: InsertType;
options: SchemaInitializerOptions<any>;
visible?: boolean;
setVisible?: (v: boolean) => void;
}
```
- 参数详解
- `insert`:参数是 Schema 对象,用于插入 Schema
- `options`:获取 `new SchemaInitializer(options)` 时 options 配置
- `visible`popover 是否显示
- `setVisible`:设置 popover 显示状态
- 示例
```tsx | pure
const schema = {
type: 'void',
'x-component': 'Hello',
}
const Demo = () => {
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert(schema);
};
return <SchemaInitializerItem title={'Demo'} onClick={handleClick}></SchemaInitializerItem>;
}
```
### useSchemaInitializerRender()
用于渲染 `SchemaInitializer`
- 类型
```tsx | pure
function useSchemaInitializerRender(name: string, options?: SchemaInitializerOptions): {
exists: boolean;
render: (props?: SchemaInitializerOptions) => React.FunctionComponentElement;
}
```
- 参数详解
返回的 `render` 方法可以接收一个参数,用于覆盖 `new SchemaInitializer(options)``options` 配置。
- 示例
```tsx | pure
const Demo = () => {
const filedSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
return <div>
<div>{ render() }</div>
<div>可以进行参数的二次覆盖:{ render({ style: { color: 'red' } }) }</div>
</div>
}
```
<code src="./demos/schema-initializer-render.tsx"></code>
### useSchemaInitializerItem()
用于获取配置项内容的,配置项是指的 `SchemaInitializer` 中的 `items` 中的一项。
- 类型
```tsx | pure
const useSchemaInitializerItem: <T = any>() => T
```
- 示例
```tsx | pure
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'a',
title: 'Item A',
foo: 'bar',
Component: Demo,
},
],
});
/**
* 通过 useSchemaInitializerItem() 获取到的是
* {
* name: 'a',
* title: 'Item A',
* foo: 'bar',
* Component: Demo,
* }
*/
const Demo = () => {
const { title, foo } = useSchemaInitializerItem();
return <div>{ title } - { foo }</div>
}
```
<code src="./demos/schema-initializer-hooks-item.tsx"></code>
## 内置组件和类型
| type | Component | 效果 |
| ----------- | ------------------------------ | ----------------------------------------- |
| item | SchemaInitializerItem | 文本|
| itemGroup | SchemaInitializerItemGroup | 分组,同 antd `Menu` 组件的 `type: 'group'` |
| subMenu | SchemaInitializerSubMenu | 子菜单,同 antd `Menu` 组件的子菜单 |
| divider | SchemaInitializerDivider | 分割线,同 antd `Menu` 组件的 `type: 'divider'` |
| switch | SchemaInitializerSwitch | 开关 |
| actionModal | SchemaInitializerActionModal | 弹窗|
以下每个示例都提供了 2 种[定义方式](/core/ui-schema/schema-initializer#两种定义方式component-和-type),一种是通过 `Component` 定义,另一种是通过 `type` 定义。
### `type: 'item'` & `SchemaInitializerItem`
文本项。
```tsx | pure
interface SchemaInitializerItemProps {
style?: React.CSSProperties;
className?: string;
name?: string;
icon?: React.ReactNode;
title?: React.ReactNode;
items?: SchemaInitializerItemType[];
onClick?: (args?: any) => any;
}
```
核心参数是 `title`、`icon`、`onClick`、`items`,其中 `onClick` 用于插入 Schema`items` 用于渲染子列表项。
<code src="./demos/schema-initializer-components-item.tsx"></code>
### `type: 'itemGroup'` & SchemaInitializerItemGroup
分组。
```tsx | pure
interface SchemaInitializerItemGroupProps {
name: string;
title: string;
children?: SchemaInitializerOptions['items'];
divider?: boolean;
}
```
核心参数是 `title`、`children`,其中 `children` 用于渲染子列表项,`divider` 用于渲染分割线。
<code src="./demos/schema-initializer-components-group.tsx"></code>
### `type: 'switch'` & SchemaInitializerSwitch
Switch 切换按钮。
```tsx | pure
interface SchemaInitializerSwitchItemProps extends SchemaInitializerItemProps {
checked?: boolean;
disabled?: boolean;
}
```
核心参数是 `checked`、`onClick`,其中 `onClick` 用于插入或者移除 Schema。
<code src="./demos/schema-initializer-components-switch.tsx"></code>
### `type: 'subMenu'` & SchemaInitializerSubMenu
子菜单。
<code src="./demos/schema-initializer-components-menu.tsx"></code>
### `type: 'divider'` & SchemaInitializerDivider
分割线。
<code src="./demos/schema-initializer-components-divider.tsx"></code>
## 渲染组件
### SchemaInitializerChildren
用于自定义渲染多个列表项。
```tsx | pure
const Demo = ({ children }) => {
// children: [{ name: 'a1', Component: ItemA1 }, { name: 'a2', type: 'item', title: 'ItemA2' }]
return <SchemaInitializerChildren>{ children }</SchemaInitializerChildren>
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
items: [
{
name: 'test',
Component: Demo,
children: [
{
name: 'a1',
Component: ItemA1,
},
{
name: 'a2',
type: 'item',
title: 'ItemA2',
}
]
}
],
});
```
### SchemaInitializerChild
用于自定义渲染单个列表项。
```tsx | pure
const Demo = (props) => {
return <SchemaInitializerChild {...props}/>
}
```

View File

@ -0,0 +1,188 @@
# SchemaSettingsManager
## 实例方法
### schemaSettingsManager.add()
添加 SchemaSettings 实例。
- 类型
```tsx | pure
class SchemaSettingsManager {
add<T = any>(...schemaSettingList: SchemaSetting<T>[]): void
}
```
- 示例
```tsx | pure
const mySchemaSettings = new SchemaSetting({
name: 'MySchemaSettings',
title: 'Add block',
items: [
{
name: 'demo',
type: 'item',
componentProps:{
title: 'Demo'
}
}
],
});
class MyPlugin extends Plugin {
async load() {
this.app.schemaSettingsManager.add(mySchemaSettings);
}
}
```
### schemaSettingsManager.get()
获取一个 SchemaSettings 实例。
- 类型
```tsx | pure
class SchemaSettingsManager {
get<T = any>(name: string): SchemaSetting<T> | undefined
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const mySchemaSettings = this.app.schemaSettingsManager.get('MySchemaSettings');
}
}
```
### schemaSettingsManager.getAll()
获取所有的 SchemaSettings 实例。
- 类型
```tsx | pure
class SchemaSettingsManager {
getAll(): Record<string, SchemaInitializer<any, any>>
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const list = this.app.schemaSettingsManager.getAll();
}
}
```
### app.schemaSettingsManager.has()
判断是否有存在某个 SchemaSettings 实例。
- 类型
```tsx | pure
class SchemaSettingsManager {
has(name: string): boolean
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const hasMySchemaSettings = this.app.schemaSettingsManager.has('MySchemaSettings');
}
}
```
### schemaSettingsManager.remove()
移除 SchemaSettings 实例。
- 类型
```tsx | pure
class SchemaSettingsManager {
remove(name: string): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.schemaSettingsManager.remove('MySchemaSettings');
}
}
```
### schemaSettingsManager.addItem()
添加 SchemaSettings 实例的 Item 项,其和直接 schemaInitializer.add() 方法的区别是,可以确保在实例存在时才会添加。
- 类型
```tsx | pure
class SchemaSettingsManager {
addItem(schemaInitializerName: string, itemName: string, data: Omit<SchemaInitializerItemType, 'name'>): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
// 方式1先获取再添加子项需要确保已注册
const mySchemaSettings = this.app.schemaSettingsManager.get('MySchemaSettings');
if (mySchemaSettings) {
mySchemaSettings.add('b', { type: 'item', componentProps:{ title: 'B' } })
}
// 方式2通过 addItem内部确保在 mySchemaSettings 注册时才会添加
this.app.schemaSettingsManager.addItem('MySchemaSettings', 'b', {
type: 'item',
componentProps:{ title: 'B' }
})
}
}
```
### schemaSettingsManager.removeItem()
移除 实例的 Item 项,其和直接 schemaInitializer.remove() 方法的区别是,可以确保在实例存在时才会移除。
- 类型
```tsx | pure
class SchemaSettingsManager {
removeItem(schemaInitializerName: string, itemName: string): void
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
// 方式1先获取再删除子项需要确保已注册
const mySchemaSettings = this.app.schemaSettingsManager.get('MySchemaSettings');
if (mySchemaSettings) {
mySchemaSettings.remove('a')
}
// 方式2通过 addItem内部确保在 mySchemaSettings 注册时才会移除
this.app.schemaSettingsManager.remove('MySchemaSettings', 'a')
}
}
```

View File

@ -0,0 +1,467 @@
# SchemaSettings
## new SchemaSettings(options)
创建一个 SchemaSettings 实例。
```tsx | pure
interface SchemaSettingsOptions<T = {}> {
name: string;
Component?: ComponentType<T>;
componentProps?: T;
style?: React.CSSProperties;
items: SchemaSettingsItemType[];
}
class SchemaSettings<T = {}>{
constructor(options: SchemaSettingsOptions<T>): SchemaSettings<T>;
add(name: string, item: Omit<SchemaSettingsItemType, 'name'>): void
get(nestedName: string): SchemaSettingsItemType | undefined
remove(nestedName: string): void
}
```
### 详细解释
![](../static/VfPGbhWo9os0qTxcITkcHNfin4g.png)
- name唯一标识必填
- Component 相关
- Component触发组件默认是 `<MenuOutlined />` 组件
- componentProps: 组件属性
- style组件的样式
- items列表项配置
### 示例
#### 基础用法
```tsx | pure
const mySchemaSettings = new SchemaSettings({
name: 'MySchemaSettings',
items: [
{
name: 'demo1', // 唯一标识
type: 'item', // 内置类型
componentProps: {
title: 'DEMO1',
onClick() {
alert('DEMO1');
},
},
},
{
name: 'demo2',
Component: () => <SchemaSettings.Item title="DEMO2" onClick={() => alert('DEMO2')} />, // 直接使用 Component 组件
},
],
});
```
#### 定制化 `Component`
```tsx | pure
const mySchemaSettings = new SchemaSettings({
name: 'MySchemaSettings',
Component: Button, // 自定义组件
componentProps: {
type: 'primary',
children: '自定义按钮',
},
// Component: (props) => <Button type='primary' {...props}>自定义按钮</Button>, // 等同于上面效果
items: [
{
name: 'demo1',
type: 'item',
componentProps: {
title: 'DEMO',
},
},
],
});
```
## options.items 配置详解
```tsx | pure
interface SchemaSettingsItemCommon<T = {}> {
name: string;
sort?: number;
type?: string;
Component: string | ComponentType<T>;
useVisible?: () => boolean;
children?: SchemaSettingsItemType[];
useChildren?: () => SchemaSettingsItemType[];
checkChildrenLength?: boolean;
componentProps?: Omit<T, 'children'>;
useComponentProps?: () => Omit<T, 'children'>;
}
```
### 两种定义方式:`Component` 和 `type`
- 通过 `Component` 定义
```tsx | pure
const Demo = () => {
// 最终渲染 `SchemaSettingsItem`
return <SchemaSettingsItem title='Demo' />
}
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
Component: Demo, // 通过 Component 定义
}
],
});
```
- 通过 `type` 定义
NocoBase 内置了一些常用的 `type`,例如 `type: 'item'`,相当于 `Component: SchemaSettingsItem`
更多内置类型,请参考:[内置组件和类型](/core/ui-schema/schema-settings#%E5%86%85%E7%BD%AE%E7%BB%84%E4%BB%B6%E5%92%8C%E7%B1%BB%E5%9E%8B)
```tsx | pure
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'a',
type: 'item',
componentProps: {
title: 'Demo',
},
}
],
});
```
<code src="./demos/schema-settings-options-item-define.tsx"></code>
### `children` 和动态方式 `useChildren`
对于某些组件而言是有子列表项的,例如 `type: 'itemGroup'`,那么我们使用 children 属性,同时考虑到某些场景下 children 是动态的,需要从 Hooks 里面获取,那么就可以通过 `useChildren` 来定义。
<code src="./demos/schema-settings-options-item-children.tsx"></code>
### 动态显示隐藏 `useVisible`
<code src="./demos/schema-settings-options-item-visible.tsx"></code>
### 组件属性 `componentProps` 和动态属性 `useComponentProps`
对于一些通用组件,我们可以通过 `componentProps` 来定义组件属性,同时考虑到某些场景下组件属性是动态的,需要从 Hooks 里面获取,那么就可以通过 `useComponentProps` 来定义。
当然也可以不使用这两个属性,直接封装成一个组件,然后通过 `Component` 来定义。
<code src="./demos/schema-settings-options-item-props.tsx"></code>
## 实例方法
```tsx | pure
const mySchemaSettings = new SchemaSettings({
name: 'MySchemaSettings',
items: [
{
name: 'a',
type: 'itemGroup',
componentProps: {
title: 'item a'
},
children: [
{
name: 'a1',
title: 'item a1',
}
],
},
],
});
```
### schemaSettings.add()
用于新增 Item。
- 类型
```tsx | pure
class SchemaSettings {
add(name: string, item: Omit<SchemaSettingsItemType, 'name'>): void
}
```
- 参数说明
第一个参数是 name作为唯一标识用于增删改查并且 `name` 支持 `.` 用于分割层级。
- 示例
```tsx | pure
mySchemaSetting.add('b', {
type: 'item',
title: 'item b',
})
mySchemaSetting.add('a.a2', {
type: 'item',
title: 'item a2',
})
```
### schemaSettings.get()
- 类型
```tsx | pure
class SchemaSettings {
get(nestedName: string): SchemaSettingsItemType| undefined
}
```
- 示例
```tsx | pure
const itemA = mySchemaSetting.get('a')
const itemA1 = mySchemaSetting.add('a.a1')
```
### schemaSettings.remove()
- 类型
```tsx | pure
class SchemaSettings {
remove(nestedName: string): void
}
```
- 示例
```tsx | pure
mySchemaSetting.remove('a.a1')
mySchemaSetting.remove('a')
```
## Hooks
### useSchemaSettingsRender()
用于渲染 SchemaInitializer。
- 类型
```tsx | pure
function useSchemaSettingsRender(name: string, options?: SchemaSettingsOptions): {
exists: boolean;
render: (options?: SchemaSettingsRenderOptions) => React.ReactElement;
}
```
- 示例
```tsx | pure
const Demo = () => {
const filedSchema = useFieldSchema();
const { render, exists } = useSchemaSettingsRender(fieldSchema['x-settings'], fieldSchema['x-settings-props'])
return <div>
<div>{ render() }</div>
<div>可以进行参数的二次覆盖:{ render({ style: { color: 'red' } }) }</div>
</div>
}
```
<code src="./demos/schema-settings-render.tsx"></code>
### useSchemaSettings()
获取 schemaSetting 上下文数据。
上下文数据包含了 `schemaSetting` 实例化时的 `options` 以及调用 `useSchemaSettingsRender()` 时传入的 `options`
- 类型
```tsx | pure
interface UseSchemaSettingsResult<T> extends SchemaSettingsOptions<T> {
dn?: Designable;
field?: GeneralField;
fieldSchema?: Schema;
}
function useSchemaSettings(): UseSchemaSettingsResult;
```
- 示例
```tsx | pure
const { dn } = useSchemaSettings();
```
### useSchemaSettingsItem()
用于获取一个 item 的数据。
- 类型
```tsx | pure
export type SchemaSettingsItemType<T = {}> = {
name: string;
type?: string;
sort?: number;
Component?: string | ComponentType<T>;
componentProps?: T;
useComponentProps?: () => T;
useVisible?: () => boolean;
children?: SchemaSettingsItemType[];
[index]: any;
};
function useSchemaSettingsItem(): SchemaSettingsItemType;
```
- 示例
```tsx | pure
const { name } = useSchemaSettingsItem();
```
## 内置组件和类型
| type | Component | 效果 |
| ----------- | ------------------------------ | ----------------------------------------- |
| item | SchemaSettingsItem | 文本 |
| itemGroup | SchemaSettingsItemGroup | 分组,同 Menu 组件的 `type: 'itemGroup'` |
| subMenu | SchemaSettingsSubMenu | 子菜单,同 Menu 组件的子菜单 |
| divider | SchemaSettingsDivider | 分割线,同 Menu 组件的 `type: 'divider'` |
| remove | SchemaSettingsRemove | 删除,用于删除一个区块 |
| select | SchemaSettingsSelectItem | 下拉选择 |
| cascader | SchemaSettingsCascaderItem | 级联选择 |
| switch | SchemaSettingsSwitchItem | 开关 |
| popup | SchemaSettingsPopupItem | 弹出层 |
| actionModal | SchemaSettingsActionModalItem | 操作弹窗 |
| modal | SchemaSettingsModalItem | 弹窗 |
### SchemaSettingsItem
文本,对应的 `type``item`
```tsx | pure
interface SchemaSettingsItemProps extends Omit<MenuItemProps, 'title'> {
title: string;
}
```
核心参数为 `title``onClick`,可以在 `onClick` 中修改 schema。
<code src="./demos/schema-settings-components-item.tsx"></code>
### SchemaSettingsItemGroup
分组,对应的 `type``itemGroup`
核心参数是 `title`
<code src="./demos/schema-settings-components-group.tsx"></code>
### SchemaSettingsSubMenu
子菜单,对应的 `type``subMenu`
核心参数是 `title`
<code src="./demos/schema-settings-components-sub-menu.tsx"></code>
### SchemaSettingsDivider
分割线,对应的 `type``divider`
<code src="./demos/schema-settings-components-divider.tsx"></code>
### SchemaSettingsRemove
删除,对应的 `type``remove`
```tsx | pure
interface SchemaSettingsRemoveProps {
confirm?: ModalFuncProps;
removeParentsIfNoChildren?: boolean;
breakRemoveOn?: ISchema | ((s: ISchema) => boolean);
}
```
- `confirm`:删除前的确认弹窗
- `removeParentsIfNoChildren`:如果删除后没有子节点了,是否删除父节点
- `breakRemoveOn`:如果删除的节点满足条件,是否中断删除
<code src="./demos/schema-settings-components-remove.tsx"></code>
### SchemaSettingsSelectItem
选择器,对应的 `type``select`
<code src="./demos/schema-settings-components-select.tsx"></code>
### SchemaSettingsCascaderItem
级联选择,对应的 `type``cascader`
### SchemaSettingsSwitchItem
开关,对应的 `type``switch`
<code src="./demos/schema-settings-components-switch.tsx"></code>
### SchemaSettingsModalItem
弹窗,对应的 `type``modal`
```tsx | pure
export interface SchemaSettingsModalItemProps {
title: string;
onSubmit: (values: any) => void;
initialValues?: any;
schema?: ISchema | (() => ISchema);
modalTip?: string;
components?: any;
hidden?: boolean;
scope?: any;
effects?: any;
width?: string | number;
children?: ReactNode;
asyncGetInitialValues?: () => Promise<any>;
eventKey?: string;
hide?: boolean;
}
```
我们可以通过 `schema` 参数来定义弹窗的表单,然后在 `onSubmit` 中获取表单的值,然后修改当前 schema 节点。
<code src="./demos/schema-settings-components-modal.tsx"></code>
### SchemaSettingsActionModalItem
操作弹窗,对应的 `type``actionModal`
其和 `modal` 的区别是,`SchemaSettingsModalItem` 弹窗会丢失上下文,而 `SchemaSettingsActionModalItem` 会保留上下文,简单场景下可以使用 `SchemaSettingsModalItem`,复杂场景下可以使用 `SchemaSettingsActionModalItem`
```tsx | pure
export interface SchemaSettingsActionModalItemProps extends SchemaSettingsModalItemProps, Omit<SchemaSettingsItemProps, 'onSubmit' | 'onClick'> {
uid?: string;
initialSchema?: ISchema;
schema?: ISchema;
beforeOpen?: () => void;
maskClosable?: boolean;
}
```
<code src="./demos/schema-settings-components-action-modal.tsx"></code>

View File

@ -0,0 +1,427 @@
# SchemaToolbar
![SchemaToolbar](../static/designer.png)
## 组件
### SchemaToolbar 组件
此为默认的 Toolbar 组件,其内部会自动根据 Schema 的 `x-settings`、`x-initializer` 渲染 `SchemaSettings`、`SchemaInitializer` 和 `Drag` 组件。
Toolbar 具体的渲染规则为:当有 `x-toolbar` 会渲染对应的组件;当无 `x-toolbar` 但是有 `x-settings`、`x-initializer` 会渲染默认的 `SchemaToolbar` 组件。
- 类型
```tsx | pure
interface SchemaToolbarProps {
title?: string;
draggable?: boolean;
initializer?: string | false;
settings?: string | false;
/**
* @default true
*/
showBorder?: boolean;
showBackground?: boolean;
}
```
- 详细解释
- `title`:左上角的标题
- `draggable`:是否可以拖拽,默认为 `true`
- `initializer``SchemaInitializer` 的默认值,当 schema 里没有 `x-initializer` 时,会使用此值;当为 `false` 时,不会渲染 `SchemaInitializer`
- `settings``SchemaSettings` 的默认值,当 schema 里没有 `x-settings` 时,会使用此值;当为 `false` 时,不会渲染 `SchemaSettings`
- `showBorder`:边框是否变为橘色
- `showBackground`:背景是否变为橘色
- 示例
未指定 `x-toolbar` 时会渲染默认的 `SchemaToolbar` 这个组件。
```tsx
import { useFieldSchema } from '@formily/react';
import {
Application,
CardItem,
Grid,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
SchemaSettings,
useSchemaInitializer,
useSchemaInitializerItem,
} from '@nocobase/client';
import React from 'react';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
},
},
],
});
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
wrap: Grid.wrap,
items: [
{
name: 'demo1',
title: 'Demo1',
Component: () => {
const itemConfig = useSchemaInitializerItem();
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick}></SchemaInitializerItem>;
},
},
],
});
const Hello = () => {
const schema = useFieldSchema();
return <h1>Hello, world! {schema.name}</h1>;
};
const hello1 = Grid.wrap({
type: 'void',
// 仅指定了 `x-settings` 但是没有 `x-toolbar`,会使用默认的 `SchemaToolbar` 组件
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'MyInitializer',
properties: {
hello1,
},
}}
/>
</div>
);
};
class PluginHello extends Plugin {
async load() {
this.app.addComponents({ Grid, CardItem, Hello });
this.app.schemaSettingsManager.add(mySettings);
this.app.schemaInitializerManager.add(myInitializer);
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
const app = new Application({
router: {
type: 'memory',
},
designable: true,
plugins: [PluginHello],
});
export default app.getRootComponent();
```
自定义 Toolbar 组件。
```tsx
import { useFieldSchema } from '@formily/react';
import {
Application,
CardItem,
Grid,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
SchemaSettings,
SchemaToolbar,
useSchemaInitializer,
useSchemaInitializerItem,
} from '@nocobase/client';
import React from 'react';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
},
},
],
});
const MyToolbar = () => {
return <SchemaToolbar showBackground title='Test' />
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
wrap: Grid.wrap,
items: [
{
name: 'demo1',
title: 'Demo1',
Component: () => {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
// 使用自定义的 Toolbar 组件
'x-toolbar': 'MyToolbar',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick}></SchemaInitializerItem>;
},
},
],
});
const Hello = () => {
const schema = useFieldSchema();
return <h1>Hello, world! {schema.name}</h1>;
};
const hello1 = Grid.wrap({
type: 'void',
// 使用自定义的 Toolbar 组件
'x-toolbar': 'MyToolbar',
'x-settings': 'mySettings',
'x-decorator': 'CardItem',
'x-component': 'Hello',
});
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'MyInitializer',
properties: {
hello1,
},
}}
/>
</div>
);
};
class PluginHello extends Plugin {
async load() {
this.app.addComponents({ Grid, CardItem, Hello, MyToolbar });
this.app.schemaSettingsManager.add(mySettings);
this.app.schemaInitializerManager.add(myInitializer);
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
const app = new Application({
router: {
type: 'memory',
},
designable: true,
plugins: [PluginHello],
});
export default app.getRootComponent();
```
## Hooks
### useSchemaToolbarRender()
用于渲染 `SchemaToolbar`
- 类型
```tsx | pure
const useSchemaToolbarRender: (fieldSchema: ISchema) => {
render(props?: SchemaToolbarProps): React.JSX.Element;
exists: boolean;
}
```
- 详细解释
前面示例中 `'x-decorator': 'CardItem'` 中组件 `CardItem` 里面就调用了 `useSchemaToolbarRender()` 进行渲染。内置的组件还有:`BlockItem`、`CardItem`、`Action`、`FormItem`。
`render()` 支持二次覆盖组件属性。
- 示例
```tsx | pure
const MyDecorator = () => {
const filedSchema = useFieldSchema();
const { render } = useSchemaToolbarRender(filedSchema); // 从 Schema 中读取 Toolbar 组件
return <Card>{ render() }</Card>
}
```
```tsx
import { Card } from 'antd';
import { useFieldSchema } from '@formily/react';
import {
Application,
CardItem,
Grid,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
SchemaSettings,
SchemaToolbar,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaToolbarRender
} from '@nocobase/client';
import React from 'react';
const mySettings = new SchemaSettings({
name: 'mySettings',
items: [
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
},
},
],
});
const MyToolbar = (props) => {
return <SchemaToolbar showBackground title='Test' {...props} />
}
// 自定义包装器
const MyDecorator = ({children}) => {
const filedSchema = useFieldSchema();
// 使用 `useSchemaToolbarRender()` 获取并渲染内容
const { render } = useSchemaToolbarRender(filedSchema);
return <Card style={{ marginBottom: 10 }}>{ render({ draggable: false }) }{children}</Card>
}
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Button Text',
wrap: Grid.wrap,
items: [
{
name: 'demo1',
title: 'Demo1',
Component: () => {
const itemConfig = useSchemaInitializerItem();
// 调用插入功能
const { insert } = useSchemaInitializer();
const handleClick = () => {
insert({
type: 'void',
// 使用自定义的 Toolbar 组件
'x-toolbar': 'MyToolbar',
'x-settings': 'mySettings',
'x-decorator': 'MyDecorator',
'x-component': 'Hello',
});
};
return <SchemaInitializerItem title={itemConfig.title} onClick={handleClick}></SchemaInitializerItem>;
},
},
],
});
const Hello = () => {
const schema = useFieldSchema();
return <h1>Hello, world! {schema.name}</h1>;
};
const hello1 = Grid.wrap({
type: 'void',
// 使用自定义的 Toolbar 组件
'x-toolbar': 'MyToolbar',
'x-settings': 'mySettings',
'x-decorator': 'MyDecorator',
'x-component': 'Hello',
});
const HelloPage = () => {
return (
<div>
<SchemaComponent
schema={{
name: 'root',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'MyInitializer',
properties: {
hello1,
},
}}
/>
</div>
);
};
class PluginHello extends Plugin {
async load() {
this.app.addComponents({ Grid, CardItem, Hello, MyToolbar, MyDecorator });
this.app.schemaSettingsManager.add(mySettings);
this.app.schemaInitializerManager.add(myInitializer);
this.router.add('hello', {
path: '/',
Component: HelloPage,
});
}
}
const app = new Application({
router: {
type: 'memory',
},
designable: true,
plugins: [PluginHello],
});
export default app.getRootComponent();
```

View File

@ -0,0 +1,7 @@
# Utils
## Function
### tval
用于输出多语言的字符串。

View File

@ -0,0 +1,3 @@
# Index
<Navigate replace to="/core/application/application"></Navigate>

View File

@ -0,0 +1,82 @@
# Add new
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-action": "create",
"x-acl-action": "create",
"title": "{{t('Add new')}}",
"x-designer": "Action.Designer",
"x-component": "Action",
"x-decorator": "ACLActionProvider",
"x-component-props": {
"openMode": "drawer",
"type": "primary",
"component": "CreateRecordAction",
"icon": "PlusOutlined"
},
"x-align": "right",
"x-acl-action-props": {
"skipScopeCheck": true
},
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"Add record\") }}",
"x-component": "Action.Container",
"x-component-props": {
"className": "nb-action-popup"
},
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializersForCreateFormBlock",
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Add new\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "CreateFormBlockInitializers",
"x-uid": "psh2rj6pkab",
"x-async": false,
"x-index": 1
}
},
"x-uid": "3i7grk0aytf",
"x-async": false,
"x-index": 1
}
},
"x-uid": "75s24n1nlkh",
"x-async": false,
"x-index": 1
}
},
"x-uid": "wg1f4xyx7vp",
"x-async": false,
"x-index": 1
}
},
"x-uid": "2hwye7mt2th",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1 @@
# Add record任意表

View File

@ -0,0 +1 @@
# 批量编辑

View File

@ -0,0 +1 @@
# 批量更新

View File

@ -0,0 +1,88 @@
# 自定义请求
<Alert type="warning">
x-component 不统一x-action 也不统一,有三种风格的 schema
</Alert>
## UI Schema
### 表单的自定义请求
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"title": "{{ t(\"Custom request\") }}",
"x-component": "CustomRequestAction",
"x-action": "customize:form:request",
"x-designer": "CustomRequestAction.Designer",
"x-decorator": "CustomRequestAction.Decorator",
"x-action-settings": {
"onSuccess": {
"manualClose": false,
"redirecting": false,
"successMessage": "{{t(\"Request success\")}}"
}
},
"type": "void",
"x-uid": "d1rfalvivfo",
"x-async": false,
"x-index": 2
}
```
### 表格列操作的自定义请求
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"title": "{{ t(\"Custom request\") }}",
"x-component": "Action.Link",
"x-action": "customize:table:request",
"x-designer": "Action.Designer",
"x-action-settings": {
"requestSettings": {},
"onSuccess": {
"manualClose": false,
"redirecting": false,
"successMessage": "{{t(\"Request success\")}}"
}
},
"x-component-props": {
"useProps": "{{ useCustomizeRequestActionProps }}"
},
"x-designer-props": {
"linkageAction": true
},
"type": "void",
"x-uid": "edn0y4fq7xb",
"x-async": false,
"x-index": 1
}
```
### 详情的自定义请求
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"title": "{{ t(\"Custom request\") }}",
"x-component": "CustomRequestAction",
"x-action": "customize:form:request",
"x-designer": "CustomRequestAction.Designer",
"x-decorator": "CustomRequestAction.Decorator",
"x-action-settings": {
"onSuccess": {
"manualClose": false,
"redirecting": false,
"successMessage": "{{t(\"Request success\")}}"
}
},
"type": "void",
"x-uid": "glc5zkr0ke3",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1 @@
# 删除

View File

@ -0,0 +1 @@
# 复制

View File

@ -0,0 +1 @@
# 编辑

View File

@ -0,0 +1 @@
# 导出

View File

@ -0,0 +1 @@
# 筛选

View File

@ -0,0 +1 @@
# 导入

View File

@ -0,0 +1,51 @@
# 概览
## UI Schema 协议
- `x-action`
- `x-align`
- `x-acl-action`
- `x-acl-action-props`
- `x-action-settings`
## 常规操作
```json
{
"type": "void",
"title": "{{ t(\"Submit\") }}",
"x-component": "Action",
"x-component-props": {
"type": "primary",
"htmlType": "submit",
"useProps": "{{ useCreateActionProps }}"
},
}
```
## 弹窗操作
```json
{
"type": "void",
"title": "{{t('Open drawer')}}",
"x-component": "Action",
"x-component-props": {
"openMode": "drawer",
"type": "primary",
"icon": "PlusOutlined"
},
"x-align": "right",
"properties": {
"drawer": {
"type": "void",
"x-component": "Action.Container",
"x-component-props": {},
"properties": {
},
}
}
}
```
更多示例查看 [Action](/apis/action)

View File

@ -0,0 +1 @@
# 打印

View File

@ -0,0 +1 @@
# 刷新

View File

@ -0,0 +1 @@
# 保存数据

View File

@ -0,0 +1 @@
# 提交至工作流

View File

@ -0,0 +1,28 @@
# 提交
## UI Schema
### 新建数据的提交
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"title": "{{ t(\"Submit\") }}",
"x-action": "submit",
"x-component": "Action",
"x-designer": "Action.Designer",
"x-component-props": {
"type": "primary",
"htmlType": "submit",
"useProps": "{{ useCreateActionProps }}"
},
"x-action-settings": {
"triggerWorkflows": []
},
"type": "void",
"x-uid": "aaxbm1i5xxd",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1 @@
# 更新数据

View File

@ -0,0 +1 @@
# 查看

View File

@ -0,0 +1,282 @@
# Calendar
## x-initializer
- CalendarActionInitializers
- TabPaneInitializers
- RecordBlockInitializers
## x-designer
- CalendarV2.Designer
## x-settings
- CalendarSettings
## UI Schema
### 日历区块
```json
{
"type": "void",
"version": "2.0",
"x-designer": "CalendarV2.Designer",
"x-component": "CardItem",
"x-decorator": "CalendarBlockProvider",
"x-acl-action": "users:list",
"x-decorator-props": {
"action": "list",
"params": {
"paginate": false
},
"resource": "users",
"collection": "users",
"fieldNames": {
"id": "id",
"end": ["f_nxqfedh5fd5"],
"start": ["f_nxqfedh5fd5"],
"title": "nickname"
}
},
"_isJSONSchemaObject": true,
"properties": {
"puytl7p5x8o": {
"type": "void",
"version": "2.0",
"x-component": "CalendarV2",
"x-component-props": {
"useProps": "{{ useCalendarBlockProps }}"
},
"_isJSONSchemaObject": true,
"properties": {
"toolBar": {
"type": "void",
"version": "2.0",
"x-component": "CalendarV2.ActionBar",
"x-initializer": "CalendarActionInitializers",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"_isJSONSchemaObject": true,
"x-uid": "494h1j6bxyg",
"x-async": false,
"x-index": 1
},
"event": {
"type": "void",
"version": "2.0",
"x-component": "CalendarV2.Event",
"_isJSONSchemaObject": true,
"properties": {
"drawer": {
"type": "void",
"title": "{{ t(\"View record\") }}",
"version": "2.0",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup"
},
"_isJSONSchemaObject": true,
"properties": {
"tabs": {
"type": "void",
"version": "2.0",
"x-component": "Tabs",
"x-initializer": "TabPaneInitializers",
"x-component-props": {},
"_isJSONSchemaObject": true,
"x-initializer-props": {
"gridInitializer": "RecordBlockInitializers"
},
"properties": {
"tab1": {
"type": "void",
"title": "{{t(\"Details\")}}",
"version": "2.0",
"x-designer": "Tabs.Designer",
"x-component": "Tabs.TabPane",
"x-component-props": {},
"_isJSONSchemaObject": true,
"properties": {
"grid": {
"type": "void",
"version": "2.0",
"x-component": "Grid",
"x-initializer": "RecordBlockInitializers",
"_isJSONSchemaObject": true,
"x-initializer-props": {
"actionInitializers": "CalendarFormActionInitializers"
},
"x-uid": "yty9wg20qq0",
"x-async": false,
"x-index": 1
}
},
"x-uid": "hcsgenoc3hb",
"x-async": false,
"x-index": 1
}
},
"x-uid": "c5cpptwxw7s",
"x-async": false,
"x-index": 1
}
},
"x-uid": "c5iarcnpefc",
"x-async": false,
"x-index": 1
}
},
"x-uid": "080x8qlpn6s",
"x-async": false,
"x-index": 2
}
},
"x-uid": "pcche4s596p",
"x-async": false,
"x-index": 1
}
},
"x-uid": "gk2jk3gpo9f",
"x-async": false,
"x-index": 1
}
```
### 关系的日历区块
```json
{
"x-uid": "8q0hsbdr9fc",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:list",
"x-decorator": "CalendarBlockProvider",
"x-decorator-props": {
"collection": "b",
"resource": "a.o2m",
"action": "list",
"fieldNames": {
"id": "id",
"start": "createdAt",
"title": "f_ex4581dfc0t"
},
"params": {
"paginate": false
},
"association": "a.o2m"
},
"x-designer": "CalendarV2.Designer",
"x-component": "CardItem",
"x-component-props": {
"title": "关系的日历区块"
},
"properties": {
"v3za6qejuz7": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "CalendarV2",
"x-component-props": {
"useProps": "{{ useCalendarBlockProps }}"
},
"properties": {
"toolBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "CalendarV2.ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-initializer": "CalendarActionInitializers",
"x-uid": "u9ntffsdsrh",
"x-async": false,
"x-index": 1
},
"event": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "CalendarV2.Event",
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup"
},
"title": "{{ t(\"View record\") }}",
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
"x-initializer-props": {
"gridInitializer": "RecordBlockInitializers"
},
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Details\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer-props": {
"actionInitializers": "CalendarFormActionInitializers"
},
"x-initializer": "RecordBlockInitializers",
"x-uid": "2swhiw4lee5",
"x-async": false,
"x-index": 1
}
},
"x-uid": "z6odjfbwnht",
"x-async": false,
"x-index": 1
}
},
"x-uid": "mxr7ikhfc9y",
"x-async": false,
"x-index": 1
}
},
"x-uid": "mfgucnj7ev8",
"x-async": false,
"x-index": 1
}
},
"x-uid": "05l5a2h42pn",
"x-async": false,
"x-index": 2
}
},
"x-uid": "v2jazzvqa85",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,143 @@
# Charts
## UI Schema
### 图表区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "CardItem",
"x-component-props": {
"name": "charts"
},
"x-designer": "ChartV2BlockDesigner",
"properties": {
"u8841bjm65y": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-decorator": "ChartV2Block",
"x-initializer": "ChartInitializers",
"x-uid": "j4hzcpzcc3g",
"x-async": false,
"x-index": 1
}
},
"x-uid": "ldc2aokaop2",
"x-async": false,
"x-index": 1
}
```
### 内嵌区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "CardItem",
"x-component-props": {
"name": "charts"
},
"x-designer": "ChartV2BlockDesigner",
"properties": {
"g3jq3vkx9fv": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-decorator": "ChartV2Block",
"x-initializer": "ChartInitializers",
"properties": {
"ue7lfigjgm1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid.Row",
"properties": {
"43ztg8gu03v": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid.Col",
"properties": {
"n47fsb4i2lm": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "ChartRendererProvider",
"x-decorator-props": {
"query": {
"measures": [
{
"field": ["id"]
}
],
"dimensions": [],
"filter": {
"$and": []
},
"orders": [],
"cache": {}
},
"config": {
"chartType": "Built-in.line",
"general": {
"xField": "id",
"yField": "id",
"smooth": false,
"isStack": false
}
},
"collection": "a",
"mode": "builder"
},
"x-acl-action": "a:list",
"x-designer": "ChartRenderer.Designer",
"x-component": "CardItem",
"x-component-props": {
"size": "small"
},
"x-initializer": "ChartInitializers",
"properties": {
"y25sgh5pukl": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "ChartRenderer",
"x-component-props": {},
"x-uid": "ma0s07k753t",
"x-async": false,
"x-index": 1
}
},
"x-uid": "k6naqcx59ow",
"x-async": false,
"x-index": 1
}
},
"x-uid": "o8ktlccovml",
"x-async": false,
"x-index": 1
}
},
"x-uid": "mvn9ai9rhx8",
"x-async": false,
"x-index": 1
}
},
"x-uid": "mcbyby93j6n",
"x-async": false,
"x-index": 1
}
},
"x-uid": "sn0lczwtkgm",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,169 @@
# Details
## UI Schema
### 详情区块(带分页)
```json
{
"type": "void",
"version": "2.0",
"x-designer": "DetailsDesigner",
"x-component": "CardItem",
"x-decorator": "DetailsBlockProvider",
"x-acl-action": "users:view",
"x-decorator-props": {
"action": "list",
"params": {
"pageSize": 1
},
"rowKey": "id",
"resource": "users",
"collection": "users",
"readPretty": true
},
"_isJSONSchemaObject": true,
"properties": {
"mjad1dcywip": {
"type": "void",
"version": "2.0",
"x-component": "Details",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useDetailsBlockProps }}"
},
"_isJSONSchemaObject": true,
"properties": {
"8avswx6qgyp": {
"type": "void",
"version": "2.0",
"x-component": "ActionBar",
"x-initializer": "DetailsActionInitializers",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"_isJSONSchemaObject": true,
"x-uid": "01w17yleng8",
"x-async": false,
"x-index": 1
},
"grid": {
"type": "void",
"version": "2.0",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"_isJSONSchemaObject": true,
"x-uid": "bgu0ix4lfmc",
"x-async": false,
"x-index": 2
},
"pagination": {
"type": "void",
"version": "2.0",
"x-component": "Pagination",
"x-component-props": {
"useProps": "{{ useDetailsPaginationProps }}"
},
"_isJSONSchemaObject": true,
"x-uid": "6riuxghrz30",
"x-async": false,
"x-index": 3
}
},
"x-uid": "3apkfu8ngrp",
"x-async": false,
"x-index": 1
}
},
"x-uid": "yeodhismy2w",
"x-async": false,
"x-index": 1
}
```
### 对多关系区块
```json
{
"x-uid": "2gm2iw937q5",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:view",
"x-decorator": "DetailsBlockProvider",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m",
"readPretty": true,
"action": "list",
"params": {
"pageSize": 1
},
"rowKey": "id"
},
"x-designer": "DetailsDesigner",
"x-component": "CardItem",
"x-component-props": {
"title": "关系区块"
},
"properties": {
"paj8b2noc1g": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Details",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useDetailsBlockProps }}"
},
"properties": {
"3jisjip7503": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "DetailsActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-uid": "cbkfxafywtx",
"x-async": false,
"x-index": 1
},
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-uid": "d1ls1w6gpzm",
"x-async": false,
"x-index": 2
},
"pagination": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Pagination",
"x-component-props": {
"useProps": "{{ useDetailsPaginationProps }}"
},
"x-uid": "u0rzv8370za",
"x-async": false,
"x-index": 3
}
},
"x-uid": "1hfu8jbzzqw",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,286 @@
# Form (Read pretty)
## UI Schema
### 单数据详情
```json
{
"x-uid": "dt4q817fw81",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a:get",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a",
"collection": "a",
"readPretty": true,
"action": "get",
"useParams": "{{ useParamsFromRecord }}",
"useSourceId": "{{ useSourceIdFromParentRecord }}"
},
"x-designer": "FormV2.ReadPrettyDesigner",
"x-component": "CardItem",
"x-component-props": {
"title": "Form read pretty"
},
"properties": {
"30qrxij609o": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ReadPrettyFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-uid": "fznn1ygt50o",
"x-async": false,
"x-index": 1
},
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-uid": "t3o4t8fi7l7",
"x-async": false,
"x-index": 2
}
},
"x-uid": "6dp2qqoc4s0",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```
### 关系数据详情
```json
{
"x-uid": "jf470wrzdaf",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2oBelongsTo:get",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a.o2oBelongsTo",
"collection": "b",
"association": "a.o2oBelongsTo",
"readPretty": true,
"action": "get",
"useParams": "{{ useParamsFromRecord }}",
"useSourceId": "{{ useSourceIdFromParentRecord }}"
},
"x-designer": "FormV2.ReadPrettyDesigner",
"x-component": "CardItem",
"x-component-props": {
"title": "对一关系详情"
},
"properties": {
"uauk2wbj3iq": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ReadPrettyFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-uid": "r3pcykksp8l",
"x-async": false,
"x-index": 1
},
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-uid": "vs000etludp",
"x-async": false,
"x-index": 2
}
},
"x-uid": "rt228uhudcf",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```
### 表格详情
```json
{
"x-uid": "lxcjjkkor1m",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:get",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m",
"readPretty": true,
"action": "get",
"useParams": "{{ useParamsFromRecord }}",
"useSourceId": "{{ useSourceIdFromParentRecord }}"
},
"x-designer": "FormV2.ReadPrettyDesigner",
"x-component": "CardItem",
"x-component-props": {
"title": "表格详情"
},
"properties": {
"7pe17tcgtye": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ReadPrettyFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-uid": "wek8nc795ja",
"x-async": false,
"x-index": 1
},
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-uid": "5y5u8qm6yvz",
"x-async": false,
"x-index": 2
}
},
"x-uid": "5bpcm982n51",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```
### 查看关系数据
```json
{
"x-uid": "5ymsrq1fv37",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:get",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m",
"readPretty": true,
"action": "get",
"useParams": "{{ useParamsFromRecord }}",
"useSourceId": "{{ useSourceIdFromParentRecord }}"
},
"x-designer": "FormV2.ReadPrettyDesigner",
"x-component": "CardItem",
"x-component-props": {
"title": "查看关系数据"
},
"properties": {
"vehcfr0fsh2": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ReadPrettyFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-uid": "zpuz629u2dg",
"x-async": false,
"x-index": 1
},
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-uid": "b8u8e2k5inf",
"x-async": false,
"x-index": 2
}
},
"x-uid": "2rz0u3j14px",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,418 @@
# Form
## UI Schema
### 添加表单
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action-props": {
"skipScopeCheck": true
},
"x-acl-action": "a:create",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a",
"collection": "a"
},
"x-designer": "FormV2.Designer",
"x-component": "CardItem",
"x-component-props": {},
"properties": {
"23pj9m7ot5a": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "FormItemInitializers",
"x-uid": "0ctuwnatyzd",
"x-async": false,
"x-index": 1
},
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "CreateFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24
}
},
"properties": {
"j7ta88et3mv": {
"_isJSONSchemaObject": true,
"version": "2.0",
"title": "{{ t(\"Submit\") }}",
"x-action": "submit",
"x-component": "Action",
"x-designer": "Action.Designer",
"x-component-props": {
"type": "primary",
"htmlType": "submit",
"useProps": "{{ useCreateActionProps }}"
},
"x-action-settings": {
"triggerWorkflows": []
},
"type": "void",
"x-uid": "ua858zswy37",
"x-async": false,
"x-index": 1
}
},
"x-uid": "o0riryr0ob0",
"x-async": false,
"x-index": 2
}
},
"x-uid": "1ng4hies4yu",
"x-async": false,
"x-index": 1
}
},
"x-uid": "ebiyvziafda",
"x-async": false,
"x-index": 1
}
```
### 编辑表单
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action-props": {
"skipScopeCheck": false
},
"x-acl-action": "a:update",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"useSourceId": "{{ useSourceIdFromParentRecord }}",
"useParams": "{{ useParamsFromRecord }}",
"action": "get",
"resource": "a",
"collection": "a"
},
"x-designer": "FormV2.Designer",
"x-component": "CardItem",
"x-component-props": {},
"properties": {
"w533dipin7p": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "FormItemInitializers",
"x-uid": "otncpmalhjr",
"x-async": false,
"x-index": 1
},
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "UpdateFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24
}
},
"x-uid": "qjjmm1qkapx",
"x-async": false,
"x-index": 2
}
},
"x-uid": "n12zo6qd72g",
"x-async": false,
"x-index": 1
}
},
"x-uid": "zafyvmng9m5",
"x-async": false,
"x-index": 1
}
```
### 关系新增表单
```json
{
"x-uid": "il5q341k4vg",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action-props": {
"skipScopeCheck": true
},
"x-acl-action": "a.o2m:create",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"useSourceId": "{{ useSourceIdFromParentRecord }}",
"useParams": "{{ useParamsFromRecord }}",
"action": null,
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m"
},
"x-designer": "FormV2.Designer",
"x-component": "CardItem",
"x-component-props": {
"title": "关系新增表单"
},
"properties": {
"3357yvwe4je": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "FormItemInitializers",
"x-uid": "88xhnoml321",
"x-async": false,
"x-index": 1
},
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "CreateFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24
}
},
"x-uid": "dhs8o8vpl1h",
"x-async": false,
"x-index": 2
}
},
"x-uid": "h6fhh9ox9ay",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```
### 关系新增(关系表格里的 Add new
useSourceIdFromParentRecord 和 useParamsFromRecord 参数缺失
```json
{
"x-uid": "6tknohm4ubr",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action-props": {
"skipScopeCheck": true
},
"x-acl-action": "a.o2m:create",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m"
},
"x-designer": "FormV2.Designer",
"x-component": "CardItem",
"x-component-props": {
"title": "关系新增表单-Table"
},
"properties": {
"o96izt6ez7m": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "FormItemInitializers",
"properties": {
"pk9l1i26yq6": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid.Row",
"properties": {
"32wz2lr9cry": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid.Col",
"properties": {
"id": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "string",
"x-designer": "FormItem.Designer",
"x-component": "CollectionField",
"x-decorator": "FormItem",
"x-collection-field": "b.id",
"x-component-props": {},
"x-read-pretty": true,
"x-uid": "jb09nj4kbfm",
"x-async": false,
"x-index": 1
}
},
"x-uid": "rtx3b0uom75",
"x-async": false,
"x-index": 1
}
},
"x-uid": "5gvd7ks6m0z",
"x-async": false,
"x-index": 1
}
},
"x-uid": "fcw1y2xk6km",
"x-async": false,
"x-index": 1
},
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "CreateFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24
}
},
"x-uid": "msgopb1meoq",
"x-async": false,
"x-index": 2
}
},
"x-uid": "g0mg6fhho4x",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```
### 关系编辑
```json
{
"x-uid": "kzg1tz339ro",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action-props": {
"skipScopeCheck": false
},
"x-acl-action": "a.o2m:update",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"useSourceId": "{{ useSourceIdFromParentRecord }}",
"useParams": "{{ useParamsFromRecord }}",
"action": "get",
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m"
},
"x-designer": "FormV2.Designer",
"x-component": "CardItem",
"x-component-props": {
"title": "关系编辑表单"
},
"properties": {
"3jqysycq8m0": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "FormV2",
"x-component-props": {
"useProps": "{{ useFormBlockProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "FormItemInitializers",
"x-uid": "sy3sjswogh4",
"x-async": false,
"x-index": 1
},
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "UpdateFormActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24
}
},
"x-uid": "xarcyjpypsy",
"x-async": false,
"x-index": 2
}
},
"x-uid": "lepez9684rm",
"x-async": false,
"x-index": 1
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,185 @@
# Gantt
## UI Schema
### 甘特图区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "users:list",
"x-decorator": "GanttBlockProvider",
"x-decorator-props": {
"collection": "users",
"resource": "users",
"action": "list",
"fieldNames": {
"id": "id",
"start": "createdAt",
"range": "day",
"title": "nickname",
"end": "f_46b1u4dp820"
},
"params": {
"paginate": false
}
},
"x-designer": "Gantt.Designer",
"x-component": "CardItem",
"properties": {
"rx21gtpcj3w": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Gantt",
"x-component-props": {
"useProps": "{{ useGanttBlockProps }}"
},
"properties": {
"toolBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24
}
},
"x-initializer": "GanttActionInitializers",
"x-uid": "m0uru68ugw9",
"x-async": false,
"x-index": 1
},
"table": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-decorator": "div",
"x-decorator-props": {
"style": {
"float": "left",
"maxWidth": "35%"
}
},
"x-initializer": "TableColumnInitializers",
"x-component": "TableV2",
"x-component-props": {
"rowKey": "id",
"rowSelection": {
"type": "checkbox"
},
"useProps": "{{ useTableBlockProps }}",
"pagination": false
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"Actions\") }}",
"x-action-column": "actions",
"x-decorator": "TableV2.Column.ActionBar",
"x-component": "TableV2.Column",
"x-designer": "TableV2.ActionColumnDesigner",
"x-initializer": "TableActionColumnInitializers",
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "DndContext",
"x-component": "Space",
"x-component-props": {
"split": "|"
},
"x-uid": "z2vnizu7bds",
"x-async": false,
"x-index": 1
}
},
"x-uid": "8gbabo6kdr6",
"x-async": false,
"x-index": 1
}
},
"x-uid": "5c8g2x6rvro",
"x-async": false,
"x-index": 2
},
"detail": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Gantt.Event",
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup"
},
"title": "{{ t(\"View record\") }}",
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Details\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "RecordBlockInitializers",
"x-uid": "jr5h3asz6gp",
"x-async": false,
"x-index": 1
}
},
"x-uid": "8uwfpgyok9x",
"x-async": false,
"x-index": 1
}
},
"x-uid": "bjm4sfwxmrx",
"x-async": false,
"x-index": 1
}
},
"x-uid": "7cmwpc0rrwj",
"x-async": false,
"x-index": 1
}
},
"x-uid": "nffb7qbs96o",
"x-async": false,
"x-index": 3
}
},
"x-uid": "hoi2j5lhlho",
"x-async": false,
"x-index": 1
}
},
"x-uid": "e0rdive4xpa",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,219 @@
# GridCard
## UI Schema
### 栅格卡片
```json
{
"type": "void",
"version": "2.0",
"x-designer": "GridCard.Designer",
"x-component": "BlockItem",
"x-decorator": "GridCard.Decorator",
"x-acl-action": "users:view",
"x-component-props": {
"useProps": "{{ useGridCardBlockItemProps }}"
},
"x-decorator-props": {
"action": "list",
"params": {
"pageSize": 12
},
"rowKey": "id",
"resource": "users",
"collection": "users",
"readPretty": true,
"runWhenParamsChanged": true
},
"_isJSONSchemaObject": true,
"properties": {
"actionBar": {
"type": "void",
"version": "2.0",
"x-component": "ActionBar",
"x-initializer": "GridCardActionInitializers",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"_isJSONSchemaObject": true,
"x-uid": "prx9wnlzqb1",
"x-async": false,
"x-index": 1
},
"list": {
"type": "array",
"version": "2.0",
"x-component": "GridCard",
"x-component-props": {
"useProps": "{{ useGridCardBlockProps }}"
},
"_isJSONSchemaObject": true,
"properties": {
"item": {
"type": "object",
"version": "2.0",
"x-component": "GridCard.Item",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useGridCardItemProps }}"
},
"_isJSONSchemaObject": true,
"properties": {
"grid": {
"type": "void",
"version": "2.0",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"_isJSONSchemaObject": true,
"x-initializer-props": {
"useProps": "{{ useGridCardItemInitializerProps }}"
},
"x-uid": "pq895sv6136",
"x-async": false,
"x-index": 1
},
"actionBar": {
"type": "void",
"version": "2.0",
"x-align": "left",
"x-component": "ActionBar",
"x-initializer": "GridCardItemActionInitializers",
"x-component-props": {
"layout": "one-column",
"useProps": "{{ useGridCardActionBarProps }}"
},
"_isJSONSchemaObject": true,
"x-uid": "w2c3qcp4nme",
"x-async": false,
"x-index": 2
}
},
"x-uid": "1ct8yn1jnzn",
"x-async": false,
"x-index": 1
}
},
"x-uid": "pvnkvtc6tte",
"x-async": false,
"x-index": 2
}
},
"x-uid": "phodsqwsifq",
"x-async": false,
"x-index": 1
}
```
### 关系数据的栅格卡片
```json
{
"x-uid": "yr79zvl98lc",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:view",
"x-decorator": "GridCard.Decorator",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m",
"readPretty": true,
"action": "list",
"params": {
"pageSize": 12
},
"runWhenParamsChanged": true,
"columnCount": {
"xs": 1,
"md": 12,
"lg": 12,
"xxl": 12
}
},
"x-component": "BlockItem",
"x-component-props": {
"useProps": "{{ useGridCardBlockItemProps }}"
},
"x-designer": "GridCard.Designer",
"properties": {
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "GridCardActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"x-uid": "u36l44beq6a",
"x-async": false,
"x-index": 1
},
"list": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-component": "GridCard",
"x-component-props": {
"useProps": "{{ useGridCardBlockProps }}"
},
"properties": {
"item": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "object",
"x-component": "GridCard.Item",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useGridCardItemProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-initializer-props": {
"useProps": "{{ useGridCardItemInitializerProps }}"
},
"x-uid": "aqxlonv3bpk",
"x-async": false,
"x-index": 1
},
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-align": "left",
"x-initializer": "GridCardItemActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"useProps": "{{ useGridCardActionBarProps }}",
"layout": "one-column"
},
"x-uid": "zxhqo81kdf5",
"x-async": false,
"x-index": 2
}
},
"x-uid": "ed56owlyz1p",
"x-async": false,
"x-index": 1
}
},
"x-uid": "nfpuvrkwkk7",
"x-async": false,
"x-index": 2
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,255 @@
# 数据区块概述
数据可能是某个表collection的数据也可能是某个关系association的数据
- collection如 a 表
- association如 a.b 关系,关系表为 b
collection 示例
```js
{
"collection": "a"
}
```
association 示例
```js
{
"collection": "b",
"association": "a.b"
}
```
数据的操作
```bash
<collection>:<action>
<collection>.<association>:<action>
```
实际的请求参数
```
<GET|POST> /api/<collection>:<action>
<GET|POST> /api/<collection>:<action>/<filterByTk>
<GET|POST> /api/<collection>/<sourceId>/<association>:<action>
<GET|POST> /api/<collection>/<sourceId>/<association>:<action>/<filterByTk>
```
- collection、association、action 是配置里存好的
- sourceId、filterByTk 由上下文提供
## RecordProvider
Record
```ts
class Record {
protected current = {};
protected parent?: Record;
public isNew = false;
constructor(options = {}) {
const { current, parent, isNew } = options;
this.current = current || {};
this.parent = parent;
this.isNew = isNew;
}
}
```
a:list**list 外层不套空 RecordPicker**
```jsx | pure
{list.map((item) => {
const record = new Record({ current: item });
return <RecordProvider record={record}></RecordProvider>
})}
```
a:get(view、edit)
```jsx | pure
const record = new Record({ current: item });
<RecordProvider record={item}></RecordProvider>
```
a:create
```jsx | pure
const record = new Record({ current, isNew: true });
<RecordProvider record={record}></RecordProvider>
```
a.b:list
```jsx | pure
<RecordProvider record={recordA}>
{list.map((item) => {
const recordB = new Record({
current: item,
});
return <RecordProvider record={recordB}></RecordProvider>
})}
</RecordProvider>
```
a.b:get
```jsx | pure
const recordA = new Record({
current: itemA,
});
const recordB = new Record({
current: itemB,
parent: recordA,
});
// 或者
recordB.setParent(recordA);
<RecordProvider record={recordA}>
<RecordProvider record={recordB}></RecordProvider>
</RecordProvider>
```
a.b:create
```jsx | pure
const recordA = new Record({
current: itemA,
});
const recordB = new Record({
isNew: true,
parent: recordA,
});
<RecordProvider record={recordA}>
<RecordProvider record={recordB}></RecordProvider>
</RecordProvider>
```
## 区块
以下重要组件说明
- `DataBlockProvider` 数据区块的总称
- `BlockProvider` 区块的各种参数设置
- `CollectionProvider` collection 信息
- `AssociationProvider` association 信息
- `UseRequestProvider` useRequest 的 result
没有当前记录的区块,如表格
```tsx | pure
<DataBlockProvider>
<ActionBar>
<Action type="filter" />
<Action type="create" />
<Action type="delete" />
</ActionBar>
<Table useProps />
<Pagination />
<DataBlockProvider/>
```
DataBlockProvider 内容
```tsx | pure
<BlockProvider>
<CollectionProvider>
<UseRequestProvider>
{props.children}
</UseRequestProvider>
</CollectionProvider>
<BlockProvider>
```
有当前记录的区块,如表单
```tsx | pure
<DataBlockProvider>
<Form useProps />
<DataBlockProvider/>
```
DataBlockProvider 内容
```tsx | pure
<BlockProvider>
<CollectionProvider>
<UseRequestProvider>
<RecordProvider record={recordA}>
{props.children}
</RecordProvider>
</UseRequestProvider>
</CollectionProvider>
</BlockProvider>
```
有当前父级记录的区块,如对多关系的表格区块,区块本身不套 RecordPicker
```tsx | pure
<RecordProvider record={recordA}>
<DataBlockProvider>
<ActionBar>
<Action type="filter" />
<Action type="create" />
<Action type="delete" />
</ActionBar>
<Table useProps />
<Pagination />
</DataBlockProvider>
<DataBlockProvider>
<ActionBar>
<Action type="filter" />
<Action type="create" />
<Action type="delete" />
</ActionBar>
<Table useProps />
<Pagination />
</DataBlockProvider>
</RecordProvider>
```
DataBlockProvider 内容
```tsx | pure
<BlockProvider>
<AssociationProvider>
<CollectionProvider>
<UseRequestProvider>
{props.children}
</UseRequestProvider>
</CollectionProvider>
</AssociationProvider>
</BlockProvider>
```
有父级记录也有当前记录的区块,如关系的表单
```tsx | pure
<RecordProvider record={recordA}>
<DataBlockProvider>
<Form useProps />
</DataBlockProvider>
<DataBlockProvider>
<Form useProps />
</DataBlockProvider>
</RecordProvider>
```
DataBlockProvider 内容
```tsx | pure
<BlockProvider>
<AssociationProvider>
<CollectionProvider>
<UseRequestProvider>
<RecordProvider record={recordA}>
{props.children}
</RecordProvider>
</UseRequestProvider>
</CollectionProvider>
</AssociationProvider>
</BlockProvider>
```

View File

@ -0,0 +1,160 @@
# Kanban
## UI Schema
### 看板区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "t_ehwqkw0ljw2:list",
"x-decorator": "KanbanBlockProvider",
"x-decorator-props": {
"collection": "t_ehwqkw0ljw2",
"resource": "t_ehwqkw0ljw2",
"action": "list",
"groupField": "f_5h4umwf59lc",
"params": {
"sort": ["f_5h4umwf59lc_sort"],
"paginate": false
}
},
"x-designer": "Kanban.Designer",
"x-component": "CardItem",
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "KanbanActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"x-uid": "4vh5t2iohwp",
"x-async": false,
"x-index": 1
},
"iwnc45ou960": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-component": "Kanban",
"x-component-props": {
"useProps": "{{ useKanbanBlockProps }}"
},
"properties": {
"card": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-read-pretty": true,
"x-label-disabled": true,
"x-decorator": "BlockItem",
"x-component": "Kanban.Card",
"x-component-props": {
"openMode": "drawer"
},
"x-designer": "Kanban.Card.Designer",
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-component-props": {
"dndContext": false
},
"x-uid": "3v010y09684",
"x-async": false,
"x-index": 1
}
},
"x-uid": "dk9a5cb9d95",
"x-async": false,
"x-index": 1
},
"cardViewer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"View\") }}",
"x-designer": "Action.Designer",
"x-component": "Kanban.CardViewer",
"x-action": "view",
"x-component-props": {
"openMode": "drawer"
},
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"View record\") }}",
"x-component": "Action.Container",
"x-component-props": {
"className": "nb-action-popup"
},
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Details\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "RecordBlockInitializers",
"x-uid": "1lhiyyydpye",
"x-async": false,
"x-index": 1
}
},
"x-uid": "gisuj0zldz1",
"x-async": false,
"x-index": 1
}
},
"x-uid": "ohly2w4gnwp",
"x-async": false,
"x-index": 1
}
},
"x-uid": "29hjrxcfczh",
"x-async": false,
"x-index": 1
}
},
"x-uid": "0k56h1q80xo",
"x-async": false,
"x-index": 2
}
},
"x-uid": "uny2d4n5ry4",
"x-async": false,
"x-index": 2
}
},
"x-uid": "3gga5lop4t6",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,213 @@
# List
## UI Schema
### 列表区块
```json
{
"x-uid": "h9mzrnxokk3",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a:view",
"x-decorator": "List.Decorator",
"x-decorator-props": {
"resource": "a",
"collection": "a",
"readPretty": true,
"action": "list",
"params": {
"pageSize": 10
},
"runWhenParamsChanged": true,
"rowKey": "id"
},
"x-component": "CardItem",
"x-designer": "List.Designer",
"x-component-props": {
"title": "List 区块"
},
"properties": {
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ListActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"x-uid": "chjgpgd45qz",
"x-async": false,
"x-index": 1
},
"list": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-component": "List",
"x-component-props": {
"props": "{{ useListBlockProps }}"
},
"properties": {
"item": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "object",
"x-component": "List.Item",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useListItemProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-initializer-props": {
"useProps": "{{ useListItemInitializerProps }}"
},
"x-uid": "u45nwmwlone",
"x-async": false,
"x-index": 1
},
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-align": "left",
"x-initializer": "ListItemActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"useProps": "{{ useListActionBarProps }}",
"layout": "one-column"
},
"x-uid": "9erso7nhotq",
"x-async": false,
"x-index": 2
}
},
"x-uid": "qkuzlun8frh",
"x-async": false,
"x-index": 1
}
},
"x-uid": "1mfaa48qy5j",
"x-async": false,
"x-index": 2
}
},
"x-async": false,
"x-index": 1
}
```
### 对多关系的列表区块
```json
{
"x-uid": "6tzpnkz60rx",
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "a.o2m:view",
"x-decorator": "List.Decorator",
"x-decorator-props": {
"resource": "a.o2m",
"collection": "b",
"association": "a.o2m",
"readPretty": true,
"action": "list",
"params": {
"pageSize": 10
},
"runWhenParamsChanged": true
},
"x-component": "CardItem",
"x-designer": "List.Designer",
"x-component-props": {
"title": "对多关系的列表区块"
},
"properties": {
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "ListActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"x-uid": "i05xr7nfs26",
"x-async": false,
"x-index": 1
},
"list": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-component": "List",
"x-component-props": {
"props": "{{ useListBlockProps }}"
},
"properties": {
"item": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "object",
"x-component": "List.Item",
"x-read-pretty": true,
"x-component-props": {
"useProps": "{{ useListItemProps }}"
},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "ReadPrettyFormItemInitializers",
"x-initializer-props": {
"useProps": "{{ useListItemInitializerProps }}"
},
"x-uid": "krerg70a28e",
"x-async": false,
"x-index": 1
},
"actionBar": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-align": "left",
"x-initializer": "ListItemActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"useProps": "{{ useListActionBarProps }}",
"layout": "one-column"
},
"x-uid": "9745q36ltfv",
"x-async": false,
"x-index": 2
}
},
"x-uid": "yxc0lnax9gk",
"x-async": false,
"x-index": 1
}
},
"x-uid": "6mlzb7p6dvn",
"x-async": false,
"x-index": 2
}
},
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,115 @@
# Map
## UI Schema
### 地图区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-acl-action": "t_ddo5bd0swdg:list",
"x-decorator": "MapBlockProvider",
"x-decorator-props": {
"collection": "t_ddo5bd0swdg",
"resource": "t_ddo5bd0swdg",
"action": "list",
"fieldNames": {
"field": ["f_tmhy5bbusr8"]
},
"params": {
"paginate": false
}
},
"x-designer": "MapBlockDesigner",
"x-component": "CardItem",
"x-filter-targets": [],
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "MapActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 16
}
},
"x-uid": "ap0xzy32bms",
"x-async": false,
"x-index": 1
},
"1672zefmplu": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "MapBlock",
"x-component-props": {
"useProps": "{{ useMapBlockProps }}"
},
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup"
},
"title": "{{ t(\"View record\") }}",
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Details\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "RecordBlockInitializers",
"x-uid": "vce3u8hfdrf",
"x-async": false,
"x-index": 1
}
},
"x-uid": "waw7z3qw7nn",
"x-async": false,
"x-index": 1
}
},
"x-uid": "szyybarlapl",
"x-async": false,
"x-index": 1
}
},
"x-uid": "83m07ksqbal",
"x-async": false,
"x-index": 1
}
},
"x-uid": "5cquhqq2161",
"x-async": false,
"x-index": 2
}
},
"x-uid": "p42awfpfnbf",
"x-async": false,
"x-index": 1
}
```

View File

@ -0,0 +1,193 @@
# Table
## UI Schema
### 表格区块
```json
{
"type": "void",
"version": "2.0",
"x-designer": "TableBlockDesigner",
"x-component": "CardItem",
"x-decorator": "TableBlockProvider",
"x-acl-action": "users:list",
"x-filter-targets": [],
"x-decorator-props": {
"action": "list",
"params": {
"pageSize": 20
},
"rowKey": "id",
"dragSort": false,
"resource": "users",
"showIndex": true,
"collection": "users",
"disableTemplate": false
},
"_isJSONSchemaObject": true,
"properties": {
"actions": {
"type": "void",
"version": "2.0",
"x-component": "ActionBar",
"x-initializer": "TableActionInitializers",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"_isJSONSchemaObject": true,
"x-uid": "55tgsn1puqk",
"x-async": false,
"x-index": 1
},
"g22il5nkv67": {
"type": "array",
"version": "2.0",
"x-component": "TableV2",
"x-initializer": "TableColumnInitializers",
"x-component-props": {
"rowKey": "id",
"useProps": "{{ useTableBlockProps }}",
"rowSelection": {
"type": "checkbox"
}
},
"_isJSONSchemaObject": true,
"properties": {
"actions": {
"type": "void",
"title": "{{ t(\"Actions\") }}",
"version": "2.0",
"x-designer": "TableV2.ActionColumnDesigner",
"x-component": "TableV2.Column",
"x-decorator": "TableV2.Column.ActionBar",
"x-initializer": "TableActionColumnInitializers",
"x-action-column": "actions",
"_isJSONSchemaObject": true,
"properties": {
"actions": {
"type": "void",
"version": "2.0",
"x-component": "Space",
"x-decorator": "DndContext",
"x-component-props": {
"split": "|"
},
"_isJSONSchemaObject": true,
"x-uid": "qkcugf5l0h6",
"x-async": false,
"x-index": 1
}
},
"x-uid": "kbgbskxsj3j",
"x-async": false,
"x-index": 1
}
},
"x-uid": "6r8e3mhnuxg",
"x-async": false,
"x-index": 2
}
},
"x-uid": "b87bbc5x0cp",
"x-async": false,
"x-index": 1
}
```
### 对多关系区块
```json
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "TableBlockProvider",
"x-acl-action": "a.o2m:list",
"x-decorator-props": {
"collection": "b",
"resource": "a.o2m",
"action": "list",
"params": {
"pageSize": 20
},
"showIndex": true,
"dragSort": false,
"disableTemplate": false,
"association": "a.o2m"
},
"x-designer": "TableBlockDesigner",
"x-component": "CardItem",
"x-filter-targets": [],
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "TableActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"x-uid": "0gwze38mzps",
"x-async": false,
"x-index": 1
},
"0wuk1cpy5zp": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-initializer": "TableColumnInitializers",
"x-component": "TableV2",
"x-component-props": {
"rowKey": "id",
"rowSelection": {
"type": "checkbox"
},
"useProps": "{{ useTableBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"Actions\") }}",
"x-action-column": "actions",
"x-decorator": "TableV2.Column.ActionBar",
"x-component": "TableV2.Column",
"x-designer": "TableV2.ActionColumnDesigner",
"x-initializer": "TableActionColumnInitializers",
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "DndContext",
"x-component": "Space",
"x-component-props": {
"split": "|"
},
"x-uid": "gq0nkc7rf0q",
"x-async": false,
"x-index": 1
}
},
"x-uid": "zx97354nzfq",
"x-async": false,
"x-index": 1
}
},
"x-uid": "nss8uocacdq",
"x-async": false,
"x-index": 2
}
},
"x-uid": "0j5kcrius5z",
"x-async": false,
"x-index": 1
}
```

Some files were not shown because too many files have changed in this diff Show More