diff --git a/docs/menus.ts b/docs/menus.ts index 9db587035..76ab3babe 100644 --- a/docs/menus.ts +++ b/docs/menus.ts @@ -39,10 +39,7 @@ export default { title: 'Getting started', 'title.zh-CN': '快速开始', type: 'group', - children: [ - '/development/index', - '/development/your-fisrt-plugin' - ], + children: ['/development/index', '/development/your-fisrt-plugin'], }, { title: 'Extension Guides', @@ -83,10 +80,7 @@ export default { { title: 'HTTP API', type: 'group', - children: [ - '/api/http-api/index', - '/api/http-api/rest-api', - ], + children: ['/api/http-api/index', '/api/http-api/rest-api'], }, ], // { @@ -166,7 +160,7 @@ export default { { title: '@nocobase/server', type: 'subMenu', - children: ['/api/server/application', '/api/server/plugin-manager', '/api/server/i18n'], + children: ['/api/server/application', '/api/server/plugin-manager', '/api/server/plugin'], }, { title: '@nocobase/database', diff --git a/docs/zh-CN/api/server/application.md b/docs/zh-CN/api/server/application.md index 5ecd37bc6..a9e1224b5 100644 --- a/docs/zh-CN/api/server/application.md +++ b/docs/zh-CN/api/server/application.md @@ -1,29 +1,37 @@ # Application -NocoBase 的应用程序类,服务端的应用进程将实例化该类并启动。因为继承自 [Koa](https://koajs.com/),所以也可以直接调用 Koa 实例的方法。 - -同时因为 Koa 还继承自 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter),所以也可以通过 `app.on` 监听事件。 +基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。 ## 构造函数 +### `constructor()` + +创建一个应用实例。 + **签名** * `constructor(options: ApplicationOptions)` -创建一个应用实例。 - **参数** | 参数名 | 类型 | 默认值 | 描述 | | --- | --- | --- | --- | | `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 | | `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 | -| `options.cors` | `CorsOptions` | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) | +| `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) | | `options.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 | -| `options.registerActions` | `boolean` | `true` | 是否注册默认的 action | -| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.npmjs.com/package/i18next) | +| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) | +| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) | | `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 | +Type + +```ts +interface ApplicationOptions { + +} +``` + **示例** ```ts @@ -51,31 +59,38 @@ const app = new Application({ ### `db` -应用初始化自动创建的数据库实例,通过 `@nocobase/database` 封装的数据库访问类,相关方法参考 [Database](./database)。 +数据库实例,相关 API 参考 [Database](/api/database)。 ### `resourcer` -应用初始化自动创建的资源路由管理实例,相关方法参考 [Resourcer](./resourcer)。 - -### `cli` - -应用初始化自动创建的命令行工具实例,相关方法参考 [CLI](../cli)。 +应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。 ### `acl` -应用初始化自动创建的访问控制表实例,相关方法参考 [ACL](./acl)。 +ACL 实例,相关 API 参考 [ACL](/api/acl)。 ### `i18n` -应用初始化自动创建的国际化工具实例,相关方法参考 [I18n](./i18n)。 - -### `appManager` - -应用初始化自动创建的应用管理实例,相关方法参考 [AppManager](./app-manager)。 +I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。 ### `pm` -应用初始化自动创建的插件管理器实例,相关方法参考 [PluginManager](./plugin-manager)。 +插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。 + +### `version` + +应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。 + +### `middleware` + +内置的中间件有: + +- i18next +- bodyParser +- cors +- dataWrapping +- collection2resource +- restApiMiddleware ### `context` @@ -94,75 +109,42 @@ NocoBase 默认对 context 注入了以下成员,可以在请求处理函数 ## 实例方法 -### `getVersion()` - -获取当前版本。 - -**签名** - -* `getVersion(): string` - -**示例** - -```ts -console.log(app.getVersion()); // 0.7.4-alpha.7 -``` - -### `plugin()` - -向应用中注册插件的快捷方法,等同于 `app.pm.add()`,参考 [PluginManager](./plugin-manager#add)。 - -### `loadPluginConfig()` - -批量注册插件与默认参数。 - -**签名** - -* `loadPluginConfig(pluginsConfigurations: PluginConfiguration[]): void` - -**参数** - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `pluginsConfigurations[]` | `string` | - | 插件的 npm 包名 | -| `pluginsConfigurations[]` | `[string, any]` | - | 插件的 npm 包名与默认参数 | - -### `getPlugin()` - -获取已注册的插件实例,等同于 `app.pm.get(name)`,参考 [PluginManager](./plugin-manager#get)。 - ### `use()` -注册应用级中间件的快捷方法,等同于 `koa.use()`。 +注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa) ### `on()` 订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。 -### `collection()` - -向数据库注册数据表模型的快捷方法,等同于 `app.db.collection()`,参考 [Database](./database#collection)。 - -### `resource()` - -向资源路由管理器注册资源的快捷方法,等同于 `app.resourcer.define()`,参考 [Resourcer](./resourcer#define)。 - -### `actions()` - -向资源路由管理器注册 action 的快捷方法,等同于 `app.resourcer.registerActions()`,参考 [Resourcer](./resourcer#registerActions)。 - ### `command()` -向命令行工具注册命令的快捷方法,等同于 `app.cli.command()`,参考 [cli](./cli)。 +### `findCommand()` + +### `runAsCLI()` + +以 CLI 的方式运行。 ### `load()` -应用生命周期方法,用于加载所有已注册的插件,等同于 `app.pm.load()`,参考 [PluginManager](./plugin-manager#load)。 +加载应用配置。 **签名** * `async load(): Promise` +### `reload()` + +重载应用配置。 + +### `install()` + +初始化安装应用,同步安装插件。 + +### `upgrade()` + +升级应用,同步升级插件。 + ### `start()` 启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。 @@ -179,84 +161,16 @@ console.log(app.getVersion()); // 0.7.4-alpha.7 | `options.listen.port?` | `number` | 13000 | 端口 | | `options.listen.host?` | `string` | `'localhost'` | 域名 | -**事件** - -`start()` 调用会触发两个事件: - -* `'beforeStart'`:在应用启动前触发。 -* `'afterStart'`:在应用启动后触发。 - ### `stop()` -停止应用,如果应用正在监听,将停止监听,并关闭数据库连接。 - -**签名** - -* `async stop(options: any): Promise` - -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `options?` | `any` | - | 将透传至停止事件的参数 | - -**事件** - -`stop()` 调用会触发两个事件: - -* `'beforeStop'`:在应用停止前触发。 -* `'afterStop'`:在应用停止后触发。 +停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。 ### `destroy()` -销毁并停止应用,包含 `stop()` 的调用。 - -**事件** - -`destroy()` 调用会触发两个事件: - -* `'beforeDestroy'`:在应用销毁前触发。 -* `'afterDestroy'`:在应用销毁后触发。 - -插件相关的销毁事件可以基于该事件进行扩展。 - -### `install()` - -应用初始化安装调用接口,用于安装应用的基础数据,包括: - -* 同步已通过 `db.collection()` 和 `db.import()` 注入的数据库表结构到数据库。 -* 调用所有已注册插件实例的 `install()` 方法,执行相应安装过程。 - -**事件** - -* `'beforeInstall'`:在应用安装前触发。 -* `'afterInstall'`:在应用安装后触发。 - -### `upgrade()` - -应用调用接口,当更新新版本代码后通过命令行工具调用,用于执行应用的升级过程,包括: - -* 数据库迁移工具执行。 - -**事件** - -* `'beforeUpgrade'`:在应用升级前触发。 -* `'afterUpgrade'`:在应用升级后触发。 +删除应用,此方法会删除应用对应的数据库。 ## 事件 -应用生命周期会触发相应事件,扩展开发可以基于这些事件进行。 - -| 事件名称 | 是否异步 | 描述 | -| --- | --- | --- | -| `'beforeInstall'` | 是 | 安装前触发 | -| `'afterInstall'` | 是 | 安装后触发 | -| `'beforeUpgrade'` | 是 | 升级前触发 | -| `'afterUpgrade'` | 是 | 升级后触发 | -| `'beforeStart'` | 是 | 启动前触发 | -| `'afterStart'` | 是 | 启动后触发 | -| `'beforeStop'` | 是 | 停止前触发 | -| `'afterStop'` | 是 | 停止后触发 | -| `'beforeDestroy'` | 是 | 销毁前触发 | -| `'afterDestroy'` | 是 | 销毁后触发 | **示例** @@ -265,3 +179,20 @@ app.on('beforeStart', async () => { console.log('app before start'); }); ``` + +### beforeLoadAll +### afterLoadAll +### beforeLoadPlugin +### afterLoadPlugin +### beforeInstallPlugin +### afterInstallPlugin +### beforeInstall +### afterInstall +### beforeUpgrade +### afterUpgrade +### beforeStart +### afterStart +### beforeStop +### afterStop +### beforeDestroy +### afterDestroy diff --git a/docs/zh-CN/api/server/plugin-manager.md b/docs/zh-CN/api/server/plugin-manager.md index d5a1282fe..ba1223ef0 100644 --- a/docs/zh-CN/api/server/plugin-manager.md +++ b/docs/zh-CN/api/server/plugin-manager.md @@ -4,30 +4,18 @@ ## 实例方法 +### `create()` + ### `add()` -**签名** +### `get()` -* `add(pluginClass: PluginConstructor, options?: any): Plugin` +### `install()` -**参数** +### `upgrade()` -| 参数名 | 类型 | 默认值 | 描述 | -| --- | --- | --- | --- | -| `pluginClass` | `PluginConstructor` | - | 插件类,参考 [Plugin 类](./plugin) | +### `enable()` -**返回值** +### `disable()` -对应插件类的实例 - -**示例** - -```ts -import Application, { Plugin } from '@nocobase/server'; - -class MyPlugin extends Plugin {} - -const app = new Application(); - -app.pm.add(MyPlugin, { a: 1 }); -``` +### `remove()` diff --git a/docs/zh-CN/api/server/plugin.md b/docs/zh-CN/api/server/plugin.md new file mode 100644 index 000000000..8e77b1bd5 --- /dev/null +++ b/docs/zh-CN/api/server/plugin.md @@ -0,0 +1,29 @@ +# Plugin + +## 属性 + +### `name` + +只读 + +### `options` + +插件配置信息 + +## 实例方法 + +### `initialize()` + +初始化 + +### `beforeLoad()` + +加载前,如事件或类注册 + +### `load()` + +加载配置 + +### `install()` + +插件安装逻辑 diff --git a/docs/zh-CN/development/guide/ui-schema-designer/block-provider.md b/docs/zh-CN/development/guide/ui-schema-designer/block-provider.md new file mode 100644 index 000000000..42fa25421 --- /dev/null +++ b/docs/zh-CN/development/guide/ui-schema-designer/block-provider.md @@ -0,0 +1,2 @@ +# 区块 + diff --git a/docs/zh-CN/development/guide/ui-schema-designer/collection-manager.md b/docs/zh-CN/development/guide/ui-schema-designer/collection-manager.md index a02a42f06..ff813ad87 100644 --- a/docs/zh-CN/development/guide/ui-schema-designer/collection-manager.md +++ b/docs/zh-CN/development/guide/ui-schema-designer/collection-manager.md @@ -1 +1,128 @@ # Collection Manager + +```tsx +import React from 'react'; +import { observer, ISchema, useForm } from '@formily/react'; +import { + SchemaComponent, + SchemaComponentProvider, + Form, + Action, + CollectionProvider, + CollectionField, +} from '@nocobase/client'; +import 'antd/dist/antd.css'; +import { FormItem, Input } from '@formily/antd'; + +export default observer(() => { + 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 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 ( + + + + + + ); +}); +``` \ No newline at end of file diff --git a/docs/zh-CN/development/guide/ui-schema-designer/designable.png b/docs/zh-CN/development/guide/ui-schema-designer/designable.png new file mode 100644 index 000000000..6d428a6ac Binary files /dev/null and b/docs/zh-CN/development/guide/ui-schema-designer/designable.png differ diff --git a/docs/zh-CN/development/guide/ui-schema-designer/extending-schema-components.md b/docs/zh-CN/development/guide/ui-schema-designer/extending-schema-components.md index e8592dd95..5fc966819 100644 --- a/docs/zh-CN/development/guide/ui-schema-designer/extending-schema-components.md +++ b/docs/zh-CN/development/guide/ui-schema-designer/extending-schema-components.md @@ -145,10 +145,13 @@ export default () => { ## 嵌套的 Schema -- `props.children` 嵌套,适用于 void 和 object 类型,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套) -- `` 嵌套,适用于各种场景,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套) +- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套) +- `` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套) -注意:仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用 +注意: + +- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `` 解决嵌套问题 +- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用 ```tsx | pure @@ -190,7 +193,7 @@ export default () => { }; ``` -各类型对比,array 和 string 并不会渲染 World 节点 +各类型 properties 渲染结果对比 ```tsx import React from 'react'; @@ -204,7 +207,7 @@ const schema = { properties: { title1: { type: 'void', - 'x-content': 'Void schema', + 'x-content': 'Void schema,渲染 properties', }, void: { type: 'void', @@ -219,7 +222,7 @@ const schema = { }, title2: { type: 'void', - 'x-content': 'Object schema', + 'x-content': 'Object schema,渲染 properties', }, object: { type: 'object', @@ -234,7 +237,7 @@ const schema = { }, title3: { type: 'void', - 'x-content': 'Array schema', + 'x-content': 'Array schema,不渲染 properties', }, array: { type: 'array', @@ -249,7 +252,7 @@ const schema = { }, title4: { type: 'void', - 'x-content': 'String schema', + 'x-content': 'String schema,不渲染 properties', }, string: { type: 'string', diff --git a/docs/zh-CN/development/guide/ui-schema-designer/what-is-ui-schema.md b/docs/zh-CN/development/guide/ui-schema-designer/what-is-ui-schema.md index 32025744e..0ea86181c 100644 --- a/docs/zh-CN/development/guide/ui-schema-designer/what-is-ui-schema.md +++ b/docs/zh-CN/development/guide/ui-schema-designer/what-is-ui-schema.md @@ -15,10 +15,10 @@ interface ISchema { ['x-component']?: string; // 组件属性 ['x-component-props']?: any; - // 组件的子节点,简单使用 - ['x-content']?: any; // 展示状态,默认为 'visible' ['x-display']?: 'none' | 'hidden' | 'visible'; + // 组件的子节点,简单使用 + ['x-content']?: any; // children 节点 schema properties?: Record; @@ -27,17 +27,17 @@ interface ISchema { // 字段联动 ['x-reactions']?: SchemaReactions; // 字段 UI 交互模式,默认为 'editable' - ['x-pattern']?: 'editable' | 'disabled' | 'readOnly' | 'readPretty'; + ['x-pattern']?: 'editable' | 'disabled' | 'readPretty'; // 字段校验 - ['x-validator']?: FieldValidator; + ['x-validator']?: Validator; // 默认数据 default: ?:any; // 设计器相关 - // 设计器组件(工具栏) + // 设计器组件(工具栏),包括:拖拽移动、插入新节点、修改参数、移除等 ['x-designer']?: any; - // 初始化器组件(工具栏) + // 初始化器组件(工具栏),决定当前 schema 内部可以插入什么 ['x-initializer']?: any; } ``` @@ -266,7 +266,7 @@ const schema = { }; ``` -JSX 等价于 +JSX 等同于 ```tsx | pure
@@ -293,7 +293,7 @@ const schema = { }; ``` -JSX 等价于 +JSX 等同于 ```tsx | pure
@@ -320,7 +320,7 @@ const schema = { }; ``` -JSX 等价于 +JSX 等同于 ```tsx | pure
diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts index d66adc7a7..70b0aaec9 100644 --- a/packages/core/server/src/application.ts +++ b/packages/core/server/src/application.ts @@ -6,7 +6,7 @@ import { applyMixins, AsyncEmitter } from '@nocobase/utils'; import { Command, CommandOptions, ParseOptions } from 'commander'; import { Server } from 'http'; import { i18n, InitOptions } from 'i18next'; -import Koa from 'koa'; +import Koa, { DefaultContext as KoaDefaultContext, DefaultState as KoaDefaultState } from 'koa'; import { isBoolean } from 'lodash'; import semver from 'semver'; import { createACL } from './acl'; @@ -35,14 +35,15 @@ export interface ApplicationOptions { plugins?: PluginConfiguration[]; } -export interface DefaultState { +export interface DefaultState extends KoaDefaultState { currentUser?: any; [key: string]: any; } -export interface DefaultContext { +export interface DefaultContext extends KoaDefaultContext { db: Database; resourcer: Resourcer; + i18n: any; [key: string]: any; } @@ -198,6 +199,8 @@ export class Application exten this._resourcer = createResourcer(options); this._cli = new Command('nocobase').usage('[command] [options]'); this._i18n = createI18n(options); + this.context.db = this._db; + this.context.resourcer = this._resourcer; this._pm = new PluginManager({ app: this, diff --git a/packages/core/server/src/helper.ts b/packages/core/server/src/helper.ts index 07e357c3d..1665c0e8f 100644 --- a/packages/core/server/src/helper.ts +++ b/packages/core/server/src/helper.ts @@ -2,10 +2,10 @@ import cors from '@koa/cors'; import Database from '@nocobase/database'; import Resourcer from '@nocobase/resourcer'; import i18next from 'i18next'; -import { DefaultContext, DefaultState } from 'koa'; import bodyParser from 'koa-bodyparser'; import Application, { ApplicationOptions } from './application'; import { dataWrapping } from './middlewares/data-wrapping'; +import { i18n } from './middlewares/i18n'; import { table2resource } from './middlewares/table2resource'; export function createI18n(options: ApplicationOptions) { @@ -46,27 +46,15 @@ export function registerMiddlewares(app: Application, options: ApplicationOption }), ); - app.use(async (ctx, next) => { + app.use(async (ctx, next) => { ctx.getBearerToken = () => { return ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); }; - ctx.db = app.db; - ctx.resourcer = app.resourcer; - const i18n = app.i18n.cloneInstance({ initImmediate: false }); - ctx.i18n = i18n; - ctx.t = i18n.t.bind(i18n); - const lng = - ctx.get('X-Locale') || - (ctx.request.query.locale as string) || - app.i18n.language || - ctx.acceptsLanguages().shift() || - 'en-US'; - if (lng !== '*' && lng) { - i18n.changeLanguage(lng); - } await next(); }); + app.use(i18n); + if (options.dataWrapping !== false) { app.use(dataWrapping()); } diff --git a/packages/core/server/src/middlewares/i18n.ts b/packages/core/server/src/middlewares/i18n.ts new file mode 100644 index 000000000..c87c173ab --- /dev/null +++ b/packages/core/server/src/middlewares/i18n.ts @@ -0,0 +1,15 @@ +export async function i18n(ctx, next) { + const i18n = ctx.app.i18n.cloneInstance({ initImmediate: false }); + ctx.i18n = i18n; + ctx.t = i18n.t.bind(i18n); + const lng = + ctx.get('X-Locale') || + (ctx.request.query.locale as string) || + ctx.app.i18n.language || + ctx.acceptsLanguages().shift() || + 'en-US'; + if (lng !== '*' && lng) { + i18n.changeLanguage(lng); + } + await next(); +}