diff --git a/.umirc.ts b/.umirc.ts index cc0e60601..bfc9b219f 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -76,6 +76,33 @@ const parseMenuItems = (items: any[], lang: string) => { return menuItems; }; +const navs = [ + { + title: 'Welcome', + 'title.zh-CN': '欢迎', + path: '/welcome', + }, + { + title: 'User manual', + 'title.zh-CN': '使用手册', + path: '/manual', + }, + { + title: 'Plugin Development', + 'title.zh-CN': '插件开发', + path: '/development', + }, + { + title: 'API reference', + 'title.zh-CN': 'API 参考', + path: '/api', + }, + { + title: 'GitHub', + path: 'https://github.com/nocobase/nocobase', + }, +]; + export default defineConfig({ title: 'NocoBase', outputPath: `./docs/dist/${lang}`, @@ -87,66 +114,12 @@ export default defineConfig({ hash: true, logo: 'https://www.nocobase.com/images/logo.png', navs: { - 'en-US': [ - { - title: 'Introduction', - path: '/introduction' - }, - { - title: 'Getting started', - path: '/getting-started' - }, - { - title: 'Manual', - path: '/manual' - }, - { - title: 'Development', - path: '/development' - }, - { - title: 'API reference', - path: '/api' - }, - { - title: 'GitHub', - path: 'https://github.com/nocobase/nocobase', - }, - ], - 'zh-CN': [ - { - title: '欢迎', - path: '/welcome' - }, - // { - // title: '快速开始', - // path: '/getting-started' - // }, - { - title: '使用手册', - path: '/manual' - }, - { - title: '插件开发', - path: '/development' - }, - { - title: 'API 参考', - path: '/api' - }, - // { - // title: '社区', - // path: '/community' - // }, - { - title: 'GitHub', - path: 'https://github.com/nocobase/nocobase', - }, - ] + 'en-US': navs, + 'zh-CN': navs.map((item) => ({ ...item, title: item['title.zh-CN'] || item.title })), }, menus: Object.keys(menus).reduce((result, key) => { const items = menus[key]; result[key] = parseMenuItems(items, lang); return result; - }, {}) + }, {}), }); diff --git a/docs/en-US/api/acl/acl-resource.md b/docs/en-US/api/acl/acl-resource.md new file mode 100644 index 000000000..305b80bed --- /dev/null +++ b/docs/en-US/api/acl/acl-resource.md @@ -0,0 +1,57 @@ +# ACLResource + +ACLResource,ACL 系统中的资源类。在 ACL 系统中,为用户授予权限时会自动创建对应的资源。 + + +## 类方法 + +### `constructor()` +构造函数 + +**签名** +* `constructor(options: AclResourceOptions)` + +**类型** +```typescript +type ResourceActions = { [key: string]: RoleActionParams }; + +interface AclResourceOptions { + name: string; // 资源名称 + role: ACLRole; // 资源所属角色 + actions?: ResourceActions; +} +``` + +**详细信息** + +`RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) + +### `getActions()` + +获取资源的所有 Action,返回结果为 `ResourceActions` 对象。 + +### `getAction()` +根据名称返回 Action 的参数配置,返回结果为 `RoleActionParams` 对象。 + +**详细信息** + +`RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) + +### `setAction()` + +在资源内部设置一个 Action 的参数配置,返回结果为 `RoleActionParams` 对象。 + +**签名** +* `setAction(name: string, params: RoleActionParams)` + +**详细信息** + +* name - 要设置的 action 名称 +* `RoleActionParams`详见 [`aclRole.grantAction`](./acl-role.md#grantaction) + +### `setActions()` + +**签名** +* `setActions(actions: ResourceActions)` + +批量调用 `setAction` 的便捷方法 diff --git a/docs/en-US/api/acl/acl-role.md b/docs/en-US/api/acl/acl-role.md new file mode 100644 index 000000000..708131702 --- /dev/null +++ b/docs/en-US/api/acl/acl-role.md @@ -0,0 +1,87 @@ +# ACL Role + +ACLRole,ACL 系统中的用户角色类。在 ACL 系统中,通常使用 `acl.define` 定义角色。 + +## 类方法 + +### `constructor()` +构造函数 + +**签名** +* `constructor(public acl: ACL, public name: string)` + +**详细信息** +* acl - ACL 实例 +* name - 角色名称 + +### `grantAction()` + +为角色授予 Action 权限 + +**签名** +* `grantAction(path: string, options?: RoleActionParams)` + +**类型** +```typescript +interface RoleActionParams { + fields?: string[]; + filter?: any; + own?: boolean; + whitelist?: string[]; + blacklist?: string[]; + [key: string]: any; +} +``` + +**详细信息** + +* path - 资源Action路径,如 `posts:edit`,表示 `posts` 资源的 `edit` Action, 资源名称和 Action 之间使用 `:` 冒号分隔。 + +RoleActionParams 为授权时,对应 action 的可配置参数,用以实现更细粒度的权限控制。 + +* fields - 可访问的字段 + ```typescript + acl.define({ + role: 'admin', + actions: { + 'posts:view': { + // admin 用户可以请求 posts:view action,但是只有 fields 配置的字段权限 + fields: ["id", "title", "content"], + }, + }, + }); + ``` +* filter - 权限资源过滤配置 + ```typescript + acl.define({ + role: 'admin', + actions: { + 'posts:view': { + // admin 用户可以请求 posts:view action,但是列出的结果必须满足 filter 设置的条件。 + filter: { + createdById: '{{ ctx.state.currentUser.id }}', // 支持模板语法,可以取 ctx 中的值,将在权限判断时替换 + }, + }, + }, + }); + ``` +* own - 是否只能访问自己的数据 + ```typescript + const actionsWithOwn = { + 'posts:view': { + "own": true // + } + } + + // 等价于 + const actionsWithFilter = { + 'posts:view': { + "filter": { + "createdById": "{{ ctx.state.currentUser.id }}" + } + } + } + ``` +* whitelist - 白名单,只有在白名单中的字段才能被访问 +* blacklist - 黑名单,黑名单中的字段不能被访问 + diff --git a/docs/en-US/api/acl/acl.md b/docs/en-US/api/acl/acl.md new file mode 100644 index 000000000..dc55f7fca --- /dev/null +++ b/docs/en-US/api/acl/acl.md @@ -0,0 +1,232 @@ +# ACL + +ACL 为 Nocobase 中的权限控制模块。在 ACL 中注册角色、资源以及配置相应权限之后,即可对角色进行权限判断。 + +## 概念解释 + +* 角色 (`ACLRole`):权限判断的对象 +* 资源 (`ACLResource`):在 Nocobase ACL 中,资源通常对应一个数据库表,概念上可类比为 Restful API 中的 Resource。 +* Action:对资源的操作,如 `create`、`read`、`update`、`delete` 等。 +* 策略 (`ACLAvailableStrategy`): 通常每个角色都有自己的权限策略,策略中定义了默认情况下的用户权限。 +* 授权:在 `ACLRole` 实例中调用 `grantAction` 函数,为角色授予 `Action` 的访问权限。 +* 鉴权:在 `ACL` 实例中调用 `can` 函数,函数返回结果既为用户的鉴权结果。 + + +## 类方法 + +### `constructor()` + +构造函数,创建一个 `ACL` 实例。 + +```typescript +import { ACL } from '@nocobase/database'; + +const acl = new ACL(); +``` + +### `define()` + +定义一个 ACL 角色 + +**签名** +* `define(options: DefineOptions): ACLRole` + +**类型** + +```typescript +interface DefineOptions { + role: string; + allowConfigure?: boolean; + strategy?: string | AvailableStrategyOptions; + actions?: ResourceActionsOptions; + routes?: any; +} +``` + +**详细信息** + +* `role` - 角色名称 + +```typescript +// 定义一个名称为 admin 的角色 +acl.define({ + role: 'admin', +}); +``` + +* `allowConfigure` - 是否允许配置权限 +* `strategy` - 角色的权限策略 + * 可以为 `string`,为要使用的策略名,表示使用已定义的策略。 + * 可以为 `AvailableStrategyOptions`,为该角色定义一个新的策略,参考[`setAvailableActions()`](#setavailableactions)。 +* `actions` - 定义角色时,可传入角色可访问的 `actions` 对象, + 之后会依次调用 `aclRole.grantAction` 授予资源权限。详见 [`aclRole.grantAction`](./acl-role.md#grantaction) + +```typescript +acl.define({ + role: 'admin', + actions: { + 'posts:edit': {} + }, +}); +// 等同于 +const role = acl.define({ + role: 'admin', +}); + +role.grantAction('posts:edit', {}); +``` + +### `getRole()` + +根据角色名称返回已注册的角色对象 + +**签名** +* `getRole(name: string): ACLRole` + +### `removeRole()` + +根据角色名称移除角色 + +**签名** +* `removeRole(name: string)` + +### `can()` +鉴权函数 + +**签名** +* `can(options: CanArgs): CanResult | null` + +**类型** + +```typescript +interface CanArgs { + role: string; // 角色名称 + resource: string; // 资源名称 + action: string; //操作名称 +} + +interface CanResult { + role: string; // 角色名称 + resource: string; // 资源名称 + action: string; // 操作名称 + params?: any; // 注册权限时传入的参数 +} + +``` + +**详细信息** + +`can` 方法首先会判断角色是否有注册对应的 `Action` 权限,如果没有则会去判断角色的 `strategy` 是否匹配。 +调用返回为`null`时,表示角色无权限,反之返回 `CanResult`对象,表示角色有权限。 + +**示例** +```typescript +// 定义角色,注册权限 +acl.define({ + role: 'admin', + actions: { + 'posts:edit': { + fields: ['title', 'content'], + }, + }, +}); + +const canResult = acl.can({ + role: 'admin', + resource: 'posts', + action: 'edit', +}); +/** + * canResult = { + * role: 'admin', + * resource: 'posts', + * action: 'edit', + * params: { + * fields: ['title', 'content'], + * } + * } + */ + +acl.can({ + role: 'admin', + resource: 'posts', + action: 'destroy', +}); // null +``` +### `use()` + +**签名** +* `use(fn: any)` +向 middlewares 中添加中间件函数。 + +### `middleware()` + +返回一个中间件函数,用于在 `@nocobase/server` 中使用。使用此 `middleware` 之后,`@nocobase/server` 在每次请求处理之前都会进行权限判断。 + +### `allow()` + +设置资源为可公开访问 + +**签名** +* `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)` + +**类型** +```typescript +type ConditionFunc = (ctx: any) => Promise | boolean; +``` + +**详细信息** + +* resourceName - 资源名称 +* actionNames - 资源动作名 +* condition? - 配置生效条件 + * 传入 `string`,表示使用已定义的条件,注册条件使用 `acl.allowManager.registerCondition` 方法。 + ```typescript + acl.allowManager.registerAllowCondition('superUser', async () => { + return ctx.state.user?.id === 1; + }); + + // 开放 users:list 的权限,条件为 superUser + acl.allow('users', 'list', 'superUser'); + ``` + * 传入 ConditionFunc,可接收 `ctx` 参数,返回 `boolean`,表示是否生效。 + ```typescript + // 当用户ID为1时,可以访问 user:list + acl.allow('users', 'list', (ctx) => { + return ctx.state.user?.id === 1; + }); + ``` + +**示例** + +```typescript +// 注册 users:login 可以被公开访问 +acl.allow('users', 'login'); +``` + +### `setAvailableActions()` + +**签名** + +* `setAvailableStrategy(name: string, options: AvailableStrategyOptions)` + +注册一个可用的权限策略 + +**类型** + +```typescript +interface AvailableStrategyOptions { + displayName?: string; + actions?: false | string | string[]; + allowConfigure?: boolean; + resource?: '*'; +} +``` + +**详细信息** + +* displayName - 策略名称 +* allowConfigure - 此策略是否拥有 **配置资源** 的权限,设置此项为`true`之后,请求判断在 `ACL` 中注册成为 `configResources` 资源的权限,会返回通过。 +* actions - 策略内的 actions 列表,支持通配符 `*` +* resource - 策略内的 resource 定义,支持通配符 `*` + diff --git a/docs/en-US/api/actions.md b/docs/en-US/api/actions.md new file mode 100644 index 000000000..4f64fb1cd --- /dev/null +++ b/docs/en-US/api/actions.md @@ -0,0 +1,349 @@ +# 内置常用资源操作 + +针对常用的 CRUD 等数据资源的操作,NocoBase 内置了对应操作方法,并通过数据表资源自动映射相关的操作。 + +所有的操作方法都是注册在 resourcer 实例上,也是标准兼容 Koa 的中间件函数(`(ctx, next) => Promise`)。操作的参数由路由解析后附加在 `ctx.action` 对象上,后续参数相关介绍均基于此对象。 + +通常情况下无需直接调用内置的 action 方法,在需要扩展默认操作行为时,可以在自定义的操作方法内调用默认方法。 + +## 包结构 + +可通过以下方式引入相关实体: + +```ts +import actions from '@nocobase/actions'; +``` + +## 单一数据资源操作 + +### `list()` + +获取数据列表。对应资源操作的 URL 为 `GET /api/:list`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | 过滤参数 | +| `fields` | `string[]` | - | 要获取的字段 | +| `except` | `string[]` | - | 要排除的字段 | +| `appends` | `string[]` | - | 要附加的关系字段 | +| `sort` | `string[]` | - | 排序参数 | +| `page` | `number` | 1 | 分页 | +| `pageSize` | `number` | 20 | 每页数据条数 | + +**示例** + +当需要提供一个查询数据列表的接口,但不是默认以 JSON 格式输出时,可以基于内置默认方法进行扩展: + +```ts +import actions from '@nocobase/actions'; + +app.actions({ + async ['books:list'](ctx, next) { + ctx.action.mergeParams({ + except: ['content'] + }); + + await actions.list(ctx, async () => { + const { rows } = ctx.body; + // transform JSON to CSV output + ctx.body = rows.map(row => Object.keys(row).map(key => row[key]).join(',')).join('\n'); + ctx.type = 'text/csv'; + + await next(); + }); + } +}); +``` + +请求示例,将获得 CSV 格式文件的返回: + +```shell +curl -X GET http://localhost:13000/api/books:list +``` + +### `get()` + +获取单条数据。对应资源操作的 URL 为 `GET /api/:get`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `filterByTk` | `number \| string` | - | 过滤主键 | +| `filter` | `Filter` | - | 过滤参数 | +| `fields` | `string[]` | - | 要获取的字段 | +| `except` | `string[]` | - | 要排除的字段 | +| `appends` | `string[]` | - | 要附加的关系字段 | +| `sort` | `string[]` | - | 排序参数 | +| `page` | `number` | 1 | 分页 | +| `pageSize` | `number` | 20 | 每页数据条数 | + +**示例** + +基于 NocoBase 内置的文件管理插件,可以扩展当客户端请求以资源标识下载一个文件时,返回文件流: + +```ts +import path from 'path'; +import actions from '@nocobase/actions'; +import { STORAGE_TYPE_LOCAL } from '@nocobase/plugin-file-manager'; + +app.actions({ + async ['attachments:get'](ctx, next) { + ctx.action.mergeParams({ + appends: ['storage'], + }); + + await actions.get(ctx, async () => { + if (ctx.accepts('json', 'application/octet-stream') === 'json') { + return next(); + } + + const { body: attachment } = ctx; + const { storage } = attachment; + + if (storage.type !== STORAGE_TYPE_LOCAL) { + return ctx.redirect(attachment.url); + } + + ctx.body = fs.createReadStream(path.resolve(storage.options.documentRoot?, storage.path)); + ctx.attachment(attachment.filename); + ctx.type = 'application/octet-stream'; + + await next(); + }); + } +}); +``` + +请求示例,将获得文件流的返回: + +```shell +curl -X GET -H "Accept: application/octet-stream" http://localhost:13000/api/attachments:get?filterByTk=1 +``` + +### `create()` + +创建单条数据。对应资源操作的 URL 为 `POST /api/:create`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `values` | `Object` | - | 要创建的数据 | + +**示例** + +类似文件管理插件,创建带有二进制内容的数据作为上传文件的附件: + +```ts +import multer from '@koa/multer'; +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:create'](ctx, next) { + if (ctx.request.type === 'application/json') { + return actions.create(ctx, next); + } + + if (ctx.request.type !== 'multipart/form-data') { + return ctx.throw(406); + } + + // 文件保存处理仅用 multer() 作为示例,不代表完整的逻辑 + multer().single('file')(ctx, async () => { + const { file, body } = ctx.request; + const { filename, mimetype, size, path } = file; + + ctx.action.mergeParams({ + values: { + filename, + mimetype, + size, + path: file.path, + meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; + } + }); + + await actions.create(ctx, next); + }); + } +}); +``` + +请求示例,可以创建文件表的普通数据,也可以含附件一起提交: + +```shell +# 仅创建普通数据 +curl -X POST -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:create" + +# 含附件一起提交 +curl -X POST -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:create" +``` + +### `update()` + +更新一条或多条数据。对应的 URL 为 `PUT /api/:update`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | 过滤参数 | +| `filterByTk` | `number \| string` | - | 过滤主键 | +| `values` | `Object` | - | 更新数据值 | + +注:参数中的 `filter` 和 `filterByTk` 至少提供一项。 + +**示例** + +类似 `create()` 的例子,更新文件记录可以扩展为可携带二进制内容的数据作为更新的文件: + +```ts +import multer from '@koa/multer'; +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:update'](ctx, next) { + if (ctx.request.type === 'application/json') { + return actions.update(ctx, next); + } + + if (ctx.request.type !== 'multipart/form-data') { + return ctx.throw(406); + } + + // 文件保存处理仅用 multer() 作为示例,不代表完整的逻辑 + multer().single('file')(ctx, async () => { + const { file, body } = ctx.request; + const { filename, mimetype, size, path } = file; + + ctx.action.mergeParams({ + values: { + filename, + mimetype, + size, + path: file.path, + meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; + } + }); + + await actions.update(ctx, next); + }); + } +}); +``` + +请求示例,可以创建文件表的普通数据,也可以含附件一起提交: + +```shell +# 仅创建普通数据 +curl -X PUT -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:update" + +# 含附件一起提交 +curl -X PUT -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:update" +``` + +### `destroy()` + +删除一条或多条数据。对应的 URL 为 `DELETE /api/:destroy`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | 过滤参数 | +| `filterByTk` | `number \| string` | - | 过滤主键 | + +注:参数中的 `filter` 和 `filterByTk` 至少提供一项。 + +**示例** + +类似对文件管理插件扩展一个删除文件数据也需要同时删除对应文件的操作处理: + +```ts +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:destroy'](ctx, next) { + // const repository = getRepositoryFromParams(ctx); + + // const { filterByTk, filter } = ctx.action.params; + + // const items = await repository.find({ + // fields: [repository.collection.filterTargetKey], + // appends: ['storage'], + // filter, + // filterByTk, + // context: ctx, + // }); + + // await items.reduce((promise, item) => promise.then(async () => { + // await item.removeFromStorage(); + // await item.destroy(); + // }), Promise.resolve()); + + await actions.destroy(ctx, async () => { + // do something + await next(); + }); + } +}); +``` + +### `move()` +对应的 URL 为 `POST /api/:move`。 + +此方法用于移动数据,调整数据的排序。例如在页面中,拖拽一个元素到另一个元素的上方或下方,可调用此方法实现顺序调整。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +|----------|-------------| -- |---------------| +| `sourceId` | `targetKey` | - | 移动的元素ID | +| `targetId` | `targetKey` | - | 与移动元素交换位置的元素ID | +| `sortField` | `string` | `sort` | 排序存储的字段名 | +| `targetScope` | `string` | - | 排序的scope,一个 resource 可以按照不同的 scope 排序 | +| `sticky` | `boolean` | - | 是否置顶移动的元素 | +| `method` | `insertAfter` \| `prepend` | - | 插入类型,插入目标元素之前还是之后 | + +## 关系资源资源操作 + +### `add()` + +添加与对象的关联关系,对应的 URL 为 `POST /api/:add`。适用于 `hasMany` 和 `belongsToMany` 关联。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | 添加的关联对象ID | + +### `remove()` +移除与对象的关联关系,对应的 URL 为 `POST /api/:remove`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | 移除的关联对象ID | + +### `set()` +设置关联的关联对象,对应的 URL 为 `POST /api/:set`。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | 设置的关联对象的ID | + +### `toggle()` + +切换关联的关联对象,对应的 URL 为 `POST /api/:toggle`。`toggle` 在内部判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。 + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +|----------|-------------| -- | --- | +| `values` | `TargetKey` | - | 切换的关联对象的ID | diff --git a/docs/en-US/api/cli.md b/docs/en-US/api/cli.md new file mode 100644 index 000000000..a073396c6 --- /dev/null +++ b/docs/en-US/api/cli.md @@ -0,0 +1,350 @@ +# NocoBase CLI + +NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。 + + + +NocoBase CLI 支持 ts-node 和 node 两种运行模式 + +- ts-node 模式(默认):用于开发环境,支持实时编译,但是响应较慢 +- node 模式:用于生产环境,响应迅速,但需要先执行 `yarn nocobase build` 将全部源码进行编译 + + + +## 使用说明 + +```bash +$ yarn nocobase -h + +Usage: nocobase [command] [options] + +Options: + -h, --help + +Commands: + console + db:auth 校验数据库是否连接成功 + db:sync 通过 collections 配置生成相关数据表和字段 + install 安装 + start 生产环境启动应用 + build 编译打包 + clean 删除编译之后的文件 + dev 启动应用,用于开发环境,支持实时编译 + doc 文档开发 + test 测试 + umi + upgrade 升级 + migrator 数据迁移 + pm 插件管理器 + help +``` + +## 在脚手架里应用 + +应用脚手架 `package.json` 里的 `scripts` 如下: + +```json +{ + "scripts": { + "dev": "nocobase dev", + "start": "nocobase start", + "clean": "nocobase clean", + "build": "nocobase build", + "test": "nocobase test", + "pm": "nocobase pm", + "postinstall": "nocobase postinstall" + } +} +``` + +## 命令行扩展 + +NocoBase CLI 基于 [commander](https://github.com/tj/commander.js) 构建,你可以自由扩展命令,扩展的 command 可以写在 `app/server/index.ts` 里: + +```ts +const app = new Application(config); + +app.command('hello').action(() => {}); +``` + +或者,写在插件里: + +```ts +class MyPlugin extends Plugin { + beforeLoad() { + this.app.command('hello').action(() => {}); + } +} +``` + +终端运行 + +```bash +$ yarn nocobase hello +``` + +## 内置命令行 + +按使用频率排序 + +### `dev` + +开发环境下,启动应用,代码实时编译。 + + +NocoBase 未安装时,会自动安装(参考 install 命令) + + +```bash +Usage: nocobase dev [options] + +Options: + -p, --port [port] + --client + --server + -h, --help +``` + +示例 + +```bash +# 启动应用,用于开发环境,实时编译 +yarn nocobase dev +# 只启动服务端 +yarn nocobase dev --server +# 只启动客户端 +yarn nocobase dev --client +``` + +### `start` + +生产环境下,启动应用,代码需要 yarn build。 + + + +- NocoBase 未安装时,会自动安装(参考 install 命令) +- 源码有修改时,需要重新打包(参考 build 命令) + + + +```bash +$ yarn nocobase start -h + +Usage: nocobase start [options] + +Options: + -p, --port + -s, --silent + -h, --help +``` + +示例 + +```bash +# 启动应用,用于生产环境, +yarn nocobase start +``` + +### `install` + +安装 + +```bash +$ yarn nocobase install -h + +Usage: nocobase install [options] + +Options: + -f, --force + -c, --clean + -s, --silent + -l, --lang [lang] + -e, --root-email + -p, --root-password + -n, --root-nickname [rootNickname] + -h, --help +``` + +示例 + +```bash +# 初始安装 +yarn nocobase install -l zh-CN -e admin@nocobase.com -p admin123 +# 删除 NocoBase 的所有数据表,并重新安装 +yarn nocobase install -f -l zh-CN -e admin@nocobase.com -p admin123 +# 清空数据库,并重新安装 +yarn nocobase install -c -l zh-CN -e admin@nocobase.com -p admin123 +``` + + + +`-f/--force` 和 `-c/--clean` 的区别 +- `-f/--force` 删除 NocoBase 的数据表 +- `-c/--clean` 清空数据库,所有数据表都会被删除 + + + +### `upgrade` + +升级 + +```bash +yarn nocobase upgrade +``` + +### `test` + +jest 测试,支持所有 [jest-cli](https://jestjs.io/docs/cli) 的 options,除此之外还扩展了 `-c, --db-clean` 的支持。 + +```bash +$ yarn nocobase test -h + +Usage: nocobase test [options] + +Options: + -c, --db-clean 运行所有测试前清空数据库 + -h, --help +``` + +示例 + +```bash +# 运行所有测试文件 +yarn nocobase test +# 运行指定文件夹下所有测试文件 +yarn nocobase test packages/core/server +# 运行指定文件里的所有测试 +yarn nocobase test packages/core/database/src/__tests__/database.test.ts + +# 运行测试前,清空数据库 +yarn nocobase test -c +yarn nocobase test packages/core/server -c +``` + +### `build` + +代码部署到生产环境前,需要将源码编译打包,如果代码有修改,也需要重新构建。 + +```bash +# 所有包 +yarn nocobase build +# 指定包 +yarn nocobase build app/server app/client +``` + +### `clean` + +删除编译之后的文件 + +```bash +yarn clean +# 等同于 +yarn rimraf -rf packages/*/*/{lib,esm,es,dist} +``` + +### `doc` + +文档开发 + +```bash +# 启动文档 +yarn doc --lang=zh-CN # 等同于 yarn doc dev +# 构建文档,默认输出到 ./docs/dist/ 目录下 +yarn doc build +# 查看 dist 输出的文档最终效果 +yarn doc serve --lang=zh-CN +``` + +### `db:auth` + +校验数据库是否连接成功 + +```bash +$ yarn nocobase db:auth -h + +Usage: nocobase db:auth [options] + +Options: + -r, --retry [retry] 重试次数 + -h, --help +``` + +### `db:sync` + +通过 collections 配置生成数据表和字段 + +```bash +$ yarn nocobase db:sync -h + +Usage: nocobase db:sync [options] + +Options: + -f, --force + -h, --help display help for command +``` + +### `migrator` + +数据迁移 + +```bash +$ yarn nocobase migrator + +Positional arguments: + + up Applies pending migrations + down Revert migrations + pending Lists pending migrations + executed Lists executed migrations + create Create a migration file +``` + +### `pm` + +插件管理器 + +```bash +# 创建插件 +yarn pm create hello +# 注册插件 +yarn pm add hello +# 激活插件 +yarn pm enable hello +# 禁用插件 +yarn pm disable hello +# 删除插件 +yarn pm remove hello +``` + +未实现 + +```bash +# 升级插件 +yarn pm upgrade hello +# 发布插件 +yarn pm publish hello +``` + +### `umi` + +`app/client` 基于 [umi](https://umijs.org/) 构建,可以通过 `nocobase umi` 来执行其他相关命令。 + +```bash +# 生成开发环境所需的 .umi 缓存 +yarn nocobase umi generate tmp +``` + +### `help` + +帮助命令,也可以用 option 参数,`-h` 和 `--help` + +```bash +# 查看所有 cli +yarn nocobase help +# 也可以用 -h +yarn nocobase -h +# 或者 --help +yarn nocobase --help +# 查看 db:sync 命令的 option +yarn nocobase db:sync -h +``` diff --git a/docs/en-US/api/client/application.md b/docs/en-US/api/client/application.md new file mode 100644 index 000000000..a7f126b4a --- /dev/null +++ b/docs/en-US/api/client/application.md @@ -0,0 +1,62 @@ +# Application + +## 构造函数 + +### `constructor()` + +创建一个应用实例。 + +**签名** + +* `constructor(options: ApplicationOptions)` + +**示例** + +```ts +const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`../plugins/${name}`); + }, +}); +``` + +## 方法 + +### use() + +添加 Providers,内置 Providers 有: + +- APIClientProvider +- I18nextProvider +- AntdConfigProvider +- RemoteRouteSwitchProvider +- SystemSettingsProvider +- PluginManagerProvider +- SchemaComponentProvider +- SchemaInitializerProvider +- BlockSchemaComponentProvider +- AntdSchemaComponentProvider +- ACLProvider +- RemoteDocumentTitleProvider + +### render() + +渲染 App 组件 + +```ts +import { Application } from '@nocobase/client'; + +export const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`../plugins/${name}`); + }, +}); + +export default app.render(); +``` \ No newline at end of file diff --git a/docs/en-US/api/client/extensions/acl.md b/docs/en-US/api/client/extensions/acl.md new file mode 100644 index 000000000..09d8e9a94 --- /dev/null +++ b/docs/en-US/api/client/extensions/acl.md @@ -0,0 +1,23 @@ +# ACL + +## Components + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +## Hooks + +### `useACLContext()` + +### `useACLRoleContext()` + +### `useRoleRecheck()` diff --git a/docs/en-US/api/client/extensions/block-provider.md b/docs/en-US/api/client/extensions/block-provider.md new file mode 100644 index 000000000..6b3334a1a --- /dev/null +++ b/docs/en-US/api/client/extensions/block-provider.md @@ -0,0 +1,25 @@ +# BlockProvider + +## 内核方法 + +### `` + +### `useBlockRequestContext()` + +## 内置 BlockProvider 组件 + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` diff --git a/docs/en-US/api/client/extensions/collection-manager.md b/docs/en-US/api/client/extensions/collection-manager.md new file mode 100644 index 000000000..d4f65dfbf --- /dev/null +++ b/docs/en-US/api/client/extensions/collection-manager.md @@ -0,0 +1,267 @@ +# CollectionManager + +## Components + +### CollectionManagerProvider + +```jsx | pure + +``` + +### CollectionProvider + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + +``` + +如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 + +```jsx | pure +const collections = [ + { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], + } +]; + + + +``` + +### CollectionFieldProvider + +```jsx | pure +const field = { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, +}; + +``` + +如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + + + +``` + +### CollectionField + +万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 + +```ts +{ + name: 'title', + 'x-decorator': 'FormItem', + 'x-decorator-props': {}, + 'x-component': 'CollectionField', + 'x-component-props': {}, + properties: {}, +} +``` + +## Hooks + +### useCollectionManager() + +与 `` 搭配使用 + +```jsx | pure +const { collections, get } = useCollectionManager(); +``` + +### useCollection() + +与 `` 搭配使用 + +```jsx | pure +const { name, fields, getField, findField, resource } = useCollection(); +``` + +### useCollectionField() + +与 `` 搭配使用 + +```jsx | pure +const { name, uiSchema, resource } = useCollectionField(); +``` + +resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。如: + +# CollectionManager + +## Components + +### CollectionManagerProvider + +```jsx | pure + +``` + +### CollectionProvider + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + +``` + +如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 + +```jsx | pure +const collections = [ + { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], + } +]; + + + +``` + +### CollectionFieldProvider + +```jsx | pure +const field = { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, +}; + +``` + +如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + + + +``` + +### CollectionField + +万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 + +```ts +{ + name: 'title', + 'x-decorator': 'FormItem', + 'x-decorator-props': {}, + 'x-component': 'CollectionField', + 'x-component-props': {}, + properties: {}, +} +``` + +## Hooks + +### useCollectionManager() + +与 `` 搭配使用 + +```jsx | pure +const { collections, get } = useCollectionManager(); +``` + +### useCollection() + +与 `` 搭配使用 + +```jsx | pure +const { name, fields, getField, findField, resource } = useCollection(); +``` + +### useCollectionField() + +与 `` 搭配使用 + +```jsx | pure +const { name, uiSchema, resource } = useCollectionField(); +``` + +resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。 \ No newline at end of file diff --git a/docs/en-US/api/client/extensions/schema-component.md b/docs/en-US/api/client/extensions/schema-component.md new file mode 100644 index 000000000..832818f75 --- /dev/null +++ b/docs/en-US/api/client/extensions/schema-component.md @@ -0,0 +1,14 @@ +# 适配的 Schema 组件 + +## Common + +- DndContext +- SortableItem + +## And Design + +- Action +- BlockItem +- Calendar +- CardItem +- Cascader diff --git a/docs/en-US/api/client/index.md b/docs/en-US/api/client/index.md new file mode 100644 index 000000000..3a2170da0 --- /dev/null +++ b/docs/en-US/api/client/index.md @@ -0,0 +1,3 @@ +# Overview + +test diff --git a/docs/en-US/api/client/route-switch.md b/docs/en-US/api/client/route-switch.md new file mode 100644 index 000000000..3aacb7083 --- /dev/null +++ b/docs/en-US/api/client/route-switch.md @@ -0,0 +1,79 @@ +# RouteSwitch + +## `` + +```ts +interface RouteSwitchProviderProps { + components?: ReactComponent; + routes?: RouteRedirectProps[]; +} +``` + +## `` + +```ts +interface RouteSwitchProps { + routes?: RouteRedirectProps[]; + components?: ReactComponent; +} + +type RouteRedirectProps = RedirectProps | RouteProps; + +interface RedirectProps { + type: 'redirect'; + to: any; + path?: string; + exact?: boolean; + strict?: boolean; + push?: boolean; + from?: string; + [key: string]: any; +} + +interface RouteProps { + type: 'route'; + path?: string | string[]; + exact?: boolean; + strict?: boolean; + sensitive?: boolean; + component?: any; + routes?: RouteProps[]; + [key: string]: any; +} +``` + +## 完整示例 + +```tsx | pure +import React from 'react'; +import { Link, MemoryRouter as Router } from 'react-router-dom'; +import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client'; + +const Home = () =>

Home

; +const About = () =>

About

; + +const routes: RouteRedirectProps[] = [ + { + type: 'route', + path: '/', + exact: true, + component: 'Home', + }, + { + type: 'route', + path: '/about', + component: 'About', + }, +]; + +export default () => { + return ( + + + Home, About + + + + ); +}; +``` \ No newline at end of file diff --git a/docs/en-US/api/client/schema-designer/schema-component.md b/docs/en-US/api/client/schema-designer/schema-component.md new file mode 100644 index 000000000..61f646103 --- /dev/null +++ b/docs/en-US/api/client/schema-designer/schema-component.md @@ -0,0 +1,13 @@ +# SchemaComponent + +## 核心组件 + +### `` +### `` +### `` + +## 核心方法 + +### `createDesignable()` +### `useDesignable()` +### `useCompile()` diff --git a/docs/en-US/api/client/schema-designer/schema-initializer.md b/docs/en-US/api/client/schema-designer/schema-initializer.md new file mode 100644 index 000000000..1502fd496 --- /dev/null +++ b/docs/en-US/api/client/schema-designer/schema-initializer.md @@ -0,0 +1,27 @@ +# SchemaInitializer + +用于各种 schema 的初始化。新增的 schema 可以插入到某个已有 schema 节点的任意位置,包括: + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +SchemaInitializer 的核心包括 `` 和 `` 两个组件。`` 用于创建 Schema 的下拉菜单按钮,下拉菜单的菜单项为 ``。 + +### `` + +### `` + +### `` diff --git a/docs/en-US/api/client/schema-designer/schema-settings.md b/docs/en-US/api/client/schema-designer/schema-settings.md new file mode 100644 index 000000000..b014e7435 --- /dev/null +++ b/docs/en-US/api/client/schema-designer/schema-settings.md @@ -0,0 +1,25 @@ +# SchemaSettings + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` diff --git a/docs/en-US/api/database/collection.md b/docs/en-US/api/database/collection.md new file mode 100644 index 000000000..8be6651ea --- /dev/null +++ b/docs/en-US/api/database/collection.md @@ -0,0 +1,484 @@ +# Collection + +数据表结构管理类。 + +大部分接口通常不会直接由开发者调用,除非进行较底层的扩展开发。 + +## 构造函数 + +通常不会直接使用,主要通过 `Database` 实例的 `collection` 方法作为代理入口调用。 + +**签名** + +* `constructor(options: CollectionOptions, context: CollectionContext)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | collection 标识 | +| `options.tableName?` | `string` | - | 数据库表名,如不传则使用 `options.name` 的值 | +| `options.fields?` | `FieldOptions[]` | - | 字段定义,详见 [Field](./field) | +| `options.model?` | `string \| ModelCtor` | - | Sequelize 的 Model 类型,如果使用的是 `string`,则需要调用之前在 db 上注册过该模型名称 | +| `options.repository?` | `string \| RepositoryType` | - | 数据仓库类型,如果使用 `string`,则需要调用之前在 db 上注册过该仓库类型 | +| `options.sortable?` | `string \| boolean \| { name?: string; scopeKey?: string }` | - | 数据可排序字段配置,默认不排序 | +| `options.autoGenId?` | `boolean` | `true` | 是否自动生成唯一主键,默认为 `true` | +| `context.database` | `Database` | - | 所在的上下文环境数据库 | + +**示例** + +创建一张文章表: + +```ts +const posts = new Collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'double', + name: 'price', + } + ] +}, { + // 已存在的数据库实例 + database: db +}); +``` + +## 实例成员 + +### `options` + +数据表配置初始参数。与构造函数的 `options` 参数一致。 + +### `context` + +当前数据表所属的上下文环境,目前主要是数据库实例。 + +### `name` + +数据表名称。 + +### `db` + +所属数据库实例。 + +### `filterTargetKey` + +作为主键的字段名。 + +### `isThrough` + +是否为中间表。 + +### `model` + +匹配 Sequelize 的 Model 类型。 + +### `repository` + +数据仓库实例。 + +## 字段配置方法 + +### `getField()` + +获取数据表已定义对应名称的字段对象。 + +**签名** + +* `getField(name: string): Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +const field = posts.getField('title'); +``` + +### `setField()` + +对数据表设置字段。 + +**签名** + +* `setField(name: string, options: FieldOptions): Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | +| `options` | `FieldOptions` | - | 字段配置,详见 [Field](./field) | + +**示例** + +```ts +const posts = db.collection({ name: 'posts' }); + +posts.setField('title', { type: 'string' }); +``` + +### `setFields()` + +对数据表批量设置多个字段。 + +**签名** + +* `setFields(fields: FieldOptions[], resetFields = true): Field[]` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `fields` | `FieldOptions[]` | - | 字段配置,详见 [Field](./field) | +| `resetFields` | `boolean` | `true` | 是否重置已存在的字段 | + +**示例** + +```ts +const posts = db.collection({ name: 'posts' }); + +posts.setFields([ + { type: 'string', name: 'title' }, + { type: 'double', name: 'price' } +]); +``` + +### `removeField()` + +移除数据表已定义对应名称的字段对象。 + +**签名** + +* `removeField(name: string): void | Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.removeField('title'); +``` + +### `resetFields()` + +重置(清空)数据表的字段。 + +**签名** + +* `resetFields(): void` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.resetFields(); +``` + +### `hasField()` + +判断数据表是否已定义对应名称的字段对象。 + +**签名** + +* `hasField(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.hasField('title'); // true +``` + +### `findField()` + +查找数据表中符合条件的字段对象。 + +**签名** + +* `findField(predicate: (field: Field) => boolean): Field | undefined` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `predicate` | `(field: Field) => boolean` | - | 查找条件 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.findField(field => field.name === 'title'); +``` + +### `forEachField()` + +遍历数据表中的字段对象。 + +**签名** + +* `forEachField(callback: (field: Field) => void): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `callback` | `(field: Field) => void` | - | 回调函数 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.forEachField(field => console.log(field.name)); +``` + +## 索引配置方法 + +### `addIndex()` + +添加数据表索引。 + +**签名** + +* `addIndex(index: string | string[] | { fields: string[], unique?: boolean,[key: string]: any })` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `index` | `string \| string[]` | - | 需要配置索引的字段名 | +| `index` | `{ fields: string[], unique?: boolean, [key: string]: any }` | - | 完整配置 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.addIndex({ + fields: ['title'], + unique: true +}); +``` + +### `removeIndex()` + +移除数据表索引。 + +**签名** + +* `removeIndex(fields: string[])` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `fields` | `string[]` | - | 需要移除索引的字段名组合 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ], + indexes: [ + { + fields: ['title'], + unique: true + } + ] +}); + +posts.removeIndex(['title']); +``` + +## 表配置方法 + +### `remove()` + +删除数据表。 + +**签名** + +* `remove(): void` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.remove(); +``` + +## 数据库操作方法 + +### `sync()` + +同步数据表定义到数据库。除了 Sequelize 中默认的 `Model.sync` 的逻辑,还会一并处理关系字段对应的数据表。 + +**签名** + +* `sync(): Promise` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +await posts.sync(); +``` + +### `existsInDb()` + +判断数据表是否存在于数据库中。 + +**签名** + +* `existsInDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options?.transaction` | `Transaction` | - | 事务实例 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +const existed = await posts.existsInDb(); + +console.log(existed); // false +``` + +### `removeFromDb()` + +**签名** + +* `removeFromDb(): Promise` + +**示例** + +```ts +const books = db.collection({ + name: 'books' +}); + +// 同步书籍表到数据库 +await db.sync(); + +// 删除数据库中的书籍表 +await books.removeFromDb(); +``` diff --git a/docs/en-US/api/database/field.md b/docs/en-US/api/database/field.md new file mode 100644 index 000000000..340b97cde --- /dev/null +++ b/docs/en-US/api/database/field.md @@ -0,0 +1,554 @@ +# Field + +数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。 + +## 构造函数 + +通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。 + +在扩展字段时主要通过继承 `Field` 抽象类,再注册到 Database 实例中来实现。 + +**签名** + +* `constructor(options: FieldOptions, context: FieldContext)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `FieldOptions` | - | 字段配置对象 | +| `options.name` | `string` | - | 字段名称 | +| `options.type` | `string` | - | 字段类型,对应在 db 中注册的字段类型名称 | +| `context` | `FieldContext` | - | 字段上下文对象 | +| `context.database` | `Database` | - | 数据库实例 | +| `context.collection` | `Collection` | - | 数据表实例 | + +## 实例成员 + +### `name` + +字段名称。 + +### `type` + +字段类型。 + +### `dataType` + +字段数据库存储类型。 + +### `options` + +字段初始化配置参数。 + +### `context` + +字段上下文对象。 + +## 配置方法 + +### `on()` + +基于数据表事件的快捷定义方式。相当于 `db.on(this.collection.name + '.' + eventName, listener)`。 + +继承时通常无需覆盖此方法。 + +**签名** + +* `on(eventName: string, listener: (...args: any[]) => void)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `eventName` | `string` | - | 事件名称 | +| `listener` | `(...args: any[]) => void` | - | 事件监听器 | + +### `off()` + +基于数据表事件的快捷移除方式。相当于 `db.off(this.collection.name + '.' + eventName, listener)`。 + +继承时通常无需覆盖此方法。 + +**签名** + +* `off(eventName: string, listener: (...args: any[]) => void)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `eventName` | `string` | - | 事件名称 | +| `listener` | `(...args: any[]) => void` | - | 事件监听器 | + +### `bind()` + +当字段被添加到数据表时触发的执行内容。通常用于添加数据表事件监听器和其他处理。 + +继承时需要先调用对应的 `super.bind()` 方法。 + +**签名** + +* `bind()` + +### `unbind()` + +当字段从数据表中移除时触发的执行内容。通常用于移除数据表事件监听器和其他处理。 + +继承时需要先调用对应的 `super.unbind()` 方法。 + +**签名** + +* `unbind()` + +### `get()` + +获取字段的配置项的值。 + +**签名** + +* `get(key: string): any` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `key` | `string` | - | 配置项名称 | + +**示例** + +```ts +const field = db.collection('users').getField('name'); + +// 获取字段名称配置项的值,返回 'name' +console.log(field.get('name')); +``` + +### `merge()` + +合并字段的配置项的值。 + +**签名** + +* `merge(options: { [key: string]: any }): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `{ [key: string]: any }` | - | 要合并的配置项对象 | + +**示例** + +```ts +const field = db.collection('users').getField('name'); + +field.merge({ + // 添加一个索引配置 + index: true +}); +``` + +### `remove()` + +从数据表中移除字段(仅从内存中移除)。 + +**示例** + +```ts +const books = db.getCollections('books'); + +books.getField('isbn').remove(); + +// really remove from db +await books.sync(); +``` + +## 数据库方法 + +### `removeFromDb()` + +从数据库中移除字段。 + +**签名** + +* `removeFromDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.transaction?` | `Transaction` | - | 事务实例 | + +### `existsInDb()` + +判断字段是否存在于数据库中。 + +**签名** + +* `existsInDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.transaction?` | `Transaction` | - | 事务实例 | + +## 内置字段类型列表 + +NocoBase 内置了一些常用的字段类型,可以直接在定义数据表的字段是使用对应的 type 名称来指定类型。不同类型的字段参数配置不同,具体可参考下面的列表。 + +所有字段类型的配置项除了以下额外介绍的以外,都会透传至 Sequelize,所以所有 Sequelize 支持的字段配置项都可以在这里使用(如 `allowNull`、`defaultValue` 等)。 + +另外 server 端的字段类型主要解决数据库存储和部分算法的问题,与前端的字段展示类型和使用组件基本无关。前端字段类型可以参考教程对应说明。 + +### `'boolean'` + +逻辑值类型。 + +**示例** + +```js +db.collection({ + name: 'books', + fields: [ + { + type: 'boolean', + name: 'published' + } + ] +}); +``` + +### `'integer'` + +整型(32 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'integer', + name: 'pages' + } + ] +}); +``` + +### `'bigInt'` + +长整型(64 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'bigInt', + name: 'words' + } + ] +}); +``` + +### `'double'` + +双精度浮点型(64 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'double', + name: 'price' + } + ] +}); +``` + +### `'real'` + +实数类型(仅 PG 适用)。 + +### `'decimal'` + +十进制小数类型。 + +### `'string'` + +字符串类型。相当于大部分数据库的 `VARCHAR` 类型。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'string', + name: 'title' + } + ] +}); +``` + +### `'text'` + +文本类型。相当于大部分数据库的 `TEXT` 类型。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'text', + name: 'content' + } + ] +}); +``` + +### `'password'` + +密码类型(NocoBase 扩展)。基于 Node.js 原生的 crypto 包的 `scrypt` 方法进行密码加密。 + +**示例** + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'password', + name: 'password', + length: 64, // 长度,默认 64 + randomBytesSize: 8 // 随机字节长度,默认 8 + } + ] +}); +``` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `length` | `number` | 64 | 字符长度 | +| `randomBytesSize` | `number` | 8 | 随机字节大小 | + +### `'date'` + +日期类型。 + +### `'time'` + +时间类型。 + +### `'array'` + +数组类型(仅 PG 适用)。 + +### `'json'` + +JSON 类型。 + +### `'jsonb'` + +JSONB 类型(仅 PG 适用,其他会被兼容为 `'json'` 类型)。 + +### `'uuid'` + +UUID 类型。 + +### `'uid'` + +UID 类型(NocoBase 扩展)。短随机字符串标识符类型。 + +### `'formula'` + +公式类型(NocoBase 扩展)。可配置基于 [mathjs](https://www.npmjs.com/package/mathjs) 的数学公式计算,公式中可以引用同一条记录中其他列的数值参与计算。 + +**示例** + +```ts +db.collection({ + name: 'orders', + fields: [ + { + type: 'double', + name: 'price' + }, + { + type: 'integer', + name: 'quantity' + }, + { + type: 'formula', + name: 'total', + expression: 'price * quantity' + } + ] +}); +``` + +### `'radio'` + +单选类型(NocoBase 扩展)。全表最多有一行数据的该字段值为 `true`,其他都为 `false` 或 `null`。 + +**示例** + +整个系统只有一个被标记为 root 的用户,任意另一个用户的 root 值被改为 `true` 之后,其他所有 root 为 `true` 的记录均会被修改为 `false`: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'radio', + name: 'root', + } + ] +}); +``` + +### `'sort'` + +排序类型(NocoBase 扩展)。基于整型数字进行排序,为新记录自动生成新序号,当移动数据时进行序号重排。 + +数据表如果定义了 `sortable` 选项,也会自动生成对应字段。 + +**示例** + +文章基于所属用户可排序: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'sort', + name: 'priority', + scopeKey: 'userId' // 以 userId 相同值分组的数据进行排序 + } + ] +}); +``` + +### `'virtual'` + +虚拟类型。不实际储存数据,仅用于特殊 getter/setter 定义时使用。 + +### `'belongsTo'` + +多对一关联类型。外键储存在自身表,与 hasOne/hasMany 相对。 + +**示例** + +任意文章属于某个作者: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsTo', + name: 'author', + target: 'users', // 不配置默认为 name 复数名称的表名 + foreignKey: 'authorId', // 不配置默认为 + Id 的格式 + sourceKey: 'id' // 不配置默认为 target 表的 id + } + ] +}); +``` + +### `'hasOne'` + +一对一关联类型。外键储存在关联表,与 belongsTo 相对。 + +**示例** + +任意用户都有一份个人资料: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'hasOne', + name: 'profile', + target: 'profiles', // 可省略 + } + ] +}) +``` + +### `'hasMany'` + +一对多关联类型。外键储存在关联表,与 belongsTo 相对。 + +**示例** + +任意用户可以拥有多篇文章: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'hasMany', + name: 'posts', + foreignKey: 'authorId', + sourceKey: 'id' + } + ] +}); +``` + +### `'belongsToMany'` + +多对多关联类型。使用中间表储存双方外键,如不指定已存在的表为中间表的话,将会自动创建中间表。 + +**示例** + +任意文章可以加任意多个标签,任意标签也可以被任意多篇文章添加: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsToMany', + name: 'tags', + target: 'tags', // 同名可省略 + through: 'postsTags', // 中间表不配置将自动生成 + foreignKey: 'postId', // 自身表在中间表的外键 + sourceKey: 'id', // 自身表的主键 + otherKey: 'tagId' // 关联表在中间表的外键 + } + ] +}); + +db.collection({ + name: 'tags', + fields: [ + { + type: 'belongsToMany', + name: 'posts', + through: 'postsTags', // 同一组关系指向同一张中间表 + } + ] +}); +``` diff --git a/docs/en-US/api/database/index.md b/docs/en-US/api/database/index.md new file mode 100644 index 000000000..975fa52aa --- /dev/null +++ b/docs/en-US/api/database/index.md @@ -0,0 +1,1130 @@ +# Database + +NocoBase 内置的数据库访问类,通过封装 [Sequelize](https://sequelize.org/) 提供了更加简单的数据库访问接口和统一化的 JSON 数据库表配置方式,同时也提供了扩展字段类型和查询操作符的能力。 + +Database 类继承自 EventEmitter,可以通过 `db.on('event', callback)` 监听数据库事件,以及 `db.off('event', callback)` 移除监听。 + +## 包结构 + +可通过以下方式引入相关实体: + +```ts +import Database, { + Field, + Collection, + Repository, + RelationRepository, + extend +} from '@nocobase/database'; +``` + +## 构造函数 + +**签名** + +* `constructor(options: DatabaseOptions)` + +创建一个数据库实例。 + +**参数** + +`options` 参数与 [Sequelize 的构造参数](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor)一致的部分会透传至 Sequelize,同时 NocoBase 也会使用一些额外的参数: + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.host` | `string` | `'localhost'` | 数据库主机 | +| `options.port` | `number` | - | 数据库服务端口,根据使用的数据库有对应默认端口 | +| `options.username` | `string` | - | 数据库用户名 | +| `options.password` | `string` | - | 数据库密码 | +| `options.database` | `string` | - | 数据库名称 | +| `options.dialect` | `string` | `'mysql'` | 数据库类型 | +| `options.storage?` | `string` | `':memory:'` | SQLite 的存储模式 | +| `options.logging?` | `boolean` | `false` | 是否开启日志 | +| `options.define?` | `Object` | `{}` | 默认的表定义参数 | +| `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 | +| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 | + +**示例** + +```ts +import Database from '@nocobase/database'; + +const app = new Database({ + dialect: 'mysql', + host: 'localhost', + port: 3306, + username: 'root', + password: '123456', + database: 'test', + tablePrefix: 'my_' +}); +``` + +## 实例成员 + +### `sequelize` + +初始化后的 Sequelize 实例,在需要使用 sequelize 底层方法时可以调用,相关信息可以直接参考 sequelize 的文档。 + +### `options` + +初始化的配置参数,包含了 Sequelize 的配置参数和 NocoBase 的额外配置参数。 + +### `version` + +连接的数据库的版本信息对象,可通过 `await db.version.satisfies()` 检查是否满足特定数据库版本要求。 + +**示例** + +```ts +const r = await this.db.version.satisfies({ + mysql: '>=8.0.17', + sqlite: '3.x', + postgres: '>=10', +}); +``` + +## 迁移相关方法 + +### `addMigration()` + +添加单个迁移文件。 + +**签名** + +* `addMigration(options: MigrationItem)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | 迁移文件名称 | +| `options.context?` | `string` | - | 迁移文件的 `up` 方法 | +| `options.migration?` | `typeof Migration` | - | 迁移文件的自定义类 | +| `options.up` | `Function` | - | 迁移文件的 `up` 方法 | +| `options.down` | `Function` | - | 迁移文件的 `down` 方法 | + +**示例** + +```ts +db.addMigration({ + name: '20220916120411-test-1', + async up() { + const queryInterface = this.context.db.sequelize.getQueryInterface(); + await queryInterface.query(/* your migration sqls */); + } +}); +``` + +### `addMigrations()` + +添加指定目录下的迁移文件。 + +**签名** + +* `addMigrations(options: AddMigrationsOptions): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.directory` | `string` | `''` | 迁移文件所在目录 | +| `options.extensions` | `string[]` | `['js', 'ts']` | 文件扩展名 | +| `options.namespace?` | `string` | `''` | 命名空间 | +| `options.context?` | `Object` | `{ db }` | 迁移文件的上下文 | + +**示例** + +```ts +db.addMigrations({ + directory: path.resolve(__dirname, './migrations'), + namespace: 'test' +}); +``` + +## 工具方法 + +### `inDialect()` + +判断当前数据库类型是否为指定类型。 + +**签名** + +* `inDialect(dialect: string[]): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `dialect` | `string[]` | - | 数据库类型,可选值为 `mysql`/`postgres`/`sqlite` | + +### `getTablePrefix()` + +获取配置中的表名前缀。 + +**签名** + +* `getTablePrefix(): string` + +## 数据表配置 + +### `collection()` + +定义一个数据表。该调用类似与 Sequelize 的 `define` 方法,只在内存中创建表结构,如需持久化到数据库,需要调用 `sync` 方法。 + +**签名** + +* `collection(options: CollectionOptions): Collection` + +**参数** + +`options` 所有配置参数与 `Collection` 类的构造函数一致,参考 [Collection](/api/server/database/collection#构造函数)。 + +**事件** + +* `'beforeDefineCollection'`:在定义表之前触发。 +* `'afterDefineCollection'`:在定义表之后触发。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'float', + name: 'price', + } + ] +}); + +// sync collection as table to db +await db.sync(); +``` + +### `getCollection()` + +获取已定义的数据表。 + +**签名** + +* `getCollection(name: string): Collection` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**示例** + +```ts +const collection = db.getCollection('books'); +``` + +### `hasCollection()` + +判断是否已定义指定的数据表。 + +**签名** + +* `hasCollection(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**示例** + +```ts +db.collection({ name: 'books' }); + +db.hasCollection('books'); // true + +db.hasCollection('authors'); // false +``` + +### `removeCollection()` + +移除已定义的数据表。仅在内存中移除,如需持久化,需要调用 `sync` 方法。 + +**签名** + +* `removeCollection(name: string): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**事件** + +* `'beforeRemoveCollection'`:在移除表之前触发。 +* `'afterRemoveCollection'`:在移除表之后触发。 + +**示例** + +```ts +db.collection({ name: 'books' }); + +db.removeCollection('books'); +``` + +### `import()` + +导入文件目录下所有文件作为 collection 配置载入内存。 + +**签名** + +* `async import(options: { directory: string; extensions?: ImportFileExtension[] }): Promise>` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.directory` | `string` | - | 要导入的目录路径 | +| `options.extensions` | `string[]` | `['ts', 'js']` | 扫描特定后缀 | + +**示例** + +`./collections/books.ts` 文件定义的 collection 如下: + +```ts +export default { + name: 'books', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}; +``` + +在插件加载时导入相关配置: + +```ts +class Plugin { + async load() { + await this.app.db.import({ + directory: path.resolve(__dirname, './collections'), + }); + } +} +``` + +## 扩展注册与获取 + +### `registerFieldTypes()` + +注册自定义字段类型。 + +**签名** + +* `registerFieldTypes(fieldTypes: MapOf): void` + +**参数** + +`fieldTypes` 是一个键值对,键为字段类型名称,值为字段类型类。 + +**示例** + +```ts +import { Field } from '@nocobase/database'; + +class MyField extends Field { + // ... +} + +db.registerFieldTypes({ + myField: MyField, +}); +``` + +### `registerModels()` + +注册自定义数据模型类。 + +**签名** + +* `registerModels(models: MapOf>): void` + +**参数** + +`models` 是一个键值对,键为数据模型名称,值为数据模型类。 + +**示例** + +```ts +import { Model } from '@nocobase/database'; + +class MyModel extends Model { + // ... +} + +db.registerModels({ + myModel: MyModel, +}); + +db.collection({ + name: 'myCollection', + model: 'myModel' +}); +``` + +### `registerRepositories()` + +注册自定义数据仓库类。 + +**签名** + +* `registerRepositories(repositories: MapOf): void` + +**参数** + +`repositories` 是一个键值对,键为数据仓库名称,值为数据仓库类。 + +**示例** + +```ts +import { Repository } from '@nocobase/database'; + +class MyRepository extends Repository { + // ... +} + +db.registerRepositories({ + myRepository: MyRepository, +}); + +db.collection({ + name: 'myCollection', + repository: 'myRepository' +}); +``` + +### `registerOperators()` + +注册自定义数据查询操作符。 + +**签名** + +* `registerOperators(operators: MapOf)` + +**参数** + +`operators` 是一个键值对,键为操作符名称,值为操作符比较语句生成函数。 + +**示例** + +```ts +db.registerOperators({ + $dateOn(value) { + return { + [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }], + }; + } +}); + +db.getRepository('books').count({ + filter: { + createdAt: { + // registered operator + $dateOn: '2020-01-01', + } + } +}); +``` + +### `getModel()` + +获取已定义的数据模型类。如果没有在之前注册自定义模型类,将返回 Sequelize 默认的模型类。默认名称与 collection 定义的名称相同。 + +**签名** + +* `getModel(name: string): Model` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 已注册的模型名 | + +**示例** + +```ts +db.registerModels({ + books: class MyModel extends Model {} +}); + +const ModelClass = db.getModel('books'); + +console.log(ModelClass.prototype instanceof MyModel) // true +``` + +注:从 collection 中获取的模型类并不与注册时的模型类严格相等,而是继承自注册时的模型类。由于 Sequelize 的模型类在初始化过程中属性会被修改,所以 NocoBase 自动处理了这个继承关系。除类不相等以外,其他所有定义都可以正常使用。 + +### `getRepository()` + +获取自定义的数据仓库类。如果没有在之前注册自定义数据仓库类,将返回 NocoBase 默认的数据仓库类。默认名称与 collection 定义的名称相同。 + +数据仓库类主要用于基于数据模型的增删改查等操作,参考 [数据仓库](/api/server/database/repository)。 + +**签名** + +* `getRepository(name: string): Repository` +* `getRepository(name: string, relationId?: string | number): Repository` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 已注册的数据仓库名 | +| `relationId` | `string` \| `number` | - | 关系数据的外键值 | + +当名称是形如 `'tables.relactions'` 的带关联的名称时,将返回关联的数据仓库类。如果提供了第二个参数,数据仓库在使用时(查询、修改等)会基于关系数据的外键值。 + +**示例** + +假设有两张数据表_文章_与_作者_,并且文章表中有一个外键指向作者表: + +```ts +const AuthorsRepo = db.getRepository('authors'); +const author1 = AuthorsRepo.create({ name: 'author1' }); + +const PostsRepo = db.getRepository('authors.posts', author1.id); +const post1 = AuthorsRepo.create({ title: 'post1' }); +asset(post1.authorId === author1.id); // true +``` + +## 数据库事件 + +### `on()` + +监听数据库事件。 + +**签名** + +* `on(event: string, listener: (...args: any[]) => void | Promise): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| event | string | - | 事件名称 | +| listener | Function | - | 事件监听器 | + +事件名称默认支持 Sequelize 的 Model 事件。针对全局事件,通过 `` 的名称方式监听,针对单 Model 事件,通过 `.` 的名称方式监听。 + +所有内置的事件类型的参数说明和详细示例参考 [内置事件](#内置事件) 部分内容。 + +### `off()` + +移除事件监听函数。 + +**签名** + +* `off(name: string, listener: Function)` + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| name | string | - | 事件名称 | +| listener | Function | - | 事件监听器 | + +**示例** + +```ts +const listener = async (model, options) => { + console.log(model); +}; + +db.on('afterCreate', listener); + +db.off('afterCreate', listener); +``` + +## 数据库操作 + +### `auth()` + +数据库连接验证。可以用于确保应用与数据已建立连接。 + +**签名** + +* `auth(options: QueryOptions & { retry?: number } = {}): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options?` | `Object` | - | 验证选项 | +| `options.retry?` | `number` | `10` | 验证失败时重试次数 | +| `options.transaction?` | `Transaction` | - | 事务对象 | +| `options.logging?` | `boolean \| Function` | `false` | 是否打印日志 | + +**示例** + +```ts +await db.auth(); +``` + +### `reconnect()` + +重新连接数据库。 + +**示例** + +```ts +await db.reconnect(); +``` + +### `closed()` + +判断数据库是否已关闭连接。 + +**签名** + +* `closed(): boolean` + +### `close()` + +关闭数据库连接。等同于 `sequelize.close()`。 + +### `sync()` + +同步数据库表结构。等同于 `sequelize.sync()`,参数参考 [Sequelize 文档](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-method-sync)。 + +### `clean()` + +清空数据库,将删除所有数据表。 + +**签名** + +* `clean(options: CleanOptions): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.drop` | `boolean` | `false` | 是否移除所有数据表 | +| `options.skip` | `string[]` | - | 跳过的表名配置 | +| `options.transaction` | `Transaction` | - | 事务对象 | + +**示例** + +移除除 `users` 表以外的所有表。 + +```ts +await db.clean({ + drop: true, + skip: ['users'] +}) +``` + +## 包级导出 + +### `defineCollection()` + +创建一个数据表的配置内容。 + +**签名** + +* `defineCollection(name: string, config: CollectionOptions): CollectionOptions` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | + +**示例** + +对于要被 `db.import()` 导入的数据表配置文件: + +```ts +import { defineCollection } from '@nocobase/database'; + +export default defineCollection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + }, + ], +}); +``` + +### `extendCollection()` + +扩展已在内存中的表结构配置内容,主要用于 `import()` 方法导入的文件内容。该方法是 `@nocobase/database` 包导出的顶级方法,不通过 db 实例调用。也可以使用 `extend` 别名。 + +**签名** + +* `extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions): ExtendedCollectionOptions` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | +| `mergeOptions?` | `MergeOptions` | - | npm 包 [deepmerge](https://npmjs.com/package/deepmerge) 的参数 | + +**示例** + +原始 books 表定义(books.ts): + +```ts +export default { + name: 'books', + fields: [ + { name: 'title', type: 'string' } + ] +} +``` + +扩展 books 表定义(books.extend.ts): + +```ts +import { extend } from '@nocobase/database'; + +// 再次扩展 +export default extend({ + name: 'books', + fields: [ + { name: 'price', type: 'number' } + ] +}); +``` + +以上两个文件如在调用 `import()` 时导入,通过 `extend()` 再次扩展以后,books 表将拥有 `title` 和 `price` 两个字段。 + +此方法在扩展已有插件已定义的表结构时非常有用。 + +## 内置事件 + +数据库会在相应的生命周期触发以下对应的事件,通过 `on()` 方法订阅后进行特定的处理可满足一些业务需要。 + +### `'beforeSync'` / `'afterSync'` + +当新的表结构配置(字段、索引等)被同步到数据库前后触发,通常在执行 `collection.sync()`(内部调用)时会触发,一般用于一些特殊的字段扩展的逻辑处理。 + +**签名** + +```ts +on(eventName: `${string}.beforeSync` | 'beforeSync' | `${string}.afterSync` | 'afterSync', listener: SyncListener): this +``` + +**类型** + +```ts +import type { SyncOptions, HookReturn } from 'sequelize/types'; + +type SyncListener = (options?: SyncOptions) => HookReturn; +``` + +**示例** + +```ts +const users = db.collection({ + name: 'users', + fields: [ + { type: 'string', name: 'username' } + ] +}); + +db.on('beforeSync', async (options) => { + // do something +}); + +db.on('users.afterSync', async (options) => { + // do something +}); + +await users.sync(); +``` + +### `'beforeValidate'` / `'afterValidate'` + +创建或更新数据前会有基于 collection 定义的规则对数据的验证过程,在验证前后会触发对应事件。当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeValidate` | 'beforeValidate' | `${string}.afterValidate` | 'afterValidate', listener: ValidateListener): this +``` + +**类型** + +```ts +import type { ValidationOptions } from 'sequelize/types/lib/instance-validator'; +import type { HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +type ValidateListener = (model: Model, options?: ValidationOptions) => HookReturn; +``` + +**示例** + +```ts +db.collection({ + name: 'tests', + fields: [ + { + type: 'string', + name: 'email', + validate: { + isEmail: true, + }, + } + ], +}); + +// all models +db.on('beforeValidate', async (model, options) => { + // do something +}); +// tests model +db.on('tests.beforeValidate', async (model, options) => { + // do something +}); + +// all models +db.on('afterValidate', async (model, options) => { + // do something +}); +// tests model +db.on('tests.afterValidate', async (model, options) => { + // do something +}); + +const repository = db.getRepository('tests'); +await repository.create({ + values: { + email: 'abc', // checks for email format + }, +}); +// or +await repository.update({ + filterByTk: 1, + values: { + email: 'abc', // checks for email format + }, +}); +``` + +### `'beforeCreate'` / `'afterCreate'` + +创建一条数据前后会触发对应事件,当调用 `repository.create()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeCreate` | 'beforeCreate' | `${string}.afterCreate` | 'afterCreate', listener: CreateListener): this +``` + +**类型** + +```ts +import type { CreateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type CreateListener = (model: Model, options?: CreateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeCreate', async (model, options) => { + // do something +}); + +db.on('books.afterCreate', async (model, options) => { + const { transaction } = options; + const result = await model.constructor.findByPk(model.id, { + transaction + }); + console.log(result); +}); +``` + +### `'beforeUpdate'` / `'afterUpdate'` + +更新一条数据前后会触发对应事件,当调用 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeUpdate` | 'beforeUpdate' | `${string}.afterUpdate` | 'afterUpdate', listener: UpdateListener): this +``` + +**类型** + +```ts +import type { UpdateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type UpdateListener = (model: Model, options?: UpdateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeUpdate', async (model, options) => { + // do something +}); + +db.on('books.afterUpdate', async (model, options) => { + // do something +}); +``` + +### `'beforeSave'` / `'afterSave'` + +创建或更新一条数据前后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeSave` | 'beforeSave' | `${string}.afterSave` | 'afterSave', listener: SaveListener): this +``` + +**类型** + +```ts +import type { SaveOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type SaveListener = (model: Model, options?: SaveOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeSave', async (model, options) => { + // do something +}); + +db.on('books.afterSave', async (model, options) => { + // do something +}); +``` + +### `'beforeDestroy'` / `'afterDestroy'` + +删除一条数据前后会触发对应事件,当调用 `repository.destroy()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeDestroy` | 'beforeDestroy' | `${string}.afterDestroy` | 'afterDestroy', listener: DestroyListener): this +``` + +**类型** + +```ts +import type { DestroyOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type DestroyListener = (model: Model, options?: DestroyOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeDestroy', async (model, options) => { + // do something +}); + +db.on('books.afterDestroy', async (model, options) => { + // do something +}); +``` + +### `'afterCreateWithAssociations'` + +创建一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterCreateWithAssociations` | 'afterCreateWithAssociations', listener: CreateWithAssociationsListener): this +``` + +**类型** + +```ts +import type { CreateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type CreateWithAssociationsListener = (model: Model, options?: CreateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterCreateWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterCreateWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'afterUpdateWithAssociations'` + +更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterUpdateWithAssociations` | 'afterUpdateWithAssociations', listener: CreateWithAssociationsListener): this +``` + +**类型** + +```ts +import type { UpdateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type UpdateWithAssociationsListener = (model: Model, options?: UpdateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterUpdateWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterUpdateWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'afterSaveWithAssociations'` + +创建或更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterSaveWithAssociations` | 'afterSaveWithAssociations', listener: SaveWithAssociationsListener): this +``` + +**类型** + +```ts +import type { SaveOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type SaveWithAssociationsListener = (model: Model, options?: SaveOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterSaveWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterSaveWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'beforeDefineCollection'` + +当定义一个数据表之前触发,如调用 `db.collection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'beforeDefineCollection', listener: BeforeDefineCollectionListener): this +``` + +**类型** + +```ts +import type { CollectionOptions } from '@nocobase/database'; + +export type BeforeDefineCollectionListener = (options: CollectionOptions) => void; +``` + +**示例** + +```ts +db.on('beforeDefineCollection', (options) => { + // do something +}); +``` + +### `'afterDefineCollection'` + +当定义一个数据表之后触发,如调用 `db.collection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'afterDefineCollection', listener: AfterDefineCollectionListener): this +``` + +**类型** + +```ts +import type { Collection } from '@nocobase/database'; + +export type AfterDefineCollectionListener = (options: Collection) => void; +``` + +**示例** + +```ts +db.on('afterDefineCollection', (collection) => { + // do something +}); +``` + +### `'beforeRemoveCollection'` / `'afterRemoveCollection'` + +当从内存中移除一个数据表前后触发,如调用 `db.removeCollection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'beforeRemoveCollection' | 'afterRemoveCollection', listener: RemoveCollectionListener): this +``` + +**类型** + +```ts +import type { Collection } from '@nocobase/database'; + +export type RemoveCollectionListener = (options: Collection) => void; +``` + +**示例** + +```ts +db.on('beforeRemoveCollection', (collection) => { + // do something +}); + +db.on('afterRemoveCollection', (collection) => { + // do something +}); +``` diff --git a/docs/en-US/api/database/operators.md b/docs/en-US/api/database/operators.md new file mode 100644 index 000000000..468d5f0e4 --- /dev/null +++ b/docs/en-US/api/database/operators.md @@ -0,0 +1,816 @@ +# Filter Operators + +用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数里。如: + +```ts +const repository = db.getRepository('books'); + +repository.find({ + filter: { + title: { + $eq: '春秋', + } + } +}); +``` + +相当于 Sequelize Where 查询的 [Op](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators) 对象。 + +为了支持 JSON 化,NocoBase 中将查询运算符转换为以 $ 为前缀的字符串标识。 + +另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。 + +## 通用运算符 + +### `$eq` + +判断字段值是否相等于指定值。相当于 SQL 的 `=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $eq: '春秋', + } + } +}); +``` + +等同于 `title: '春秋'`。 + +### `$ne` + +判断字段值是否不等于指定值。相当于 SQL 的 `!=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $ne: '春秋', + } + } +}); +``` + +### `$is` + +判断字段值是否为指定值。相当于 SQL 的 `IS`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $is: null, + } + } +}); +``` + +### `$not` + +判断字段值是否不为指定值。相当于 SQL 的 `IS NOT`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $not: null, + } + } +}); +``` + +### `$col` + +判断字段值是否等于另一个字段的值。相当于 SQL 的 `=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $col: 'name', + } + } +}); +``` + +### `$in` + +判断字段值是否在指定数组中。相当于 SQL 的 `IN`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $in: ['春秋', '战国'], + } + } +}); +``` + +### `$notIn` + +判断字段值是否不在指定数组中。相当于 SQL 的 `NOT IN`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIn: ['春秋', '战国'], + } + } +}); +``` + +### `$empty` + +判断一般字段是否为空,如果是字符串字段,判断是否为空串,如果是数组字段,判断是否为空数组。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $empty: true, + } + } +}); +``` + +### `$notEmpty` + +判断一般字段是否不为空,如果是字符串字段,判断是否不为空串,如果是数组字段,判断是否不为空数组。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notEmpty: true, + } + } +}); +``` + +## 逻辑运算符 + +### `$and` + +逻辑 AND。相当于 SQL 的 `AND`。 + +**示例** + +```ts +repository.find({ + filter: { + $and: [ + { title: '诗经' }, + { isbn: '1234567890' }, + ] + } +}); +``` + +### `$or` + +逻辑 OR。相当于 SQL 的 `OR`。 + +**示例** + +```ts +repository.find({ + filter: { + $or: [ + { title: '诗经' }, + { publishedAt: { $lt: '0000-00-00T00:00:00Z' } }, + ] + } +}); +``` + +## 布尔类型字段运算符 + +用于布尔类型字段 `type: 'boolean'` + +### `$isFalsy` + +判断布尔类型字段值是否为假。布尔字段值为 `false`、`0` 和 `NULL` 的情况都会被判断为 `$isFalsy: true`。 + +**示例** + +```ts +repository.find({ + filter: { + isPublished: { + $isFalsy: true, + } + } +}) +``` + +### `$isTruly` + +判断布尔类型字段值是否为真。布尔字段值为 `true` 和 `1` 的情况都会被判断为 `$isTruly: true`。 + +**示例** + +```ts +repository.find({ + filter: { + isPublished: { + $isTruly: true, + } + } +}) +``` + +## 数字类型字段运算符 + +用于数字类型字段,包括: + +- `type: 'integer'` +- `type: 'float'` +- `type: 'double'` +- `type: 'real'` +- `type: 'decimal'` + +### `$gt` + +判断字段值是否大于指定值。相当于 SQL 的 `>`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $gt: 100, + } + } +}); +``` + +### `$gte` + +判断字段值是否大于等于指定值。相当于 SQL 的 `>=`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $gte: 100, + } + } +}); +``` + +### `$lt` + +判断字段值是否小于指定值。相当于 SQL 的 `<`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $lt: 100, + } + } +}); +``` + +### `$lte` + +判断字段值是否小于等于指定值。相当于 SQL 的 `<=`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $lte: 100, + } + } +}); +``` + +### `$between` + +判断字段值是否在指定的两个值之间。相当于 SQL 的 `BETWEEN`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $between: [100, 200], + } + } +}); +``` + +### `$notBetween` + +判断字段值是否不在指定的两个值之间。相当于 SQL 的 `NOT BETWEEN`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $notBetween: [100, 200], + } + } +}); +``` + +## 字符串类型字段运算符 + +用于字符串类型字段,包括 `string` + +### `$includes` + +判断字符串字段是否包含指定子串。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $includes: '三字经', + } + } +}) +``` + +### `$notIncludes` + +判断字符串字段是否不包含指定子串。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIncludes: '三字经', + } + } +}) +``` + +### `$startsWith` + +判断字符串字段是否以指定子串开头。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $startsWith: '三字经', + } + } +}) +``` + +### `$notStatsWith` + +判断字符串字段是否不以指定子串开头。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notStatsWith: '三字经', + } + } +}) +``` + +### `$endsWith` + +判断字符串字段是否以指定子串结尾。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $endsWith: '三字经', + } + } +}) +``` + +### `$notEndsWith` + +判断字符串字段是否不以指定子串结尾。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notEndsWith: '三字经', + } + } +}) +``` + +### `$like` + +判断字段值是否包含指定的字符串。相当于 SQL 的 `LIKE`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $like: '计算机', + } + } +}); +``` + +### `$notLike` + +判断字段值是否不包含指定的字符串。相当于 SQL 的 `NOT LIKE`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notLike: '计算机', + } + } +}); +``` + +### `$iLike` + +判断字段值是否包含指定的字符串,忽略大小写。相当于 SQL 的 `ILIKE`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $iLike: 'Computer', + } + } +}); +``` + +### `$notILike` + +判断字段值是否不包含指定的字符串,忽略大小写。相当于 SQL 的 `NOT ILIKE`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notILike: 'Computer', + } + } +}); +``` + +### `$regexp` + +判断字段值是否匹配指定的正则表达式。相当于 SQL 的 `REGEXP`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $regexp: '^计算机', + } + } +}); +``` + +### `$notRegexp` + +判断字段值是否不匹配指定的正则表达式。相当于 SQL 的 `NOT REGEXP`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notRegexp: '^计算机', + } + } +}); +``` + +### `$iRegexp` + +判断字段值是否匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `~*`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $iRegexp: '^COMPUTER', + } + } +}); +``` + +### `$notIRegexp` + +判断字段值是否不匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `!~*`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIRegexp: '^COMPUTER', + } + } +}); +``` + +## 日期类型字段运算符 + +用于日期类型字段 `type: 'date'` + +### `$dateOn` + +判断日期字段是否在某天内。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateOn: '2021-01-01', + } + } +}) +``` + +### `$dateNotOn` + +判断日期字段是否不在某天内。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotOn: '2021-01-01', + } + } +}) +``` + +### `$dateBefore` + +判断日期字段是否在某个值之前。相当于小于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateBefore: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateNotBefore` + +判断日期字段是否不在某个值之前。相当于大于等于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotBefore: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateAfter` + +判断日期字段是否在某个值之后。相当于大于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateAfter: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateNotAfter` + +判断日期字段是否不在某个值之后。相当于小于等于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotAfter: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +## 数组类型字段运算符 + +用于数组类型字段 `type: 'array'` + +### `$match` + +判断数组字段的值是否匹配指定数组中的值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $match: ['文学', '历史'], + } + } +}) +``` + +### `$notMatch` + +判断数组字段的值是否不匹配指定数组中的值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $notMatch: ['文学', '历史'], + } + } +}) +``` + +### `$anyOf` + +判断数组字段的值是否包含指定数组中的任意值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $anyOf: ['文学', '历史'], + } + } +}) +``` + +### `$noneOf` + +判断数组字段的值是否不包含指定数组中的任意值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $noneOf: ['文学', '历史'], + } + } +}) +``` + +### `$arrayEmpty` + +判断数组字段是否为空。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $arrayEmpty: true, + } + } +}); +``` + +### `$arrayNotEmpty` + +判断数组字段是否不为空。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $arrayNotEmpty: true, + } + } +}); +``` + +## 关系字段类型运算符 + +用于判断关系是否存在,字段类型包括: + +- `type: 'hasOne'` +- `type: 'hasMany'` +- `type: 'belongsTo'` +- `type: 'belongsToMany'` + +### `$exists` + +有关系数据 + +**示例** + +```ts +repository.find({ + filter: { + author: { + $exists: true, + } + } +}); +``` + +### `$notExists` + +无关系数据 + +**示例** + +```ts +repository.find({ + filter: { + author: { + $notExists: true, + } + } +}); +``` diff --git a/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md b/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md new file mode 100644 index 000000000..1c612ed46 --- /dev/null +++ b/docs/en-US/api/database/relation-repository/belongs-to-many-repository.md @@ -0,0 +1,181 @@ +# BelongsToManyRepository +`BelongsToManyRepository` 是用于处理 `BelongsToMany` 关系的 `Relation Repository`。 + +不同于其他关系类型,`BelongsToMany` 类型的关系需要通过中间表来记录。 +在 `Nocobase` 中定义关联关系,可自动创建中间表,也可以明确指定中间表。 + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +### `findOne()` + +查找关联对象,仅返回一条记录 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + + +### `count()` + +返回符合查询条件的记录数 + +**签名** + +* `async count(options?: CountOptions)` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` + +**类型** +```typescript +type FindAndCountOptions = CommonFindOptions +``` + +### `create()` + +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +### `update()` + +更新符合条件的关联对象 + +**签名** + +* `async update(options?: UpdateOptions): Promise` + + + +### `destroy()` + +删除符合条件的关联对象 + +**签名** + +* `async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise` + + + +### `add()` + +添加新的关联对象 + +**签名** + +* `async add( + options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions + ): Promise` + +**类型** + +```typescript +type PrimaryKeyWithThroughValues = [TargetKey, Values]; + +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; +} +``` + +**详细信息** + +可以直接传入关联对象的 `targetKey`,也可将 `targetKey` 与中间表的字段值一并传入。 + +**示例** +```typescript +const t1 = await Tag.repository.create({ + values: { name: 't1' }, +}); + +const t2 = await Tag.repository.create({ + values: { name: 't2' }, +}); + +const p1 = await Post.repository.create({ + values: { title: 'p1' }, +}); + +const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.id); + +// 传入 targetKey +PostTagRepository.add([ + t1.id, t2.id +]); + +// 传入中间表字段 +PostTagRepository.add([ + [t1.id, { tagged_at: '123' }], + [t2.id, { tagged_at: '456' }], +]); +``` + +### `set()` + +设置关联对象 + +**签名** +* async set( + options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions, + ): Promise + +**详细信息** + +参数同 [add()](#add) + +### `remove()` + +移除与给定对象之间的关联关系 + +**签名** +* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**类型** +```typescript +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[]; +} +``` + +### `toggle()` + +切换关联对象。 + +在一些业务场景中,经常需要切换关联对象,比如用户收藏商品,用户可以取消收藏,也可以再次收藏。使用 `toggle` 方法可以快速实现类似功能。 + +**签名** + +* `async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promise` + +**详细信息** + +`toggle` 方法会自动判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。 diff --git a/docs/en-US/api/database/relation-repository/belongs-to-repository.md b/docs/en-US/api/database/relation-repository/belongs-to-repository.md new file mode 100644 index 000000000..15824a3d2 --- /dev/null +++ b/docs/en-US/api/database/relation-repository/belongs-to-repository.md @@ -0,0 +1,3 @@ +## BelongsToRepository + +`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](./has-one-repository.md) 一致。 diff --git a/docs/en-US/api/database/relation-repository/has-many-repository.md b/docs/en-US/api/database/relation-repository/has-many-repository.md new file mode 100644 index 000000000..2e4b2be42 --- /dev/null +++ b/docs/en-US/api/database/relation-repository/has-many-repository.md @@ -0,0 +1,132 @@ + +# HasManyRepository + +`HasManyRepository` 是用于处理 `HasMany` 关系的 `Relation Repository`。 + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +### `findOne()` + +查找关联对象,仅返回一条记录 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + + +### `count()` + +返回符合查询条件的记录数 + +**签名** + +* `async count(options?: CountOptions)` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` + +**类型** +```typescript +type FindAndCountOptions = CommonFindOptions +``` + + +### `create()` + +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +### `update()` + +更新符合条件的关联对象 + +**签名** + +* `async update(options?: UpdateOptions): Promise` + + + +### `destroy()` + +删除符合条件的关联对象 + +**签名** + +* `async destroy(options?: TK | DestroyOptions): Promise` + + + +### `add()` + +添加对象关联关系 + +**签名** +* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**类型** +```typescript +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[]; +} +``` + +**详细信息** + +* `tk` - 关联对象的 targetKey 值,可以是单个值,也可以是数组。 + + +### `remove()` + +移除与给定对象之间的关联关系 + +**签名** +* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**详细信息** + +参数同 [`add()`](#add) 方法。 + +### `set()` + +设置当前关系的关联对象 + +**签名** + +* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**详细信息** + +参数同 [`add()`](#add) 方法。 + + diff --git a/docs/en-US/api/database/relation-repository/has-one-repository.md b/docs/en-US/api/database/relation-repository/has-one-repository.md new file mode 100644 index 000000000..fd1037604 --- /dev/null +++ b/docs/en-US/api/database/relation-repository/has-one-repository.md @@ -0,0 +1,181 @@ +# HasOneRepository +`HasOneRepository` 为 `HasOne` 类型的关联 Repository。 + +```typescript +const User = db.collection({ + name: 'users', + fields: [ + { type: 'hasOne', name: 'profile' }, + { type: 'string', name: 'name' }, + ], +}); + +const Profile = db.collection({ + name: 'profiles', + fields: [{ type: 'string', name: 'avatar' }], +}); + +const user = await User.repository.create({ + values: { name: 'u1' }, +}); + +// 创建 HasOneRepository 实例 +const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id')); + +``` + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: SingleRelationFindOption): Promise | null>` + +**类型** + +```typescript +interface SingleRelationFindOption extends Transactionable { + fields?: Fields; + except?: Except; + appends?: Appends; + filter?: Filter; +} +``` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +**示例** + +```typescript +const profile = await UserProfileRepository.find(); +// 关联对象不存在时,返回 null +``` + +### `create()` +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +**示例** + +```typescript +const profile = await UserProfileRepository.create({ + values: { avatar: 'avatar1' }, +}); + +console.log(profile.toJSON()); +/* +{ + id: 1, + avatar: 'avatar1', + userId: 1, + updatedAt: 2022-09-24T13:59:40.025Z, + createdAt: 2022-09-24T13:59:40.025Z +} +*/ + +``` + +### `update()` + +更新关联对象 + +**签名** + +* `async update(options: UpdateOptions): Promise` + + + + +**示例** + +```typescript +const profile = await UserProfileRepository.update({ + values: { avatar: 'avatar2' }, +}); + +profile.get('avatar'); // 'avatar2' +``` + +### `remove()` + +移除关联对象,仅解除关联关系,不删除关联对象 + +**签名** + +* `async remove(options?: Transactionable): Promise` + +**详细信息** + +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +await UserProfileRepository.remove(); +await UserProfileRepository.find() == null; // true + +await Profile.repository.count() === 1; // true +``` + +### `destroy()` + +删除关联对象 + +**签名** + +* `async destroy(options?: Transactionable): Promise` + + +**详细信息** + +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +await UserProfileRepository.destroy(); +await UserProfileRepository.find() == null; // true +await Profile.repository.count() === 0; // true +``` + +### `set()` + +设置关联对象 + +**签名** + +* `async set(options: TargetKey | SetOption): Promise` + +**类型** + +```typescript +interface SetOption extends Transactionable { + tk?: TargetKey; +} +```` +**详细信息** + +* tk: 设置关联对象的 targetKey +* transaction: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +const newProfile = await Profile.repository.create({ + values: { avatar: 'avatar2' }, +}); + +await UserProfileRepository.set(newProfile.get('id')); + +(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true +``` diff --git a/docs/en-US/api/database/relation-repository/index.md b/docs/en-US/api/database/relation-repository/index.md new file mode 100644 index 000000000..27140968d --- /dev/null +++ b/docs/en-US/api/database/relation-repository/index.md @@ -0,0 +1,45 @@ +# RelationRepository + +`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为 + +* [`HasOneRepository`](#has-one-repository) +* `HasManyRepository` +* `BelongsToRepository` +* `BelongsToManyRepository` + + +## 构造函数 + +**签名** + +* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection | +| `association` | `string` | - | 关联名称 | +| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 | + + +## 基类属性 + +### `db: Database` + +数据库对象 + +### `sourceCollection` +关联中的参照关系(referencing relation)对应的 Collection + +### `targetCollection` +关联中被参照关系(referenced relation)对应的 Collection + +### `association` +sequelize 中的与当前关联对应的 association 对象 + +### `associationField` +collection 中的与当前关联对应的字段 + +### `sourceKeyValue` +参照关系中对应的 key 值 diff --git a/docs/en-US/api/database/repository.md b/docs/en-US/api/database/repository.md new file mode 100644 index 000000000..f80fde92f --- /dev/null +++ b/docs/en-US/api/database/repository.md @@ -0,0 +1,352 @@ +# Repository + +数据表数据仓库管理类。大部分基于数据表的数据存取等操作均通过该类实现。 + +## 构造函数 + +通常不会直接由开发者调用,主要通过 `db.registerRepositories()` 注册类型以后,在 `db.colletion()` 的参数中指定对应已注册的仓库类型,并完成实例化。 + +**签名** + +* `constructor(collection: Collection)` + +**示例** + +```ts +import { Repository } from '@nocobase/database'; + +class MyRepository extends Repository { + async myQuery(sql) { + return this.database.sequelize.query(sql); + } +} + +db.registerRepositories({ + books: MyRepository +}); + +db.collection({ + name: 'books', + // here link to the registered repository + repository: 'books' +}); + +await db.sync(); + +const books = db.getRepository('books') as MyRepository; +await books.myQuery('SELECT * FROM books;'); +``` + +## 实例成员 + +### `database` + +上下文所在的数据库管理实例。 + +### `collection` + +对应的数据表管理实例。 + +### `model` + +对应的数据模型类。 + +## 实例方法 + + +### `find()` + +从数据库查询特定条件的数据集。相当于 Sequelize 中的 `Model.findAll()`。 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**类型** +```typescript +type Filter = FilterWithOperator | FilterWithValue | FilterAnd | FilterOr; +type Appends = string[]; +type Except = string[]; +type Fields = string[]; +type Sort = string[] | string; + +interface SequelizeFindOptions { + limit?: number; + offset?: number; +} + +interface FilterByTk { + filterByTk?: TargetKey; +} + +interface CommonFindOptions extends Transactionable { + filter?: Filter; + fields?: Fields; + appends?: Appends; + except?: Except; + sort?: Sort; +} + +type FindOptions = SequelizeFindOptions & CommonFindOptions & FilterByTk; +``` + +**详细信息** + +#### `filter: Filter` +查询条件,用于过滤数据结果。传入的查询参数中,`key` 为查询的字段名,`value` 可传要查询的值, +也可配合使用操作符进行其他条件的数据筛选。 + +```typescript +// 查询 name 为 foo,并且 age 大于 18 的记录 +repository.find({ + filter: { + name: "foo", + age: { + $gt: 18, + }, + } +}) +``` +更多操作符请参考 [查询操作符](./operators.md)。 + +#### `filterByTk: TargetKey` +通过 `TargetKey` 查询数据,为 `filter` 参数的便捷方法。`TargetKey` 具体是哪一个字段, +可在 `Collection` 中进行[配置](./collection.md#filtertargetkey),默认为 `primaryKey`。 + +```typescript + +// 默认情况下,查找 id 为 1 的记录 +repository.find({ + filterByTk: 1, +}); + +``` + +#### `fields: string[]` +查询列,用户控制数据字段结果。传入此参数之后,只会返回指定的字段。 + +#### `except: string[]` +排除列,用于控制数据字段结果。传入此参数之后,传入的字段将不会输出。 + +#### `appends: string[]` +追加列,用于加载关联数据。传入此参数之后,指定的关联字段将一并输出。 + +#### `sort: string[] | string` +指定查询结果排序方式,传入参数为字段名称,默认按照升序 `asc` 排序,若需按降序 `desc` 排序, +可在字段名称前加上 `-` 符号,如:`['-id', 'name']`,表示按 `id desc, name asc` 排序。 + +#### `limit: number` +限制结果数量,同 `SQL` 中的 `limit` + +#### `offset: number` +查询偏移量,同 `SQL` 中的 `offset` + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const results = await posts.find({ + filter: { + createdAt: { + $gt: '2022-01-01T00:00:00.000Z', + } + }, + fields: ['title'], + appends: ['user'], +}); +``` + +### `findOne()` + +从数据库查询特定条件的单条数据。相当于 Sequelize 中的 `Model.findOne()`。 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.findOne({ + filterByTk: 1, +}); +``` + +### `count()` + +从数据库查询特定条件的数据总数。相当于 Sequelize 中的 `Model.count()`。 + +**签名** + +* `count(options?: CountOptions): Promise` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +**示例** + +```ts +const books = db.getRepository('books'); + +const count = await books.count({ + filter: { + title: '三字经' + } +}); +``` + + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。相当于 Sequelize 中的 `Model.findAndCountAll()`。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]>` + +**类型** +```typescript +type FindAndCountOptions = Omit & CommonFindOptions; +``` + +**详细信息** + +查询参数与 `find()` 相同。返回值为一个数组,第一个元素为查询结果,第二个元素为结果总数。 + +### `create()` + +向数据表插入一条新创建的数据。相当于 Sequelize 中的 `Model.create()`。当要创建的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 + +**签名** + +* `async create(options: CreateOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.create({ + values: { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, +}); +``` + +### `createMany()` + +向数据表插入多条新创建的数据。相当于多次调用 `create()` 方法。 + +**签名** + +* `createMany(options: CreateManyOptions): Promise` + +**类型** +```typescript +interface CreateManyOptions extends BulkCreateOptions { + records: Values[]; +} +``` + +**详细信息** + +* `records`:要创建的记录的数据对象数组。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const results = await posts.createMany({ + records: [ + { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, + { + title: 'NocoBase 1.1 发布日志', + tags: [ + { id: 1 } + ] + }, + ], +}); +``` + +### `update()` + +更新数据表中的数据。相当于 Sequelize 中的 `Model.update()`。当要更新的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 + +**签名** + +* `async update(options: UpdateOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.update({ + filterByTk: 1, + values: { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, +}); +``` + +### `destory()` + +删除数据表中的数据。相当于 Sequelize 中的 `Model.destroy()`。 + +**签名** + +* `async destory(options?: TargetKey | TargetKey[] | DestoryOptions): Promise` + +**类型** + +```typescript +interface DestroyOptions extends SequelizeDestroyOptions { + filter?: Filter; + filterByTk?: TargetKey | TargetKey[]; + truncate?: boolean; + context?: any; +} +``` + +**详细信息** + +* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 +* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared.md b/docs/en-US/api/database/shared.md new file mode 100644 index 000000000..fd2b409fb --- /dev/null +++ b/docs/en-US/api/database/shared.md @@ -0,0 +1,8 @@ +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.values` | `M` | `{}` | 插入的数据对象 | +| `options.whitelist?` | `string[]` | - | `values` 字段的白名单,只有名单内的字段会被存储 | +| `options.blacklist?` | `string[]` | - | `values` 字段的黑名单,名单内的字段不会被存储 | +| `options.transaction?` | `Transaction` | - | 事务 | diff --git a/docs/en-US/api/database/shared/create-options.md b/docs/en-US/api/database/shared/create-options.md new file mode 100644 index 000000000..51b245a45 --- /dev/null +++ b/docs/en-US/api/database/shared/create-options.md @@ -0,0 +1,21 @@ +**类型** +```typescript +type WhiteList = string[]; +type BlackList = string[]; +type AssociationKeysToBeUpdate = string[]; + +interface CreateOptions extends SequelizeCreateOptions { + values?: Values; + whitelist?: WhiteList; + blacklist?: BlackList; + updateAssociationValues?: AssociationKeysToBeUpdate; + context?: any; +} +``` + +**详细信息** + +* `values`:要创建的记录的数据对象。 +* `whitelist`:指定要创建的记录的数据对象中,哪些字段**可以被写入**。若不传入此参数,则默认允许所有字段写入。 +* `blacklist`:指定要创建的记录的数据对象中,哪些字段**不允许被写入**。若不传入此参数,则默认允许所有字段写入。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/destroy-options.md b/docs/en-US/api/database/shared/destroy-options.md new file mode 100644 index 000000000..aaf8e63b9 --- /dev/null +++ b/docs/en-US/api/database/shared/destroy-options.md @@ -0,0 +1,17 @@ +**类型** + +```typescript +interface DestroyOptions extends SequelizeDestroyOptions { + filter?: Filter; + filterByTk?: TargetKey | TargetKey[]; + truncate?: boolean; + context?: any; +} +``` + +**详细信息** + +* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 +* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/find-one.md b/docs/en-US/api/database/shared/find-one.md new file mode 100644 index 000000000..47e5f2192 --- /dev/null +++ b/docs/en-US/api/database/shared/find-one.md @@ -0,0 +1,8 @@ +**类型** +```typescript +type FindOneOptions = Omit; +``` + +**参数** + +大部分参数与 `find()` 相同,不同之处在于 `findOne()` 只返回单条数据,所以不需要 `limit` 参数,且查询时始终为 `1`。 diff --git a/packages/core/cli/templates/plugin/src/server/actions/.gitkeep b/docs/en-US/api/database/shared/find-options.md similarity index 100% rename from packages/core/cli/templates/plugin/src/server/actions/.gitkeep rename to docs/en-US/api/database/shared/find-options.md diff --git a/docs/en-US/api/database/shared/transaction.md b/docs/en-US/api/database/shared/transaction.md new file mode 100644 index 000000000..27068fcf9 --- /dev/null +++ b/docs/en-US/api/database/shared/transaction.md @@ -0,0 +1 @@ +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/en-US/api/database/shared/update-options.md b/docs/en-US/api/database/shared/update-options.md new file mode 100644 index 000000000..ea03c3ea7 --- /dev/null +++ b/docs/en-US/api/database/shared/update-options.md @@ -0,0 +1,24 @@ +**类型** +```typescript +interface UpdateOptions extends Omit { + values: Values; + filter?: Filter; + filterByTk?: TargetKey; + whitelist?: WhiteList; + blacklist?: BlackList; + updateAssociationValues?: AssociationKeysToBeUpdate; + context?: any; +} +``` + +**详细信息** + + +* `values`:要更新的记录的数据对象。 +* `filter`:指定要更新的记录的过滤条件, Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要更新的记录的过滤条件。 +* `whitelist`: `values` 字段的白名单,只有名单内的字段会被写入。 +* `blacklist`: `values` 字段的黑名单,名单内的字段不会被写入。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +`filterByTk` 与 `filter` 至少要传其一。 diff --git a/docs/en-US/api/env.md b/docs/en-US/api/env.md new file mode 100644 index 000000000..e3140bfff --- /dev/null +++ b/docs/en-US/api/env.md @@ -0,0 +1,225 @@ +# 环境变量 + +## 全局环境变量 + +保存在 `.env` 文件里 + +### APP_ENV + +应用环境,默认值 `development`,可选项包括: + +- `production` 生产环境 +- `development` 开发环境 + +```bash +APP_ENV=production +``` + +### APP_HOST + +应用主机,默认值 `0.0.0.0` + +```bash +APP_HOST=192.168.3.154 +``` + +### APP_PORT + +应用端口,默认值 `13000` + +```bash +APP_PORT=13000 +``` + +### APP_KEY + +秘钥,用于 jwt 等场景 + +```bash +APP_KEY=app-key-test +``` + +### API_BASE_PATH + +NocoBase API 地址前缀,默认值 `/api/` + +```bash +API_BASE_PATH=/api/ +``` + +### PLUGIN_PACKAGE_PREFIX + +插件包前缀,默认值 `@nocobase/plugin-,@nocobase/preset-` + +例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。 + +PLUGIN_PACKAGE_PREFIX 配置如下: + +```bash +PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- +``` + +插件名和包名的对应关系为: + +- `users` 插件包名为 `@nocobase/plugin-users` +- `nocobase` 插件包名为 `@nocobase/preset-nocobase` +- `hello` 插件包名为 `@my-nocobase-app/plugin-hello` + +### DB_DIALECT + +数据库类型,默认值 `sqlite`,可选项包括: + +- `sqlite` +- `mysql` +- `postgres` + +```bash +DB_DIALECT=mysql +``` + +### DB_STORAGE + +数据库文件路径(使用 SQLite 数据库时配置) + +```bash +# 相对路径 +DB_HOST=storage/db/nocobase.db +# 绝对路径 +DB_HOST=/your/path/nocobase.db +``` + +### DB_HOST + +数据库主机(使用 mysql 或 postgres 数据库时需要配置) + +默认值 `localhost` + +```bash +DB_HOST=localhost +``` + +### DB_PORT + +数据库端口(使用 mysql 或 postgres 数据库时需要配置) + +- MySQL 默认端口 3306 +- PostgreSQL 默认端口 5432 + +```bash +DB_PORT=3306 +``` + +### DB_DATABASE + +数据库名(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_DATABASE=nocobase +``` + +### DB_USER + +数据库用户(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_USER=nocobase +``` + +### DB_PASSWORD + +数据库密码(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_PASSWORD=nocobase +``` + +### DB_TABLE_PREFIX + +数据表前缀 + +```bash +DB_TABLE_PREFIX=nocobase_ +``` + +### DB_LOGGING + +数据库日志开关,默认值 `off`,可选项包括: + +- `on` 打开 +- `off` 关闭 + +```bash +DB_LOGGING=on +``` + +## 临时环境变量 + +安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如: + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + INIT_ROOT_NICKNAME="Super Admin" \ + nocobase install + +# 等同于 +yarn nocobase install \ + --lang=zh-CN \ + --root-email=demo@nocobase.com \ + --root-password=admin123 \ + --root-nickname="Super Admin" + +# 等同于 +yarn nocobase install -l zh-CN -e demo@nocobase.com -p admin123 -n "Super Admin" +``` + +### INIT_APP_LANG + +安装时的语言,默认值 `en-US`,可选项包括: + +- `en-US` +- `zh-CN` + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + nocobase install +``` + +### INIT_ROOT_EMAIL + +Root 用户邮箱 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + nocobase install +``` + +### INIT_ROOT_PASSWORD + +Root 用户密码 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + nocobase install +``` + +### INIT_ROOT_NICKNAME + +Root 用户昵称 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + INIT_ROOT_NICKNAME="Super Admin" \ + nocobase install +``` diff --git a/docs/en-US/api/index.md b/docs/en-US/api/index.md new file mode 100644 index 000000000..cb8199c15 --- /dev/null +++ b/docs/en-US/api/index.md @@ -0,0 +1,12 @@ +# 概览 + +| 模块 | 包名 | 描述 | +| --------------------------------- | --------------------- | ------------------- | +| [Server](/api/server) | `@nocobase/server` | 服务端应用 | +| [Database](/api/database) | `@nocobase/database` | 数据库访问层 | +| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 | +| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 | +| [Client](/api/client/application) | `@nocobase/client` | 客户端应用 | +| [CLI](/api/cli) | `@nocobase/cli` | NocoBase 命令行工具 | +| [SDK](/api/sdk) | `@nocobase/sdk` | NocoBase SDK | +| [Actions](/api/actions) | `@nocobase/actions` | 内置常用资源操作 | diff --git a/docs/en-US/api/resourcer/action.md b/docs/en-US/api/resourcer/action.md new file mode 100644 index 000000000..9b5a0968a --- /dev/null +++ b/docs/en-US/api/resourcer/action.md @@ -0,0 +1,148 @@ +# Action + +Action 是对资源的操作过程的描述,通常包含数据库处理等,类似其他框架中的 service 层,最简化的实现可以是一个 Koa 的中间件函数。在资源管理器里,针对特定资源定义的普通操作函数会被包装成 Action 类型的实例,当请求匹配对应资源的操作时,执行对应的操作过程。 + +## 构造函数 + +通常不需要直接实例化 Action,而是由资源管理器自动调用 `Action` 的静态方法 `toInstanceMap()` 进行实例化。 + +### `constructor(options: ActionOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `handler` | `Function` | - | 操作函数 | +| `middlewares?` | `Middleware \| Middleware[]` | - | 针对操作的中间件 | +| `values?` | `Object` | - | 默认的操作数据 | +| `fields?` | `string[]` | - | 默认针对的字段组 | +| `appends?` | `string[]` | - | 默认附加的关联字段组 | +| `except?` | `string[]` | - | 默认排除的字段组 | +| `filter` | `FilterOptions` | - | 默认的过滤参数 | +| `sort` | `string[]` | - | 默认的排序参数 | +| `page` | `number` | - | 默认的页码 | +| `pageSize` | `number` | - | 默认的每页数量 | +| `maxPageSize` | `number` | - | 默认最大每页数量 | + +## 实例成员 + +### `actionName` + +被实例化后对应的操作名称。在实例化时从请求中解析获取。 + +### `resourceName` + +被实例化后对应的资源名称。在实例化时从请求中解析获取。 + +### `resourceOf` + +被实例化后对应的关系资源的主键值。在实例化时从请求中解析获取。 + +### `readonly middlewares` + +针对操作的中间件列表。 + +### `params` + +操作参数。包含对应操作的所有相关参数,实例化时根据定义的 action 参数初始化,之后请求中解析前端传入的参数并根据对应参数的合并策略合并。如果有其他中间件的处理,也会有类似的合并过程。直到 handler 处理时,访问 params 得到的是经过多次合并的最终参数。 + +参数的合并过程提供了针对操作处理的可扩展性,可以通过自定义中间件的方式按业务需求进行参数的前置解析和处理,例如表单提交的参数验证就可以在此环节实现。 + +预设的参数可以参考 [/api/actions] 中不同操作的参数。 + +参数中还包含请求资源路由的描述部分,具体如下: + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `resourceName` | `string` | - | 资源名称 | +| `resourceIndex` | `string \| number` | - | 资源的主键值 | +| `associatedName` | `string` | - | 所属关系资源的名称 | +| `associatedIndex` | `string \| number` | - | 所属关系资源的主键值 | +| `associated` | `Object` | - | 所属关系资源的实例 | +| `actionName` | `string` | - | 操作名称 | + +**示例** + +```ts +app.resourcer.define('books', { + actions: { + publish(ctx, next) { + ctx.body = ctx.action.params.values; + // { + // id: 1234567890 + // publishedAt: '2019-01-01', + // } + } + }, + middlewares: [ + async (ctx, next) => { + ctx.action.mergeParams({ + values: { + id: Math.random().toString(36).substr(2, 10), + publishedAt: new Date(), + } + }); + await next(); + } + ] +}); +``` + +## 实例方法 + +### `mergeParams()` + +将额外的参数合并至当前参数集,且可以根据不同的策略进行合并。 + +**签名** + +* `mergeParams(params: ActionParams, strategies: MergeStrategies = {})` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `params` | `ActionParams` | - | 额外的参数集 | +| `strategies` | `MergeStrategies` | - | 针对每个参数的合并策略 | + +内置操作的默认合并策略如下表: + +| 参数名 | 类型 | 默认值 | 合并策略 | 描述 | +| --- | --- | --- | --- | --- | +| `filterByTk` | `number \| string` | - | SQL `and` | 查询主键值 | +| `filter` | `FilterOptions` | - | SQL `and` | 查询过滤参数 | +| `fields` | `string[]` | - | 取并集 | 字段组 | +| `appends` | `string[]` | `[]` | 取并集 | 附加的关联字段组 | +| `except` | `string[]` | `[]` | 取并集 | 排除的字段组 | +| `whitelist` | `string[]` | `[]` | 取交集 | 可处理字段的白名单 | +| `blacklist` | `string[]` | `[]` | 取并集 | 可处理字段的黑名单 | +| `sort` | `string[]` | - | SQL `order by` | 查询排序参数 | +| `page` | `number` | - | 覆盖 | 页码 | +| `pageSize` | `number` | - | 覆盖 | 每页数量 | +| `values` | `Object` | - | 深度合并 | 操作提交的数据 | + +**示例** + +```ts +ctx.action.mergeParams({ + filter: { + name: 'foo', + }, + fields: ['id', 'name'], + except: ['name'], + sort: ['id'], + page: 1, + pageSize: 10, + values: { + name: 'foo', + }, +}, { + filter: 'and', + fields: 'union', + except: 'union', + sort: 'overwrite', + page: 'overwrite', + pageSize: 'overwrite', + values: 'deepMerge', +}); +``` diff --git a/docs/en-US/api/resourcer/index.md b/docs/en-US/api/resourcer/index.md new file mode 100644 index 000000000..d1eb0c49b --- /dev/null +++ b/docs/en-US/api/resourcer/index.md @@ -0,0 +1,252 @@ +# Resourcer + +Resourcer 主要用于管理 API 资源与路由,也是 NocoBase 的内置模块,app 默认会自动创建一个 Resourcer 实例,大部分情况你可以通过 `app.resourcer` 访问。 + +资源路由管理器主要通过 [资源](/api/server/resourcer/resource) + [操作](/api/server/resourcer/action) 的概念定义服务端 API 接口,与 RESTful 的概念相似。大部分资源通过映射数据库表生成,包含常规的 CRUD 操作,以覆盖常见场景。但如果有额外需求,也可以在此基础上扩展更多的资源类型和操作类型。 + +## 包结构 + +可通过以下方式引入相关实体: + +```ts +import Resourcer, { + Resource, + Action, + Middleware, + branch +} from '@nocobase/resourcer'; +``` + +## 构造函数 + +用于创建 Resourcer 管理器实例。由于 app 默认创建一个内置实例,所以通常不会直接使用构造函数。 + +**签名** + +* `constructor(options: ResourcerOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `prefix` | `string` | - | 路由路径前缀 | +| `accessors` | `Object` | _以下成员值_ | 默认操作方法名称标识 | +| `accessors.list` | `string` | `'list'` | 列举操作方法名称标识 | +| `accessors.get` | `string` | `'get'` | 获取操作方法名称标识 | +| `accessors.create` | `string` | `'create'` | 创建操作方法名称标识 | +| `accessors.update` | `string` | `'update'` | 更新操作方法名称标识 | +| `accessors.delete` | `string` | `'destroy'` | 删除操作方法名称标识 | +| `accessors.add` | `string` | `'add'` | 增加关联操作方法名称标识 | +| `accessors.remove` | `string` | `'remove'` | 移除关联操作方法名称标识 | +| `accessors.set` | `string` | `'set'` | 全量设置关联操作方法名称标识 | + +**示例** + +在创建 app 时件时,可以通过 `resourcer` 选项传入: + +```ts +const app = new Application({ + // 对应默认 resourcer 实例的配置项 + resourcer: { + prefix: process.env.API_BASE_PATH + } +}); +``` + +## 实例方法 + +### `define()` + +定义并向资源管理器注册一个资源对象。通常代替 `Resource` 类的构造函数使用。 + +**签名** + +* `define(options: ResourceOptions): Resource` + +**参数** + +详见 [Resource 构造函数](/api/server/resourcer/resource#构造函数)。 + +**示例** + +```ts +app.resourcer.define({ + name: 'books', + actions: { + // 扩展的 action + publish(ctx, next) { + ctx.body = 'ok'; + } + } +}); +``` + +### `isDefined()` + +检查对应名称的资源是否已被注册。 + +**签名** + +* `isDefined(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 资源名称 | + +**示例** + +```ts +app.resourcer.isDefined('books'); // true +``` + +### `registerAction()` + +向资源管理器注册一个操作,可以指定针对特定的资源,如不指定资源名称,则认为是针对全局所有资源都可访问的操作。 + +**签名** + +* `registerAction(name: string, handler: HandlerType): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | +| `handler` | `HandlerType` | - | 操作处理函数 | + +`name` 的值如果以 `:` 开头则代表仅针对 `` 资源可访问,否则认为是全局操作。 + +**示例** + +```ts +// 注册后任意资源都可以进行 upload 操作 +app.resourcer.registerAction('upload', async (ctx, next) => { + ctx.body = 'ok'; +}); + +// 仅针对 attachments 资源注册 upload 操作 +app.resourcer.registerAction('attachments:upload', async (ctx, next) => { + ctx.body = 'ok'; +}); +``` + +### `registerActions()` + +向资源管理器注册多个操作的集合方法。 + +**签名** + +* `registerActions(actions: { [name: string]: HandlerType }): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `actions` | `{ [name: string]: HandlerType }` | - | 操作集合 | + +**示例** + +```ts +app.resourcer.registerActions({ + upload: async (ctx, next) => { + ctx.body = 'ok'; + }, + 'attachments:upload': async (ctx, next) => { + ctx.body = 'ok'; + } +}); +``` + +### `getResource()` + +获取对应名称的资源对象。 + +**签名** + +* `getResource(name: string): Resource` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 资源名称 | + +**示例** + +```ts +app.resourcer.getResource('books'); +``` + +### `getAction()` + +获取对应名称的操作处理函数。 + +**签名** + +* `getAction(name: string): HandlerType` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | + +`name` 的值如果以 `:` 开头则代表仅针对 `` 资源的操作,否则认为是全局操作。 + +**示例** + +```ts +app.resourcer.getAction('upload'); +app.resourcer.getAction('attachments:upload'); +``` + +### `use()` + +以 Koa 的形式注册一个中间件,中间件形成一个队列,并排在所有资源的操作处理函数之前执行。 + +**签名** + +* `use(middleware: Middleware | Middleware[]): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Middleware \| Middleware[]` | - | 中间件 | + +**示例** + +```ts +app.resourcer.use(async (ctx, next) => { + console.log(ctx.req.url); + await next(); +}); +``` + +### `middleware()` + +生成一个兼容 Koa 的中间件,用于将资源的路由处理注入到应用中。 + +**签名** + +* `middleware(options: KoaMiddlewareOptions): KoaMiddleware` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.prefix?` | `string` | `''` | 路径前缀。 | +| `options.accessors?` | `Object` | `{}` | 常用方法的名称映射,与构造函数的 `accessors` 参数结构相同。 | + +**示例** + +```ts +const koa = new Koa(); + +const resourcer = new Resourcer(); + +// 生成兼容 Koa 的中间件 +koa.use(resourcer.middleware()); +``` diff --git a/docs/en-US/api/resourcer/middleware.md b/docs/en-US/api/resourcer/middleware.md new file mode 100644 index 000000000..1f0a4d3a8 --- /dev/null +++ b/docs/en-US/api/resourcer/middleware.md @@ -0,0 +1,168 @@ +# Middleware + +与 Koa 的中间件类似,但提供了更多增强的功能,可以方便的进行更多的扩展。 + +中间件定义后可以在资源管理器等多处进行插入使用,由开发者自行控制调用的时机。 + +## 构造函数 + +**签名** + +* `constructor(options: Function)` +* `constructor(options: MiddlewareOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `Function` | - | 中间件处理函数 | +| `options` | `MiddlewareOptions ` | - | 中间件配置项 | +| `options.only` | `string[]` | - | 仅允许指定的操作 | +| `options.except` | `string[]` | - | 排除指定的操作 | +| `options.handler` | `Function` | - | 处理函数 | + +**示例** + +简单定义: + +```ts +const middleware = new Middleware((ctx, next) => { + await next(); +}); +``` + +使用相关参数: + +```ts +const middleware = new Middleware({ + only: ['create', 'update'], + async handler(ctx, next) { + await next(); + }, +}); +``` + +## 实例方法 + +### `getHandler()` + +返回已经过编排的处理函数。 + +**示例** + +以下中间件在请求时会先输出 `1`,再输出 `2`。 + +```ts +const middleware = new Middleware((ctx, next) => { + console.log(1); + await next(); +}); + +middleware.use(async (ctx, next) => { + console.log(2); + await next(); +}); + +app.resourcer.use(middleware.getHandler()); +``` + +### `use()` + +对当前中间件添加中间件函数。用于提供中间件的扩展点。示例见 `getHandler()`。 + +**签名** + +* `use(middleware: Function)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Function` | - | 中间件处理函数 | + +### `disuse()` + +移除当前中间件已添加的中间件函数。 + +**签名** + +* `disuse(middleware: Function)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Function` | - | 中间件处理函数 | + +**示例** + +以下示例在请求处理是只输出 `1`,不执行 fn1 中的 `2` 输出。 + +```ts +const middleware = new Middleware((ctx, next) => { + console.log(1); + await next(); +}); + +async function fn1(ctx, next) { + console.log(2); + await next(); +} + +middleware.use(fn1); + +app.resourcer.use(middleware.getHandler()); + +middleware.disuse(fn1); +``` + +### `canAccess()` + +判断当前中间件针对特定操作是否要被调用,通常由资源管理器内部处理。 + +**签名** + +* `canAccess(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | + +## 其他导出 + +### `branch()` + +创建一个分支中间件,用于在中间件中进行分支处理。 + +**签名** + +* `branch(map: { [key: string]: Function }, reducer: Function, options): Function` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `map` | `{ [key: string]: Function }` | - | 分支处理函数映射表,键名由后续计算函数在调用时给出 | +| `reducer` | `(ctx) => string` | - | 计算函数,用于基于上下文计算出分支的键名 | +| `options?` | `Object` | - | 分支配置项 | +| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | 未找到键名时的处理函数 | +| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | 未定义处理函数时的处理 | + +**示例** + +用户验证时,根据请求 URL 中 query 部分的 `authenticator` 参数的值决定后续需要如何处理: + +```ts +app.resourcer.use(branch({ + 'password': async (ctx, next) => { + // ... + }, + 'sms': async (ctx, next) => { + // ... + }, +}, (ctx) => { + return ctx.action.params.authenticator ?? 'password'; +})); +``` diff --git a/docs/en-US/api/resourcer/resource.md b/docs/en-US/api/resourcer/resource.md new file mode 100644 index 000000000..4e83b2d21 --- /dev/null +++ b/docs/en-US/api/resourcer/resource.md @@ -0,0 +1,114 @@ +# Resource + +Resource 用于定义资源实例。被 Resourcer 管理的资源实例都可以通过 HTTP 请求访问。 + +## 构造函数 + +用于创建 Resource 实例。通常由 Resourcer 管理器的 `define()` 接口调用替代,不需要直接使用。 + +**签名** + +* `constructor(options: ResourceOptions, resourcer: Resourcer)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | 资源名称,对应 URL 路由中的资源地址部分。 | +| `options.type` | `string` | `'single'` | 资源类型,可选项为 `'single'`、`'hasOne'`、`'hasMany'`、`'belongsTo'`、`'belongsToMany'`。 | +| `options.actions` | `Object` | - | 对资源可进行的操作列表,详见示例部分。 | +| `options.middlewares` | `MiddlewareType \| MiddlewareType[]` | - | 对当前定义资源进行任意操作访问时的中间件列表,详见示例部分。 | +| `options.only` | `ActionName[]` | `[]` | 针对全局操作的白名单列表,当数组中有值时(`length > 0`),只有数组中的操作可被访问。 | +| `options.except` | `ActionName[]` | `[]` | 针对全局操作的黑名单列表,当数组中有值时(`length > 0`),除数组中的操作外,其他操作可被访问。 | +| `resourcer` | `Resourcer` | - | 所属资源管理器实例。 | + +**示例** + +```ts +app.resourcer.define({ + name: 'books', + actions: { + // 扩展的 action + publish(ctx, next) { + ctx.body = 'ok'; + } + }, + middleware: [ + // 扩展的中间件 + async (ctx, next) => { + await next(); + } + ] +}); +``` + +## 实例成员 + +### `options` + +当前资源的配置项。 + +### `resourcer` + +所属的资源管理器实例。 + +### `middlewares` + +已注册的中间件列表。 + +### `actions` + +已注册的操作映射表。 + +### `except` + +操作排除的名单列表。 + +## 实例方法 + +### `getName()` + +获取当前资源的名称。 + +**签名** + +* `getName(): string` + +**示例** + +```ts +const resource = app.resourcer.define({ + name: 'books' +}); + +resource.getName(); // 'books' +``` + +### `getAction()` + +根据名称获取当前资源的操作。 + +**签名** + +* `getAction(name: string): Action` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称。 | + +**示例** + +```ts +const resource = app.resourcer.define({ + name: 'books', + actions: { + publish(ctx, next) { + ctx.body = 'ok'; + } + } +}); + +resource.getAction('publish'); // [Function: publish] +``` diff --git a/docs/en-US/development/http-api/javascript-sdk.md b/docs/en-US/api/sdk.md similarity index 90% rename from docs/en-US/development/http-api/javascript-sdk.md rename to docs/en-US/api/sdk.md index 329441c51..d4fc6fa70 100644 --- a/docs/en-US/development/http-api/javascript-sdk.md +++ b/docs/en-US/api/sdk.md @@ -1,32 +1,32 @@ -# JavaScript SDK +# @nocobase/sdk ## APIClient ```ts class APIClient { - // axios instance + // axios 实例 axios: AxiosInstance; - // constructors + // 构造器 constructor(instance?: AxiosInstance | AxiosRequestConfig); - // Client-side requests, support for AxiosRequestConfig and ResourceActionOptions + // 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; - // Get Resources + // 获取资源 resource(name: string, of?: any): R; } ``` -Initialize instance +初始化实例 ```ts import axios from 'axios'; import { APIClient } from '@nocobase/sdk'; -// Provide AxiosRequestConfig configuration parameters +// 提供 AxiosRequestConfig 配置参数 const api = new APIClient({ baseURL: 'https://localhost:8000/api', }); -// Provide AxiosInstance +// 提供 AxiosInstance const instance = axios.create({ baseURL: 'https://localhost:8000/api', }); @@ -54,7 +54,7 @@ await api.request({ url: 'users:get' }); ## Storage -APIClient uses localStorage by default, you can also custom storage. +APIClient 默认使用的 localStorage,你也可以自定义 Storage,如: ```ts import { Storage } from '@nocobase/sdk'; @@ -88,19 +88,19 @@ const api = new APIClient({ ## Auth ```ts -// sign in and remember the current token +// 登录并记录 token api.auth.signIn({ email, password }); -// sign out and delete the token +// 注销并删除 token api.auth.signOut(); -// set the token +// 设置 token api.auth.setToken('123'); -// set the role (multiple roles) +// 设置 role(当需要多角色时) api.auth.setRole('admin'); -// set the locale (multiple languages) +// 设置 locale(当需要多语言时) api.auth.setLocale('zh-CN'); ``` -Custom Auth +自定义 Auth ```ts import { Auth } from '@nocobase/sdk'; diff --git a/docs/en-US/development/plugin-development/server/app-manager.md b/docs/en-US/api/server/app-manager.md similarity index 100% rename from docs/en-US/development/plugin-development/server/app-manager.md rename to docs/en-US/api/server/app-manager.md diff --git a/docs/en-US/api/server/application.md b/docs/en-US/api/server/application.md new file mode 100644 index 000000000..8aac7a008 --- /dev/null +++ b/docs/en-US/api/server/application.md @@ -0,0 +1,188 @@ +# Application + +基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。 + +## 构造函数 + +### `constructor()` + +创建一个应用实例。 + +**签名** + +* `constructor(options: ApplicationOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 | +| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 | +| `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` | 是否注册默认的 [actions](#) | +| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) | +| `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 | + +Type + +```ts +interface ApplicationOptions { + +} +``` + +**示例** + +```ts +import Application from '@nocobase/server'; + +const app = new Application({ + database: { + dialect: 'mysql', + host: 'localhost', + port: 3306, + username: 'root', + password: '123456', + database: 'test', + }, + resourcer: { + prefix: '/api', + }, + cors: { + origin: '*', + } +}); +``` + +## 实例成员 + +### `cli` + +命令行工具实例,参考 npm 包 [Commander](https://www.npmjs.com/package/commander)。 + +### `db` + +数据库实例,相关 API 参考 [Database](/api/database)。 + +### `resourcer` + +应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。 + +### `acl` + +ACL 实例,相关 API 参考 [ACL](/api/acl)。 + +### `i18n` + +I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。 + +### `pm` + +插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。 + +### `version` + +应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。 + +### `middleware` + +内置的中间件有: + +- i18next +- bodyParser +- cors +- dataWrapping +- collection2resource +- restApiMiddleware + +### `context` + +继承自 koa 的 context,可以通过 `app.context` 访问,用于向每个请求注入上下文可访问的内容。参考 [Koa Context](https://koajs.com/#app-context)。 + +NocoBase 默认对 context 注入了以下成员,可以在请求处理函数中直接使用: + +| 变量名 | 类型 | 描述 | +| --- | --- | --- | +| `ctx.app` | `Application` | 应用实例 | +| `ctx.db` | `Database` | 数据库实例 | +| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 | +| `ctx.action` | `Action` | 资源操作相关对象实例 | +| `ctx.i18n` | `I18n` | 国际化实例 | +| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 | +| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token | + +## 实例方法 + +### `use()` + +注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa) + +### `on()` + +订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。 + +### `command()` + +自定义 command + +### `findCommand()` + +查找已定义 command + +### `runAsCLI()` + +以 CLI 的方式运行。 + +### `load()` + +加载应用配置。 + +**签名** + +* `async load(): Promise` + +### `reload()` + +重载应用配置。 + +### `install()` + +初始化安装应用,同步安装插件。 + +### `upgrade()` + +升级应用,同步升级插件。 + +### `start()` + +启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。 + +**签名** + +* `async start(options: StartOptions): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.listen?` | `ListenOptions` | `{}` | HTTP 监听参数对象 | +| `options.listen.port?` | `number` | 13000 | 端口 | +| `options.listen.host?` | `string` | `'localhost'` | 域名 | + +### `stop()` + +停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。 + +### `destroy()` + +删除应用,此方法会删除应用对应的数据库。 + +## 事件 + +### `'beforeLoad'` / `'afterLoad'` +### `'beforeInstall'` / `'afterInstall'` +### `'beforeUpgrade'` / `'afterUpgrade'` +### `'beforeStart'` / `'afterStart'` +### `'beforeStop'` / `'afterStop'` +### `'beforeDestroy'` / `'afterDestroy'` diff --git a/docs/en-US/development/plugin-development/server/i18n.md b/docs/en-US/api/server/i18n.md similarity index 100% rename from docs/en-US/development/plugin-development/server/i18n.md rename to docs/en-US/api/server/i18n.md diff --git a/docs/en-US/api/server/plugin-manager.md b/docs/en-US/api/server/plugin-manager.md new file mode 100644 index 000000000..e161d37f0 --- /dev/null +++ b/docs/en-US/api/server/plugin-manager.md @@ -0,0 +1,59 @@ +# PluginManager + +应用插件管理器的实例,由应用自动创建,可以通过 `app.pm` 访问。 + +## 实例方法 + +### `create()` + +在本地创建一个插件脚手架 + +**签名** + +```ts +create(name, options): void; +``` + +### `addStatic()` + +**签名** + +```ts +addStatic(plugin: any, options?: PluginOptions): Plugin; +``` + +**示例** + +```ts +pm.addStatic('nocobase'); +``` + +### `add()` + +**签名** + +```ts +async add(plugin: any, options?: PluginOptions): Promise; +async add(plugin: string[], options?: PluginOptions): Promise; +``` + +**示例** + +```ts +await pm.add(['test'], { + builtIn: true, + enabled: true, +}); +``` + +### `get()` + +获取插件实例 + +### `enable()` + +### `disable()` + +### `remove()` + +### `upgrade()` diff --git a/docs/en-US/api/server/plugin.md b/docs/en-US/api/server/plugin.md new file mode 100644 index 000000000..2f7a9705c --- /dev/null +++ b/docs/en-US/api/server/plugin.md @@ -0,0 +1,59 @@ +# Plugin + +## 示例 + +```ts +const app = new Application(); + +class MyPlugin extends Plugin { + afterAdd() {} + beforeLoad() {} + load() {} + install() {} + afterEnable() {} + afterDisable() {} + remove() {} +} + +app.plugin(MyPlugin, { name: 'my-plugin' }); +``` + +## 属性 + +### `options` + +插件配置信息 + +### `name` + +插件标识,只读 + +## 实例方法 + +### `afterAdd()` + +插件 add/addStatic 之后 + +### `beforeLoad()` + +插件加载前,如事件或类注册 + +### `load()` + +加载插件,配置之类 + +### `install()` + +插件安装逻辑,如初始化数据 + +### `afterEnable()` + +插件激活之后的逻辑 + +### `afterDisable()` + +插件禁用之后的逻辑 + +### `remove()` + +用于实现插件删除逻辑 \ No newline at end of file diff --git a/docs/en-US/development/directory-structure.md b/docs/en-US/development/directory-structure.md index 30e340370..770ab204c 100644 --- a/docs/en-US/development/directory-structure.md +++ b/docs/en-US/development/directory-structure.md @@ -1,24 +1,18 @@ -# Directory structure +# 项目目录结构 -## Application scaffolding - -```bash -$ yarn create nocobase-app my-nocobase-app -``` - -The directory structure of the application scaffold created by `create-nocobase-app` is as follows +无论是源码还是 `create-nocobase-app` 创建的应用,目录结构都是一样的,结构如下: ```bash ├── my-nocobase-app - ├── packages # Use the Monorepo approach to manage code, dividing different modules into packages + ├── packages # 采用 Monorepo 的方式管理代码,将不同模块划分到不同包里 ├── app - ├── client # Client-side modules - ├── server # Server-side modules - ├── plugins # Plugins directory - ├── storage # For database files, attachments, cache, etc. + ├── client # 客户端模块 + ├── server # 服务端模块 + ├── plugins # 插件目录 + ├── storage # 用于存放数据库文件、附件、缓存等 ├── db - ├── .env # Environment variables - ├── .buildrc.ts # Packaging configuration for packages, supports cjs, esm and umd packaging. + ├── .env # 环境变量 + ├── .buildrc.ts # packages 的打包配置,支持 cjs、esm 和 umd 三种格式的打包。 ├── jest.config.js ├── jest.setup.ts ├── lerna.json @@ -28,7 +22,7 @@ The directory structure of the application scaffold created by `create-nocobase- ├── tsconfig.server.json ``` -### packages directory +### packages 目录 ```bash ├── packages @@ -51,39 +45,20 @@ The directory structure of the application scaffold created by `create-nocobase- ├── package.json ``` -NocoBase uses the Monorepo approach to manage the code, dividing the different modules into different packages. +NocoBase 采用 Monorepo 的方式管理代码,将不同模块划分到不同包里。 -- `app/client` is the client-side module of the application, built on [umi](https://umijs.org). -- `app/server` is the server-side module of the application. -- `plugins/*` directory can hold various plugins. +- `app/client` 为应用的客户端模块,基于 [umi](https://umijs.org/zh-CN) 构建; +- `app/server` 为应用的服务端模块; +- `plugins/*` 目录里可以放各种插件。 -### storages directory +### storages 目录 -Used to store database files, attachments, cache, etc. +用于存放数据库文件、附件、缓存等。 -### .env file +### .env 文件 -Environment variables +环境变量。 -### .buildrc.ts file +### .buildrc.ts 文件 -Packaging configuration for packages, supports cjs, esm and umd packaging. - -## Plugins scaffolding - -```bash -$ yarn nocobase create-plugin my-plugin -``` - -The plugin scaffolding directory initialized by `nocobase create-plugin` is as follows - -```bash -├── my-nocobase-app - ├── packages - ├── plugins - ├── my-plugin - ├── src - ├── client - ├── server - ├── package.json -``` +packages 的打包配置,支持 cjs、esm 和 umd 三种格式的打包。 diff --git a/docs/en-US/development/env.md b/docs/en-US/development/env.md deleted file mode 100644 index ce93251f6..000000000 --- a/docs/en-US/development/env.md +++ /dev/null @@ -1,207 +0,0 @@ -# Environment variables - -## Global environment variables - -Saved in the `.env` file - -### APP_ENV - -Application environment, default value `development`, options include - -- `production` production environment -- `development` development environment - -```bash -APP_ENV=production -``` - -### APP_HOST - -Application host, default value `0.0.0.0` - -```bash -APP_HOST=192.168.3.154 -``` - -### APP_PORT - -Application port, default value `13000` - -```bash -APP_PORT=13000 -``` - -### APP_KEY - -Secret key for scenarios such as jwt - -```bash -APP_KEY=app-key-test -``` - -### API_BASE_PATH - -NocoBase API address prefix, default value `/api/` - -```bash -API_BASE_PATH=/api/ -``` - -### DB_DIALECT - -Database type, default value `sqlite`,options include - -- `sqlite` -- `mysql` -- `postgres` - -```bash -DB_DIALECT=mysql -``` - -### DB_STORAGE - -Database file path (configured when using SQLite) - -```bash -# Relative path -DB_HOST=storage/db/nocobase.db -# Absolute path -DB_HOST=/your/path/nocobase.db -``` - -### DB_HOST - -Database host (required when using MySQL or PostgreSQL) - -Default value `localhost` - -```bash -DB_HOST=localhost -``` - -### DB_PORT - -Database port (required when using MySQL or PostgreSQL) - -- MySQL default port 3306 -- PostgreSQL default port 5432 - -```bash -DB_PORT=3306 -``` - -### DB_DATABASE - -Database name (required when using MySQL or PostgreSQL) - -```bash -DB_DATABASE=nocobase -``` - -### DB_USER - -Database user (required when using MySQL or PostgreSQL) - -```bash -DB_USER=nocobase -``` - -### DB_PASSWORD - -Database password (required when using MySQL or PostgreSQL) - -```bash -DB_PASSWORD=nocobase -``` - -### DB_TABLE_PREFIX - -Data Table Prefix - -```bash -DB_TABLE_PREFIX=nocobase_ -``` - -### DB_LOGGING - -Switching of logs, default value `off`, options include: - -- `on` On -- `off` Off - -```bash -DB_LOGGING=on -``` - -## Temporary environment variables - -When installing NocoBase, you can assist in the installation by setting temporary environment variables, such as - -```bash -yarn cross-env \ - INIT_APP_LANG=en-US \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install - -# Equivalent to -yarn nocobase install \ - --lang=en-US \ - --root-email=demo@nocobase.com \ - --root-password=admin123 \ - --root-nickname="Super Admin" - -# Equivalent to -yarn nocobase install -l en-US -e demo@nocobase.com -p admin123 -n "Super Admin" -``` - -### INIT_APP_LANG - -Language at installation, default value `en-US`, options include - -- `en-US` -- `zh-CN` - -```bash -yarn cross-env \ - INIT_APP_LANG=en-US \ - nocobase install -``` - -### INIT_ROOT_EMAIL - -Root user's email - -```bash -yarn cross-env \ - INIT_APP_LANG=en-US \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - nocobase install -``` - -### INIT_ROOT_PASSWORD - -Root user's password - -```bash -yarn cross-env \ - INIT_APP_LANG=en-US \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - nocobase install -``` - -### INIT_ROOT_NICKNAME - -Root user's name - -```bash -yarn cross-env \ - INIT_APP_LANG=en-US \ - INIT_ROOT_EMAIL=demo@nocobase.com \ - INIT_ROOT_PASSWORD=admin123 \ - INIT_ROOT_NICKNAME="Super Admin" \ - nocobase install -``` diff --git a/docs/en-US/development/guide/collections-fields.md b/docs/en-US/development/guide/collections-fields.md new file mode 100644 index 000000000..c10bd0ae0 --- /dev/null +++ b/docs/en-US/development/guide/collections-fields.md @@ -0,0 +1,319 @@ +# 数据表与字段 + +## 基础概念 + +数据建模是一个应用最底层的基础,在 NocoBase 应用中我们通过数据表(Collection)和字段(Field)来进行数据建模,并且建模也将映射到数据库表以持久化。 + +### Collection + +Collection 是所有同类数据的集合,在 NocoBase 中对应数据库表的概念,如订单、商品、用户、评论等都可以形成 Collection 定义,不同 Collection 通过 name 区分,包含的字段由 `fields` 定义,如: + +```ts +db.collection({ + name: 'posts', + fields: [ + { name: 'title', type: 'string' }, + { name: 'content', type: 'text' }, + // ... + ] +}); +``` + +定义完成后 collection 暂时只处于内存中,还需要调用 [`db.sync()`](/api/database#sync) 方法将其同步到数据库中。 + +### Field + +对应数据库表“字段”的概念,每个数据表(Collection)都可以有若干 Fields,例如: + +```ts +db.collection({ + name: 'users', + fields: [ + { type: 'string', name: 'name' }, + { type: 'integer', name: 'age' }, + // 其他字段 + ], +}); +``` + +其中字段名称(`name`)和字段类型(`type`)是必填项,不同字段通过字段名(`name`)区分,除 `name` 与 `type` 以外,根据不同字段类型可以有更多的配置信息。所有数据库字段类型及配置详见 API 参考的[内置字段类型列表](/api/database/field#内置字段类型列表)部分。 + +## 示例 + +对于开发者,通常我们会建立与普通数据表不同的一些功能型数据表,并把这些数据表固化成插件的一部分,并结合其他数据处理流程以形成完整的功能。 + +接下来我们以一个简单的在线商店插件为例来介绍如何建模并管理插件的数据表。假设你已经学习过 [编写第一个插件](/development/your-first-plugin),我们继续在之前的插件代码上开发,只不过插件的名称从 `hello` 改为 `shop-modeling`。 + +### 插件中定义并创建数据表 + +对于一个店铺,首先需要建立一张商品的数据表,命名为 `products`。与直接调用 [`db.collection()`](/api/database#collection) 这样的方法稍有差异,在插件中我们会使用更方便的方法一次性导入多个文件定义的数据表。所以我们先为商品数据表的定义创建一个文件命名为 `collections/products.ts`,填入以下内容: + +```ts +export default { + name: 'products', + fields: [ + { + type: 'string', + name: 'title' + }, + { + type: 'integer', + name: 'price' + }, + { + type: 'boolean', + name: 'enabled' + }, + { + type: 'integer', + name: 'inventory' + } + ] +}; +``` + +可以看到,NocoBase 的数据库表结构定义可以直接使用标准的 JSON 格式,其中 `name` 和 `fields` 都是必填项,代表数据表名和该表中的字段定义。字段定义中与 Sequelize 类似会默认创建主键(`id`)、数据创建时间(`createdAt`)和数据更新时间(`updatedAt`)等系统字段,如有特殊需要可以以同名的配置覆盖定义。 + +该文件定义的数据表我们可以在插件主类的 `load()` 周期中使用 `db.import()` 引入并完成定义。如下所示: + +```ts +import path from 'path'; +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + await this.db.import({ + directory: path.resolve(__dirname, 'collections'), + }); + + this.app.acl.allow('products', '*'); + this.app.acl.allow('categories', '*'); + this.app.acl.allow('orders', '*'); + } +} +``` + +同时我们为了方便测试,先暂时允许针对这几张表数据资源的所有访问权限,后面我们会在 [权限管理](/development/guide/acl) 中详细介绍如何管理资源的权限。 + +这样在插件被主应用加载时,我们定义的 `products` 表也就被加载到数据库管理实例的内存中了。同时,基于 NocoBase 约定式的数据表资源映射,在应用的服务启动以后,会自动生成对应的 CRUD HTTP API。 + +当从客户端请求以下 URL 时,会得到对应的响应结果: + +* `GET /api/products:list`:获取所有商品数据列表 +* `GET /api/products:get?filterByTk=`:获取指定 ID 的商品数据 +* `POST /api/products`:创建一条新的商品数据 +* `PUT /api/products:update?filterByTk=`:更新一条商品数据 +* `DELETE /api/products:destroy?filterByTk=`:删除一条商品数据 + +### 定义关系表和关联字段 + +在上面的例子中,我们只定义了一个商品数据表,但是实际上一个商品还需要关联到一个分类,一个品牌,一个供应商等等。这些关联关系可以通过定义关系表来实现,比如我们可以定义一个 `categories` 表,用来关联商品和分类,然后在商品表中添加一个 `category` 字段来关联到分类表。 + +新增文件 `collections/categories.ts`,并填入内容: + +```ts +export default { + name: 'categories', + fields: [ + { + type: 'string', + name: 'title' + }, + { + type: 'hasMany', + name: 'products', + } + ] +}; +``` + +我们为 `categories` 表定义了两个字段,一个是标题,另一个是该分类下关联的所有产品的一对多字段,会在后面一起介绍。因为我们已经在插件的主类中使用了 `db.import()` 方法导入 `collections` 目录下的所有数据表定义,所以这里新增的 `categories` 表也会被自动导入到数据库管理实例中。 + +修改文件 `collections/products.ts`,在 `fields` 中添加一个 `category` 字段: + +```ts +{ + name: 'products', + fields: [ + // ... + { + type: 'belongsTo', + name: 'category', + target: 'categories', + } + ] +} +``` + +可以看到,我们为 `products` 表新增的 `category` 字段是一个 `belongsTo` 类型的字段,它的 `target` 属性指向了 `categories` 表,这样就定义了一个 `products` 表和 `categories` 表之间的多对一关系。同时结合我们在 `categories` 表中定义的 `hasMany` 字段,就可以实现一个商品可以关联到多个分类,一个分类下可以有多个商品的关系。通常 `belongsTo` 和 `hasMany` 可以成对出现,分别定义在两张表中。 + +定义好两张表之间的关系后,同样的我们就可以直接通过 HTTP API 来请求关联数据了: + +* `GET /api/products:list?appends=category`:获取所有商品数据,同时包含关联的分类数据 +* `GET /api/products:get?filterByTk=&appends=category`:获取指定 ID 的商品数据,同时包含关联的分类数据 +* `GET /api/categories//products:list`:获取指定分类下的所有商品数据 +* `POST /api/categories//products`:在指定分类下创建新的商品 + +与一般的 ORM 框架类似,NocoBase 内置了四种关系字段类型,更多信息可以参考 API 字段类型相关的章节: + +* [`belongsTo` 类型](/api/database/field#belongsto) +* [`belongsToMany` 类型](/api/database/field#belongstomany) +* [`hasMany` 类型](/api/database/field#hasmany) +* [`hasOne` 类型](/api/database/field#hasone) + +### 扩展已有数据表 + +在上面的例子中,我们已经有了商品表和分类表,为了提供销售流程,我们还需要一个订单表。我们可以在 `collections` 目录下新增一个 `orders.ts` 文件,然后定义一个 `orders` 表: + +```ts +export default { + name: 'orders', + fields: [ + { + type: 'uuid', + name: 'id', + primaryKey: true + }, + { + type: 'belongsTo', + name: 'product' + }, + { + type: 'integer', + name: 'quantity' + }, + { + type: 'integer', + name: 'totalPrice' + }, + { + type: 'integer', + name: 'status' + }, + { + type: 'string', + name: 'address' + }, + { + type: 'belongsTo', + name: 'user' + } + ] +} +``` + +为了简化,订单表中与商品的关联我们只简单的定义为多对一关系,而在实际业务中可能会用到多对多或快照等复杂的建模方式。可以看到,一个订单除了对应某个商品,我们还增加了一个对应用户的关系定义,用户是 NocoBase 内置插件管理的数据表(详细参考[用户插件的代码](https://github.com/nocobase/nocobase/tree/main/packages/plugins/users)),如果我们希望针对已存在的用户表扩展定义“一个用户所拥有的多个订单”的关系,可以在当前的 shop-modeling 插件内继续新增一个数据表文件 `collections/users.ts`,与直接导出 JSON 数据表配置不同的是,这里使用 `@nocobase/database` 包的 `extend()` 方法,进行对已有数据表的扩展定义: + +```ts +import { extend } from '@nocobase/database'; + +export extend({ + name: 'users', + fields: [ + { + type: 'hasMany', + name: 'orders' + } + ] +}); +``` + +这样,原先已存在的用户表也就拥有了一个 `orders` 关联字段,我们可以通过 `GET /api/users//orders:list` 来获取指定用户的所有订单数据。 + +这个方法在扩展其他已有插件已定义的数据表时非常有用,使得其他已有插件不会反向依赖新的插件,仅形成单向依赖关系,方便在扩展层面进行一定程度的解耦。 + +### 扩展字段类型 + +我们在定义订单表的时候针对 `id` 字段使用了 `uuid` 类型,这是一个内置的字段类型,有时候我们也会觉得 UUID 看起来太长比较浪费空间,且查询性能不佳,希望用一个更适合的字段类型,比如一个含日期信息等复杂的编号逻辑,或者是 Snowflake 算法,我们就需要扩展一个自定义字段类型。 + +假设我们需要直接应用 Snowflake ID 生成算法,扩展出一个 `snowflake` 字段类型,我们可以创建一个 `fields/snowflake.ts` 文件: + +```ts +import { DataTypes } from 'sequelize'; +// 引入算法工具包 +import { Snowflake } from 'nodejs-snowflake'; +// 引入字段类型基类 +import { Field, BaseColumnFieldOptions } from '@nocobase/database'; + +export interface SnowflakeFieldOptions extends BaseColumnFieldOptions { + type: 'snowflake'; + epoch: number; + instanceId: number; +} + +export class SnowflakeField extends Field { + get dataType() { + return DataTypes.BIGINT; + } + + constructor(options: SnowflakeFieldOptions, context) { + super(options, context); + + const { + epoch: custom_epoch, + instanceId: instance_id = process.env.INSTANCE_ID ? Number.parseInt(process.env.INSTANCE_ID) : 0, + } = options; + this.generator = new Snowflake({ custom_epoch, instance_id }); + } + + setValue = (instance) => { + const { name } = this.options; + instance.set(name, this.generator.getUniqueID()); + }; + + bind() { + super.bind(); + this.on('beforeCreate', this.setValue); + } + + unbind() { + super.unbind(); + this.off('beforeCreate', this.setValue); + } +} + +export default SnowflakeField; +``` + +之后在插件主文件向数据库注册新的字段类型: + +```ts +import SnowflakeField from './fields/snowflake'; + +export default class ShopPlugin extends Plugin { + initialize() { + // ... + this.db.registerFieldTypes({ + snowflake: SnowflakeField + }); + // ... + } +} +``` + +这样,我们就可以在订单表中使用 `snowflake` 字段类型了: + +```ts +export default { + name: 'orders', + fields: [ + { + type: 'snowflake' + name: 'id', + primaryKey: true + }, + // ...other fields + ] +} +``` + +## 小结 + +通过上面的示例,我们基本了解了如何在一个插件中进行数据建模,包括: + +* 定义数据表和普通字段 +* 定义关联表和关联字段关系 +* 扩展已有的数据表的字段 +* 扩展新的字段类型 + +我们将本章所涉及的代码放到了一个完整的示例包 [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling) 中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/guide/commands.md b/docs/en-US/development/guide/commands.md new file mode 100644 index 000000000..5334d3b8d --- /dev/null +++ b/docs/en-US/development/guide/commands.md @@ -0,0 +1,98 @@ +# 命令行 + +## 简介 + +NocoBase Server Application 除了用作 WEB 服务器以外,也是个强大可扩展的 CLI 工具。 + +新建一个 `app.js` 文件,代码如下: + +```ts +const Application = require('@nocobase/server'); + +// 此处省略具体配置 +const app = new Application({/*...*/}); + +app.runAsCLI(); +``` + +以 `runAsCLI()` 方式运行的 app.js 是一个 CLI,在命令行工具就可以像这样操作了: + +```bash +node app.js install # 安装 +node app.js start # 启动 +``` + +为了更好的开发、构建和部署 NocoBase 应用,NocoBase 内置了许多命令,详情查看 [NocoBase CLI](/api/cli) 章节。 + +## 自定义 Command + +NocoBase CLI 的设计思想与 [Laravel Artisan](https://laravel.com/docs/9.x/artisan) 非常相似,都是可扩展的。NocoBase CLI 基于 [commander](https://www.npmjs.com/package/commander) 实现,可以这样扩展 Command: + +```ts +app + .command('echo') + .option('-v, --version'); + .action(async ([options]) => { + console.log('Hello World!'); + if (options.version) { + console.log('Current version:', app.getVersion()); + } + }); +``` + +这个方法定义了以下命令: + +```bash +yarn nocobase echo +# Hello World! +yarn nocobase echo -v +# Hello World! +# Current version: 0.7.4-alpha.7 +``` + +更多 API 细节可参考 [Application.command()](/api/server/application#command) 部分。 + +## 示例 + +### 定义导出数据表的命令 + +如果我们希望把应用的数据表中的数据导出成 JSON 文件,可以定义一个如下的子命令: + +```ts +import path from 'path'; +import * as fs from 'fs/promises'; + +class MyPlugin extends Plugin { + load() { + this.app + .command('export') + .option('-o, --output-dir') + .action(async (options, ...collections) => { + const { outputDir = path.join(process.env.PWD, 'storage') } = options; + await collections.reduce((promise, collection) => promise.then(async () => { + if (!this.db.hasCollection(collection)) { + console.warn('No such collection:', collection); + return; + } + + const repo = this.db.getRepository(collection); + const data = repo.find(); + await fs.writeFile(path.join(outputDir, `${collection}.json`), JSON.stringify(data), { mode: 0o644 }); + }), Promise.resolve()); + }); + } +} +``` + +注册和激活插件之后在命令行调用: + +```bash +mkdir -p ./storage/backups +yarn nocobase export -o ./storage/backups users +``` + +执行后会生成 `./storage/backups/users.json` 文件包含数据表中的数据。 + +## 小结 + +本章所涉及示例代码整合在 [packages/samples/command](https://github.com/nocobase/nocobase/tree/main/packages/samples/command) 包中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/guide/events.md b/docs/en-US/development/guide/events.md new file mode 100644 index 000000000..5c2029304 --- /dev/null +++ b/docs/en-US/development/guide/events.md @@ -0,0 +1,176 @@ +# 事件 + +事件在很多插件化可扩展的框架和系统中都有应用,比如著名的 Wordpress,是比较广泛的对生命周期支持扩展的机制。 + +## 基础概念 + +NocoBase 在应用生命周期中提供了一些钩子,以便在运行中的一些特殊时期根据需要进行扩展开发。 + +### 数据库事件 + +主要通过 `db.on()` 的方法定义,大部分事件兼容 Sequelize 原生的事件类型。例如需要在某张数据表创建一条数据后做一些事情时,可以使用 `.afterCreate` 事件: + +```ts +// posts 表创建数据完成时触发 +db.on('posts.afterCreate', async (post, options) => { + console.log(post); +}); +``` + +由于 Sequelize 默认的单条数据创建成功触发的时间点上并未完成与该条数据所有关联数据的处理,所以 NocoBase 针对默认封装的 Repository 数据仓库类完成数据创建和更新操作时,扩展了几个额外的事件,代表关联数据被一并操作完成: + +```ts +// 已创建且已根据创建数据完成关联数据创建或更新完成时触发 +db.on('posts.afterCreateWithAssociations', async (post, options) => { + console.log(post); +}); +``` + +与 Sequelize 同样的也可以针对全局的数据处理都定义特定的事件: + +```ts +// 每张表创建数据完成都触发 +db.on('beforeCreate', async (model, options) => { + console.log(model); +}); +``` + +针对特殊的生命周期比如定义数据表等,NocoBase 也扩展了相应事件: + +```ts +// 定义任意数据表之前触发 +db.on('beforeDefineCollection', (collection) => { + collection.options.tableName = 'somePrefix' + collection.options.tableName; +}); +``` + +其他所有可用的数据库事件类型可以参考 [Database API](/api/database#on)。 + +### 应用级事件 + +在某些特殊需求时,会需要在应用的外层生命周期中定义事件进行扩展,比如当应用启动前做一些准备操作,当应用停止前做一些清理操作等: + +```ts +app.on('beforeStart', async () => { + console.log('app is starting...'); +}); + +app.on('beforeStop', async () => { + console.log('app is stopping...'); +}); +``` + +其他所有可用的应用级事件类型可以参考 [Application API](/api/server/application#事件)。 + +## 示例 + +我们继续以简单的在线商店来举例,相关的数据表建模可以回顾 [数据表和字段](/development/) 部分的示例。 + +### 创建订单后减商品库存 + +通常我们的商品和订单是不同的数据表,而客户在下单以后把商品的库存减掉可以解决超卖的问题,这时候我们可以针对创建订单这个数据操作定义相应的事件,在这个时机一并解决库存修改的问题: + +```ts +class ShopPlugin extends Plugin { + load() { + this.db.on('orders.afterCreate', async (order, options) => { + const product = await order.getProduct({ + transaction: options.transaction + }); + + await product.update({ + inventory: product.inventory - order.quantity + }, { + transaction: options.transaction + }); + }); + } +} +``` + +因为默认 Sequelize 的事件中就携带事务等信息,所以我们可以直接使用 transaction 以保证两个数据操作都在同一事务中进行。 + +同样的,也可以在创建发货记录后修改订单状态为已发货: + +```ts +class ShopPlugin extends Plugin { + load() { + this.db.on('deliveries.afterCreate', async (delivery, options) => { + const orderRepo = this.db.getRepository('orders'); + await orderRepo.update({ + filterByTk: delivery.orderId, + value: { + status: 2 + } + transaction: options.transaction + }); + }); + } +} +``` + +### 随应用同时存在的定时任务 + +在不考虑使用工作流插件等复杂情况下,我们也可以通过应用级的事件实现一个简单的定时任务机制,且可以与应用的进程绑定,退出后就停止。比如我们希望定时扫描所有订单,超过签收时间后自动签收: + +```ts +class ShopPlugin extends Plugin { + timer = null; + orderReceiveExpires = 86400 * 7; + + checkOrder = async () => { + const expiredDate = new Date(Date.now() - this.orderReceiveExpires); + const deliveryRepo = this.db.getRepository('deliveries'); + const expiredDeliveries = await deliveryRepo.find({ + fields: ['id', 'orderId'], + filter: { + status: 0, + createdAt: { + $lt: expiredDate + } + } + }); + await deliveryRepo.update({ + filter: { + id: expiredDeliveries.map(item => item.get('id')), + }, + values: { + status: 1 + } + }); + const orderRepo = this.db.getRepository('orders'); + const [updated] = await orderRepo.update({ + filter: { + status: 2, + id: expiredDeliveries.map(item => item.get('orderId')) + }, + values: { + status: 3 + } + }); + + console.log('%d orders expired', updated); + }; + + load() { + this.app.on('beforeStart', () => { + // 每分钟执行一次 + this.timer = setInterval(this.checkOrder, 1000 * 60); + }); + + this.app.on('beforeStop', () => { + clearInterval(this.timer); + this.timer = null; + }); + } +} +``` + +## 小结 + +通过上面的示例,我们基本了解了事件的作用和可以用于扩展的方式: + +* 数据库相关的事件 +* 应用相关的事件 + +本章涉及的示例代码整合在对应的包 [packages/samples/shop-events](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-events) 中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/guide/i18n.md b/docs/en-US/development/guide/i18n.md new file mode 100644 index 000000000..9c08448cd --- /dev/null +++ b/docs/en-US/development/guide/i18n.md @@ -0,0 +1,266 @@ +# 国际化 + +## 基础概念 + +多语言国际化支持根据服务端和客户端分为两大部分,各自有相应的实现。 + +语言配置均为普通的 JSON 对象键值对,如果没有翻译文件(或省略编写)的话会直接输出键名的字符串。 + +### 服务端 + +服务端的多语言国际化基于 npm 包 [i18next](https://npmjs.com/package/i18next) 实现,在服务端应用初始化时会创建一个 i18next 实例,同时也会将此实例注入到请求上下文(`context`)中,以供在各处方便的使用。 + +在创建服务端 Application 实例时可以传入配置对应的初始化参数: + +```ts +import { Application } from '@nocobase/server'; + +const app = new Application({ + i18n: { + defaultNS: 'test', + resources: { + 'en-US': { + test: { + hello: 'Hello', + }, + }, + 'zh-CN': { + test: { + hello: '你好', + } + } + } + } +}); +``` + +或者在插件中对已存在的 app 实例添加语言数据至对应命名空间: + +```ts +app.i18n.addResources('zh-CN', 'test', { + Hello: '你好', + World: '世界', +}); + +app.i18n.addResources('en-US', 'test', { + Hello: 'Hello', + World: 'World', +}); +``` + +基于应用: + +```ts +app.i18n.t('World') // “世界”或“World” +``` + +基于请求: + +```ts +app.resource({ + name: 'test', + actions: { + async get(ctx, next) { + ctx.body = `${ctx.t('Hello')} ${ctx.t('World')}`; + await next(); + } + } +}); +``` + +通常服务端的多语言处理主要用于错误信息的输出。 + +### 客户端 + +客户端的多语言国际化基于 npm 包 [react-i18next](https://npmjs.com/package/react-i18next) 实现,在应用顶层提供了 `` 组件的包装,可以在任意位置直接使用相关的方法。 + +添加语言包: + +```tsx | pure +import { i18n } from '@nocobase/client'; + +i18n.addResources('zh-CN', 'test', { + Hello: '你好', + World: '世界', +}); +``` + +注:这里第二个参数填写的 `'test'` 是语言的命名空间,通常插件自己定义的语言资源都应该按自己插件包名创建特定的命名空间,以避免和其他语音资源冲突。NocoBase 中默认的命名空间是 `'client'`,大部分常用和基础的语言翻译都放置在此命名空间,当没有提供所需语言时,可在插件自身的命名空间内进行扩展定义。 + +在组件中调用翻译函数: + +```tsx | pure +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function MyComponent() { + // 使用之前定义的命名空间 + const { t } = useTranslation('test'); + + return ( +
+

{t('World')}

+
+ ); +} +``` + +在 SchemaComponent 组件中可以直接使用模板方法 `'{{t()}}'`,模板中的翻译函数会自动被执行: + +```tsx | pure +import React from 'react'; +import { SchemaComponent } from '@nocobase/client'; + +export default function MySchemaComponent() { + return ( + + ); +} +``` + +在某些特殊情况下也需要以模板的方式定义多语言时,可以使用 NocoBase 内置的 `compile()` 方法编译为多语言结果: + +```tsx | pure +import React from 'react'; +import { useCompile } from '@nocobase/client'; + +const title = '{{t("Hello", { ns: "test" })}}'; + +export default function MyComponent() { + const { compile } = useCompile(); + + return ( +
{compile(title)}
+ ); +} +``` + +### 建议配置 + +当添加语言资源时,推荐在 JSON 配置模板中将语言串的键名设置为默认语言,更方便统一处理,且可以省去默认语言的翻译。例如以英语为默认语言: + +```ts +i18n.addResources('zh-CN', 'your-namespace', { + 'Show dialog': '显示对话框', + 'Hide dialog': '隐藏对话框' +}); +``` + +语言内容如果比较多,推荐在插件中创建一个 `locals` 目录,并把对应语言文件都放置在其中方便管理: + +``` +- server +| - locals +| | - zh-CN.ts +| | - en-US.ts +| | - ... +| - index.ts +``` + +## 示例 + +### 服务端错误提示 + +例如用户在店铺对某个商品下单时,如果商品的库存不够,或者未上架,那么下单接口被调用时,应该返回相应的错误。 + +```ts +const namespace = 'shop'; + +export default class ShopPlugin extends Plugin { + async load() { + this.app.i18n.addResources('zh-CN', namespace, { + 'No such product': '商品不存在', + 'Product not on sale': '商品已下架', + 'Out of stock': '库存不足', + }); + + this.app.resource({ + name: 'orders', + actions: { + async create(ctx, next) { + const productRepo = ctx.db.getRepository('products'); + const product = await productRepo.findOne({ + filterByTk: ctx.action.params.values.productId + }); + + if (!product) { + return ctx.throw(404, ctx.t('No such product')); + } + + if (!product.enabled) { + return ctx.throw(400, ctx.t('Product not on sale')); + } + + if (!product.inventory) { + return ctx.throw(400, ctx.t('Out of stock')); + } + + const orderRepo = ctx.db.getRepository('orders'); + ctx.body = await orderRepo.create({ + values: { + productId: product.id, + quantity: 1, + totalPrice: product.price, + userId: ctx.state.currentUser.id + } + }); + + next(); + } + } + }); + } +} +``` + +### 客户端组件多语言 + +例如订单状态的组件,根据不同值有不同的文本显示: + +```tsx +import React from 'react'; +import { Select } from 'antd'; +import { i18n } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; + +i18n.addResources('zh-CN', '@nocobase/plugin-sample-shop-i18n', { + Pending: '已下单', + Paid: '已支付', + Delivered: '已发货', + Received: '已签收' +}); + +const ORDER_STATUS_LIST = [ + { value: -1, label: 'Canceled (untranslated)' }, + { value: 0, label: 'Pending' }, + { value: 1, label: 'Paid' }, + { value: 2, label: 'Delivered' }, + { value: 3, label: 'Received' }, +] + +function OrderStatusSelect() { + const { t } = useTranslation('@nocobase/plugin-sample-shop-i18n'); + + return ( + + ); +} + +export default function () { + return ( + + ); +} +``` diff --git a/docs/en-US/development/guide/index.md b/docs/en-US/development/guide/index.md new file mode 100644 index 000000000..fa1ec74e4 --- /dev/null +++ b/docs/en-US/development/guide/index.md @@ -0,0 +1,71 @@ +# 概述 + +## Web Server + +- Collection & Field +- Resource & Action +- Middleware +- Events & Hooks + +**Samples** + +- samples/shop-modeling +- samples/shop-actions +- samples/ratelimit +- samples/model-hooks + +## UI Schema Designer + +平台最核心的功能,用于可视化配置页面。 + +- 扩展各种供 UI 设计器使用的 Schema 组件 +- 各种 x-designer 和 x-initializer + +**Samples** + +- samples/custom-block +- samples/custom-action +- samples/custom-field +- samples/custom-x-designer +- samples/custom-x-initializer + +## UI Router + +用于自定义页面,包括: + +- routes config +- route components + +**Samples** + +- samples/custom-page + +## Settings Center + +由插件提供的系统级配置的能力 + +- plugin settings tabs + +**Samples** + +- samples/custom-settings-center-page + +## I18n + +国际化 + +- I18n + +**Samples** + +- samples/shop-i18n + +## Devtools + +- Commands +- Migrations + +**Samples** + +- samples/custom-command +- samples/custom-migration diff --git a/docs/en-US/development/guide/m.svg b/docs/en-US/development/guide/m.svg new file mode 100644 index 000000000..932d4de2b --- /dev/null +++ b/docs/en-US/development/guide/m.svg @@ -0,0 +1 @@ +
Pylons Application
restApi
action
acl
parseToken
db2resource
dataWrapping
Before
After
Request
Response
before
after
resourcer.use
checkRole
app.use
\ No newline at end of file diff --git a/docs/en-US/development/guide/middleware.md b/docs/en-US/development/guide/middleware.md new file mode 100644 index 000000000..fbb4ecc9f --- /dev/null +++ b/docs/en-US/development/guide/middleware.md @@ -0,0 +1,148 @@ +# 中间件 + +## 添加方法 + +1. `app.acl.use()` 添加资源权限级中间件,在权限判断之前执行 +2. `app.resourcer.use()` 添加资源级中间件,只有请求已定义的 resource 时才执行 +3. `app.use()` 添加应用级中间件,每次请求都执行 + +## 洋葱圈模型 + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); +``` + +访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: + +```js +{"data": [1,3,4,2]} +``` + +## 内置中间件及执行顺序 + +1. `cors` +2. `bodyParser` +3. `i18n` +4. `dataWrapping` +5. `db2resource` +6. `restApi` + 1. `parseToken` + 2. `checkRole` + 3. `acl` + 1. `acl.use()` 添加的其他中间件 + 4. `resourcer.use()` 添加的其他中间件 + 5. action handler +7. `app.use()` 添加的其他中间件 + +也可以使用 `before` 或 `after` 将中间件插入到前面的某个 `tag` 标记的位置,如: + +```ts +app.use(m1, { tag: 'restApi' }); +app.resourcer.use(m2, { tag: 'parseToken' }); +app.resourcer.use(m3, { tag: 'checkRole' }); +// m4 将排在 m1 前面 +app.use(m4, { before: 'restApi' }); +// m5 会插入到 m2 和 m3 之间 +app.resourcer.use(m5, { after: 'parseToken', before: 'checkRole' }); +``` + +如果未特殊指定位置,新增的中间件的执行顺序是: + +1. 优先执行 acl.use 添加的, +2. 然后是 resourcer.use 添加的,包括 middleware handler 和 action handler, +3. 最后是 app.use 添加的。 + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.resourcer.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); + +app.acl.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(5); + await next(); + ctx.body.push(6); +}); + +app.resourcer.define({ + name: 'test', + actions: { + async list(ctx, next) { + ctx.body = ctx.body || []; + ctx.body.push(7); + await next(); + ctx.body.push(8); + }, + }, +}); +``` + +访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: + +```js +{"data": [1,2]} +``` + +访问 http://localhost:13000/api/test:list 查看,浏览器响应的数据是: + +```js +{"data": [5,3,7,1,2,8,4,6]} +``` + +### resource 未定义,不执行 resourcer.use() 添加的中间件 + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.resourcer.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); +``` + +访问 http://localhost:13000/api/hello 查看,浏览器响应的数据是: + +```js +{"data": [1,2]} +``` + +以上示例,hello 资源未定义,不会进入 resourcer,所以就不会执行 resourcer 里的中间件 + +## 中间件用途 + +待补充 + +## 完整示例 + +待补充 + +- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting diff --git a/docs/en-US/development/guide/migration.md b/docs/en-US/development/guide/migration.md new file mode 100644 index 000000000..ad54e6fd2 --- /dev/null +++ b/docs/en-US/development/guide/migration.md @@ -0,0 +1,219 @@ +# 数据库迁移 + +应用在业务发展或版本升级过程中,某些情况会需要修改数据库表或字段等信息,为保证安全无冲突且可回溯的解决数据库变更,通常的做法是使用数据库迁移的方式完成。 + +## 介绍 + +Nocobase 基于 npm 包 [Umzug](https://www.npmjs.com/package/umzug) 处理数据库迁移。并将相关功能集成在命令行的子命令 `nocobase migrator` 中,大部分操作通过该命令处理。 + +### 仅增加表或字段无需迁移脚本 + +通常如果只是增加数据表或增加字段,可以不使用数据库迁移脚本,而是直接修改数据表定义(`collection`)的内容即可。例如文章表定义(`collections/posts.ts`)初始状态: + +```ts +export default { + name: 'posts', + fields: [ + { + name: 'title', + type: 'string', + } + ] +} +``` + +当需要增加一个分类字段时,直接修改原来的表结构定义文件内容: + +```ts +export default { + name: 'posts', + fields: [ + { + name: 'title', + type: 'string', + }, + { + name: 'category', + type: 'belongsTo' + } + ] +} +``` + +当新版本代码在环境中调用升级命令时,新的字段会以 Sequelize 中 sync 的逻辑自动同步到数据库中,完成表结构变更。 + +### 创建迁移文件 + +如果表结构的变更涉及到字段类型变更、索引调整等,需要人工创建迁移脚本文件: + +```bash +yarn nocobase migrator create --name change-some-field.ts --folder path/to/migrations +``` + +该命令会在 `path/to/migrations` 目录中创建一个基于时间戳的迁移脚本文件 `YYYY.MM.DDTHH.mm.ss.change-some-field.ts`,内容如下: + +```ts +import { Migration } from '@nocobase/server'; + +export default class MyMigration extends Migration { + async up() { + // TODO + } + + async down() { + // TODO + } +} +``` + +脚本导出的主类相关 API 可以参考 [`Migration` 类](/api/server/migration)。 + +### 数据库操作内容 + +`up()`、`down()` 是一对互逆操作,升级时会调用 `up()`,降级时会调用 `down()`。大部分情况我们主要考虑升级操作。 + +在升级中我们有几种方式进行数据库变更: + +```ts +import { Migration } from '@nocobase/server'; + +export default class MyMigration extends Migration { + async up() { + // 1. 针对自行管理的静态数据表,调用 Sequelize 提供的 queryInterface 实例上的方法 + await this.queryInterface.changeColumn('posts', 'title', { + type: DataTypes.STRING, + unique: true // 添加索引 + }); + + // 2. 针对被 collection-manager 插件管理的动态数据表,调用插件提供的 collections / fields 表的数据仓库方法 + await this.db.getRepository('fields').update({ + values: { + collectionName: 'posts', + name: 'title', + type: 'string', + unique: true // 添加索引 + } + }); + } +} +``` + +### 数据变更 + +除了表结构变更,也可以在迁移过程中导入需要的数据,或对数据进行调整: + +```ts +import { Migration } from '@nocobase/server'; + +export default class MyMigration extends Migration { + async up() { + await this.sequelize.transaction(async transaction => { + const defaultCategory = await this.db.getRepository('categories').create({ + values: { + title: '默认分类' + }, + transaction + }); + + await this.db.getRepository('posts').update({ + filter: { + categoryId: null + }, + values: { + categoryId: defaultCategory.id + }, + transaction + }); + }); + } +} +``` + +在一个脚本中有多项数据库操作时,建议使用事务保证所有操作都成功才算迁移完成。 + +### 执行升级 + +迁移脚本准备好以后,在项目目录下执行对应的升级命令即可完成数据库变更: + +```bash +yarn nocobase upgrade +``` + +根据数据库迁移的机制,迁移脚本执行成功后也会被记录在数据库的升级记录表中,只有第一次执行有效,之后的多次重复执行都会被忽略。 + +## 示例 + +### 修改主键字段类型 + +假设订单表一开始使用数字类型,但后期希望改成可以包含字母的字符串类型,我们可以在迁移文件中填写: + +```ts +import { Migration } from '@nocobase/server'; + +export default class MyMigration extends Migration { + async up() { + await this.sequelize.transaction(async transaction => { + await this.queryInterface.changeColumn('orders', 'id', { + type: DataTypes.STRING + }, { + transaction + }); + }); + } +} +``` + +注:修改字段类型只有在未加入新类型数据之前可以进行逆向降级操作,否则需要自行备份数据并对数据进行特定处理。 + +另外,直接修改数据表主键 `id` 的类型在某些数据库中会提示错误(SQLite 正常,PostgreSQL 失败)。这时候需要把相关操作分步执行: + +1. 创建一个新的字符串类型字段 `id_new;` +2. 复制原有表 `id` 的数据到新的字段; +3. 移除原有 `id` 主键约束; +4. 将原有 `id` 列改名为不使用的列名 `id_old`; +5. 将新的 `id_new` 列改名为 `id`; +6. 对新的 `id` 列增加主键约束; + +```ts +import { Migration } from '@nocobase/server'; + +export default class MyMigration extends Migration { + async up() { + await this.sequelize.transaction(async transaction => { + await this.queryInterface.addColumn('orders', 'id_new', { + type: DataTypes.STRING + }, { transaction }); + + const PendingOrderModel = this.sequelize.define('orders', { + id_new: DataTypes.STRING + }); + + await PendingOrderModel.update({ + id_new: col('id') + }, { + where: { + id: { [Op.not]: null } + }, + transaction + }); + + await this.queryInterface.removeConstraint('orders', 'orders_pkey', { transaction }); + + await this.queryInterface.renameColumn('orders', 'id', 'id_old', { transaction }); + + await this.queryInterface.renameColumn('orders', 'id_new', 'id', { transaction }); + + await this.queryInterface.addConstraint('orders', { + type: 'PRIMARY KEY', + name: 'orders_pkey', + fields: ['id'], + transaction + }); + }); + } +} +``` + +通常修改列类型在已存在数据量较大的表里操作时也建议用新列代替旧列的方式,性能会更好。其他更多细节可以参考 [Sequelize 的 `queryInterface` API](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js),以及各个数据库引擎的细节。 + +注:在执行升级命令后,应用启动之前请确保变更的表结构能够对应上 collections 中定义的内容,以免不一致导致错误。 diff --git a/docs/en-US/development/guide/pm-built-in.jpg b/docs/en-US/development/guide/pm-built-in.jpg new file mode 100644 index 000000000..2fe60fdac Binary files /dev/null and b/docs/en-US/development/guide/pm-built-in.jpg differ diff --git a/docs/en-US/development/guide/resources-actions.md b/docs/en-US/development/guide/resources-actions.md new file mode 100644 index 000000000..eb9978650 --- /dev/null +++ b/docs/en-US/development/guide/resources-actions.md @@ -0,0 +1,462 @@ +# 资源与操作 + +在 Web 开发领域,你可能听说过 RESTful 的概念,NocoBase 也借用了这个资源的概念来映射系统中的各种实体,比如数据库中的数据、文件系统中的文件或某个服务等。但 NocoBase 基于实践考虑,并未完全遵循 RESTful 的约定,而是参考 [Google Cloud API 设计指南](https://cloud.google.com/apis/design) 的规范做了一些扩展,以适应更多的场景。 + +## 基础概念 + +与 RESTful 中资源的概念相同,是系统中对外提供的可操作的对象,可以是数据表、文件、和其他自定义的对象。 + +操作主要指对资源的读取和写入,通常用于查阅数据、创建数据、更新数据、删除数据等。NocoBase 通过定义操作来实现对资源的访问,操作的核心其实是一个用于处理请求且兼容 Koa 的中间件函数。 + +### 数据表自动映射为资源 + +目前的资源主要针对数据库表中的数据,NocoBase 在默认情况下都会将数据库中的数据表自动映射为资源,同时也提供了服务端的数据接口。所以在默认情况下,只要使用了 `db.collection()` 定义了数据表,就可以通过 NocoBase 的 HTTP API 访问到这个数据表的数据资源了。自动生成的资源的名称与数据库表定义的表名相同,比如 `db.collection({ name: 'users' })` 定义的数据表,对应的资源名称就是 `users`。 + +同时,还为这些数据资源内置了常用的 CRUD 操作,对关系型数据资源也内置了关联数据从操作方法。 + +对简单数据资源的默认操作: + +* [`list`](/api/actions#list):查询数据表中的数据列表 +* [`get`](/api/actions#get):查询数据表中的单条数据 +* [`create`](/api/actions#create):对数据表创建单条数据 +* [`update`](/api/actions#update):对数据表更新单条数据 +* [`destroy`](/api/actions#destroy):对数据表删除单条数据 + +对关系资源除了简单的 CRUD 操作,还有默认的关系操作: + +* [`add`](/api/actions#add):对数据添加关联 +* [`remove`](/api/actions#remove):对数据移除关联 +* [`set`](/api/actions#set):对数据设置关联 +* [`toggle`](/api/actions#toggle):对数据添加或移除关联 + +比如定义一个文章数据表并同步到数据: + +```ts +app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' } + ] +}); + +await app.db.sync(); +``` + +之后针对 `posts` 数据资源的所有 CRUD 方法就可以直接通过 HTTP API 被调用了: + +```bash +# create +curl -X POST -H "Content-Type: application/json" -d '{"title":"first"}' http://localhost:13000/api/posts:create +# list +curl http://localhost:13000/api/posts:list +# update +curl -X PUT -H "Content-Type: application/json" -d '{"title":"second"}' http://localhost:13000/api/posts:update +# destroy +curl -X DELETE http://localhost:13000/api/posts:destroy?filterByTk=1 +``` + +### 自定义 Action + +当默认提供的 CRUD 等操作不满足业务场景的情况下,也可以对特定资源扩展更多的操作。比如是对内置操作额外的处理需求,或者需要设置默认参数的情况。 + +针对特定资源的自定义操作,如覆盖定义文章表的创建操作: + +```ts +// 等同于 app.resourcer.registerActions() +// 注册针对文章资源的 create 操作方法 +app.actions({ + async ['posts:create'](ctx, next) { + const postRepo = ctx.db.getRepository('posts'); + await postRepo.create({ + values: { + ...ctx.action.params.values, + // 限定当前用户是文章的创建者 + userId: ctx.state.currentUserId + } + }); + + await next(); + } +}); +``` + +这样在业务中就增加了合理的限制,用户不能以其他用户身份创建文章。 + +针对全局所有资源的自定义操作,如对所有数据表都增加导出的操作: + +```ts +app.actions({ + // 对所有资源都增加了 export 方法,用于导出数据 + async export(ctx, next) { + const repo = ctx.db.getRepository(ctx.action.resource); + const results = await repo.find({ + filter: ctx.action.params.filter + }); + ctx.type = 'text/csv'; + // 拼接为 CSV 格式 + ctx.body = results + .map(row => Object.keys(row) + .reduce((arr, col) => [...arr, row[col]], []).join(',') + ).join('\n'); + + next(); + } +}); +``` + +则可以按以下 HTTP API 的方式进行 CSV 格式的数据导出: + +```bash +curl http://localhost:13000/api/:export +``` + +### Action 参数 + +客户端的请求到达服务端后,相关的请求参数会被按规则解析并放在请求的 `ctx.action.params` 对象上。Action 参数主要有三个来源: + +1. Action 定义时默认参数 +2. 客户端请求携带 +3. 其他中间件预处理 + +在真正操作处理函数处理之前,上面这三个部分的参数会按此顺序被合并到一起,最终传入操作的执行函数中。在多个中间件中也是如此,上一个中间件处理完的参数会被继续随 `ctx` 传递到下一个中间件中。 + +针对内置的操作可使用的参数,可以参考 [@nocobase/actions](/api/actions) 包的内容。除自定义操作以外,客户端请求主要使用这些参数,自定义的操作可以根据业务需求扩展需要的参数。 + +中间件预处理主要使用 `ctx.action.mergeParams()` 方法,且根据不同的参数类型有不同的合并策略,具体也可以参考 [mergeParams()](/api/resourcer/action#mergeparams) 方法的内容。 + +内置 Action 的默认参数在合并时只能以 `mergeParams()` 方法针对各个参数的默认策略执行,以达到服务端进行一定操作限制的目的。例如: + +```ts +app.resource({ + name: 'posts', + actions: { + create: { + whitelist: ['title', 'content'], + blacklist: ['createdAt', 'createdById'], + } + } +}); +``` + +如上定义了针对 `posts` 资源的 `create` 操作,其中 `whitelist` 和 `blacklist` 分别是针对 `values` 参数的白名单和黑名单,即只允许 `values` 参数中的 `title` 和 `content` 字段,且禁止 `values` 参数中的 `createdAt` 和 `createdById` 字段。 + +### 自定义资源 + +数据型的资源还分为独立资源和关系资源: + +* 独立资源:`` +* 关系资源:`.` + +```ts +// 等同于 app.resourcer.define() + +// 定义文章资源 +app.resource({ + name: 'posts' +}); + +// 定义文章的作者资源 +app.resource({ + name: 'posts.user' +}); + +// 定义文章的评论资源 +app.resource({ + name: 'posts.coments' +}); +``` + +需要自定义的情况主要针对于非数据库表类资源,比如内存中的数据、其他服务的代理接口等,以及需要对已有数据表类资源定义特定操作的情况。 + +例如定义一个与数据库无关的发送通知操作的资源: + +```ts +app.resource({ + name: 'notifications', + actions: { + async send(ctx, next) { + await someProvider.send(ctx.request.body); + next(); + } + } +}); +``` + +则在 HTTP API 中可以这样访问: + +```bash +curl -X POST -d '{"title": "Hello", "to": "hello@nocobase.com"}' 'http://localhost:13000/api/notifications:send' +``` + +## 示例 + +我们继续之前 [数据表与字段示例](/development/guide/collections-fields#示例) 中的简单店铺场景,进一步理解资源与操作相关的概念。这里假设我们的在基于之前数据表的示例进行进一步资源和操作的定义,所以这里不再重复定义数据表的内容。 + +另外,只要定义了对应的数据表,我们对商品、订单等数据资源就可以直接使用这些默认操作以完成最场景的 CRUD 场景。 +### 覆盖默认操作 + +某些情况下,不只是简单的针对单条数据的操作时,或者默认操作的参数需要有一定控制时,我们也可以覆盖默认的操作行为。比如我们创建订单时,不应该由客户端提交 `userId` 来代表订单的归属,而是应该由服务端根据当前登录用户来确定订单归属,这时我们就可以覆盖默认的 `create` 操作。对于简单的扩展,我们直接在插件的主类中编写: + +```ts +import { Plugin } from '@nocobase/server'; +import actions from '@nocobase/actions'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + async create(ctx, next) { + ctx.action.mergeParams({ + values: { + userId: ctx.state.user.id + } + }); + + return actions.create(ctx, next); + } + } + }); + } +} +``` + +这样,我们在插件加载过程中针对订单数据资源就覆盖了默认的 `create` 操作,但在修改操作参数以后仍调用了默认逻辑,无需自行编写。修改提交参数的 `mergeParams()` 方法对内置默认操作来说非常有用,我们会在后面介绍。 + +### 数据表资源的自定义操作 + +当内置操作不能满足业务需求时,我们可以通过自定义操作来扩展资源的功能。例如通常一个订单会有很多状态,如果我们对 `status` 字段的取值设计为一系列枚举值: + +* `-1`:已取消 +* `0`:已下单,未付款 +* `1`:已付款,未发货 +* `2`:已发货,未签收 +* `3`:已签收,订单完成 + +那么我们就可以通过自定义操作来实现订单状态的变更,比如对订单进行一个发货的操作,虽然简单的情况下可以通过 `update` 操作来实现,但是如果还有支付、签收等更复杂的情况,仅使用 `update` 会造成语义不清晰且参数混乱的问题,因此我们可以通过自定义操作来实现。 + +首先我们增加一张发货信息表的定义,保存到 `collections/deliveries.ts`: + +```ts +export default { + name: 'deliveries', + fields: [ + { + type: 'belongsTo', + name: 'order' + }, + { + type: 'string', + name: 'provider' + }, + { + type: 'string', + name: 'trackingNumber' + }, + { + type: 'integer', + name: 'status' + } + ] +}; +``` + +同时对订单表也扩展一个发货信息的关联字段(`collections/orders.ts`): + +```ts +export default { + name: 'orders', + fields: [ + // ...other fields + { + type: 'hasOne', + name: 'delivery' + } + ] +}; +``` + +然后我们在插件的主类中增加对应的操作定义: + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + async deliver(ctx, next) { + const { filterByTk } = ctx.action.params; + const orderRepo = ctx.db.getRepository('orders'); + + const [order] = await orderRepo.update({ + filterByTk, + values: { + status: 2, + delivery: { + ...ctx.action.params.values, + status: 0 + } + } + }); + + ctx.body = order; + + next(); + } + } + }); + } +} +``` + +其中,Repository 是使用数据表数据仓库类,大部分进行数据读写的操作都会由此完成,详细可以参考 [Repository API](/api/database/repository) 部分。 + +定义好之后我们从客户端就可以通过 HTTP API 来调用“发货”这个操作了: + +```bash +curl \ + -X POST \ + -H 'Content-Type: application/json' \ + -d '{"provider": "SF", "trackingNumber": "SF1234567890"}' \ + '/api/orders:deliver/' +``` + +同样的,我们还可以定义更多类似的操作,比如支付、签收等。 + +### 参数合并 + +假设我们要提供用户查询自己的且只能查询自己的订单,同时我们需要限制用户不能查询已取消的订单,那么我们可以通过 action 的默认参数来定义: + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + // 对 list 操作的默认参数 + list: { + filter: { + // 由 users 插件扩展的过滤器运算符 + $isCurrentUser: true, + status: { + $ne: -1 + } + }, + fields: ['id', 'status', 'createdAt', 'updatedAt'] + } + } + }); + } +} +``` + +当用户从客户端查询时,也可以在请求的 URL 上加入其他的参数,比如: + +```bash +curl 'http://localhost:13000/api/orders:list?productId=1&fields=id,status,quantity,totalPrice&appends=product' +``` + +实际的查询条件会合并为: + +```json +{ + "filter": { + "$and": { + "$isCurrentUser": true, + "status": { + "$ne": -1 + }, + "productId": 1 + } + }, + "fields": ["id", "status", "quantity", "totalPrice", "createdAt", "updatedAt"], + "appends": ["product"] +} +``` + +并得到预期的查询结果。 + +另外的,如果我们需要对创建订单的接口限制不能由客户端提交订单编号(`id`)、总价(`totalPrice`)等字段,可以通过对 `create` 操作定义默认参数控制: + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + create: { + blacklist: ['id', 'totalPrice', 'status', 'createdAt', 'updatedAt'], + values: { + status: 0 + } + } + } + }); + } +} +``` + +这样即使客户端故意提交了这些字段,也会被过滤掉,不会存在于 `ctx.action.params` 参数集中。 + +如果还要有更复杂的限制,比如只能在商品上架且有库存的情况下才能下单,可以通过配置中间件来实现: + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + create: { + middlewares: [ + async (ctx, next) => { + const { productId } = ctx.action.params.values; + + const product = await ctx.db.getRepository('products').findOne({ + filterByTk: productId, + filter: { + enabled: true, + inventory: { + $gt: 0 + } + } + }); + + if (!product) { + return ctx.throw(404); + } + + await next(); + } + ] + } + } + }); + } +} +``` + +把部分业务逻辑(尤其是前置处理)放到中间件中,可以让我们的代码更加清晰,也更容易维护。 + +## 小结 + +通过上面的示例我们介绍了如何定义资源和相关的操作,回顾一下本章内容: + +* 数据表自动映射为资源 +* 内置默认的资源操作 +* 对资源自定义操作 +* 操作的参数合并顺序与策略 + +本章所涉及到的相关代码放到了一个完整的示例包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/guide/settings-center.md b/docs/en-US/development/guide/settings-center.md new file mode 100644 index 000000000..37286175c --- /dev/null +++ b/docs/en-US/development/guide/settings-center.md @@ -0,0 +1,33 @@ +# 配置中心 + + + +## 示例 + +```tsx | pure +import { SettingsCenterProvider } from '@nocobase/client'; +import React, { useContext } from 'react'; + +const HelloTab => () =>
Hello Tab
; + +export default React.memo((props) => { + return ( + {props.children} + ); +}); +``` + +完整示例查看 [samples/hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello)。 \ No newline at end of file diff --git a/docs/en-US/development/guide/settings-tab.jpg b/docs/en-US/development/guide/settings-tab.jpg new file mode 100644 index 000000000..34a8da116 Binary files /dev/null and b/docs/en-US/development/guide/settings-tab.jpg differ diff --git a/docs/en-US/development/guide/ui-router.md b/docs/en-US/development/guide/ui-router.md new file mode 100644 index 000000000..5a620942b --- /dev/null +++ b/docs/en-US/development/guide/ui-router.md @@ -0,0 +1,63 @@ +# UI 路由 + +NocoBase Client 的 Router 基于 [React Router](https://v5.reactrouter.com/web/guides/quick-start),可以通过 `` 来配置 ui routes,例子如下: + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Link, MemoryRouter as Router } from 'react-router-dom'; +import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client'; + +const Home = () =>

Home

; +const About = () =>

About

; + +const routes: RouteRedirectProps[] = [ + { + type: 'route', + path: '/', + exact: true, + component: 'Home', + }, + { + type: 'route', + path: '/about', + component: 'About', + }, +]; + +export default () => { + return ( + + + Home, About + + + + ); +}; +``` + +在完整的 NocoBase 应用里,可以类似以下的的方式扩展 Route: + +```tsx | pure +import { RouteSwitchContext } from '@nocobase/client'; +import React, { useContext } from 'react'; + +const HelloWorld = () => { + return
Hello ui router
; +}; + +export default React.memo((props) => { + const ctx = useContext(RouteSwitchContext); + ctx.routes.push({ + type: 'route', + path: '/hello-world', + component: HelloWorld, + }); + return {props.children}; +}); +``` + +完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/acl.md b/docs/en-US/development/guide/ui-schema-designer/acl.md similarity index 85% rename from docs/en-US/development/plugin-development/server/acl.md rename to docs/en-US/development/guide/ui-schema-designer/acl.md index 8cd8bbcac..d1c6127f6 100644 --- a/docs/en-US/development/plugin-development/server/acl.md +++ b/docs/en-US/development/guide/ui-schema-designer/acl.md @@ -1 +1,2 @@ # ACL + diff --git a/docs/en-US/development/guide/ui-schema-designer/block-provider.md b/docs/en-US/development/guide/ui-schema-designer/block-provider.md new file mode 100644 index 000000000..42fa25421 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/block-provider.md @@ -0,0 +1,2 @@ +# 区块 + diff --git a/docs/en-US/development/guide/ui-schema-designer/collection-manager.md b/docs/en-US/development/guide/ui-schema-designer/collection-manager.md new file mode 100644 index 000000000..ff813ad87 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/collection-manager.md @@ -0,0 +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/en-US/development/guide/ui-schema-designer/component-library.md b/docs/en-US/development/guide/ui-schema-designer/component-library.md new file mode 100644 index 000000000..db99b2c6b --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/component-library.md @@ -0,0 +1,82 @@ +# Schema 组件库 + +## 包装器组件 + +- BlockItem +- FormItem +- CardItem + +## 布局 + +- Page +- Grid +- Tabs +- Space + +## 字段组件 + +字段组件一般不单独使用,而是用在数据展示组件当中 + +- CollectionField:万能组件 +- Cascader +- Checkbox +- ColorSelect +- DatePicker +- Filter +- Formula +- IconPicker +- Input +- InputNumber +- Markdown +- Password +- Percent +- Radio +- RecordPicker +- RichText +- Select +- TimePicker +- TreeSelect +- Upload + +## 数据展示组件 + +需要与字段组件搭配使用 + +- Calendar +- Form +- Kanban +- Table +- TableV2 + +## 操作(onClick 事件型组件) + +- Action +- Action.Drawer +- Action.Modal +- ActionBar:用于操作布局 +- Menu + +## 其他 + +- G2plot +- Markdown.Void + +## `x-designer` 和 `x-initializer` 的使用场景 + +`x-decorator` 或 `x-component` 是以下组件时,`x-designer` 生效: + +- BlockItem +- CardItem +- FormItem +- Table.Column +- Tabs.TabPane + +`x-decorator` 或 `x-component` 是以下组件时,`x-initializer` 生效: + +- ActionBar +- BlockItem +- CardItem +- FormItem +- Grid +- Table +- Tabs \ No newline at end of file diff --git a/docs/en-US/development/guide/ui-schema-designer/demo1.tsx b/docs/en-US/development/guide/ui-schema-designer/demo1.tsx new file mode 100644 index 000000000..25469fab9 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/demo1.tsx @@ -0,0 +1,107 @@ +import { ArrayField } from '@formily/core'; +import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import { Table, TableColumnType } from 'antd'; +import React from 'react'; + +const ArrayTable = observer((props: any) => { + const { rowKey } = props; + const field = useField(); + const schema = useFieldSchema(); + const columnSchemas = schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'ArrayTable.Column') { + buf.push(s); + } + return buf; + }, []); + + const columns = columnSchemas.map((s) => { + return { + render: (value, record) => { + return ; + }, + } as TableColumnType; + }); + + return ; +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • ; +}); + +const schema: ISchema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { __path: '0', id: 1, value: 't1' }, + { + __path: '1', + id: 2, + value: 't2', + children: [ + { + __path: '1.children.0', + id: 5, + value: 't5', + parentId: 2, + }, + ], + }, + { + __path: '2', + id: 3, + value: 't3', + children: [ + { + __path: '2.children.0', + id: 4, + value: 't4', + parentId: 3, + children: [ + { + __path: '2.children.0.children.0', + id: 6, + value: 't6', + parentId: 4, + }, + { + __path: '2.children.0.children.1', + id: 7, + value: 't7', + parentId: 4, + }, + ], + }, + ], + }, + ], + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + properties: { + c1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + properties: { + value: { + type: 'string', + 'x-component': 'Value', + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; diff --git a/docs/en-US/development/guide/ui-schema-designer/designable.md b/docs/en-US/development/guide/ui-schema-designer/designable.md new file mode 100644 index 000000000..2aaa16b4a --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/designable.md @@ -0,0 +1,353 @@ +# Schema 的设计能力 + +Schema 的设计能力主要体现在 + +- 邻近位置插入,可用于 + - 插入新的 schema 节点 + - 现有 schema 节点的拖拽移动 +- schema 参数修改 + +设计器核心 API 和参数有: + +- 设计器 API:`createDesignable()` & `useDesignable()` +- Schema 参数:`x-designer`,用于适配设计器组件 + +## 设计器 API + +### createDesignable() + +```ts +import { Schema } from '@nocobase/client'; + +const current = new Schema({ + type: 'void', + 'x-component': 'div', +}); + +const { + designable, // 是否可以配置 + remove, + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = createDesignable({ + current, +}); + +const newSchema = { + type: 'void', + name: 'hello', + 'x-component': 'Hello', +}; + +insertAfterBegin(newSchema); + +console.log(current.toJSON()); +{ + type: 'void', + 'x-component': 'div', + properties: { + hello: { + type: 'void', + 'x-component': 'Hello', + }, + }, +} +``` + +### useDesignable() + +React Hook 场景也可以用 `useDesignable()` 获取当前 schema 组件设计器的 API + +```ts +const { + designable, // 是否可以配置 + remove, + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = useDesignable(); + +const schema = { + name: uid(), + 'x-component': 'Hello', +}; + +// 在当前节点的前面插入 +insertBeforeBegin(schema); +// 等同于 +insertAdjacent('beforeBegin', schema); + +// 在当前节点的第一个子节点前面插入 +insertAfterBegin(schema); +// 等同于 +insertAdjacent('afterBegin', schema); + +// 在当前节点的最后一个子节点后面 +insertBeforeEnd(schema); +// 等同于 +insertAdjacent('beforeEnd', schema); + +// 在当前节点的后面 +insertAfterEnd(schema); +// 等同于 +insertAdjacent('afterEnd', schema); +``` + +## 邻近位置插入 + +与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `insertAdjacent()` 方法用于解决邻近位置的插入问题。 + +四个邻近位置 + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +和 HTML 标签一样,Schema 组件库的组件也是可以相互组合,通过 insertAdjacent API 按实际需要插入在合理的邻近位置。 + +### 插入新的 schema 节点 + +在 Schema 组件里,可以直接通过 `useDesignable()` 在当前 Schema 的相邻位置插入新节点: + + +示例 + +```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 ( +
    +

    {fieldSchema.name}

    + + + + + + +
    {props.children}
    +
    + ); +}); + +const Page = observer((props) => { + return
    {props.children}
    ; +}); + +export default () => { + return ( + + + + ); +} +``` + +### 现有 schema 节点的拖拽移动 + +insertAdjacent 等方法也可用于节点的拖拽移动 + +```tsx +import React from 'react'; +import { uid } from '@formily/shared'; +import { observer, useField, useFieldSchema } from '@formily/react'; +import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core'; +import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client'; + +const useDragEnd = () => { + const { refresh } = useDesignable(); + + return ({ active, over }: DragEndEvent) => { + const activeSchema = active?.data?.current?.schema; + const overSchema = over?.data?.current?.schema; + + if (!activeSchema || !overSchema) { + return; + } + + const dn = createDesignable({ + current: overSchema, + }); + + dn.on('insertAdjacent', refresh); + dn.insertBeforeBeginOrAfterEnd(activeSchema); + }; +}; + +const Page = observer((props) => { + return {props.children}; +}); + +function Draggable(props) { + const { attributes, listeners, setNodeRef, transform } = useDraggable({ + id: props.id, + data: props.data, + }); + const style = transform + ? { + transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, + } + : undefined; + + return ( + + ); +} + +function Droppable(props) { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + const style = { + color: isOver ? 'green' : undefined, + }; + + return ( +
    + {props.children} +
    + ); +} + +const Block = observer((props) => { + const field = useField(); + const fieldSchema = useFieldSchema(); + return ( + +
    + Block {fieldSchema.name}{' '} + + Drag + +
    +
    + ); +}); + +export default function App() { + return ( + + + + ); +} +``` + +## `x-designer` 的应用 + +`x-designer` 通常只在 BlockItem、CardItem、FormItem 等包装器组件中使用。 + +```ts +{ + type: 'object', + properties: { + title: { + type: 'string', + title: '标题', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-designer': 'FormItem.Designer', + }, + status: { + type: 'string', + title: '状态', + 'x-decorator': 'FormItem', + 'x-component': 'Select', + 'x-designer': 'FormItem.Designer', + }, + }, +} +``` + +说明:NocoBase 提供的 Schema 设计器是以工具栏形式直接嵌入于界面,当激活界面配置时(`designable = true`),`x-designer` 组件(设计器工具栏)会显示出来,就可以通过工具栏更新当前 schema 组件了,工具栏提供的设计能力包括: + +- 拖拽移动:DndContext + DragHandler +- 插入新节点:SchemaInitializer +- 参数配置:SchemaSettings diff --git a/docs/en-US/development/guide/ui-schema-designer/designable.png b/docs/en-US/development/guide/ui-schema-designer/designable.png new file mode 100644 index 000000000..6d428a6ac Binary files /dev/null and b/docs/en-US/development/guide/ui-schema-designer/designable.png differ diff --git a/docs/en-US/development/guide/ui-schema-designer/extending-schema-components.md b/docs/en-US/development/guide/ui-schema-designer/extending-schema-components.md new file mode 100644 index 000000000..5fc966819 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/extending-schema-components.md @@ -0,0 +1,518 @@ +# 扩展 Schema 组件 + +除了原生的 html 标签,开发也可以适配更多的自定义组件,用于丰富 Schema 组件库。 + +扩展组件时,常用的方法: + +- [connect](https://react.formilyjs.org/api/shared/connect) 无侵入接入第三方组件,一般用于适配字段组件,和 [mapProps](https://react.formilyjs.org/api/shared/map-props)[、mapReadPretty](https://react.formilyjs.org/api/shared/map-read-pretty) 搭配使用 +- [observer](https://react.formilyjs.org/api/shared/observer) 当组件内部使用了 observable 对象,而你希望组件响应 observable 对象的变化时 + +## 最简单的扩展 + +直接将现成的 React 组件注册进来。 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const Hello = () =>

    Hello, world!

    ; + +const schema = { + type: 'void', + name: 'hello', + 'x-component': 'Hello', +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 通过 connect 接入第三方组件 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Input } from 'antd' +import { connect, mapProps, mapReadPretty } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const ReadPretty = (props) => { + return
    {props.value}
    +}; + +const SingleText = connect( + Input, + mapProps((props, field) => { + return { + ...props, + suffix: '后缀', + } + }), + mapReadPretty(ReadPretty), +); + +const schema = { + type: 'object', + properties: { + t1: { + type: 'string', + default: 'hello t1', + 'x-component': 'SingleText', + }, + t2: { + type: 'string', + default: 'hello t2', + 'x-component': 'SingleText', + 'x-pattern': 'readPretty', + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 使用 observer 响应数据 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Input } from 'antd'; +import { connect, observer, useForm } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const SingleText = connect(Input); + +const UsedObserver = observer((props) => { + const form = useForm(); + return
    UsedObserver: {form.values.t1}
    +}); + +const NotUsedObserver = (props) => { + const form = useForm(); + return
    NotUsedObserver: {form.values.t1}
    +}; + +const schema = { + type: 'object', + properties: { + t1: { + type: 'string', + 'x-component': 'SingleText', + }, + t2: { + type: 'string', + 'x-component': 'UsedObserver', + }, + t3: { + type: 'string', + 'x-component': 'NotUsedObserver', + }, + } +}; + +const components = { + SingleText, + UsedObserver, + NotUsedObserver +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 嵌套的 Schema + +- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套) +- `` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套) + +注意: + +- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `` 解决嵌套问题 +- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用 + +```tsx | pure + +``` + +### void 和 object 类型 schema 的嵌套 + +直接通过 props.children 就可以适配 properties 节点了 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +// Hello 组件适配了 children,可以嵌套 properties 了 +const Hello = (props) =>

    Hello, {props.children}!

    ; +const World = () => world; + +const schema = { + type: 'object', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` + +各类型 properties 渲染结果对比 + +```tsx +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const Hello = (props) =>

    Hello, {props.children}!

    ; +const World = () => world; + +const schema = { + type: 'object', + properties: { + title1: { + type: 'void', + 'x-content': 'Void schema,渲染 properties', + }, + void: { + type: 'void', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'void', + 'x-component': 'World', + }, + }, + }, + title2: { + type: 'void', + 'x-content': 'Object schema,渲染 properties', + }, + object: { + type: 'object', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + title3: { + type: 'void', + 'x-content': 'Array schema,不渲染 properties', + }, + array: { + type: 'array', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + title4: { + type: 'void', + 'x-content': 'String schema,不渲染 properties', + }, + string: { + type: 'string', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +### array 类型 schema 的嵌套 + +可以通过 `` 解决自定义嵌套问题 + +#### Array 元素是 string 或 number 时 + +```tsx +import React from 'react'; +import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const useValueSchema = () => { + const schema = useFieldSchema(); + return schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Value') { + return s; + } + return buf; + }); +}; + +const ArrayList = observer((props) => { + const field = useField(); + const schema = useValueSchema(); + return ( + <> + String Array +
      + {field.value?.map((item, index) => { + // 只有一个元素 + return + })} +
    + + ); +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • +}); + +const schema = { + type: 'object', + properties: { + strArr: { + type: 'array', + default: [1, 2, 3], + 'x-component': 'ArrayList', + properties: { + value: { + type: 'number', + 'x-component': 'Value', + }, + } + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +#### Array 元素是 Object 时 + +```tsx +import React from 'react'; +import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const ArrayList = observer((props) => { + const field = useField(); + const schema = useFieldSchema(); + // array 类型的 schema 无法 onlyRenderProperties,需要转化为 object 类型 + const objSchema = new Schema({ + type: 'object', + properties: schema.properties, + }); + return ( +
      + {field.value?.map((item, index) => { + // array 元素是 object + return ( + + ) + })} +
    + ); +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • +}); + +const schema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { value: 't1' }, + { value: 't2' }, + { value: 't3' }, + ], + 'x-component': 'ArrayList', + properties: { + value: { + type: 'number', + 'x-component': 'Value', + }, + } + } + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +#### Tree 结构数据 + +```tsx +import { ArrayField } from '@formily/core'; +import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import { Table, TableColumnType } from 'antd'; +import React from 'react'; + +const ArrayTable = observer((props: any) => { + const { rowKey } = props; + const field = useField(); + const schema = useFieldSchema(); + const columnSchemas = schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'ArrayTable.Column') { + buf.push(s); + } + return buf; + }, []); + + const columns = columnSchemas.map((s) => { + return { + render: (value, record) => { + return ; + }, + } as TableColumnType; + }); + + return
    ; +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • ; +}); + +const schema: ISchema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { __path: '0', id: 1, value: 't1' }, + { + __path: '1', + id: 2, + value: 't2', + children: [ + { + __path: '1.children.0', + id: 5, + value: 't5', + parentId: 2, + }, + ], + }, + { + __path: '2', + id: 3, + value: 't3', + children: [ + { + __path: '2.children.0', + id: 4, + value: 't4', + parentId: 3, + children: [ + { + __path: '2.children.0.children.0', + id: 6, + value: 't6', + parentId: 4, + }, + { + __path: '2.children.0.children.1', + id: 7, + value: 't7', + parentId: 4, + }, + ], + }, + ], + }, + ], + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + properties: { + c1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + properties: { + value: { + type: 'string', + 'x-component': 'Value', + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` \ No newline at end of file diff --git a/docs/en-US/development/guide/ui-schema-designer/index.md b/docs/en-US/development/guide/ui-schema-designer/index.md new file mode 100644 index 000000000..102eb7c9a --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/index.md @@ -0,0 +1,3 @@ +# Overview + + \ No newline at end of file diff --git a/docs/en-US/development/guide/ui-schema-designer/insert-adjacent.md b/docs/en-US/development/guide/ui-schema-designer/insert-adjacent.md new file mode 100644 index 000000000..f315f0c28 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/insert-adjacent.md @@ -0,0 +1,144 @@ +# 邻近位置插入 + +与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `dn.insertAdjacent()` 方法用于解决邻近位置的插入问题。 + +## 邻近位置 + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +## useDesignable() + +获取当前 schema 节点设计器的 API + +```ts +const { + designable, // 是否可以配置 + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = useDesignable(); + +const schema = { + name: uid(), + 'x-component': 'Hello', +}; + +// 在当前节点的前面插入 +insertBeforeBegin(schema); +// 等同于 +insertAdjacent('beforeBegin', schema); + +// 在当前节点的第一个子节点前面插入 +insertAfterBegin(schema); +// 等同于 +insertAdjacent('afterBegin', schema); + +// 在当前节点的最后一个子节点后面 +insertBeforeEnd(schema); +// 等同于 +insertAdjacent('beforeEnd', schema); + +// 在当前节点的后面 +insertAfterEnd(schema); +// 等同于 +insertAdjacent('afterEnd', schema); +``` + +示例 + +```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 ( +
    +

    {fieldSchema.name}

    + + + + + + +
    {props.children}
    +
    + ); +}); + +const Page = observer((props) => { + return
    {props.children}
    ; +}); + +export default () => { + return ( + + + + ); +} +``` diff --git a/docs/en-US/development/guide/ui-schema-designer/what-is-ui-schema.md b/docs/en-US/development/guide/ui-schema-designer/what-is-ui-schema.md new file mode 100644 index 000000000..0ea86181c --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/what-is-ui-schema.md @@ -0,0 +1,329 @@ +# UI Schema 是什么? + +一种描述前端组件的协议,基于 Formily Schema 2.0,类 JSON Schema 风格。 + +```ts +interface ISchema { + type: 'void' | 'string' | 'number' | 'object' | 'array'; + name?: string; + title?: any; + // 包装器组件 + ['x-decorator']?: string; + // 包装器组件属性 + ['x-decorator-props']?: any; + // 组件 + ['x-component']?: string; + // 组件属性 + ['x-component-props']?: any; + // 展示状态,默认为 'visible' + ['x-display']?: 'none' | 'hidden' | 'visible'; + // 组件的子节点,简单使用 + ['x-content']?: any; + // children 节点 schema + properties?: Record; + + // 以下仅字段组件时使用 + + // 字段联动 + ['x-reactions']?: SchemaReactions; + // 字段 UI 交互模式,默认为 'editable' + ['x-pattern']?: 'editable' | 'disabled' | 'readPretty'; + // 字段校验 + ['x-validator']?: Validator; + // 默认数据 + default: ?:any; + + // 设计器相关 + + // 设计器组件(工具栏),包括:拖拽移动、插入新节点、修改参数、移除等 + ['x-designer']?: any; + // 初始化器组件(工具栏),决定当前 schema 内部可以插入什么 + ['x-initializer']?: any; +} +``` + +## 最简单的组件 + +所有的原生 html 标签都可以转为 schema 的写法。如: + +```ts +{ + type: 'void', + 'x-component': 'h1', + 'x-content': 'Hello, world!', +} +``` + +JSX 示例 + +```tsx | pure +

    Hello, world!

    +``` + +## children 组件可以写在 properties 里 + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + +
    +``` + +## decorator 的巧妙用法 + +decorator + component 的组合,可以将两个组件放在一个 schema 节点里,降低 schema 结构复杂度,提高组件的复用率。 + +例如表单场景里,可以将 FormItem 组件与任意字段组件组合,在这里 FormItem 就是 Decorator。 + +```ts +{ + type: 'void', + ['x-component']: 'div', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + content: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input.TextArea', + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + + + + + + +
    +``` + +也可以提供一个 CardItem 组件,用于包裹所有区块,这样所有区块就都是 Card 包裹的了。 + +```ts +{ + type: 'void', + ['x-component']: 'div', + properties: { + title: { + type: 'string', + 'x-decorator': 'CardItem', + 'x-component': 'Table', + }, + content: { + type: 'string', + 'x-decorator': 'CardItem', + 'x-component': 'Kanban', + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + +
    + + + + + +``` + +## 组件的展示状态 + +- `'x-display': 'visible'`:显示组件 +- `'x-display': 'hidden'`:隐藏组件,数据不隐藏 +- `'x-display': 'none'`:隐藏组件,数据也隐藏 + +### `'x-display': 'visible'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'visible' + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + +
    +``` + +### `'x-display': 'hidden'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'hidden' + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + {/* 此处不输出 input 组件,对应的 name=title 的字段模型还存在 */} +
    +``` + +### `'x-display': 'none'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'none' + }, + }, +} +``` + +JSX 等同于 + +```tsx | pure +
    + {/* 此处不输出 input 组件,对应的 name=title 的字段模型也不存在了 */} +
    +``` + +## 组件的显示模式 + +用于字段组件,有三种显示模式: + +- `'x-pattern': 'editable'` 可编辑 +- `'x-pattern': 'disabled'` 不可编辑 +- `'x-pattern': 'readPretty'` 友好阅读 + +如单行文本 `` 组件,编辑和不可编辑模式为 ``,友好阅读模式为 `
    ` + +### `'x-pattern': 'editable'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'editable' + }, + }, +}; +``` + +JSX 等同于 + +```tsx | pure +
    + +
    +``` + +### `'x-pattern': 'disabled'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'disabled' + }, + }, +}; +``` + +JSX 等同于 + +```tsx | pure +
    + +
    +``` + +### `'x-pattern': 'readPretty'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'readPretty', + }, + }, +}; +``` + +JSX 等同于 + +```tsx | pure +
    +
    Hello
    +
    +``` diff --git a/docs/en-US/development/guide/ui-schema-designer/x-designer.md b/docs/en-US/development/guide/ui-schema-designer/x-designer.md new file mode 100644 index 000000000..276fe609c --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/x-designer.md @@ -0,0 +1,61 @@ +# x-designer 组件 + +## 内置 x-designer 组件 + +- Action.Designer +- Calendar.Designer +- Filter.Action.Designer +- Form.Designer +- FormItem.Designer +- FormV2.Designer +- FormV2.ReadPrettyDesigner +- DetailsDesigner +- G2Plot.Designer +- Kanban.Designer +- Kanban.Card.Designer +- Markdown.Void.Designer +- Menu.Designer +- TableV2.Column.Designer +- TableV2.ActionColumnDesigner +- TableBlockDesigner +- TableSelectorDesigner +- Tabs.Designer + +## 替换 + +```tsx | pure +import React, { useContext } from 'react'; +import { useFieldSchema } from '@formily/react'; +import { + SchemaComponentOptions, + GeneralSchemaDesigner, + SchemaSettings, + useCollection +} from '@nocobase/client'; +import React from 'react'; + +const CustomActionDesigner = () => { + const { name, title } = useCollection(); + const fieldSchema = useFieldSchema(); + return ( + + + + ); +}; + +export default React.memo((props) => { + return ( + {props.children} + ); +}); +``` diff --git a/docs/en-US/development/guide/ui-schema-designer/x-initializer.md b/docs/en-US/development/guide/ui-schema-designer/x-initializer.md new file mode 100644 index 000000000..7932e6062 --- /dev/null +++ b/docs/en-US/development/guide/ui-schema-designer/x-initializer.md @@ -0,0 +1,39 @@ +# x-initializer 组件 + +## 内置 x-initializer 组件 + +- BlockInitializers +- CalendarActionInitializers +- CreateFormBlockInitializers +- CustomFormItemInitializers +- DetailsActionInitializers +- FormActionInitializers +- FormItemInitializers +- KanbanActionInitializers +- ReadPrettyFormActionInitializers +- ReadPrettyFormItemInitializers +- RecordBlockInitializers +- RecordFormBlockInitializers +- SubTableActionInitializers +- TableActionColumnInitializers +- TableActionInitializers +- TableColumnInitializers +- TableSelectorInitializers +- TabPaneInitializers + +## 替换 + +```tsx |pure +import React, { useContext } from 'react'; +import { SchemaInitializerContext } from '@nocobase/client'; + +export default React.memo((props) => { + const items = useContext(SchemaInitializerContext); + const BlockInitializers = {}; + return ( + + {props.children} + + ); +}); +``` diff --git a/docs/en-US/development/http-api/action-api.md b/docs/en-US/development/http-api/action-api.md index 1445d05e5..26fd5e1b8 100644 --- a/docs/en-US/development/http-api/action-api.md +++ b/docs/en-US/development/http-api/action-api.md @@ -4,7 +4,7 @@ --- -Collection and Association resources are common. +Collection 和 Association 资源通用。 ### `create` @@ -15,12 +15,12 @@ POST /api/users:create?whitelist=a,b&blacklist=c,d ``` - Parameters - - whitelist White list - - blacklist Black list -- Request body: JSON data to be inserted -- Response body data: Created data JSON + - whitelist 白名单 + - blacklist 黑名单 +- Request body: 待插入的 JSON 数据 +- Response body data: 已创建的数据 JSON -#### Add a User +#### 新增用户 ```bash POST /api/users:create @@ -37,7 +37,7 @@ Response 200 (application/json) } ``` -#### Add a user's article +#### 新增用户文章 ```bash POST /api/users/1/posts:create @@ -49,11 +49,11 @@ Request Body Response 200 (application/json) { - "data": {}, + "data": {} } ``` -#### Association in Request Body +#### Request Body 里的 association ```bash POST /api/posts:create @@ -86,13 +86,13 @@ POST /api/users:create?filterByTk=1&whitelist=a,b&blacklist=c,d ``` - Parameters - - whitelist White list - - blacklist Black list - - filterByTk Filter by tk field, by default tk is the primary key of the data table - - filter Filter,support json string -- Request body: JSON data to be updated + - whitelist 白名单 + - blacklist 黑名单 + - filterByTk 根据 tk 字段过滤,默认情况 tk 为数据表的主键 + - filter 过滤,支持 json string +- Request body: 待更新的 JSON 数据 -#### Association in Request Body +#### Request Body 里的 association ```bash POST /api/posts:update/1 @@ -137,6 +137,3 @@ Response 200 (application/json) ### `remove` ### `toggle` - - - diff --git a/docs/en-US/development/http-api/filter-operators.md b/docs/en-US/development/http-api/filter-operators.md index a31112c5c..332f28a50 100644 --- a/docs/en-US/development/http-api/filter-operators.md +++ b/docs/en-US/development/http-api/filter-operators.md @@ -1,6 +1,6 @@ # Filter operators -## Common +## 通用 - $eq - $ne @@ -37,7 +37,7 @@ ## boolean -- $isTruthy +- $isTruly - $isFalsy ## date diff --git a/docs/en-US/development/http-api/index.md b/docs/en-US/development/http-api/index.md index 8d9323d65..a3117cf49 100644 --- a/docs/en-US/development/http-api/index.md +++ b/docs/en-US/development/http-api/index.md @@ -1,36 +1,36 @@ -# Overview +# 概述 -NocoBase HTTP API is designed based on Resource & Action, it is a superset of REST API. The operation is not limited to add, delete, change, and check, Resource Action can be extended arbitrarily in NocoBase. +NocoBase 的 HTTP API 基于 Resource & Action 设计,是 REST API 的超集,操作不局限于增删改查,在 NocoBase 里,Resource Action 可以任意的扩展。 -## Resource +## 资源 Resource -Resource has two expressions in NocoBase. +在 NocoBase 里,资源(resource)有两种表达方式: - `` - `.` -- collection is the set of all abstract data -- association is the association data for the collection -- resource includes both collection and collection.association +- collection 是所有抽象数据的集合 +- association 为 collection 的关联数据 +- resource 包括 collection 和 collection.association 两类 -### Example +### 示例 -- `posts` Post -- `posts.user` Post user -- `posts.tags` Post tags +- `posts` 文章 +- `posts.user` 文章用户 +- `posts.tags` 文章标签 -## Action +## 操作 Action -Representing resource operations as `:` +以 `:` 的方式表示资源操作 - `:` - `.:` -Built-in global operations for collection or association +内置的全局操作,可用于 collection 或 association - `create` - `get` @@ -39,20 +39,20 @@ Built-in global operations for collection or association - `destroy` - `move` -Built-in association operation for association only +内置的关联操作,仅用于 association - `set` - `add` - `remove` - `toggle` -### Example +### 示例 -- `posts:create` Create posts -- `posts.user:get` View posts user -- `posts.tags:add` Attach post tags (associate existing tags with post) +- `posts:create` 创建文章 +- `posts.user:get` 查看文章用户 +- `posts.tags:add` 附加文章标签(将现有的标签与文章关联) -## Request URL +## 请求 URL ```bash /api/: @@ -61,9 +61,9 @@ Built-in association operation for association only /api///:/ ``` -### Example +### 示例 -posts resource +posts 资源 ```bash POST /api/posts:create @@ -73,7 +73,7 @@ POST /api/posts:update/1 POST /api/posts:destroy/1 ``` -posts.comments resource +posts.comments 资源 ```bash POST /api/posts/1/comments:create @@ -83,7 +83,7 @@ POST /api/posts/1/comments:update/1 POST /api/posts/1/comments:destroy/1 ``` -posts.tags resource +posts.tags 资源 ```bash POST /api/posts/1/tags:create @@ -95,45 +95,45 @@ POST /api/posts/1/tags:add GET /api/posts/1/tags:remove ``` -## Resource location +## 资源定位 -- collection resource, locates the data to be processed by `collectionIndex`, `collectionIndex` must be unique -- association resource, locates the data to be processed by `collectionIndex` and `associationIndex` jointly, `associationIndex` may not be unique, but `collectionIndex` and `associationIndex`'s association indexes must be unique +- collection 资源,通过 `collectionIndex` 定位到待处理的数据,`collectionIndex` 必须唯一 +- association 资源,通过 `collectionIndex` 和 `associationIndex` 联合定位待处理的数据,`associationIndex` 可能不是唯一的,但是 `collectionIndex` 和 `associationIndex` 的联合索引必须唯一 -When viewing association resource details, the requested URL needs to provide both `` and ``, `` is not redundant because `` may not be unique. +查看 association 资源详情时,请求的 URL 需要同时提供 `` 和 ``,`` 并不多余,因为 `` 可能不是唯一的。 -For example, `tables.fields` indicates the fields of a data table +例如 `tables.fields` 表示数据表的字段 ```bash GET /api/tables/table1/fields/title GET /api/tables/table2/fields/title ``` -Both table1 and table2 have a title field. The title is unique in table1, but other tables may also have a title field +table1 和 table2 都有 title 字段,title 在 table1 里是唯一的,但是其他表也可能有 title 字段 -## Request parameters +## 请求参数 -Request parameters can be placed in the request's headers, parameters (query string), and body (GET requests do not have a body). +请求的参数可以放在 Request 的 headers、parameters(query string)、body(GET 请求没有 body) 里。 -A few special request parameters +几个特殊的 Parameters 请求参数 -- `filter` Data filtering, used in query-related operations. -- `filterByTk` filter by tk field, used in operations that specify details of the data. -- `sort` Sorting, used in query-related operations. -- `fields` which data to output, for use in query-related operations. -- `appends` additional relationship fields for use in query-related operations. -- `except` which fields to exclude (no output), used in query-related operations. -- `whitelist` fields whitelist, used in data creation and update related operations. -- `blacklist` fields blacklist, used in data creation and update related operations. +- `filter` 数据过滤,用于查询相关操作里; +- `filterByTk` 根据 tk 字段字过滤,用于指定详情数据的操作里; +- `sort` 排序,用于查询相关操作里。 +- `fields` 输出哪些数据,用于查询相关操作里; +- `appends` 附加关系字段,用于查询相关操作里; +- `except` 排除哪些字段(不输出),用于查询相关操作里; +- `whitelist` 字段白名单,用于数据的创建和更新相关操作里; +- `blacklist` 字段黑名单,用于数据的创建和更新相关操作里; ### filter -Data filter +数据过滤 ```bash # simple GET /api/posts?filter[status]=publish -# Recommend using the json string format, which requires encodeURIComponent encoding +# 推荐使用 json string 的格式,需要 encodeURIComponent 编码 GET /api/posts?filter={"status":"published"} # filter operators @@ -150,14 +150,14 @@ GET /api/posts?filter[user.email.$includes]=gmail GET /api/posts?filter={"user.email.$includes":"gmail"} ``` -[Click here for more information about filter operators](http-api/filter-operators) +[点此查看更多关于 filter operators 的内容](http-api/filter-operators) ### filterByTk -Filter by tk field. By default +根据 tk 字段过滤,默认情况: -- collection resource, tk is the primary key of the data table. -- association resource, tk is the targetKey field of the association. +- collection 资源,tk 为数据表的主键; +- association 资源,tk 为 association 的 targetKey 字段。 ```bash GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags @@ -165,20 +165,20 @@ GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags ### sort -Sorting. When sorting in descending order, the fields are preceded by the minus sign `-`. +排序。降序时,字段前面加上减号 `-`。 ```bash -# createAt field in ascending order -GET /api/posts:get?sort=createdAt -# createAt field descending -GET /api/posts:get?sort=-createdAt -# Multiple fields sorted jointly, createAt field descending, title A-Z ascending -GET /api/posts:get?sort=-createdAt,title +# createAt 字段升序 +GET /api/posts:get?sort=createdAt +# createAt 字段降序 +GET /api/posts:get?sort=-createdAt +# 多个字段联合排序,createAt 字段降序、title A-Z 升序 +GET /api/posts:get?sort=-createdAt,title ``` ### fields -Which fields to output +输出哪些数据 ```bash GET /api/posts:list?fields=name,title @@ -197,47 +197,47 @@ Response 200 (application/json) ### appends -Appends a relationship field +附加关系字段 ### except -Which fields to exclude (not output) for use in query-related operations. +排除哪些字段(不输出),用于查询相关操作里; ### whitelist -Whitelist +白名单 ```bash POST /api/posts:create?whitelist=title { "title": "My first post", - "date": "2022-05-19" # The date field will be filtered out and will not be written to the database + "date": "2022-05-19" # date 字段会被过滤掉,不会写入数据库 } ``` ### blacklist -Blacklist +黑名单 ```bash POST /api/posts:create?blacklist=date { "title": "My first post", - "date": "2022-05-19" # The date field will be filtered out and will not be written to the database + "date": "2022-05-19" # date 字段会被过滤掉,不会写入数据库 } ``` -## Request Response +## 请求响应 -Format of the response +响应的格式 ```ts type ResponseResult = { - data?: any; // Master data - meta?: any; // Additional Data - errors?: ResponseError[]; // Errors + data?: any; // 主体数据 + meta?: any; // 附加数据 + errors?: ResponseError[]; // 报错 }; type ResponseError = { @@ -246,9 +246,9 @@ type ResponseError = { }; ``` -### Example +### 示例 -View list +查看列表 ```bash GET /api/posts:list @@ -270,7 +270,7 @@ Response 200 (application/json) } ``` -View details +查看详情 ```bash GET /api/posts:get/1 @@ -280,11 +280,11 @@ Response 200 (application/json) { data: { id: 1 - } + }, } ``` -Error +报错 ```bash POST /api/posts:create @@ -298,4 +298,4 @@ Response 400 (application/json) }, ], } -``` +``` \ No newline at end of file diff --git a/docs/en-US/development/http-api/rest-api.md b/docs/en-US/development/http-api/rest-api.md index ae3d9b81a..2d7fd86d5 100644 --- a/docs/en-US/development/http-api/rest-api.md +++ b/docs/en-US/development/http-api/rest-api.md @@ -1,12 +1,12 @@ # REST API -NocoBase's HTTP API is a superset of the REST API, and the standard CRUD API also supports the RESTful style. +NocoBase 的 HTTP API 是 REST API 的超集,标准的 CRUD API 也支持 RESTful 风格。 -## Collection resources +## Collection 资源 --- -### Create collection +### 创建 collection HTTP API @@ -24,7 +24,7 @@ POST /api/ {} # JSON body ``` -### List collection +### 查看 collection 列表 HTTP API @@ -38,7 +38,7 @@ REST API GET /api/ ``` -### View collection details +### 查看 collection 详情 HTTP API @@ -53,7 +53,7 @@ REST API GET /api// ``` -### Update collection +### 更新 collection HTTP API @@ -62,7 +62,7 @@ POST /api/:update?filterByTk= {} # JSON body -# Or +# 或者 POST /api/:update/ {} # JSON body @@ -76,13 +76,13 @@ PUT /api// {} # JSON body ``` -### Delete collection +### 删除 collection HTTP API ```bash POST /api/:destroy?filterByTk= -# Or +# 或者 POST /api/:destroy/ ``` @@ -92,11 +92,11 @@ REST API DELETE /api// ``` -## Association resources +## Association 资源 --- -### Create Association +### 创建 Association HTTP API @@ -114,7 +114,7 @@ POST /api/// {} # JSON body ``` -### List Association +### 查看 Association 列表 HTTP API @@ -128,13 +128,13 @@ REST API GET /api/// ``` -### View Association details +### 查看 Association 详情 HTTP API ```bash GET /api///:get?filterByTk= -# Or +# 或者 GET /api///:get/ ``` @@ -144,7 +144,7 @@ REST API GET /api///:get/ ``` -### Update Association +### 更新 Association HTTP API @@ -153,7 +153,7 @@ POST /api///:update?filterByTk=//:update/ {} # JSON body @@ -164,16 +164,16 @@ REST API ```bash PUT /api///:update/ -{} # JSON +{} # JSON 数据 ``` -### Delete Association +### 删除 Association HTTP API ```bash POST /api///:destroy?filterByTk= -# Or +# 或者 POST /api///:destroy/ ``` diff --git a/docs/en-US/development/index.md b/docs/en-US/development/index.md new file mode 100644 index 000000000..ec9544ba7 --- /dev/null +++ b/docs/en-US/development/index.md @@ -0,0 +1,42 @@ +# 介绍 + +NocoBase 采用微内核架构,各类功能以插件形式扩展,所以微内核架构也叫插件化架构,由内核和插件两部分组成。内核提供了最小功能的 WEB 服务器,还提供了各种插件化接口;插件是按功能划分的各种独立模块,通过接口适配,具有可插拔的特点。插件化的设计降低了模块之间的耦合度,提高了复用率。随着插件库的不断扩充,常见的场景只需要组合插件即可完成基础搭建。例如 NocoBase 的无代码平台,就是由各种插件组合起来。 + + + +## 插件管理器 + +NocoBase 提供了强大的插件管理器用于管理插件,插件管理器的流程如下: + + + +开发可以通过 CLI 的方式管理插件: + +```bash +# 创建插件 +yarn pm create hello +# 注册插件 +yarn pm add hello +# 激活插件 +yarn pm enable hello +# 禁用插件 +yarn pm disable hello +# 删除插件 +yarn pm remove hello +``` + +无代码用户也可以通过插件管理器界面激活、禁用、删除已添加的本地插件: + + + +更多插件示例,查看 [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples)。 + +## 扩展能力 + +无论是通用性的功能,还是个性化定制,都建议以插件的形式编写,NocoBase 的扩展性体现在方方面面: + +- 可以是用户直观可见的界面相关的页面模块、区块类型、操作类型、字段类型等 +- 也可以是用于增强或限制 HTTP API 的过滤器、校验器、访问限制等 +- 也可以是更底层的数据表、迁移、事件、命令行等功能的增强 + +不仅如此,更多扩展介绍请查看 [扩展指南 - 概述](/development/guide) 章节。 \ No newline at end of file diff --git a/docs/zh-CN/welcome/introduction/learning-guide.md b/docs/en-US/development/learning-guide.md similarity index 100% rename from docs/zh-CN/welcome/introduction/learning-guide.md rename to docs/en-US/development/learning-guide.md diff --git a/docs/en-US/development/nocobase-cli.md b/docs/en-US/development/nocobase-cli.md index 0ba30255b..bb1c0402a 100644 --- a/docs/en-US/development/nocobase-cli.md +++ b/docs/en-US/development/nocobase-cli.md @@ -4,18 +4,18 @@ order: 2 # NocoBase CLI -NocoBase CLI is designed to help you develop, build, and deploy NocoBase applications. +NocoBase CLI 旨在帮助你开发、构建和部署 NocoBase 应用。 -NocoBase CLI supports both ts-node and node modes +NocoBase CLI 支持 ts-node 和 node 两种运行模式 -- ts-node mode (default): used for development environment, supports real-time compilation, but slow response -- node mode: for production environment, fast response, but need to execute `yarn nocobase build` to compile all source code first +- ts-node 模式(默认):用于开发环境,支持实时编译,但是响应较慢 +- node 模式:用于生产环境,响应迅速,但需要先执行 `yarn nocobase build` 将全部源码进行编译 -## Instructions +## 使用说明 ```bash $ yarn nocobase -h @@ -26,25 +26,25 @@ Options: -h, --help Commands: - create-plugin Create plugin scaffolding + create-plugin 创建插件脚手架 console - db:auth Verify that the database connection is successful - db:sync Generate relevant data tables and fields from collections configuration - install Install - start Start the application in the production environment - build Compile and package - clean Delete the compiled files - dev Start the application for the development environment and supports live compilation - doc Documentation development - test Test + db:auth 校验数据库是否连接成功 + db:sync 通过 collections 配置生成相关数据表和字段 + install 安装 + start 生产环境启动应用 + build 编译打包 + clean 删除编译之后的文件 + dev 启动应用,用于开发环境,支持实时编译 + doc 文档开发 + test 测试 umi - upgrade Upgrade + upgrade 升级 help ``` -## Use in scaffolding +## 在脚手架里应用 -The `scripts` in the application scaffolding `package.json` are as follows +应用脚手架 `package.json` 里的 `scripts` 如下: ```json { @@ -59,9 +59,9 @@ The `scripts` in the application scaffolding `package.json` are as follows } ``` -## Extensions +## 命令行扩展 -NocoBase CLI is built on [commander](https://github.com/tj/commander.js), you can freely extend the command, the extended command can be written in `app/server/index.ts`. +NocoBase CLI 基于 [commander](https://github.com/tj/commander.js) 构建,你可以自由扩展命令,扩展的 command 可以写在 `app/server/index.ts` 里: ```ts const app = new Application(config); @@ -69,7 +69,7 @@ const app = new Application(config); app.command('hello').action(() => {}); ``` -Alternatively, write in the plugin. +或者,写在插件里: ```ts class MyPlugin extends Plugin { @@ -79,22 +79,22 @@ class MyPlugin extends Plugin { } ``` -Terminal runs +终端运行 ```bash $ yarn nocobase hello ``` -## Built-in command line +## 内置命令行 -Sort by frequency of use +按使用频率排序 ### `dev` -Start the application in the development environment and the code is compiled in real time. +开发环境下,启动应用,代码实时编译。 -NocoBase will be installed automatically if it is not installed (refer to the install command) +NocoBase 未安装时,会自动安装(参考 install 命令) ```bash @@ -107,25 +107,25 @@ Options: -h, --help ``` -Example +示例 ```bash -# Start application for development environment, live compile +# 启动应用,用于开发环境,实时编译 yarn nocobase dev -# Start only the server side +# 只启动服务端 yarn nocobase dev --server -# Start only the client side +# 只启动客户端 yarn nocobase dev --client ``` ### `start` -Start the application in a production environment, the code needs to be yarn build. +生产环境下,启动应用,代码需要 yarn build。 -- NocoBase will be installed automatically if it is not installed (refer to the install command) -- If the source code has been modified, it needs to be repackaged (refer to the build command) +- NocoBase 未安装时,会自动安装(参考 install 命令) +- 源码有修改时,需要重新打包(参考 build 命令) @@ -140,16 +140,16 @@ Options: -h, --help ``` -Example +示例 ```bash -# Start the application in a production environment +# 启动应用,用于生产环境, yarn nocobase start ``` ### `install` -Install +安装 ```bash $ yarn nocobase install -h @@ -167,28 +167,28 @@ Options: -h, --help ``` -Example +示例 ```bash -# Initial Installation -yarn nocobase install -l en-US -e admin@nocobase.com -p admin123 -# Delete all data tables of NocoBase and reinstall -yarn nocobase install -f -l en-US -e admin@nocobase.com -p admin123 -# Empty the database and reinstall -yarn nocobase install -c -l en-US -e admin@nocobase.com -p admin123 +# 初始安装 +yarn nocobase install -l zh-CN -e admin@nocobase.com -p admin123 +# 删除 NocoBase 的所有数据表,并重新安装 +yarn nocobase install -f -l zh-CN -e admin@nocobase.com -p admin123 +# 清空数据库,并重新安装 +yarn nocobase install -c -l zh-CN -e admin@nocobase.com -p admin123 ``` -Difference between `-f/--force` and `-c/--clean` -- `-f/--force` Delete all data tables of NocoBase -- `-c/--clean` Delete all data tables of the database +`-f/--force` 和 `-c/--clean` 的区别 +- `-f/--force` 删除 NocoBase 的数据表 +- `-c/--clean` 清空数据库,所有数据表都会被删除 ### `upgrade` -Upgrade +升级 ```bash yarn nocobase upgrade @@ -196,7 +196,7 @@ yarn nocobase upgrade ### `test` -jest tests, supports all [jest-cli](https://jestjs.io/docs/cli) options, and extends `-c, --db-clean` support in addition. +jest 测试,支持所有 [jest-cli](https://jestjs.io/docs/cli) 的 options,除此之外还扩展了 `-c, --db-clean` 的支持。 ```bash $ yarn nocobase test -h @@ -204,62 +204,62 @@ $ yarn nocobase test -h Usage: nocobase test [options] Options: - -c, --db-clean Empty the database before running all tests + -c, --db-clean 运行所有测试前清空数据库 -h, --help ``` -Example +示例 ```bash -# Run all test files +# 运行所有测试文件 yarn nocobase test -# Run all test files in the specified folder +# 运行指定文件夹下所有测试文件 yarn nocobase test packages/core/server -# Run all tests in the specified file +# 运行指定文件里的所有测试 yarn nocobase test packages/core/database/src/__tests__/database.test.ts -# Empty the database before running tests +# 运行测试前,清空数据库 yarn nocobase test -c yarn nocobase test packages/core/server -c ``` ### `build` -Before deployed to the production environment, the source code needs to be compiled and packaged. It needs to be rebuilt if there are changes to the code. +代码部署到生产环境前,需要将源码编译打包,如果代码有修改,也需要重新构建。 ```bash -# All packages +# 所有包 yarn nocobase build -# Specified package +# 指定包 yarn nocobase build app/server app/client ``` ### `clean` -Delete the compiled file +删除编译之后的文件 ```bash yarn clean -# Equivalent to +# 等同于 yarn rimraf -rf packages/*/*/{lib,esm,es,dist} ``` ### `doc` -Documentation development +文档开发 ```bash -# Start documentation -yarn doc --lang=en-US # Equivalent to yarn doc dev -# Build the documentation and output it to . /docs/dist/ directory by default +# 启动文档 +yarn doc --lang=zh-CN # 等同于 yarn doc dev +# 构建文档,默认输出到 ./docs/dist/ 目录下 yarn doc build -# View the final result of the document output by dist -yarn doc serve --lang=en-US +# 查看 dist 输出的文档最终效果 +yarn doc serve --lang=zh-CN ``` ### `db:auth` -Verify that the database is successfully connected +校验数据库是否连接成功 ```bash $ yarn nocobase db:auth -h @@ -267,13 +267,13 @@ $ yarn nocobase db:auth -h Usage: nocobase db:auth [options] Options: - -r, --retry [retry] retry times + -r, --retry [retry] 重试次数 -h, --help ``` ### `db:sync` -Generate data tables and fields via collections configuration +通过 collections 配置生成数据表和字段 ```bash $ yarn nocobase db:sync -h @@ -287,24 +287,24 @@ Options: ### `umi` -`app/client` is built based on [umi](https://umijs.org/) and can be used to execute other related commands via `nocobase umi`. +`app/client` 基于 [umi](https://umijs.org/) 构建,可以通过 `nocobase umi` 来执行其他相关命令。 ```bash -# Generate the .umi cache required by the development environment +# 生成开发环境所需的 .umi 缓存 yarn nocobase umi generate tmp ``` ### `help` -The help command, also available with the option parameter, `-h` and `--help` +帮助命令,也可以用 option 参数,`-h` 和 `--help` ```bash -# View all cli +# 查看所有 cli yarn nocobase help -# You can also use -h +# 也可以用 -h yarn nocobase -h -# or --help +# 或者 --help yarn nocobase --help -# Option to view the db:sync command +# 查看 db:sync 命令的 option yarn nocobase db:sync -h ``` diff --git a/docs/en-US/development/others/build.md b/docs/en-US/development/others/build.md new file mode 100644 index 000000000..1294b9a65 --- /dev/null +++ b/docs/en-US/development/others/build.md @@ -0,0 +1 @@ +# Building \ No newline at end of file diff --git a/docs/en-US/development/others/testing.md b/docs/en-US/development/others/testing.md new file mode 100644 index 000000000..1f37805b5 --- /dev/null +++ b/docs/en-US/development/others/testing.md @@ -0,0 +1,163 @@ +# 单元测试 + +## 介绍 + +NocoBase 的测试基于 [Jest](https://jestjs.io/) 测试框架。同时,为了方便的编写测试,我们提供了两个工具类,在测试环境模拟正常的数据库和应用的服务端。 + +### MockDatabase + +模拟数据库类继承自 [`Database`](/api/database) 类,大部分内容没有区别,主要在构造函数默认内置了随机表前缀,在每个测试用例初始化数据库时相关数据表都通过前缀名称与其他用例进行隔离,在运行测试用例时互不影响。 + +```ts +import { MockDatabase } from '@nocobase/test'; + +describe('my suite', () => { + let db; + + beforeEach(async () => { + db = new MockDatabase(); + + db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] + }); + + await db.sync(); + }); + + test('my case', async () => { + const postRepository = db.getRepository('posts'); + const p1 = await postRepository.create({ + values: { + title: 'hello' + } + }); + + expect(p1.get('title')).toEqual('hello'); + }); +}); +``` + +### MockServer + +模拟服务器也继承自 [Application](/api/server/application) 类,除了内置的数据库实例是通过模拟数据库类生成的以外,还提供了比较方便的生成基于 [superagent](https://www.npmjs.com/package/superagent) 请求代理功能,针对从发送请求到获取响应的写法也集成了 `.resource('posts').create()`,比较简化。 + +```ts +import { mockServer } from '@nocobase/test'; + +describe('my suite', () => { + let app; + let agent; + let db; + + beforeEach(async () => { + app = mockServer(); + agent = app.agent(); + + db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] + }); + + await db.sync(); + await app.load(); + }); + + test('my case', async () => { + const { body } = await agent.resource('posts').create({ + values: { + title: 'hello' + } + }); + + expect(body.data.title).toEqual('hello'); + }); +}); +``` + +## 示例 + +我们以之前在 [资源与操作](development/guide/resources-actions) 章节的功能为例,来写一个插件的测试: + +```ts +import { mockServer } from '@nocobase/test'; +import Plugin from '../../src/server'; + +describe('shop actions', () => { + let app; + let agent; + let db; + + beforeEach(async () => { + app = mockServer(); + app.plugin(Plugin); + agent = app.agent(); + db = app.db; + + await app.load(); + await db.sync(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + test('product order case', async () => { + const { body: product } = await agent.resource('products').create({ + values: { + title: 'iPhone 14 Pro', + price: 7999, + enabled: true, + inventory: 1 + } + }); + expect(product.data.price).toEqual(7999); + + const { body: order } = await agent.resource('orders').create({ + values: { + productId: product.data.id + } + }); + expect(order.data.totalPrice).toEqual(7999); + expect(order.data.status).toEqual(0); + + const { body: deliveredOrder } = await agent.resource('orders').deliver({ + filterByTk: order.data.id, + values: { + provider: 'SF', + trackingNumber: '123456789' + } + }); + expect(deliveredOrder.data.status).toBe(2); + expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); + }); +}); +``` + +编写完成后,在命令行中允许测试命令: + +```bash +yarn test packages/samples/shop-actions +``` + +该测试将验证: + +1. 商品可以创建成功; +2. 订单可以创建成功; +3. 订单可以发货成功; + +当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。 + +## 小结 + +本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/en-US/development/plugin-development/client/overview.md b/docs/en-US/development/plugin-development/client/overview.md deleted file mode 100644 index 4bba659eb..000000000 --- a/docs/en-US/development/plugin-development/client/overview.md +++ /dev/null @@ -1 +0,0 @@ -# Overview \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/acl.md b/docs/en-US/development/plugin-development/client/providers/acl.md deleted file mode 100644 index 45cdee8d8..000000000 --- a/docs/en-US/development/plugin-development/client/providers/acl.md +++ /dev/null @@ -1 +0,0 @@ -# ACL \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/antd.md b/docs/en-US/development/plugin-development/client/providers/antd.md deleted file mode 100644 index a80a47303..000000000 --- a/docs/en-US/development/plugin-development/client/providers/antd.md +++ /dev/null @@ -1 +0,0 @@ -# Ant Design \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/api-client.md b/docs/en-US/development/plugin-development/client/providers/api-client.md deleted file mode 100644 index 235b800a3..000000000 --- a/docs/en-US/development/plugin-development/client/providers/api-client.md +++ /dev/null @@ -1 +0,0 @@ -# APIClient diff --git a/docs/en-US/development/plugin-development/client/providers/china-region.md b/docs/en-US/development/plugin-development/client/providers/china-region.md deleted file mode 100644 index 3706bbe44..000000000 --- a/docs/en-US/development/plugin-development/client/providers/china-region.md +++ /dev/null @@ -1 +0,0 @@ -# ChianRegion \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/collection-manager.md b/docs/en-US/development/plugin-development/client/providers/collection-manager.md deleted file mode 100644 index da019d550..000000000 --- a/docs/en-US/development/plugin-development/client/providers/collection-manager.md +++ /dev/null @@ -1 +0,0 @@ -# CollectionManager \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/i18n.md b/docs/en-US/development/plugin-development/client/providers/i18n.md deleted file mode 100644 index a4f29d977..000000000 --- a/docs/en-US/development/plugin-development/client/providers/i18n.md +++ /dev/null @@ -1 +0,0 @@ -# I18n \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/route-switch.md b/docs/en-US/development/plugin-development/client/providers/route-switch.md deleted file mode 100644 index 3bfc5a330..000000000 --- a/docs/en-US/development/plugin-development/client/providers/route-switch.md +++ /dev/null @@ -1 +0,0 @@ -# RouteSwitch \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/client/providers/schema-component.md b/docs/en-US/development/plugin-development/client/providers/schema-component.md deleted file mode 100644 index 90d3aa4d8..000000000 --- a/docs/en-US/development/plugin-development/client/providers/schema-component.md +++ /dev/null @@ -1 +0,0 @@ -# SchemaComponent diff --git a/docs/en-US/development/plugin-development/client/providers/schema-initializer.md b/docs/en-US/development/plugin-development/client/providers/schema-initializer.md deleted file mode 100644 index bd2e33b59..000000000 --- a/docs/en-US/development/plugin-development/client/providers/schema-initializer.md +++ /dev/null @@ -1 +0,0 @@ -# SchemaInitializer diff --git a/docs/en-US/development/plugin-development/index.md b/docs/en-US/development/plugin-development/index.md deleted file mode 100644 index 05cf8c1fd..000000000 --- a/docs/en-US/development/plugin-development/index.md +++ /dev/null @@ -1 +0,0 @@ -# Quick Start diff --git a/docs/en-US/development/plugin-development/server/cli.md b/docs/en-US/development/plugin-development/server/cli.md deleted file mode 100644 index db3cc6737..000000000 --- a/docs/en-US/development/plugin-development/server/cli.md +++ /dev/null @@ -1 +0,0 @@ -# CLI \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/database.md b/docs/en-US/development/plugin-development/server/database.md deleted file mode 100644 index 4a79ce0cd..000000000 --- a/docs/en-US/development/plugin-development/server/database.md +++ /dev/null @@ -1 +0,0 @@ -# Database \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/events.md b/docs/en-US/development/plugin-development/server/events.md deleted file mode 100644 index b649f948c..000000000 --- a/docs/en-US/development/plugin-development/server/events.md +++ /dev/null @@ -1 +0,0 @@ -# Event \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/middleware.md b/docs/en-US/development/plugin-development/server/middleware.md deleted file mode 100644 index c5033ed0e..000000000 --- a/docs/en-US/development/plugin-development/server/middleware.md +++ /dev/null @@ -1 +0,0 @@ -# Middleware \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/overview.md b/docs/en-US/development/plugin-development/server/overview.md deleted file mode 100644 index 4bba659eb..000000000 --- a/docs/en-US/development/plugin-development/server/overview.md +++ /dev/null @@ -1 +0,0 @@ -# Overview \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/plugin-manager.md b/docs/en-US/development/plugin-development/server/plugin-manager.md deleted file mode 100644 index b98d016d5..000000000 --- a/docs/en-US/development/plugin-development/server/plugin-manager.md +++ /dev/null @@ -1 +0,0 @@ -# PluginManager \ No newline at end of file diff --git a/docs/en-US/development/plugin-development/server/resourcer.md b/docs/en-US/development/plugin-development/server/resourcer.md deleted file mode 100644 index 5c0d22865..000000000 --- a/docs/en-US/development/plugin-development/server/resourcer.md +++ /dev/null @@ -1 +0,0 @@ -# Resource & Action \ No newline at end of file diff --git a/docs/en-US/development/pm-flow.svg b/docs/en-US/development/pm-flow.svg new file mode 100644 index 000000000..d15cf800c --- /dev/null +++ b/docs/en-US/development/pm-flow.svg @@ -0,0 +1 @@ +
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    app/client plugins
    pm.enable
    pm.disable
    pm.remove
    \ No newline at end of file diff --git a/docs/en-US/development/pm-ui.jpg b/docs/en-US/development/pm-ui.jpg new file mode 100644 index 000000000..4c8fdd3c1 Binary files /dev/null and b/docs/en-US/development/pm-ui.jpg differ diff --git a/docs/en-US/development/your-fisrt-plugin.md b/docs/en-US/development/your-fisrt-plugin.md new file mode 100644 index 000000000..03099b080 --- /dev/null +++ b/docs/en-US/development/your-fisrt-plugin.md @@ -0,0 +1,125 @@ +# 编写第一个插件 + +在此之前,需要先安装好 NocoBase: + +- [create-nocobase-app 安装](/getting-started/installation/create-nocobase-app) +- [Git 源码安装](/getting-started/installation/git-clone) + +安装好 NocoBase 之后,我们就可以开始插件开发之旅了。 + +## 创建插件 + +首先,你可以通过 CLI 快速的创建一个空插件,命令如下: + +```bash +yarn pm create hello +``` + +插件所在目录 `packages/plugins/hello`,插件目录结构为: + +```bash +|- /hello + |- /src + |- /client # 插件客户端代码 + |- /server # 插件服务端代码 + |- client.d.ts + |- client.js + |- package.json # 插件包信息 + |- server.d.ts + |- server.js +``` + +package.json 信息 + +```json +{ + "name": "@nocobase/plugin-hello", + "version": "0.1.0", + "main": "lib/server/index.js", + "devDependencies": { + "@nocobase/client": "0.8.0-alpha.1", + "@nocobase/test": "0.8.0-alpha.1" + } +} +``` + +NocoBase 插件也是 NPM 包,插件名和 NPM 包名的对应规则为 `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`。 + +`PLUGIN_PACKAGE_PREFIX` 为插件包前缀,可以在 .env 里自定义,[点此查看 PLUGIN_PACKAGE_PREFIX 说明](/api/env#plugin_package_prefix)。 + +## 编写插件 + +查看 `packages/plugins/hello/src/server/plugin.ts` 文件,并修改为: + +```ts +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class HelloPlugin extends Plugin { + afterAdd() {} + + beforeLoad() {} + + async load() { + this.db.collection({ + name: 'hello', + fields: [ + { type: 'string', name: 'name' } + ], + }); + this.app.acl.allow('hello', '*'); + } + + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default HelloPlugin; +``` + +## 注册插件 + +```bash +yarn pm add hello +``` + +## 激活插件 + +插件激活时,会自动创建刚才编辑插件配置的 hello 表。 + +```bash +yarn pm enable hello +``` + +## 启动应用 + +```bash +# for development +yarn dev + +# for production +yarn build +yarn start +``` + +## 体验插件功能 + +向插件的 hello 表里插入数据 + +```bash +curl --location --request POST 'http://localhost:13000/api/hello:create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "name": "Hello world" +}' +``` + +查看 hello 表数据 + +```bash +curl --location --request GET 'http://localhost:13000/api/hello:list' +``` diff --git a/docs/en-US/getting-started/upgrading.md b/docs/en-US/getting-started/upgrading.md deleted file mode 100644 index 126d189d0..000000000 --- a/docs/en-US/getting-started/upgrading.md +++ /dev/null @@ -1,65 +0,0 @@ -# Upgrading - - - -This document only applies to versions after v0.7.0-alpha.57. Projects created before need to be recreated. - - - -Make sure to back up your database before upgrading - -## Docker - -Switch to the project directory - -```bash -# SQLite -cd nocobase/docker/app-sqlite -# MySQL -cd nocobase/docker/app-mysql -# PostgreSQL -cd nocobase/docker/app-postgres -``` - -In the `docker-compose.yml` file, replace the image of the app container with the latest version - -```yml -services: - app: - image: nocobase/nocobase:0.7.0-alpha.62 -``` - -Download the image and start it - -```bash -# Download the latest image and start it -docker-compose up -d app -# Check the status of the app process -docker-compose logs app -``` - -## create-nocobase-app - -Execute the `nocobase upgrade` command - -```bash -# Switch to the project directory -cd my-nocobase-app -# Execute the update command -yarn nocobase upgrade -# Start -yarn start -``` - -## Git source code - -```bash -# Switch to the project directory -cd my-nocobase-app -# Pull latest source code -git pull -# Execute the update command -yarn nocobase upgrade -# Start -yarn start -``` diff --git a/docs/en-US/index.md b/docs/en-US/index.md index 546202590..ab11f4321 100644 --- a/docs/en-US/index.md +++ b/docs/en-US/index.md @@ -1,52 +1 @@ -# Introduction - -![](https://nocobase.oss-cn-beijing.aliyuncs.com/bbcedd403d31cd1ccc4e9709581f5c2f.png) - -**Note:** 📌 - -NocoBase is in early stage of development and is subject to frequent changes, please use caution in production environments. - -## What is NocoBase - -NocoBase is a scalability-first, open-source no-code development platform. No programming required, build your own collaboration platform, management system with NocoBase in minutes. - -Homepage: -https://www.nocobase.com/ - -Online Demo: -https://demo.nocobase.com/new - -Contact Us: -hello@nocobase.com - -## Features - -- **Open source and free** - - Unrestricted commercial use under the Apache-2.0 license - - Full code ownership, private deployment, private and secure data - - Free to expand and develop for actual needs - - Good ecological support -- **Strong no-code capability** - - Data Model - - Create independent data models using dozens of field types such as text, date, number, attachment, option, icon, etc., and various association relationships such as one-to-one, one-to-many, many-to-many, etc. - - Block - - Display and manipulate data within a page using a free combination of block types such as tables, forms, kanban, calendars, details, etc. - - ACL - - Role-based control of user's system configuration rights, data action rights and menu access rights. - - Workflow - - Repetitive tasks are replaced by automation to increase efficiency. Manual approval is required for important matters. - - Menu - - You can group menus, support adding pages and links, and support unlimited submenus. - - Action - - Support filtering, exporting, adding, deleting, modifying, viewing and other operations to process data, which can be extended to more types. -- **Built for extended development** - - Microkernel architecture, flexible and easy to extend, with a robust plug-in system - - Node.js-based, with popular frameworks and technologies, including Koa, Sequelize, React, Formily, Ant Design, etc. - - Progressive development, easy for getting-started, friendly to newcomers - - No binding, no strong dependencies, can be used in any combination or extensions, can be used in existing projects - -## Architecture - -![](https://www.nocobase.com/images/NocoBaseMindMapLite.png) - -[Click here to view the full image](https://www.nocobase.com/images/NocoBaseMindMap.png) + \ No newline at end of file diff --git a/docs/en-US/manual/blocks-guide/charts.md b/docs/en-US/manual/blocks-guide/charts.md new file mode 100755 index 000000000..e349de5a5 --- /dev/null +++ b/docs/en-US/manual/blocks-guide/charts.md @@ -0,0 +1,201 @@ +# Charts + +Currently, chart blocks in NocoBase need to be implemented via a configuration file or by writing code. The chart library uses [g2plot](https://g2plot.antv.vision/en/examples/gallery), which theoretically supports all charts on [https://g2plot.antv.vision/en/examples/gallery](https://g2plot.antv.vision/en/examples/gallery). The currently configurable charts include + +- Column charts +- Bar charts +- Line charts +- Pie charts +- Area charts + +## Add and edit charts + +![chart-edit.gif](./charts/chart-edit.gif) + +## Chart Configuration + +The initial chart configuration is static JSON data + +```json +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ], + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +Data supports expression, NocoBase has a built-in `requestChartData(config)` function for custom chart data requests. Parameters are described in: [https://github.com/axios/axios#request-config](https://github.com/axios/axios#request-config) + +Example. + +```json +{ + "data": "{{requestChartData({ url: 'collectionName:getColumnChartData' })}}", + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +HTTP API example. + +```bash +GET /api/collectionName:getColumnChartData + +Response Body +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ] +} + +``` + +## Server-side implementation + +Add a custom getColumnChartData method to the data table named collectionName. + +```js +app.resourcer.registerActionHandlers({ + 'collectionName:getColumnChartData': (ctx, next) => { + // The data to be output + ctx.body = []; + await next(); + }, +}); + +``` + +## Video + +### Static data +https://user-images.githubusercontent.com/1267426/198877269-1c56562b-167a-4808-ada3-578f0872bce1.mp4 + +### Dynamic data +https://user-images.githubusercontent.com/1267426/198877336-6bd85f0b-17c5-40a5-9442-8045717cc7b0.mp4 + +### More charts + +Theoretically supports all charts on [https://g2plot.antv.vision/en/examples/gallery](https://g2plot.antv.vision/en/examples/gallery) + +https://user-images.githubusercontent.com/1267426/198877347-7fc2544c-b938-4e34-8a83-721b3f62525e.mp4 + +## JS Expressions + +Syntax + +```js +{ + "key1": "{{ js expression }}" +} +``` + +https://user-images.githubusercontent.com/1267426/198877361-808a51cc-6c91-429f-8cfc-8ad7f747645a.mp4 + diff --git a/docs/en-US/manual/blocks-guide/charts/chart-edit.gif b/docs/en-US/manual/blocks-guide/charts/chart-edit.gif new file mode 100755 index 000000000..86a3540a3 Binary files /dev/null and b/docs/en-US/manual/blocks-guide/charts/chart-edit.gif differ diff --git a/docs/en-US/manual/core-concepts/a-b-c.md b/docs/en-US/manual/core-concepts/a-b-c.md new file mode 100755 index 000000000..d1b6e9951 --- /dev/null +++ b/docs/en-US/manual/core-concepts/a-b-c.md @@ -0,0 +1,21 @@ +# A·B·C + +At the no-code level, the core concept of NocoBase can be summarized as `A·B·C`. + +`A·B·C` stands for `Action·Block·Collection`. We design data structure by `Collection`, organize and display data by `Block`, and interact with data by `Action`. + +## **Separate "data structure" and "user interface"** + +When defining data, focus on defining data; when defining views, focus on defining views. + +Abstract the business by defining the data; then define blocks to organize the content to present the data in the way you want. + +## **One Data table, Many Presentations** + +Abstract a unified data model for the business, and then with blocks you can build a variety of presentations for the same data table for different scenarios, different roles, and different combinations. + +## **Driven by Action** + +`Collection`defines the structure of the data, and the `Block`organize the presentation of the data. So, what drives data interactions and changes? The answer is `Action`. + +`Block`present the data to the user, and `Action`send the user's instructions to the server to complete the interaction or change of the data. diff --git a/docs/en-US/manual/core-concepts/actions.md b/docs/en-US/manual/core-concepts/actions.md new file mode 100755 index 000000000..b4551a486 --- /dev/null +++ b/docs/en-US/manual/core-concepts/actions.md @@ -0,0 +1,31 @@ +# Actions + +An `action` is a collection of actions that accomplish a specific goal. An `action` is used in NocoBase to process data or communicate with the server. Actions are usually triggered by clicking a button. + +## Action types + +NocoBase currently supports more than 10 types of actions, and more can be supported in the future by way of plugins. + +| Name | Description | +| --- | --- | +| Filter | Specifies the range of data to be displayed | +| Add | Opens a popup window for adding new data, which usually contains a form block. | +| View | Opens a popup window to view the specified data, which usually contains a detail block. | +| Edit | Opens a popup window to modify the specified data, which usually contains a form block. | +| Delete | Opens a dialog box to delete the specified data, and then delete it after confirmation. | +| Export | Exports data to Excel, often combined with filtering. | +| Print | Opens a browser print window to print the specified data, often combined with a detail block. | +| Submit | Submit the data of the specified form block to the server. | +| Refresh | Refreshes the data in the current block. | +| Import | Import data from an Excel template | +| Bulk Edit | Batch Edit Data | +| Bulk Update | Batch Update Data | +| Popup | Open a popup window or drawer in which you can place blocks | +| Update record | Automatically update specified fields when clicked | +| Customize request | Send requests to third parties | + +## Configure actions + +In UI Editor mode, move the mouse over an action button and the configuration items supported by that action will appear in the upper right corner. For example, for the filter action. + +![action-config-5.jpg](./actions/action-config-5.jpg) \ No newline at end of file diff --git a/docs/en-US/manual/core-concepts/actions/action-config-5.jpg b/docs/en-US/manual/core-concepts/actions/action-config-5.jpg new file mode 100755 index 000000000..b9429826f Binary files /dev/null and b/docs/en-US/manual/core-concepts/actions/action-config-5.jpg differ diff --git a/docs/en-US/manual/core-concepts/blocks.md b/docs/en-US/manual/core-concepts/blocks.md new file mode 100755 index 000000000..ed694e978 --- /dev/null +++ b/docs/en-US/manual/core-concepts/blocks.md @@ -0,0 +1,86 @@ +# Blocks + +Blocks are views used to display and manipulate data. In NocoBase, pages, popups and drawers are considered as containers of blocks, and the container is like a canvas in which various blocks can be placed. + +Thanks to NocoBase's design of separating data and view, pages carry data through blocks and organize and manage data in different forms according to different block types. + +## Structure of blocks + +A block consists of three parts. + +1. content area: the body of the block +2. action area: various action buttons can be placed to manipulate the block data +3. configuration area: buttons for operating the block configuration + +![6.block.jpg](./blocks/6.block.jpg) + +## Block types + +![add-block.jpg](./blocks/add-block.jpg) + +NocoBase currently has 10+ types of blocks built in, more can be supported in the future by way of plugins. + +- **Data blocks:** blocks designed for organizing data. + - **Table:** A block that present multiple data in a table, either a single collection or multiple collections that are related to each other. + - **Form:** A block for entering or editing data in a form, either for a particular collection or for multiple collections that are related to each other in a unified way. + - **Details:** A block to display a specific record, either for a particular collection or for multiple collection that are related to each other. + - **Calendar:** A block that displays multiple records in the form of a calendar, suitable for certain data with important characteristics in terms of date. + - **Kanban:** A block that displays multiple data in the form of a Kanban board, suitable for managing production processes. +- **Chart blocks:** Blocks designed for graphical presentation of statistical data. Currently supports: bar graphs, bar charts, line graphs, pie charts, area charts, etc. +- **Other blocks:** Blocks designed to display special data. + - **Markdown:** Text content written in Markdown. + - **Audit Log**: Show the change records of all data in a collection, including new, edit and delete. + +## Add block + +Enter the UI Editor mode and click the Add block button on the page and in the pop-up window to add the block. The options are divided into 4 steps. + +1. Select block type: Currently available block types include Table, Form, Details, Calendar, Kanban, Markdown +2. Select Collection: All collections will be listed here +3. Choose the creation method: create a blank block, or duplicate a block template , or reference a block template +4. Select Template: If you selected Create from Template in step 3, select the template in step 4 + +![6.block-add.jpg](./blocks/6.block-add.jpg) + +## Configure Blocks + +The configuration of blocks consists of three elements. + +- Configure block content +- Configure block actions +- Configure block properties + +### Configure block content + +Take the table block as an example, the content of the block is the columns to be displayed in the table. Click Configure columns to configure the columns to be displayed. + +![6.block-content.gif](./blocks/6.block-content.gif) + +### Configure block actions + +Take table block as an example, there are filter, add, delete, view, edit, customize and other actions available. Click the Configure actions button to configure the actions. Each of the action buttons can be configured for their own properties. + +![6.block-content.gif](./blocks/6.block-content%201.gif) + +### Configure block properties + +Move the cursor to the upper right corner of the block and you will see the block configuration button. Using the table block as an example, the following properties can be configured. + +- Drag & drop sorting +- Set the data scope +- Set default sorting rules +- Records per page + +![6.collection-setting.gif](./blocks/6.collection-setting.gif) + +## Adjust the layout + +It is possible to put either just one block or multiple blocks in combination within the page. You can adjust the position and width of the blocks by dragging and dropping them. + +![block-drag.gif](./blocks/block-drag.gif) + +## Block templates + +You can save a block as a template, which can be copied or referenced later. + +For example, if a form is used for both adding and editing data, then you can save this form as a template and reference it in the Add Data and Edit Data blocks. \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/blocks/6.block-add.jpg b/docs/en-US/manual/core-concepts/blocks/6.block-add.jpg similarity index 100% rename from docs/en-US/user-manual/advanced-guide/blocks/6.block-add.jpg rename to docs/en-US/manual/core-concepts/blocks/6.block-add.jpg diff --git a/docs/en-US/user-manual/advanced-guide/blocks/6.block-content 1.gif b/docs/en-US/manual/core-concepts/blocks/6.block-content 1.gif similarity index 100% rename from docs/en-US/user-manual/advanced-guide/blocks/6.block-content 1.gif rename to docs/en-US/manual/core-concepts/blocks/6.block-content 1.gif diff --git a/docs/en-US/user-manual/advanced-guide/blocks/6.block-content.gif b/docs/en-US/manual/core-concepts/blocks/6.block-content.gif similarity index 100% rename from docs/en-US/user-manual/advanced-guide/blocks/6.block-content.gif rename to docs/en-US/manual/core-concepts/blocks/6.block-content.gif diff --git a/docs/en-US/user-manual/advanced-guide/blocks/6.block.jpg b/docs/en-US/manual/core-concepts/blocks/6.block.jpg similarity index 100% rename from docs/en-US/user-manual/advanced-guide/blocks/6.block.jpg rename to docs/en-US/manual/core-concepts/blocks/6.block.jpg diff --git a/docs/en-US/user-manual/advanced-guide/blocks/6.collection-setting.gif b/docs/en-US/manual/core-concepts/blocks/6.collection-setting.gif similarity index 100% rename from docs/en-US/user-manual/advanced-guide/blocks/6.collection-setting.gif rename to docs/en-US/manual/core-concepts/blocks/6.collection-setting.gif diff --git a/docs/en-US/manual/core-concepts/blocks/add-block.jpg b/docs/en-US/manual/core-concepts/blocks/add-block.jpg new file mode 100755 index 000000000..2b463f2cf Binary files /dev/null and b/docs/en-US/manual/core-concepts/blocks/add-block.jpg differ diff --git a/docs/en-US/manual/core-concepts/blocks/block-drag.gif b/docs/en-US/manual/core-concepts/blocks/block-drag.gif new file mode 100755 index 000000000..69563d0dc Binary files /dev/null and b/docs/en-US/manual/core-concepts/blocks/block-drag.gif differ diff --git a/docs/en-US/manual/core-concepts/collections.md b/docs/en-US/manual/core-concepts/collections.md new file mode 100755 index 000000000..3defda4cb --- /dev/null +++ b/docs/en-US/manual/core-concepts/collections.md @@ -0,0 +1,60 @@ +# Collections + +Before developing a system, we usually have to abstract the business and build a data model. It's called collections in NocoBase. Collections in NocoBase consists of fields (columns) and records (rows). The concept of a collection is similar to the concept of a data table in a relational database, but the concept of fields is slightly different. + +For example, in a collection describing an order, each column contains information about a specific attribute of the order, such as the delivery address, while each row contains all the information about a specific order, such as order number, customer name, phone number, delivery address, etc. + +## Separate "data structure" and "user interface" + +NocoBase's `Data` and `View` are separated, managed and presented by `Collections` and `Blocks` respectively. + +This means that, + +- you can create **one collection** and design **one set of interfaces** for it, to enable the presentation and manipulation of data. +- You can also create **one collection** and design **many sets of interfaces** for it, for the presentation and manipulation of data in different scenarios or roles. +- You can also create **multiple collections** and then design **one set of interfaces** for them to display and manipulate multiple data tables at the same time. +- You can even create **multiple collections** and then design **multiple sets of interfaces** for them, each of which can operate on multiple data tables and perform unique functions. + +Simply put, the separation of data and interfaces makes **the organization and management of data more flexible**, and how you present the data depends on how you configure the interfaces. + +## Field Types + +NocoBase currently supports the following dozens of fields, and more can be supported in the future by way of plug-ins. + +| Name | Type | +| --- | --- | +| single-line text | basic type | +| Icon | Basic Type | +| Multi-line text | Basic type | +| Password | Basic type | +| Mobile Number | Basic Type | +| Number | Basic Type | +| Integer | Basic Type | +| Email | Basic Type | +| Percent | Basic Type | +| Drop-down menu (single selection) | Select type | +| Drop-down menu (multiple choice) | Select type | +| China Administrative Region | Select Type | +| Check | Select Type | +| Radio | Select Type | +| Checkbox | Select Type | +| Link to | Relationship Type | +| One-to-One (has one) | Relationship Type | +| One-to-One (belongs to) | Relationship Type | +| One-to-many | Relationship Type | +| Many-to-one | relationship type | +| Many-to-many | relationship type | +| Formula | advanced type | +| AutoCoding | Advanced Types | +| JSON | Advanced Types | +| Markdown | Multimedia | +| Rich Text | Multimedia | +| Attachments | Multimedia | +| Date | Date & Time | +| Time | Date & Time | +| ID | System Information | +| Created by | System Information | +| Date Created | System Information | +| Last Modified By | System Information | +| Last Modified Date | System Information | +| Formula | Advanced Type | diff --git a/docs/en-US/manual/core-concepts/containers.md b/docs/en-US/manual/core-concepts/containers.md new file mode 100755 index 000000000..e89102fb8 --- /dev/null +++ b/docs/en-US/manual/core-concepts/containers.md @@ -0,0 +1,23 @@ +# Containers + +Pages, popups, and drawers are considered as containers of blocks in NocoBase. The container is like a canvas in which various blocks can be placed. + +## Pages + +![container-page.jpg](./containers/container-page.jpg) + +## Popups + +![container-dialog.jpg](./containers/container-dialog.jpg) + +## Drawers + +![container-drawer.jpg](./containers/container-drawer.jpg) + +## Tabs are supported inside containers + +Multiple tabs can be added within popups, drawers, and pages. Add different blocks to each tab to display different content and actions. For example, in a customer information popup, add 3 tabs to display customer's personal information, order history, customer reviews. + +![7.tabs.gif](./containers/7.tabs.gif) + +![container-tab-2.jpg](./containers/container-tab-2.jpg) diff --git a/docs/en-US/user-manual/advanced-guide/tabs/7.tabs.gif b/docs/en-US/manual/core-concepts/containers/7.tabs.gif similarity index 100% rename from docs/en-US/user-manual/advanced-guide/tabs/7.tabs.gif rename to docs/en-US/manual/core-concepts/containers/7.tabs.gif diff --git a/docs/en-US/manual/core-concepts/containers/container-dialog.jpg b/docs/en-US/manual/core-concepts/containers/container-dialog.jpg new file mode 100755 index 000000000..b5984954d Binary files /dev/null and b/docs/en-US/manual/core-concepts/containers/container-dialog.jpg differ diff --git a/docs/en-US/manual/core-concepts/containers/container-drawer.jpg b/docs/en-US/manual/core-concepts/containers/container-drawer.jpg new file mode 100755 index 000000000..b550ea29e Binary files /dev/null and b/docs/en-US/manual/core-concepts/containers/container-drawer.jpg differ diff --git a/docs/en-US/manual/core-concepts/containers/container-page.jpg b/docs/en-US/manual/core-concepts/containers/container-page.jpg new file mode 100755 index 000000000..c765777ea Binary files /dev/null and b/docs/en-US/manual/core-concepts/containers/container-page.jpg differ diff --git a/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg b/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg new file mode 100755 index 000000000..07b1a982b Binary files /dev/null and b/docs/en-US/manual/core-concepts/containers/container-tab-2.jpg differ diff --git a/docs/en-US/manual/core-concepts/menus.md b/docs/en-US/manual/core-concepts/menus.md new file mode 100755 index 000000000..b60130b1c --- /dev/null +++ b/docs/en-US/manual/core-concepts/menus.md @@ -0,0 +1,54 @@ +# Menus + +The default menu location for NocoBase is at the top and on the left. The top is the first level menu and the left side is the menu for the second level and lower levels. + +Three types of menu items are supported. + +- Menu groups +- Pages +- Links + +Once you enter the UI Editor mode, you can add and edit menus, as well as sort menu items. + +NocoBase currently supports three types of menu items. + +- Page: jumps to the content page the menu item is associated. +- Group: grouping menu items and placing similar menus in a uniform location. +- Link: jumps to a specified URL. + +Take the warehouse system as an example, if you have storage management in your business, storage management contains in and out logs, inventory queries, jump to the ERP application storage and other functions. Then you can set the menu like this. + +``` +- Storage space management (grouping) + - Inventory query (page) + - Inbound and outbound log (page) + - Jump ERP application storage space (link) + +``` + +## Default position + +In NocoBase's built-in page templates, the menu appears at the top and on the left. + +![menu-position.jpg](./menus/menu-position.jpg) + +## Add Menu Item + +![5.menu-add.jpg](./menus/5.menu-add.jpg) + +Click Add menu item to select the type to add. Support infinite level submenu. + +## Configure and Sort + +Move the cursor over the menu item and the Sort and Configure buttons will appear in the upper right corner. Press and hold the Sort button to drag and drop the sorting. + +Configurations that are operable on menu items: + +- Edit +- Move to +- Insert before +- Insert after +- Insert Inner +- Delete + +![menu-move.gif](./menus/menu-move.gif) diff --git a/docs/en-US/user-manual/advanced-guide/menus/5.menu-add.jpg b/docs/en-US/manual/core-concepts/menus/5.menu-add.jpg similarity index 100% rename from docs/en-US/user-manual/advanced-guide/menus/5.menu-add.jpg rename to docs/en-US/manual/core-concepts/menus/5.menu-add.jpg diff --git a/docs/en-US/manual/core-concepts/menus/menu-move.gif b/docs/en-US/manual/core-concepts/menus/menu-move.gif new file mode 100755 index 000000000..1e278d868 Binary files /dev/null and b/docs/en-US/manual/core-concepts/menus/menu-move.gif differ diff --git a/docs/en-US/manual/core-concepts/menus/menu-position.jpg b/docs/en-US/manual/core-concepts/menus/menu-position.jpg new file mode 100755 index 000000000..8e21a0a18 Binary files /dev/null and b/docs/en-US/manual/core-concepts/menus/menu-position.jpg differ diff --git a/docs/en-US/user-manual/advanced-guide/functional-zoning.md b/docs/en-US/manual/quick-start/functional-zoning.md old mode 100644 new mode 100755 similarity index 93% rename from docs/en-US/user-manual/advanced-guide/functional-zoning.md rename to docs/en-US/manual/quick-start/functional-zoning.md index 13b7c75b1..79c4f4044 --- a/docs/en-US/user-manual/advanced-guide/functional-zoning.md +++ b/docs/en-US/manual/quick-start/functional-zoning.md @@ -6,4 +6,4 @@ NocoBase has a built-in layout template by default, and the interface of this la 2. Menu area. At the top is the first level menu, and on the left side are the menus for the second level and lower tiers. Each menu item can be configured as a menu group, page, or link. 3. Block container. This is the block container for the page, in which various blocks can be placed. -![3.zone.jpg](./functional-zoning/3.zone.jpg) +![3.zone.jpg](./functional-zoning/3.zone.jpg) \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/functional-zoning/3.zone.jpg b/docs/en-US/manual/quick-start/functional-zoning/3.zone.jpg similarity index 100% rename from docs/en-US/user-manual/advanced-guide/functional-zoning/3.zone.jpg rename to docs/en-US/manual/quick-start/functional-zoning/3.zone.jpg diff --git a/docs/en-US/manual/quick-start/plugins.md b/docs/en-US/manual/quick-start/plugins.md new file mode 100755 index 000000000..6c53a6f9d --- /dev/null +++ b/docs/en-US/manual/quick-start/plugins.md @@ -0,0 +1,13 @@ +# Plugins + +NocoBase is designed as a plugin architecture, and most of the functionality is powered by plugins, except for a lightweight kernel. + +Theoretically, even built-in features can be replaced with new plugins as well. + +## Plugin Manager + +NocoBase provides a visual plugin manager to enable and disable plugins, or to configure them. + +The plugin manager is still in its early stages and in the future hopefully we will find a wide variety of plugins here to meet the needs. + +![plugin-manager.jpg](./plugins/plugin-manager.jpg) diff --git a/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg b/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg new file mode 100755 index 000000000..ff17fe5e1 Binary files /dev/null and b/docs/en-US/manual/quick-start/plugins/plugin-manager.jpg differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started.md b/docs/en-US/manual/quick-start/the-first-app.md old mode 100644 new mode 100755 similarity index 59% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started.md rename to docs/en-US/manual/quick-start/the-first-app.md index 621873fce..6226bf123 --- a/docs/en-US/user-manual/introduction/5-minutes-to-get-started.md +++ b/docs/en-US/manual/quick-start/the-first-app.md @@ -1,6 +1,6 @@ -# 5 minutes to get started +# The First APP -Let's take 5 minutes to build an order management system using NocoBase. +Let's build an order management system using NocoBase. ## 1. Create data collections and fields @@ -8,9 +8,10 @@ In this order management system, we need to have the information of `Customers`, - Customers - Name - - Birthday + - Address - Gender - Phone + - *Orders* (All orders purchased, data from `Orders` , each customer data contains multiple order data) - Products - Product name - Description @@ -29,30 +30,40 @@ In this order management system, we need to have the information of `Customers`, Where the fields in italics are relational fields, associated to other data tables. -Next, click the "Collections & Fields" button to enter the Configuration screen and create the first Collection `Customers`. +Next, click the "Collections & Fields" button to enter the Configuration screen and create the first Collection `Customers`. -![1.customers.gif](./5-minutes-to-get-started/1.customers.gif) +![create-collection.gif](./the-first-app/create-collection.gif) Then click on "Configure fields" to add a name field for `Customers`, which is a Single line text type. -![2.field.gif](./5-minutes-to-get-started/2.field.gif) +![create-field.gif](./the-first-app/create-field.gif) -In the same way, add Birthday, Gender, and Phone for `Customers`, which are the Datetime type, Radio group type, and Phone type respectively. +In the same way, add Address, Gender, and Phone for `Customers`, which are the Text, Radio group type, and Phone type respectively. -![1.fields.jpg](./5-minutes-to-get-started/1.fields.jpg) +![fields-list.jpg](./the-first-app/fields-list.jpg) In the same way, create Collections `Products`, `Orders`, `Order List` and their fields. -![1.collections.jpg](./5-minutes-to-get-started/1.collections.jpg) +![collection-list.jpg](./the-first-app/collection-list.jpg) - -In this case, for the relationship fields, we have to select the correct type so that we can create the association between the data collections. Let's take `Orders` as an example, create the Customer field and select the **Many to One** relationship to associate to `Customers`. +In this case, for relational fields, if you are not familiar with the concepts of one-to-many, many-to-many, etc., you can directly use the Link to type to create associations between collections. If you are familiar with these concepts, please use the correct types of One to many, Many to one, etc. to establish the association between collections. For example, in this example, we associate `Orders` with `Order list`Order list with the relationship One to many. -![1.relation.jpg](./5-minutes-to-get-started/1.relation.jpg) +![collection-list.jpg](./the-first-app/order-list-relation.jpg) -After creating a relationship field, we can see the automatically generated reverse association field in the Collection being associated. For example, we can see the automatically generated Orders field in `Customers`, so we can call the data of `Orders` in the block of `Customers`. -![1.auto.relation.jpg](./5-minutes-to-get-started/1.auto.relation.jpg) +In the graphical interface, you can visualize the relationship between the various collections. (Note: Graph-collection plugin is not yet open source) + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 Once the data collections and fields are created, we start making the interface. @@ -62,11 +73,11 @@ We need three pages for customers, orders, and products to display and manage ou Click the UI Editor button to enter the interface configuration mode. In this mode, we can add menu items, add pages, and arrange blocks within the pages. -![1.editor.gif](./5-minutes-to-get-started/1.editor.gif) +![1.editor.gif](./the-first-app/1.editor.gif) Click Add menu item, add menu groups "Customers" and "Orders & Products", then add submenu pages "All Orders" and "Products". -![1.menu.gif](./5-minutes-to-get-started/1.menu.gif) +![1.menu.gif](./the-first-app/1.menu.gif) After adding menus and pages, we can add and configure blocks within the pages. @@ -76,16 +87,16 @@ NocoBase currently supports table, kanban, calendar, form, items, and other type We add a table block to the "All Orders" page, select Collection `Orders` as the data source, and configure the columns to be displayed for this table block. -![1.block.gif](./5-minutes-to-get-started/1.block.gif) +![1.block.gif](./the-first-app/1.block.gif) Configure actions for this table block, including filter, add, delete, view, and edit. -![1.action.gif](./5-minutes-to-get-started/1.action.gif) +![1.action.gif](./the-first-app/1.action.gif) Configure form and item blocks for add, edit, view actions. -![1.action-block.gif](./5-minutes-to-get-started/1.action-block.gif) +![1.action-block.gif](./the-first-app/1.action-block.gif) Then, lay out the form blocks on the Products and Customers pages with the same method. When you are done, exit the UI Editor mode and enter the usage mode, and a simple order management system is completed. -![1.finished.gif](./5-minutes-to-get-started/1.finished.gif) +![demo-finished.jpg](./the-first-app/demo-finished.jpg) diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.action-block.gif b/docs/en-US/manual/quick-start/the-first-app/1.action-block.gif similarity index 100% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.action-block.gif rename to docs/en-US/manual/quick-start/the-first-app/1.action-block.gif diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.action.gif b/docs/en-US/manual/quick-start/the-first-app/1.action.gif similarity index 100% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.action.gif rename to docs/en-US/manual/quick-start/the-first-app/1.action.gif diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.block.gif b/docs/en-US/manual/quick-start/the-first-app/1.block.gif similarity index 100% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.block.gif rename to docs/en-US/manual/quick-start/the-first-app/1.block.gif diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.editor.gif b/docs/en-US/manual/quick-start/the-first-app/1.editor.gif similarity index 100% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.editor.gif rename to docs/en-US/manual/quick-start/the-first-app/1.editor.gif diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.menu.gif b/docs/en-US/manual/quick-start/the-first-app/1.menu.gif similarity index 100% rename from docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.menu.gif rename to docs/en-US/manual/quick-start/the-first-app/1.menu.gif diff --git a/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg b/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg new file mode 100755 index 000000000..b291ed3b8 Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/collection-list.jpg differ diff --git a/docs/en-US/manual/quick-start/the-first-app/create-collection.gif b/docs/en-US/manual/quick-start/the-first-app/create-collection.gif new file mode 100755 index 000000000..d8eae060c Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/create-collection.gif differ diff --git a/docs/en-US/manual/quick-start/the-first-app/create-field.gif b/docs/en-US/manual/quick-start/the-first-app/create-field.gif new file mode 100755 index 000000000..ba3f95278 Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/create-field.gif differ diff --git a/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg b/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg new file mode 100755 index 000000000..b880c89de Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/demo-finished.jpg differ diff --git a/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg b/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg new file mode 100755 index 000000000..13a382880 Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/fields-list.jpg differ diff --git a/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg b/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg new file mode 100644 index 000000000..64c02fdd2 Binary files /dev/null and b/docs/en-US/manual/quick-start/the-first-app/order-list-relation.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode.md b/docs/en-US/manual/quick-start/ui-editor-mode.md new file mode 100755 index 000000000..8ca8aeb36 --- /dev/null +++ b/docs/en-US/manual/quick-start/ui-editor-mode.md @@ -0,0 +1,51 @@ +# UI Editor Mode + +NocoBase uses a WYSIWYG approach to configure the interface. Click the `UI Editor` button in the upper right corner to switch between configuration mode and usage mode. Once you enter the configuration mode, many orange configuration portals will appear everywhere on the interface. + +![ui-editor.gif](./ui-editor-mode/ui-editor.gif) + +Usually, the configuration entry appears in the upper right corner of the element. + +## Menu item configuration + +Move the mouse over the menu item, in the upper right corner you can see the drag and drop sort button, configuration item button, you can edit, move, insert, delete. + +![menu-config.jpg](./ui-editor-mode/menu-config.jpg) + +## Block configuration + +Move the mouse over a block and you will see drag-and-drop sort button, add block button and configure item button in the upper right corner. + +![block-config.jpg](./ui-editor-mode/block-config.jpg) + +Different blocks also have their own unique configuration items. For example, in the table block, you can see the configuration items of the table header in the upper right corner by hovering over the table header; you can also see the configuration items of the table columns in the far right side of the table header. + +![block-config-2.jpg](./ui-editor-mode/block-config-2.jpg) + +![block-config-3.jpg](./ui-editor-mode/block-config-3.jpg) + +## Action configuration + +In the block you can see the configuration entries for the actions, which appear in different places in different blocks. + +For example, in the table block, the actions for the table data can be seen in the upper right: + +![action-config-1.jpg](./ui-editor-mode/action-config-1.jpg) + +In the table header of the actions column you can see the action for the single row of data: ! + +![action-config-2.jpg](./ui-editor-mode/action-config-2.jpg) + +You can see the actions for the details in the upper right corner of the details block: + +![action-config-3.jpg](./ui-editor-mode/action-config-3.jpg) + +The actions for the form can be seen at the bottom of the form block: + +![action-config-4.jpg](./ui-editor-mode/action-config-4.jpg) + +## Tab configuration + +Multiple tabs can be added in a popup or drawer to host different blocks. + +![tab-config.jpg](./ui-editor-mode/tab-config.jpg) \ No newline at end of file diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg new file mode 100755 index 000000000..c31a20f7a Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-1.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg new file mode 100755 index 000000000..22b14119f Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-2.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg new file mode 100755 index 000000000..59e5a2527 Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-3.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg new file mode 100755 index 000000000..f799395bf Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/action-config-4.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg new file mode 100755 index 000000000..0e041e35e Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-2.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg new file mode 100755 index 000000000..0b1244cc0 Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/block-config-3.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg new file mode 100755 index 000000000..e1a94876c Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/block-config.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg new file mode 100755 index 000000000..8765202f0 Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/menu-config.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg b/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg new file mode 100755 index 000000000..ac46ee284 Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/tab-config.jpg differ diff --git a/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif b/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif new file mode 100755 index 000000000..0ea3bf781 Binary files /dev/null and b/docs/en-US/manual/quick-start/ui-editor-mode/ui-editor.gif differ diff --git a/docs/en-US/release-notes.md b/docs/en-US/release-notes.md deleted file mode 100644 index 8cbb88844..000000000 --- a/docs/en-US/release-notes.md +++ /dev/null @@ -1,380 +0,0 @@ -# Release Notes - -## 2022/08/15 ~ v0.7.4-alpha.7 - -### Details - -- fix(collection-manager): update collection without fields - -## 2022/08/12 ~ v0.7.4-alpha.4 - -### New features - -- Field default value - -### Details - -- fix(database): error getting db version number -- fix: record provider required for read pretty -- fix: sync table sort to export (#723) -- feat: full version of the NocoBase dockerfile (#719) -- feat: add examples -- chore: update node ci -- fix(plugin-workflow): fix extend collection (#708) -- fix: DB_TABLE_PREFIX doesn't get applied (#710) -- feat: default value (#679) -- fix: required field delete submit error (#688) (#694) - -## 2022/07/28 ~ v0.7.4-alpha.1 - -### Details - -- fix: append roles to current user (#695) -- fix(client): required for the sub-table field -- fix: date format (#686) -- test(plugin-workflow): skip prompt tests (#692) -- fix: accuracy of percent (#685) -- fix: the database only supports MySQL 8.0.17 and above, SQLite 3.x and PostgreSQL 10+ -- fix(plugin-workflow): adjust await sleep time for test cases (#691) -- feat(plugin-workflow): add assignees config for prompt instruction (#690) -- fix: role export button display (#616) (#666) -- fix: uid validate (#681) -- feat(client): tab icon -- fix(plugin-error-handler): no error message -- fix(client): fieldNames of RecordPicker -- fix: hide password -- refactor: replace react-drag-listview with @dnd-kit/sortable (#660) -- refactor(plugin-users): improve extendibility of middlewares (#677) -- feat: o2m delete not refresh (#646) -- feat: kanban add description (#659) -- fix: field loss enum (#667) -- feat: add ui editor hot key Ctrl+Shift+U (#675) -- fix: calendar change field error (#626) (#671) -- chore: fix eslint not work (#670) -- feat: number precision (#661) -- feat: nginx config (#664) -- feat: form item designer form switch issue (#656) -- fix: wrong operator - -## 2022/07/20 ~ v0.7.3-alpha.1 - -### New features - -- Form validation -- Actions: Print, Refresh - -### Details - -- fix(client): hide modal header -- feat: customizable jwt expiration date -- feat: print action (#652) -- feat: restore action-hooks (#655) -- feat: collections & fields pagination issue (#653) -- fix(core): change proxied agent methods to native (#654) -- feat: remove table field details actions (#638) -- fix: link to default value (#641) -- fix(client): build error -- fix: localStorage is not defined -- feat: support for displaying relational table fields in details or form blocks (#635) -- fix: record picker cannot select from different pages (#623) -- feat(client): plugin toolbar icons and translations -- fix: dragging an element to the left, right, or bottom would cause the element to disappear (#620) -- feat: table action add reload button (#630) -- feat: improve language settings (#627) -- feat: field assignment for custom actions supports string variables (#597) -- fix(client): blocks are deleted when they are dragged below the current block -- fix: skip recursive remove on grid component (#621) -- feat: fix time and collection pagination (#618) -- feat: recordblockinitializers fields pick (#558) -- fix: incorrectly :active background (#607) -- fix: obo table selector (#613) -- feat: form validator (#569) -- fix: table selector (#612) - -## 2022/07/07 ~ v0.7.2-alpha.2 - -- fix(g2plot): import all plots -- fix: field permissions cannot be saved (#605) -- fix(plugin-workflow): fix revision bug (#603) -- fix(plugin-workflow): fix select value (#600) -- fix(plugin-workflow): fix CollectionFieldSelect component (#598) -- feat(plugin-workflow): add association select in calculation (#584) -- feat: function for chart data request -- fix(cli): remove process.env.NODE_OPTIONS - -## 2022/07/05 ~ v0.7.2-alpha.1 - -### New features - -- Fields: Integer field -- Blocks: Display fields of relational collections in blocks -- Plugins: Filter conditions support variables - -### Breaking changes - -- New version does not create foreign key constraints by default, old version will delete all created foreign key constraints after upgrade -- If you installed NocoBase using yarn create before, you need to yarn create again and then execute `yarn nocobase upgrade --raw` - -### Details - -- fix: drop all foreign keys (#576) -- fix(plugin-workflow): fix collection trigger config (#575) -- fix: improve filter item styling -- fix(collection-manager): missing collection manager context -- feat: filter with variable (#574) -- feat(cli): check database version before installation (#572) -- fix(client): comment out useless code -- fix(cli): app start before sync and upgrade -- feat(client): integer field -- fix(database): index invalid (#564) -- fix: export association table data (#561) -- fix(client): maximum call stack size exceeded (#554) -- refactor(plugin-workflow): move client files into plugin (#556) -- fix(database): constraints default to false (#550) -- fix(client): cannot read properties of undefined (reading 'split') -- fix(workflow): merge workflow providers -- fix(workflow): load workflow after application initialization -- fix(plugin-workflow): fix select width (#552) -- feat: compatible with old kanban (#553) -- fix(client): consider explicitly re-exporting to resolve the ambiguity -- feat: display association fields (#512) -- Fix(plugin workflow) (#549) -- fix: update mysql port (#548) -- fix: export of relation blocks (#546) -- fix(plugin-workflow): clear options when change collection (#547) -- feat(plugin-workflow): add race mode (#542) -- fix(client): change toArr to _.castArray in select component (#543) - -## 2022/06/26 ~ v0.7.1-alpha.7 - -### New features - -- Fields: Formula、Relationships(o2o, o2m, m2o, m2m) -- Blocks: Charts(g2plot) -- Plugins: Audit logs, Export, Workflow(schedule trigger) - -### Breaking changes - -- The percentage field stores the original value. For example, the old version stored 1% as 1 and the new version stores 1% as 0.01 -- Remove sub-table field and replace it with one-to-many relationship -- If the NocoBase application was previously installed using yarn create, you need to yarn create again, and then execute yarn nocobase upgrade - -### Details - -- fix(cli): upgrade from docker -- chore(create-nocobase-app): fix some bugs (#538) -- feat: relationship fields are loaded on demand -- fix: destroy collection fields (#536) -- feat(plugin-workflow): add delay node type (#532) -- refactor: client application (#533) -- fix: missing transaction (#531) -- fix: add ellipsis property to record picker (#527) -- fix: remove pattern without form item (#528) -- fix(client): update only fields in the form -- fix(client): remove z-index -- fix(plugin-workflow): set current when update (#526) -- fix(client): non-empty judgment -- fix: order nulls last (#519) -- fix(client): close the pop-up after request -- fix: action loading, refresh context, form submit and validate (#523) -- fix: field pattern (#520) -- fix(plugin-workflow): fix searchable select min-width (#524) -- fix: template with fields only (#517) -- fix(plugin-workflow): fix update workflow current property (#521) -- feat: improve chart component -- refactor(plugin-workflow): abstract to classes (#515) -- feat: column sortable and form item pattern (#518) -- feat(client): display option value -- feat(client): hide drawer header -- fix(audit-logs): operator does not exist: character varying = integer -- fix(custom-request): support string/json templates (#514) -- fix(cli): missing await -- feat: add block title (#513) -- fix: remove collections & fields from db (#511) -- fix(cli): upgrade error in node v14 -- feat: improve migrations (#510) -- fix(client): improve datepicker component, date with time zone, gmt support -- fix: datepicker with timezone -- fix(client): consolidate usage of date/time as UTC in transfering (#509) -- fix: formula bug -- fix: default exportable fields (#506) -- fix(audit-logs): sort by createdAt -- fix(plugin-export): allow to configure in acl -- fix: sign in/sign up with enter key -- fix(client): percent precision -- feat: association field block (#493) -- feat: plugin export (#479) -- fix: create or delete collection error (#501) -- feat: update collections & fields (#500) -- fix: rollback when field creation fails (#498) -- fix(client): set `dropdownMatchSelectWidth` to false globally (#497) -- fix(client): no-key warning in user menu items (#496) -- Feat(plugin workflow): cron field for schedule trigger configuration (#495) -- feat: audit logs (#494) -- fix(client): language settings -- feat(client): improve locale -- refactor(plugin-workflow): add revision column to execution (#491) -- fix(plugin-multi-app-manager): fix pg cannot create database block tests -- refactor(database): hook proxy (#402) -- feat: chart blocks (#484) -- refactor(plugin workflow): support number in repeat config for schedule -- chore(debug): add debug config (#475) -- fix: has one bug -- feat: relationships (#473) -- fix(plugin-workflow): fix collection trigger transaction (#474) -- fix(plugin-workflow): temporary solution for collection trigger conditions -- fix: markdown component (#469) -- fix: formula field and percent field (#467) -- fix(plugin-workflow): fix update workflow action (#464) -- fix(acl): skip when field does not exist -- fix: update formula field and percent field (#461) -- fix(client): export useSignin and useSignup -- fix(ci): node_version = 14 -- fix(cli): yarn install --production error -- fix(client): build error -- feat: add formula field type (#457) -- fix: the details of the associated data in the subtable are not displayed -- fix(plugin-workflow): fix languages (#451) -- fix: afterSync hook not triggered (#450) - -## 2022/06/01 ~ v0.7.0-alpha.83 - -- fix: default value of time zone -- fix(database): add timezone support -- docs(various): Improve readability (#447) -- fix(client): datetime with timezone -- feat(plugin-file-manager): record the creator of the attachment -- feat: custom request (#439) -- feat(plugin workflow): schedule trigger (#438) -- feat(database): db migrator (#432) -- fix(client): select component cannot be opened in sub-table block (#431) -- fix: error message "error:0308010C:digital envelope routines::unsupported -- docs(github): change to markdown format (#430) -- fix(cli): typo (#429) - -### New Features - -- Core: db migrator - -## 2022/05/26 ~ v0.7.0-alpha.82 - -- feat(client,sdk): improve api client - -### Breaking changes - -There are major changes to the `APIClient` API, see details [JavaScript SDK](./development/http-api/javascript-sdk.md) - -## 2022/05/25 ~ v0.7.0-alpha.81 - -- feat: add create-plugin command (#423) -- fix: "typescript": "4.5.5" -- docs: update documentation -- fix(client): filter menu item schema by permissions -- fix(database): cannot read properties of null (reading 'substring') -- fix(client): add description -- fix(client): clone schema before insert -- feat(client): add a description to the junction collection field -- fix(devtools): unexpected token '.' - -## 2022/05/24 ~ v0.7.0-alpha.78 - -- fix(client): add RemoteDocumentTitleProvider -- fix(client): incomplete calendar events -- fix(plugin-users): add translations (#416) - -## 2022/05/23 ~ v0.7.0-alpha.59 - -- feat(docs): add alert message -- fix(create-nocobase-app): storage path error -- fix(client): improve translation -- fix(cli): nocobase test command --db-clean option is invalid -- refactor(plugin-workflow): change column type of executed from boolean to integer (#411) - -## 2022/05/22 ~ v0.7.0-alpha.58 - -- fix: 204 no content response (#378) -- feat: destroy association field after target collection destroy (#376) -- fix(type): use sequelize native Transactionable instead of TransactionAble (#410) -- fix(plugin-workflow): remove previous listeners when collection changed in config (#409) -- fix(plugin-acl): missing pagination parameters (#394) -- feat(client): add custom action (#396) -- refactor(plugin-workflow): multiple instances and event management (fix #384) (#408) -- feat(cli): --db-sync options -- fix(client): pagination dropdown menu is blocked (#398) -- feat: display version number (#386) -- fix: missing isTruly/isFalsy filter operators (#390) -- fix(client): reset page number to first page (#399) - -## 2022/05/19 ~ v0.7.0-alpha.57 - -### New features -- Packaging tool `@nocobase/build` -- CLI `@nocobase/cli` -- devtools `@nocobase/devtools` -- JavaScript SDK `@nocobase/sdk` -- Documents(v0.7) - -### Bug fixes & improvements -- `@nocobase/preset-nocobase` -- create scaffolding `create-nocobase-app` -- Documents theme `dumi-theme-nocobase` - -### Breaking changes - -📢 Previously created projects need to be recreated. - -## 2022/05/14 ~ v0.7.0-alpha.34 - -- feat: add plugins:getPinned action api -- fix(plugin workflow): cannot get job result properties (#382) -- feat: exist on server start throw error (#374) -- chore: application options (#375) -- fix: not in operator with null value record (#377) - -## 2022/05/13 ~ v0.7.0-alpha.33 - -- fix: link-to field data scope error (#1337) -- feat(plugin workflow): revisions (#379) -- fix(database): fix option-parser include list index (#371) -- fix(plugin-workflow): fix duplicated description in fields values (#368) -- fix(database): fix type and transaction in repository (#366) -- fix(plugin workflow): fix transaction of execution (#364) - -## 2022/05/05 ~ v0.7.0-alpha.30 - -- fix(client): upgrade formily packages -- fix(client): setFormValueChanged must be defined - -## 2022/05/01 ~ v0.7.0-alpha.27 - -- fix: use wrapper when greater than one column -- fix: props for CreateFormBlockInitializers -- fix: add schema initializer icon -- fix: plugin workflow (#349) -- fix: db:sync not working (#348) -- fix(plugin-workflow): fix trigger bind logic to avoid duplication (#347) -- fix(plugin workflow) (#346) -- fix: action open mode -- fix: menu url style (#344) -- feat: action loading -- fix: compile the label field -- fix: invalid drag and drop sort - -## 2022/04/25 ~ v0.7.0-alpha.16 - -- fix: cannot find module mkdirp (#330) -- fix(plugin workflow): UX issues (#329) -- fix(plugin-file-manager): test failed -- fix(app-server): dist options - -## 2022/04/25 ~ v0.7.0-alpha.0 - -- Alpha Version - -## 2021/10/07 ~ v0.5.0 - -- The second preview version - -## 2021/04/07 ~ v0.4.0 - -- The first preview version diff --git a/docs/en-US/roadmap.md b/docs/en-US/roadmap.md deleted file mode 100644 index 6026d355a..000000000 --- a/docs/en-US/roadmap.md +++ /dev/null @@ -1,29 +0,0 @@ -# Roadmap - -## Iterating - -- `core` Roles & Permissions -- `core` Upgrades & Migrations -- `plugin` Workflow -- `doc` Development Doc -- `plugin` Data visualization(low code) - -## Developing - -- `core` Plugin manager -- `ui` Mobile responsive -- `plugin` Rollup field -- `core` field options (default value, etc...) -- `plugin` Form validator - -## Planning - -- `plugin` Database connection - -## Future - -- `plugin` Approval -- `plugin` Full-text search -- `plugin` Sharing pages -- `plugin` Data visualization(no code) -- `plugin` Open API diff --git a/docs/en-US/user-manual/advanced-guide/actions.md b/docs/en-US/user-manual/advanced-guide/actions.md deleted file mode 100644 index f48d91130..000000000 --- a/docs/en-US/user-manual/advanced-guide/actions.md +++ /dev/null @@ -1,3 +0,0 @@ -# Actions - -TO DO \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/blocks.md b/docs/en-US/user-manual/advanced-guide/blocks.md deleted file mode 100644 index b3aba1d55..000000000 --- a/docs/en-US/user-manual/advanced-guide/blocks.md +++ /dev/null @@ -1,63 +0,0 @@ -# Blocks - -Blocks are views that are used to display and manipulate data. Blocks can be placed in pages and popups. A block consists of three parts. - -1. content area: the body of the block -2. action area: various action buttons can be placed to manipulate the block data -3. configuration area: buttons for operating the block configuration - -![6.block.jpg](./blocks/6.block.jpg) - -## Add block - -Enter the UI Editor mode and click the Add block button on the page and in the pop-up window to add the block. The options are divided into 4 steps. - -1. Select block type: Currently available block types include Table, Form, Details, Calendar, Kanban, Markdown -2. Select Collection: All collections will be listed here -3. Choose the creation method: create a blank block, or duplicate a block template , or reference a block template -4. Select Template: If you selected Create from Template in step 3, select the template in step 4 - -![6.block-add.jpg](./blocks/6.block-add.jpg) - -## Configure Blocks - -The configuration of blocks consists of three elements. - -- Configure block content -- Configure block actions -- Configure block properties - -### Configure block content - -Take the table block as an example, the content of the block is the columns to be displayed in the table. Click Configure columns to configure the columns to be displayed. - -![6.block-content.gif](./blocks/6.block-content.gif) - -### Configure block actions - -Take table block as an example, there are filter, add, delete, view, edit, customize and other actions available. Click the Configure actions button to configure the actions. Each of the action buttons can be configured for their own properties. - -![6.block-content.gif](./blocks/6.block-content%201.gif) - -### Configure block properties - -Move the cursor to the upper right corner of the block and you will see the block configuration button. Using the table block as an example, the following properties can be configured. - -- Drag & drop sorting -- Set the data scope -- Set default sorting rules -- Records per page - -![6.collection-setting.gif](./blocks/6.collection-setting.gif) - -## Block Types - -Currently NocoBase supports the following types of blocks. - -- Table -- Form -- Details -- Kanban -- Calendar -- Related Data -- Markdown \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/collections.md b/docs/en-US/user-manual/advanced-guide/collections.md deleted file mode 100644 index 1b20a6835..000000000 --- a/docs/en-US/user-manual/advanced-guide/collections.md +++ /dev/null @@ -1,42 +0,0 @@ -# Collections - -Before developing a system, we usually have to abstract the business and build a data model. In NocoBase, the concept of a data collection is similar to that of a table in a relational database. - -Click the "Collections & Fields" button to enter the configuration interface. Here, you can add, edit and delete data collections. - -![4.collections.gif](./collections/4.collections.gif) - -## Field Types - -- Basic - - Single line text - - Long text - - Phone - - Email - - Number - - Percent - - Password - - Icon -- Choices - - Checkbox - - Single select - - Multiple select - - Radio group - - Checkbox group - - China region -- Media - - Attachment - - Markdown - - Rich Text -- Date & Time - - Date - - Time -- Relationship Type - - Link to - - Sub-table -- System Info - - ID - - Created at - - Last updated at - - Created by - - Last updated by \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/collections/4.collections.gif b/docs/en-US/user-manual/advanced-guide/collections/4.collections.gif deleted file mode 100755 index dd0f01a4d..000000000 Binary files a/docs/en-US/user-manual/advanced-guide/collections/4.collections.gif and /dev/null differ diff --git a/docs/en-US/user-manual/advanced-guide/file-storages.md b/docs/en-US/user-manual/advanced-guide/file-storages.md deleted file mode 100644 index 56970cc4d..000000000 --- a/docs/en-US/user-manual/advanced-guide/file-storages.md +++ /dev/null @@ -1,11 +0,0 @@ -# File Storages - -NocoBase file storage currently supports the following three methods - -- Local storage -- Aliyun OSS -- Amazon S3 - -Click File storage to enter the configuration interface and add the appropriate information. - -![8.storage.gif](./file-storages/8.storage.gif) \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/file-storages/8.storage.gif b/docs/en-US/user-manual/advanced-guide/file-storages/8.storage.gif deleted file mode 100755 index c9edfa8f9..000000000 Binary files a/docs/en-US/user-manual/advanced-guide/file-storages/8.storage.gif and /dev/null differ diff --git a/docs/en-US/user-manual/advanced-guide/menus.md b/docs/en-US/user-manual/advanced-guide/menus.md deleted file mode 100644 index 08ae51210..000000000 --- a/docs/en-US/user-manual/advanced-guide/menus.md +++ /dev/null @@ -1,32 +0,0 @@ -# Menus - -The default menu location for NocoBase is at the top and on the left. The top is the first level menu and the left side is the menu for the second level and lower levels. - -Three types of menu items are supported. - -- Menu groups -- Pages -- Links - -Once you enter the UI Editor mode, you can add and edit menus, as well as sort menu items. - -## Add Menu Item - -![5.menu-add.jpg](./menus/5.menu-add.jpg) - -Click Add menu item to select the type to add. Support infinite level submenu. - -## Configure and Sort - -Move the cursor over the menu item and the Sort and Configure buttons will appear in the upper right corner. Press and hold the Sort button to drag and drop the sorting. - -Configurations that are operable on menu items: - -- Edit -- Move to -- Insert before -- Insert after -- Insert Inner -- Delete - -![5.menu-edit.jpg](./menus/5.menu-edit.jpg) \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/menus/5.menu-edit.jpg b/docs/en-US/user-manual/advanced-guide/menus/5.menu-edit.jpg deleted file mode 100755 index 0252465f7..000000000 Binary files a/docs/en-US/user-manual/advanced-guide/menus/5.menu-edit.jpg and /dev/null differ diff --git a/docs/en-US/user-manual/advanced-guide/plugins.md b/docs/en-US/user-manual/advanced-guide/plugins.md deleted file mode 100644 index 779d515c6..000000000 --- a/docs/en-US/user-manual/advanced-guide/plugins.md +++ /dev/null @@ -1 +0,0 @@ -# Plugins diff --git a/docs/en-US/user-manual/advanced-guide/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md b/docs/en-US/user-manual/advanced-guide/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md deleted file mode 100755 index d9afb34e2..000000000 --- a/docs/en-US/user-manual/advanced-guide/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md +++ /dev/null @@ -1,3 +0,0 @@ -# Workflow - -TO DO \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/roles-permissions.md b/docs/en-US/user-manual/advanced-guide/roles-permissions.md deleted file mode 100644 index 1a9001f27..000000000 --- a/docs/en-US/user-manual/advanced-guide/roles-permissions.md +++ /dev/null @@ -1,3 +0,0 @@ -# Roles & Permissions - -TO DO \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/system-settings.md b/docs/en-US/user-manual/advanced-guide/system-settings.md deleted file mode 100644 index c9fc3da68..000000000 --- a/docs/en-US/user-manual/advanced-guide/system-settings.md +++ /dev/null @@ -1,10 +0,0 @@ -# System Settings - -Click System settings, the properties that can be configured include: - -- System title -- Logo -- Language -- Allow sign up - -![9.system.gif](./system-settings/9.system.gif) \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/system-settings/9.system.gif b/docs/en-US/user-manual/advanced-guide/system-settings/9.system.gif deleted file mode 100755 index e1d26b471..000000000 Binary files a/docs/en-US/user-manual/advanced-guide/system-settings/9.system.gif and /dev/null differ diff --git a/docs/en-US/user-manual/advanced-guide/tabs.md b/docs/en-US/user-manual/advanced-guide/tabs.md deleted file mode 100644 index 5ede09e00..000000000 --- a/docs/en-US/user-manual/advanced-guide/tabs.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tabs - -In a single record page or popup, you can add multiple tabs and add different blocks to each tab to display different content and actions. For example, in a customer information popup, add 3 tabs to display customer's personal information, order history, customer reviews. - -![7.tabs.gif](./tabs/7.tabs.gif) - -Or, in an order record to be shipped, place a form block in the first tab for quick shipping, a block of associated data in the second tab to display the order items for the current order, and an order details block in the third tab. - -![7.tabs-2.gif](./tabs/7.tabs-2.gif) \ No newline at end of file diff --git a/docs/en-US/user-manual/advanced-guide/tabs/7.tabs-2.gif b/docs/en-US/user-manual/advanced-guide/tabs/7.tabs-2.gif deleted file mode 100755 index 3699ad9e7..000000000 Binary files a/docs/en-US/user-manual/advanced-guide/tabs/7.tabs-2.gif and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.auto.relation.jpg b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.auto.relation.jpg deleted file mode 100644 index c445d50a7..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.auto.relation.jpg and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.collections.jpg b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.collections.jpg deleted file mode 100755 index e916ec514..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.collections.jpg and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.customers.gif b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.customers.gif deleted file mode 100755 index 9cdcb735d..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.customers.gif and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.fields.jpg b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.fields.jpg deleted file mode 100755 index 529fc6b5c..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.fields.jpg and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.finished.gif b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.finished.gif deleted file mode 100755 index be301d4ea..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.finished.gif and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.relation.jpg b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.relation.jpg deleted file mode 100755 index 4c9ee076e..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/1.relation.jpg and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/2.field.gif b/docs/en-US/user-manual/introduction/5-minutes-to-get-started/2.field.gif deleted file mode 100755 index 65ba382c8..000000000 Binary files a/docs/en-US/user-manual/introduction/5-minutes-to-get-started/2.field.gif and /dev/null differ diff --git a/docs/en-US/user-manual/introduction/important-features.md b/docs/en-US/user-manual/introduction/important-features.md deleted file mode 100644 index 9eb650f64..000000000 --- a/docs/en-US/user-manual/introduction/important-features.md +++ /dev/null @@ -1,19 +0,0 @@ -# Important Features - -## 1. Separate "data structure" and "user interface" - -Most form-, table-, or process-driven codeless products create data structures directly in the user interface, such as Airtable, where adding a new column to a table is adding a new field. This has the advantage of simplicity of use, but the disadvantage of limited functionality and flexibility to meet the needs of more complex scenarios. - -NocoBase adopts the design idea of separating the data structure from the user interface, allowing you to create any number of blocks (data views) for the data collections, with different type, styles, content, and actions in each block. This takes into account the simplicity of codeless operation, but also the flexibility of native development. - -![2.collection-block.png](./important-features/2.collection-block.png) - -## 2. Separate "system configuration" and "system usage" - -NocoBase is used to develop relatively complex business systems. In these scenarios, we want the system developers and the system users to be different roles. The user sees a mature, well-designed system, like a product developed natively, rather than a rigid, drag-and-drop free system that can be modified at any time; while the developer can quickly develop the system using a WYSIWYG approach. - -![2.user-root.gif](./important-features/2.user-root.gif) - -## 3. Everything is plugins - -NocoBase adopts plugin architecture, all new features can be implemented by developing and installing plugins. In the future, we will build a plug-in marketplace where extending functionality is as easy as installing an APP on your phone. diff --git a/docs/en-US/user-manual/introduction/why-nocobase.md b/docs/en-US/user-manual/introduction/why-nocobase.md deleted file mode 100644 index 8452e5027..000000000 --- a/docs/en-US/user-manual/introduction/why-nocobase.md +++ /dev/null @@ -1,9 +0,0 @@ -# Why NocoBase - -NocoBase is designed for you if you have the following needs. - -- Develop an internal management system -- Meet most of your business needs with codeless development -- Extremely easy to extend to meet your individual needs -- Private deployment with full control of code and data -- Free to use or you can pay for more technical support \ No newline at end of file diff --git a/docs/en-US/contributing.md b/docs/en-US/welcome/community/contributing.md similarity index 98% rename from docs/en-US/contributing.md rename to docs/en-US/welcome/community/contributing.md index 1e6536df2..547c2cdab 100644 --- a/docs/en-US/contributing.md +++ b/docs/en-US/welcome/community/contributing.md @@ -3,6 +3,7 @@ - Fork the source code to your own repository - Modify source code - Submit pull request +- Sign the CLA ## Download diff --git a/docs/en-US/faq.md b/docs/en-US/welcome/community/faq.md similarity index 85% rename from docs/en-US/faq.md rename to docs/en-US/welcome/community/faq.md index a39c1bc0d..4514b4c10 100644 --- a/docs/en-US/faq.md +++ b/docs/en-US/welcome/community/faq.md @@ -1,2 +1 @@ # FAQ - diff --git a/docs/en-US/thanks.md b/docs/en-US/welcome/community/thanks.md similarity index 100% rename from docs/en-US/thanks.md rename to docs/en-US/welcome/community/thanks.md diff --git a/docs/en-US/translations.md b/docs/en-US/welcome/community/translations.md similarity index 98% rename from docs/en-US/translations.md rename to docs/en-US/welcome/community/translations.md index 2e30d7d29..272b9391c 100644 --- a/docs/en-US/translations.md +++ b/docs/en-US/welcome/community/translations.md @@ -1,6 +1,6 @@ # Translations -The default language of NocoBase is English, currently English, Simplified Chinese are supported. You can help NocoBase to translate into your language. +The default language of NocoBase is English, currently English, Simplified Chinese, Japanese, Russian, Turkish are supported. You can help NocoBase to translate into your language. The NocoBase language files are located at the following locations. @@ -15,7 +15,7 @@ https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/locale Please copy en_US.ts, name it with the name of the language you want to add, and then translate the strings in it. Once the translation is done, please submit it to NocoBase via pull request and we will add it to the list of languages. Then you will see the new languages in the system configuration, where you can configure which languages you want to display for users to choose. -![enabled-languages](./images/enabled-languages.jpg) + The following table lists the Language Culture Name, Locale File Name, Display Name. @@ -156,4 +156,4 @@ The following table lists the Language Culture Name, Locale File Name, Display N | ur-PK | ur_PK.ts | Urdu - Pakistan | | Cy-uz-UZ | Cy_uz_UZ.ts | Uzbek (Cyrillic) - Uzbekistan | | Lt-uz-UZ | Lt_uz_UZ.ts | Uzbek (Latin) - Uzbekistan | -| vi-VN | vi_VN.ts | Vietnamese - Vietnam | \ No newline at end of file +| vi-VN | vi_VN.ts | Vietnamese - Vietnam | diff --git a/docs/en-US/images/enabled-languages.jpg b/docs/en-US/welcome/community/translations/enabled-languages.jpg similarity index 100% rename from docs/en-US/images/enabled-languages.jpg rename to docs/en-US/welcome/community/translations/enabled-languages.jpg diff --git a/docs/zh-CN/images/language-settings-1.jpg b/docs/en-US/welcome/community/translations/language-settings-1.jpg similarity index 100% rename from docs/zh-CN/images/language-settings-1.jpg rename to docs/en-US/welcome/community/translations/language-settings-1.jpg diff --git a/docs/zh-CN/images/language-settings-2.jpg b/docs/en-US/welcome/community/translations/language-settings-2.jpg similarity index 100% rename from docs/zh-CN/images/language-settings-2.jpg rename to docs/en-US/welcome/community/translations/language-settings-2.jpg diff --git a/docs/en-US/welcome/getting-started/deployment.md b/docs/en-US/welcome/getting-started/deployment.md new file mode 100644 index 000000000..ed3c9a4c8 --- /dev/null +++ b/docs/en-US/welcome/getting-started/deployment.md @@ -0,0 +1,2 @@ +# Deployment + diff --git a/docs/en-US/getting-started/installation/create-nocobase-app.md b/docs/en-US/welcome/getting-started/installation/create-nocobase-app.md similarity index 100% rename from docs/en-US/getting-started/installation/create-nocobase-app.md rename to docs/en-US/welcome/getting-started/installation/create-nocobase-app.md diff --git a/docs/en-US/getting-started/installation/docker-compose.md b/docs/en-US/welcome/getting-started/installation/docker-compose.md similarity index 100% rename from docs/en-US/getting-started/installation/docker-compose.md rename to docs/en-US/welcome/getting-started/installation/docker-compose.md diff --git a/docs/en-US/getting-started/installation/git-clone.md b/docs/en-US/welcome/getting-started/installation/git-clone.md similarity index 100% rename from docs/en-US/getting-started/installation/git-clone.md rename to docs/en-US/welcome/getting-started/installation/git-clone.md diff --git a/docs/en-US/getting-started/installation/overview.md b/docs/en-US/welcome/getting-started/installation/index.md similarity index 100% rename from docs/en-US/getting-started/installation/overview.md rename to docs/en-US/welcome/getting-started/installation/index.md diff --git a/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md b/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md new file mode 100644 index 000000000..ad4facaa3 --- /dev/null +++ b/docs/en-US/welcome/getting-started/upgrading/create-nocobase-app.md @@ -0,0 +1,75 @@ +# Upgrading for `create-nocobase-app` + +## Minor version upgrade + +Just execute the `nocobase upgrade` upgrade command + +```bash +# Switch to the corresponding directory +cd my-nocobase-app +# Execute the update command +yarn nocobase upgrade +# Start +yarn dev +``` + +## Major upgrade + +You can also use this upgrade method if a minor upgrade fails. + +### 1. Create a new NocoBase project + +```bash +## SQLite +yarn create nocobase-app my-nocobase-app -d sqlite +# MySQL +yarn create nocobase-app my-nocobase-app -d mysql +# PostgreSQL +yarn create nocobase-app my-nocobase-app -d postgres +``` + +### 2. Switching directories + +```bash +cd my-nocobase-app +``` + +### 3. Install dependencies + +📢 This next step may take more than ten minutes due to network environment, system configuration and other factors. + +```bash +yarn install +``` + +### 4. Modify the .env configuration + +Refer to the old version of .env to modify, the database information needs to be configured correctly. The SQLite database also needs to be copied to the `. /storage/db/` directory. + +### 5. Old code migration (not required) + +Business code refer to the new version of plug-in development tutorial and API reference for modification. + +### 6. Execute the upgrade command + +The code is already the latest version, so you need to skip the code update `--skip-code-update` when upgrading. + +```bash +yarn nocobase upgrade --skip-code-update +``` + +### 7. Start NocoBase + +development environment + +```bash +yarn dev +``` + +Production environment + +```bash +yarn start # Not supported on win platforms yet +``` + +Note: For production environment, if the code has been modified, you need to execute ``yarn build`` and restart NocoBase. diff --git a/docs/en-US/welcome/getting-started/upgrading/docker-compose.md b/docs/en-US/welcome/getting-started/upgrading/docker-compose.md new file mode 100644 index 000000000..5e41bbc2d --- /dev/null +++ b/docs/en-US/welcome/getting-started/upgrading/docker-compose.md @@ -0,0 +1,60 @@ +# Upgrading for Docker compose + + + +The Docker installation described in this document is based on the `docker-compose.yml` configuration file, which is also available in the [NocoBase GitHub repository](https://github.com/nocobase/nocobase/tree/main/docker). + + + +## 1. Switch to the directory where you installed it before + +You can also switch to the directory where `docker-compose.yml` is located, depending on the situation. + +```bash +# SQLite +cd nocobase/docker/app-sqlite +# MySQL +cd nocobase/docker/app-mysql +# PostgreSQL +cd nocobase/docker/app-postgres +``` + +## 2. Update the image version number + +`docker-compose.yml` file, replace the image of the app container with the latest version. + +```yml +services: + app: + image: nocobase/nocobase:0.8.0-alpha.1 +``` + +## 3. Delete old images (not required) + +If you are using the latest image, you need to stop and delete the corresponding container first. + +```bash +# find container ID +docker ps +# stop container +docker stop +# delete container +docker rm +``` + +Delete the old image + +```bash +# find image +docker images +# delete image +docker rmi +``` + +## 4. Restart the container + +```bash +docker-compose up -d app +# 查看 app 进程的情况 +docker-compose logs app +``` diff --git a/docs/en-US/welcome/getting-started/upgrading/git-clone.md b/docs/en-US/welcome/getting-started/upgrading/git-clone.md new file mode 100644 index 000000000..e83081cef --- /dev/null +++ b/docs/en-US/welcome/getting-started/upgrading/git-clone.md @@ -0,0 +1,44 @@ +# Upgrading for Git source code + +## 1. switch to the NocoBase project directory + +```bash +cd my-nocobase-app +``` + +## 2. Pull the latest code + +```bash +git pull +``` + +## 3. Update dependencies + +``` +yarn install +``` + +## 4. Execute the update command + +```bash +yarn nocobase upgrade +``` + +## 5. Start NocoBase + +development environment + +```bash +yarn dev +``` + +Production environment + +```bash +# compile +yarn build + +# Start +yarn start # Not supported on Windows platforms yet +``` + diff --git a/docs/en-US/welcome/getting-started/upgrading/index.md b/docs/en-US/welcome/getting-started/upgrading/index.md new file mode 100644 index 000000000..f04985368 --- /dev/null +++ b/docs/en-US/welcome/getting-started/upgrading/index.md @@ -0,0 +1,7 @@ +# Overview + +NocoBase supports three types of installation, with slight differences in upgrades. + +- [Upgrading for Docker compose](./upgrading/docker-compose.md) +- [Upgrading for create-nocobase-app](./upgrading/create-nocobase-app.md) +- [Upgrading for Git source code](./upgrading/git-clone.md) diff --git a/docs/en-US/why.md b/docs/en-US/welcome/introduction/features.md similarity index 86% rename from docs/en-US/why.md rename to docs/en-US/welcome/introduction/features.md index 4814883b7..d9f359a2a 100644 --- a/docs/en-US/why.md +++ b/docs/en-US/welcome/introduction/features.md @@ -6,13 +6,13 @@ Most form-, table-, or process-driven codeless products create data structures d NocoBase adopts the design idea of separating the data structure from the user interface, allowing you to create any number of blocks (data views) for the data collections, with different type, styles, content, and actions in each block. This takes into account the simplicity of codeless operation, but also the flexibility like native development. -![2.collection-block.png](./user-manual/introduction/important-features/2.collection-block.png) +![2.collection-block.png](./features/2.collection-block.png) -## 2. Integrate "system configuration" and "system use" +## 2. What you see is what you get NocoBase enables the development of complex and distinctive business systems, but this does not mean that complex and specialized operations are required. With a single click, configuration options can be displayed on the usage interface, which means that administrators with system configuration rights can configure the user interface directly with WYSIWYG operations. -![2.user-root.gif](./user-manual/introduction/important-features/2.user-root.gif) +![2.user-root.gif](./features/2.user-root.gif) ## 3. Everything is a plugin diff --git a/docs/en-US/user-manual/introduction/important-features/2.collection-block.png b/docs/en-US/welcome/introduction/features/2.collection-block.png similarity index 100% rename from docs/en-US/user-manual/introduction/important-features/2.collection-block.png rename to docs/en-US/welcome/introduction/features/2.collection-block.png diff --git a/docs/en-US/user-manual/introduction/important-features/2.user-root.gif b/docs/en-US/welcome/introduction/features/2.user-root.gif similarity index 100% rename from docs/en-US/user-manual/introduction/important-features/2.user-root.gif rename to docs/en-US/welcome/introduction/features/2.user-root.gif diff --git a/docs/en-US/welcome/introduction/index.md b/docs/en-US/welcome/introduction/index.md new file mode 100644 index 000000000..5670e4104 --- /dev/null +++ b/docs/en-US/welcome/introduction/index.md @@ -0,0 +1,50 @@ +# Introduction + +![](https://nocobase.oss-cn-beijing.aliyuncs.com/bbcedd403d31cd1ccc4e9709581f5c2f.png) + +**Note:** 📌 + +NocoBase is in early stage of development and is subject to frequent changes, please use caution in production environments. + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. No programming required, build your own collaboration platform, management system with NocoBase in hours. + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Contact Us: +hello@nocobase.com + +## Features + +- **Open source and free** + - Unrestricted commercial use under the Apache-2.0 license + - Full code ownership, private deployment, private and secure data + - Free to expand and develop for actual needs + - Good ecological support +- **Strong no-code capability** + - Data Model + - Create independent data models using dozens of field types such as text, number, attachment, and various association relationships such as one-to-one, one-to-many, etc. + - Block + - Display and manipulate data within a page using a free combination of block types such as tables, forms, kanban, calendars, details, etc. + - Action + - Support filtering, exporting, adding, deleting, modifying, viewing and other operations to process data, which can be extended to more types. + - ACL + - Role-based control of user's system configuration rights, data action rights and menu access rights. + - Workflow + - Repetitive tasks are replaced by automation to increase efficiency. Manual approval is required for important matters. +- **Built for extended development** + - Microkernel architecture, flexible and easy to extend, with a robust plug-in system + - Node.js-based, with popular frameworks and technologies, including Koa, Sequelize, React, Formily, Ant Design, etc. + - Progressive development, easy for getting-started, friendly to newcomers + - No binding, no strong dependencies, can be used in any combination or extensions, can be used in existing projects + +## Architecture + +![](https://www.nocobase.com/images/NocoBaseMindMapLite.png) + +[Click here to view the full image](https://www.nocobase.com/images/NocoBaseMindMap.png) diff --git a/docs/en-US/who.md b/docs/en-US/welcome/introduction/when.md similarity index 100% rename from docs/en-US/who.md rename to docs/en-US/welcome/introduction/when.md diff --git a/docs/menus.ts b/docs/menus.ts index 231035caf..060311f3f 100644 --- a/docs/menus.ts +++ b/docs/menus.ts @@ -8,7 +8,7 @@ export default { '/welcome/introduction/index', '/welcome/introduction/features', '/welcome/introduction/when', - '/welcome/introduction/learning-guide', + // '/welcome/introduction/learning-guide', ], }, { @@ -27,8 +27,17 @@ export default { '/welcome/getting-started/installation/git-clone', ], }, - '/welcome/getting-started/upgrading', - '/welcome/getting-started/deployment', + { + title: 'Upgrading', + 'title.zh-CN': '升级', + type: 'subMenu', + children: [ + '/welcome/getting-started/upgrading/index', + '/welcome/getting-started/upgrading/docker-compose', + '/welcome/getting-started/upgrading/create-nocobase-app', + '/welcome/getting-started/upgrading/git-clone', + ], + }, ], }, { @@ -37,30 +46,65 @@ export default { type: 'group', children: [ '/welcome/community/contributing', - '/welcome/community/faq', + // '/welcome/community/faq', '/welcome/community/translations', '/welcome/community/thanks', ], }, ], '/manual': [ - '/manual/functional-zoning', - '/manual/collections', - '/manual/menus', - '/manual/blocks', - '/manual/actions', - '/manual/roles-permissions', - '/manual/tabs', - '/manual/file-storages', - '/manual/system-settings', - '/manual/plugins', + { + title: 'Quick Start', + 'title.zh-CN': '快速上手', + type: 'group', + children: [ + '/manual/quick-start/the-first-app', + '/manual/quick-start/functional-zoning', + '/manual/quick-start/ui-editor-mode', + '/manual/quick-start/plugins', + ], + }, + { + title: 'Core Concepts', + 'title.zh-CN': '核心概念', + type: 'group', + children: [ + '/manual/core-concepts/a-b-c', + '/manual/core-concepts/collections', + '/manual/core-concepts/blocks', + '/manual/core-concepts/actions', + '/manual/core-concepts/menus', + '/manual/core-concepts/containers', + ], + }, + { + title: 'Blocks Guide', + 'title.zh-CN': '区块指南', + type: 'group', + children: [ + '/manual/blocks-guide/charts', + ], + }, ], '/development': [ { title: 'Getting started', 'title.zh-CN': '快速开始', type: 'group', - children: ['/development/index', '/development/your-fisrt-plugin'], + children: [ + '/development/index', + '/development/your-fisrt-plugin', + '/development/learning-guide', + ], + }, + { + title: '约束规范', + 'title.zh-CN': '约束规范', + type: 'group', + children: [ + '/development/app-ds', + '/development/plugin-ds', + ], }, { title: 'Extension Guides', diff --git a/docs/zh-CN/api/env.md b/docs/zh-CN/api/env.md index 2919e7e62..e3140bfff 100644 --- a/docs/zh-CN/api/env.md +++ b/docs/zh-CN/api/env.md @@ -47,6 +47,24 @@ NocoBase API 地址前缀,默认值 `/api/` API_BASE_PATH=/api/ ``` +### PLUGIN_PACKAGE_PREFIX + +插件包前缀,默认值 `@nocobase/plugin-,@nocobase/preset-` + +例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。 + +PLUGIN_PACKAGE_PREFIX 配置如下: + +```bash +PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- +``` + +插件名和包名的对应关系为: + +- `users` 插件包名为 `@nocobase/plugin-users` +- `nocobase` 插件包名为 `@nocobase/preset-nocobase` +- `hello` 插件包名为 `@my-nocobase-app/plugin-hello` + ### DB_DIALECT 数据库类型,默认值 `sqlite`,可选项包括: diff --git a/docs/zh-CN/demos/demo1.tsx b/docs/zh-CN/demos/demo1.tsx deleted file mode 100644 index 794bfbca7..000000000 --- a/docs/zh-CN/demos/demo1.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { APIClient, APIClientProvider, G2Plot, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import MockAdapter from 'axios-mock-adapter'; -import React from 'react'; - -const api = new APIClient(); - -const mock = new MockAdapter(api.axios); - -mock.onGet('/test').reply(200, { - data: [ - { - Date: '2010-01', - scales: 1998, - }, - { - Date: '2010-02', - scales: 1850, - }, - { - Date: '2010-03', - scales: 1720, - }, - { - Date: '2010-04', - scales: 1818, - }, - { - Date: '2010-05', - scales: 1920, - }, - { - Date: '2010-06', - scales: 1802, - }, - { - Date: '2010-07', - scales: 1945, - }, - { - Date: '2010-08', - scales: 1856, - }, - { - Date: '2010-09', - scales: 2107, - }, - ], -}); - -const fetchData = async (api: APIClient, options) => { - const response = await api.request(options); - return response?.data?.data; -}; - -const schema = { - type: 'void', - name: 'line', - 'x-designer': 'G2Plot.Designer', - 'x-decorator': 'CardItem', - 'x-component': 'G2Plot', - 'x-component-props': { - plot: 'Line', - config: { - data: '{{ fetchData(api, { url: "/test" }) }}', - padding: 'auto', - xField: 'Date', - yField: 'scales', - xAxis: { - // type: 'timeCat', - tickCount: 5, - }, - }, - }, -}; - -export default () => { - return ( - - - - - - ); -}; diff --git a/docs/zh-CN/development/directory-structure.md b/docs/zh-CN/development/directory-structure.md index 34a8b990a..770ab204c 100644 --- a/docs/zh-CN/development/directory-structure.md +++ b/docs/zh-CN/development/directory-structure.md @@ -1,12 +1,6 @@ -# 目录结构 +# 项目目录结构 -## 应用脚手架 - -```bash -$ yarn create nocobase-app my-nocobase-app -``` - -通过 `create-nocobase-app` 创建的应用脚手架目录结构如下: +无论是源码还是 `create-nocobase-app` 创建的应用,目录结构都是一样的,结构如下: ```bash ├── my-nocobase-app @@ -68,22 +62,3 @@ NocoBase 采用 Monorepo 的方式管理代码,将不同模块划分到不同 ### .buildrc.ts 文件 packages 的打包配置,支持 cjs、esm 和 umd 三种格式的打包。 - -## 插件脚手架 - -```bash -$ yarn nocobase create-plugin my-plugin -``` - -通过 `nocobase create-plugin` 初始化的插件脚手架目录如下: - -```bash -├── my-nocobase-app - ├── packages - ├── plugins - ├── my-plugin - ├── src - ├── client - ├── server - ├── package.json -``` diff --git a/docs/zh-CN/development/index.md b/docs/zh-CN/development/index.md index 5c1d42f45..ec9544ba7 100644 --- a/docs/zh-CN/development/index.md +++ b/docs/zh-CN/development/index.md @@ -6,7 +6,11 @@ NocoBase 采用微内核架构,各类功能以插件形式扩展,所以微 ## 插件管理器 -NocoBase 提供了强大的插件管理器用于管理插件,开发可以通过 CLI 的方式管理插件,也可以通过界面激活、禁用已添加的插件。如: +NocoBase 提供了强大的插件管理器用于管理插件,插件管理器的流程如下: + + + +开发可以通过 CLI 的方式管理插件: ```bash # 创建插件 @@ -21,6 +25,10 @@ yarn pm disable hello yarn pm remove hello ``` +无代码用户也可以通过插件管理器界面激活、禁用、删除已添加的本地插件: + + + 更多插件示例,查看 [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples)。 ## 扩展能力 diff --git a/docs/zh-CN/development/learning-guide.md b/docs/zh-CN/development/learning-guide.md new file mode 100644 index 000000000..cc5744757 --- /dev/null +++ b/docs/zh-CN/development/learning-guide.md @@ -0,0 +1,127 @@ +# 学习路线指南 + +## 1. 从安装运行 NocoBase 开始 + +**相关文档:快速开始** + +主要命令包括: + +下载 + +```bash +yarn create/git clone +yarn install +``` + +安装 + +```bash +yarn nocobase install +``` + +运行 + +```bash +# for development +yarn dev + +# for production +yarn build +yarn start +``` + +## 2. 了解 NocoBase 平台提供的核心功能 + +**相关文档:使用手册** + +主要的三部分包括: + +- UI 设计器:主要包括区块、字段和操作 +- 插件管理器:功能需求扩展 +- 配置中心:已激活插件提供的配置功能 + +## 3. 进一步了解插件管理器的使用 + +**相关文档:插件开发** + +NocoBase 提供了简易的插件管理器界面,但是在界面上只能处理本地插件的 enable、disable 和 remove,完整的操作需要通过 CLI + +```bash +# 创建插件 +yarn pm create hello +# 注册插件 +yarn pm add hello +# 激活插件 +yarn pm enable hello +# 禁用插件 +yarn pm disable hello +# 删除插件 +yarn pm remove hello +``` + +更多插件示例,查看 packages/samples,通过 samples 插件能够了解插件的基本用法,就可以进一步开发插件了。 + +## 4. 开发新插件,了解模块分布 + +**相关文档:扩展指南** + +[编写第一个插件](/development/your-fisrt-plugin) 章节,虽然简单的讲述了插件的主要开发流程,但是为了更快速的介入插件细节,你可能需要进一步了解 NocoBase 框架的模块分布: + +- Server + - Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置 + - Resources & Actions:主要用于扩展 Action API + - Middleware:中间件 + - Events:事件 + - I18n:服务端国际化 +- Client + - UI Schema Designer:页面设计器 + - UI Router:有自定义页面需求时 + - Settings Center:为插件提供配置页面 + - I18n:客户端国际化 +- Devtools + - Commands:自定义命令行 + - Migrations:迁移脚本 + +## 5. 查阅各模块主要 API + +**相关文档:API 参考** + +查看各模块的 packages/samples,进一步了解模块主要 API 的用法 + +- Server + - Collections & Fields + - db.collection + - db.import + - Resources & Actions + - app.resourcer.define + - app.resourcer.registerActions + - Middleware + - app.use + - app.acl.use + - app.resourcer.use + - Events + - app.on + - app.db.on + - I18n + - app.i18n + - ctx.i18n +- Client + - UI Schema Designer + - SchemaComponent + - SchemaInitializer + - SchemaSettings + - UI Router + - RouteSwitchProvider + - RouteSwitch + - Settings Center + - SettingsCenterProvider + - I18n + - app.i18n + - useTranslation +- Devtools + - Commands + - app.command + - app.findCommand + - Migrations + - app.db.addMigration + - app.db.addMigrations diff --git a/docs/zh-CN/development/pm-flow.svg b/docs/zh-CN/development/pm-flow.svg new file mode 100644 index 000000000..d15cf800c --- /dev/null +++ b/docs/zh-CN/development/pm-flow.svg @@ -0,0 +1 @@ +
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    app/client plugins
    pm.enable
    pm.disable
    pm.remove
    \ No newline at end of file diff --git a/docs/zh-CN/development/pm-ui.jpg b/docs/zh-CN/development/pm-ui.jpg new file mode 100644 index 000000000..4c8fdd3c1 Binary files /dev/null and b/docs/zh-CN/development/pm-ui.jpg differ diff --git a/docs/zh-CN/development/your-fisrt-plugin.md b/docs/zh-CN/development/your-fisrt-plugin.md index fca26f384..03099b080 100644 --- a/docs/zh-CN/development/your-fisrt-plugin.md +++ b/docs/zh-CN/development/your-fisrt-plugin.md @@ -9,65 +9,76 @@ ## 创建插件 -首先,你可以通过 CLI 快速的创建一个初始化的插件,命令如下: +首先,你可以通过 CLI 快速的创建一个空插件,命令如下: ```bash yarn pm create hello ``` -新建的插件,会放置在 `packages/plugins/hello` 目录下。 +插件所在目录 `packages/plugins/hello`,插件目录结构为: -## 插件目录结构 - -```ts +```bash |- /hello |- /src - |- /client - |- /server + |- /client # 插件客户端代码 + |- /server # 插件服务端代码 |- client.d.ts |- client.js - |- package.json + |- package.json # 插件包信息 |- server.d.ts |- server.js ``` +package.json 信息 + +```json +{ + "name": "@nocobase/plugin-hello", + "version": "0.1.0", + "main": "lib/server/index.js", + "devDependencies": { + "@nocobase/client": "0.8.0-alpha.1", + "@nocobase/test": "0.8.0-alpha.1" + } +} +``` + +NocoBase 插件也是 NPM 包,插件名和 NPM 包名的对应规则为 `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`。 + +`PLUGIN_PACKAGE_PREFIX` 为插件包前缀,可以在 .env 里自定义,[点此查看 PLUGIN_PACKAGE_PREFIX 说明](/api/env#plugin_package_prefix)。 + ## 编写插件 -插件的主体文件在 `packages/plugins/hello/src/server/plugin.ts`,修改为: +查看 `packages/plugins/hello/src/server/plugin.ts` 文件,并修改为: ```ts import { InstallOptions, Plugin } from '@nocobase/server'; -export class Hello extends Plugin { - initialize() { - // TODO - } +export class HelloPlugin extends Plugin { + afterAdd() {} - beforeLoad() { - // TODO - } + beforeLoad() {} async load() { - // TODO - // Visit: http://localhost:13000/api/hello:get - this.app.resource({ + this.db.collection({ name: 'hello', - actions: { - async get(ctx, next) { - ctx.body = `Hello plugin1!`; - next(); - }, - }, + fields: [ + { type: 'string', name: 'name' } + ], }); - this.app.acl.allow('hello', 'get'); + this.app.acl.allow('hello', '*'); } - async install(options: InstallOptions) { - // TODO - } + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} } -export default Hello; +export default HelloPlugin; ``` ## 注册插件 @@ -78,6 +89,8 @@ yarn pm add hello ## 激活插件 +插件激活时,会自动创建刚才编辑插件配置的 hello 表。 + ```bash yarn pm enable hello ``` @@ -95,4 +108,18 @@ yarn start ## 体验插件功能 -访问地址 http://localhost:13000/api/hello:get +向插件的 hello 表里插入数据 + +```bash +curl --location --request POST 'http://localhost:13000/api/hello:create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "name": "Hello world" +}' +``` + +查看 hello 表数据 + +```bash +curl --location --request GET 'http://localhost:13000/api/hello:list' +``` diff --git a/docs/zh-CN/manual/actions.md b/docs/zh-CN/manual/actions.md deleted file mode 100644 index b403cd20d..000000000 --- a/docs/zh-CN/manual/actions.md +++ /dev/null @@ -1,3 +0,0 @@ -# 操作 - -TO DO \ No newline at end of file diff --git a/docs/zh-CN/manual/blocks-guide/charts.md b/docs/zh-CN/manual/blocks-guide/charts.md new file mode 100755 index 000000000..c38b3f26b --- /dev/null +++ b/docs/zh-CN/manual/blocks-guide/charts.md @@ -0,0 +1,204 @@ +# 图表 + +目前,NocoBase 图表区块需要通过配置文件或编写代码来实现。图表库使用的是 [g2plot](https://g2plot.antv.vision/en/examples/gallery),理论上支持 https://g2plot.antv.vision/en/examples/gallery 上的所有图表。目前可以配置的图表包括: + +- 柱状图 +- 条形图 +- 折线图 +- 饼图 +- 面积图 + +## 添加和编辑图表 + +![chart-edit.gif](./charts/chart-edit.gif) + +## 图表配置 + +初始化的图表配置是静态的 JSON 数据 + +```json +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ], + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +data 支持表达式的写法,NocoBase 内置了 `requestChartData(config)` 函数,用于自定义图表数据的请求。Config 参数说明见: [https://github.com/axios/axios#request-config](https://github.com/axios/axios#request-config) + +示例: + +```json +{ + "data": "{{requestChartData({ url: 'collectionName:getColumnChartData' })}}", + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +HTTP API 示例: + +```bash +GET /api/collectionName:getColumnChartData + +Response Body +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ] +} + +``` + +## Server 端实现 + +为名为 collectionName 的数据表,添加自定义的 getColumnChartData 方法: + +```js +app.resourcer.registerActionHandlers({ + 'collectionName:getColumnChartData': (ctx, next) => { + // The data to be output + ctx.body = []; + await next(); + }, +}); + +``` + +## 视频 + +### 静态数据 + +https://user-images.githubusercontent.com/1267426/198877269-1c56562b-167a-4808-ada3-578f0872bce1.mp4 + + +### 动态数据 + +https://user-images.githubusercontent.com/1267426/198877336-6bd85f0b-17c5-40a5-9442-8045717cc7b0.mp4 + + +### 更多图表 +理论上支持 https://g2plot.antv.vision/en/examples/gallery 上的所有图表 + +https://user-images.githubusercontent.com/1267426/198877347-7fc2544c-b938-4e34-8a83-721b3f62525e.mp4 + +## JS 表达式 + +Syntax + +```js +{ + "key1": "{{ js expression }}" +} +``` + +https://user-images.githubusercontent.com/1267426/198877361-808a51cc-6c91-429f-8cfc-8ad7f747645a.mp4 + diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v new file mode 100644 index 000000000..76d8c994b Binary files /dev/null and b/docs/zh-CN/manual/blocks-guide/charts/Chart-Dynamic-1.m4v differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v new file mode 100644 index 000000000..482b078eb Binary files /dev/null and b/docs/zh-CN/manual/blocks-guide/charts/Chart-Js-1.m4v differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v new file mode 100644 index 000000000..944d8da25 Binary files /dev/null and b/docs/zh-CN/manual/blocks-guide/charts/Chart-More-1.m4v differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v b/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v new file mode 100644 index 000000000..8009d7a07 Binary files /dev/null and b/docs/zh-CN/manual/blocks-guide/charts/Chart-Static-1.m4v differ diff --git a/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif b/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif new file mode 100755 index 000000000..86a3540a3 Binary files /dev/null and b/docs/zh-CN/manual/blocks-guide/charts/chart-edit.gif differ diff --git a/docs/zh-CN/manual/blocks.md b/docs/zh-CN/manual/blocks.md deleted file mode 100644 index 716368802..000000000 --- a/docs/zh-CN/manual/blocks.md +++ /dev/null @@ -1,63 +0,0 @@ -# 区块 - -区块是用来展示和操作数据的视图。区块可以放在页面和弹窗里。一个完整的区块由三部分组成: - -1. 内容区:区块的主体 -2. 操作区:可以放置各种操作按钮,用于操作区块数据 -3. 配置区:操作区块配置的按钮 - -![6.block.jpg](./blocks/6.block.jpg) - -## 添加区块 - -进入界面配置模式,在页面和弹窗内点击 Add block 按钮即可添加区块。选项分为 4 步: - -1. 选择区块类型:目前可用的区块类型包括表格、表单、详情、日历、看板、Markdown -2. 选择 Collection:此处会列出所有的 Collection -3. 选择创建方式:创建空白区块,或者从复制区块模板,或者引用区块模板 -4. 选择模板:若第 3 步选择了从模板创建,则在第 4 步选择模板 - -![6.block-add.jpg](./blocks/6.block-add.jpg) - -## 配置区块 - -配置区块包括三方面的内容: - -- 配置区块内容 -- 配置区块操作 -- 配置区块属性 - -### 配置区块内容 - -以表格区块为例,区块内容是指表格中要显示的列。点击 Configure columns 即可配置要显示的列: - -![6.block-content.gif](./blocks/6.block-content.gif) - -### 配置区块操作 - -以表格区块为例,有筛选、添加、删除、查看、编辑、自定义等操作可选。点击 Configure actions 按钮可以配置操作。其中,每个操作按钮都可以单独配置属性: - -![6.block-content.gif](./blocks/6.block-content%201.gif) - -### 配置区块属性 - -将光标移到区块右上角,会看到区块配置按钮。以表格区块为例,可以配置的属性有: - -- Drag & drop sorting -- Set the data scope -- Set default sorting rules -- Records per page - -![6.collection-setting.gif](./blocks/6.collection-setting.gif) - -## 区块类型 - -目前 NocoBase 支持以下几种区块: - -- 表格 -- 表单 -- 详情 -- 看板 -- 日历 -- 相关数据 -- Markdown \ No newline at end of file diff --git a/docs/zh-CN/manual/collections.md b/docs/zh-CN/manual/collections.md deleted file mode 100644 index 4f07705ce..000000000 --- a/docs/zh-CN/manual/collections.md +++ /dev/null @@ -1,42 +0,0 @@ -# 数据表 - -开发一个系统之前,我们通常要对业务进行抽象,建立数据模型。在 NocoBase 里,数据表的概念与关系型数据库的数据表概念相近。 - -点击“数据表配置”按钮,进入数据表配置界面。在这里,可以对数据表进行新增、编辑、删除等操作。 - -![4.collections.gif](./collections/4.collections.gif) - -## 字段类型 - -- 基本类型 - - 单行文本 - - 多行文本 - - 手机号码 - - 电子邮箱 - - 数字 - - 百分比 - - 密码 - - 图标 -- 选择类型 - - 勾选 - - 下拉菜单(单选) - - 下拉菜单(多选) - - 单选框 - - 复选框 - - 中国行政区 -- 多媒体 - - 附件 - - Markdown - - 富文本 -- 日期 & 时间 - - 日期 - - 时间 -- 关系类型 - - 关联 - - 子表格 -- 系统信息 - - ID - - 创建日期 - - 最后修改日期 - - 创建人 - - 最后修改人 \ No newline at end of file diff --git a/docs/zh-CN/manual/collections/4.collections.gif b/docs/zh-CN/manual/collections/4.collections.gif deleted file mode 100755 index dd0f01a4d..000000000 Binary files a/docs/zh-CN/manual/collections/4.collections.gif and /dev/null differ diff --git a/docs/zh-CN/manual/core-concepts/a-b-c.md b/docs/zh-CN/manual/core-concepts/a-b-c.md new file mode 100755 index 000000000..0c707572f --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/a-b-c.md @@ -0,0 +1,21 @@ +# A·B·C + +在无代码层面,NocoBase 的核心概念可以总结为 `A·B·C`。 + +`A·B·C` 是`Action·Block·Collection` 的缩写,即`操作·区块·数据表`。通过 `Collection` 设计数据结构,通过 `Block` 组织与展示数据,通过 `Action` 交互数据。 + +## 数据与视图分离 + +定义数据时,专注于定义数据;定义视图时,专注于定义视图。 + +通过定义数据,来抽象业务;再通过定义区块去组织内容以你所期望的方式呈现数据。 + +## 一种数据,多种呈现 + +为业务抽象出统一的数据模型,然后通过区块可以为同一个数据表建立各种各样的呈现方式,用于不同的场景、不同的角色、不同的组合。 + +## 操作驱动 + +数据表来定义数据的结构,区块来组织数据的呈现方式。那么,什么驱动数据的交互和变更?答案是操作。 + +区块将数据呈现给用户,操作则是将用户的指令发送给服务器完成数据的交互或变更。 diff --git a/docs/zh-CN/manual/core-concepts/actions.md b/docs/zh-CN/manual/core-concepts/actions.md new file mode 100755 index 000000000..e47ce928c --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/actions.md @@ -0,0 +1,31 @@ +# 操作 + +`操作`是完成某个特定目标的动作集合。在 NocoBase 中通过 `操作`来处理数据或者与服务器通信。 操作通常会通过点击某个按钮触发。 + +## 操作类型 + +NocoBase 目前支持 10 几种操作,未来可以通过插件的方式支持更多种。 + +| 名称 | 描述 | +| --- | --- | +| 筛选 | 指定数据的显示范围 | +| 添加 | 打开添加新数据的弹窗,在弹窗里通常包含一个表单区块 | +| 查看 | 打开查看指定数据的弹窗,在弹窗里通常包含一个详情区块 | +| 编辑 | 打开修改指定数据的弹窗,在弹窗里通常包含一个表单区块 | +| 删除 | 打开删除指定数据的对话框,确认后删除 | +| 导出 | 将数据导出为 Excel,常和筛选组合使用 | +| 打印 | 打开浏览器打印窗口,打印指定的数据,常和详情区块组合使用 | +| 提交 | 将指定表单区块的数据提交到服务端 | +| 刷新 | 刷新当前区块内的数据 | +| 导入 | 从 Excel 模板中导入数据 | +| 批量编辑 | 批量编辑数据 | +| 批量更新 | 批量更新数据 | +| 打开弹窗 | 打开弹窗或抽屉,在里面可以放置区块 | +| 更新数据 | 点击后自动更新指定的字段 | +| 自定义请求 | 向第三方发送请求 | + +## 配置操作 + +在界面配置模式下,将鼠标移到操作按钮上,右上角就会出现该操作支持的配置项。比如筛选操作: + +![action-config-5.jpg](./actions/action-config-5.jpg) \ No newline at end of file diff --git a/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg b/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg new file mode 100755 index 000000000..b9429826f Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/actions/action-config-5.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/blocks.md b/docs/zh-CN/manual/core-concepts/blocks.md new file mode 100755 index 000000000..68ff9eae8 --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/blocks.md @@ -0,0 +1,87 @@ +# 区块 + +区块是用来展示和操作数据的视图。在 NocoBase 里,将页面、弹窗、抽屉看作是区块的容器,容器就像一张画布,在里面可以放置各种各样的区块。 + +得益于 NocoBase 将数据与视图分离的设计,页面通过区块承载数据,并根据不同的区块类型,以不同的形式组织和管理数据。 + +## 区块结构 + +一个完整的区块由三部分组成: + +1. 内容区:区块的主体 +2. 操作区:可以放置各种操作按钮,用于操作区块数据 +3. 配置区:操作区块配置的按钮 + +![6.block.jpg](./blocks/6.block.jpg) + +## 区块类型 + +![add-block.jpg](./blocks/add-block.jpg) + +NocoBase 目前内置 10 几种区块,未来可以通过插件的方式支持更多种。 + +- **数据区块**:为组织数据而设计的区块。 + - **表格**:以表格形式展示多条数据的区块,既可以展示一个数据表,也可以展示相互之间有关联关系的多个数据表。 + - **表单**:以各种类型的输入框录入或编辑数据的区块,既可以为某一个数据表进行录入,也可以对相互之间有关联关系的多个数据表统一录入。 + - **详情**:展示一条特定数据的区块,既可以对某一个数据表的某一条数据进行展示,也可以对相互之间有关联关系的多个数据表中的多条数据统一展示。 + - **日历**:以日历的形式展示多条数据的区块,适合某些在日期上具备重要特征的数据。 + - **看板**:以看板的形式展示多条数据的区块,适合用来对生产过程进行管理。 +- **图表区块**:为图形化展示统计数据而设计的区块。目前支持:柱状图、条形图、折线图、饼图、面积图等。 +- **其他区块**:为展示特殊数据而设计的区块。 + - **Markdown**:用 Markdown 书写的文本内容。 + - **操作记录**:展示一个数据表中的所有数据的变更记录,包括新建、编辑和删除。 + +## 添加区块 + +进入界面配置模式,在页面和弹窗内点击 Add block 按钮即可添加区块。选项分为 4 步: + +1. 选择区块类型:目前可用的区块类型包括表格、表单、详情、日历、看板、Markdown +2. 选择 Collection:此处会列出所有的 Collection +3. 选择创建方式:创建空白区块,或者从复制区块模板,或者引用区块模板 +4. 选择模板:若第 3 步选择了从模板创建,则在第 4 步选择模板 + +![6.block-add.jpg](./blocks/6.block-add.jpg) + +## 配置区块 + +配置区块包括三方面的内容: + +- 配置区块内容 +- 配置区块操作 +- 配置区块属性 + +### 配置区块内容 + +以表格区块为例,区块内容是指表格中要显示的列。点击 Configure columns 即可配置要显示的列: + +![6.block-content.gif](./blocks/6.block-content.gif) + +### 配置区块操作 + +以表格区块为例,有筛选、添加、删除、查看、编辑、自定义等操作可选。点击 Configure actions 按钮可以配置操作。其中,每个操作按钮都可以单独配置属性: + +![6.block-content.gif](./blocks/6.block-content1.gif) + +### 配置区块属性 + +将光标移到区块右上角,会看到区块配置按钮。以表格区块为例,可以配置的属性有: + +- Block title +- Drag & drop sorting +- Set the data scope +- Set default sorting rules +- Records per page + +## 调整布局 + +页面内既可以只放一个区块,也可以放多个区块进行组合。你可以通过拖拽完成区块位置和宽度的调整。 + +![block-drag.gif](./blocks/block-drag.gif) + +## 区块模板 + +你可以将一个区块保存为模板,以后可以复制或引用这个模板。 + +比如,一个数据表的表单,既用于新增数据,又用于编辑数据,那就可以将这个表单保存为模板,在新增数据和编辑数据的界面里引用它。 + +![block-template.jpg](./blocks/block-template.jpg) diff --git a/docs/zh-CN/manual/blocks/6.block-add.jpg b/docs/zh-CN/manual/core-concepts/blocks/6.block-add.jpg similarity index 100% rename from docs/zh-CN/manual/blocks/6.block-add.jpg rename to docs/zh-CN/manual/core-concepts/blocks/6.block-add.jpg diff --git a/docs/zh-CN/manual/blocks/6.block-content.gif b/docs/zh-CN/manual/core-concepts/blocks/6.block-content.gif similarity index 100% rename from docs/zh-CN/manual/blocks/6.block-content.gif rename to docs/zh-CN/manual/core-concepts/blocks/6.block-content.gif diff --git a/docs/zh-CN/manual/blocks/6.block-content 1.gif b/docs/zh-CN/manual/core-concepts/blocks/6.block-content1.gif similarity index 100% rename from docs/zh-CN/manual/blocks/6.block-content 1.gif rename to docs/zh-CN/manual/core-concepts/blocks/6.block-content1.gif diff --git a/docs/zh-CN/manual/blocks/6.block.jpg b/docs/zh-CN/manual/core-concepts/blocks/6.block.jpg similarity index 100% rename from docs/zh-CN/manual/blocks/6.block.jpg rename to docs/zh-CN/manual/core-concepts/blocks/6.block.jpg diff --git a/docs/zh-CN/manual/blocks/6.collection-setting.gif b/docs/zh-CN/manual/core-concepts/blocks/6.collection-setting.gif similarity index 100% rename from docs/zh-CN/manual/blocks/6.collection-setting.gif rename to docs/zh-CN/manual/core-concepts/blocks/6.collection-setting.gif diff --git a/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg b/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg new file mode 100755 index 000000000..2b463f2cf Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/blocks/add-block.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif b/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif new file mode 100755 index 000000000..69563d0dc Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/blocks/block-drag.gif differ diff --git a/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg b/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg new file mode 100755 index 000000000..86da281e0 Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/blocks/block-template.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/collections.md b/docs/zh-CN/manual/core-concepts/collections.md new file mode 100755 index 000000000..2b2cfe9e4 --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/collections.md @@ -0,0 +1,59 @@ +# 数据表 + +开发一个系统之前,我们通常要对业务进行抽象,建立数据模型。NocoBase 的数据表由字段(列)和记录(行)组成。数据表的概念与关系型数据库的数据表概念相近,但是字段的概念略有不同。 + +例如,在一个描述订单的数据表中,每列包含的是订单某个特定属性的信息,如收件地址;而每行则包含了某个特定订单的所有信息,如订单号、顾客姓名、电话、收件地址等。 + +## 数据与视图分离 + +NocoBase 的`数据`和`视图`是分离的,分别由数据表和区块来管理和呈现。 + +这就意味着: + +- 你可以创建**一个**数据表,并为其设计**一套**界面,实现数据的展示和操作; +- 你也可以创建**一个**数据表,然后为其设计**多套**界面,用于不同的场景或角色下对数据的展示和操作; +- 你还可以创建**多个**数据表,然后为其设计**一套**界面,实现多个数据表的同时展示和操作; +- 你甚至可以创建**多个**数据表,然后为其设计**多套**界面,每套界面都可以操作多个数据表并完成独特的功能; + +简单说,数据与界面的分离使得**数据的组织和管理更加灵活**,如何呈现数据就看你如何配置界面。 + +## 字段类型 + +NocoBase 目前支持以下几十种字段,未来可以通过插件的方式支持更多种。 + +| 名称 | 类型 | +| --- | --- | +| 单行文本 | 基本类型 | +| 图标 | 基本类型 | +| 多行文本 | 基本类型 | +| 密码 | 基本类型 | +| 手机号码 | 基本类型 | +| 数字 | 基本类型 | +| 整数 | 基本类型 | +| 电子邮箱 | 基本类型 | +| 百分比 | 基本类型 | +| 下拉菜单(单选) | 选择类型 | +| 下拉菜单(多选) | 选择类型 | +| 中国行政区 | 选择类型 | +| 勾选 | 选择类型 | +| 单选框 | 选择类型 | +| 复选框 | 选择类型 | +| 关联 | 关系类型 | +| 一对一(belongs to) | 关系类型 | +| 一对一(has one) | 关系类型 | +| 一对多 | 关系类型 | +| 多对一 | 关系类型 | +| 多对多 | 关系类型 | +| 公式 | 高级类型 | +| 自动编码 | 高级类型 | +| JSON | 高级类型 | +| Markdown | 多媒体 | +| 富文本 | 多媒体 | +| 附件 | 多媒体 | +| 日期 | 日期&时间 | +| 时间 | 日期&时间 | +| ID | 系统信息 | +| 创建人 | 系统信息 | +| 创建日期 | 系统信息 | +| 最后修改人 | 系统信息 | +| 最后修改日期 | 系统信息 | \ No newline at end of file diff --git a/docs/zh-CN/manual/core-concepts/containers.md b/docs/zh-CN/manual/core-concepts/containers.md new file mode 100755 index 000000000..a6a3bda29 --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/containers.md @@ -0,0 +1,23 @@ +# 容器 + +在 NocoBase 里,将页面、弹窗、抽屉看作是区块的容器,容器就像一张画布,在里面可以放置各种各样的区块 + +## 页面 + +![container-page.jpg](./containers/container-page.jpg) + +## 弹窗 + +![container-dialog.jpg](./containers/container-dialog.jpg) + +## 抽屉 + +![container-drawer.jpg](./containers/container-drawer.jpg) + +## 容器内支持标签页 + +在弹窗、抽屉、页面内,可以添加多个标签页。向每个标签页里添加不同的区块,从而显示不同的内容和操作。比如,在一个顾客信息的弹窗里,添加 3 个标签页,分别用来显示顾客的个人信息、订单记录、顾客评价: + +![7.tabs.gif](./containers/7.tabs.gif) + +![container-tab-2.jpg](./containers/container-tab-2.jpg) diff --git a/docs/zh-CN/manual/tabs/7.tabs.gif b/docs/zh-CN/manual/core-concepts/containers/7.tabs.gif similarity index 100% rename from docs/zh-CN/manual/tabs/7.tabs.gif rename to docs/zh-CN/manual/core-concepts/containers/7.tabs.gif diff --git a/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg b/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg new file mode 100755 index 000000000..b5984954d Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/containers/container-dialog.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg b/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg new file mode 100755 index 000000000..b550ea29e Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/containers/container-drawer.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-page.jpg b/docs/zh-CN/manual/core-concepts/containers/container-page.jpg new file mode 100755 index 000000000..c765777ea Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/containers/container-page.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg b/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg new file mode 100755 index 000000000..07b1a982b Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/containers/container-tab-2.jpg differ diff --git a/docs/zh-CN/manual/core-concepts/menus.md b/docs/zh-CN/manual/core-concepts/menus.md new file mode 100755 index 000000000..f8c14f25b --- /dev/null +++ b/docs/zh-CN/manual/core-concepts/menus.md @@ -0,0 +1,43 @@ +# 菜单 + +目前 NocoBase 支持三种类型的菜单项: + +- 页面:跳转至菜单关联的 NocoBase 的页面; +- 分组:对菜单进行分组,将同类菜单放到统一的位置; +- 链接:跳转至指定的 URL; + +以仓储系统为例,如果你的业务里有储位管理,储位管理里又包含出入库日志、库存查询、跳转 ERP 申请储位等功能。那么可以这样设置菜单: + +``` +- 储位管理(分组) + - 库存查询(页面) + - 出入库日志(页面) + - 跳转ERP申请储位(链接) +``` + +## 默认位置 + +在 NocoBase 内置的页面模板中,菜单会出现在顶部和左侧。 + +![menu-position.jpg](./menus/menu-position.jpg) + +## 添加 + +![5.menu-add.jpg](./menus/5.menu-add.jpg) + +点击 Add menu item,选择添加的类型。支持无限级子菜单。 + +## 配置和排序 + +将光标移到菜单项上,右上角会出现排序和配置按钮。按住排序按钮,可以拖拽排序。 + +对菜单项可操作的配置: + +- Edit +- Move to +- Insert before +- Insert after +- Insert Inner +- Delete + +![menu-move.gif](./menus/menu-move.gif) diff --git a/docs/zh-CN/manual/menus/5.menu-add.jpg b/docs/zh-CN/manual/core-concepts/menus/5.menu-add.jpg similarity index 100% rename from docs/zh-CN/manual/menus/5.menu-add.jpg rename to docs/zh-CN/manual/core-concepts/menus/5.menu-add.jpg diff --git a/docs/zh-CN/manual/core-concepts/menus/menu-move.gif b/docs/zh-CN/manual/core-concepts/menus/menu-move.gif new file mode 100755 index 000000000..1e278d868 Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/menus/menu-move.gif differ diff --git a/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg b/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg new file mode 100755 index 000000000..8e21a0a18 Binary files /dev/null and b/docs/zh-CN/manual/core-concepts/menus/menu-position.jpg differ diff --git a/docs/zh-CN/manual/file-storages.md b/docs/zh-CN/manual/file-storages.md deleted file mode 100644 index f26b9d2ef..000000000 --- a/docs/zh-CN/manual/file-storages.md +++ /dev/null @@ -1,11 +0,0 @@ -# 文件存储 - -NocoBase 文件存储目前支持以下三种方式 - -- Local storage -- Aliyun OSS -- Amazon S3 - -点击 File storage 进入配置界面,添加相应的信息即可。 - -![8.storage.gif](./file-storages/8.storage.gif) \ No newline at end of file diff --git a/docs/zh-CN/manual/file-storages/8.storage.gif b/docs/zh-CN/manual/file-storages/8.storage.gif deleted file mode 100755 index c9edfa8f9..000000000 Binary files a/docs/zh-CN/manual/file-storages/8.storage.gif and /dev/null differ diff --git a/docs/zh-CN/manual/language-settings.md b/docs/zh-CN/manual/language-settings.md deleted file mode 100644 index b895be85d..000000000 --- a/docs/zh-CN/manual/language-settings.md +++ /dev/null @@ -1,21 +0,0 @@ -# 语言设置 - -NocoBase 可以在安装时选择语言,目前支持两种语言: - -- 英文:`en-US`(默认) -- 中文:`zh-CN` - -```bash -# 英文 -yarn nocobase install --lang=en-US -# 中文 -yarn nocobase install --lang=zh-CN -``` - -安装之后,可以在 System settings 里修改 Enabled languages。 - -![](../images/language-settings-1.jpg) - -如果启用了多个语言,用户可以在个人中心设置自己的语言环境。 - -![](../images/language-settings-2.jpg) diff --git a/docs/zh-CN/manual/menus.md b/docs/zh-CN/manual/menus.md deleted file mode 100644 index d41aa14ef..000000000 --- a/docs/zh-CN/manual/menus.md +++ /dev/null @@ -1,32 +0,0 @@ -# 菜单 - -NocoBase 的默认菜单位置在顶部和左侧。顶部是一级菜单,左侧是二级及以下层级的菜单。 - -菜单项支持三种类型: - -- 菜单分组 -- 页面 -- 外部链接 - -进入界面配置模式之后,可以添加和编辑菜单,也可以对菜单项进行排序。 - -## 添加 - -![5.menu-add.jpg](./menus/5.menu-add.jpg) - -点击 Add menu item,选择添加的类型。支持无限级子菜单。 - -## 配置和排序 - -将光标移到菜单项上,右上角会出现排序和配置按钮。按住排序按钮,可以拖拽排序。 - -对菜单项可操作的配置: - -- Edit -- Move to -- Insert before -- Insert after -- Insert Inner -- Delete - -![5.menu-edit.jpg](./menus/5.menu-edit.jpg) \ No newline at end of file diff --git a/docs/zh-CN/manual/menus/5.menu-edit.jpg b/docs/zh-CN/manual/menus/5.menu-edit.jpg deleted file mode 100755 index 0252465f7..000000000 Binary files a/docs/zh-CN/manual/menus/5.menu-edit.jpg and /dev/null differ diff --git a/docs/zh-CN/manual/plugins.md b/docs/zh-CN/manual/plugins.md deleted file mode 100644 index f007e86c9..000000000 --- a/docs/zh-CN/manual/plugins.md +++ /dev/null @@ -1 +0,0 @@ -# 插件 diff --git a/docs/zh-CN/manual/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md b/docs/zh-CN/manual/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md deleted file mode 100755 index d9afb34e2..000000000 --- a/docs/zh-CN/manual/plugins/Workflow 7229c53cffc8429dbf7acb58cac90a76.md +++ /dev/null @@ -1,3 +0,0 @@ -# Workflow - -TO DO \ No newline at end of file diff --git a/docs/zh-CN/manual/functional-zoning.md b/docs/zh-CN/manual/quick-start/functional-zoning.md old mode 100644 new mode 100755 similarity index 78% rename from docs/zh-CN/manual/functional-zoning.md rename to docs/zh-CN/manual/quick-start/functional-zoning.md index d9902ee1e..b6846a853 --- a/docs/zh-CN/manual/functional-zoning.md +++ b/docs/zh-CN/manual/quick-start/functional-zoning.md @@ -2,7 +2,7 @@ NocoBase 默认内置一个布局模板,这个布局模板的界面主要分为三个区域: -1. 配置入口区。具备系统配置权限的用户,可以在这里看到界面配置、数据表配置、角色和权限、区块模板、工作流,以及其他扩展的配置选项。 +1. 配置入口区。具备系统配置权限的用户,可以在这里看到界面配置、插件管理器、设置中心的入口。 2. 菜单区。顶部是一级菜单,左侧是二级及以下层级的菜单。每个菜单项都可以配置为菜单分组、页面、外部链接。 3. 区块容器。这里是页面的区块容器,在里面可以放置各种各样的区块。 diff --git a/docs/zh-CN/manual/functional-zoning/3.zone.jpg b/docs/zh-CN/manual/quick-start/functional-zoning/3.zone.jpg similarity index 100% rename from docs/zh-CN/manual/functional-zoning/3.zone.jpg rename to docs/zh-CN/manual/quick-start/functional-zoning/3.zone.jpg diff --git a/docs/zh-CN/manual/quick-start/plugins.md b/docs/zh-CN/manual/quick-start/plugins.md new file mode 100755 index 000000000..2c5569dbc --- /dev/null +++ b/docs/zh-CN/manual/quick-start/plugins.md @@ -0,0 +1,13 @@ +# 插件 + +NocoBase 采用微内核架构,各类功能以插件形式扩展。 + +理论上来说,即使是 NocoBase 内置的功能,同样可以通过新的插件来替换。 + +## 插件管理器 + +NocoBase 提供了可视化的插件管理器,可以启用、禁用插件,或者对插件进行配置。 + +插件管理器还处于早期阶段,未来希望我们可以在这里找到各种各样的插件来满足需求。 + +![plugin-manager.jpg](./plugins/plugin-manager.jpg) diff --git a/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg b/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg new file mode 100755 index 000000000..ff17fe5e1 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/plugins/plugin-manager.jpg differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app.md b/docs/zh-CN/manual/quick-start/the-first-app.md new file mode 100755 index 000000000..f3b0e16c2 --- /dev/null +++ b/docs/zh-CN/manual/quick-start/the-first-app.md @@ -0,0 +1,105 @@ +# 第一个 APP + +让我们用 NocoBase 搭建一个订单管理系统。 + +## 1. 创建数据表和字段 + +在这个订单管理系统中,我们需要掌握`Customers`、`Products`、`Orders`的信息,他们彼此之间互相关联。经过分析,我们需要建立 4 个数据表,它们的字段分别为: + +- Customers + - 姓名 + - 地址 + - 性别 + - 电话 + - *订单(购买过的所有订单,数据来自`Orders`,每条顾客数据包含多条订单数据)* +- Products + - 商品名称 + - 描述 + - 图片 + - 价格 +- Orders + - 订单编号 + - 总价 + - 备注 + - 地址 + - *顾客(该订单所属的顾客,数据来自`Customers`,每条订单数据属于一条顾客数据)* + - *订单明细(该订单中的商品,数据来自`Order Details`,每条订单数据包含多条订单明细数据)* +- Order list + - *订单(该明细所属的订单,数据来自`Orders`,每条订单明细数据属于一条订单数据)* + - *商品(该明细所包含的商品,数据来自`Products`,每条订单明细数据包含一条商品数据)* + - 数量 + +其中,斜体的字段是关系字段,关联到其他数据表。 + +接下来,点击“数据表配置”按钮,进入数据表配置界面,创建第一个 Collection `Customers`。 + +![create-collection.gif](./the-first-app/create-collection.gif) + +然后点击“字段配置”,为`Customers` 添加 name 字段,它是单行文本类型。 + +![create-field.gif](./the-first-app/create-field.gif) + +用同样的方法,为`Customers` 添加 Address、Gender、Phone,它们分别是单行文本、单项选择类型、手机号码类型。 + +![fields-list.jpg](./the-first-app/fields-list.jpg) + +用同样的方法,创建 Collection `Products`、`Orders`、`Order list` 以及它们的字段。 + + + +![collection-list.jpg](./the-first-app/collection-list.jpg) + +其中,对于关系字段,如果你不熟悉一对多、多对多等概念,可以直接使用 Link to 类型来建立数据表之间的关联。如果你熟悉这几个概念,请正确使用 One to many, Many to one 等类型来建立数据表之间的关联。比如在这个例子中,我们将 `Orders` 与 `Order list` 关联,关系为 One to many。 + +![collection-list.jpg](./the-first-app/order-list-relation.jpg) + + +在图形化的数据表里,可以很直观的看出各个表之间的关系。(注:Graph-collection 插件暂未开源) + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 + +将数据表和字段创建完成后,我们开始制作界面。 + +## 2. 配置菜单和页面 + +我们需要顾客、订单、商品三个页面展示和管理我们的数据。 + +点击界面配置按钮,进入界面配置模式。在界面配置模式下,我们可以添加菜单项,添加页面,在页面内布置区块。 + +![1.editor.gif](./the-first-app/1.editor.gif) + +点击添加菜单项,添加菜单分组 “Customers” 和 “Orders & Products” ,然后添加子菜单页面 “All Orders” 和 “Products”。 + +![1.menu.gif](./the-first-app/1.menu.gif) + +添加完菜单和页面之后,我们可以在页面内添加和配置区块了。 + +## 3. 添加和配置区块 + +NocoBase 目前支持表格、看板、日历、表单、详情等类型的区块,它们可以将数据表中的数据展示出来,并可以对数据进行操作。显然,顾客、订单、商品 都适合用表格的方式展示和操作。 + +我们在“所有订单”页面,添加一个表格区块,数据源选择 Collection `Orders` ,并为这个表格区块配置需要显示的列。 + +![1.block.gif](./the-first-app/1.block.gif) + +给这个表格区块配置操作,包括筛选、添加、删除、查看、编辑。 + +![1.action.gif](./the-first-app/1.action.gif) + +为新增、编辑、查看等操作配置表单和详情区块。 + +![1.action-block.gif](./the-first-app/1.action-block.gif) + +然后,用同样的方法,在 Products 和 Customers 页面布置表格区块。完成后,退出界面配置模式,进入使用模式,一个简单的订单管理系统就完成了。 + +![demo-finished.jpg](./the-first-app/demo-finished.jpg) diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.action-block.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.action-block.gif similarity index 100% rename from docs/zh-CN/welcome/introduction/quickstart/1.action-block.gif rename to docs/zh-CN/manual/quick-start/the-first-app/1.action-block.gif diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.action.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.action.gif similarity index 100% rename from docs/zh-CN/welcome/introduction/quickstart/1.action.gif rename to docs/zh-CN/manual/quick-start/the-first-app/1.action.gif diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.block.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.block.gif similarity index 100% rename from docs/zh-CN/welcome/introduction/quickstart/1.block.gif rename to docs/zh-CN/manual/quick-start/the-first-app/1.block.gif diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.editor.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.editor.gif similarity index 100% rename from docs/zh-CN/welcome/introduction/quickstart/1.editor.gif rename to docs/zh-CN/manual/quick-start/the-first-app/1.editor.gif diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.menu.gif b/docs/zh-CN/manual/quick-start/the-first-app/1.menu.gif similarity index 100% rename from docs/zh-CN/welcome/introduction/quickstart/1.menu.gif rename to docs/zh-CN/manual/quick-start/the-first-app/1.menu.gif diff --git a/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg b/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg new file mode 100755 index 000000000..b291ed3b8 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/collection-list.jpg differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif b/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif new file mode 100755 index 000000000..d8eae060c Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/create-collection.gif differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif b/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif new file mode 100755 index 000000000..ba3f95278 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/create-field.gif differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg b/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg new file mode 100755 index 000000000..b880c89de Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/demo-finished.jpg differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg b/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg new file mode 100755 index 000000000..13a382880 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/fields-list.jpg differ diff --git a/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg b/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg new file mode 100644 index 000000000..64c02fdd2 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/the-first-app/order-list-relation.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode.md b/docs/zh-CN/manual/quick-start/ui-editor-mode.md new file mode 100755 index 000000000..56a777db7 --- /dev/null +++ b/docs/zh-CN/manual/quick-start/ui-editor-mode.md @@ -0,0 +1,51 @@ +# 界面配置模式 + +NocoBase 采用所见即所得的方式来配置界面。点击右上角的`UI Editor`按钮,即可切换配置模式和使用模式。进入配置模式之后,界面上各处将会出现橙色的配置入口。 + +![ui-editor.gif](./ui-editor-mode/ui-editor.gif) + +通常,配置项入口会出现在元素的右上角。 + +## 菜单项配置 + +将鼠标移到菜单项上,在右上角即可以看到拖拽排序按钮、配置项按钮,可以编辑、移动、插入、删除。 + +![menu-config.jpg](./ui-editor-mode/menu-config.jpg) + +## 区块配置 + +将鼠标移到一个区块上,在右上角即可以看到拖拽排序按钮、新增区块按钮、配置项按钮。 + +![block-config.jpg](./ui-editor-mode/block-config.jpg) + +不同的区块还会有一些自己独有的配置项。比如表格区块,将鼠标移到表头上即可以在右上角看到表头的配置项;在表头最右侧还可以看到表格列的配置项。 + +![block-config-2.jpg](./ui-editor-mode/block-config-2.jpg) + +![block-config-3.jpg](./ui-editor-mode/block-config-3.jpg) + +## 操作配置 + +在区块中可以看到操作的配置入口,这些入口在不同的区块里会出现在不同的位置。 + +比如表格区块,在右上方可以看到针对表格数据的操作: + +![action-config-1.jpg](./ui-editor-mode/action-config-1.jpg) + +在操作列的表头里可以看到针对单行数据的操作: + +![action-config-2.jpg](./ui-editor-mode/action-config-2.jpg) + +在详情区块的右上角可以看到针对详情的操作: + +![action-config-3.jpg](./ui-editor-mode/action-config-3.jpg) + +在表单区块底部可以看到针对表单的操作: + +![action-config-4.jpg](./ui-editor-mode/action-config-4.jpg) + +## 标签页配置 + +在弹窗或抽屉里,可以添加多个标签页,用于承载不同的区块。 + +![tab-config.jpg](./ui-editor-mode/tab-config.jpg) diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg new file mode 100755 index 000000000..c31a20f7a Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-1.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg new file mode 100755 index 000000000..22b14119f Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-2.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg new file mode 100755 index 000000000..59e5a2527 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-3.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg new file mode 100755 index 000000000..f799395bf Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/action-config-4.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg new file mode 100755 index 000000000..0e041e35e Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-2.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg new file mode 100755 index 000000000..0b1244cc0 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config-3.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg new file mode 100755 index 000000000..e1a94876c Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/block-config.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg new file mode 100755 index 000000000..8765202f0 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/menu-config.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg b/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg new file mode 100755 index 000000000..ac46ee284 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/tab-config.jpg differ diff --git a/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif b/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif new file mode 100755 index 000000000..0ea3bf781 Binary files /dev/null and b/docs/zh-CN/manual/quick-start/ui-editor-mode/ui-editor.gif differ diff --git a/docs/zh-CN/manual/roles-permissions.md b/docs/zh-CN/manual/roles-permissions.md deleted file mode 100644 index c64e82982..000000000 --- a/docs/zh-CN/manual/roles-permissions.md +++ /dev/null @@ -1,3 +0,0 @@ -# 角色和权限 - -TO DO \ No newline at end of file diff --git a/docs/zh-CN/manual/system-settings.md b/docs/zh-CN/manual/system-settings.md deleted file mode 100644 index a4b550cab..000000000 --- a/docs/zh-CN/manual/system-settings.md +++ /dev/null @@ -1,10 +0,0 @@ -# 系统配置 - -点击 System settings 进入系统配置,可以配置的属性包括: - -- System title -- Logo -- Language -- Allow sign up - -![9.system.gif](./system-settings/9.system.gif) \ No newline at end of file diff --git a/docs/zh-CN/manual/system-settings/9.system.gif b/docs/zh-CN/manual/system-settings/9.system.gif deleted file mode 100755 index e1d26b471..000000000 Binary files a/docs/zh-CN/manual/system-settings/9.system.gif and /dev/null differ diff --git a/docs/zh-CN/manual/tabs.md b/docs/zh-CN/manual/tabs.md deleted file mode 100644 index 57da5f6be..000000000 --- a/docs/zh-CN/manual/tabs.md +++ /dev/null @@ -1,9 +0,0 @@ -# 标签页 - -在单条数据的页面或弹窗里,可以添加多个标签页,向每个标签页里添加不同的区块,从而显示不同的内容和操作。比如,在一个顾客信息的弹窗里,添加 3 个标签页,分别用来显示顾客的个人信息、订单记录、顾客评价: - -![7.tabs.gif](./tabs/7.tabs.gif) - -或者在一条待发货的订单记录里,在第 1 个标签页里放置操作发货的表单区块,实现快捷发货;第 2 个标签页放置关联数据区块,用来显示当前订单的订单商品;第 3 个标签页放置订单详情区块: - -![7.tabs-2.gif](./tabs/7.tabs-2.gif) diff --git a/docs/zh-CN/manual/tabs/7.tabs-2.gif b/docs/zh-CN/manual/tabs/7.tabs-2.gif deleted file mode 100755 index 3699ad9e7..000000000 Binary files a/docs/zh-CN/manual/tabs/7.tabs-2.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/community/contributing.md b/docs/zh-CN/welcome/community/contributing.md index 0c3e240be..c34b7d6dc 100644 --- a/docs/zh-CN/welcome/community/contributing.md +++ b/docs/zh-CN/welcome/community/contributing.md @@ -3,6 +3,7 @@ - Fork 源代码到自己的仓库 - 修改源代码 - 提交 Pull Request +- 签署 CLA ## 下载项目 @@ -44,4 +45,4 @@ yarn doc --lang=en-US ## 其他 -更多 Commands 使用说明 [参考 NocoBase CLI 章节](./development/nocobase-cli.md) \ No newline at end of file +更多 Commands 使用说明 [参考 NocoBase CLI 章节](./development/nocobase-cli.md) diff --git a/docs/zh-CN/welcome/community/translations.md b/docs/zh-CN/welcome/community/translations.md index 93e4508a8..f86540a3e 100644 --- a/docs/zh-CN/welcome/community/translations.md +++ b/docs/zh-CN/welcome/community/translations.md @@ -1,6 +1,6 @@ # 翻译 -NocoBase 默认语言是英语,目前支持英语、简体中文。你可以帮助 NocoBase 翻译成你的语言。 +NocoBase 默认语言是英语,目前支持英语、简体中文、日语、俄语、土耳其语。你可以帮助 NocoBase 翻译成你的语言。 NocoBase 语言文件在以下位置: @@ -15,7 +15,7 @@ https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/locale 请复制 en_US.ts,命名为想要新增的语言的名字,然后对其中的字符串进行翻译。翻译完成之后,请通过 pull request 提交给 NocoBase,我们将会把它加入语言列表。之后你将在系统配置中看到新增的语言,在这里你可以配置需要显示哪些语言供用户选择。 - + 下面的表格中列出了 Language Culture Name, Locale File Name, Display Name. diff --git a/docs/zh-CN/images/enabled-languages.jpg b/docs/zh-CN/welcome/community/translations/enabled-languages.jpg similarity index 100% rename from docs/zh-CN/images/enabled-languages.jpg rename to docs/zh-CN/welcome/community/translations/enabled-languages.jpg diff --git a/docs/zh-CN/welcome/community/translations/language-settings-1.jpg b/docs/zh-CN/welcome/community/translations/language-settings-1.jpg new file mode 100644 index 000000000..eb352a0f8 Binary files /dev/null and b/docs/zh-CN/welcome/community/translations/language-settings-1.jpg differ diff --git a/docs/zh-CN/welcome/community/translations/language-settings-2.jpg b/docs/zh-CN/welcome/community/translations/language-settings-2.jpg new file mode 100644 index 000000000..3e9f7b08e Binary files /dev/null and b/docs/zh-CN/welcome/community/translations/language-settings-2.jpg differ diff --git a/docs/zh-CN/welcome/getting-started/installation/docker-compose.md b/docs/zh-CN/welcome/getting-started/installation/docker-compose.md index 8cc6ceb4f..71aad67fd 100644 --- a/docs/zh-CN/welcome/getting-started/installation/docker-compose.md +++ b/docs/zh-CN/welcome/getting-started/installation/docker-compose.md @@ -1,10 +1,3 @@ ---- -group: - path: /getting-started/installation/docker-compose - title: Docker 安装 (👍 推荐) - order: 1 ---- - # Docker 安装 ## 0. 先决条件 diff --git a/docs/zh-CN/welcome/getting-started/upgrading.md b/docs/zh-CN/welcome/getting-started/upgrading.md deleted file mode 100644 index 8575a8eb3..000000000 --- a/docs/zh-CN/welcome/getting-started/upgrading.md +++ /dev/null @@ -1,64 +0,0 @@ -# 升级 - - - -此篇升级文档只适用于 v0.7.0-alpha.57 之后的版本,在此之前创建的项目需要重新创建。 - - - -升级前请务必将数据库数据进行备份 - -## Docker - -切换到对应的目录 - -```bash -# SQLite -cd nocobase/docker/app-sqlite -# MySQL -cd nocobase/docker/app-mysql -# PostgreSQL -cd nocobase/docker/app-postgres -``` - -`docker-compose.yml` 文件,app 容器的 image 替换为最新版本 - -```yml -services: - app: - image: nocobase/nocobase:0.7.0-alpha.62 -``` - -下载最新镜像并启动容器 - -```bash -docker-compose up -d app -# 查看 app 进程的情况 -docker-compose logs app -``` - -## create-nocobase-app - -执行 `nocobase upgrade` 升级命令 - -```bash -# 切换到对应的目录 -cd my-nocobase-app -# 执行更新命令 -yarn nocobase upgrade -# 启动 -yarn start -``` - -## Git 源码 - -```bash -# 切换到对应的目录 -cd my-nocobase-app -# pull 最新代码 -git pull -# 执行更新命令 -yarn nocobase upgrade -# 启动 -yarn start -``` diff --git a/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md b/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md new file mode 100644 index 000000000..2ebf3b97a --- /dev/null +++ b/docs/zh-CN/welcome/getting-started/upgrading/create-nocobase-app.md @@ -0,0 +1,75 @@ +# `create-nocobase-app` 安装的升级 + +## 小版本升级 + +执行 `nocobase upgrade` 升级命令即可 + +```bash +# 切换到对应的目录 +cd my-nocobase-app +# 执行更新命令 +yarn nocobase upgrade +# 启动 +yarn dev +``` + +## 大版本升级 + +如果小版本升级失效,也可以采用此升级办法。 + +### 1. 创建新的 NocoBase 项目 + +```bash +# SQLite +yarn create nocobase-app my-nocobase-app -d sqlite +# MySQL +yarn create nocobase-app my-nocobase-app -d mysql +# PostgreSQL +yarn create nocobase-app my-nocobase-app -d postgres +``` + +### 2. 切换目录 + +```bash +cd my-nocobase-app +``` + +### 3. 安装依赖 + +📢 由于网络环境、系统配置等因素影响,接下来这一步骤可能需要十几分钟时间。 + +```bash +yarn install +``` + +### 4. 修改 .env 配置 + +参考旧版本的 .env 修改,数据库信息需要配置正确。SQLite 数据库也需要将数据库文件复制到 `./storage/db/` 目录。 + +### 5. 旧代码迁移(非必须) + +业务代码参考新版插件开发教程和 API 参考进行修改。 + +### 6. 执行升级命令 + +代码已经是最新版了,所以 upgrade 时需要跳过代码更新 `--skip-code-update`。 + +```bash +yarn nocobase upgrade --skip-code-update +``` + +### 7. 启动 NocoBase + +开发环境 + +```bash +yarn dev +``` + +生产环境 + +```bash +yarn start # 暂不支持在 win 平台下运行 +``` + +注:生产环境,如果代码有修改,需要执行 `yarn build`,再重新启动 NocoBase。 diff --git a/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md b/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md new file mode 100644 index 000000000..8140020af --- /dev/null +++ b/docs/zh-CN/welcome/getting-started/upgrading/docker-compose.md @@ -0,0 +1,60 @@ +# Docker 安装的升级 + + + +本篇文档所讲的 Docker 安装是基于 `docker-compose.yml` 配置文件,在 [NocoBase GitHub 仓库](https://github.com/nocobase/nocobase/tree/main/docker) 里也有提供。 + + + +## 1. 切换到之前安装时的目录 + +也可以根据实际情况,切换到 `docker-compose.yml` 所在的目录 + +```bash +# SQLite +cd nocobase/docker/app-sqlite +# MySQL +cd nocobase/docker/app-mysql +# PostgreSQL +cd nocobase/docker/app-postgres +``` + +## 2. 更新 image 版本号 + +`docker-compose.yml` 文件,app 容器的 image 替换为最新版本 + +```yml +services: + app: + image: nocobase/nocobase:0.8.0-alpha.1 +``` + +## 3. 删除旧镜像(非必须) + +如果使用的是 latest 镜像,需要先停止并删除相对应容器 + +```bash +# find container ID +docker ps +# stop container +docker stop +# delete container +docker rm +``` + +删除掉旧镜像 + +```bash +# find image +docker images +# delete image +docker rmi +``` + +## 4. 重启容器 + +```bash +docker-compose up -d app +# 查看 app 进程的情况 +docker-compose logs app +``` diff --git a/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md b/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md new file mode 100644 index 000000000..efa0a8506 --- /dev/null +++ b/docs/zh-CN/welcome/getting-started/upgrading/git-clone.md @@ -0,0 +1,43 @@ +# Git 源码安装的升级 + +## 1. 切换到 NocoBase 项目目录 + +```bash +cd my-nocobase-app +``` + +## 2. 拉取最新代码 + +```bash +git pull +``` + +## 3. 更新依赖 + +``` +yarn install +``` + +## 4. 执行更新命令 + +```bash +yarn nocobase upgrade +``` + +## 5. 启动 NocoBase + +开发环境 + +```bash +yarn dev +``` + +生产环境 + +```bash +# 编译 +yarn build +# 启动 +yarn start # 暂不支持在 win 平台下运行 +``` + diff --git a/docs/zh-CN/welcome/getting-started/upgrading/index.md b/docs/zh-CN/welcome/getting-started/upgrading/index.md new file mode 100644 index 000000000..c71ad7416 --- /dev/null +++ b/docs/zh-CN/welcome/getting-started/upgrading/index.md @@ -0,0 +1,7 @@ +# 升级概述 + +NocoBase 支持三种安装方式,升级时略有不同。 + +- [Docker 安装的升级](./upgrading/docker-compose.md) +- [create-nocobase-app 安装的升级](./upgrading/create-nocobase-app.md) +- [Git 源码安装的升级](./upgrading/git-clone.md) diff --git a/docs/zh-CN/welcome/introduction/features.md b/docs/zh-CN/welcome/introduction/features.md index 9c7b194ae..508529f56 100644 --- a/docs/zh-CN/welcome/introduction/features.md +++ b/docs/zh-CN/welcome/introduction/features.md @@ -1,4 +1,4 @@ -# 功能特色 +# 与众不同之处 ## 1. “数据结构”与“使用界面”分离 @@ -8,8 +8,8 @@ NocoBase 采用数据结构与使用界面分离的设计思路,可以为数 ![2.collection-block.png](./features/2.collection-block.png) -## 2. “配置”与“使用”融为一体 -NocoBase 可以开发复杂和有特色的业务系统,但这并意味着需要复杂和专业的操作。只需一次点击,就可以在使用界面上显示出配置选项,这意味着具备系统配置权限的管理员可以用所见即所得的操作方式,直接配置用户的使用界面。 +## 2. 所见即所得 +NocoBase 可以开发复杂和有特色的业务系统,但这并意味着需要复杂和专业的操作。只需一次点击,就可以在使用界面上显示出配置选项,这意味着具备系统配置权限的管理员可以用所见即所得的方式,直接配置用户的使用界面。 ![2.user-root.gif](./features/2.user-root.gif) diff --git a/docs/zh-CN/welcome/introduction/index.md b/docs/zh-CN/welcome/introduction/index.md index 1bcd92e73..dd646c809 100644 --- a/docs/zh-CN/welcome/introduction/index.md +++ b/docs/zh-CN/welcome/introduction/index.md @@ -9,7 +9,7 @@ NocoBase 正处在早期开发阶段,可能变动频繁,请谨慎用于生 ## NocoBase 是什么 NocoBase 是一个极易扩展的开源无代码开发平台。 -无需编程,使用 NocoBase 搭建自己的协作平台、管理系统,只需要几分钟时间。 +无需编程,使用 NocoBase 搭建自己的协作平台、管理系统,只需要数小时时间。 官网:https://cn.nocobase.com/ @@ -17,25 +17,24 @@ NocoBase 是一个极易扩展的开源无代码开发平台。 ## 为什么选择 NocoBase -- **开源免费** +- **开源,自主可控** - 采用 Apache-2.0 许可协议,不限制商业使用 - 拥有全部代码,私有化部署,保障数据私有和安全 - 针对实际需求自由扩展开发 - 具备良好的生态支持 - **无代码能力强** - 数据模型 - - 使用文本、日期、数字、附件、选项、图标等数十种字段类型,以及一对一、一对多、多对多等各种关联关系,创建独立的数据模型 + - 使用文本、数字、附件等数十种字段类型,以及一对多、多对多等各种关联关系,创建独立的数据模型 - 区块 - - 使用表格、表单、看板、日历、详情等区块类型在页面内自由组合,来展示和操作数据 + - 使用表格、表单、看板、日历、详情等区块类型在页面内自由组合,来展示和交互数据 + - 操作 + - 支持筛选、导出、添加、删除、修改、查看等操作对数据进行处理,可以扩展更多类型 - 权限 - 基于角色控制用户的系统配置权限、数据操作权限和菜单访问权限 - 工作流 - - 重复性的任务由自动化代替,减少人工操作, 提高效率。重要的事情需经过人工审批。 - - 菜单 - - 可以对菜单分组,支持添加页面和链接,支持无限级子菜单 - - 操作 - - 支持筛选、导出、添加、删除、修改、查看等操作对数据进行处理,可以扩展更多类型 -- **对开发者友好** + - 重复性的任务由自动化代替,减少人工操作, 提高效率。 + +- **扩展能力强** - 微内核,灵活易扩展,具备健全的插件体系 - 基于 Node.js,使用主流框架和技术,包括 Koa、Sequelize、React、Formily、Ant Design 等 - 渐进式开发,上手难度低,对新人友好 diff --git a/docs/zh-CN/welcome/introduction/quickstart.md b/docs/zh-CN/welcome/introduction/quickstart.md deleted file mode 100644 index 559eaff19..000000000 --- a/docs/zh-CN/welcome/introduction/quickstart.md +++ /dev/null @@ -1,90 +0,0 @@ -# 5 分钟上手 - -让我们花 5 分钟时间用 NocoBase 搭建一个订单管理系统。 - -## 1. 创建数据表和字段 - -在这个订单管理系统中,我们需要掌握`Customers`、`Products`、`Orders`的信息,他们彼此之间互相关联。经过分析,我们需要建立 4 个数据表,它们的字段分别为: - -- Customers - - 姓名 - - 生日 - - 性别 - - 电话 -- Products - - 名称 - - 描述 - - 图片 - - 价格 -- Orders - - 订单编号 - - 总价 - - 备注 - - 地址 - - *顾客*(该订单所属的顾客,与`Customers`建立关联,是 **多对一** 关系。每个订单属于一个顾客,一个顾客可能有个订单) - - *订单明细*(该订单中的商品及数量,与`Order List`建立关联,是 **一对多** 关系。每个订单包含多条订单明细,每条订单明细只属于一个订单) -- Order List - - *商品*(该明细所包含的商品,与`Products`建立关联,是 **多对一** 关系。每条订单明细包含一个商品,每个商品可能属于多个订单明细) - - 数量 - -其中,斜体的字段是关系字段,关联到其他数据表。 - -接下来,点击“数据表配置”按钮,进入数据表配置界面,创建第一个 Collection `Customers`。 - -![1.customers.gif](./quickstart/1.customers.gif) - -然后点击“字段配置”,为`Customers` 添加 name 字段,它是单行文本类型。 - -![2.field.gif](./quickstart/2.field.gif) - -用同样的方法,为`Customers` 添加 Birthday、Gender、Phone,它们分别是日期类型、单项选择类型、手机号码类型。 - -![1.fields.jpg](./quickstart/1.fields.jpg) - -用同样的方法,创建 Collection `Products`、`Orders`、`Order List` 以及它们的字段。 - -![1.collections.jpg](./quickstart/1.collections.jpg) - - 其中,对于关系字段,我们要选择正确的类型,从而建立数据表之间的关联。我们以`Orders`为例,创建 Customer 字段,选择 **多对一** 关系,关联到`Customers`。 - -![1.relation.jpg](./quickstart/1.relation.jpg) - -创建关系字段后,我们可以在被关联的 Collection 里看到自动生成的反向关联字段。比如在`Customers`中看到自动生成的 Orders 字段,这样我们在`Customers`的区块里可以调用`Orders`的数据。 - -![1.auto.relation.jpg](./quickstart/1.auto.relation.jpg) - -将数据表和字段创建完成后,我们开始制作界面。 - -## 2. 配置菜单和页面 - -我们需要顾客、订单、商品三个页面展示和管理我们的数据。 - -点击界面配置按钮,进入界面配置模式。在界面配置模式下,我们可以添加菜单项,添加页面,在页面内布置区块。 - -![1.editor.gif](./quickstart/1.editor.gif) - -点击添加菜单项,添加菜单分组 “Customers” 和 “Orders & Products” ,然后添加子菜单页面 “All Orders” 和 “Products”。 - -![1.menu.gif](./quickstart/1.menu.gif) - -添加完菜单和页面之后,我们可以在页面内添加和配置区块了。 - -## 3. 添加和配置区块 - -NocoBase 目前支持表格、看板、日历、表单、详情等类型的区块,它们可以将数据表中的数据展示出来,并可以对数据进行操作。显然,顾客、订单、商品 都适合用表格的方式展示和操作。 - -我们在“所有订单”页面,添加一个表格区块,数据源选择 Collection `Orders` ,并为这个表格区块配置需要显示的列。 - -![1.block.gif](./quickstart/1.block.gif) - -给这个表格区块配置操作,包括筛选、添加、删除、查看、编辑。 - -![1.action.gif](./quickstart/1.action.gif) - -为新增、编辑、查看等操作配置表单和详情区块。 - -![1.action-block.gif](./quickstart/1.action-block.gif) - -然后,用同样的方法,在 Products 和 Customers 页面布置表格区块。完成后,退出界面配置模式,进入使用模式,一个简单的订单管理系统就完成了。 - -![1.finished.gif](./quickstart/1.finished.gif) diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.auto.relation.jpg b/docs/zh-CN/welcome/introduction/quickstart/1.auto.relation.jpg deleted file mode 100644 index c445d50a7..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.auto.relation.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.collections.jpg b/docs/zh-CN/welcome/introduction/quickstart/1.collections.jpg deleted file mode 100755 index e916ec514..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.collections.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif b/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif deleted file mode 100755 index 9cdcb735d..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif.gif b/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif.gif deleted file mode 100644 index 9cdcb735d..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.customers.gif.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.fields.jpg b/docs/zh-CN/welcome/introduction/quickstart/1.fields.jpg deleted file mode 100755 index 529fc6b5c..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.fields.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.finished.gif b/docs/zh-CN/welcome/introduction/quickstart/1.finished.gif deleted file mode 100755 index be301d4ea..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.finished.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/1.relation.jpg b/docs/zh-CN/welcome/introduction/quickstart/1.relation.jpg deleted file mode 100755 index 4c9ee076e..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/1.relation.jpg and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/2.field.gif b/docs/zh-CN/welcome/introduction/quickstart/2.field.gif deleted file mode 100755 index 65ba382c8..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/2.field.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/quickstart/2.field.gif.gif b/docs/zh-CN/welcome/introduction/quickstart/2.field.gif.gif deleted file mode 100644 index 65ba382c8..000000000 Binary files a/docs/zh-CN/welcome/introduction/quickstart/2.field.gif.gif and /dev/null differ diff --git a/docs/zh-CN/welcome/introduction/roadmap.md b/docs/zh-CN/welcome/introduction/roadmap.md deleted file mode 100644 index dee239007..000000000 --- a/docs/zh-CN/welcome/introduction/roadmap.md +++ /dev/null @@ -1,29 +0,0 @@ -# 路线图 - -## 正在迭代 - -- `core` 角色和权限 -- `core` 升级和迁移 -- `plugin` 工作流 -- `doc` 开发文档 -- `plugin` 数据可视化(低代码) - -## 正在开发 - -- `core` 插件管理器 -- `ui` 移动端响应 -- `plugin` 聚合计算字段 -- `core` 字段默认值 -- `plugin` 表单验证 - -## 准备开发 - -- `plugin` 连接第三方数据库 - -## 未来开发 - -- `plugin` 审批 -- `plugin` 全文搜索 -- `plugin` 分享页面 -- `plugin` 数据可视化(无代码) -- `plugin` Open API diff --git a/docs/zh-CN/welcome/introduction/when.md b/docs/zh-CN/welcome/introduction/when.md index d0dc1ae96..881e94de8 100644 --- a/docs/zh-CN/welcome/introduction/when.md +++ b/docs/zh-CN/welcome/introduction/when.md @@ -3,8 +3,8 @@ 如果你同时有以下需求,NocoBase 就是为你设计的: - 开发组织内部管理系统 -- 在很短时间内通过无代码的方式完成 90% 的系统开发 -- 自由扩展开发,满足剩余 10% 的个性化需求部分 -- 系统功能需要频繁变动 +- 通过无代码开发,满足大部分业务需求 +- 无代码开发在操作上足够简单,满足非开发人员;在功能上足够灵活,接近原生开发 +- 可以非常方便的进行扩展开发 - 私有部署,掌控全部代码和数据 - 可免费使用,也可以付费获得更多技术支持 diff --git a/packages/core/cli/src/commands/upgrade.js b/packages/core/cli/src/commands/upgrade.js index b4cf9f336..08895779b 100644 --- a/packages/core/cli/src/commands/upgrade.js +++ b/packages/core/cli/src/commands/upgrade.js @@ -15,19 +15,19 @@ module.exports = (cli) => { .option('--raw') .option('-S|--skip-code-update') .action(async (options) => { + if (!hasTsNode()) { + return; + } promptForTs(); + if (hasCorePackages()) { + // await run('yarn', ['install']); + await runAppCommand('upgrade'); + return; + } if (options.skipCodeUpdate) { await runAppCommand('upgrade'); return; } - if (hasCorePackages()) { - await run('yarn', ['install']); - await runAppCommand('upgrade'); - return; - } - if (!hasTsNode()) { - return; - } const version = await getVersion(); await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']); const clientPackage = resolve(process.cwd(), `packages/${APP_PACKAGE_ROOT}/client/package.json`); diff --git a/packages/core/cli/src/plugin-generator.js b/packages/core/cli/src/plugin-generator.js index 7321e09ec..b47baaba5 100644 --- a/packages/core/cli/src/plugin-generator.js +++ b/packages/core/cli/src/plugin-generator.js @@ -34,11 +34,13 @@ class PluginGenerator extends Generator { async getContext() { const { name } = this.context; const packageName = await getProjectName(); + const nocobaseVersion = require('@nocobase/server/package.json').version; const packageVersion = await getProjectVersion(); return { ...this.context, packageName: `@${packageName}/plugin-${name}`, - packageVersion: packageVersion, + packageVersion, + nocobaseVersion, pascalCaseName: capitalize(camelize(name)), }; } diff --git a/packages/core/cli/templates/plugin/package.json.tpl b/packages/core/cli/templates/plugin/package.json.tpl index d56b34e2f..2eb499b1f 100644 --- a/packages/core/cli/templates/plugin/package.json.tpl +++ b/packages/core/cli/templates/plugin/package.json.tpl @@ -2,9 +2,8 @@ "name": "{{{packageName}}}", "version": "{{{packageVersion}}}", "main": "lib/server/index.js", - "dependencies": {}, - "peerDependencies": { - "@nocobase/server": "*", - "@nocobase/test": "*" + "devDependencies": { + "@nocobase/server": "{{{nocobaseVersion}}}", + "@nocobase/test": "{{{nocobaseVersion}}}" } } diff --git a/packages/core/cli/templates/plugin/src/server/index.ts.tpl b/packages/core/cli/templates/plugin/src/server/index.ts.tpl index 8635649e6..b68aea57f 100644 --- a/packages/core/cli/templates/plugin/src/server/index.ts.tpl +++ b/packages/core/cli/templates/plugin/src/server/index.ts.tpl @@ -1,29 +1 @@ -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class {{{pascalCaseName}}}Plugin extends Plugin { - - beforeLoad() { - // TODO - } - - async load() { - // TODO - // Visit: http://localhost:13000/api/test{{{pascalCaseName}}}:getInfo - this.app.resource({ - name: 'test{{{pascalCaseName}}}', - actions: { - async getInfo(ctx, next) { - ctx.body = `Hello {{{name}}}!`; - next(); - }, - }, - }); - this.app.acl.allow('test{{{pascalCaseName}}}', 'getInfo'); - } - - async install(options: InstallOptions) { - // TODO - } -} - -export default {{{pascalCaseName}}}Plugin; +export { default } from './plugin'; diff --git a/packages/core/cli/templates/plugin/src/server/models/.gitkeep b/packages/core/cli/templates/plugin/src/server/models/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/core/cli/templates/plugin/src/server/plugin.ts.tpl b/packages/core/cli/templates/plugin/src/server/plugin.ts.tpl new file mode 100644 index 000000000..aaae501bc --- /dev/null +++ b/packages/core/cli/templates/plugin/src/server/plugin.ts.tpl @@ -0,0 +1,19 @@ +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class {{{pascalCaseName}}}Plugin extends Plugin { + afterAdd() {} + + beforeLoad() {} + + async load() {} + + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default {{{pascalCaseName}}}Plugin; diff --git a/packages/core/cli/templates/plugin/src/server/repositories/.gitkeep b/packages/core/cli/templates/plugin/src/server/repositories/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/core/server/src/plugin-manager/PluginManager.ts b/packages/core/server/src/plugin-manager/PluginManager.ts index fe8ccfc54..62dfef9b3 100644 --- a/packages/core/server/src/plugin-manager/PluginManager.ts +++ b/packages/core/server/src/plugin-manager/PluginManager.ts @@ -97,6 +97,15 @@ export class PluginManager { clientWrite(data: any) { const { method, plugins } = data; + if (method === 'create') { + try { + console.log(method, plugins); + this[method](plugins); + } catch (error) { + console.error(error.message); + } + return; + } const client = new net.Socket(); client.connect(this.pmSock, () => { client.write(JSON.stringify(data)); @@ -123,17 +132,22 @@ export class PluginManager { }); } - async create(name: string) { + async create(name: string | string[]) { + console.log('creating...'); + const pluginNames = Array.isArray(name) ? name : [name]; const { run } = require('@nocobase/cli/src/util'); - const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator'); - const generator = new PluginGenerator({ - cwd: resolve(process.cwd(), name), - args: {}, - context: { - name, - }, - }); - await generator.run(); + const createPlugin = async (name) => { + const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator'); + const generator = new PluginGenerator({ + cwd: resolve(process.cwd(), name), + args: {}, + context: { + name, + }, + }); + await generator.run(); + }; + await Promise.all(pluginNames.map((pluginName) => createPlugin(pluginName))); await run('yarn', ['install']); }