chore: 清理部分插件,修复 @tachybase/cli 工具 关联 #704

Reviewed-on: daoyoucloud/tachycode#706
This commit is contained in:
sealday 2024-04-15 01:58:18 +08:00
parent 690a275f1a
commit f64a661e05
357 changed files with 154 additions and 35537 deletions

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["esnext", "DOM.Iterable", "DOM"],
"esModuleInterop": true,
"moduleResolution": "node",
"jsx": "react",
"target": "esnext",
"module": "esnext",
"allowJs": true,
"noUnusedLocals": false,
"preserveConstEnums": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"sourceMap": true,
"inlineSources": true,
"resolveJsonModule": true,
"declaration": true,
"experimentalDecorators": true,
"downlevelIteration": true
},
"exclude": ["bin", "node_modules", "lib"]
}

View File

@ -7,6 +7,7 @@ const entry = fg.globSync(['src/**', ...globExcludeFiles], { cwd: __dirname, abs
export default defineConfig({
entry,
tsconfig: path.join(__dirname, 'tsconfig.json'),
outDir: path.join(__dirname, 'lib'),
splitting: false,
silent: true,
@ -14,7 +15,7 @@ export default defineConfig({
clean: true,
bundle: false,
loader: {
'.d.ts': 'copy'
'.d.ts': 'copy',
},
skipNodeModulesBundle: true,
});

View File

@ -9,11 +9,12 @@ import semver from 'semver';
const cli = new Command();
cli.version((await import('../package.json')).version);
await commands(cli);
initEnv();
genTsConfigPaths();
await commands(cli);
if (semver.satisfies(process.version, '<20')) {
console.error(chalk.red('[tachybase cli]: Node.js version must be >= 20'));
process.exit(1);

View File

@ -222,9 +222,7 @@ export async function genTsConfigPaths() {
const cwd = process.cwd();
const cwdLength = cwd.length;
const paths: Record<string, string[]> = {
'@@/*': ['.dumi/tmp/*'],
};
const paths: Record<string, string[]> = {};
const packages = fastGlob.sync(['packages/*/*/package.json', 'packages/*/*/*/package.json'], {
absolute: true,
onlyFiles: true,
@ -324,7 +322,7 @@ export function initEnv() {
SOCKET_PATH: 'storage/gateway.sock',
NODE_MODULES_PATH: resolve(process.cwd(), 'node_modules'),
PM2_HOME: resolve(process.cwd(), './storage/.pm2'),
PLUGIN_PACKAGE_PREFIX: '@nocobase/plugin-,@nocobase/plugin-sample-,@nocobase/preset-',
PLUGIN_PACKAGE_PREFIX: '@nocobase/plugin-,@nocobase/plugin-sample-,@nocobase/preset-,@hera/plugin-',
SERVER_TSCONFIG_PATH: './tsconfig.server.json',
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
CACHE_DEFAULT_STORE: 'memory',

View File

@ -1,11 +0,0 @@
import React from "react";
const LangSwitch = () => {
const { hostname } = window.location
if (hostname === 'localhost') return null;
const en = window.location.href.replace(hostname, 'docs.nocobase.com')
const cn = window.location.href.replace(hostname, 'docs-cn.nocobase.com')
return <span><a href={en}>EN</a> | <a href={cn}></a></span>
}
export default LangSwitch;

View File

@ -1,23 +0,0 @@
import React, { FC, useRef, useEffect, Suspense } from 'react';
import { Root, createRoot } from 'react-dom/client';
import { IPreviewerProps } from 'dumi';
import DefaultPreviewer from 'dumi/theme-default/builtins/Previewer';
const Previewer: FC<IPreviewerProps> = ({ children, ...props }) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let root: Root
if (ref.current) {
root = createRoot(ref.current)
root.render(<Suspense fallback={<div>loading...</div>}>{children}</Suspense>)
}
return () => {
if (root) {
root.unmount()
}
}
}, []);
return <DefaultPreviewer {...props}><div ref={ref} /></DefaultPreviewer>;
};
export default Previewer;

View File

@ -1,219 +0,0 @@
import DumiPreviewerActions from 'dumi/theme-default/slots/PreviewerActions';
import React, { useRef, useEffect, useState } from 'react';
import { Spin } from 'antd'
import { IPreviewerProps } from 'dumi';
const indexHtml = `<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
`
const mainTsx = `
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
`
const packageJson = `
{
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
},
"devDependencies": {
"flat": "^5.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"less": "^4.2.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}
`
const tsConfigJson = `
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src",
"vite.config.ts"
]
}
`
const viteConfigTs = `
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
`
const sandboxTask = `
{
"setupTasks": [
{
"name": "Install Dependencies",
"command": "pnpm install"
}
],
"tasks": {
"dev": {
"name": "dev",
"command": "pnpm dev",
"runAtStart": true,
"preview": {
"port": 5173
}
},
"build": {
"name": "build",
"command": "pnpm build",
"runAtStart": false
},
"preview": {
"name": "preview",
"command": "pnpm preview",
"runAtStart": false
}
}
}
`
function getCSBData(opts: IPreviewerProps, ext: string) {
const files: Record<
string,
{
content: string;
isBinary: boolean;
}
> = {};
const deps: Record<string, string> = {};
const entryFileName = `index${ext}`;
Object.entries(opts.asset.dependencies).forEach(([name, { type, value }]) => {
if (type === 'NPM') {
// generate dependencies
deps[name] = value;
} else {
// append other imported local files
files[name === entryFileName ? `src/App${ext}` : name] = {
content: value,
isBinary: false,
};
}
});
// append package.json
let pkg = JSON.parse(packageJson)
try {
for (let key in deps) {
if (!pkg['devDependencies'][key]) {
pkg.dependencies[key] = deps[key]
}
}
} catch (e) {
console.log(e)
}
files['package.json'] = {
content: JSON.stringify(
{
name: opts.title,
...pkg,
},
null,
2,
),
isBinary: false,
};
files['index.html'] = { content: indexHtml, isBinary: false };
files['src/main.tsx'] = { content: mainTsx, isBinary: false };
files['package.json'] = { content: JSON.stringify(pkg, null, 2), isBinary: false };
files['.codesandbox/task.json'] = { content: sandboxTask, isBinary: false };
files['tsconfig.json'] = { content: tsConfigJson, isBinary: false };
files['vite.config.ts'] = { content: viteConfigTs, isBinary: false };
return { files };
}
export function openCodeSandbox(opts: IPreviewerProps) {
const isTSX = Boolean(opts.asset.dependencies?.['index.tsx']);
const ext = isTSX ? '.tsx' : '.jsx';
return fetch("https://codesandbox.io/api/v1/sandboxes/define?json=1", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(getCSBData(opts, ext))
})
.then(x => x.json())
.then(data => {
window.open(`https://codesandbox.io/p/sandbox/${data.sandbox_id}?file=/src/App${ext}`);
});
}
const PreviewerActions: typeof DumiPreviewerActions = (props) => {
const div = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (div.current) {
const element = div.current.querySelector('.dumi-default-previewer-action-btn');
element?.addEventListener('click', (e) => {
e.stopImmediatePropagation();
setLoading(true);
openCodeSandbox(props).finally(() => {
setLoading(false);
});
})
}
}, [div])
return <Spin spinning={loading}><div ref={div}><DumiPreviewerActions {...props} disabledActions={['STACKBLITZ']} /></div></Spin>
};
export default PreviewerActions;

View File

@ -1,4 +0,0 @@
{
"extends": "../tsconfig.json",
"include": ["**/*"]
}

View File

@ -1,469 +0,0 @@
import { getUmiConfig } from '@nocobase/devtools/umiConfig';
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,
},
fastRefresh: false, // 热更新会导致 Context 丢失,不开启
// ssr: {},
// exportStatic: {
// ignorePreRenderError: true
// },
cacheDirectoryPath: `node_modules/.docs-client-${lang}-cache`,
outputPath: `./dist/${lang}`,
resolve: {
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',
github: 'https://github.com/nocobase/nocobase',
footer: 'nocobase | Copyright © 2022',
// sidebarGroupModePath: ['/components'],
nav: [
{
title: 'API',
link: '/core/application/application',
},
// {
// title: 'UI Schema',
// link: '/ui-schema',
// },
],
sidebarEnhance: {
'/core': [
{
title: 'Application',
type: 'group',
children: [
{
title: 'Application',
link: '/core/application/application',
},
{
title: 'Plugin',
link: '/core/application/plugin',
},
{
title: 'PluginManager',
link: '/core/application/plugin-manager',
},
{
title: 'RouterManager',
link: '/core/application/router-manager',
},
{
title: 'PluginSettingsManager',
link: '/core/application/plugin-settings-manager',
},
],
},
{
title: 'UI Schema',
type: 'group',
children: [
{
title: 'SchemaComponent',
link: '/core/ui-schema/schema-component',
},
{
title: 'Designable',
link: '/core/ui-schema/designable',
},
{
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',
},
],
},
{
title: 'Data Source',
type: 'group',
children: [
{
title: 'DataSourceManager',
link: '/core/data-source/data-source-manager',
},
{
title: 'DataSourceManagerProvider',
link: '/core/data-source/data-source-manager-provider',
},
{
title: 'DataSource',
link: '/core/data-source/data-source',
},
{
title: 'DataSourceProvider',
link: '/core/data-source/data-source-provider',
},
{
title: 'CollectionManager',
link: '/core/data-source/collection-manager',
},
{
title: 'CollectionManagerProvider',
link: '/core/data-source/collection-manager-provider',
},
{
title: 'CollectionTemplateManager',
link: '/core/data-source/collection-template-manager',
},
{
title: 'CollectionTemplate',
link: '/core/data-source/collection-template',
},
{
title: 'Collection',
link: '/core/data-source/collection',
},
{
title: 'CollectionProvider',
link: '/core/data-source/collection-provider',
},
{
title: 'CollectionMixins',
link: '/core/data-source/collection-mixins',
},
{
title: 'CollectionField',
link: '/core/data-source/collection-field',
},
{
title: 'CollectionFieldInterfaceManager',
link: '/core/data-source/collection-field-interface-manager',
},
{
title: 'CollectionFieldInterface',
link: '/core/data-source/collection-field-interface',
},
{
title: 'AssociationProvider',
link: '/core/data-source/association-provider',
},
{
title: 'ExtendCollectionsProvider',
link: '/core/data-source/extend-collections-provider',
},
]
},
{
title: 'DataBlock',
type: 'group',
children: [
{
title: 'CollectionRecord',
link: '/core/data-block/collection-record',
},
{
title: 'CollectionRecordProvider',
link: '/core/data-block/collection-record-provider',
},
{
title: 'DataBlockProvider',
link: '/core/data-block/data-block-provider',
},
{
title: 'DataBlockResourceProvider',
link: '/core/data-block/data-block-resource-provider',
},
{
title: 'DataBlockRequestProvider',
link: '/core/data-block/data-block-request-provider',
},
]
}
],
// '/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,374 +0,0 @@
# Application
## new Application(options)
创建一个 NocoBase 应用。
- 类型
```tsx | pure
export interface ApplicationOptions {
apiClient?: APIClientOptions | APIClient;
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;
dataSourceManager?: DataSourceManagerOptions;
}
```
- 详细信息
- 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 环境)。
- dataSourceManager数据源管理器具体参考[DataSourceManager](/core/data-source/data-source-manager)
- 示例
```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.dataSourceManager
详细介绍,请参考:[dataSourceManager](/core/data-source/data-source-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

@ -1,122 +0,0 @@
# 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

@ -1,269 +0,0 @@
# 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

@ -1,89 +0,0 @@
# 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;
}
get dataSourceManager() {
return this.app.dataSourceManager;
}
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

@ -1,386 +0,0 @@
# 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();
}
```

View File

@ -1,259 +0,0 @@
# CollectionRecordProvider
用于提供 [CollectionRecord](./collection-record) 实例。
## 组件
- 类型
```tsx | pure
interface CollectionRecordProviderProps<DataType = {}, ParentDataType = {}> {
isNew?: boolean;
record?: CollectionRecord<DataType, ParentDataType> | DataType;
parentRecord?: CollectionRecord<ParentDataType> | DataType;
/**
* 当前记录所属的 collection name
*/
collectionName?: string;
}
```
- 详解
参数的具体说明参见 [CollectionRecord](./collection-record)。
需要说明的是 `record``parentRecord` 即可以是普通的对象,也可以是 [CollectionRecord](./collection-record) 实例,但最终会转为 `CollectionRecord` 实例,并通过 context 传递给子组件。
## 示例
- record 参数为 CollectionRecord 实例
```tsx | pure
import { CollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
<CollectionRecordProvider record={record} />
// 最终向子组件传递的数据为: props.record
```
```tsx
import { CollectionRecord, useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
const Demo = () => {
const record = useCollectionRecord();
return <pre>{JSON.stringify(record, null, 2)}</pre>;
}
export default () => <CollectionRecordProvider record={record}><Demo /></CollectionRecordProvider>
```
- record 参数为普通对象
```tsx | pure
<CollectionRecordProvider record={{ id: 1, name: 'foo' }} />
// 最终向子组件传递的数据为: const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
```
```tsx
import { useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const Demo = () => {
const record = useCollectionRecord();
return <pre>{JSON.stringify(record, null, 2)}</pre>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }}><Demo /></CollectionRecordProvider>
```
- record 参数为 CollectionRecord 实例且带有父记录
```tsx | pure
const parentRecord = new CollectionRecord({ data: { id: 1, role: 'admin' } });
const record = new CollectionRecord({ data: { id: 1, name: 'foo' }, parentRecord });
<CollectionRecordProvider record={record} />
// 最终向子组件传递的数据为: props.record
```
```tsx
import { CollectionRecord, useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const parentRecord = new CollectionRecord({ data: { id: 1, role: 'admin' } });
const record = new CollectionRecord({ data: { id: 1, name: 'foo' }, parentRecord });
const Demo = () => {
const record = useCollectionRecord();
return <pre>{JSON.stringify(record, null, 2)}</pre>;
}
export default () => <CollectionRecordProvider record={record}><Demo /></CollectionRecordProvider>
```
- record 参数为 CollectionRecord 实例,父记录通过 `parentRecord` 参数传递
```tsx | pure
const parentRecord = new CollectionRecord({ data: { id: 1, role: 'admin' } });
const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
<CollectionRecordProvider record={record} parentRecord={parentRecord} />
// 首先设置父记录record.setParentRecord(parentRecord);
// 最终向子组件传递的数据为record带有父记录
```
```tsx
import { CollectionRecord, useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const parentRecord = new CollectionRecord({ data: { id: 1, role: 'admin' } });
const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
const Demo = () => {
const record = useCollectionRecord();
return <pre>{JSON.stringify(record, null, 2)}</pre>;
}
export default () => <CollectionRecordProvider record={record} parentRecord={parentRecord}><Demo /></CollectionRecordProvider>
```
- record 参数为普通对象,父记录也是普通对象
```tsx | pure
<CollectionRecordProvider record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }} />
// 首先实例化父记录const parentRecord = new CollectionRecord({ data: { id: 1, role: 'admin' } });
// 然后实例化记录const record = new CollectionRecord({ data: { id: 1, name: 'foo' } });
// 最后设置父记录record.setParentRecord(parentRecord);
// 最终向子组件传递的数据为record带有父记录
```
```tsx
import { useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const Demo = () => {
const record = useCollectionRecord();
return <pre>{JSON.stringify(record, null, 2)}</pre>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }} ><Demo /></CollectionRecordProvider>
```
## Hooks
### useCollectionRecord()
用于获取 `CollectionRecordProvider` 组件传递的数据记录。
- 示例
```tsx | pure
const record = useCollectionRecord();
console.log(record, record.data, record.parentRecord, record.parentRecord.data);
```
```tsx
import { useCollectionRecord, CollectionRecordProvider } from '@nocobase/client';
const Demo = () => {
const record = useCollectionRecord();
return <div>
<div>record: <pre>{JSON.stringify(record, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.data: <pre>{JSON.stringify(record.data, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.parentRecord: <pre>{JSON.stringify(record.parentRecord, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.parentRecord.data: <pre>{JSON.stringify(record.parentRecord.data, null, 2)}</pre></div>
</div>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }} ><Demo /></CollectionRecordProvider>
```
### useCollectionRecordData()
直接获取 CollectionRecord 的 `data` 属性,等同于 `useCollectionRecord().data`
- 示例
```tsx | pure
const data = useCollectionRecordData();
const record = useCollectionRecord();
console.log(data === record.data);
```
```tsx
import { useCollectionRecord, useCollectionRecordData, CollectionRecordProvider } from '@nocobase/client';
const Demo = () => {
const data = useCollectionRecordData();
const record = useCollectionRecord();
return <div>
<div>data === record.data: { JSON.stringify(data === record.data) }</div>
<div style={{ marginTop: 10 }}>data: <pre>{JSON.stringify(data, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.data: <pre>{JSON.stringify(record.data, null, 2)}</pre></div>
</div>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }} ><Demo /></CollectionRecordProvider>
```
### useCollectionParentRecord()
直接获取 CollectionRecord 的数据 `parentRecord`,等同于 `useCollectionRecord().parentRecord`
- 示例
```tsx | pure
const parentRecord = useCollectionParentRecord();
const record = useCollectionRecord();
console.log(parentRecord === record.parentRecord);
```
```tsx
import { useCollectionRecord, useCollectionParentRecord, CollectionRecordProvider } from '@nocobase/client';
const Demo = () => {
const record = useCollectionRecord();
const parentRecord = useCollectionParentRecord();
return <div>
<div>parentRecord === record.parentRecord: { JSON.stringify(parentRecord === record.parentRecord) }</div>
<div style={{ marginTop: 10 }}>parentRecord: <pre>{JSON.stringify(parentRecord, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.parentRecord: <pre>{JSON.stringify(record.parentRecord, null, 2)}</pre></div>
</div>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }} ><Demo /></CollectionRecordProvider>
```
### useCollectionParentRecordData()
直接获取 CollectionRecord 的数据 `parentRecord.data`,等同于 `useCollectionRecord().parentRecord.data`
- 示例
```tsx | pure
const record = useCollectionRecord();
const parentData = useCollectionParentRecordData();
const parentRecord = useCollectionParentRecord();
console.log(parentData === parentRecord.data === record.parentRecord.data);
```
```tsx
import { useCollectionRecord, CollectionRecordProvider, useCollectionParentRecordData, useCollectionParentRecord } from '@nocobase/client';
const Demo = () => {
const record = useCollectionRecord();
const parentData = useCollectionParentRecordData();
const parentRecord = useCollectionParentRecord();
return <div>
<div style={{ marginTop: 10 }}>parentData: <pre>{JSON.stringify(parentData, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>parentRecord.data: <pre>{JSON.stringify(parentRecord.data, null, 2)}</pre></div>
<div style={{ marginTop: 10 }}>record.parentRecord.data: <pre>{JSON.stringify(record.parentRecord.data, null, 2)}</pre></div>
</div>;
}
export default () => <CollectionRecordProvider record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }} ><Demo /></CollectionRecordProvider>
```

View File

@ -1,194 +0,0 @@
# CollectionRecord
数据表中的一条记录。
## 类型
```tsx | pure
interface CollectionRecordOptions<DataType = {}, ParentDataType = {}> {
isNew?: boolean;
data?: DataType;
parentRecord?: CollectionRecord<ParentDataType>;
/**
* 当前记录所属的 collection name
*/
collectionName?: string;
}
class CollectionRecord<DataType = {}, ParentDataType = {}> {
public isNew?: boolean;
public data?: DataType;
public parentRecord?: CollectionRecord<ParentDataType>;
public collectionName?: string;
constructor(options: CollectionRecordOptions<DataType, ParentDataType>) {}
setData(data: DataType) {
this.data = data;
}
setParentRecord(parentRecord: CollectionRecord<ParentDataType>) {
this.parentRecord = parentRecord;
}
}
```
## 详解
### CollectionRecord 基本概念
CollectionRecord 类用于提供数据记录,通常情况下对应着后端数据表中的一条记录。以用户表为例,其一条数据对应的 CollectionRecord 类如下:
```tsx | pure
const useCollectionRecord = new CollectionRecord({
data: {
"id": 1,
"roleId": 10,
"appLang": null,
"createdById": null,
"email": "test@nocobase.com",
"nickname": "Admin",
"phone": null,
"systemSettings": {},
"updatedById": null,
"username": "nocobase",
"createdAt": "2023-12-04T09:42:52.953Z",
"updatedAt": "2023-12-04T09:42:52.953Z",
}
});
```
### CollectionRecord 和 Collection 的关系
CollectionRecord 是指的数据,而 Collection 则是表结构。对于上面的用户表,其对应的 Collection 如下:
```tsx | pure
const usersCollection = new Collection({
name: 'users',
fields: [
{
type: 'bigInt',
name: 'id',
},
{
type: 'string',
name: 'username',
},
{
type: 'integer',
name: 'age',
},
{
"name": "email",
"type": "string",
},
// ....
],
});
```
### 父子关系和关系字段
对于[关系字段](https://docs.nocobase.com/development/server/collections/association-fields),例如用户和角色的关系,在用户表中会有一个 `roleId` 字段,其值为角色表中的 `id`,当我们通过 `users.roleId` 字段查询用户的角色时:
```bash | pure
GET /api/users/1/roles:get/10
```
其中 `1` 为用户的 `id``10` 为角色的 `id`,我们可以得到用户的角色数据:
```tsx | pure
const roleRecord = new CollectionRecord({
data: {
"id": 10,
"name": "member",
"title": "test role",
"strategy": {
"actions": [
"view",
"update:own",
"destroy:own",
"create"
]
},
"createdAt": "2023-03-30T07:53:10.924Z",
"updatedAt": "2023-12-15T02:51:43.577Z",
}
})
```
其中 `users` id 为 1 的记录我们称之为父记录:
```tsx | pure
roleRecord.setParentRecord(userRecord);
```
### 新记录
对于新表单,我们可以通过 `isNew` 属性来标识:
```tsx | pure
const record = new CollectionRecord({
isNew: true,
});
```
## 示例
### 基本使用
```tsx | pure
import { CollectionRecord } from '@nocobase/client';
const record = new CollectionRecord({
data: {
name: 'foo',
}
});
```
### 创建空记录
```tsx | pure
import { CollectionRecord } from '@nocobase/client';
const record = new CollectionRecord({
isNew: true,
});
```
### 设置 parentRecord
方式1: 通过构造函数设置
```tsx | pure
const parentRecord = new CollectionRecord({
data: {
foo: 'foo',
}
});
const record = new CollectionRecord({
data: {
name: 'bar',
},
parentRecord,
});
```
方式2: 通过 `setParentRecord` 方法设置
```tsx | pure
const parentRecord = new CollectionRecord({
data: {
foo: 'foo',
}
});
const record = new CollectionRecord({
data: {
name: 'bar',
}
});
record.setParentRecord(parentRecord);
```

View File

@ -1,246 +0,0 @@
# DataBlockProvider
## 区块类型
区块分为简单区块和包含各种数据的区块。
### 简单区块
简单区块例如 Markdown 区块。
![](./images/markdown-block.png)
它只有文本内容,没有其他更多复杂数据,且文本内容是存储在 `schema` 中的,没有存储在数据库中。
```json {5}| pure
{
"type": "void",
"x-component": "Markdown.Void",
"x-component-props": {
"content": "markdown content"
},
}
```
### 数据区块
数据区块是指区块的数据存储在服务端的数据表中,例如 Table 组件。
![](./images/data-block.png)
Table 中的字段信息及列表数据,都是存储在数据库中的。
## DataBlockProvider 介绍
为了方便对数据区块的数据进行管理,我们提供了 `DataBlockProvider` 组件,其内部封装了:
- `DataBlockProvider`:封装了下面的所有组件,并提供了区块属性
- [CollectionProvider](/core/data-source/collection-provider) / [AssociationProvider](/core/data-source/association-provider): 根据 `DataBlockProvider` 提供的上下文信息,查询对应数据表数据及关系字段信息并传递
- [BlockResourceProvider](/core/data-block/data-block-resource-provider): 根据 `DataBlockProvider` 提供的上下文信息,构建区块 [Resource](https://docs.nocobase.com/api/sdk#resource-action) API用于区块数据的增删改查
- [BlockRequestProvider](/core/data-block/data-block-request-provider): 根据 `DataBlockProvider` 提供的上下文信息,自动调用 `BlockResourceProvider` 提供的 `resource.get()``resource.list()` 发起请求,得到区块数据,并传递
- [CollectionRecordProvider](/core/data-source/record-provider): 对于 `resource.get()` 场景,会自动嵌套 `CollectionRecordProvider` 并将 `resource.get()` 请求结果传递下去,`resource.list()` 场景则需要自行使用 `CollectionRecordProvider` 提供数据记录
```tsx | pure
const DataBlockProvider = (props) => {
return <DataBlockContext.Provider>
<CollectionDataSourceProvider>
<CollectionProvider> / <AssociationProvider>
<BlockResourceProvider>
<BlockRequestProvider>
{action !== 'list' && <CollectionRecordProvider record={blocRequest.data}>
{props.children}
</Record>}
</BlockRequestProvider>
</BlockResourceProvider> / </AssociationProvider>
</CollectionProvider>
</CollectionDataSourceProvider>
</DataBlockContext.Provider>
}
```
上述组件封装到 `DataBlockProvider` 的内部,只需要使用 `DataBlockProvider` 即可自行得到上述数据。
### 使用方式
其主要使用在区块的 schema [x-decorator](https://docs.nocobase.com/development/client/ui-schema/what-is-ui-schema#x-decorator) 中,例如:
```js {5}| pure
{
type: 'void',
name: 'hello-block',
'x-component': 'CardItem',
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
tableProps: {
bordered: true,
}
},
'x-use-decorator-props': 'useDynamicDataBlockProps',
}
```
### 完整示例
<code src="./demos/data-block-provider/complete-demo.tsx"></code>
## 属性
### 静态属性和动态属性
- schema 中的 `x-decorator-props` 称为静态属性,它是一个普通对象,记录区块的配置信息
- schema 中的 `x-use-decorator-props` 中的属性称为动态属性,它是一个 React hook可用于获取例如 URL 上的 ID或者父级的 context 数据
当两者都存在时,会进行深度合并,作为 `DataBlockProvider` 的属性。
### 属性详解
```ts | pure
interface AllDataBlockProps {
collection?: string;
association?: string;
sourceId?: string | number;
record?: Record;
action?: 'list' | 'get';
filterByTk?: string;
params?: Record<string, any>;
parentRecord?: Record;
[index: string]: any;
}
```
- collection`x-decorator-props`):区块的 collection 表名,用于获取区块的字段信息和区块数据
- association`x-decorator-props`):区块的关系字段名,用于获取区块的关系字段信息和关系字段数据
- dataSource(`x-decorator-props`): 数据源,具体可参考 [Data Modeling](https://docs.nocobase.com/manual/data-modeling)
- action`x-decorator-props`):区块的请求类型,`list` 或 `get`
- params`x-decorator-props` 和 `x-use-decorator-props`):区块的请求参数,同时存在于
- filterByTk`x-use-decorator-props`):相当于 `params.filterByTk`,可理解为 `id`,用于获取单条数据
- sourceId`x-use-decorator-props`):区块的 sourceId配合 `association` 使用,用于获取区块的关系字段数据
- record`x-use-decorator-props`):当提供 `record` 时,会使用 `record` 作为区块的数据,不发起请求
- parentRecord`x-use-decorator-props`):当提供 `parentRecord` 时,会使用 `parentRecord` 作为关系字段的表数据,不发起请求
```tsx | pure
const DataBlockProvider = (props) => {
return <DataBlockContext.Provider value={props}>
<CollectionDataSourceProvider>
<CollectionProvider name={props.collection}> / <CollectionProvider name={props.association}>
<BlockResourceProvider {...props}>
<BlockRequestProvider resource={resource}>
{action !== 'list' && <CollectionRecordProvider record={blocRequest.data}>
{props.children}
</Record>}
</BlockRequestProvider>
</BlockResourceProvider>
</CollectionProvider>
</CollectionDataSourceProvider>
</DataBlock.Provider>
}
```
### 属性组合和场景
这些属性根据不同的场景,共有 8 中情况:
- collection
- 创建:`collection`
- 获取单条数据:`collection` + `action: get` + `params`
- 获取列表数据:`collection` + `action: list` + `params`
- 使用 `record` 作为数据:`collection` + `record`
对于 *获取单条数据**获取列表数据* `params` 非必须。
- association
- 创建:`association` + `sourceId`
- 获取单条数据:`association` + `sourceId` + `action: get` + `params` + `parentRecord`
- 获取列表数据:`association` + `sourceId` + `action: list` + `params` + `parentRecord`
- 使用 `record` 作为数据:`association` + `sourceId` + `record` + `parentRecord`
对于 *获取单条数据**获取列表数据* `params``parentRecord` 非必须,当没有 `parentRecord` 会根据 `association` 查询到对应的 `collection`,然后再根据 `collection` 查询到对应的 `parentRecord`
### 属性获取和修改
#### useDataBlock()
可用于获取和修改 `DataBlockProvider` 的属性。
- 类型
```tsx | pure
interface Result<T extends {} = {}> {
props: AllDataBlockProps & T;
dn: Designable;
}
const useDataBlock: <T extends {}>() => Result<T>
```
- 详解
`props` 就对应着上面的 `AllDataBlockProps`
`dn``Designable` 对象,可用于修改 `DataBlockProvider` 的 UI schema详细见 [Designable](/core/ui-schema/designable)。
- 示例
```tsx | pure
const { props, dn } = useDataBlock<{ tableProps: { bordered?: boolean } }>();
// 获取
const checked = props.tableProps.bordered;
// 修改
dn.deepMerge({
'x-decorator-props': {
tableProps: {
bordered: !checked,
},
},
});
```
#### useDataBlockProps()
相当于 `useDataBlock().props`
```tsx | pure
const props = useDataBlockProps<{ tableProps: { bordered?: boolean } }>();
const checked = props.tableProps.bordered;
```
## 示例
### collection
#### Table list
<code src="./demos/data-block-provider/collection-table-list.tsx"></code>
#### Form get & update
<code src="./demos/data-block-provider/collection-form-get-and-update.tsx"></code>
#### Form create
<code src="./demos/data-block-provider/collection-form-create.tsx"></code>
#### Form record & update
<code src="./demos/data-block-provider/collection-form-record-and-update.tsx"></code>
### association
association 与 collection 类似,只是需要提供 `sourceId`,我们以 `Table list` 为例。
#### Table list & sourceId
<code src="./demos/data-block-provider/association-table-list-and-source-id.tsx"></code>
#### Table list & parentRecord
如果不提供 `sourceId`,则需要提供 `parentRecord`,我们以 `Table list` 为例。
<code src="./demos/data-block-provider/association-table-list-and-parent-record.tsx"></code>

View File

@ -1,139 +0,0 @@
# DataBlockRequestProvider
其内部获取到 [BlockResourceProvider](/core/data-block/data-block-resource-provider) 提供的 `resource`,根据 [BlockProvider](/core/data-block/data-block-provider) 提供的参数,自动调用 `resource.get()` 或者 `resource.list()` 获取的区块的数据,并通过 context 传递下去。
## 请求参数
请求参数是获取 `DataBlockProvider` 提供中的 `params``filterByTk`
```ts | pure
const schema = {
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
'collection': 'users',
'action': 'list',
// 静态参数
params: {
pageSize: 10,
}
},
// 动态参数
'x-use-decorator-props': 'useDynamicDataBlockProps',
}
const useDynamicDataBlockProps: UseDataBlockProps<'CollectionList'> = () => {
return {
params: {
size: 15,
}
}
}
```
会自动调用 `resource.list()` 获取数据,发起 `GET /api/users:list?pageSize=10&size=15` 的请求。
## Hooks
### useDataBlockRequest()
用于获取请求对象,一般用区块组件中。
```tsx | pure
const MyTable = () => {
const { data, loading } = useDataBlockRequest();
return (
<Table
dataSource={data?.data || []}
loading={loading}
pagination={{
total: data?.meta.total,
pageSize: data?.meta.pageSize,
page: data?.meta.page,
}}
/>
)
}
```
## Record
### Get 请求
对于 `get` 请求,当获取到 `data` 数据后,会通过 `CollectionRecordProvider` 提供 `record` 对象,用于获取当前区块的数据。
```ts | pure
const schema = {
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
'collection': 'users',
'action': 'get', // get 请求
},
// 动态参数
'x-use-decorator-props': 'useDynamicFormProps',
}
const useDynamicDataBlockProps: UseDataBlockProps<'CollectionGet'> = () => {
return {
params: {
filterByTk: 1,
}
}
}
```
会自动调用 `resource.get()` 获取数据,发起 `GET /api/users:get/1` 的请求,并通过 `CollectionRecordProvider` 提供上下文。
```tsx | pure
const { data } = useDataBlockRequest();
const record = useCollectionRecord(); // record 上下文数据
// 相等
record.data === data;
```
### List 请求
对于 `list` 请求则不会提供 `record` 对象,需要自己通过 `<CollectionRecordProvider />` 设置上下文。
```tsx | pure
const MyTable = () => {
const { data } = useDataBlockRequest();
return (
<Table
dataSource={data?.data || []}
columns={[
{
title: 'ID',
dataIndex: 'id',
},
{
title: 'Action',
render: (v, record) => {
return (
<CollectionRecordProvider record={record}>
<MyAction />
</CollectionRecordProvider>
)
},
},
]}
pagination={{
total: data?.meta.total,
pageSize: data?.meta.pageSize,
page: data?.meta.page,
}}
/>
)
}
const MyAction = () => {
const record = useCollectionRecord();
return (
<Button onClick={() => {
console.log(record.data);
}}>Dialog</Button>
)
}
```

View File

@ -1,36 +0,0 @@
# DataBlockResourceProvider
根据 `DataBlockProvider` 中的 `collection`、`association`、`sourceId` 等属性,构建好 [resource](https://docs.nocobase.com/api/sdk#resource-action) 对象,方便子组件对区块数据的增删改查操作,其内置在 [DataBlockProvider](/core/data-block/data-block-provider) 中
## useDataBlockResource
用于获取当前数据块的 resource 对象。
- 类型
```ts | pure
function useDataBlockResource(): IResource
```
- 示例
```ts | pure
const resource = useDataBlockResource();
const onSubmit = async (values) => {
// 创建
await resource.create({ values });
}
```
```ts | pure
const resource = useDataBlockResource();
const onDelete = async () => {
// 删除
await resource.destroy();
}
```
可运行示例

View File

@ -1,97 +0,0 @@
import React from 'react';
import { Table, TableProps } from 'antd';
import { SchemaComponent, UseDataBlockProps, useDataBlockRequest, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import { createApp } from '../../../data-source/demos/createApp';
const collection = 'users';
const associationField = 'roles';
const association = `${collection}.${associationField}`;
const action = 'list';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
association,
action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps',
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequest<any[]>();
return {
loading,
dataSource: data?.data || [],
columns: [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Description',
dataIndex: 'description',
},
],
};
}
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
const parentRecord = {
id: 1,
username: 'Tom',
};
return {
parentRecord,
};
};
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const mocks = {
[`${collection}/1/${associationField}:${action}`]: {
data: [
{
name: 'admin',
title: 'Admin',
description: 'Admin description',
},
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
],
},
};
const Root = createApp(
Demo,
{
components: { MyTable },
scopes: { useTableProps, useBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -1,132 +0,0 @@
import React from 'react';
import { Select, Table, TableProps } from 'antd';
import { SchemaComponent, UseDataBlockProps, useDataBlockRequest, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import { createApp } from '../../../data-source/demos/createApp';
import useUrlState from '@ahooksjs/use-url-state';
const collection = 'users';
const associationField = 'roles';
const association = `${collection}.${associationField}`;
const action = 'list';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
association,
action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps',
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequest<any[]>();
return {
loading,
dataSource: data?.data || [],
columns: [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Description',
dataIndex: 'description',
},
],
};
}
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
const [state] = useUrlState({ userId: '1' });
return {
sourceId: state.userId,
};
};
const Demo = () => {
const [state, setState] = useUrlState({ userId: '1' });
return (
<>
<Select
defaultValue={state.userId}
options={[
{ key: 1, value: '1', label: 'Tom' },
{ key: 2, value: '2', label: 'Jack' },
]}
onChange={(v) => {
setState({ userId: v });
}}
></Select>
<SchemaComponent schema={schema}></SchemaComponent>
</>
);
};
const mocks = {
[`${collection}/1/${associationField}:${action}`]: {
data: [
{
name: 'admin',
title: 'Admin',
description: 'Admin description',
},
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
],
},
[`${collection}/2/${associationField}:${action}`]: {
data: [
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
{
name: 'tester',
title: 'Tester',
description: 'Tester description',
},
],
},
[`${collection}:get/1`]: {
id: 1,
username: 'Tom',
},
[`${collection}:get/2`]: {
id: 1,
username: 'Jack',
},
};
const Root = createApp(
Demo,
{
components: { MyTable },
scopes: { useTableProps, useBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -1,95 +0,0 @@
import React, { FC } from 'react';
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
import { SchemaComponent, useDataBlockResource, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import { createApp } from '../../../data-source/demos/createApp';
interface DemoFormFieldType {
id: number;
username: string;
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item<DemoFormFieldType>
label="Age"
name="age"
rules={[{ required: true, message: 'Please input your age!' }]}
>
<InputNumber />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
});
function useDemoFormProps(): DemoFormProps {
const resource = useDataBlockResource();
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.create({
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
onFinish,
};
}
const collection = 'users';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: collection,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const mocks = {
[`${collection}:create`]: (config) => {
console.log('config.data', config.data);
return [200, { msg: 'ok' }];
},
};
const Root = createApp(
Demo,
{
components: { DemoForm },
scopes: { useDemoFormProps },
},
mocks,
);
export default Root;

View File

@ -1,158 +0,0 @@
import React, { FC, useEffect } from 'react';
import { Button, Form, FormProps, Input, InputNumber, Select, notification } from 'antd';
import {
SchemaComponent,
UseDataBlockProps,
useDataBlockResource,
useCollectionRecordData,
withDynamicSchemaProps,
} from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import useUrlState from '@ahooksjs/use-url-state';
import { createApp } from '../../../data-source/demos/createApp';
interface DemoFormFieldType {
id: number;
username: string;
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item<DemoFormFieldType>
label="Age"
name="age"
rules={[{ required: true, message: 'Please input your age!' }]}
>
<InputNumber />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
});
function useDemoFormProps(): DemoFormProps {
const data = useCollectionRecordData<DemoFormFieldType>();
const resource = useDataBlockResource();
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(data);
}, [data, form]);
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.update({
filterByTk: data.id,
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
initialValues: data,
preserve: true,
onFinish,
form,
};
}
const useBlockDecoratorProps: UseDataBlockProps<'CollectionGet'> = () => {
const [state] = useUrlState({ id: '1' });
return {
filterByTk: state.id,
};
};
const collection = 'users';
const action = 'get';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
collection: collection,
action: action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const Demo = () => {
const [state, setState] = useUrlState({ id: '1' });
return (
<>
<Select
defaultValue={state.id}
options={[
{ key: 1, value: '1', label: 'Bamboo' },
{ key: 2, value: '2', label: 'Mary' },
]}
onChange={(v) => {
setState({ id: v });
}}
></Select>
<SchemaComponent schema={schema}></SchemaComponent>
</>
);
};
const mocks = {
[`${collection}:${action}`]: function (config) {
const { filterByTk } = config.params;
return {
data:
Number(filterByTk) === 1
? {
id: 1,
username: 'Bamboo',
age: 18,
}
: {
id: 2,
username: 'Mary',
age: 25,
},
};
},
[`${collection}:update`]: function (config) {
console.log('config.data', config.data);
return {
data: 'ok',
};
},
};
const Root = createApp(
Demo,
{
components: { DemoForm },
scopes: { useDemoFormProps, useBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -1,137 +0,0 @@
import React, { FC, useEffect } from 'react';
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
import {
CollectionRecordProvider,
SchemaComponent,
UseDataBlockProps,
useDataBlockResource,
useCollectionRecordData,
withDynamicSchemaProps,
} from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import { createApp } from '../../../data-source/demos/createApp';
interface DemoFormFieldType {
id: number;
username: string;
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item<DemoFormFieldType>
label="Age"
name="age"
rules={[{ required: true, message: 'Please input your age!' }]}
>
<InputNumber />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
});
function useDemoFormProps(): DemoFormProps {
const data = useCollectionRecordData<DemoFormFieldType>();
const resource = useDataBlockResource();
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(data);
}, [data, form]);
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.update({
filterByTk: data.id,
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
initialValues: data,
preserve: true,
onFinish,
form,
};
}
const useFormBlockDecoratorProps: UseDataBlockProps<'CollectionRecord'> = () => {
const record = useCollectionRecordData();
return {
record,
};
};
const collection = 'users';
const action = 'get';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-use-decorator-props': 'useFormBlockDecoratorProps',
'x-decorator-props': {
collection: collection,
action: action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const recordData = {
id: 1,
username: 'Bamboo',
age: 18,
};
const Demo = () => {
return (
<>
<CollectionRecordProvider record={recordData}>
<SchemaComponent schema={schema}></SchemaComponent>
</CollectionRecordProvider>
</>
);
};
const mocks = {
[`${collection}:update`]: function (config) {
console.log('config.data', config.data);
return {
data: 'ok',
};
},
};
const Root = createApp(
Demo,
{
components: { DemoForm },
scopes: { useDemoFormProps, useFormBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -1,59 +0,0 @@
import React from 'react';
import { Table, TableProps } from 'antd';
import { SchemaComponent, useDataBlockRequest, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@nocobase/schema';
import { createApp } from '../../../data-source/demos/createApp';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: 'users',
action: 'list',
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps', // 动态 table 属性
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequest<any[]>();
return {
loading,
dataSource: data?.data || [],
columns: [
{
title: 'UserName',
dataIndex: 'username',
},
{
title: 'NickName',
dataIndex: 'nickname',
},
{
title: 'Email',
dataIndex: 'email',
},
],
};
}
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const Root = createApp(Demo, {
components: { MyTable },
scopes: { useTableProps },
});
export default Root;

View File

@ -1,162 +0,0 @@
import React, { useMemo } from 'react';
import {
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
SchemaSettings,
useDataBlockRequest,
useCollectionManager,
useCollection,
useCompile,
useDataBlockProps,
useDataBlock,
useSchemaInitializer,
useSchemaInitializerRender,
withDynamicSchemaProps,
} from '@nocobase/client';
import { createApp } from '../../../data-source/demos/createApp';
import { Table, TableProps } from 'antd';
import { ISchema } from '@nocobase/schema';
import { observer, useFieldSchema } from '@nocobase/schema';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-component': 'Page',
'x-initializer': 'MyInitializer',
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { tableProps } = useDataBlockProps();
const { data, loading } = useDataBlockRequest<any[]>();
const compile = useCompile();
const collection = useCollection();
const columns = useMemo(() => {
return collection.getFields().map((field) => {
return {
title: compile(field.uiSchema?.title || field.name),
dataIndex: field.name,
};
});
}, [collection, compile]);
return {
...tableProps,
loading,
dataSource: data?.data || [],
columns,
};
}
const MyTableSettings = new SchemaSettings({
name: 'MyTableSettings',
items: [
{
name: 'bordered',
type: 'switch',
useComponentProps() {
const { props: blockSettingsProps, dn } = useDataBlock();
return {
title: 'Bordered',
checked: !!blockSettingsProps.tableProps?.bordered,
onChange: (checked) => {
// 修改 schema
dn.deepMerge({ 'x-decorator-props': { tableProps: { bordered: checked } } });
},
};
},
},
],
});
const TableDataBlockInitializer = () => {
const { insert } = useSchemaInitializer();
const handleClick = ({ item }) => {
insert({
type: 'void',
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: item.value,
action: 'list',
tableProps: {
bordered: true, // 用于设置是否显示边框
},
},
'x-settings': 'MyTableSettings',
'x-component': 'CardItem',
'x-toolbar-props': {
draggable: false,
},
properties: {
table: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps', // 动态 table 属性
},
},
});
};
const compile = useCompile();
const collectionManager = useCollectionManager();
const collectionMenuItems = useMemo(
() =>
collectionManager.getCollections().map((collection) => {
return {
label: compile(collection.getOption('title')),
value: collection.getOption('name'),
};
}),
[collectionManager, compile],
);
return <SchemaInitializerItem title={'Table'} items={collectionMenuItems} onClick={handleClick} />;
};
const myInitializer = new SchemaInitializer({
name: 'MyInitializer',
title: 'Add Block',
// 插入位置
insertPosition: 'beforeEnd',
items: [
{
name: 'table',
Component: TableDataBlockInitializer,
},
],
});
const AddBlockButton = observer(
() => {
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer']);
return render();
},
{ displayName: 'AddBlockButton' },
);
const Page = observer(
(props) => {
return (
<div>
{props.children}
<AddBlockButton />
</div>
);
},
{ displayName: 'Page' },
);
const Root = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const App = createApp(Root, {
components: { MyTable, Page },
scopes: { useTableProps },
schemaSettings: [MyTableSettings],
schemaInitializers: [myInitializer],
});
export default App;

View File

@ -1,99 +0,0 @@
import React from 'react';
import {
SchemaComponent,
SchemaSettings,
useDataBlockRequest,
useDataBlock,
withDynamicSchemaProps,
} from '@nocobase/client';
import { createApp } from '../../../data-source/demos/createApp';
import { Table, TableProps } from 'antd';
import { ISchema } from '@nocobase/schema';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: 'users',
action: 'list',
bordered: true, // 用于设置是否显示边框
},
'x-settings': 'MyTableSettings',
'x-component': 'CardItem',
'x-toolbar-props': {
draggable: false,
},
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps', // 动态 table 属性
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
// 获取
const { props } = useDataBlock();
const { bordered } = props;
const { data, loading } = useDataBlockRequest<any[]>();
return {
bordered,
loading,
dataSource: data?.data || [],
columns: [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
],
};
}
const MyTableSettings = new SchemaSettings({
name: 'MyTableSettings',
items: [
{
name: 'bordered',
type: 'switch',
useComponentProps() {
// 获取
const { props: blockSettingsProps, dn } = useDataBlock();
const { bordered } = blockSettingsProps;
return {
title: 'Bordered',
checked: bordered,
onChange: (checked) => {
// 修改
dn.deepMerge({ 'x-decorator-props': { bordered: checked } });
},
};
},
},
],
});
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const Root = createApp(Demo, {
components: { MyTable },
scopes: { useTableProps },
schemaSettings: [MyTableSettings],
});
export default Root;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,13 +0,0 @@
# AssociationProvider
用于传递关联数据字段以及对应的数据表信息,等同于 `CollectionFieldProvider` + `CollectionProvider`
```tsx | pure
const AssociationProvider = <CollectionFieldProvider name={fieldName}>
<CollectionProvider name={collectionManager.getCollectionName(fieldName)}>
{children}
</CollectionProvider>
</CollectionFieldProvider>
```
关于关系字段的更多信息,请参考 [关系字段](https://docs.nocobase.com/development/server/collections/association-fields) 以及 [CollectionField](/core/data-source/collection-field)。

View File

@ -1,171 +0,0 @@
# CollectionFieldInterfaceManager
主要是用来管理 [CollectionFieldInterface](./collection-field-interface.md) 和 [CollectionFieldInterfaceGroups](#collectionfieldinterfacegroups),其被 [DataSourceManager](./data-source-manager) 管理。
## CollectionFieldInterfaceGroups
CollectionFieldInterfaceGroups 是用来对数据表字段进行分组的。
![Field Groups](./images/field-groups.png)
## 实例方法
### field interface
#### addFieldInterfaces()
添加 field interface。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
addFieldInterfaces(fieldInterfaces: CollectionFieldInterface[]): void
}
```
- 示例
```tsx | pure
class CheckboxFieldInterface extends CollectionFieldInterface {
name = 'checkbox';
type = 'object';
group = 'choices';
title = '{{t("Checkbox")}}';
// ...
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaces([CheckboxFieldInterface]);
// or
this.app.dataSourceManager.addFieldInterfaces([CheckboxFieldInterface]);
}
}
```
#### getFieldInterface()
获取 field interface。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
getFieldInterface<T extends CollectionFieldInterface>(name: string): T;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const fieldInterface = this.app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface('checkbox'); // checkboxFieldInterface
}
}
```
#### getFieldInterfaces()
获取所有的 field interface。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
getFieldInterfaces(): CollectionFieldInterface[];
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const fieldInterfaces = this.app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterfaces();
}
}
```
### field interface group
#### addFieldInterfaceGroups()
添加 field interface group。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
addFieldInterfaceGroups(fieldGroups: Record<string, { label: string; order?: number }>): void;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceGroups({
'test': {
label: 'Test',
order: 1,
}
});
// or
this.app.dataSourceManager.addFieldInterfaceGroups({
'test': {
label: 'Test',
order: 1,
}
});
}
}
```
#### getFieldInterfaceGroups()
获取所有的 field interface group。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
getFieldInterfaceGroups(): Record<string, { label: string; order?: number }>;
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const fieldInterfaceGroups = this.app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterfaceGroups(); // { 'test': { label: 'Test', order: 1 } }
}
}
```
#### getFieldInterfaceGroup()
获取 field interface group。
- 类型
```tsx | pure
class CollectionFieldInterfaceManager {
getFieldInterfaceGroup(name: string): { label: string; order?: number };
}
```
- 示例
```tsx | pure
class MyPlugin extends Plugin {
async load() {
const fieldInterfaceGroup = this.app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterfaceGroup('test'); // { label: 'Test', order: 1 }
}
}
```

View File

@ -1,94 +0,0 @@
# CollectionFieldInterface
用于创建数据字段。
![](./images/collection-field-interface.png)
```ts
class CollectionFieldInterface {
app: Application;
collectionManager: CollectionManager;
name: string;
group: string;
title?: string;
description?: string;
order?: number;
default?: {
type: string;
uiSchema?: ISchema;
[key: string]: any;
};
sortable?: boolean;
availableTypes?: string[];
hasDefaultValue?: boolean;
isAssociation?: boolean;
operators?: any[];
filterable?: {
operators?: any[];
children?: any[];
[key: string]: any;
};
titleUsable?: boolean;
validateSchema(fieldSchema: ISchema): Record<string, ISchema>
usePathOptions(field: CollectionFieldOptions): any
schemaInitialize(schema: ISchema, data: any): void
getOption<K extends keyof IField>(key: K): CollectionFieldInterfaceOptions[K]
getOptions(): CollectionFieldInterfaceOptions;
setOptions(options: CollectionFieldInterfaceOptions): void;
}
```
其需要结合 [CollectionManager](./collection-field-interface-manager.md) 使用。
```ts
class EmailFieldInterface extends CollectionFieldInterface {
name = 'email';
type = 'object';
group = 'basic';
order = 4;
title = '{{t("Email")}}';
sortable = true;
// ...
}
class MyPlugin extends Plugin {
load() {
this.app.dataSourceManager.addFieldInterfaces([ EmailFieldInterface ]);
}
}
```
## 实例属性
### name
唯一标识符。
### group
分组。
### title
标题。
### default
配置表单默认值字段 schema。
![](./images/collection-field-interface-form.png)
## 实例方法
### collectionFieldInterface.validateSchema(fieldSchema)
### collectionFieldInterface.usePathOptions(field: CollectionFieldOptions)
### collectionFieldInterface.schemaInitialize(schema: ISchema, data: any)

View File

@ -1,164 +0,0 @@
# CollectionField
NocoBase 将字段的 schema 分为 2 部分,一部分在 schema 中,一部分在 collection 中。例如:
```tsx | pure
const schema = {
properties: {
username: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
}
}
const collection = {
fields: [
{
type: 'string',
name: 'username',
interface: 'input',
uiSchema: {
title: 'UserName',
type: 'string',
'x-component': 'Input',
required: true,
description: 'description1',
} as ISchema,
}
],
}
```
两者通过 `name: username` 联系起来,`CollectionField` 会自动读取 schema `name` 属性,并根据 `name` 属性查找 collection 中对应的 `uiSchema` 属性,然后拼接到 schema 中,进行渲染。
这样做的好处是,对于同一个字段创建的内容,可以在不同的地方共享同一个 schema当 schema 变化时,只需要修改一处即可。比如通过上面的 `title: "UserName"` 假设变化 `title: "Name"` 则所有使用到此字段的地方都会变化。
<code src="./demos/collection-field/demo1.tsx"></code>
## CollectionFieldOptions
字段的配置项。
```ts
interface CollectionFieldOptions {
name?: any;
collectionName?: string;
sourceKey?: string;
uiSchema?: ISchema;
target?: string;
[key: string]: any;
}
```
### 普通字段和关系字段
字段有 2 种情况,一种是普通字段,一种是 [关系字段](https://docs-cn.nocobase.com/development/server/collections/association-fields)。
关系字段是指,字段的值是另一个 collection 的数据,例如 `users``roles` 两个 collection`users` 中有一个字段 `roles`,其值是 `roles` collection 的数据,那么 `roles` 就是一个关系字段。
普通字段的示例如下:
```json
{
"key": "ootprgkoawo",
"name": "email",
"type": "string",
"interface": "email",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true,
"uiSchema": {
"type": "string",
"title": "{{t(\"Email\")}}",
"x-component": "Input",
"x-validator": "email",
"required": true
}
}
```
关系字段的示例如下:
```json
{
"key": "t09bauwm0wb",
"name": "roles",
"type": "belongsToMany",
"interface": "m2m",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "roles",
"foreignKey": "userId",
"otherKey": "roleName",
"onDelete": "CASCADE",
"sourceKey": "id",
"targetKey": "name",
"through": "rolesUsers",
"uiSchema": {
"type": "array",
"title": "{{t(\"Roles\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": true,
"fieldNames": {
"label": "title",
"value": "name"
}
}
}
}
```
相对于普通字段,关系字段多了以下属性:
- xx
- xx
### 全部字段说明
- `name`:字段名称
- `collectionName`:数据表名称
- `sourceKey`:当字段为关系字段时,对应的关系字段名称。
TODO补全
## Hooks
### useCollectionField()
用于获取字段信息。
```tsx | pure
const collection = {
fields: [
{
type: 'string',
name: 'username',
interface: 'input',
uiSchema: {
title: 'UserName',
type: 'string',
'x-component': 'Input',
required: true,
description: 'description1',
} as ISchema,
}
],
}
const { uiSchema } = useCollectionField()
const required = uiSchema?.required
```
其通常在 [SchemaSettings](/core/ui-schema/schema-settings) 中使用,用来获取和修改字段的属性。
<code src="./demos/collection-field/demo2.tsx"></code>

View File

@ -1,52 +0,0 @@
# CollectionManagerProvider
用于提供 [CollectionManager](/core/data-source/collection-manager) 实例。
## 组件
- 类型
```tsx | pure
interface CollectionManagerProviderProps {
instance?: CollectionManager;
dataSource?: string;
children?: ReactNode;
}
```
- 参数详解
- `dataSource` - 数据源名称,如果为空,则会取默认数据源。
- `instance` - CollectionManager 实例,如果没有,则会取 `dataSource` 对应的 collectionManager。
- 示例
```tsx | pure
const collectionManager = new CollectionManager();
const Demo = () => {
return (
<CollectionManagerProvider dataSource='test'>
<div>...</div>
</CollectionManagerProvider>
);
};
```
## Hooks
### useCollectionManager()
用于获取 `CollectionManagerProvider` 传递的实例。
- 示例
```tsx | pure
const Demo = () => {
const collectionManager = useCollectionManager();
const collections = collectionManager.getCollections()
return <div>
<pre>{JSON.stringify(collections, null, 2)}</pre>
</div>;
};
```

View File

@ -1,175 +0,0 @@
# CollectionManager
用于管理 [Collection](./collection.md),其被 [DataSource](./data-source.md) 管理。
## 实例方法
### addCollections(collections)
添加数据表。
- 类型
```tsx | pure
class CollectionManager {
addCollections(collections: CollectionOptions[]): void
}
```
- 示例
```tsx | pure
const userCollectionOptions = {
"name": "users",
"title": "Users",
fields: [
// ...
],
};
collectionManager.addCollections([userCollectionOptions]);
```
### setCollections(collections)
重置数据表,会先移除所有数据表,然后再调用 `addCollections()` 添加数据表。
- 类型
```tsx | pure
class CollectionManager {
setCollections(collections: CollectionOptions[]): void
}
```
### reAddCollections(collections)
由于 [CollectionTemplate](./collection-template.md) 或者 [CollectionMixins](./collection-mixins.md) 的添加会影响 Collection 的实例化,所以提供了重新添加数据表的方法。
- 类型
```tsx | pure
class CollectionManager {
reAddCollections(collectionInstances: Collection[]): void
}
```
- 示例
```tsx | pure
const userCollectionInstance = collectionManager.getCollection('users');
collectionManager.reAddCollections([userCollectionInstance]);
```
### getCollections(predicate?)
获取数据表。
- 类型
```tsx | pure
class CollectionManager {
getCollections(predicate?: (collection: Collection) => boolean)
}
```
- 示例
```tsx | pure
collectionManager.getCollections(); // [ userCollection ]
collectionManager.getCollections(collection => collection.name === 'posts'); // [ postCollection ]
```
### getCollection(path)
获取数据表。
- 类型
```tsx | pure
class CollectionManager {
getCollection<Mixins = {}>(path: string): (Mixins & Collection) | undefined
}
```
- 详细解释
- `path` 参数可以是数据表名称,也可以是[关系字段](https://docs.nocobase.com/development/server/collections/association-fields)路径。
- `path: 'users'`: 获取 `users` 数据表
- `path: 'users.posts'`: 获取 `users` 数据表的 `posts` 关联字段对应的数据表,即 `postCollection`
- 示例
```tsx | pure
collectionManager.getCollection('users'); // userCollection
collectionManager.getCollection('users.posts'); // postCollection
collectionManager.getCollection('users.profileId'); // profileCollection
```
结合 Mixin 使用:
```tsx | pure
const collection = collectionManager.getCollection<TestMixin>('users');
const collection = collectionManager.getCollection<TestMixin & TestMixin2>('users');
```
### getCollectionFields(collectionName)
获取数据表字段列表。
- 类型
```tsx | pure
class CollectionManager {
getCollectionFields(collectionName: string): CollectionFieldOptions[];
}
```
- 示例
```tsx | pure
collectionManager.getCollectionFields('users'); // [ { name: 'username', type: 'string', title: 'Username', .. }, { name: 'password', type: 'password', title: 'Password', .. } ]
```
### getCollectionName(path)
获取数据表名称。
- 类型
```tsx | pure
class CollectionManager {
getCollectionName(path: string: GetCollectionOptions): string | undefined;
}
```
- 示例
```tsx | pure
collectionManager.getCollectionName('users'); // 'users'
collectionManager.getCollectionName('users.profiles'); // 'profiles'
```
### getCollectionField(path)
获取数据表字段。
- 类型
```tsx | pure
class CollectionManager {
getCollectionField(path: string: GetCollectionOptions): CollectionFieldOptions | undefined;
}
```
- 示例
```tsx | pure
collectionManager.getCollectionField('users.username'); // { name: 'username', type: 'string', title: 'Username', .. }
collectionManager.getCollectionField('users.roles.name'); // 获取 roles 关联字段对应的 roles 表中的 name 字段
```

View File

@ -1,57 +0,0 @@
## Collection Mixins
Collection Mixins 是为扩展 Collection 类提供的一种机制,可以通过 `dataSourceManager.addCollectionMixins()` 添加 Collection Mixins。
### 定义和注册
```tsx | pure
import { Collection, Plugin } from '@nocobase/client';
class TestMixin extends Collection {
test() {
const { name } = this.options;
return 'test '+ name;
}
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addCollectionMixins([TestMixin]);
}
}
```
### 使用
- 使用方式1`CollectionManager` 实例上调用 `getCollection()` 获取指定 `Collection` 实例。
```tsx | pure
const Demo = () => {
const cm = useCollectionManager();
const userCollection = cm.getCollection<TestMixin>('users');
userCollection.test(); // 'test users'
}
```
- 使用方式2调用 `useCollection()` 获取当前上下文的数据表信息。
```tsx | pure
const Demo = () => {
const collection = useCollection<TestMixin>();
collection.test(); // 'test users'
}
```
### 多个 Mixins 的使用
如果添加了 Mixins可通过如下方式获得类型提示
```tsx | pure
const Demo = () => {
const collection = useCollection<TestMixin & Test2Mixin>();
}
```
<code src='./demos/data-source-manager/mixins.tsx'></code>

View File

@ -1,63 +0,0 @@
# CollectionProvider
用于提供 [Collection](/core/data-source/collction) 实例。
## 组件
### CollectionProvider
- 类型
```tsx | pure
interface CollectionProviderProps {
name: string;
dataSource?: string;
children?: ReactNode;
}
```
- 详解
组件会根据 `name` 去 [CollectionManager](/core/data-source/collection-manager) 中查询数据表信息,如果查询不到,则会不进行渲染。
`dataSource` 用于指定数据表所在的[命名空间](/core/data-source/collection-manager#datasource),如果不指定,则默认命名空间。
- 示例
```tsx | pure
import { CollectionProvider } from '@nocobase/client';
const MyComponent = () => {
return (
<CollectionProvider name="users">
<div>...</div>
</CollectionProvider>
)
}
```
## Hooks
### useCollection()
用于获取 `CollectionProvider` 传递的 `Collection` 实例。
```tsx | pure
const collection = useCollection()
console.log(collection instanceof Collection) // true
console.log(collection);
```
结合 Mixin 使用:
```tsx | pure
const collection = useCollection<TestMixin>()
const collection = useCollection<TestMixin & TestMixin2>()
```
## 示例
<code src="./demos/collection/demo1.tsx"></code>

View File

@ -1,86 +0,0 @@
# CollectionTemplateManager
用于管理 [CollectionTemplate](./collection-template),其被 [DataSourceManager](./data-source-manager) 管理。
## 实例方法
### addCollectionTemplates()
用于添加 collection template。
- 类型
```tsx | pure
class CollectionTemplateManager {
addCollectionTemplates(templates: CollectionTemplate[]): void;
}
```
- 示例
```tsx | pure
class SqlCollectionTemplate extends CollectionTemplate {
name = 'sql';
type = 'object';
title = '{{t("SQL collection")}}';
configurableProperties = {
// ...
}
}
class TreeCollectionTemplate extends CollectionTemplate {
name = 'tree';
type = 'object';
title = '{{t("Tree collection")}}';
configurableProperties = {
// ...
}
}
class MyPlugin extends Plugin {
async load() {
this.dataSourceManager.collectionTemplateManager.addCollectionTemplates([ SqlCollectionTemplate, TreeCollectionTemplate ]);
// or
this.dataSourceManager.addCollectionTemplates([ SqlCollectionTemplate, TreeCollectionTemplate ]);
}
}
```
### getCollectionTemplate()
用于获取 collection template。
- 类型
```tsx | pure
class CollectionTemplateManager {
getCollectionTemplate(name: string): CollectionTemplate;
}
```
- 示例
```tsx | pure
collectionManager.getCollectionTemplate(); // generalCollectionTemplate
collectionManager.getCollectionTemplate('tree'); // treeCollectionTemplate
```
### getCollectionTemplates()
用于获取所有 collection templates。
- 类型
```tsx | pure
class CollectionTemplateManager {
getCollectionTemplates(): CollectionTemplate[];
}
```
- 示例
```tsx | pure
collectionManager.getCollectionTemplates(); // [ generalCollectionTemplate, treeCollectionTemplate, sqlCollectionTemplate ]
```

View File

@ -1,249 +0,0 @@
# CollectionTemplate
用于创建数据表的模板。
![](./images/collection-template.png)
```ts
interface AvailableFieldInterfacesInclude {
include?: any[];
}
interface AvailableFieldInterfacesExclude {
exclude?: any[];
}
interface CollectionTemplateDefaultOptions {
/**
* 自动生成 id
* @default true
* */
autoGenId?: boolean;
/** 创建人 */
createdBy?: boolean;
/** 最后更新人 */
updatedBy?: boolean;
/** 创建日期 */
createdAt?: boolean;
/** 更新日期 */
updatedAt?: boolean;
/** 可排序 */
sortable?: boolean;
/* 树结构 */
tree?: string;
/* 日志 */
logging?: boolean;
/** 继承 */
inherits?: string | string[];
/* 字段列表 */
fields?: CollectionOptions['fields'];
}
class CollectionTemplate {
app: Application;
collectionManager: CollectionManager;
name: string;
Collection?: typeof Collection;
transform?: (collection: CollectionOptions, app: Application) => CollectionOptions;
title?: string;
color?: string;
/** 排序 */
order?: number;
/** 默认配置 */
default?: CollectionTemplateDefaultOptions;
events?: any;
/** UI 可配置的 CollectionOptions 参数(添加或编辑的 Collection 表单的字段) */
configurableProperties?: Record<string, ISchema>;
/** 当前模板可用的字段类型 */
availableFieldInterfaces?: AvailableFieldInterfacesInclude | AvailableFieldInterfacesExclude;
/** 是否分割线 */
divider?: boolean;
/** 模板描述 */
description?: string;
/**配置字段中的操作按钮 */
configureActions?: Record<string, ISchema>;
//是否禁止删除字段
forbidDeletion?: boolean;
}
```
其需要结合 [CollectionManager](./collection-template-manager.md) 使用。
```ts
import { Plugin, Collection, CollectionTemplate } from '@nocobase/client';
class SqlCollection extends Collection {
otherMethods() {
// ...
}
}
class SqlCollectionTemplate extends CollectionTemplate {
name = 'sql';
Collection = SqlCollection; // 自定义的数据表类
title = '{{t("SQL collection")}}';
order = 4;
color = 'yellow';
default = {
fields: [],
};
configurableProperties = {
// ...
}
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionTemplateManager.addCollectionTemplates([ SqlCollectionTemplate ]);
// or
this.app.dataSourceManager.addCollectionTemplates([ SqlCollectionTemplate ]);
}
}
```
## 实例属性
### name
模板的唯一标识符。
### Collection
模板对应的数据表类。
在创建数据表后Collection 会有 [template 字段](/core/data-source/collection#collectionoptions),用于标识该数据表是由哪个模板创建的。
当通过 `collectionManager.addCollections()` 添加数据表对象时,会先读取 `collection.template` 字段,然后通过 `collectionManager.getCollectionTemplate(collection.template)` 获取到 `collectionTemplate`
读取 `collectionTemplate.Collection` 字段,并通过 `new collectionTemplate.Collection(collection)` 创建对应的实例。
如果不传递 `Collection`,则会通过 `new Collection(collection)` 创建对应的实例。
```ts
class SqlCollection extends Collection {
otherMethods() {
// ...
}
}
class SqlCollectionTemplate extends CollectionTemplate {
name = 'sql';
Collection = SqlCollection; // 自定义的数据表类
// ...
}
const userCollection = {
name: 'users',
template: 'sql',
// ...
}
// 内部会调用 new SqlCollection(userCollection)
```
### title
模板的标题。
### color
模板的颜色。
### order
模板的排序。
### events
- `beforeSubmit`:提交前触发
### configurableProperties
表单配置项。
![](./images//collection-template-form.png)
```ts
class SqlCollectionTemplate extends CollectionTemplate {
name = 'sql',
// ...
configurableProperties = {
title: {
type: 'string',
title: '{{ t("Collection display name") }}',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '{{t("Collection name")}}',
required: true,
'x-disabled': '{{ !createOnly }}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'uid',
description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
},
// ...
},
}
```
### default
表单默认值。
## 实例方法
### collectionTemplate.transform(collection)
collection 创建后,会调用该方法,用于对 collection 进行转换。
## Utils
### getConfigurableProperties()
用于获取内置的配置项字段。
- 类型
```tsx | pure
export type DefaultConfigurableKeys =
| 'name'
| 'title'
| 'inherits'
| 'category'
| 'autoGenId'
| 'createdBy'
| 'updatedBy'
| 'createdAt'
| 'updatedAt'
| 'sortable'
| 'description'
| 'moreOptions';
const getConfigurableProperties: (...keys: DefaultConfigurableKeys[]) => Record<DefaultConfigurableKeys, any>
```
- 示例
```tsx | pure
import { getConfigurableProperties } from '@nocobase/client';
const sqlCollectionTemplate = new CollectionTemplate({
name: 'sql',
// ...
configurableProperties: {
...getConfigurableProperties('name', 'title', 'description'),
// ...
},
});
```

View File

@ -1,293 +0,0 @@
# Collection
Collection 数据表类,其被 [CollectionManager](/core/data-source/collection-manager) 管理。
## 类型
```tsx | pure
interface CollectionOptions {
name: string;
title?: string;
fields?: FieldOptions[];
// ....
}
class Collection {
app: Application;
collectionManager: CollectionManager;
constructor(options: CollectionOptions) {}
name: string;
primaryKey: string;
titleField: string;
getOptions(): CollectionOptions;
setOptions(options: CollectionOptions): void;
getOption<K extends keyof CollectionOptions>(key: K): CollectionOptions[K];
getFields(predicate?: CollectionFieldOptions | ((collection: CollectionFieldOptions) => boolean) | keyof CollectionFieldOptions): any[]
getField(name: SchemaKey): CollectionFieldOptions
hasField(name: SchemaKey): boolean;
}
```
```tsx | pure
const usersCollection = new Collection({
name: 'users',
title: 'Users',
fields: [
{
type: 'string',
name: 'username',
},
{
type: 'integer',
name: 'age',
},
],
});
```
## CollectionOptions
```tsx | pure
export interface CollectionOptions {
name: string;
title?: string;
dataSource?: string;
/**
* Used for @nocobase/plugin-duplicator
* @see packages/core/database/src/collection-group-manager.tss
*
* @prop {'required' | 'optional' | 'skip'} dumpable - Determine whether the collection is dumped
* @prop {string[] | string} [with] - Collections dumped with this collection
* @prop {any} [delayRestore] - A function to execute after all collections are restored
*/
duplicator?:
| dumpable
| {
dumpable: dumpable;
with?: string[] | string;
delayRestore?: any;
};
tableName?: string;
inherits?: string[] | string;
inherit?: string;
key?: string;
viewName?: string;
writableView?: boolean;
filterTargetKey?: string;
fields?: CollectionFieldOptions[];
model?: any;
repository?: any;
sortable?: CollectionSortable;
/**
* @default true
*/
autoGenId?: boolean;
/**
* @default 'options'
*/
magicAttribute?: string;
tree?: string;
template?: string;
isThrough?: boolean;
autoCreate?: boolean;
resource?: string;
collectionName?: string;
sourceKey?: string;
uiSchema?: any;
[key: string]: any;
}
```
- name: Collection 的标识,必须唯一。
- title: Collection 的标题,用于显示。
- fields: 字段列表,详细说明请查看 [CollectionField](/core/data-source/collection-field)
- template: 模板标识,用于标识该 Collection 是由哪个模板创建的,详细说明请查看 [CollectionTemplate](/core/data-source/collection-template)
- dataSource: 数据源标识,用于标识该 Collection 是由哪个数据源创建的,详细说明请查看 [CollectionDataSource](/core/data-source/collection-manager#datasource)
- duplicator
- tableName
- inherits
- viewName
- writableView
- filterTargetKey
- model
- repository
## 实例属性
### collection.collectionManager
[CollectionManager](/core/data-source/collection-manager) 的实例。
### collection.titleFieldName
标题字段的 name 属性。
### 其他属性
其他属性同 [CollectionOptions](/core/data-source/collection#collectionoptions)。
## 实例方法
### collection.getOptions()
获取 collection 的所有配置项。
- 类型
```tsx | pure
class Collection {
getOptions(): CollectionOptions;
}
```
- 示例
```tsx | pure
const usersCollection = new Collection({
name: 'users',
title: 'Users',
fields: [
// ...
],
});
console.log(usersCollection.getOptions()); // { name: 'users', title: 'Users', fields: [ ] }
```
### collection.setOptions(options)
设置 collection 的配置项,最终会和默认配置项进行合并。
- 类型
```tsx | pure
class Collection {
setOptions(options: CollectionOptions): void;
}
```
- 示例
```tsx | pure
collection.setOptions({
name: 'users',
title: 'Users',
fields: [
// ...
],
});
```
### collection.getOption(key)
获取 collection 的单个配置项。
- 类型
```tsx | pure
class Collection {
getOption<K extends keyof CollectionOptions>(key: K): CollectionOptions[K];
}
```
- 示例
```tsx | pure
collection.getOption('name'); // 'users'
collection.getOption('title'); // 'Users'
```
### collection.getFields(predicate?)
获取 collection 的字段列表。
- 类型
```tsx | pure
class Collection {
getFields(predicate?: CollectionFieldOptions | ((collection: CollectionFieldOptions) => boolean) | keyof CollectionFieldOptions): any[]
}
```
- 详解
- predicate
- 类型
- `CollectionFieldOptions`
- `(collection: CollectionFieldOptions) => boolean`
- `keyof CollectionFieldOptions`
- 说明
- 如果传递了 `predicate`,则返回符合条件的字段列表
- 如果没有传递 `predicate`,则返回所有字段列表
`predicate` 的使用可看参考 [lodash.filter](https://www.lodashjs.com/docs/lodash.filter)。
- 示例
```tsx | pure
collection.getFields(); // [{ name: 'username', type: 'string', primaryKey: true }, { name: 'age', type: 'integer' }]
collection.getFields({ name: 'age' }); // [{ name: 'age', type: 'integer' }]
collection.getFields('primaryKey'); // [{ name: 'username', type: 'string', primaryKey: true }]
collection.getFields(field => field.type === 'string'); // [{ name: 'name', type: 'string' }]
```
### collection.getField(name)
获取 collection 的单个字段。
- 类型
```tsx | pure
class Collection {
getField(name: SchemaKey): CollectionFieldOptions
}
```
- 示例
```tsx | pure
collection.getField('username'); // { name: 'username', type: 'string', primaryKey: true }
```
### collection.hasField(name)
判断 collection 是否存在某个字段。
- 类型
```tsx | pure
class Collection {
hasField(name: SchemaKey): boolean;
}
```
- 示例
```tsx | pure
collection.hasField('username'); // true
collection.hasField('name'); // false
```

View File

@ -1,25 +0,0 @@
# DataSourceManagerProvider
用于提供 `DataSourceManager` 实例。
## Hooks
### useDataSourceManager()
获取 `DataSourceManager` 实例。
- 类型
```tsx | pure
function useDataSourceManager(): DataSourceManager;
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
dataSourceManager.removeDataSources(['my-data-source']);
}
```

View File

@ -1,334 +0,0 @@
# DataSourceManager
Nocobase 数据源前端体系包含如下内容:
- [DataSourceManager](./data-source-manager.md)
- [DataSource](./data-source)
- [CollectionManager](./collection-manager)
- [Collection](./collection)
- [Field](./collection-field)
- [CollectionTemplateManager](./collection-template-manager)
- [CollectionTemplate](./collection-template)
- [CollectionFieldInterfaceManager](./collection-field-interface-manager)
- [CollectionFieldInterface](./collection-field-interface)
- [CollectionMixins](./collection-mixins)
## 实例属性
- collectionTemplateManager
用于管理 `CollectionTemplate` 实例。
```tsx | pure
import { Plugin, CollectionTemplate } from '@nocobase/client';
class MyCollectionTemplate extends CollectionTemplate {
name = 'my-collection-template';
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionTemplateManager.addCollectionTemplates([MyCollectionTemplate])
}
}
```
详细请参考:[CollectionTemplateManager](./collection-template-manager)
- collectionFieldInterfaceManager
用于管理 `CollectionFieldInterface` 实例。
```tsx | pure
import { Plugin, CollectionFieldInterface } from '@nocobase/client';
class MyCollectionFieldInterface extends CollectionFieldInterface {
name = 'my-collection-field-interface';
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaces([MyCollectionFieldInterface])
}
}
```
详细请参考:[CollectionTemplateManager](./collection-template-manager)
## 实例方法
### addCollectionTemplates()
`CollectionTemplateManager` 的快捷方法,用于添加 `CollectionTemplate`
- 类型
```tsx | pure
class DataSourceManager {
addCollectionTemplates(templates: CollectionTemplate[]): void;
}
```
- 示例
```tsx | pure
import { Plugin, CollectionTemplate } from '@nocobase/client';
class MyCollectionTemplate extends CollectionTemplate {
name = 'my-collection-template';
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionTemplateManager.addCollectionTemplates([MyCollectionTemplate])
}
}
```
更多详细请参考:[CollectionTemplateManager](./collection-template-manager)
### addFieldInterfaces()
`CollectionFieldInterfaceManager` 的快捷方法,用于添加 `CollectionFieldInterface`
- 类型
```tsx | pure
class DataSourceManager {
addFieldInterfaces(fieldInterfaces: CollectionFieldInterface[]): void;
}
```
- 示例
```tsx | pure
import { Plugin, CollectionFieldInterface } from '@nocobase/client';
class MyCollectionFieldInterface extends CollectionFieldInterface {
name = 'my-collection-field-interface';
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaces([MyCollectionFieldInterface])
}
}
```
更多详细请参考:[CollectionFieldInterfaceManager](./collection-field-interface-manager)
### addCollectionMixins()
用于添加 `Collection` 的 Mixins。
- 类型
```tsx | pure
class DataSourceManager {
addCollectionMixins(mixins: (typeof Collection)[]): void;
}
```
- 示例
```tsx | pure
import { Plugin, Collection } from '@nocobase/client';
class MyCollectionMixin extends Collection {
otherMethod() {
console.log('otherMethod');
}
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addCollectionMixins([MyCollectionMixin])
}
}
const MyComponent = () => {
const collection = useCollection<MyCollectionMixin>();
collection.otherMethod();
}
```
更多详细请参考:[CollectionMixins](./collection-mixins)
### addDataSource()
用于添加 `DataSource`
- 类型
```tsx | pure
class DataSourceManager {
addDataSource(DataSource: DataSource, options: DataSourceOptions): void;
}
```
- 示例
```tsx | pure
import { Plugin, DataSource, DataSourceOptions } from '@nocobase/client';
class MyDataSource extends DataSource {
async getDataSource() {
return {
status: 'loaded',
collections: [{ name: 'users' }]
}
}
}
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addDataSource(MyDataSource, {
key: 'my-data-source',
displayName: 'My Data Source',
})
}
}
```
更多详细请参考:[DataSource](./data-source)
### removeDataSources()
移除 `DataSource`
- 类型
```tsx | pure
class DataSourceManager {
removeDataSources(keys: string[]): void;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
dataSourceManager.removeDataSources(['my-data-source']);
}
```
### getDataSources()
获取全部 `DataSource` 实例列表。
- 类型
```tsx | pure
class DataSourceManager {
getDataSources(): DataSource[];
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
const dataSources = dataSourceManager.getDataSources();
return (
<div>
{dataSources.map(dataSource => (
<div key={dataSource.key}>{dataSource.displayName}</div>
))}
</div>
)
}
```
### getDataSource()
获取 `DataSource` 实例。
- 类型
```tsx | pure
class DataSourceManager {
getDataSource(key: string): DataSource;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
const dataSource = dataSourceManager.getDataSource('my-data-source');
return (
<div>
{dataSource.displayName}
</div>
)
}
```
### getAllCollections()
获取所有 DataSource 的所有 Collection 实例。
- 类型
```tsx | pure
class DataSourceManager {
getAllCollections(options?: {
filterCollection?: (collection: Collection) => boolean;
filterDataSource?: (dataSource: DataSource) => boolean;
}): (DataSourceOptions & { collections: Collection[] })[];
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
const collections = dataSourceManager.getAllCollections();
return (
<div>
{collections.map(({ key, displayName, collections }) => (
<div key={key}>
<h3>{displayName}</h3>
<ul>
{collections.map(collection => (
<li key={collection.name}>{collection.name}</li>
))}
</ul>
</div>
))}
</div>
)
}
```
### reload()
重载所有 `DataSource`
- 类型
```tsx | pure
class DataSourceManager {
reload(): Promise<void>;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceManager = useDataSourceManager();
dataSourceManager.reload();
}
```

View File

@ -1,66 +0,0 @@
# DataSourceProvider
用于提供 `DataSource` 的实例。
## 组件
- 类型
```tsx | pure
interface DataSourceProviderProps {
dataSource?: string;
children?: ReactNode;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
return (
<DataSourceProvider dataSource="my-data-source">
<MyChildComponent />
</DataSourceProvider>
);
}
```
## Hooks
### useDataSource()
获取 `DataSource` 实例。
- 类型
```tsx | pure
function useDataSource(): DataSource;
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSource = useDataSource();
return <div>{dataSource.displayName}</div>
}
```
### useDataSourceKey()
获取 `DataSource` 的 key。
- 类型
```tsx | pure
function useDataSourceKey(): string;
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSourceKey = useDataSourceKey();
return <div>{dataSourceKey}</div>
}
```

View File

@ -1,176 +0,0 @@
# DataSource
主要是用于获取数据源和数据源的数据表结构列表,并在获取后交给 [CollectionManager](./collection-manager.md) 进行管理,其被 [DataSourceManager](./data-source-manager.md) 管理。
## 数据源定义
数据源的定义需要继承 `DataSource` 类,并实现 `getDataSource` 方法,当调用 `reload` 方法时,会调用 `getDataSource` 方法获取数据表结构。
```tsx | pure
import { DataSource } from '@nocobase/client';
class MyDataSource extends DataSource {
async getDataSource() {
return this.app.request({
url: 'xxx',
method: 'GET',
});
}
}
```
### 数据源注册
数据源需要在插件中注册,通过 `DataSourceManager``addDataSource` 方法进行注册。
初始化添加的时候 `collections` 可以为空,当调用 `reload` 方法时,会调用 `getDataSource` 方法获取数据表结构。
```tsx | pure
import { Plugin, DataSource, DataSourceOptions } from '@nocobase/client';
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addDataSource(MyDataSource, {
key: 'my-data-source',
displayName: 'My Data Source',
status: 'loaded',
collections: [
{
name: 'users',
fields: [
{
name: 'name',
type: 'string',
},
],
},
],
});
}
}
```
- `key`:数据源的唯一标识
- `displayName`:数据源的显示名称
- `status`:数据源的状态,`loaded` 表示已加载,`loading` 表示正在加载,`loading-failed` 表示加载失败
- `collections`:数据表结构
- `errorMessage`:错误信息
## 实例方法
### getDataSource()
用于获取数据源信息,其会被 `reload` 方法内部调用,外部不需要调用。
### addReloadCallback()
用于添加数据源加载完成后的回调函数。
- 类型
```tsx | pure
type LoadCallback = (collections: CollectionOptions[]) => void;
class DataSource {
addReloadCallback(callback: LoadCallback): void;
}
```
### removeReloadCallback()
用于移除数据源加载完成后的回调函数。
- 类型
```tsx | pure
type LoadCallback = (collections: CollectionOptions[]) => void;
class DataSource {
removeReloadCallback(callback: LoadCallback): void;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSource = useDataSource();
useEffect(() => {
const callback = (collections) => {
console.log(collections);
};
dataSource.addReloadCallback(callback);
return () => {
dataSource.removeReloadCallback(callback);
};
}, []);
};
```
### reload()
用于重新加载数据源,会调用 `getDataSource` 方法获取数据表结构,并内部调用 `addReloadCallback` 添加的回调函数。
- 类型
```tsx | pure
class DataSource {
reload(): Promise<void>;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSource = useDataSource();
const handleClick = async () => {
await dataSource.reload();
};
};
```
### getOptions()
获取数据源的配置信息。
- 类型
```tsx | pure
interface DataSourceOptions {
key: string;
displayName: string;
collections?: CollectionOptions[];
errorMessage?: string;
status?: 'loaded' | 'loading-failed' | 'loading';
}
class DataSource {
getOptions(): DataSourceOptions;
}
```
### getOption()
获取数据源的配置信息。
- 类型
```tsx | pure
class DataSource {
getOption(key: string): any;
}
```
- 示例
```tsx | pure
const MyComponent = () => {
const dataSource = useDataSource();
const handleClick = async () => {
console.log(dataSource.getOption('key'));
};
};
```

View File

@ -1,136 +0,0 @@
import { ISchema, observer, useForm } from '@nocobase/schema';
import {
Action,
Application,
CollectionProvider,
Form,
SchemaComponent,
FormItem,
InheritanceCollectionMixin,
Input,
SchemaComponentOptions,
} from '@nocobase/client';
import React from 'react';
const collection = {
name: 'tests',
fields: [
{
type: 'string',
name: 'title1',
interface: 'input',
uiSchema: {
title: 'Title1',
type: 'string',
'x-component': 'Input',
required: true,
description: 'description1',
} as ISchema,
},
{
type: 'string',
name: 'title2',
interface: 'input',
uiSchema: {
title: 'Title2',
type: 'string',
'x-component': 'Input',
description: 'description',
default: 'ttt',
},
},
{
type: 'string',
name: 'title3',
},
],
};
const Root = observer(() => {
const schema: ISchema = {
type: 'object',
properties: {
form1: {
type: 'void',
'x-component': 'Form',
properties: {
// 字段 title1 直接使用全局提供的 uiSchema
title1: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
default: '111',
},
// 等同于
// title1: {
// type: 'string',
// title: 'Title',
// required: true,
// 'x-component': 'Input',
// 'x-decorator': 'FormItem',
// },
title2: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
title: 'Title4', // 覆盖全局已定义的 Title2
required: true, // 扩展的配置参数
description: 'description4',
},
// 等同于
// title2: {
// type: 'string',
// title: 'Title22',
// required: true,
// 'x-component': 'Input',
// 'x-decorator': 'FormItem',
// },
// 字段 title3 没有提供 uiSchema自行处理
title3: {
'x-component': 'Input',
'x-decorator': 'FormItem',
title: 'Title3',
required: true,
},
action1: {
// type: 'void',
'x-component': 'Action',
title: 'Submit',
'x-component-props': {
type: 'primary',
useAction: '{{ useSubmit }}',
},
},
},
},
},
};
const useSubmit = () => {
const form = useForm();
return {
async run() {
form.submit(() => {
console.log(form.values);
});
},
};
};
return (
<SchemaComponentOptions scope={{ useSubmit }}>
<CollectionProvider name="tests">
<SchemaComponent schema={schema} />
</CollectionProvider>
</SchemaComponentOptions>
);
});
const app = new Application({
providers: [Root],
components: { Action, Form, Input, FormItem },
dataSourceManager: {
collections: [collection],
collectionMixins: [InheritanceCollectionMixin],
},
});
export default app.getRootComponent();

View File

@ -1,89 +0,0 @@
import { ISchema, observer } from '@nocobase/schema';
import {
Application,
CollectionProvider,
Form,
FormItem,
InheritanceCollectionMixin,
Input,
SchemaComponent,
SchemaSettings,
} from '@nocobase/client';
import React from 'react';
const collection = {
name: 'tests',
fields: [
{
type: 'string',
name: 'title1',
interface: 'input',
uiSchema: {
title: 'Title1',
type: 'string',
'x-component': 'Input',
required: true,
description: 'description1',
} as ISchema,
},
],
};
const Root = observer(() => {
const schema: ISchema = {
type: 'object',
properties: {
form1: {
type: 'void',
'x-component': 'Form',
properties: {
title1: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-settings': 'FormItemSettings',
},
},
},
},
};
return (
<CollectionProvider name="tests">
<SchemaComponent schema={schema} />
</CollectionProvider>
);
});
const formSettings = new SchemaSettings({
name: 'FormItemSettings',
items: [
{
name: 'required',
type: 'switch',
useComponentProps() {
// const collectionField = useCollectionField(); // 报错
return {
checked: true,
// checked: !!collectionField?.uiSchema?.required,
title: 'Required',
onChange(v) {
// ?
},
};
},
},
],
});
const app = new Application({
providers: [Root],
components: { Form, Input, FormItem },
dataSourceManager: {
collections: [collection],
collectionMixins: [InheritanceCollectionMixin],
},
schemaSettings: [formSettings],
designable: true,
});
export default app.getRootComponent();

View File

@ -1,18 +0,0 @@
import React from 'react';
import { CollectionProvider, useCollection } from '@nocobase/client';
import { createApp } from '../createApp';
const Demo = () => {
const collection = useCollection();
return <pre>{JSON.stringify(collection.getField('username'), null, 2)}</pre>;
};
const Root = () => {
return (
<CollectionProvider name="users">
<Demo />
</CollectionProvider>
);
};
export default createApp(Root);

View File

@ -1,198 +0,0 @@
[
{
"key": "h7b9i8khc3q",
"name": "users",
"inherit": false,
"hidden": false,
"description": null,
"category": [],
"namespace": "users.users",
"duplicator": {
"dumpable": "optional",
"with": "rolesUsers"
},
"sortable": "sort",
"model": "UserModel",
"createdBy": true,
"updatedBy": true,
"logging": true,
"from": "db2cm",
"title": "{{t(\"Users\")}}",
"rawTitle": "{{t(\"Users\")}}",
"fields": [
{
"uiSchema": {
"type": "number",
"title": "{{t(\"ID\")}}",
"x-component": "InputNumber",
"x-read-pretty": true,
"rawTitle": "{{t(\"ID\")}}"
},
"key": "ffp1f2sula0",
"name": "id",
"type": "bigInt",
"interface": "id",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"autoIncrement": true,
"primaryKey": true,
"allowNull": false
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Nickname\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Nickname\")}}"
},
"key": "vrv7yjue90g",
"name": "nickname",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Username\")}}",
"x-component": "Input",
"x-validator": {
"username": true
},
"required": true,
"rawTitle": "{{t(\"Username\")}}"
},
"key": "2ccs6evyrub",
"name": "username",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Email\")}}",
"x-component": "Input",
"x-validator": "email",
"required": true,
"rawTitle": "{{t(\"Email\")}}"
},
"key": "rrskwjl5wt1",
"name": "email",
"type": "string",
"interface": "email",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"key": "t09bauwm0wb",
"name": "roles",
"type": "belongsToMany",
"interface": "m2m",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "roles",
"foreignKey": "userId",
"otherKey": "roleName",
"onDelete": "CASCADE",
"sourceKey": "id",
"targetKey": "name",
"through": "rolesUsers",
"uiSchema": {
"type": "array",
"title": "{{t(\"Roles\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": true,
"fieldNames": {
"label": "title",
"value": "name"
}
}
}
}
]
},
{
"key": "pqnenvqrzxr",
"name": "roles",
"inherit": false,
"hidden": false,
"description": null,
"category": [],
"namespace": "acl.acl",
"duplicator": {
"dumpable": "required",
"with": "uiSchemas"
},
"autoGenId": false,
"model": "RoleModel",
"filterTargetKey": "name",
"sortable": true,
"from": "db2cm",
"title": "{{t(\"Roles\")}}",
"rawTitle": "{{t(\"Roles\")}}",
"fields": [
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Role UID\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Role UID\")}}"
},
"key": "jbz9m80bxmp",
"name": "name",
"type": "uid",
"interface": "input",
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"prefix": "r_",
"primaryKey": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Role name\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Role name\")}}"
},
"key": "faywtz4sf3u",
"name": "title",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"unique": true,
"translation": true
},
{
"key": "1enkovm9sye",
"name": "description",
"type": "string",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null
}
]
}
]

View File

@ -1,98 +0,0 @@
import {
Application,
ApplicationOptions,
CardItem,
Plugin,
CollectionPlugin,
DataBlockProvider,
DEFAULT_DATA_SOURCE_KEY,
DEFAULT_DATA_SOURCE_TITLE,
LocalDataSource,
} from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import { ComponentType } from 'react';
import collections from './collections.json';
const defaultMocks = {
'users:list': {
data: [
{
id: '1',
username: 'jack',
nickname: 'Jack Ma',
email: 'test@gmail.com',
},
{
id: '2',
username: 'jim',
nickname: 'Jim Green',
},
{
id: '3',
username: 'tom',
nickname: 'Tom Cat',
email: 'tom@gmail.com',
},
],
},
'roles:list': {
data: [
{
name: 'root',
title: 'Root',
description: 'Root',
},
{
name: 'admin',
title: 'Admin',
description: 'Admin description',
},
],
},
};
export function createApp(
Demo: ComponentType<any>,
options: ApplicationOptions = {},
mocks: Record<string, any> = defaultMocks,
) {
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addDataSource(LocalDataSource, {
key: DEFAULT_DATA_SOURCE_KEY,
displayName: DEFAULT_DATA_SOURCE_TITLE,
collections: collections as any,
});
}
}
const app = new Application({
apiClient: {
baseURL: 'http://localhost:8000',
},
providers: [Demo],
...options,
components: {
...options.components,
DataBlockProvider,
CardItem,
},
plugins: [CollectionPlugin, MyPlugin, ...(options.plugins || [])],
designable: true,
});
const mock = new MockAdapter(app.apiClient.axios);
Object.entries(mocks).forEach(([url, data]) => {
mock.onGet(url).reply(async (config) => {
const res = typeof data === 'function' ? data(config) : data;
return [200, res];
});
mock.onPost(url).reply(async (config) => {
const res = typeof data === 'function' ? data(config) : data;
return [200, res];
});
});
const Root = app.getRootComponent();
return Root;
}

View File

@ -1,46 +0,0 @@
import React from 'react';
import { CollectionProvider, Collection, useCollection, Plugin } from '@nocobase/client';
import { createApp } from '../createApp';
class TestMixin extends Collection {
test() {
const { name } = this.options;
return 'test ' + name;
}
}
class Test2Mixin extends Collection {
test2() {
const { name } = this.options;
return 'test2 ' + name;
}
}
const Demo = () => {
const collection = useCollection<TestMixin & Test2Mixin>();
return (
<div>
<div>test: {collection.test()}</div>
<div>test2: {collection.test2()}</div>
<div>fields.length: {collection.getFields().length}</div>
</div>
);
};
const Root = () => {
return (
<CollectionProvider name="users">
<Demo />
</CollectionProvider>
);
};
class MyPlugin extends Plugin {
async load() {
this.app.dataSourceManager.addCollectionMixins([TestMixin, Test2Mixin]);
}
}
export default createApp(Root, {
plugins: [MyPlugin],
});

View File

@ -1,79 +0,0 @@
# ExtendCollectionsProvider
用于扩展 [Collection](./collection.md) 。
## 组件
- 类型
```tsx | pure
interface ExtendCollectionsProviderProps {
collections: CollectionOptions[];
children?: ReactNode;
}
```
- 示例
```tsx | pure
import { ExtendCollectionsProvider, CollectionOptions } from '@nocobase/client';
const userCollection: CollectionOptions = {
name: 'users',
title: '{{t("Users")}}',
fields: [
{
type: 'string',
name: 'name',
title: '{{t("Name")}}',
},
{
type: 'string',
name: 'email',
title: '{{t("Email")}}',
},
],
}
const MyPlugin = () => {
return (
<ExtendCollectionsProvider collections={[ userCollection ]}>
<CollectionProvider name="users">
<ChildComponent />
</CollectionProvider>
</ExtendCollectionsProvider>
);
}
const ChildComponent = () => {
const collection = useCollection();
return (
<div>
<h1>{collection.name}</h1>
</div>
);
}
```
## Hooks
### useExtendCollections()
获取扩展的数据表。
```tsx | pure
import { useExtendCollections } from '@nocobase/client';
const MyComponent = () => {
const collections = useExtendCollections();
return (
<div>
{collections.map((collection) => (
<div key={collection.name}>{collection.title}</div>
))}
</div>
);
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

View File

@ -1,125 +0,0 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@nocobase/schema';
import { Field } from '@nocobase/schema';
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();
},
{ displayName: 'AddBlockButton' },
);
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

@ -1,38 +0,0 @@
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

@ -1,133 +0,0 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@nocobase/schema';
import { Field } from '@nocobase/schema';
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();
},
{ displayName: 'AddBlockButton' },
);
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

@ -1,57 +0,0 @@
/**
* 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

@ -1,71 +0,0 @@
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

@ -1,88 +0,0 @@
/**
* 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 '@nocobase/schema';
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

@ -1,53 +0,0 @@
/**
* 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

@ -1,53 +0,0 @@
/**
* defaultShowCode: true
*/
import { Grid, SchemaInitializer, Application, SchemaInitializerSelect, useDesignable } from '@nocobase/client';
import React from 'react';
import { appOptions } from './schema-initializer-common';
import { useFieldSchema } from '@nocobase/schema';
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

@ -1,88 +0,0 @@
/**
* 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

@ -1,33 +0,0 @@
/**
* 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

@ -1,146 +0,0 @@
import React, { FC } from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerChild,
SchemaInitializerItem,
SchemaInitializerItemsProps,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@nocobase/schema';
import { Field } from '@nocobase/schema';
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();
},
{ displayName: 'AddBlockButton' },
);
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

@ -1,61 +0,0 @@
/**
* 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

@ -1,34 +0,0 @@
/**
* 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

@ -1,40 +0,0 @@
/**
* 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

@ -1,32 +0,0 @@
/**
* 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

@ -1,114 +0,0 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
useDesignable,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@nocobase/schema';
import { Field } from '@nocobase/schema';
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();
},
{ displayName: 'AddBlockButton' },
);
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

@ -1,127 +0,0 @@
import React from 'react';
import {
Application,
Plugin,
SchemaComponent,
SchemaInitializer,
SchemaInitializerItem,
useSchemaInitializer,
useSchemaInitializerItem,
useSchemaInitializerRender,
} from '@nocobase/client';
import { observer, useField, useFieldSchema } from '@nocobase/schema';
import { Field } from '@nocobase/schema';
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

@ -1,48 +0,0 @@
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

@ -1,66 +0,0 @@
/**
* 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 '@nocobase/schema';
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

@ -1,68 +0,0 @@
/**
* 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

@ -1,64 +0,0 @@
/**
* 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

@ -1,76 +0,0 @@
/**
* 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 '@nocobase/schema';
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

@ -1,66 +0,0 @@
/**
* defaultShowCode: true
*/
import { ISchema } from '@nocobase/schema';
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

@ -1,28 +0,0 @@
/**
* 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

@ -1,82 +0,0 @@
/**
* 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 '@nocobase/schema';
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

@ -1,64 +0,0 @@
/**
* 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

@ -1,74 +0,0 @@
/**
* 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 '@nocobase/schema';
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

@ -1,72 +0,0 @@
/**
* 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: 'b',
type: 'itemGroup',
componentProps: {
title: '动态 children',
},
useChildren() {
return [
{
name: 'a1',
type: 'item',
componentProps: {
title: 'A 1',
},
onClick: () => {
alert('a-1');
},
},
{
name: 'a2',
type: 'item',
componentProps: {
title: 'A 2',
},
},
];
},
},
],
});
const app = new Application({
...appOptions,
schemaSettings: [mySettings],
});
export default app.getRootComponent();

View File

@ -1,35 +0,0 @@
/**
* 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

@ -1,39 +0,0 @@
/**
* 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

@ -1,35 +0,0 @@
/**
* 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

@ -1,150 +0,0 @@
import { ISchema, useField, useFieldSchema } from '@nocobase/schema';
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();
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

@ -1,791 +0,0 @@
# 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

@ -1,212 +0,0 @@
# 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
### useSchemaOptionsContext()
用于获取注册的 `scope``components`
```tsx | pure
const { scope, components } = useSchemaOptionsContext();
```
## 组件
### 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

@ -1,186 +0,0 @@
# 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

@ -1,704 +0,0 @@
# 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

@ -1,188 +0,0 @@
# 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

@ -1,467 +0,0 @@
# 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()
用于渲染 SchemaSettings。
- 类型
```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

@ -1,427 +0,0 @@
# 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();
```

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