fix: client docs (#2965)

* fix: application bug

* fix: test error

* feat: client doc

* fix: delete it.only

* fix: demos

* fix: ci

* fix: create index.md

---------

Co-authored-by: dream2023 <1098626505@qq.com>
This commit is contained in:
chenos 2023-11-06 11:50:13 +08:00 committed by GitHub
parent bafb03a228
commit 05040a1c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 125 additions and 566 deletions

View File

@ -0,0 +1,23 @@
name: deploy client docs
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
- run: yarn install
- name: Build
run: yarn doc build core/client
- name: copy files via ssh
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.CN_CLIENT_HOST }}
username: ${{ secrets.CN_CLIENT_USERNAME }}
key: ${{ secrets.CN_CLIENT_KEY }}
port: ${{ secrets.CN_CLIENT_PORT }}
source: "packages/core/client/dist/*"
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ github.ref_name }}

View File

@ -1,18 +1,6 @@
import { defineConfig } from 'dumi';
import { defineThemeConfig } from 'dumi-theme-nocobase'
import { getUmiConfig } from '@nocobase/devtools/umiConfig';
import fs from 'fs';
import path from 'path';
const contributingPath = path.resolve(__dirname, './contributing.md');
const docsContributingPath = path.resolve(__dirname, './docs/contributing.md');
// check if the target path already exists, and remove it if it does
if (fs.existsSync(docsContributingPath)) {
fs.unlinkSync(docsContributingPath);
}
fs.copyFileSync(contributingPath, docsContributingPath);
import { defineConfig } from 'dumi';
import { defineThemeConfig } from 'dumi-theme-nocobase';
const umiConfig = getUmiConfig();
@ -23,34 +11,106 @@ export default defineConfig({
},
resolve: {
atomDirs: [
{ type: 'component', dir: 'src' },
{ type: 'component', dir: 'src/schema-component/antd' },
{ type: 'component', dir: 'src/route-switch/antd' },
]
{ type: 'api', dir: 'src' },
{ type: 'api', dir: 'src/schema-component/antd' },
{ type: 'api', dir: 'src/route-switch/antd' },
],
},
themeConfig: defineThemeConfig({
title: 'NocoBase',
logo: 'https://www.nocobase.com/images/logo.png',
github: 'https://github.com/nocobase/nocobase',
footer: 'nocobase | Copyright © 2022',
sidebarGroupModePath: ['/components'],
// sidebarGroupModePath: ['/components'],
nav: [
{
title: 'Intro',
link: '/intro',
title: 'API',
link: '/apis/application',
},
{
title: 'Client',
link: '/components/acl',
},
{
title: 'Develop',
link: '/develop',
},
{
title: 'Contributing',
link: '/contributing',
}
]
})
],
sidebarEnhance: {
'/apis': [
{
title: 'Core',
type: 'group',
children: [
{
title: 'Application',
children: [
{
title: 'Application',
link: '/apis/application',
},
{
title: 'APIClient',
link: '/apis/api-client',
},
{
title: 'SettingsCenter',
link: '#',
},
],
},
{
title: 'UI schema designer',
children: [
{
title: 'SchemaComponent',
link: '#',
},
{
title: 'SchemaInitializer',
link: '#',
},
{
title: 'SchemaSettings',
link: '#',
},
{
title: 'DNDContext & DragHandler',
link: '#',
},
],
},
{
title: 'Collection Manager',
link: '#',
},
{
title: 'BlockProvider',
link: '#',
},
{
title: 'RecordProvider',
link: '#',
},
],
},
{
title: 'React components',
type: 'group',
children: [
{
title: 'Board',
link: '#',
},
{
title: 'Icon',
link: '#',
},
],
},
{
title: 'Schema components',
type: 'group',
children: [
{
title: 'Input',
link: '#',
},
],
},
],
},
}),
});

View File

@ -1,61 +0,0 @@
---
toc: menu
---
# 参与贡献
客户端的模块都在 `src` 目录下,各自模块独立。
## 客户端模块
最小单元模块化,一个完整的模块包含:
```bash
|- /__tests__/ # 测试文件目录
|- /demos/ # demo 目录
|- index.ts # 最好不要直接在 index.ts 文件里写代码index 文件只负责 export
|- index.md # 文档,默认为英文
|- index.zh-CN.md # 中文文档,可缺失,可以先都写到 index.md 里
```
## 测试
详细文档见 [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/)
```tsx | pure
import React from 'react';
import { render } from '@testing-library/react';
import { compose } from '../';
describe('compose', () => {
it('case 1', () => {
const A: React.FC = (props) => (
<div>
<h1>A</h1>
{props.children}
</div>
);
const App = compose(A)();
const { container } = render(<App />);
expect(container).toMatchSnapshot();
});
});
```
## Demo
组件 Demo 见 [dumi](https://d.umijs.org/guide/basic#write-component-demo),可以直接写在文档里,如:
<pre lang="markdown">
```jsx
import React from 'react';
export default () => <h1>Hello NocoBase!</h1>;
```
</pre>
也可以引用 demo 文件
```markdown
<code src="./demos/dmeo1.tsx"></code>
```

View File

@ -0,0 +1 @@
<Navigate replace to="/apis/application"></Navigate>

View File

@ -1,9 +0,0 @@
---
group:
title: Client
order: 1
---
# ACL
访问控制列表plugin-acl 的前端模块,详细文档待补充。

View File

@ -1,25 +0,0 @@
import React from 'react';
import { Table } from 'antd';
import { i18n, compose, APIClient, APIClientProvider, AntdConfigProvider } from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import { I18nextProvider } from 'react-i18next';
const apiClient = new APIClient({
baseURL: `${location.protocol}//${location.host}/api/`,
});
const mock = new MockAdapter(apiClient.axios);
mock.onGet('/app:getLang').reply(200, {
data: { lang: 'zh-CN' },
});
const providers = [
[APIClientProvider, { apiClient }],
[I18nextProvider, { i18n }],
[AntdConfigProvider, { remoteLocale: true }],
];
export default compose(...providers)(() => {
return <Table />;
});

View File

@ -1,11 +0,0 @@
---
group:
title: Client
order: 1
---
# AntdConfigProvider
为 antd 组件提供统一的全局化配置
<code src="./demos/demo1.tsx"></code>

View File

@ -0,0 +1 @@
# AppInfo

View File

@ -1,7 +1,6 @@
import { Application, Plugin } from '@nocobase/client';
import React, { FC } from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { Application } from '../Application';
import { Plugin } from '../Plugin';
const Root = () => {
return (

View File

@ -1,9 +1,6 @@
import { Application, Plugin, RouterManager, useApp } from '@nocobase/client';
import React, { useMemo } from 'react';
import { Link, Navigate, Outlet, useParams } from 'react-router-dom';
import { Application } from '../Application';
import { useApp } from '../hooks';
import { Plugin } from '../Plugin';
import { RouterManager } from '../RouterManager';
const Root = () => {
return (

View File

@ -1,6 +1,6 @@
---
group:
title: Client
title: core
order: 1
---

View File

@ -1,22 +0,0 @@
import React, { createContext, useContext } from 'react';
import MockAdapter from 'axios-mock-adapter';
import { APIClient, APIClientProvider, compose, AsyncDataProvider, useAsyncData } from '@nocobase/client';
const apiClient = new APIClient();
const mock = new MockAdapter(apiClient.axios);
mock.onGet('/users/1').reply(200, {
data: { id: 1, name: 'John Smith' },
});
const providers = [
[APIClientProvider, { apiClient }],
[AsyncDataProvider, { request: { url: '/users/1' } }],
];
export default compose(...providers)(() => {
const { data } = useAsyncData();
console.log(data);
return <div>{data?.data?.name}</div>;
});

View File

@ -1,51 +0,0 @@
import { APIClient, APIClientProvider, SchemaComponent, SchemaComponentProvider, useRequest } from '@nocobase/client';
import { Table } from 'antd';
import MockAdapter from 'axios-mock-adapter';
import React from 'react';
const apiClient = new APIClient();
const mock = new MockAdapter(apiClient.axios);
mock.onGet('/users').reply(200, {
data: [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Mike' },
],
});
const TableView = (props) => {
const { request, ...others } = props;
const callback = () => props.dataSource || [];
const { data, loading } = useRequest<{
data: any;
}>(props.request || callback);
return <Table {...others} dataSource={data?.data} loading={loading} />;
};
export default () => {
return (
<APIClientProvider apiClient={apiClient}>
<SchemaComponentProvider components={{ TableView }}>
<SchemaComponent
schema={{
type: 'void',
name: 'tableView',
'x-component': 'TableView',
'x-component-props': {
request: { url: '/users' },
rowKey: 'id',
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
},
}}
/>
</SchemaComponentProvider>
</APIClientProvider>
);
};

View File

@ -1,84 +0,0 @@
import {
APIClient,
APIClientProvider,
AsyncDataProvider,
SchemaComponent,
SchemaComponentProvider,
useAsyncData,
} from '@nocobase/client';
import { Table } from 'antd';
import MockAdapter from 'axios-mock-adapter';
import React from 'react';
const apiClient = new APIClient();
const mock = new MockAdapter(apiClient.axios);
mock.onGet('/users').reply(200, {
data: [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Mike' },
],
});
const TableView = (props) => {
const { data, loading } = useAsyncData();
return <Table {...props} dataSource={data?.data} loading={loading} />;
};
const AsyncDataTableView = (props) => {
const { request, ...others } = props;
return (
<AsyncDataProvider request={request}>
<TableView {...others} />
</AsyncDataProvider>
);
};
export default () => {
return (
<APIClientProvider apiClient={apiClient}>
<SchemaComponentProvider components={{ TableView, AsyncDataTableView, AsyncDataProvider }}>
<SchemaComponent
schema={{
properties: {
table1: {
type: 'void',
'x-decorator': 'AsyncDataProvider',
'x-decorator-props': {
request: { url: '/users' },
},
'x-component': 'TableView',
'x-component-props': {
rowKey: 'id',
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
},
},
table2: {
type: 'void',
'x-component': 'AsyncDataTableView',
'x-component-props': {
request: { url: '/users' },
rowKey: 'id',
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
},
},
},
}}
/>
</SchemaComponentProvider>
</APIClientProvider>
);
};

View File

@ -1,154 +0,0 @@
---
group:
title: Client
order: 1
---
# AsyncDataProvider
利用 Formily 提供的 [Effect Hooks](https://core.formilyjs.org/zh-CN/api/entry/form-effect-hooks) 或 [x-reactions](https://react.formilyjs.org/zh-CN/api/shared/schema#schemareactions) 协议能一定程度的解决某些场景的异步数据源问题,但是如果只用来处理异步请求,并不会很好用,而且局限于 Formily 体系内。为了更灵活的处理异步请求的各种需求NocoBase 提供了非常好用的可配置化的 [useRequest()](api-client#userequest),用于配置各种异步请求。与此同时,又提供了 `<AsyncDataProvider/>` 组件,可以将请求结果共享给子组件,方便处理其他更复杂的需求。以平时最常见,但却可能非常复杂的表格为例。表格的使用场景有:
- TableView 只作为表格视图
- RowSelection 动态数据的勾选项
- TableField 表格字段
各自的 schema 可以这么表示:
```js
// 表格视图type 为 void无 default 默认值,无 field.value
// dataSource 直接写在组件的 props 里
{
type: 'void',
'x-component': 'TableView',
'x-component-props': {
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
dataSource: [
{ id: 1, name: 'Name1' },
{ id: 2, name: 'Name2' },
],
},
}
// 单选(表格视图),类似于 Radio.Group 字段,可选项由表格视图提供
{
type: 'number',
'x-component': 'RowSelection',
'x-component-props': {
rowSelection: {
type: 'radio',
},
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
dataSource: [
{ id: 1, name: 'Name1' },
{ id: 2, name: 'Name2' },
],
},
default: 1,
}
// 多选(表格视图),类似于 Checkbox.Group 字段,可选项由表格视图提供
{
type: 'array',
'x-component': 'RowSelection',
'x-component-props': {
rowSelection: {
type: 'checkbox',
},
columns: [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
],
dataSource: [
{ id: 1, name: 'Name1' },
{ id: 2, name: 'Name2' },
],
},
default: [1],
}
// 表格字段数据由表单提供schema 层面可以配置默认值
// 与表格视图的方式不同dataSource 和 columns 不适合直接配置在 x-component-props 里,怎么处理先忽略。
{
type: 'array',
'x-component': 'TableField',
'x-component-props': {},
default: [
{ id: 1, name: 'Name1' },
{ id: 2, name: 'Name2' },
],
}
```
大多数实际应用中,表格视图的 dataSource 是异步获取的,可能也需要过滤和分页,这样一来直接在 x-component-props 里写 dataSource 并不合适。为了解决这个问题,可以给组件扩展一个 request 属性,用于请求远程数据,例子如下:
<code src="./demos/demo2.tsx"></code>
上述例子的 `<TableView/>` 组件就是一个 React Component可以正常使用而不局限于 SchemaComponent 场景。更进一步,为了处理过滤表单、分页等问题,建议将 request 通过 `<AsyncDataProvider/>` 共享给 `<TableView/>` 组件。
```tsx | pure
const result = useRequest(props.request);
<AsyncDataProvider value={result}>
<TableView/>
</AsyncDataProvider>
```
或者直接将 request 交给 AsyncDataProvider
```tsx | pure
<AsyncDataProvider request={props.request}>
<TableView/>
</AsyncDataProvider>
```
`<AsyncDataProvider/>` 的子组件里就可以通过 `useAsyncData()` 来获取 result可用于处理过滤、分页等。
```ts
const { data, loading, params, run, refresh, parent } = useAsyncData();
```
转化为 Schema 时,可以将 AsyncDataProvider 放在 `x-decorator` 里,也可以将重新组合的 AsyncDataProvider + TableView放在 `x-component` 里。
<code src="./demos/demo3.tsx"></code>
将 AsyncDataProvider 独立出来,放到 x-decorator 里的另一个好处,例子如下:
```tsx | pure
<CollectionProvider>
{/* 从 CollectionProvider 里获取资源请求参数,并将请求结果同时共享给 DesignableBar 和 TableView */}
<AsyncDataProvider>
<DesignableBar/>
<TableView>
<RecordProvider>
<CollectionFieldProvider>
{/* 从 CollectionFieldProvider 里获取关系资源请求参数,并将请求结果同时共享给子组件 */}
<AsyncDataProvider>
{/* 组件 */}
</AsyncDataProvider>
</CollectionFieldProvider>
</RecordProvider>
</TableView>
</AsyncDataProvider>
</CollectionProvider>
```
上面例子嵌套了两层 AsyncDataProvider在子 AsyncDataProvider 里,也可以获取到更上一级的请求结果,如:
```ts
const { parent } = useAsyncData();
parent.refresh();
```

View File

@ -0,0 +1 @@
# BlockProvider

View File

@ -1,9 +0,0 @@
---
group:
title: Client
order: 1
---
# I18n
提供国际化解决方案。

View File

@ -1,25 +0,0 @@
---
group:
title: Route Components
order: 2
---
# AdminLayout <Badge>待定</Badge>
结构
- Layout
- Layout.Header
- SiteTitle
- MenuSchemaComponent
- PluginManager.Toolbar
- DesignableAction
- CollectionManager.Action
- ACL.Action
- SystemSettings.Action
- CurrentUser.Dropdown
- Layout.Sider
- SideMenu随 Menu 联动的)
- Layout.Content
- PageTitle
- SchemaComponent

View File

@ -1,8 +0,0 @@
---
group:
title: Route Components
order: 2
---
# AuthLayout

View File

@ -0,0 +1 @@
# SchemaSettings

View File

@ -1,44 +0,0 @@
import {
AntdSchemaComponentProvider,
APIClient,
APIClientProvider,
SchemaComponentProvider,
SystemSettingsProvider,
useSystemSettings,
} from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import React from 'react';
const apiClient = new APIClient();
const mock = new MockAdapter(apiClient.axios);
mock.onGet('/systemSettings:get/1').reply(200, {
data: {
title: 'NocoBase',
},
});
const Demo = () => {
const { data } = useSystemSettings();
return (
<div>
<h3></h3>
System title: {data?.data?.title}
</div>
);
};
export default () => {
return (
<APIClientProvider apiClient={apiClient}>
<SystemSettingsProvider>
<SchemaComponentProvider>
<AntdSchemaComponentProvider>
<Demo />
</AntdSchemaComponentProvider>
</SchemaComponentProvider>
</SystemSettingsProvider>
</APIClientProvider>
);
};

View File

@ -1,21 +0,0 @@
---
group:
title: Client
order: 1
---
# SystemSettings
## SystemSettingsProvider
## SystemSettingsShortcut
## useSystemSettings
```ts
const { data, loading, refresh } = useSystemSettings();
```
## Examples
<code src="./demos/demo1.tsx"></code>