From f9a18863ad73f3e672b9fcc2d1cb770720ed0419 Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 28 Jan 2022 09:28:01 +0800 Subject: [PATCH] feat: table related components (#172) * table-related components * feat: ArrayTable & VoidTable & RowSelection * update * fix: missing request params * fix(client): get sideMenuRef from context * void table * fix: use request params merge * demo * void table demo * feat: improve code * feat: improve code * feat: row select table * feat: record picker --- .../src/application/demos/demo2/mock.ts | 2 +- .../client/src/async-data-provider/index.tsx | 12 +- .../schema-component/antd/action/context.tsx | 2 +- .../antd/array-table/ArrayTable.tsx | 56 +++++ .../antd/array-table/demos/demo1.tsx | 67 +++++ .../antd/array-table/index.md | 39 ++- .../antd/array-table/index.ts | 1 + .../client/src/schema-component/antd/index.ts | 5 + .../antd/record-picker/RecordPicker.tsx | 138 ++++++++++ .../antd/record-picker/demos/demo1.tsx | 109 ++++++++ .../antd/record-picker/index.md | 76 +++++- .../antd/record-picker/index.tsx | 1 + .../antd/row-selection/RowSelection.tsx | 31 +++ .../antd/row-selection/demos/apiClient.ts | 35 +++ .../antd/row-selection/demos/demo1.tsx | 88 +++++++ .../antd/row-selection/index.md | 84 ++++++- .../antd/row-selection/index.ts | 1 + .../antd/void-table/VoidTable.tsx | 109 ++++++++ .../antd/void-table/demos/apiClient.ts | 35 +++ .../antd/void-table/demos/demo1.tsx | 87 +++++++ .../schema-component/antd/void-table/index.md | 235 +++++++++++++++++- .../schema-component/antd/void-table/index.ts | 1 + 22 files changed, 1194 insertions(+), 20 deletions(-) create mode 100644 packages/client/src/schema-component/antd/array-table/ArrayTable.tsx create mode 100644 packages/client/src/schema-component/antd/array-table/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/array-table/index.ts create mode 100644 packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx create mode 100644 packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/record-picker/index.tsx create mode 100644 packages/client/src/schema-component/antd/row-selection/RowSelection.tsx create mode 100644 packages/client/src/schema-component/antd/row-selection/demos/apiClient.ts create mode 100644 packages/client/src/schema-component/antd/row-selection/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/row-selection/index.ts create mode 100644 packages/client/src/schema-component/antd/void-table/VoidTable.tsx create mode 100644 packages/client/src/schema-component/antd/void-table/demos/apiClient.ts create mode 100644 packages/client/src/schema-component/antd/void-table/demos/demo1.tsx create mode 100644 packages/client/src/schema-component/antd/void-table/index.ts diff --git a/packages/client/src/application/demos/demo2/mock.ts b/packages/client/src/application/demos/demo2/mock.ts index 1426fa7bf..eadde72a7 100644 --- a/packages/client/src/application/demos/demo2/mock.ts +++ b/packages/client/src/application/demos/demo2/mock.ts @@ -131,7 +131,7 @@ export default (apiClient: APIClient) => { uiSchemaUid: 'qqzzjakwkwl', path: '/admin/:name(.+)?', component: 'AdminLayout', - title: 'NocoBase', + title: 'NocoBase Admin', }, { type: 'route', diff --git a/packages/client/src/async-data-provider/index.tsx b/packages/client/src/async-data-provider/index.tsx index fe77e9c55..b5c8d4582 100644 --- a/packages/client/src/async-data-provider/index.tsx +++ b/packages/client/src/async-data-provider/index.tsx @@ -1,24 +1,22 @@ -import React, { createContext, useContext } from 'react'; import { Result } from 'ahooks/lib/useRequest/src/types'; +import React, { createContext, useContext } from 'react'; import { useRequest } from '../api-client'; export const AsyncDataContext = createContext>(null); export interface AsyncDataProviderProps { value?: any; - resource?: any; request?: any; - action?: string; - defaultParams?: any; + uid?: string; + onSuccess?: (data, params) => void; } export const AsyncDataProvider: React.FC = (props) => { - const { value, request, resource, action, defaultParams, children } = props; + const { value, request, children, ...others } = props; if (value) { return {children}; } - const callback = (params?: any) => resource[action]({ ...defaultParams, ...params }); - const result = useRequest(request || callback); + const result = useRequest(request, { ...others }); return {children}; }; diff --git a/packages/client/src/schema-component/antd/action/context.tsx b/packages/client/src/schema-component/antd/action/context.tsx index 4d3d8bacd..a703edcfa 100644 --- a/packages/client/src/schema-component/antd/action/context.tsx +++ b/packages/client/src/schema-component/antd/action/context.tsx @@ -1,3 +1,3 @@ import { createContext } from 'react'; -export const VisibleContext = createContext(null); +export const VisibleContext = createContext<[boolean, any]>([false, () => {}]); diff --git a/packages/client/src/schema-component/antd/array-table/ArrayTable.tsx b/packages/client/src/schema-component/antd/array-table/ArrayTable.tsx new file mode 100644 index 000000000..fe41c471e --- /dev/null +++ b/packages/client/src/schema-component/antd/array-table/ArrayTable.tsx @@ -0,0 +1,56 @@ +import { ArrayField } from '@formily/core'; +import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { Table, TableColumnProps } from 'antd'; +import React from 'react'; + +const isColumnComponent = (schema: Schema) => { + return schema['x-component']?.endsWith('.Column') > -1; +}; + +const useTableColumns = () => { + const field = useField(); + const schema = useFieldSchema(); + const columns = schema + .reduceProperties((buf, s) => { + if (isColumnComponent(s)) { + return buf.concat([s]); + } + }, []) + .map((s: Schema) => { + return { + title: , + dataIndex: s.name, + render: (v, record) => { + const index = field.value?.indexOf(record); + return ; + }, + } as TableColumnProps; + }); + console.log(columns); + return columns; +}; + +type ArrayTableType = React.FC & { + Column?: React.FC; + mixin?: (T: any) => void; +}; + +export const ArrayTable: ArrayTableType = observer((props) => { + const field = useField(); + const columns = useTableColumns(); + const { onChange, ...others } = props; + return ( +
+ + + ); +}); + +ArrayTable.Column = (props) => { + const field = useField(); + return
{field.title}
; +}; + +ArrayTable.mixin = (Table: any) => { + Table.Column = ArrayTable.Column; +}; diff --git a/packages/client/src/schema-component/antd/array-table/demos/demo1.tsx b/packages/client/src/schema-component/antd/array-table/demos/demo1.tsx new file mode 100644 index 000000000..5eec566fb --- /dev/null +++ b/packages/client/src/schema-component/antd/array-table/demos/demo1.tsx @@ -0,0 +1,67 @@ +/** + * title: 勾选 + */ +import { FormItem } from '@formily/antd'; +import { ISchema } from '@formily/react'; +import { ArrayTable, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import React from 'react'; + +const schema: ISchema = { + type: 'object', + properties: { + input: { + type: 'array', + title: `编辑模式`, + default: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + 'x-reactions': { + target: 'read', + fulfill: { + state: { + value: '{{$self.value}}', + }, + }, + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'ArrayTable.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + read: { + type: 'array', + title: `阅读模式`, + 'x-read-pretty': true, + 'x-decorator': 'FormItem', + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/array-table/index.md b/packages/client/src/schema-component/antd/array-table/index.md index dac5a3329..dc0ab5946 100644 --- a/packages/client/src/schema-component/antd/array-table/index.md +++ b/packages/client/src/schema-component/antd/array-table/index.md @@ -5,4 +5,41 @@ group: path: /schema-components --- -# ArrayTable 待定 +# ArrayTable - 表格(数据录入) 待定 + +ArrayTable 更侧重于数据录入,如果需要动态的表格数据展示,请使用 [VoidTable](void-table)。 + +## JSON Schema + +ArrayTable 的 props 与 antd 的 [Table](https://ant.design/components/table/#API) 基本一致。但并不直接用 Table 组件的 columns 和 dataSource。dataSource 由表单提供,默认值写在 default 里;为了更好的支持 columns 的渲染,添加了 ArrayTable.Column 用于配置表格列,ArrayTable.Column 写在 properties 里,属性与 antd 的 [Table.Column](https://ant.design/components/table/#Column) 一致。 + +```ts +{ + type: 'array', + 'x-component': 'ArrayTable', + default: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + properties: { + column1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: 'Name', + }, + properties: { + name: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, +} +``` + +## Examples + + \ No newline at end of file diff --git a/packages/client/src/schema-component/antd/array-table/index.ts b/packages/client/src/schema-component/antd/array-table/index.ts new file mode 100644 index 000000000..18795279a --- /dev/null +++ b/packages/client/src/schema-component/antd/array-table/index.ts @@ -0,0 +1 @@ +export * from './ArrayTable'; diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index 2adc7fb71..c74f55f3b 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -1,4 +1,5 @@ export * from './action'; +export * from './array-table'; export * from './block-item'; export * from './cascader'; export * from './checkbox'; @@ -15,7 +16,11 @@ export * from './menu'; export * from './page'; export * from './password'; export * from './radio'; +export * from './record-picker'; +export * from './row-selection'; export * from './select'; export * from './time-picker'; export * from './tree-select'; export * from './upload'; +export * from './void-table'; + diff --git a/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx new file mode 100644 index 000000000..f4b2f1ff4 --- /dev/null +++ b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx @@ -0,0 +1,138 @@ +import { LoadingOutlined } from '@ant-design/icons'; +import { createForm, Field, onFormSubmit } from '@formily/core'; +import { + connect, + FieldContext, + FormContext, + mapProps, + mapReadPretty, + RecursionField, + Schema, + useField, + useFieldSchema +} from '@formily/react'; +import { toArr } from '@formily/shared'; +import { Button, Drawer, Select, Tag } from 'antd'; +import React, { createContext, useContext, useMemo, useState } from 'react'; +import { useAttach } from '../../hooks/useAttach'; +import { VisibleContext } from '../action'; + +const InputRecordPicker: React.FC = (props) => { + const [visible, setVisible] = useState(false); + const fieldSchema = useFieldSchema(); + const field = useField(); + const s = fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'RowSelection') { + return s; + } + return buf; + }, new Schema({})); + const form = useMemo( + () => + createForm({ + initialValues: { + [s.name]: field.value, + }, + effects() { + onFormSubmit((form) => { + field.value = form.values[s.name]; + console.log('field.value', form.values[s.name]); + }); + }, + }), + [], + ); + const f = useAttach(form.createVoidField({ ...field.props, basePath: '' })); + return ( +
+ + + + setVisible(false)} + footer={ + + } + > + { + return s['x-component'] === 'RowSelection'; + }} + /> + + + +
+ ); +}; + +const RowContext = createContext({}); + +const ReadPrettyRecordPicker: React.FC = (props) => { + const fieldSchema = useFieldSchema(); + const field = useField(); + return ( +
+ {toArr(field.value).map((record, index) => { + return ( + + { + return s['x-component'] === 'RecordPicker.SelectedItem'; + }} + /> + + ); + })} +
+ ); +}; + +const mapSuffixProps = (props, field) => { + return { + ...props, + suffix: {field?.['loading'] || field?.['validating'] ? : props.suffix}, + }; +}; + +export const RecordPicker: any = connect( + InputRecordPicker, + mapProps(mapSuffixProps), + mapReadPretty(ReadPrettyRecordPicker), +); + +RecordPicker.SelectedItem = () => { + const ctx = useContext(RowContext); + const fieldSchema = useFieldSchema(); + const [visible, setVisible] = useState(false); + return ( + <> + + setVisible(true)}> + {ctx.record?.name} + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx b/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx new file mode 100644 index 000000000..60c44c25d --- /dev/null +++ b/packages/client/src/schema-component/antd/record-picker/demos/demo1.tsx @@ -0,0 +1,109 @@ +/** + * title: 勾选 + */ +import { FormItem } from '@formily/antd'; +import { ISchema } from '@formily/react'; +import { Action, Input, RecordPicker, RowSelection, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import React from 'react'; + +const schema: ISchema = { + type: 'object', + properties: { + input: { + type: 'array', + title: `编辑模式`, + default: [ + { id: 1, name: 'name1' }, + { id: 2, name: 'name2' }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'RecordPicker', + 'x-reactions': { + target: 'read', + fulfill: { + state: { + value: '{{$self.value}}', + }, + }, + }, + properties: { + rowSelection: { + 'x-component': 'RowSelection', + 'x-component-props': { + rowKey: 'id', + objectValue: true, + rowSelection: { + type: 'checkbox', + }, + dataSource: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'RowSelection.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, + }, + read: { + type: 'array', + title: `阅读模式`, + 'x-read-pretty': true, + 'x-decorator': 'FormItem', + 'x-component': 'RecordPicker', + properties: { + item: { + 'x-component': 'RecordPicker.SelectedItem', + 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: { + type: 'void', + title: 'Close', + 'x-component': 'Action', + 'x-component-props': { + // useAction: '{{ useCloseAction }}', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/record-picker/index.md b/packages/client/src/schema-component/antd/record-picker/index.md index 3a8d14202..ecd576dd0 100644 --- a/packages/client/src/schema-component/antd/record-picker/index.md +++ b/packages/client/src/schema-component/antd/record-picker/index.md @@ -5,4 +5,78 @@ group: path: /schema-components --- -# RecordPicker 待定 +# RecordPicker - 记录选择器 待定 + +## JSON Schema + +通过弹窗选择可选项,可选项用表格展示,在特定的 `RecordPicker.RowSelection` 节点里配置,仅当 `x-read-pretty: false` 时有效。 + +```ts +{ + type: 'array', + default: [ + { id: 1, name: 'tag1' }, + { id: 2, name: 'tag2' }, + ], + 'x-component': 'RecordPicker', + properties: { + rowSelection: { + 'x-component': 'RecordPicker.RowSelection', + }, + }, +} +``` + +`x-read-pretty: true` 时,可以在 `RecordPicker.SelectedItem` 里配置选中项的 schema。 + +```ts +{ + type: 'array', + 'x-read-pretty': true, + 'x-component': 'RecordPicker', + properties: { + item: { + 'x-component': 'RecordPicker.SelectedItem', + 'x-component-props': { + // label、value 与字段的映射关系 + fieldNames: { + value: 'id', + label: 'name' + }, + }, + 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: { + type: 'void', + title: 'Close', + 'x-component': 'Action', + 'x-component-props': { + // useAction: '{{ useCloseAction }}', + }, + }, + }, + }, + }, + }, + }, + }, + }, +} +``` + +## Examples + + diff --git a/packages/client/src/schema-component/antd/record-picker/index.tsx b/packages/client/src/schema-component/antd/record-picker/index.tsx new file mode 100644 index 000000000..104723bcf --- /dev/null +++ b/packages/client/src/schema-component/antd/record-picker/index.tsx @@ -0,0 +1 @@ +export * from './RecordPicker'; diff --git a/packages/client/src/schema-component/antd/row-selection/RowSelection.tsx b/packages/client/src/schema-component/antd/row-selection/RowSelection.tsx new file mode 100644 index 000000000..447a027a8 --- /dev/null +++ b/packages/client/src/schema-component/antd/row-selection/RowSelection.tsx @@ -0,0 +1,31 @@ +import { Field } from '@formily/core'; +import { observer, useField } from '@formily/react'; +import { isArr, isValid } from '@formily/shared'; +import { TableProps } from 'antd'; +import React from 'react'; +import { VoidTable } from '../void-table'; + +type Props = TableProps & { value?: any; onChange?: any; objectValue?: boolean; }; + +const toArr = (value: any) => (isArr(value) ? value : isValid(value) ? [value] : []); + +export const RowSelection = observer((props: Props) => { + const { rowKey = 'id', objectValue } = props; + const field = useField(); + console.log('field.value', field.value) + const rowSelection: any = { + type: 'checkbox', + ...props.rowSelection, + selectedRowKeys: toArr(field.value).map(val => typeof val === 'object' ? val[rowKey as any] : val), + onChange(selectedRowKeys: any[], selectedRows?: any) { + if (rowSelection.type === 'checkbox') { + props.onChange(objectValue ? selectedRows : selectedRowKeys); + } else { + props.onChange([...(objectValue ? selectedRows : selectedRowKeys)].shift()); + } + }, + }; + return ; +}); + +VoidTable.mixin(RowSelection); diff --git a/packages/client/src/schema-component/antd/row-selection/demos/apiClient.ts b/packages/client/src/schema-component/antd/row-selection/demos/apiClient.ts new file mode 100644 index 000000000..4ff7f027b --- /dev/null +++ b/packages/client/src/schema-component/antd/row-selection/demos/apiClient.ts @@ -0,0 +1,35 @@ +import { uid } from '@formily/shared'; +import { APIClient } from '@nocobase/client'; +import MockAdapter from 'axios-mock-adapter'; +import _ from 'lodash'; + +export const apiClient = new APIClient(); + +const mock = new MockAdapter(apiClient.axios); + +const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); + +mock.onGet('/posts:list').reply(async (config) => { + // const [{ pageSize }] = config.params; + const pageSize = config.params.pageSize || 10; + const page = config.params.page || 1; + console.log(pageSize, page, config.params); + await sleep(1000); + return [ + 200, + { + data: _.range(pageSize).map((v) => { + return { + id: v + (page - 1) * pageSize, + name: uid(), + }; + }), + meta: { + count: 100, + pageSize, + page, + }, + }, + ]; +}); + diff --git a/packages/client/src/schema-component/antd/row-selection/demos/demo1.tsx b/packages/client/src/schema-component/antd/row-selection/demos/demo1.tsx new file mode 100644 index 000000000..c3d0c471e --- /dev/null +++ b/packages/client/src/schema-component/antd/row-selection/demos/demo1.tsx @@ -0,0 +1,88 @@ +/** + * title: 勾选 + */ +import { ISchema } from '@formily/react'; +import { + APIClientProvider, + Input, + RowSelection, + SchemaComponent, + SchemaComponentProvider, + useAPIClient +} from '@nocobase/client'; +import React from 'react'; +import { apiClient } from './apiClient'; + +const schema: ISchema = { + type: 'object', + properties: { + hello: { + 'x-component': 'Hello', + }, + table1: { + type: 'string', + default: 1, + 'x-uid': 'input', + 'x-component': 'RowSelection', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'radio', + }, + pagination: { + // current: 2, + pageSize: 2, + }, + request: { + resource: 'posts', + action: 'list', + params: { + filter: {}, + // pageSize: 5, + }, + }, + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'RowSelection.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; + +const Hello = () => { + const api = useAPIClient(); + return ( +
{ + const service = api.service('input'); + if (!service) { + return; + } + service.run({ ...service.params[0], page: 3 }); + }} + > + Hello +
+ ); +}; + +export default () => { + return ( + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/row-selection/index.md b/packages/client/src/schema-component/antd/row-selection/index.md index 0182ff47b..82ce5b099 100644 --- a/packages/client/src/schema-component/antd/row-selection/index.md +++ b/packages/client/src/schema-component/antd/row-selection/index.md @@ -5,4 +5,86 @@ group: path: /schema-components --- -# RowSelection 待定 +# RowSelection - 行选择器 待定 + +用表格视图展示可选项数据,功能上与 Radio.Group 和 CheckBox.Group 一致。 + +## JSON Schema + +[rowSelection](https://ant.design/components/table/#rowSelection) + +单选 + +```ts +{ + type: 'number', + 'x-component': 'RowSelection', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'radio', + }, + dataSource: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + }, + default: 1, + properties: { + column1: { + type: 'void', + 'x-component': 'VoidTable.Column', + 'x-component-props': { + title: 'Name', + }, + properties: { + name: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, +} +``` + +多选 + +```ts +{ + type: 'number', + 'x-component': 'RowSelection', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'radio', + }, + dataSource: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + }, + default: 1, + properties: { + column1: { + type: 'void', + 'x-component': 'VoidTable.Column', + 'x-component-props': { + title: 'Name', + }, + properties: { + name: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, +} +``` + +## Examples + + diff --git a/packages/client/src/schema-component/antd/row-selection/index.ts b/packages/client/src/schema-component/antd/row-selection/index.ts new file mode 100644 index 000000000..0569ee0ac --- /dev/null +++ b/packages/client/src/schema-component/antd/row-selection/index.ts @@ -0,0 +1 @@ +export * from './RowSelection'; diff --git a/packages/client/src/schema-component/antd/void-table/VoidTable.tsx b/packages/client/src/schema-component/antd/void-table/VoidTable.tsx new file mode 100644 index 000000000..24b71a306 --- /dev/null +++ b/packages/client/src/schema-component/antd/void-table/VoidTable.tsx @@ -0,0 +1,109 @@ +import { createForm, Field } from '@formily/core'; +import { FieldContext, FormContext, observer, useField, useFieldSchema } from '@formily/react'; +import { Options, Result } from 'ahooks/lib/useRequest/src/types'; +import { TablePaginationConfig, TableProps } from 'antd'; +import { cloneDeep } from 'lodash'; +import React, { useMemo } from 'react'; +import { AsyncDataProvider, useRequest } from '../../../'; +import { useAttach } from '../../hooks'; +import { ArrayTable } from '../array-table'; + +type VoidTableProps = TableProps & { + request?: any; + useDataSource?: (data?: any, options?: Options & { uid?: string }) => Result; +}; + +type VoidTableType = React.FC & { + Column?: React.FC; + mixin?: (T: any) => void; +}; + +const usePaginationProps = (props: TableProps & { request?: any }, service): TablePaginationConfig | false => { + if (props.pagination === false) { + return false; + } + const pagination: TablePaginationConfig = props.pagination || {}; + if (props?.request?.params?.pageSize) { + pagination.defaultPageSize = props?.request?.params?.pageSize; + } + return { + showSizeChanger: true, + ...pagination, + onChange(page, pageSize) { + service?.run({ ...service?.params?.[0], page, pageSize }); + }, + }; +}; + +const useRequestProps = (props) => { + const { request, pagination, dataSource } = props; + if (request) { + if (pagination === false) { + return request; + } + const params = cloneDeep(request.params || {}); + if (!params.page) { + params.page = pagination?.current || pagination?.defaultCurrent || 1; + } + if (!params.pageSize) { + params.pageSize = pagination?.pageSize || pagination?.defaultPageSize || 10; + } + request.params = params; + return request; + } + return (params: any = {}) => { + const { page = 1, pageSize = 10 } = params; + const startIndex = (page - 1) * pageSize; + const endIndex = startIndex + pageSize - 1; + return Promise.resolve({ + data: pagination === false ? dataSource : dataSource?.slice(startIndex, endIndex + 1), + meta: { + page, + pageSize, + count: dataSource?.length || 0, + }, + }); + }; +}; + +const useDefDataSource = (props, options) => { + return useRequest(useRequestProps(props), options); +}; + +export const VoidTable: VoidTableType = observer((props) => { + const { useDataSource = useDefDataSource } = props; + const field = useField(); + const fieldSchema = useFieldSchema(); + const form = useMemo(() => createForm(), []); + const f = useAttach(form.createArrayField({ name: fieldSchema.name })); + const result = useDataSource(props, { + uid: fieldSchema['x-uid'], + onSuccess(data) { + form.setValues({ + [fieldSchema.name]: data?.data, + }); + if (field?.componentProps?.pagination === false) { + return; + } + field.componentProps.pagination = field.componentProps.pagination || {}; + if (data?.meta?.count) { + field.componentProps.pagination.total = data?.meta?.count; + } + field.componentProps.pagination.current = data?.meta?.page || 1; + field.componentProps.pagination.pageSize = data?.meta?.pageSize || 10; + }, + }); + return ( + + + + + + + + ); +}); + +VoidTable.mixin = ArrayTable.mixin; + +ArrayTable.mixin(VoidTable); diff --git a/packages/client/src/schema-component/antd/void-table/demos/apiClient.ts b/packages/client/src/schema-component/antd/void-table/demos/apiClient.ts new file mode 100644 index 000000000..4ff7f027b --- /dev/null +++ b/packages/client/src/schema-component/antd/void-table/demos/apiClient.ts @@ -0,0 +1,35 @@ +import { uid } from '@formily/shared'; +import { APIClient } from '@nocobase/client'; +import MockAdapter from 'axios-mock-adapter'; +import _ from 'lodash'; + +export const apiClient = new APIClient(); + +const mock = new MockAdapter(apiClient.axios); + +const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); + +mock.onGet('/posts:list').reply(async (config) => { + // const [{ pageSize }] = config.params; + const pageSize = config.params.pageSize || 10; + const page = config.params.page || 1; + console.log(pageSize, page, config.params); + await sleep(1000); + return [ + 200, + { + data: _.range(pageSize).map((v) => { + return { + id: v + (page - 1) * pageSize, + name: uid(), + }; + }), + meta: { + count: 100, + pageSize, + page, + }, + }, + ]; +}); + diff --git a/packages/client/src/schema-component/antd/void-table/demos/demo1.tsx b/packages/client/src/schema-component/antd/void-table/demos/demo1.tsx new file mode 100644 index 000000000..6ec03c79d --- /dev/null +++ b/packages/client/src/schema-component/antd/void-table/demos/demo1.tsx @@ -0,0 +1,87 @@ +/** + * title: 勾选 + */ +import { ISchema } from '@formily/react'; +import { + APIClientProvider, + Input, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + VoidTable +} from '@nocobase/client'; +import React from 'react'; +import { apiClient } from './apiClient'; + +const schema: ISchema = { + type: 'object', + properties: { + hello: { + 'x-component': 'Hello', + }, + table1: { + type: 'void', + 'x-uid': 'input', + 'x-component': 'VoidTable', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + pagination: { + current: 2, + pageSize: 2, + }, + request: { + resource: 'posts', + action: 'list', + params: { + filter: {}, + // pageSize: 5, + }, + }, + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'VoidTable.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; + +const Hello = () => { + const api = useAPIClient(); + return ( +
{ + const service = api.service('input'); + if (!service) { + return; + } + service.run({ ...service.params[0], page: 3 }); + }} + > + Hello +
+ ); +}; + +export default () => { + return ( + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/void-table/index.md b/packages/client/src/schema-component/antd/void-table/index.md index 3bf73ef5a..2a10504ea 100644 --- a/packages/client/src/schema-component/antd/void-table/index.md +++ b/packages/client/src/schema-component/antd/void-table/index.md @@ -5,12 +5,231 @@ group: path: /schema-components --- -# VoidTable 待定 +# VoidTable - 表格(数据展示) 待定 -- VoidTable -- VoidTable.Column - - VoidTable.SortHandle - - TaVoidTableble.Index -- VoidTable.Pagination -- VoidTable.ActionBar -- VoidTable.Filter +VoidTable 只用作数据展示,如果需要可以录入数据的表格字段,请使用 [ArrayTable](array-table)。 + +## Examples + +VoidTable 的 props 与 antd 的 [Table](https://ant.design/components/table/#API) 一致。 + +### 基础使用 + +```tsx +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { + APIClientProvider, + Input, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + VoidTable +} from '@nocobase/client'; +import _ from 'lodash'; +import React from 'react'; + +const schema: ISchema = { + type: 'object', + properties: { + table1: { + type: 'void', + 'x-component': 'VoidTable', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + dataSource: _.range(5).map((v) => { + return { + id: v, + name: uid(), + }; + }), + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'VoidTable.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` + +### 分页 + +```tsx +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { + APIClientProvider, + Input, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + VoidTable +} from '@nocobase/client'; +import _ from 'lodash'; +import React from 'react'; + +const schema: ISchema = { + type: 'object', + properties: { + table1: { + type: 'void', + 'x-component': 'VoidTable', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + dataSource: _.range(50).map((v) => { + return { + id: v, + name: uid(), + }; + }), + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'VoidTable.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` + +### 不分页 + +```tsx +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { + APIClientProvider, + Input, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + VoidTable +} from '@nocobase/client'; +import _ from 'lodash'; +import React from 'react'; + +const schema: ISchema = { + type: 'object', + properties: { + table1: { + type: 'void', + 'x-uid': 'input', + 'x-component': 'VoidTable', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + pagination: false, + dataSource: _.range(12).map((v) => { + return { + id: v, + name: uid(), + }; + }), + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'VoidTable.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` + +### 异步数据 + + + +为了更好的支持 columns 的渲染,添加了 VoidTable.Column 用于配置表格列,VoidTable.Column 写在 properties 里,属性与 antd 的 [Table.Column](https://ant.design/components/table/#Column) 一致。 + +```ts +{ + type: 'void', + 'x-component': 'VoidTable', + 'x-component-props': { + rowKey: 'id', + dataSource: [ + { id: 1, name: 'Name1' }, + { id: 2, name: 'Name2' }, + { id: 3, name: 'Name3' }, + ], + }, + properties: { + column1: { + type: 'void', + 'x-component': 'VoidTable.Column', + 'x-component-props': { + title: 'Name', + }, + properties: { + name: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, +} +``` diff --git a/packages/client/src/schema-component/antd/void-table/index.ts b/packages/client/src/schema-component/antd/void-table/index.ts new file mode 100644 index 000000000..4679e657a --- /dev/null +++ b/packages/client/src/schema-component/antd/void-table/index.ts @@ -0,0 +1 @@ +export * from './VoidTable';