chore: 清理部分插件,修复 @tachybase/cli 工具 关联 #704
Reviewed-on: daoyoucloud/tachycode#706
22
packages/core/build/tsconfig.json
Normal 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"]
|
||||||
|
}
|
@ -7,6 +7,7 @@ const entry = fg.globSync(['src/**', ...globExcludeFiles], { cwd: __dirname, abs
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry,
|
entry,
|
||||||
|
tsconfig: path.join(__dirname, 'tsconfig.json'),
|
||||||
outDir: path.join(__dirname, 'lib'),
|
outDir: path.join(__dirname, 'lib'),
|
||||||
splitting: false,
|
splitting: false,
|
||||||
silent: true,
|
silent: true,
|
||||||
@ -14,7 +15,7 @@ export default defineConfig({
|
|||||||
clean: true,
|
clean: true,
|
||||||
bundle: false,
|
bundle: false,
|
||||||
loader: {
|
loader: {
|
||||||
'.d.ts': 'copy'
|
'.d.ts': 'copy',
|
||||||
},
|
},
|
||||||
skipNodeModulesBundle: true,
|
skipNodeModulesBundle: true,
|
||||||
});
|
});
|
||||||
|
@ -9,11 +9,12 @@ import semver from 'semver';
|
|||||||
const cli = new Command();
|
const cli = new Command();
|
||||||
|
|
||||||
cli.version((await import('../package.json')).version);
|
cli.version((await import('../package.json')).version);
|
||||||
await commands(cli);
|
|
||||||
|
|
||||||
initEnv();
|
initEnv();
|
||||||
genTsConfigPaths();
|
genTsConfigPaths();
|
||||||
|
|
||||||
|
await commands(cli);
|
||||||
|
|
||||||
if (semver.satisfies(process.version, '<20')) {
|
if (semver.satisfies(process.version, '<20')) {
|
||||||
console.error(chalk.red('[tachybase cli]: Node.js version must be >= 20'));
|
console.error(chalk.red('[tachybase cli]: Node.js version must be >= 20'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -222,9 +222,7 @@ export async function genTsConfigPaths() {
|
|||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const cwdLength = cwd.length;
|
const cwdLength = cwd.length;
|
||||||
const paths: Record<string, string[]> = {
|
const paths: Record<string, string[]> = {};
|
||||||
'@@/*': ['.dumi/tmp/*'],
|
|
||||||
};
|
|
||||||
const packages = fastGlob.sync(['packages/*/*/package.json', 'packages/*/*/*/package.json'], {
|
const packages = fastGlob.sync(['packages/*/*/package.json', 'packages/*/*/*/package.json'], {
|
||||||
absolute: true,
|
absolute: true,
|
||||||
onlyFiles: true,
|
onlyFiles: true,
|
||||||
@ -324,7 +322,7 @@ export function initEnv() {
|
|||||||
SOCKET_PATH: 'storage/gateway.sock',
|
SOCKET_PATH: 'storage/gateway.sock',
|
||||||
NODE_MODULES_PATH: resolve(process.cwd(), 'node_modules'),
|
NODE_MODULES_PATH: resolve(process.cwd(), 'node_modules'),
|
||||||
PM2_HOME: resolve(process.cwd(), './storage/.pm2'),
|
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',
|
SERVER_TSCONFIG_PATH: './tsconfig.server.json',
|
||||||
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
|
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
|
||||||
CACHE_DEFAULT_STORE: 'memory',
|
CACHE_DEFAULT_STORE: 'memory',
|
||||||
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"include": ["**/*"]
|
|
||||||
}
|
|
@ -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',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
@ -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;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- 详细信息
|
|
||||||
- apiClient:API 请求实例,具体说明请参见:[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)
|
|
||||||
- schemaSettings:Schema 设置工具,具体参考:[SchemaSettingsManager](/core/ui-schema/schema-initializer-manager)
|
|
||||||
- schemaInitializers:Schema 添加工具,具体参考:[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();
|
|
||||||
```
|
|
@ -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>
|
|
||||||
}
|
|
||||||
```
|
|
@ -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;
|
|
||||||
}
|
|
||||||
```
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -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();
|
|
||||||
}
|
|
||||||
```
|
|
@ -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>
|
|
||||||
```
|
|
@ -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);
|
|
||||||
```
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
@ -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();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
可运行示例
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 4.8 KiB |
@ -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)。
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -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)
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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>;
|
|
||||||
};
|
|
||||||
```
|
|
@ -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 字段
|
|
||||||
```
|
|
@ -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>
|
|
@ -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>
|
|
||||||
|
|
@ -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 ]
|
|
||||||
```
|
|
@ -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'),
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
@ -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
|
|
||||||
```
|
|
@ -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']);
|
|
||||||
}
|
|
||||||
```
|
|
@ -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();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
@ -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>
|
|
||||||
}
|
|
||||||
```
|
|
@ -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'));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
@ -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();
|
|
@ -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();
|
|
@ -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);
|
|
@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
@ -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;
|
|
||||||
}
|
|
@ -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],
|
|
||||||
});
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 131 KiB |
@ -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();
|
|
@ -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 };
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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 };
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
@ -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;
|
|
||||||
```
|
|
@ -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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -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`
|
|
||||||
- popoverProps:popover 的属性
|
|
||||||
- Schema 操作相关
|
|
||||||
|
|
||||||
- insertPosition:插入位置,参考:[useDesignable()](/core/ui-schema/designable#usedesignable)
|
|
||||||
- designable:是否显示设计模式,参考:[useDesignable()](/core/ui-schema/designable#usedesignable)
|
|
||||||
- wrap:对 Schema 的二次处理,参考:[useDesignable()](/core/ui-schema/designable#usedesignable)
|
|
||||||
- onSuccess:Schema 更新到服务端后的回调,参考:[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}/>
|
|
||||||
}
|
|
||||||
```
|
|
@ -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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -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>
|
|
@ -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();
|
|
||||||
```
|
|