mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
client components (#4216)
* docs: update docs components * docs: add more component docs * docs: add more docs * fix: add more docs * fix: build bug * feat: docs * fix: build error * fix: docs * fix: change x-read-pretty to x-patten * fix: upgrade docs and types * fix: build bug * fix: add more docs * fix: build bug * fix: cascader component * fix: bug * fix: add more docs * fix: add backend ci time --------- Co-authored-by: katherinehhh <katherine_15995@163.com>
This commit is contained in:
parent
5313b8e495
commit
95fef86880
8
.github/workflows/nocobase-test-backend.yml
vendored
8
.github/workflows/nocobase-test-backend.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
|||||||
DB_STORAGE: /tmp/db.sqlite
|
DB_STORAGE: /tmp/db.sqlite
|
||||||
DB_TEST_PREFIX: test_
|
DB_TEST_PREFIX: test_
|
||||||
DB_UNDERSCORED: ${{ matrix.underscored }}
|
DB_UNDERSCORED: ${{ matrix.underscored }}
|
||||||
timeout-minutes: 40
|
timeout-minutes: 60
|
||||||
|
|
||||||
postgres-test:
|
postgres-test:
|
||||||
strategy:
|
strategy:
|
||||||
@ -132,7 +132,7 @@ jobs:
|
|||||||
COLLECTION_MANAGER_SCHEMA: ${{ matrix.collection_schema }}
|
COLLECTION_MANAGER_SCHEMA: ${{ matrix.collection_schema }}
|
||||||
DB_TEST_DISTRIBUTOR_PORT: 23450
|
DB_TEST_DISTRIBUTOR_PORT: 23450
|
||||||
DB_TEST_PREFIX: test_
|
DB_TEST_PREFIX: test_
|
||||||
timeout-minutes: 40
|
timeout-minutes: 60
|
||||||
|
|
||||||
mysql-test:
|
mysql-test:
|
||||||
strategy:
|
strategy:
|
||||||
@ -183,7 +183,7 @@ jobs:
|
|||||||
DB_UNDERSCORED: ${{ matrix.underscored }}
|
DB_UNDERSCORED: ${{ matrix.underscored }}
|
||||||
DB_TEST_DISTRIBUTOR_PORT: 23450
|
DB_TEST_DISTRIBUTOR_PORT: 23450
|
||||||
DB_TEST_PREFIX: test_
|
DB_TEST_PREFIX: test_
|
||||||
timeout-minutes: 40
|
timeout-minutes: 60
|
||||||
mariadb-test:
|
mariadb-test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -233,4 +233,4 @@ jobs:
|
|||||||
DB_UNDERSCORED: ${{ matrix.underscored }}
|
DB_UNDERSCORED: ${{ matrix.underscored }}
|
||||||
DB_TEST_DISTRIBUTOR_PORT: 23450
|
DB_TEST_DISTRIBUTOR_PORT: 23450
|
||||||
DB_TEST_PREFIX: test_
|
DB_TEST_PREFIX: test_
|
||||||
timeout-minutes: 40
|
timeout-minutes: 60
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import path from 'path';
|
|
||||||
import glob from 'glob';
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { getUmiConfig } from '@nocobase/devtools/umiConfig';
|
import { getUmiConfig } from '@nocobase/devtools/umiConfig';
|
||||||
import { defineConfig } from 'dumi';
|
import { defineConfig } from 'dumi';
|
||||||
@ -11,19 +9,6 @@ const lang = process.env.DOC_LANG;
|
|||||||
|
|
||||||
console.log('process.env.DOC_LANG', lang);
|
console.log('process.env.DOC_LANG', lang);
|
||||||
|
|
||||||
const componentsDir = 'src/schema-component/antd';
|
|
||||||
|
|
||||||
function getComponentsMenu() {
|
|
||||||
const cwd = path.join(__dirname, componentsDir);
|
|
||||||
const ignores = ['table/index.md', 'form/index.md']; // 老版本,不需要展示
|
|
||||||
const files = glob.sync('*/index.md', { cwd, ignore: ignores });
|
|
||||||
|
|
||||||
return files.map((file) => ({
|
|
||||||
title: _.upperFirst(_.camelCase(file.replace('/index.md', ''))),
|
|
||||||
link: `/components/${file.replace('/index.md', '')}`,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
hash: true,
|
hash: true,
|
||||||
alias: {
|
alias: {
|
||||||
@ -39,7 +24,7 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
docDirs: [`./docs/${lang}`],
|
docDirs: [`./docs/${lang}`],
|
||||||
atomDirs: [
|
atomDirs: [
|
||||||
{ type: 'component', dir: componentsDir },
|
{ type: 'component', dir: 'src/schema-component/antd' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
locales: [
|
locales: [
|
||||||
@ -92,6 +77,10 @@ export default defineConfig({
|
|||||||
title: 'PluginSettingsManager',
|
title: 'PluginSettingsManager',
|
||||||
link: '/core/application/plugin-settings-manager',
|
link: '/core/application/plugin-settings-manager',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Request',
|
||||||
|
link: '/core/request',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -225,7 +214,196 @@ export default defineConfig({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'/components': getComponentsMenu(),
|
'/components': [
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
"title": "Action",
|
||||||
|
"link": "/components/action"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Filter",
|
||||||
|
"link": "/components/filter"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Field',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
"title": "Checkbox",
|
||||||
|
"link": "/components/checkbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Cascader",
|
||||||
|
"link": "/components/cascader"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ColorPicker",
|
||||||
|
"link": "/components/color-picker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ColorSelect",
|
||||||
|
"link": "/components/color-select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "DatePicker",
|
||||||
|
"link": "/components/date-picker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TimePicker",
|
||||||
|
"link": "/components/time-picker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "IconPicker",
|
||||||
|
"link": "/components/icon-picker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "InputNumber",
|
||||||
|
"link": "/components/input-number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Input",
|
||||||
|
"link": "/components/input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "AutoComplete",
|
||||||
|
"link": "/components/auto-complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "NanoIDInput",
|
||||||
|
"link": "/components/nanoid-input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Password",
|
||||||
|
"link": "/components/password"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Percent",
|
||||||
|
"link": "/components/percent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Radio",
|
||||||
|
"link": "/components/radio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Select",
|
||||||
|
"link": "/components/select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "RemoteSelect",
|
||||||
|
"link": "/components/remote-select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TreeSelect",
|
||||||
|
"link": "/components/tree-select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Upload",
|
||||||
|
"link": "/components/upload"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CollectionSelect",
|
||||||
|
"link": "/components/collection-select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Cron",
|
||||||
|
"link": "/components/cron"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Markdown",
|
||||||
|
"link": "/components/markdown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Variable",
|
||||||
|
"link": "/components/variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "QuickEdit",
|
||||||
|
"link": "/components/quick-edit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "RichText",
|
||||||
|
"link": "/components/rich-text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Block',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
"title": "BlockItem",
|
||||||
|
"link": "/components/block-item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "CardItem",
|
||||||
|
"link": "/components/card-item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "FormItem",
|
||||||
|
"link": "/components/form-item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "FormV2",
|
||||||
|
"link": "/components/form-v2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "TableV2",
|
||||||
|
"link": "/components/table-v2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Details",
|
||||||
|
"link": "/components/details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "GridCard",
|
||||||
|
"link": "/components/grid-card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Grid",
|
||||||
|
"link": "/components/grid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "List",
|
||||||
|
"link": "/components/list"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Others',
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
"title": "Tabs",
|
||||||
|
"link": "/components/tabs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "ErrorFallback",
|
||||||
|
"link": "/components/error-fallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "G2Plot",
|
||||||
|
"link": "/components/g2plot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Menu",
|
||||||
|
"link": "/components/menu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Pagination",
|
||||||
|
"link": "/components/pagination"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Preview",
|
||||||
|
"link": "/components/preview"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
// '/ui-schema': [
|
// '/ui-schema': [
|
||||||
// {
|
// {
|
||||||
// title: 'Overview',
|
// title: 'Overview',
|
||||||
|
@ -37,7 +37,7 @@ Table 中的字段信息及列表数据,都是存储在数据库中的。
|
|||||||
|
|
||||||
- `DataBlockProvider`:封装了下面的所有组件,并提供了区块属性
|
- `DataBlockProvider`:封装了下面的所有组件,并提供了区块属性
|
||||||
- [CollectionProvider](/core/data-source/collection-provider) / [AssociationProvider](/core/data-source/association-provider): 根据 `DataBlockProvider` 提供的上下文信息,查询对应数据表数据及关系字段信息并传递
|
- [CollectionProvider](/core/data-source/collection-provider) / [AssociationProvider](/core/data-source/association-provider): 根据 `DataBlockProvider` 提供的上下文信息,查询对应数据表数据及关系字段信息并传递
|
||||||
- [BlockResourceProvider](/core/data-block/data-block-resource-provider): 根据 `DataBlockProvider` 提供的上下文信息,构建区块 [Resource](https://docs.nocobase.com/api/sdk#resource-action) API,用于区块数据的增删改查
|
- [BlockResourceProvider](/core/data-block/data-block-resource-provider): 根据 `DataBlockProvider` 提供的上下文信息,构建区块 [Resource](/core/request) API,用于区块数据的增删改查
|
||||||
- [BlockRequestProvider](/core/data-block/data-block-request-provider): 根据 `DataBlockProvider` 提供的上下文信息,自动调用 `BlockResourceProvider` 提供的 `resource.get()` 或 `resource.list()` 发起请求,得到区块数据,并传递
|
- [BlockRequestProvider](/core/data-block/data-block-request-provider): 根据 `DataBlockProvider` 提供的上下文信息,自动调用 `BlockResourceProvider` 提供的 `resource.get()` 或 `resource.list()` 发起请求,得到区块数据,并传递
|
||||||
- [CollectionRecordProvider](/core/data-source/record-provider): 对于 `resource.get()` 场景,会自动嵌套 `CollectionRecordProvider` 并将 `resource.get()` 请求结果传递下去,`resource.list()` 场景则需要自行使用 `CollectionRecordProvider` 提供数据记录
|
- [CollectionRecordProvider](/core/data-source/record-provider): 对于 `resource.get()` 场景,会自动嵌套 `CollectionRecordProvider` 并将 `resource.get()` 请求结果传递下去,`resource.list()` 场景则需要自行使用 `CollectionRecordProvider` 提供数据记录
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# DataBlockResourceProvider
|
# DataBlockResourceProvider
|
||||||
|
|
||||||
根据 `DataBlockProvider` 中的 `collection`、`association`、`sourceId` 等属性,构建好 [resource](https://docs.nocobase.com/api/sdk#resource-action) 对象,方便子组件对区块数据的增删改查操作,其内置在 [DataBlockProvider](/core/data-block/data-block-provider) 中
|
根据 `DataBlockProvider` 中的 `collection`、`association`、`sourceId` 等属性,构建好 [resource](/core/request) 对象,方便子组件对区块数据的增删改查操作,其内置在 [DataBlockProvider](/core/data-block/data-block-provider) 中
|
||||||
|
|
||||||
|
|
||||||
## useDataBlockResource
|
## useDataBlockResource
|
||||||
|
24
packages/core/client/docs/en-US/core/request/demos/demo1.tsx
Normal file
24
packages/core/client/docs/en-US/core/request/demos/demo1.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { APIClient, APIClientProvider, compose, useRequest } 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('/users:get').reply(200, {
|
||||||
|
data: { id: 1, name: 'John Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const providers = [[APIClientProvider, { apiClient }]];
|
||||||
|
|
||||||
|
export default compose(...providers)(() => {
|
||||||
|
const { data } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>({
|
||||||
|
resource: 'users',
|
||||||
|
action: 'get',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
return <div>{data?.data?.name}</div>;
|
||||||
|
});
|
23
packages/core/client/docs/en-US/core/request/demos/demo2.tsx
Normal file
23
packages/core/client/docs/en-US/core/request/demos/demo2.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { APIClient, APIClientProvider, compose, useRequest } 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('/users:get').reply(200, {
|
||||||
|
data: { id: 1, name: 'John Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const providers = [[APIClientProvider, { apiClient }]];
|
||||||
|
|
||||||
|
export default compose(...providers)(() => {
|
||||||
|
const { data } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>({
|
||||||
|
url: 'users:get',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return <div>{data?.data?.name}</div>;
|
||||||
|
});
|
69
packages/core/client/docs/en-US/core/request/demos/demo3.tsx
Normal file
69
packages/core/client/docs/en-US/core/request/demos/demo3.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { APIClient, APIClientProvider, useAPIClient, useRequest } from '@nocobase/client';
|
||||||
|
import { Button, Input, Space, Table } from 'antd';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
const mock = new MockAdapter(apiClient.axios);
|
||||||
|
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
||||||
|
|
||||||
|
mock.onGet('/users:list').reply(async () => {
|
||||||
|
await sleep(1000);
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ id: 1, name: uid() },
|
||||||
|
{ id: 2, name: uid() },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const ComponentA = () => {
|
||||||
|
console.log('ComponentA');
|
||||||
|
const { data, loading } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
url: 'users:list',
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'test', // 当指定了 uid 的 useRequest 的结果,可以通过 api.service(uid) 获取
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
rowKey={'id'}
|
||||||
|
loading={loading}
|
||||||
|
dataSource={data?.data}
|
||||||
|
columns={[{ title: 'Name', dataIndex: 'name' }]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ComponentB = () => {
|
||||||
|
console.log('ComponentB');
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Input />
|
||||||
|
<Button onClick={() => apiClient.service('test')?.run()}>提交</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={apiClient}>
|
||||||
|
<ComponentB />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<ComponentA />
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
};
|
167
packages/core/client/docs/en-US/core/request/index.md
Normal file
167
packages/core/client/docs/en-US/core/request/index.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# APIClient
|
||||||
|
|
||||||
|
## APIClient
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class APIClient {
|
||||||
|
// axios 实例
|
||||||
|
axios: AxiosInstance;
|
||||||
|
// 缓存带 uid 的 useRequest({}, {uid}) 的结果,可供其他组件调用
|
||||||
|
services: Record<string, Result<any, any>>;
|
||||||
|
// 构造器
|
||||||
|
constructor(instance?: AxiosInstance | AxiosRequestConfig);
|
||||||
|
// 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions
|
||||||
|
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>;
|
||||||
|
// 获取资源
|
||||||
|
resource<R = IResource>(name: string, of?: any): R;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// 不传参时,内部直接创建 axios 实例
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
// 提供 AxiosRequestConfig 配置参数
|
||||||
|
const apiClient = new APIClient({
|
||||||
|
baseURL: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提供 AxiosInstance
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: '',
|
||||||
|
});
|
||||||
|
const apiClient = new APIClient(instance);
|
||||||
|
|
||||||
|
// 常规请求
|
||||||
|
const response = await apiClient.request({ url });
|
||||||
|
|
||||||
|
// NocoBase 特有的资源操作
|
||||||
|
const response = await apiClient.resource('posts').list();
|
||||||
|
|
||||||
|
// 请求共享
|
||||||
|
const { data, loading, run } = apiClient.service('uid');
|
||||||
|
```
|
||||||
|
|
||||||
|
`api.service(uid)` 的例子,ComponentB 里刷新 ComponentA 的请求数据
|
||||||
|
|
||||||
|
<code src="./demos/demo3.tsx"></code>
|
||||||
|
|
||||||
|
## APIClientProvider
|
||||||
|
|
||||||
|
提供 APIClient 实例的上下文。
|
||||||
|
|
||||||
|
```tsx | pure
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
<APIClientProvider apiClient={apiClient}></APIClientProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## useAPIClient
|
||||||
|
|
||||||
|
获取当前上下文的 APIClient 实例。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
```
|
||||||
|
|
||||||
|
## useRequest
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useRequest<P>(
|
||||||
|
service: AxiosRequestConfig<P> | ResourceActionOptions<P> | FunctionService,
|
||||||
|
options?: Options<any, any>,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
支持 `axios.request(config)`,config 详情查看 [axios](https://github.com/axios/axios#request-config)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, loading, refresh, run, params } = useRequest({ url: '/users' });
|
||||||
|
|
||||||
|
// useRequest 里传的是 AxiosRequestConfig,所以 run 里传的也是 AxiosRequestConfig
|
||||||
|
run({
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
例子如下:
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx"></code>
|
||||||
|
|
||||||
|
或者是 NocoBase 的 resource & action 请求:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, run } = useRequest({
|
||||||
|
resource: 'users',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// useRequest 传的是 ResourceActionOptions,所以 run 直接传 action params 就可以了。
|
||||||
|
run({
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
例子如下:
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx"></code>
|
||||||
|
|
||||||
|
也可以是自定义的异步函数:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, loading, run, refresh, params } = useRequest((...params) => Promise.resolve({}));
|
||||||
|
|
||||||
|
run(...params);
|
||||||
|
```
|
||||||
|
|
||||||
|
更多用法查看 ahooks 的 [useRequest()](https://ahooks.js.org/hooks/use-request/index)
|
||||||
|
|
||||||
|
## useResource
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useResource(name: string, of?: string | number): IResource;
|
||||||
|
```
|
||||||
|
|
||||||
|
资源是 NocoBase 的核心概念,包括:
|
||||||
|
|
||||||
|
- 独立资源,如 `posts`
|
||||||
|
- 关系资源,如 `posts.tags` `posts.user` `posts.comments`
|
||||||
|
|
||||||
|
资源 URI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 独立资源,文章
|
||||||
|
/api/posts
|
||||||
|
# 关系资源,文章 ID=1 的评论
|
||||||
|
/api/posts/1/comments
|
||||||
|
```
|
||||||
|
|
||||||
|
通过 APIClient 获取资源
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const api = new APIClient();
|
||||||
|
|
||||||
|
api.resource('posts');
|
||||||
|
api.resource('posts.comments', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
useResource 用法:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const resource = useResource('posts');
|
||||||
|
const resource = useResource('posts.comments', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
resource 的实际场景用例参见:
|
||||||
|
|
||||||
|
- [useCollection()](collection-manager#usecollection)
|
||||||
|
- [useCollectionField()](collection-manager#usecollectionfield)
|
@ -37,7 +37,7 @@ Table 中的字段信息及列表数据,都是存储在数据库中的。
|
|||||||
|
|
||||||
- `DataBlockProvider`:封装了下面的所有组件,并提供了区块属性
|
- `DataBlockProvider`:封装了下面的所有组件,并提供了区块属性
|
||||||
- [CollectionProvider](/core/data-source/collection-provider) / [AssociationProvider](/core/data-source/association-provider): 根据 `DataBlockProvider` 提供的上下文信息,查询对应数据表数据及关系字段信息并传递
|
- [CollectionProvider](/core/data-source/collection-provider) / [AssociationProvider](/core/data-source/association-provider): 根据 `DataBlockProvider` 提供的上下文信息,查询对应数据表数据及关系字段信息并传递
|
||||||
- [BlockResourceProvider](/core/data-block/data-block-resource-provider): 根据 `DataBlockProvider` 提供的上下文信息,构建区块 [Resource](https://docs.nocobase.com/api/sdk#resource-action) API,用于区块数据的增删改查
|
- [BlockResourceProvider](/core/data-block/data-block-resource-provider): 根据 `DataBlockProvider` 提供的上下文信息,构建区块 [Resource](/core/request) API,用于区块数据的增删改查
|
||||||
- [BlockRequestProvider](/core/data-block/data-block-request-provider): 根据 `DataBlockProvider` 提供的上下文信息,自动调用 `BlockResourceProvider` 提供的 `resource.get()` 或 `resource.list()` 发起请求,得到区块数据,并传递
|
- [BlockRequestProvider](/core/data-block/data-block-request-provider): 根据 `DataBlockProvider` 提供的上下文信息,自动调用 `BlockResourceProvider` 提供的 `resource.get()` 或 `resource.list()` 发起请求,得到区块数据,并传递
|
||||||
- [CollectionRecordProvider](/core/data-source/record-provider): 对于 `resource.get()` 场景,会自动嵌套 `CollectionRecordProvider` 并将 `resource.get()` 请求结果传递下去,`resource.list()` 场景则需要自行使用 `CollectionRecordProvider` 提供数据记录
|
- [CollectionRecordProvider](/core/data-source/record-provider): 对于 `resource.get()` 场景,会自动嵌套 `CollectionRecordProvider` 并将 `resource.get()` 请求结果传递下去,`resource.list()` 场景则需要自行使用 `CollectionRecordProvider` 提供数据记录
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# DataBlockResourceProvider
|
# DataBlockResourceProvider
|
||||||
|
|
||||||
根据 `DataBlockProvider` 中的 `collection`、`association`、`sourceId` 等属性,构建好 [resource](https://docs.nocobase.com/api/sdk#resource-action) 对象,方便子组件对区块数据的增删改查操作,其内置在 [DataBlockProvider](/core/data-block/data-block-provider) 中
|
根据 `DataBlockProvider` 中的 `collection`、`association`、`sourceId` 等属性,构建好 [resource](/core/request) 对象,方便子组件对区块数据的增删改查操作,其内置在 [DataBlockProvider](/core/data-block/data-block-provider) 中
|
||||||
|
|
||||||
|
|
||||||
## useDataBlockResource
|
## useDataBlockResource
|
||||||
|
24
packages/core/client/docs/zh-CN/core/request/demos/demo1.tsx
Normal file
24
packages/core/client/docs/zh-CN/core/request/demos/demo1.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { APIClient, APIClientProvider, compose, useRequest } 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('/users:get').reply(200, {
|
||||||
|
data: { id: 1, name: 'John Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const providers = [[APIClientProvider, { apiClient }]];
|
||||||
|
|
||||||
|
export default compose(...providers)(() => {
|
||||||
|
const { data } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>({
|
||||||
|
resource: 'users',
|
||||||
|
action: 'get',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
return <div>{data?.data?.name}</div>;
|
||||||
|
});
|
23
packages/core/client/docs/zh-CN/core/request/demos/demo2.tsx
Normal file
23
packages/core/client/docs/zh-CN/core/request/demos/demo2.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { APIClient, APIClientProvider, compose, useRequest } 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('/users:get').reply(200, {
|
||||||
|
data: { id: 1, name: 'John Smith' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const providers = [[APIClientProvider, { apiClient }]];
|
||||||
|
|
||||||
|
export default compose(...providers)(() => {
|
||||||
|
const { data } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>({
|
||||||
|
url: 'users:get',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return <div>{data?.data?.name}</div>;
|
||||||
|
});
|
69
packages/core/client/docs/zh-CN/core/request/demos/demo3.tsx
Normal file
69
packages/core/client/docs/zh-CN/core/request/demos/demo3.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { APIClient, APIClientProvider, useAPIClient, useRequest } from '@nocobase/client';
|
||||||
|
import { Button, Input, Space, Table } from 'antd';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
const mock = new MockAdapter(apiClient.axios);
|
||||||
|
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
||||||
|
|
||||||
|
mock.onGet('/users:list').reply(async () => {
|
||||||
|
await sleep(1000);
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ id: 1, name: uid() },
|
||||||
|
{ id: 2, name: uid() },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const ComponentA = () => {
|
||||||
|
console.log('ComponentA');
|
||||||
|
const { data, loading } = useRequest<{
|
||||||
|
data: any;
|
||||||
|
}>(
|
||||||
|
{
|
||||||
|
url: 'users:list',
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'test', // 当指定了 uid 的 useRequest 的结果,可以通过 api.service(uid) 获取
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
pagination={false}
|
||||||
|
rowKey={'id'}
|
||||||
|
loading={loading}
|
||||||
|
dataSource={data?.data}
|
||||||
|
columns={[{ title: 'Name', dataIndex: 'name' }]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ComponentB = () => {
|
||||||
|
console.log('ComponentB');
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Input />
|
||||||
|
<Button onClick={() => apiClient.service('test')?.run()}>提交</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={apiClient}>
|
||||||
|
<ComponentB />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<ComponentA />
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
};
|
167
packages/core/client/docs/zh-CN/core/request/index.md
Normal file
167
packages/core/client/docs/zh-CN/core/request/index.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# APIClient
|
||||||
|
|
||||||
|
## APIClient
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class APIClient {
|
||||||
|
// axios 实例
|
||||||
|
axios: AxiosInstance;
|
||||||
|
// 缓存带 uid 的 useRequest({}, {uid}) 的结果,可供其他组件调用
|
||||||
|
services: Record<string, Result<any, any>>;
|
||||||
|
// 构造器
|
||||||
|
constructor(instance?: AxiosInstance | AxiosRequestConfig);
|
||||||
|
// 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions
|
||||||
|
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>;
|
||||||
|
// 获取资源
|
||||||
|
resource<R = IResource>(name: string, of?: any): R;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// 不传参时,内部直接创建 axios 实例
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
// 提供 AxiosRequestConfig 配置参数
|
||||||
|
const apiClient = new APIClient({
|
||||||
|
baseURL: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提供 AxiosInstance
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: '',
|
||||||
|
});
|
||||||
|
const apiClient = new APIClient(instance);
|
||||||
|
|
||||||
|
// 常规请求
|
||||||
|
const response = await apiClient.request({ url });
|
||||||
|
|
||||||
|
// NocoBase 特有的资源操作
|
||||||
|
const response = await apiClient.resource('posts').list();
|
||||||
|
|
||||||
|
// 请求共享
|
||||||
|
const { data, loading, run } = apiClient.service('uid');
|
||||||
|
```
|
||||||
|
|
||||||
|
`api.service(uid)` 的例子,ComponentB 里刷新 ComponentA 的请求数据
|
||||||
|
|
||||||
|
<code src="./demos/demo3.tsx"></code>
|
||||||
|
|
||||||
|
## APIClientProvider
|
||||||
|
|
||||||
|
提供 APIClient 实例的上下文。
|
||||||
|
|
||||||
|
```tsx | pure
|
||||||
|
const apiClient = new APIClient();
|
||||||
|
|
||||||
|
<APIClientProvider apiClient={apiClient}></APIClientProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## useAPIClient
|
||||||
|
|
||||||
|
获取当前上下文的 APIClient 实例。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
```
|
||||||
|
|
||||||
|
## useRequest
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useRequest<P>(
|
||||||
|
service: AxiosRequestConfig<P> | ResourceActionOptions<P> | FunctionService,
|
||||||
|
options?: Options<any, any>,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
支持 `axios.request(config)`,config 详情查看 [axios](https://github.com/axios/axios#request-config)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, loading, refresh, run, params } = useRequest({ url: '/users' });
|
||||||
|
|
||||||
|
// useRequest 里传的是 AxiosRequestConfig,所以 run 里传的也是 AxiosRequestConfig
|
||||||
|
run({
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
例子如下:
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx"></code>
|
||||||
|
|
||||||
|
或者是 NocoBase 的 resource & action 请求:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, run } = useRequest({
|
||||||
|
resource: 'users',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// useRequest 传的是 ResourceActionOptions,所以 run 直接传 action params 就可以了。
|
||||||
|
run({
|
||||||
|
pageSize: 50,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
例子如下:
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx"></code>
|
||||||
|
|
||||||
|
也可以是自定义的异步函数:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, loading, run, refresh, params } = useRequest((...params) => Promise.resolve({}));
|
||||||
|
|
||||||
|
run(...params);
|
||||||
|
```
|
||||||
|
|
||||||
|
更多用法查看 ahooks 的 [useRequest()](https://ahooks.js.org/hooks/use-request/index)
|
||||||
|
|
||||||
|
## useResource
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useResource(name: string, of?: string | number): IResource;
|
||||||
|
```
|
||||||
|
|
||||||
|
资源是 NocoBase 的核心概念,包括:
|
||||||
|
|
||||||
|
- 独立资源,如 `posts`
|
||||||
|
- 关系资源,如 `posts.tags` `posts.user` `posts.comments`
|
||||||
|
|
||||||
|
资源 URI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 独立资源,文章
|
||||||
|
/api/posts
|
||||||
|
# 关系资源,文章 ID=1 的评论
|
||||||
|
/api/posts/1/comments
|
||||||
|
```
|
||||||
|
|
||||||
|
通过 APIClient 获取资源
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const api = new APIClient();
|
||||||
|
|
||||||
|
api.resource('posts');
|
||||||
|
api.resource('posts.comments', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
useResource 用法:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const resource = useResource('posts');
|
||||||
|
const resource = useResource('posts.comments', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
resource 的实际场景用例参见:
|
||||||
|
|
||||||
|
- [useCollection()](collection-manager#usecollection)
|
||||||
|
- [useCollectionField()](collection-manager#usecollectionfield)
|
@ -166,8 +166,3 @@ useResource 用法:
|
|||||||
const resource = useResource('posts');
|
const resource = useResource('posts');
|
||||||
const resource = useResource('posts.comments', 1);
|
const resource = useResource('posts.comments', 1);
|
||||||
```
|
```
|
||||||
|
|
||||||
resource 的实际场景用例参见:
|
|
||||||
|
|
||||||
- [useCollection()](collection-manager#usecollection)
|
|
||||||
- [useCollectionField()](collection-manager#usecollectionfield)
|
|
||||||
|
@ -386,7 +386,7 @@ describe('Application', () => {
|
|||||||
render(<Root />);
|
render(<Root />);
|
||||||
|
|
||||||
await sleep(10);
|
await sleep(10);
|
||||||
expect(screen.getByText('Load Plugin Error')).toBeInTheDocument();
|
expect(screen.getByText('App Error')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('replace Component', async () => {
|
it('replace Component', async () => {
|
||||||
|
@ -14,7 +14,7 @@ const Loading: FC = () => <div>Loading...</div>;
|
|||||||
const AppError: FC<{ error: Error }> = ({ error }) => {
|
const AppError: FC<{ error: Error }> = ({ error }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>Load Plugin Error</div>
|
<div>App Error</div>
|
||||||
{error?.message}
|
{error?.message}
|
||||||
{process.env.__TEST__ && error?.stack}
|
{process.env.__TEST__ && error?.stack}
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,10 @@ interface WithSchemaHookOptions {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withDynamicSchemaProps<T = any>(Component: any, options: WithSchemaHookOptions = {}) {
|
export function withDynamicSchemaProps<T = any>(
|
||||||
|
Component: React.ComponentType<T>,
|
||||||
|
options: WithSchemaHookOptions = {},
|
||||||
|
) {
|
||||||
const displayName = options.displayName || Component.displayName || Component.name;
|
const displayName = options.displayName || Component.displayName || Component.name;
|
||||||
const ComponentWithProps: ComponentType<T> = (props) => {
|
const ComponentWithProps: ComponentType<T> = (props) => {
|
||||||
const { dn, findComponent } = useDesignable();
|
const { dn, findComponent } = useDesignable();
|
||||||
|
@ -63,10 +63,10 @@ export * from './variables';
|
|||||||
export { withDynamicSchemaProps } from './application/hoc/withDynamicSchemaProps';
|
export { withDynamicSchemaProps } from './application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
export * from './modules/blocks/BlockSchemaToolbar';
|
export * from './modules/blocks/BlockSchemaToolbar';
|
||||||
export * from './modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem';
|
|
||||||
export * from './modules/blocks/data-blocks/form';
|
export * from './modules/blocks/data-blocks/form';
|
||||||
export * from './modules/blocks/data-blocks/table';
|
export * from './modules/blocks/data-blocks/table';
|
||||||
export * from './modules/blocks/data-blocks/table-selector';
|
export * from './modules/blocks/data-blocks/table-selector';
|
||||||
export * from './modules/blocks/useParentRecordCommon';
|
export * from './modules/blocks/useParentRecordCommon';
|
||||||
|
export * from './modules/blocks/index';
|
||||||
|
|
||||||
export { DeclareVariable } from './modules/variable/DeclareVariable';
|
export { DeclareVariable } from './modules/variable/DeclareVariable';
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from './useDetailsWithPaginationBlockParams';
|
||||||
|
export * from './useDetailsWithPaginationProps';
|
||||||
|
export * from './useDetailsWithPaginationDecoratorProps';
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './hooks';
|
||||||
|
export * from './setDataLoadingModeSettingsItem';
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './useDetailsDecoratorProps';
|
||||||
|
export * from './useDetailsProps';
|
@ -0,0 +1 @@
|
|||||||
|
export * from './hooks';
|
@ -16,3 +16,4 @@ export * from './tableColumnSettings';
|
|||||||
export * from './TableColumnInitializers';
|
export * from './TableColumnInitializers';
|
||||||
export * from './createTableBlockUISchema';
|
export * from './createTableBlockUISchema';
|
||||||
export * from './hooks/useTableBlockDecoratorProps';
|
export * from './hooks/useTableBlockDecoratorProps';
|
||||||
|
export * from './hooks/useTableBlockProps';
|
||||||
|
2
packages/core/client/src/modules/blocks/index.ts
Normal file
2
packages/core/client/src/modules/blocks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './data-blocks/details-multi';
|
||||||
|
export * from './data-blocks/details-single';
|
@ -11,7 +11,7 @@ import { observer, RecursionField, useField, useFieldSchema } from '@formily/rea
|
|||||||
import { Drawer } from 'antd';
|
import { Drawer } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { OpenSize } from './';
|
import { OpenSize } from './types';
|
||||||
import { useStyles } from './Action.Drawer.style';
|
import { useStyles } from './Action.Drawer.style';
|
||||||
import { useActionContext } from './hooks';
|
import { useActionContext } from './hooks';
|
||||||
import { useSetAriaLabelForDrawer } from './hooks/useSetAriaLabelForDrawer';
|
import { useSetAriaLabelForDrawer } from './hooks/useSetAriaLabelForDrawer';
|
||||||
|
@ -12,10 +12,10 @@ import { observer, RecursionField, useField, useFieldSchema } from '@formily/rea
|
|||||||
import { Modal, ModalProps } from 'antd';
|
import { Modal, ModalProps } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { OpenSize, useActionContext } from '.';
|
|
||||||
import { useToken } from '../../../style';
|
import { useToken } from '../../../style';
|
||||||
import { useSetAriaLabelForModal } from './hooks/useSetAriaLabelForModal';
|
import { useSetAriaLabelForModal } from './hooks/useSetAriaLabelForModal';
|
||||||
import { ComposedActionDrawer } from './types';
|
import { ComposedActionDrawer, OpenSize } from './types';
|
||||||
|
import { useActionContext } from './hooks';
|
||||||
|
|
||||||
const openSizeWidthMap = new Map<OpenSize, string>([
|
const openSizeWidthMap = new Map<OpenSize, string>([
|
||||||
['small', '40%'],
|
['small', '40%'],
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
@ -42,11 +42,11 @@ import useStyles from './Action.style';
|
|||||||
import { ActionContextProvider } from './context';
|
import { ActionContextProvider } from './context';
|
||||||
import { useA } from './hooks';
|
import { useA } from './hooks';
|
||||||
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
|
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
|
||||||
import { ComposedAction } from './types';
|
import { ActionProps, ComposedAction } from './types';
|
||||||
import { linkageAction, setInitialActionState } from './utils';
|
import { linkageAction, setInitialActionState } from './utils';
|
||||||
|
|
||||||
export const Action: ComposedAction = withDynamicSchemaProps(
|
export const Action: ComposedAction = withDynamicSchemaProps(
|
||||||
observer((props: any) => {
|
observer((props: ActionProps) => {
|
||||||
const {
|
const {
|
||||||
popover,
|
popover,
|
||||||
confirm,
|
confirm,
|
||||||
@ -59,6 +59,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
title,
|
title,
|
||||||
onClick,
|
onClick,
|
||||||
style,
|
style,
|
||||||
|
loading,
|
||||||
openSize: os,
|
openSize: os,
|
||||||
disabled: propsDisabled,
|
disabled: propsDisabled,
|
||||||
actionCallback,
|
actionCallback,
|
||||||
@ -180,14 +181,14 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
aria-label={getAriaLabel()}
|
aria-label={getAriaLabel()}
|
||||||
{...others}
|
{...others}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
loading={field?.data?.loading}
|
loading={field?.data?.loading || loading}
|
||||||
icon={icon ? <Icon type={icon} /> : null}
|
icon={icon ? <Icon type={icon} /> : null}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={buttonStyle}
|
style={buttonStyle}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
component={tarComponent || Button}
|
component={tarComponent || Button}
|
||||||
className={classnames(componentCls, hashId, className, 'nb-action')}
|
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||||
type={props.type === 'danger' ? undefined : props.type}
|
type={(props as any).type === 'danger' ? undefined : props.type}
|
||||||
>
|
>
|
||||||
{actionTitle}
|
{actionTitle}
|
||||||
<Designer {...designerProps} />
|
<Designer {...designerProps} />
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { cx } from '@emotion/css';
|
import { cx } from '@emotion/css';
|
||||||
import { RecursionField, observer, useFieldSchema } from '@formily/react';
|
import { RecursionField, observer, useFieldSchema } from '@formily/react';
|
||||||
import { Space } from 'antd';
|
import { Space, SpaceProps } from 'antd';
|
||||||
import React, { CSSProperties, useContext } from 'react';
|
import React, { CSSProperties, useContext } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { DndContext } from '../../common';
|
import { DndContext } from '../../common';
|
||||||
@ -17,10 +17,11 @@ import { useDesignable, useProps } from '../../hooks';
|
|||||||
import { useSchemaInitializerRender } from '../../../application';
|
import { useSchemaInitializerRender } from '../../../application';
|
||||||
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
interface ActionBarContextForceProps {
|
export interface ActionBarProps {
|
||||||
layout?: 'one-column' | 'tow-columns';
|
layout?: 'one-column' | 'two-columns';
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
spaceProps?: SpaceProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionBarContextValue {
|
export interface ActionBarContextValue {
|
||||||
@ -28,7 +29,7 @@ export interface ActionBarContextValue {
|
|||||||
/**
|
/**
|
||||||
* override props
|
* override props
|
||||||
*/
|
*/
|
||||||
forceProps?: ActionBarContextForceProps;
|
forceProps?: ActionBarProps;
|
||||||
parentComponents?: string[];
|
parentComponents?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ export const ActionBar = withDynamicSchemaProps(
|
|||||||
const { forceProps = {} } = useActionBarContext();
|
const { forceProps = {} } = useActionBarContext();
|
||||||
|
|
||||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
|
const { layout = 'two-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
|
||||||
|
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
||||||
|
@ -7,11 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Schema } from '@formily/react';
|
|
||||||
import { DrawerProps, ModalProps } from 'antd';
|
|
||||||
import React, { createContext, useEffect, useRef, useState } from 'react';
|
import React, { createContext, useEffect, useRef, useState } from 'react';
|
||||||
import { useActionContext } from './hooks';
|
import { useActionContext } from './hooks';
|
||||||
import { useDataBlockRequest } from '../../../data-source';
|
import { useDataBlockRequest } from '../../../data-source';
|
||||||
|
import { ActionContextProps } from './types';
|
||||||
|
|
||||||
export const ActionContext = createContext<ActionContextProps>({});
|
export const ActionContext = createContext<ActionContextProps>({});
|
||||||
ActionContext.displayName = 'ActionContext';
|
ActionContext.displayName = 'ActionContext';
|
||||||
@ -46,21 +45,3 @@ export const ActionContextProvider: React.FC<ActionContextProps & { value?: Acti
|
|||||||
</ActionContext.Provider>
|
</ActionContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenSize = 'small' | 'middle' | 'large';
|
|
||||||
export interface ActionContextProps {
|
|
||||||
button?: any;
|
|
||||||
visible?: boolean;
|
|
||||||
setVisible?: (v: boolean) => void;
|
|
||||||
openMode?: 'drawer' | 'modal' | 'page';
|
|
||||||
snapshot?: boolean;
|
|
||||||
openSize?: OpenSize;
|
|
||||||
containerRefKey?: string;
|
|
||||||
formValueChanged?: boolean;
|
|
||||||
setFormValueChanged?: (v: boolean) => void;
|
|
||||||
fieldSchema?: Schema;
|
|
||||||
drawerProps?: DrawerProps;
|
|
||||||
modalProps?: ModalProps;
|
|
||||||
submitted?: boolean;
|
|
||||||
setSubmitted?: (v: boolean) => void;
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import { useActionContext } from '@nocobase/client';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open',
|
||||||
|
'x-component-props': {
|
||||||
|
openMode: 'drawer',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Container',
|
||||||
|
title: 'Title',
|
||||||
|
properties: {
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Container.Footer',
|
||||||
|
properties: {
|
||||||
|
close: {
|
||||||
|
title: 'Close',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': function useActionProps() {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
type: 'default',
|
||||||
|
onClick() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,59 @@
|
|||||||
|
import { ISchema, observer } from '@formily/react';
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
ActionContextProvider,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
useActionContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
drawer1: {
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
type: 'void',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
properties: {
|
||||||
|
hello1: {
|
||||||
|
'x-content': 'Hello',
|
||||||
|
title: 'T1',
|
||||||
|
},
|
||||||
|
footer1: {
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
close1: {
|
||||||
|
title: 'Close',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': function useActionProps() {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
onClick() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(() => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Form, Action, Input, FormItem }}>
|
||||||
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
|
<a onClick={() => setVisible(true)}>Open</a>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</ActionContextProvider>
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
title: 'Edit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,103 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space, App as AntdApp } from 'antd';
|
||||||
|
import { useAPIClient, useActionContext } from '@nocobase/client';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
|
||||||
|
const useCloseActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
type: 'default',
|
||||||
|
onClick() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSubmitActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { message } = AntdApp.useApp();
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'primary',
|
||||||
|
async onClick() {
|
||||||
|
// Submit the form
|
||||||
|
await form.submit();
|
||||||
|
const values = form.values;
|
||||||
|
|
||||||
|
console.log('values:', values);
|
||||||
|
const { data } = await api.request({ url: 'test', data: values, method: 'POST' });
|
||||||
|
if (data.data === 'ok') {
|
||||||
|
message.success('Submit success');
|
||||||
|
setVisible(false);
|
||||||
|
form.reset(); // 提交成功后重置表单
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open Modal',
|
||||||
|
'x-component-props': {
|
||||||
|
openSize: 'small',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Modal',
|
||||||
|
title: 'Modal Title',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Username`,
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Modal.Footer',
|
||||||
|
properties: {
|
||||||
|
close: {
|
||||||
|
title: 'Close',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
'x-use-component-props': 'useCloseActionProps',
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: 'Submit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useSubmitActionProps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
scopes: {
|
||||||
|
useSubmitActionProps,
|
||||||
|
useCloseActionProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apis: {
|
||||||
|
test: { data: 'ok' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
popover: true,
|
||||||
|
},
|
||||||
|
type: 'void',
|
||||||
|
title: 'Open',
|
||||||
|
properties: {
|
||||||
|
popover: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Popover',
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-content': 'Hello',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,77 @@
|
|||||||
|
import { ActionInitializer, SchemaInitializer } from '@nocobase/client';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const addActionButton = new SchemaInitializer({
|
||||||
|
name: 'addActionButton',
|
||||||
|
designable: true,
|
||||||
|
title: 'Configure actions',
|
||||||
|
style: {
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: 'Enable actions',
|
||||||
|
name: 'enableActions',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'action1',
|
||||||
|
title: '{{t("Action 1")}}',
|
||||||
|
Component: 'ActionInitializer',
|
||||||
|
schema: {
|
||||||
|
title: 'Action 1',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1', // unique identifier
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action2',
|
||||||
|
title: '{{t("Action 2")}}',
|
||||||
|
Component: 'ActionInitializer',
|
||||||
|
schema: {
|
||||||
|
title: 'Action 2',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2', // unique identifier
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-initializer': 'addActionButton',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
a1: {
|
||||||
|
title: 'Action 1',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1',
|
||||||
|
},
|
||||||
|
a2: {
|
||||||
|
title: 'Action 2',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaInitializers: [addActionButton],
|
||||||
|
components: {
|
||||||
|
ActionInitializer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,81 @@
|
|||||||
|
import { ActionInitializer, SchemaInitializer } from '@nocobase/client';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const addActionButton = new SchemaInitializer({
|
||||||
|
name: 'addActionButton',
|
||||||
|
designable: true,
|
||||||
|
title: 'Configure actions',
|
||||||
|
style: {
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: 'Enable actions',
|
||||||
|
name: 'enableActions',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'action1',
|
||||||
|
title: '{{t("Action 1")}}',
|
||||||
|
Component: 'ActionInitializer',
|
||||||
|
schema: {
|
||||||
|
title: 'Action 1',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1',
|
||||||
|
'x-align': 'left',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action2',
|
||||||
|
title: '{{t("Action 2")}}',
|
||||||
|
Component: 'ActionInitializer',
|
||||||
|
schema: {
|
||||||
|
title: 'Action 2',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2',
|
||||||
|
'x-align': 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-initializer': 'addActionButton',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'two-columns',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
a1: {
|
||||||
|
title: 'Action 1',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1',
|
||||||
|
'x-align': 'left',
|
||||||
|
},
|
||||||
|
a2: {
|
||||||
|
title: 'Action 2',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2',
|
||||||
|
'x-align': 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaInitializers: [addActionButton],
|
||||||
|
components: {
|
||||||
|
ActionInitializer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,23 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
ghost: true, // ButtonProps
|
||||||
|
type: 'dashed', // ButtonProps
|
||||||
|
danger: true, // ButtonProps
|
||||||
|
title: 'Open', // title
|
||||||
|
},
|
||||||
|
// title: 'Open', // It's also possible here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,35 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space, App as AntdApp } from 'antd';
|
||||||
|
import { ActionProps } from '@nocobase/client';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Delete',
|
||||||
|
'x-use-component-props': function useActionProps(): ActionProps {
|
||||||
|
const { message } = AntdApp.useApp();
|
||||||
|
|
||||||
|
return {
|
||||||
|
confirm: {
|
||||||
|
// confirm props
|
||||||
|
title: 'Delete',
|
||||||
|
content: 'Are you sure you want to delete it?',
|
||||||
|
},
|
||||||
|
onClick() {
|
||||||
|
// after confirm ok
|
||||||
|
message.success('Deleted!');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,38 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Button, Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ComponentButton = (props) => {
|
||||||
|
return <Button {...props}>Custom Component</Button>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
component: 'ComponentButton', // string type
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
component: ComponentButton, // ComponentType type
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
components: {
|
||||||
|
ComponentButton, // register custom component
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,26 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open Drawer',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,79 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
import { useActionContext } from '@nocobase/client';
|
||||||
|
|
||||||
|
const useCloseActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
type: 'default',
|
||||||
|
onClick() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSubmitActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
type: 'primary',
|
||||||
|
onClick() {
|
||||||
|
console.log('submit');
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open Drawer',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
properties: {
|
||||||
|
content: {
|
||||||
|
type: 'void',
|
||||||
|
'x-content': 'Hello',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer', // must be `Action.Drawer.Footer`
|
||||||
|
properties: {
|
||||||
|
close: {
|
||||||
|
title: 'Close',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useCloseActionProps',
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: 'Submit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useSubmitActionProps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
scopes: {
|
||||||
|
useCloseActionProps,
|
||||||
|
useSubmitActionProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apis: {
|
||||||
|
test: { data: 'ok' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,36 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
openSize: 'large', // open drawer size
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
properties: {
|
||||||
|
// Drawer content
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-content': 'Hello',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,101 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space, App as AntdApp } from 'antd';
|
||||||
|
import { useAPIClient, useActionContext } from '@nocobase/client';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
|
||||||
|
const useCloseActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
type: 'default',
|
||||||
|
onClick() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSubmitActionProps = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { message } = AntdApp.useApp();
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'primary',
|
||||||
|
async onClick() {
|
||||||
|
// Submit the form
|
||||||
|
await form.submit();
|
||||||
|
const values = form.values;
|
||||||
|
|
||||||
|
console.log('values:', values);
|
||||||
|
const { data } = await api.request({ url: 'test', data: values, method: 'POST' });
|
||||||
|
if (data.data === 'ok') {
|
||||||
|
message.success('Submit success');
|
||||||
|
setVisible(false);
|
||||||
|
form.reset(); // 提交成功后重置表单
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'Open Drawer',
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
title: 'Drawer Title',
|
||||||
|
'x-decorator': 'FormV2', // This uses the `FormV2` component.
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
// This is a form field.
|
||||||
|
type: 'string',
|
||||||
|
title: `Username`,
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
close: {
|
||||||
|
title: 'Close',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
'x-use-component-props': 'useCloseActionProps',
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: 'Submit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useSubmitActionProps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
scopes: {
|
||||||
|
useSubmitActionProps,
|
||||||
|
useCloseActionProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apis: {
|
||||||
|
test: { data: 'ok' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,68 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { Space, App as AntdApp } from 'antd';
|
||||||
|
import { useAPIClient, ActionProps } from '@nocobase/client';
|
||||||
|
|
||||||
|
const useCustomActionProps = (): ActionProps => {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { message } = AntdApp.useApp();
|
||||||
|
|
||||||
|
return {
|
||||||
|
onClick: async () => {
|
||||||
|
const { data } = await api.request({ url: 'test' });
|
||||||
|
if (data.data.result === 'ok') {
|
||||||
|
message.success('Success!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': Space,
|
||||||
|
properties: {
|
||||||
|
test1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'test1',
|
||||||
|
'x-use-component-props': useCustomActionProps, // function type
|
||||||
|
},
|
||||||
|
test2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'test2',
|
||||||
|
'x-use-component-props': function useCustomActionProps(): ActionProps {
|
||||||
|
// inline function type
|
||||||
|
const api = useAPIClient();
|
||||||
|
const { message } = AntdApp.useApp();
|
||||||
|
|
||||||
|
return {
|
||||||
|
onClick: async () => {
|
||||||
|
const { data } = await api.request({ url: 'test' });
|
||||||
|
if (data.data.result === 'ok') {
|
||||||
|
message.success('Success!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test3: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: 'test2',
|
||||||
|
'x-use-component-props': 'useCustomActionProps', // string type
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
scopes: {
|
||||||
|
useCustomActionProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apis: {
|
||||||
|
test: { data: { result: 'ok' } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,64 @@
|
|||||||
|
import { SchemaSettings } from '@nocobase/client';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const myActionSettings = new SchemaSettings({
|
||||||
|
name: 'myActionSettings',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'two-columns',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
a1: {
|
||||||
|
title: 'Action 1',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1',
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-settings': 'myActionSettings',
|
||||||
|
},
|
||||||
|
a2: {
|
||||||
|
title: 'Action 2',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2',
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-settings': 'myActionSettings',
|
||||||
|
},
|
||||||
|
a3: {
|
||||||
|
title: 'Action 3',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a1',
|
||||||
|
'x-align': 'left',
|
||||||
|
'x-settings': 'myActionSettings',
|
||||||
|
},
|
||||||
|
a4: {
|
||||||
|
title: 'Action 4',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'a2',
|
||||||
|
'x-align': 'left',
|
||||||
|
'x-settings': 'myActionSettings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaSettings: [myActionSettings],
|
||||||
|
},
|
||||||
|
designable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,46 +1,206 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Action
|
# Action
|
||||||
|
|
||||||
## Nodes
|
将按钮和各种操作结合在一起,提供了一种统一的方式来处理操作。
|
||||||
|
|
||||||
- Action
|
## Action
|
||||||
- Action.Drawer
|
|
||||||
- Action.Drawer.Footer
|
|
||||||
- Action.Modal
|
|
||||||
- Action.Modal.Footer
|
|
||||||
- Action.Drawer
|
|
||||||
- Action.Drawer.Footer
|
|
||||||
- Action.Container
|
|
||||||
- Action.Container.Footer
|
|
||||||
- ActionBar
|
|
||||||
|
|
||||||
## Examples
|
操作的触发器,默认是 ant-design 的 `Button` 组件,并为后续的弹窗或者抽屉提供上下文。
|
||||||
|
|
||||||
### Action + Action.Drawer
|
```ts
|
||||||
|
export interface ActionProps extends ButtonProps {
|
||||||
|
/**
|
||||||
|
* button title
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
/**
|
||||||
|
* custom component, replace the default Button component
|
||||||
|
*/
|
||||||
|
component?: string | ComponentType<any>;
|
||||||
|
|
||||||
### ActionContext + Action.Drawer
|
/**
|
||||||
|
* Dynamic rendering of the opened content in conjunction with `Action.Container`.
|
||||||
|
*/
|
||||||
|
openMode?: 'drawer' | 'modal' | 'page';
|
||||||
|
/**
|
||||||
|
* The size of the pop-up window, only valid when `openMode: 'modal'`
|
||||||
|
*/
|
||||||
|
openSize?: 'small' | 'middle' | 'large';
|
||||||
|
/**
|
||||||
|
* Customize the position of the pop-up window
|
||||||
|
*/
|
||||||
|
containerRefKey?: string;
|
||||||
|
|
||||||
只配置 Action.Drawer,而不需要 Action,结合 ActionContext 自定义按钮。
|
/**
|
||||||
|
* Whether to display the popover, only valid when `openMode: 'popover'`
|
||||||
|
*/
|
||||||
|
popover?: boolean;
|
||||||
|
|
||||||
<code src="./demos/demo2.tsx"></code>
|
/**
|
||||||
|
* When the button is clicked, whether a pop-up confirmation is required
|
||||||
|
*/
|
||||||
|
confirm?: false | {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 不同的打开方式
|
### Basic Usage
|
||||||
|
|
||||||
<code src="./demos/demo3.tsx"></code>
|
- `ButtonProp`
|
||||||
|
- title
|
||||||
|
|
||||||
### Action + Action.Popover
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
<code src="./demos/demo4.tsx"></code>
|
### Custom Component
|
||||||
|
|
||||||
<code src="./demos/demo5.tsx"></code>
|
- component
|
||||||
|
|
||||||
### ActionBar
|
<code src="./demos/new-demos/custom-component.tsx"></code>
|
||||||
|
|
||||||
<code src="./demos/demo6.tsx"></code>
|
### Dynamic Props
|
||||||
|
|
||||||
|
这里使用了 `x-use-component-props` 的能力,具体可以查看 [x-use-component-props](https://docs.nocobase.com/development/client/ui-schema/what-is-ui-schema#x-component-props-%E5%92%8C-x-use-component-props)。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/dynamic-props.tsx"></code>
|
||||||
|
|
||||||
|
### Confirm
|
||||||
|
|
||||||
|
- confirm
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/confirm.tsx"></code>
|
||||||
|
|
||||||
|
## Action.Link
|
||||||
|
|
||||||
|
将 `Button` 组件替换为 `a` 标签。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/action-link.tsx"></code>
|
||||||
|
|
||||||
|
## Action.Drawer
|
||||||
|
|
||||||
|
主要用于在右侧弹出一个抽屉。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ActionDrawer extends DrawerProps {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
- `DrawerProps`
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/drawer-basic.tsx"></code>
|
||||||
|
|
||||||
|
### openSize
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/drawer-openSize.tsx"></code>
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
|
||||||
|
Footer 可以放一些按钮,比如取消或者提交等。
|
||||||
|
|
||||||
|
其 Schema `x-component` 必须为 `Action.Drawer.Footer` 组件。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/drawer-footer.tsx"></code>
|
||||||
|
|
||||||
|
### With Form
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/drawer-with-form.tsx"></code>
|
||||||
|
|
||||||
|
## Action.Modal
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ActionModal extends ModalProps {}
|
||||||
|
```
|
||||||
|
|
||||||
|
其用法和 `Action.Drawer` 类似,这里只举一个例子。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/action-modal.tsx"></code>
|
||||||
|
|
||||||
|
## Action.Popover
|
||||||
|
|
||||||
|
注意,此时 Action 的 `popover` 属性必须为 `true`。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/action-popover.tsx"></code>
|
||||||
|
|
||||||
|
## Action.Container
|
||||||
|
|
||||||
|
当根据需要动态渲染内容时,可以使用 `Action.Container` + Action `openMode` 属性动态决定。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/action-container.tsx"></code>
|
||||||
|
|
||||||
|
## ActionBar
|
||||||
|
|
||||||
|
一般用于区块的顶部的操作按钮,其内部会自动处理布局和渲染 [schema-initializer](/core/ui-schema/schema-initializer)。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { SpaceProps } from 'antd'
|
||||||
|
|
||||||
|
interface ActionBarProps {
|
||||||
|
layout?: 'one-column' | 'two-columns';
|
||||||
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
|
spaceProps?: SpaceProps;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### one-column
|
||||||
|
|
||||||
|
一列布局,全部靠左。
|
||||||
|
|
||||||
|
Schema 中的 `x-action` 是按钮的唯一标识,不能和已有的重复,用于 `ActionInitializer` 的查找和删除。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/actionbar-one-column.tsx"></code>
|
||||||
|
|
||||||
|
### two-columns
|
||||||
|
|
||||||
|
左右布局,通过 `x-align` 控制。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/actionbar-two-columns.tsx"></code>
|
||||||
|
|
||||||
|
## ActionContext
|
||||||
|
|
||||||
|
封装在 `Action` 组件内部,用于传递上下文。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type OpenSize = 'small' | 'middle' | 'large';
|
||||||
|
export interface ActionContextProps {
|
||||||
|
button?: React.JSX.Element;
|
||||||
|
visible?: boolean;
|
||||||
|
setVisible?: (v: boolean) => void;
|
||||||
|
openMode?: 'drawer' | 'modal' | 'page';
|
||||||
|
snapshot?: boolean;
|
||||||
|
openSize?: OpenSize;
|
||||||
|
/**
|
||||||
|
* Customize the position of the pop-up window
|
||||||
|
*/
|
||||||
|
containerRefKey?: string;
|
||||||
|
formValueChanged?: boolean;
|
||||||
|
setFormValueChanged?: (v: boolean) => void;
|
||||||
|
fieldSchema?: Schema;
|
||||||
|
drawerProps?: DrawerProps;
|
||||||
|
modalProps?: ModalProps;
|
||||||
|
submitted?: boolean;
|
||||||
|
setSubmitted?: (v: boolean) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
假设 Action 组件无法满足需求,我们可以直接使用 ActionContext 组件进行自定义。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/action-context.tsx"></code>
|
||||||
|
|
||||||
|
## ActionSchemaToolbar
|
||||||
|
|
||||||
|
用于单个按钮渲染 [SchemaToolbar](/core/ui-schema/schema-toolbar) 和 [SchemaSettings](/core/ui-schema/schema-settings)。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/schema-toolbar.tsx"></code>
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
### useActionContext()
|
||||||
|
|
||||||
|
获取 `ActionContext` 上下文。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { visible, setVisible, fieldSchema } = useActionContext();
|
||||||
|
```
|
||||||
|
@ -16,3 +16,4 @@ export * from './hooks/useGetAriaLabelOfDrawer';
|
|||||||
export * from './hooks/useGetAriaLabelOfModal';
|
export * from './hooks/useGetAriaLabelOfModal';
|
||||||
export * from './hooks/useGetAriaLabelOfPopover';
|
export * from './hooks/useGetAriaLabelOfPopover';
|
||||||
export * from './Action.Designer';
|
export * from './Action.Designer';
|
||||||
|
export * from './types';
|
||||||
|
@ -8,14 +8,79 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ButtonProps, DrawerProps, ModalProps } from 'antd';
|
import { ButtonProps, DrawerProps, ModalProps } from 'antd';
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
import { Schema } from '@formily/react';
|
||||||
|
|
||||||
export type ActionProps = ButtonProps & {
|
export type OpenSize = 'small' | 'middle' | 'large';
|
||||||
component?: any;
|
export interface ActionContextProps {
|
||||||
useAction?: () => {
|
button?: React.JSX.Element;
|
||||||
run(): Promise<void>;
|
visible?: boolean;
|
||||||
};
|
setVisible?: (v: boolean) => void;
|
||||||
|
openMode?: 'drawer' | 'modal' | 'page';
|
||||||
|
snapshot?: boolean;
|
||||||
|
openSize?: OpenSize;
|
||||||
|
/**
|
||||||
|
* Customize the position of the pop-up window
|
||||||
|
*/
|
||||||
|
containerRefKey?: string;
|
||||||
|
formValueChanged?: boolean;
|
||||||
|
setFormValueChanged?: (v: boolean) => void;
|
||||||
|
fieldSchema?: Schema;
|
||||||
|
drawerProps?: DrawerProps;
|
||||||
|
modalProps?: ModalProps;
|
||||||
|
submitted?: boolean;
|
||||||
|
setSubmitted?: (v: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseActionType = (callback?: () => void) => {
|
||||||
|
run: () => void | Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ActionProps extends ButtonProps {
|
||||||
|
/**
|
||||||
|
* button title
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom component, replace the default Button component
|
||||||
|
*/
|
||||||
|
component?: string | ComponentType<any>;
|
||||||
|
|
||||||
|
openMode?: ActionContextProps['openMode'];
|
||||||
|
openSize?: ActionContextProps['openSize'];
|
||||||
|
containerRefKey?: ActionContextProps['containerRefKey'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to display the popover, only valid when `openMode: 'popover'`
|
||||||
|
*/
|
||||||
|
popover?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the button is clicked, whether a pop-up confirmation is required
|
||||||
|
*/
|
||||||
|
confirm?:
|
||||||
|
| false
|
||||||
|
| {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
useAction?: string | UseActionType;
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
actionCallback?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
addChild?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type ComposedAction = React.FC<ActionProps> & {
|
export type ComposedAction = React.FC<ActionProps> & {
|
||||||
Drawer?: ComposedActionDrawer;
|
Drawer?: ComposedActionDrawer;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'AssociationSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
service: {
|
||||||
|
resource: 'roles', // roles 表
|
||||||
|
action: 'list', // 列表接口
|
||||||
|
},
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title', // 显示的字段
|
||||||
|
value: 'name', // 值字段
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delayResponse: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,32 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'array', // 数组类型
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'AssociationSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true, // 多选
|
||||||
|
service: {
|
||||||
|
resource: 'roles',
|
||||||
|
action: 'list',
|
||||||
|
},
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delayResponse: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
default: 'admin',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'AssociationSelect',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
'x-component-props': {
|
||||||
|
service: {
|
||||||
|
resource: 'roles',
|
||||||
|
action: 'list',
|
||||||
|
},
|
||||||
|
fieldNames: {
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delayResponse: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,27 +1,24 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# AssociationSelect
|
# AssociationSelect
|
||||||
|
|
||||||
## Examples
|
通过指定数据表和字段,获取数据表的数据。
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
基于 Ant Design 的 [Select](https://ant.design/components/select/#API),相关扩展属性有:
|
|
||||||
|
|
||||||
- `objectValue` 值为 object 类型
|
|
||||||
- `fieldNames` 默认值有区别
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const defaultFieldNames = {
|
type AssociationSelectProps<P = any> = RemoteSelectProps<P> & {
|
||||||
label: 'label',
|
action?: string;
|
||||||
value: 'value',
|
multiple?: boolean;
|
||||||
color: 'color',
|
|
||||||
options: 'children',
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Multiple Selection
|
||||||
|
|
||||||
|
`type` 需要改为 `array`,并且属性需要增加 `multiple: true`。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/multiple.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/read-pretty.tsx"></code>
|
||||||
|
@ -10,11 +10,14 @@
|
|||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { AutoComplete as AntdAutoComplete } from 'antd';
|
import { AutoComplete as AntdAutoComplete } from 'antd';
|
||||||
import { ReadPretty } from '../input';
|
import { ReadPretty } from '../input';
|
||||||
|
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
export const AutoComplete = connect(
|
export const AutoComplete = withDynamicSchemaProps(
|
||||||
AntdAutoComplete,
|
connect(
|
||||||
mapProps({
|
AntdAutoComplete,
|
||||||
dataSource: 'options',
|
mapProps({
|
||||||
}),
|
dataSource: 'options',
|
||||||
mapReadPretty(ReadPretty.Input),
|
}),
|
||||||
|
mapReadPretty(ReadPretty.Input),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const mockVal = (str: string, repeat = 1) => ({
|
||||||
|
value: str.repeat(repeat),
|
||||||
|
});
|
||||||
|
const getPanelValue = (searchText: string) =>
|
||||||
|
!searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)];
|
||||||
|
|
||||||
|
function useAutoCompleteProps() {
|
||||||
|
const field = useField<any>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
onSearch(text: string) {
|
||||||
|
field.dataSource = getPanelValue(text);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'AutoComplete',
|
||||||
|
'x-use-component-props': useAutoCompleteProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: 'Test',
|
||||||
|
default: 'aaa',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'AutoComplete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
# AutoComplete
|
||||||
|
|
||||||
|
自动完成输出框。其基于 ant-design [AutoComplete](https://ant.design/components/auto-complete) 组件封装。
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type AutoCompleteProps = AntdAutoCompleteProps;
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type AutoCompleteReadPrettyProps = InputReadPrettyProps;
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/read-pretty.tsx"></code>
|
@ -67,7 +67,13 @@ const useStyles = createStyles(({ css, token }: CustomCreateStylesUtils) => {
|
|||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BlockItem: React.FC<any> = withDynamicSchemaProps(
|
export interface BlockItemProps {
|
||||||
|
name?: string;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockItem: React.FC<BlockItemProps> = withDynamicSchemaProps(
|
||||||
(props) => {
|
(props) => {
|
||||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
const { className, children } = useProps(props);
|
const { className, children } = useProps(props);
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { DragHandler, SchemaSettings } from '@nocobase/client';
|
||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { observer } from '@formily/reactive-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const simpleSettings = new SchemaSettings({
|
||||||
|
name: 'simpleSettings',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const MyBlock = observer(
|
||||||
|
() => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="nc-block-item"
|
||||||
|
style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}
|
||||||
|
>
|
||||||
|
{fieldSchema.name}
|
||||||
|
<DragHandler />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ displayName: 'MyBlock' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
designable: true,
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': 'DndContext',
|
||||||
|
properties: {
|
||||||
|
block1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'BlockItem',
|
||||||
|
'x-component': 'MyBlock',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
},
|
||||||
|
block2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'BlockItem',
|
||||||
|
'x-component': 'MyBlock',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
},
|
||||||
|
block3: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'BlockItem',
|
||||||
|
'x-component': 'MyBlock',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaSettings: [simpleSettings],
|
||||||
|
components: {
|
||||||
|
MyBlock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,11 +1,22 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# BlockItem
|
# BlockItem
|
||||||
|
|
||||||
普通的装饰器(Decorator)组件,无特殊 UI 效果,一般用在 x-decorator 中。用于提供区块的管理,如拖拽功能、当前节点的 SettingsForm。CardItem 和 FormItem 组件都是基于 BlockItem 实现,也具备以上相同功能。
|
普通的装饰器(Decorator)组件,无 UI 效果,一般用在 `x-decorator` 中。
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
主要提供了以下 2 个能力:
|
||||||
|
|
||||||
|
- 拖拽功能
|
||||||
|
- [SchemaToolbar](/core/ui-schema/schema-toolbar) 和 [SchemaSettings](/core/ui-schema/schema-settings) 的渲染
|
||||||
|
|
||||||
|
[CardItem](/components/card-item) 和 [FormItem](/components/form-item) 组件都是基于 BlockItem 实现,也具备以上相同功能。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BlockItemProps {
|
||||||
|
name?: string;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意拖拽功能需要配置 `DndContext` 组件。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
@ -8,32 +8,38 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton, CardProps } from 'antd';
|
||||||
import React from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { IntersectionOptions, useInView } from 'react-intersection-observer';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { BlockItem } from '../block-item';
|
import { BlockItem } from '../block-item';
|
||||||
import { BlockItemCard } from '../block-item/BlockItemCard';
|
import { BlockItemCard } from '../block-item/BlockItemCard';
|
||||||
import { BlockItemError } from '../block-item/BlockItemError';
|
import { BlockItemError } from '../block-item/BlockItemError';
|
||||||
import useStyles from './style';
|
import useStyles from './style';
|
||||||
|
|
||||||
interface Props {
|
interface CardItemProps extends CardProps {
|
||||||
children?: React.ReactNode;
|
|
||||||
/** 区块标识 */
|
|
||||||
name?: string;
|
name?: string;
|
||||||
[key: string]: any;
|
children?: React.ReactNode;
|
||||||
|
/**
|
||||||
|
* lazy render options
|
||||||
|
* @default { threshold: 0, initialInView: true, triggerOnce: true }
|
||||||
|
* @see https://github.com/thebuilder/react-intersection-observer
|
||||||
|
*/
|
||||||
|
lazyRender?: IntersectionOptions & { element?: React.JSX.Element };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CardItem = (props: Props) => {
|
export const CardItem: FC<CardItemProps> = (props) => {
|
||||||
const { children, name, ...restProps } = props;
|
const { children, name, lazyRender = {}, ...restProps } = props;
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const templateKey = fieldSchema?.['x-template-key'];
|
const templateKey = fieldSchema?.['x-template-key'];
|
||||||
|
const { element: lazyRenderElement, ...resetLazyRenderOptions } = lazyRender;
|
||||||
const { ref, inView } = useInView({
|
const { ref, inView } = useInView({
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
initialInView: true,
|
initialInView: true,
|
||||||
triggerOnce: true,
|
triggerOnce: true,
|
||||||
skip: !!process.env.__E2E__,
|
skip: !!process.env.__E2E__,
|
||||||
|
...resetLazyRenderOptions,
|
||||||
});
|
});
|
||||||
const { wrapSSR, componentCls, hashId } = useStyles();
|
const { wrapSSR, componentCls, hashId } = useStyles();
|
||||||
|
|
||||||
@ -42,7 +48,7 @@ export const CardItem = (props: Props) => {
|
|||||||
<BlockItemError>
|
<BlockItemError>
|
||||||
<BlockItem name={name} className={`${componentCls} ${hashId} noco-card-item`}>
|
<BlockItem name={name} className={`${componentCls} ${hashId} noco-card-item`}>
|
||||||
<BlockItemCard ref={ref} {...restProps}>
|
<BlockItemCard ref={ref} {...restProps}>
|
||||||
{inView ? props.children : <Skeleton paragraph={{ rows: 4 }} />}
|
{inView ? props.children : lazyRenderElement ?? <Skeleton paragraph={{ rows: 4 }} />}
|
||||||
</BlockItemCard>
|
</BlockItemCard>
|
||||||
</BlockItem>
|
</BlockItem>
|
||||||
</BlockItemError>,
|
</BlockItemError>,
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { SchemaSettings } from '@nocobase/client';
|
||||||
|
|
||||||
|
const simpleSettings = new SchemaSettings({
|
||||||
|
name: 'simpleSettings',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
designable: true,
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': 'DndContext',
|
||||||
|
properties: {
|
||||||
|
block1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-component-props': {
|
||||||
|
title: 'Block 1',
|
||||||
|
},
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-content': 'Hello Card!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
block2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
'x-component-props': {
|
||||||
|
title: 'Block 2',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-content': 'Hello Card!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaSettings: [simpleSettings],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,84 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import { SchemaSettings } from '@nocobase/client';
|
||||||
|
|
||||||
|
const simpleSettings = new SchemaSettings({
|
||||||
|
name: 'simpleSettings',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'remove',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
designable: true,
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
height: 300,
|
||||||
|
overflow: 'auto',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
block1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-component-props': {
|
||||||
|
title: 'Block 1',
|
||||||
|
},
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-content': 'Hello Card!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
block2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
'x-component-props': {
|
||||||
|
title: 'Block 2',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-content': 'Hello Card!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
block3: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-settings': 'simpleSettings',
|
||||||
|
'x-component-props': {
|
||||||
|
title: 'Block 3',
|
||||||
|
lazyRender: {
|
||||||
|
threshold: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hello: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
'x-content': 'Hello Card!',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
schemaSettings: [simpleSettings],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,13 +1,27 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# CardItem
|
# CardItem
|
||||||
|
|
||||||
卡片装饰器。除此之外,也继承了 BlockItem 的功能。
|
卡片装饰器。主要功能为:
|
||||||
|
|
||||||
## Example
|
- 拖拽和 [SchemaToolbar](/core/ui-schema/schema-toolbar) 和 [SchemaSettings](/core/ui-schema/schema-settings) 的渲染,继承自[BlockItem](/components/block-item)
|
||||||
|
- 懒渲染
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx" ></code>
|
其基于 ant-design [Card](https://ant.design/components/card-cn/) 组件进行封装,懒加载基于 [react-intersection-observer](https://github.com/thebuilder/react-intersection-observer) 实现。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CardItemProps extends CardProps {
|
||||||
|
name?: string;
|
||||||
|
/**
|
||||||
|
* lazy render options
|
||||||
|
* @see https://github.com/thebuilder/react-intersection-observer
|
||||||
|
*/
|
||||||
|
lazyRender?: IntersectionOptions;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx" ></code>
|
||||||
|
|
||||||
|
## Custom lazy render
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/lazy-render.tsx" ></code>
|
||||||
|
@ -11,107 +11,140 @@ import { LoadingOutlined } from '@ant-design/icons';
|
|||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||||
import { toArr } from '@formily/shared';
|
import { toArr } from '@formily/shared';
|
||||||
import { Cascader as AntdCascader, Space } from 'antd';
|
import { Cascader as AntdCascader, Space, CascaderProps as AntdCascaderProps } from 'antd';
|
||||||
import { isBoolean, omit } from 'lodash';
|
import { isBoolean, omit } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRequest } from '../../../api-client';
|
import { UseRequestResult, useRequest } from '../../../api-client';
|
||||||
import { ReadPretty } from './ReadPretty';
|
import { ReadPretty } from './ReadPretty';
|
||||||
import { defaultFieldNames } from './defaultFieldNames';
|
import { defaultFieldNames } from './defaultFieldNames';
|
||||||
|
import { BaseOptionType } from 'antd/es/select';
|
||||||
|
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
const useDefDataSource = (options) => {
|
const useDefDataSource = (options, props: any) => {
|
||||||
const field = useField<ArrayField>();
|
const field = useField<ArrayField>();
|
||||||
return useRequest(() => Promise.resolve({ data: field.dataSource || [] }), options);
|
return useRequest(() => Promise.resolve({ data: field.dataSource || props.options || [] }), {
|
||||||
|
...options,
|
||||||
|
refreshDeps: [field.dataSource, props.options],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const useDefLoadData = (props: any) => {
|
const useDefLoadData = (props: any) => {
|
||||||
return props?.loadData;
|
return props?.loadData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Cascader = connect(
|
export type CascaderProps<DataNodeType extends BaseOptionType = any> = AntdCascaderProps<DataNodeType> & {
|
||||||
(props: any) => {
|
/**
|
||||||
const field = useField<ArrayField>();
|
* @deprecated use `x-use-component-props` instead
|
||||||
const {
|
*/
|
||||||
value,
|
useLoadData: (props: CascaderProps) => AntdCascaderProps['loadData'];
|
||||||
onChange,
|
/**
|
||||||
labelInValue,
|
* @deprecated use `x-use-component-props` instead
|
||||||
// fieldNames = defaultFieldNames,
|
*/
|
||||||
useDataSource = useDefDataSource,
|
useDataSource?: (options: any) => UseRequestResult<unknown>;
|
||||||
useLoadData = useDefLoadData,
|
/**
|
||||||
changeOnSelectLast,
|
* Whether to wrap the label of option into the value
|
||||||
changeOnSelect,
|
*/
|
||||||
maxLevel,
|
labelInValue?: boolean;
|
||||||
...others
|
/**
|
||||||
} = props;
|
* must select the last level
|
||||||
const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
|
*/
|
||||||
const loadData = useLoadData(props);
|
changeOnSelectLast?: boolean;
|
||||||
const { loading, run } = useDataSource({
|
onChange?: (value: any) => void;
|
||||||
onSuccess(data) {
|
maxLevel?: number;
|
||||||
field.dataSource = data?.data || [];
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
// 兼容值为 object[] 的情况
|
|
||||||
const toValue = () => {
|
|
||||||
return toArr(value).map((item) => {
|
|
||||||
if (typeof item === 'object') {
|
|
||||||
return item[fieldNames.value];
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const displayRender = (labels: string[], selectedOptions: any[]) => {
|
|
||||||
return (
|
|
||||||
<Space split={'/'}>
|
|
||||||
{labels.map((label, index) => {
|
|
||||||
if (selectedOptions[index]) {
|
|
||||||
return <span key={index}>{label}</span>;
|
|
||||||
}
|
|
||||||
const item = toArr(value)
|
|
||||||
.filter(Boolean)
|
|
||||||
.find((item) => item[fieldNames.value] === label);
|
|
||||||
return <span key={index}>{item?.[fieldNames.label] || label}</span>;
|
|
||||||
})}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const handelDropDownVisible = (value) => {
|
|
||||||
if (value && !field.dataSource?.length) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
export const Cascader = withDynamicSchemaProps(
|
||||||
<AntdCascader
|
connect(
|
||||||
loading={loading}
|
(props: CascaderProps) => {
|
||||||
{...others}
|
const field = useField<ArrayField>();
|
||||||
options={field.dataSource}
|
const {
|
||||||
loadData={loadData}
|
value,
|
||||||
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
|
onChange,
|
||||||
value={toValue()}
|
labelInValue,
|
||||||
fieldNames={fieldNames}
|
options,
|
||||||
displayRender={displayRender}
|
// fieldNames = defaultFieldNames,
|
||||||
onDropdownVisibleChange={handelDropDownVisible}
|
useDataSource = useDefDataSource,
|
||||||
onChange={(value, selectedOptions) => {
|
useLoadData = useDefLoadData,
|
||||||
if (value && labelInValue) {
|
changeOnSelectLast,
|
||||||
onChange(selectedOptions.map((option) => omit(option, [fieldNames.children])));
|
changeOnSelect,
|
||||||
} else {
|
maxLevel,
|
||||||
onChange(value);
|
...others
|
||||||
|
} = props;
|
||||||
|
const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
|
||||||
|
const loadData = useLoadData(props);
|
||||||
|
const { loading, run } = useDataSource(
|
||||||
|
{
|
||||||
|
onSuccess(data) {
|
||||||
|
field.dataSource = data?.data || [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
);
|
||||||
|
// 兼容值为 object[] 的情况
|
||||||
|
const toValue = () => {
|
||||||
|
return toArr(value).map((item) => {
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
return item[fieldNames.value];
|
||||||
}
|
}
|
||||||
}}
|
return item;
|
||||||
/>
|
});
|
||||||
);
|
|
||||||
},
|
|
||||||
mapProps(
|
|
||||||
{
|
|
||||||
dataSource: 'options',
|
|
||||||
},
|
|
||||||
(props, field) => {
|
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
|
|
||||||
};
|
};
|
||||||
|
const displayRender = (labels: string[], selectedOptions: any[]) => {
|
||||||
|
return (
|
||||||
|
<Space split={'/'}>
|
||||||
|
{labels.map((label, index) => {
|
||||||
|
if (selectedOptions[index]) {
|
||||||
|
return <span key={index}>{label}</span>;
|
||||||
|
}
|
||||||
|
const item = toArr(value)
|
||||||
|
.filter(Boolean)
|
||||||
|
.find((item) => item[fieldNames.value] === label);
|
||||||
|
return <span key={index}>{item?.[fieldNames.label] || label}</span>;
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handelDropDownVisible = (value) => {
|
||||||
|
if (value && !field.dataSource?.length) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AntdCascader
|
||||||
|
loading={loading}
|
||||||
|
{...others}
|
||||||
|
options={field.dataSource}
|
||||||
|
loadData={loadData}
|
||||||
|
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
|
||||||
|
value={toValue()}
|
||||||
|
fieldNames={fieldNames}
|
||||||
|
displayRender={displayRender}
|
||||||
|
onDropdownVisibleChange={handelDropDownVisible}
|
||||||
|
onChange={(value, selectedOptions: any) => {
|
||||||
|
if (value && labelInValue) {
|
||||||
|
onChange(selectedOptions.map((option) => omit(option, [fieldNames.children])));
|
||||||
|
} else {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
mapProps(
|
||||||
|
{
|
||||||
|
dataSource: 'options',
|
||||||
|
},
|
||||||
|
(props, field) => {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
mapReadPretty(ReadPretty),
|
||||||
),
|
),
|
||||||
mapReadPretty(ReadPretty),
|
{ displayName: 'Cascader' },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Cascader;
|
export default Cascader;
|
||||||
|
@ -13,7 +13,18 @@ import { toArr } from '@formily/shared';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defaultFieldNames } from './defaultFieldNames';
|
import { defaultFieldNames } from './defaultFieldNames';
|
||||||
|
|
||||||
export const ReadPretty: React.FC<unknown> = (props: any) => {
|
interface FieldNames {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CascaderReadPrettyProps {
|
||||||
|
fieldNames?: FieldNames;
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReadPretty: React.FC<CascaderReadPrettyProps> = (props) => {
|
||||||
const { fieldNames = defaultFieldNames } = props;
|
const { fieldNames = defaultFieldNames } = props;
|
||||||
const values = toArr(props.value);
|
const values = toArr(props.value);
|
||||||
const len = values.length;
|
const len = values.length;
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: 'Hangzhou',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: 'West Lake',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: 'Nanjing',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: 'Zhong Hua Men',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: options,
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,59 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: 'Hangzhou',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: 'West Lake',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: 'Nanjing',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: 'Zhong Hua Men',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: options,
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
'x-component-props': {
|
||||||
|
changeOnSelectLast: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,59 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: 'Hangzhou',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: 'West Lake',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: 'Nanjing',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: 'Zhong Hua Men',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: options,
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
'x-component-props': {
|
||||||
|
labelInValue: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,76 @@
|
|||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
value?: string | number | null;
|
||||||
|
label: React.ReactNode;
|
||||||
|
children?: Option[];
|
||||||
|
isLeaf?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionLists: Option[] = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
isLeaf: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
isLeaf: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const useCustomCascaderProps = () => {
|
||||||
|
const field = useField<any>();
|
||||||
|
field.dataSource = optionLists;
|
||||||
|
const loadData = (selectedOptions: Option[]) => {
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
|
||||||
|
// load options lazily
|
||||||
|
setTimeout(() => {
|
||||||
|
targetOption.children = [
|
||||||
|
{
|
||||||
|
label: `${targetOption.label} Dynamic 1`,
|
||||||
|
value: 'dynamic1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `${targetOption.label} Dynamic 2`,
|
||||||
|
value: 'dynamic2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
field.dataSource = [...field.dataSource];
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
changeOnSelect: true,
|
||||||
|
loadData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
'x-use-component-props': 'useCustomCascaderProps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
appOptions: {
|
||||||
|
scopes: {
|
||||||
|
useCustomCascaderProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,58 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: 'Hangzhou',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: 'West Lake',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: 'Nanjing',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: 'Zhong Hua Men',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
default: ['zhejiang', 'hangzhou', 'xihu'],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: options,
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,36 +1,53 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Cascader
|
# Cascader
|
||||||
|
|
||||||
## Examples
|
级联选择器,其基于 ant-design [Cascader](https://ant.design/components/cascader-cn/) 组件封装。
|
||||||
|
|
||||||
### Cascader
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
||||||
|
|
||||||
### Asynchronous Data Source
|
|
||||||
|
|
||||||
<code src="./demos/demo2.tsx"></code>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
基于 antd 的 [Cascader](https://ant.design/components/cascader/#API) 附加的一些属性:
|
|
||||||
|
|
||||||
- `labelInValue` 是否把每个选项的 label 包装到 value 中
|
|
||||||
- `changeOnSelectLast` 必须选到最后一级
|
|
||||||
- `useLoadData` 可调用 hook 的 loadData
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
type CascaderProps<DataNodeType extends BaseOptionType = any> = AntdCascaderProps<DataNodeType> & {
|
||||||
useLoadData: (props) => {
|
/**
|
||||||
// 这里可以写 hook
|
* Whether to wrap the label of option into the value
|
||||||
return function loadData(selectedOptions) {
|
*/
|
||||||
// Cascader 的 loadData
|
labelInValue?: boolean;
|
||||||
}
|
/**
|
||||||
}
|
* must select the last level
|
||||||
|
*/
|
||||||
|
changeOnSelectLast?: boolean;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Asynchronous Data Source
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/loadData.tsx"></code>
|
||||||
|
|
||||||
|
## labelInValue
|
||||||
|
|
||||||
|
如果设置 `labelInValue` 为 `true`,则选中的数据为 `{ label: string, value: string }` 格式,否则为 `string` 格式。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/labelInValue.tsx"></code>
|
||||||
|
|
||||||
|
## changeOnSelectLast
|
||||||
|
|
||||||
|
如果设置 `changeOnSelectLast` 为 `true`,则必须选择最后一级,如果为 `false`,则可以选择任意级。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/changeOnSelectLast.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface FieldNames {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CascaderReadPrettyProps {
|
||||||
|
fieldNames?: FieldNames;
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/read-pretty.tsx"></code>
|
||||||
|
@ -11,21 +11,29 @@ import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
|||||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||||
import { isValid } from '@formily/shared';
|
import { isValid } from '@formily/shared';
|
||||||
import { Checkbox as AntdCheckbox, Tag } from 'antd';
|
import { Checkbox as AntdCheckbox, Tag } from 'antd';
|
||||||
import type { CheckboxGroupProps, CheckboxProps } from 'antd/es/checkbox';
|
import type {
|
||||||
|
CheckboxGroupProps as AntdCheckboxGroupProps,
|
||||||
|
CheckboxProps as AntdCheckboxProps,
|
||||||
|
} from 'antd/es/checkbox';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
import React, { useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { useCollectionField } from '../../../data-source/collection-field/CollectionFieldProvider';
|
import { useCollectionField } from '../../../data-source/collection-field/CollectionFieldProvider';
|
||||||
import { EllipsisWithTooltip } from '../input/EllipsisWithTooltip';
|
import { EllipsisWithTooltip } from '../input/EllipsisWithTooltip';
|
||||||
|
|
||||||
type ComposedCheckbox = React.ForwardRefExoticComponent<
|
type ComposedCheckbox = React.ForwardRefExoticComponent<
|
||||||
Pick<Partial<any>, string | number | symbol> & React.RefAttributes<unknown>
|
Pick<Partial<any>, string | number | symbol> & React.RefAttributes<unknown>
|
||||||
> & {
|
> & {
|
||||||
Group?: React.FC<CheckboxGroupProps>;
|
Group?: React.FC<AntdCheckboxGroupProps>;
|
||||||
__ANT_CHECKBOX?: boolean;
|
__ANT_CHECKBOX?: boolean;
|
||||||
ReadPretty?: React.FC<CheckboxProps>;
|
ReadPretty?: React.FC<CheckboxReadPrettyProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReadPretty = (props) => {
|
export interface CheckboxReadPrettyProps {
|
||||||
|
showUnchecked?: boolean;
|
||||||
|
value?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReadPretty: FC<CheckboxReadPrettyProps> = (props) => {
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
return <CheckOutlined style={{ color: '#52c41a' }} />;
|
return <CheckOutlined style={{ color: '#52c41a' }} />;
|
||||||
}
|
}
|
||||||
@ -33,7 +41,7 @@ const ReadPretty = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Checkbox: ComposedCheckbox = connect(
|
export const Checkbox: ComposedCheckbox = connect(
|
||||||
(props: any) => {
|
(props: AntdCheckboxProps) => {
|
||||||
const changeHandler = (val) => {
|
const changeHandler = (val) => {
|
||||||
props?.onChange(val);
|
props?.onChange(val);
|
||||||
};
|
};
|
||||||
@ -52,12 +60,17 @@ Checkbox.ReadPretty.displayName = 'Checkbox.ReadPretty';
|
|||||||
|
|
||||||
Checkbox.__ANT_CHECKBOX = true;
|
Checkbox.__ANT_CHECKBOX = true;
|
||||||
|
|
||||||
|
export interface CheckboxGroupReadPrettyProps {
|
||||||
|
value?: any[];
|
||||||
|
ellipsis?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
Checkbox.Group = connect(
|
Checkbox.Group = connect(
|
||||||
AntdCheckbox.Group,
|
AntdCheckbox.Group,
|
||||||
mapProps({
|
mapProps({
|
||||||
dataSource: 'options',
|
dataSource: 'options',
|
||||||
}),
|
}),
|
||||||
mapReadPretty((props) => {
|
mapReadPretty((props: CheckboxGroupReadPrettyProps) => {
|
||||||
if (!isValid(props.value)) {
|
if (!isValid(props.value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,57 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: '选项1',
|
||||||
|
value: 1,
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '选项2',
|
||||||
|
value: 2,
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '选项3',
|
||||||
|
value: 3,
|
||||||
|
color: 'yellow',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test1: {
|
||||||
|
type: 'array',
|
||||||
|
default: [1, 2],
|
||||||
|
title: 'Test1',
|
||||||
|
enum: options,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox.Group',
|
||||||
|
},
|
||||||
|
test2: {
|
||||||
|
type: 'array',
|
||||||
|
default: [1, 2, 3],
|
||||||
|
title: 'Test2',
|
||||||
|
enum: options,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox.Group',
|
||||||
|
'x-decorator-props': {
|
||||||
|
style: {
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,34 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: '选项1',
|
||||||
|
value: 1,
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '选项2',
|
||||||
|
value: 2,
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'array',
|
||||||
|
title: 'Test',
|
||||||
|
enum: options,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox.Group',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,39 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
title: 'Test1',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
|
test2: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
title: 'Test2',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
|
test3: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
title: 'Test3',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-component-props': {
|
||||||
|
showUnchecked: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,17 +1,46 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# Checkbox
|
# Checkbox
|
||||||
|
|
||||||
## Examples
|
复选框,其基于 ant-design [Checkbox](https://ant.design/components/checkbox/) 组件封装。
|
||||||
|
|
||||||
### 勾选
|
|
||||||
|
|
||||||
<code src="./demos/checkbox.tsx"></code>
|
## Basic Usage
|
||||||
|
|
||||||
### 组
|
```ts
|
||||||
|
type CheckboxProps = AntdCheckboxProps;
|
||||||
|
```
|
||||||
|
|
||||||
<code src="./demos/checkbox.group.tsx"></code>
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CheckboxReadPrettyProps {
|
||||||
|
showUnchecked?: boolean;
|
||||||
|
value?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果值为 `false`,默认情况下不显示内容,可以通过 `showUnchecked` 属性来显示未选中的复选框。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/read-pretty.tsx"></code>
|
||||||
|
|
||||||
|
## Checkbox Group
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type CheckboxGroupProps = CheckboxGroupProps;
|
||||||
|
```
|
||||||
|
|
||||||
|
注意 schema 的 type 属性为 `array`。
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/group.tsx"></code>
|
||||||
|
|
||||||
|
## Checkbox Group Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface CheckboxGroupReadPrettyProps {
|
||||||
|
value?: any[];
|
||||||
|
ellipsis?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/group-read-pretty.tsx"></code>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,23 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'array',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'multiple',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'users',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,8 +1,24 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# CollectionSelect
|
# CollectionSelect
|
||||||
|
|
||||||
## Example
|
用于选择当前数据源的数据表。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type CollectionSelectProps = SelectProps<any, any> & {
|
||||||
|
filter?: (item: any, index: number, array: any[]) => boolean;
|
||||||
|
isTableOid?: boolean;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
<code src="./demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Multiple Selection
|
||||||
|
|
||||||
|
`type` 需要改为 `array`,并且属性需要增加 `mode: 'multiple'`。
|
||||||
|
|
||||||
|
<code src="./demos/multiple.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
<code src="./demos/read-pretty.tsx"></code>
|
||||||
|
@ -10,12 +10,16 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { ColorPicker as AntdColorPicker } from 'antd';
|
import { ColorPicker as AntdColorPicker, ColorPickerProps as AntdColorPickerProps } from 'antd';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface ColorPickerProps extends Omit<AntdColorPickerProps, 'onChange'> {
|
||||||
|
onChange?: (color: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const ColorPicker = connect(
|
export const ColorPicker = connect(
|
||||||
(props) => {
|
(props: ColorPickerProps) => {
|
||||||
const { value, onChange, ...others } = props;
|
const { value, onChange, ...others } = props;
|
||||||
return (
|
return (
|
||||||
<div role="button" aria-label="color-picker-normal" style={{ display: 'inline-block' }}>
|
<div role="button" aria-label="color-picker-normal" style={{ display: 'inline-block' }}>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorPicker',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
default: '#8BBB11',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorPicker',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,18 +1,17 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# ColorPicker
|
# ColorPicker
|
||||||
|
|
||||||
## Examples
|
颜色选择器,其基于 ant-design [ColorPicker](https://ant.design/components/color-picker/) 组件进行封装。
|
||||||
|
|
||||||
### Basic
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ColorPickerProps extends Omit<AntdColorPickerProps, 'onChange'> {
|
||||||
|
onChange?: (color: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/read-pretty.tsx"></code>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { Select, Tag } from 'antd';
|
import { Select, SelectProps, Tag } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useCompile } from '../../hooks/useCompile';
|
import { useCompile } from '../../hooks/useCompile';
|
||||||
|
|
||||||
@ -28,8 +28,12 @@ const colors = {
|
|||||||
default: '{{t("Default")}}',
|
default: '{{t("Default")}}',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ColorSelectProps extends SelectProps {
|
||||||
|
suffix?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export const ColorSelect = connect(
|
export const ColorSelect = connect(
|
||||||
(props) => {
|
(props: ColorSelectProps) => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
return (
|
return (
|
||||||
<Select {...props}>
|
<Select {...props}>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorSelect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'red',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorSelect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,13 +1,22 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
order: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
# ColorSelect
|
# ColorSelect
|
||||||
|
|
||||||
## Examples
|
颜色下拉选择器,其基于 ant-design [Select](https://ant.design/components/select/) 组件封装。
|
||||||
|
|
||||||
### ColorSelect usage
|
## Basic Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ColorSelectProps = ColorSelectProps;
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ColorSelectReadPrettyProps {
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/read-pretty.tsx"></code>
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
||||||
|
@ -12,11 +12,15 @@ import { connect, mapReadPretty } from '@formily/react';
|
|||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
import cronstrue from 'cronstrue';
|
import cronstrue from 'cronstrue';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { CronProps, Cron as ReactCron } from 'react-js-cron';
|
import { CronProps as ReactJsCronProps, Cron as ReactCron } from 'react-js-cron';
|
||||||
import 'react-js-cron/dist/styles.css';
|
import 'react-js-cron/dist/styles.css';
|
||||||
import { useAPIClient } from '../../../api-client';
|
import { useAPIClient } from '../../../api-client';
|
||||||
|
|
||||||
const Input = (props: Omit<CronProps, 'setValue'> & { onChange: (value: string) => void }) => {
|
export interface CronProps extends Omit<ReactJsCronProps, 'setValue'> {
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = (props: CronProps) => {
|
||||||
const { onChange, ...rest } = props;
|
const { onChange, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<fieldset
|
<fieldset
|
||||||
@ -45,7 +49,11 @@ const Input = (props: Omit<CronProps, 'setValue'> & { onChange: (value: string)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReadPretty = (props) => {
|
interface CronReadPrettyProps {
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReadPretty = (props: CronReadPrettyProps) => {
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const locale = api.auth.getLocale();
|
const locale = api.auth.getLocale();
|
||||||
const value = useMemo(() => {
|
const value = useMemo(() => {
|
||||||
|
@ -15,7 +15,7 @@ import { useCompile } from '../../hooks';
|
|||||||
import { EllipsisWithTooltip } from '../input';
|
import { EllipsisWithTooltip } from '../input';
|
||||||
import Cron from './Cron';
|
import Cron from './Cron';
|
||||||
|
|
||||||
interface CronSetProps extends SelectProps {
|
export interface CronSetProps extends SelectProps {
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,12 @@ const CronSetInternal = (props: CronSetProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReadPretty = (props: CronSetProps) => {
|
export interface CronReadPrettyProps {
|
||||||
|
value?: string;
|
||||||
|
options?: SelectProps['options'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReadPretty = (props: CronReadPrettyProps) => {
|
||||||
const { value } = props;
|
const { value } = props;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Cron',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
default: '13 6 11 * *',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Cron',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,40 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'CronSet',
|
||||||
|
'x-component-props': {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '{{t("Daily")}}',
|
||||||
|
value: '0 0 0 * * ?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("Weekly")}}',
|
||||||
|
value: 'every_week',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("Monthly")}}',
|
||||||
|
value: 'every_month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("Yearly")}}',
|
||||||
|
value: 'every_year',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,34 @@
|
|||||||
|
import { getAppComponent } from '@nocobase/test/web';
|
||||||
|
|
||||||
|
const App = getAppComponent({
|
||||||
|
schema: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'root',
|
||||||
|
'x-decorator': 'FormV2',
|
||||||
|
'x-component': 'ShowFormData',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
default: '0 0 0 * * ?',
|
||||||
|
title: 'Test',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'CronSet',
|
||||||
|
'x-component-props': {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '{{t("Daily")}}',
|
||||||
|
value: '0 0 0 * * ?',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("Weekly")}}',
|
||||||
|
value: '* * * * * ?',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default App;
|
@ -1,16 +1,53 @@
|
|||||||
---
|
|
||||||
group:
|
|
||||||
title: Schema Components
|
|
||||||
---
|
|
||||||
|
|
||||||
# Cron
|
# Cron
|
||||||
|
|
||||||
## Example
|
定时任务表达式组件,其基于 [react-js-cron](https://github.com/xrutayisire/react-js-cron) 封装。
|
||||||
|
|
||||||
#### Cron
|
## Cron
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
## Basic Usage
|
||||||
|
|
||||||
#### CronSet
|
```ts
|
||||||
|
import { CronProps as ReactJsCronProps } from 'react-js-cron';
|
||||||
|
|
||||||
|
interface CronProps extends Omit<ReactJsCronProps, 'setValue'> {
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/cron-basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CronReadPrettyProps {
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/cron-read-pretty.tsx"></code>
|
||||||
|
|
||||||
|
## CronSet
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CronSetProps extends SelectProps {
|
||||||
|
onChange: (v: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/cronset-basic.tsx"></code>
|
||||||
|
|
||||||
|
## Read Pretty
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface CronReadPrettyProps {
|
||||||
|
value?: string;
|
||||||
|
options?: SelectProps['options'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<code src="./demos/new-demos/cronset-read-pretty.tsx"></code>
|
||||||
|
|
||||||
<code src="./demos/demo2.tsx"></code>
|
|
||||||
|
@ -8,23 +8,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { DatePicker as AntdDatePicker } from 'antd';
|
import { DatePicker as AntdDatePicker, DatePickerProps as AntdDatePickerProps } from 'antd';
|
||||||
import type {
|
import React, { FC } from 'react';
|
||||||
DatePickerProps as AntdDatePickerProps,
|
|
||||||
RangePickerProps as AntdRangePickerProps,
|
|
||||||
} from 'antd/es/date-picker';
|
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ReadPretty } from './ReadPretty';
|
import { ReadPretty, ReadPrettyComposed } from './ReadPretty';
|
||||||
import { getDateRanges, mapDatePicker, mapRangePicker } from './util';
|
import { getDateRanges, mapDatePicker, mapRangePicker } from './util';
|
||||||
|
import { RangePickerProps } from 'antd/es/date-picker';
|
||||||
|
|
||||||
interface IDatePickerProps {
|
interface IDatePickerProps {
|
||||||
utc?: boolean;
|
utc?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComposedDatePicker = React.FC<AntdDatePickerProps> & {
|
type ComposedDatePicker = React.FC<AntdDatePickerProps> & {
|
||||||
ReadPretty?: React.FC<AntdDatePickerProps>;
|
ReadPretty?: ReadPrettyComposed['DatePicker'];
|
||||||
RangePicker?: React.FC<AntdRangePickerProps>;
|
RangePicker?: ComposedRangePicker;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ComposedRangePicker = React.FC<RangePickerProps> & {
|
||||||
|
ReadPretty?: ReadPrettyComposed['DateRangePicker'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const DatePickerContext = React.createContext<IDatePickerProps>({ utc: true });
|
const DatePickerContext = React.createContext<IDatePickerProps>({ utc: true });
|
||||||
@ -44,11 +45,11 @@ const InternalRangePicker = connect(
|
|||||||
mapReadPretty(ReadPretty.DateRangePicker),
|
mapReadPretty(ReadPretty.DateRangePicker),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const DatePicker = (props) => {
|
export const DatePicker: ComposedDatePicker = (props) => {
|
||||||
const { utc = true } = useDatePickerContext();
|
const { utc = true } = useDatePickerContext();
|
||||||
const value = Array.isArray(props.value) ? props.value[0] : props.value;
|
const value = Array.isArray(props.value) ? props.value[0] : props.value;
|
||||||
props = { utc, ...props };
|
const newProps = { utc, ...props };
|
||||||
return <InternalDatePicker {...props} value={value} />;
|
return <InternalDatePicker {...newProps} value={value} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
DatePicker.ReadPretty = ReadPretty.DatePicker;
|
DatePicker.ReadPretty = ReadPretty.DatePicker;
|
||||||
@ -78,8 +79,8 @@ DatePicker.RangePicker = function RangePicker(props) {
|
|||||||
{ label: t('Last 90 days'), value: rangesValue.last90Days },
|
{ label: t('Last 90 days'), value: rangesValue.last90Days },
|
||||||
{ label: t('Next 90 days'), value: rangesValue.next90Days },
|
{ label: t('Next 90 days'), value: rangesValue.next90Days },
|
||||||
];
|
];
|
||||||
props = { utc, presets, ...props };
|
const newProps: any = { utc, presets, ...props };
|
||||||
return <InternalRangePicker {...props} />;
|
return <InternalRangePicker {...newProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DatePicker;
|
export default DatePicker;
|
||||||
|
@ -9,23 +9,32 @@
|
|||||||
|
|
||||||
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
||||||
import { isArr } from '@formily/shared';
|
import { isArr } from '@formily/shared';
|
||||||
import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
|
import {
|
||||||
import type {
|
GetDefaultFormatProps,
|
||||||
DatePickerProps as AntdDatePickerProps,
|
Str2momentOptions,
|
||||||
RangePickerProps as AntdRangePickerProps,
|
Str2momentValue,
|
||||||
} from 'antd/es/date-picker';
|
getDefaultFormat,
|
||||||
|
str2moment,
|
||||||
|
} from '@nocobase/utils/client';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type Composed = {
|
export type ReadPrettyComposed = {
|
||||||
DatePicker: React.FC<AntdDatePickerProps>;
|
DatePicker: React.FC<ReadPrettyDatePickerProps>;
|
||||||
DateRangePicker: React.FC<AntdRangePickerProps>;
|
DateRangePicker: React.FC<DateRangePickerReadPrettyProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReadPretty: Composed = () => null;
|
export const ReadPretty: ReadPrettyComposed = () => null;
|
||||||
|
|
||||||
ReadPretty.DatePicker = function DatePicker(props: any) {
|
export interface ReadPrettyDatePickerProps extends Str2momentOptions, GetDefaultFormatProps {
|
||||||
|
value?: Str2momentValue;
|
||||||
|
className?: string;
|
||||||
|
prefixCls?: string;
|
||||||
|
showTime?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadPretty.DatePicker = function DatePicker(props) {
|
||||||
const prefixCls = usePrefixCls('description-date-picker', props);
|
const prefixCls = usePrefixCls('description-date-picker', props);
|
||||||
|
|
||||||
if (!props.value) {
|
if (!props.value) {
|
||||||
@ -41,7 +50,14 @@ ReadPretty.DatePicker = function DatePicker(props: any) {
|
|||||||
return <div className={cls(prefixCls, props.className)}>{getLabels()}</div>;
|
return <div className={cls(prefixCls, props.className)}>{getLabels()}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
ReadPretty.DateRangePicker = function DateRangePicker(props: any) {
|
export interface DateRangePickerReadPrettyProps extends Str2momentOptions, GetDefaultFormatProps {
|
||||||
|
value?: Str2momentValue;
|
||||||
|
className?: string;
|
||||||
|
prefixCls?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadPretty.DateRangePicker = function DateRangePicker(props: DateRangePickerReadPrettyProps) {
|
||||||
const prefixCls = usePrefixCls('description-text', props);
|
const prefixCls = usePrefixCls('description-text', props);
|
||||||
const format = getDefaultFormat(props);
|
const format = getDefaultFormat(props);
|
||||||
const getLabels = () => {
|
const getLabels = () => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user