feat: cacader component

* feat: add Cacader component into schema component

* docs: change document language

* Nocobase next color select (#157)

* feat: add useCompile hook

* feat: add ColorSelect component into schema component

* optimize: rename checkbox component file (#155)

* optimize: rename checkbox component file

* optimize: rename checkbox component file

* feat: optimize antd-config-orovider

* feat: move admin-layout and auth-layout to route-switch

* feat: add application demo

* feat: add radio into schema component (#154)

* feat: add radio to schema component

* docs: add Radio  demos into schema component

* rafactor: change import path

* docs: change demo language

* fix: add title and description to demos

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat: add input into schema component (#158)

* feat: add Input Component to schema compoennt

* code cleanup

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat: add RemoteSchemaComponent

* feat: add admin layout

* feat: add menu into schema component

* fix: remove antd/dist/antd.css

* feat: improve application demo

* feat: add DatePicker into schema components (#161)

* feat: add DatePicker into schema components

* improve code

Co-authored-by: chenos <chenlinxh@gmail.com>

* feature/nocobase next password (#159)

* feat: add Password Component into schema components

* improve code

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat: add InputNumber Component into schema component (#160)

* feat: add InputNumber Component into schema component

* improve code

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat(client-menu): improve code

* feat: acl (#153)

* feat: getRepository

* getRepository return type

* export action

* add: acl

* feat: setResourceAction

* feat: action alias

* chore: code struct

* feat: removeResourceAction

* chore: file name

* ignorecase

* remove ACL

* feat: ACL

* feat: role toJSON

* using emit

Co-authored-by: chenos <chenlinxh@gmail.com>

* Acl (#162)

* feat: getRepository

* getRepository return type

* export action

* add: acl

* feat: setResourceAction

* feat: action alias

* chore: code struct

* feat: removeResourceAction

* chore: file name

* ignorecase

* remove ACL

* feat: ACL

* feat: role toJSON

* using emit

* chore: test

Co-authored-by: chenos <chenlinxh@gmail.com>

* Feat/plugin collection manager (#147)

* refactor: collection manager plugin

* feat(database): magic attribute model

* MagicAttributeModel

* load collections & fields options

* collections filterTargetKey

* Feat/plugin UI schema v0.6 (#143)

* v0.6

* plugin-ui-schema: insert && getJsonSchema

* plugin-ui-schema: insert schema with sort

* plugin-ui-schema: node with x-index

* insert adjacent method

* chore: insert

* typo

* insert with x-uid

* fix: getSchema by subtree

* add ui-schema actions

* fix: mysql compatibility

* remove ui-schema when remove node tree

* ui schema patch

* ui_schemas.create

* test cases

* test cases

* fix(database): reset changed before update

* feat: insert ui schema node after created

* feat:  patch ui schema node after updated

* fix: sqlite error

* uid

* cleanup

* test cases

* feat: ui_schema items type support

* fix: insert items node

* fix: get inner type

* change items struct

* add insert return value

* add insert return value

Co-authored-by: chenos <chenlinxh@gmail.com>

* update yarn.lock

* rename

* feat(client): plugin manager toolbar

* feat: add demo for plugin manager toolbar

* feat: improve the toolbar of the plugin manager

* feat: improve document title and page title support

* feat: add IconPicker component into schema components

* feat: improve icon component

* migrate TimePicker component into schema components (#164)

* feat: add TimePicker component into schema components

* improve

* TimePicker.RangePicker

Co-authored-by: chenos <chenlinxh@gmail.com>

* Load Options Lazily

* feat: extract read pretty

* fix: useEffect only on update

* feat: add Cacader component into schema component

* docs: change document language

* feat: extract read pretty

* Load Options Lazily

* improve code

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: ChengLei Shao <chareice@live.com>
This commit is contained in:
SemmyWong 2022-01-21 09:32:13 +08:00 committed by GitHub
parent f882ab42c5
commit 48722c7c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 374 additions and 0 deletions

View File

@ -0,0 +1,96 @@
import { LoadingOutlined } from '@ant-design/icons';
import { ArrayField } from '@formily/core';
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
import { toArr } from '@formily/shared';
import { Cascader as AntdCascader } from 'antd';
import { isBoolean, omit } from 'lodash';
import React from 'react';
import { defaultFieldNames } from './defaultFieldNames';
import { ReadPretty } from './ReadPretty';
const useDefLoadData = (props: any) => props.loadData;
export const Cascader = connect(
(props: any) => {
const field = useField<ArrayField>();
const {
value,
onChange,
labelInValue,
fieldNames = defaultFieldNames,
useLoadData = useDefLoadData,
changeOnSelectLast,
changeOnSelect,
...others
} = props;
const loadData = useLoadData(props);
// 兼容值为 object[] 的情况
const toValue = () => {
return toArr(value).map((item) => {
if (typeof item === 'object') {
return item[fieldNames.value];
}
return item;
});
};
const displayRender = (labels: string[], selectedOptions: any[]) => {
const values = toArr(value);
if (values.length !== labels.length) {
labels = toValue();
selectedOptions = values;
}
if (selectedOptions.length === 0) {
selectedOptions = values;
}
return labels.map((label, i) => {
let option = selectedOptions[i];
if (!option || typeof option === 'string' || typeof option === 'number') {
option = { [fieldNames.label]: label, [fieldNames.value]: label };
}
if (i === labels.length - 1) {
return <span key={option[fieldNames.value]}>{option[fieldNames.label]}</span>;
}
return <span key={option[fieldNames.value]}>{option[fieldNames.label]} / </span>;
});
};
// if (loadData) {
// Object.assign(others, {
// loadData: (selectedOptions: any[]) => {
// // 将 field 传给 loadData
// loadData(selectedOptions, field);
// },
// });
// }
return (
<AntdCascader
{...others}
loadData={loadData}
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
value={toValue()}
fieldNames={fieldNames}
displayRender={displayRender}
onChange={(value, selectedOptions) => {
if (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),
);
export default Cascader;

View File

@ -0,0 +1,35 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import { toArr } from '@formily/shared';
import React from 'react';
import { defaultFieldNames } from './defaultFieldNames';
export const ReadPretty: React.FC<unknown> = (props: any) => {
const { fieldNames = defaultFieldNames } = props;
const values = toArr(props.value);
const len = values.length;
const field = useField<ArrayField>();
let dataSource = field.dataSource;
const data = [];
for (const item of values) {
if (typeof item === 'object') {
data.push(item);
} else {
const curr = dataSource?.find((v) => v[fieldNames.value] === item);
dataSource = curr?.[fieldNames.children] || [];
data.push(curr || { label: item, value: item });
}
}
return (
<div>
{data.map((item, index) => {
return (
<span key={index}>
{typeof item === 'object' ? item[fieldNames.label] : item}
{len > index + 1 && ' / '}
</span>
);
})}
</div>
);
};

View File

@ -0,0 +1,5 @@
export const defaultFieldNames = {
label: 'label',
value: 'value',
children: 'children',
};

View File

@ -0,0 +1,83 @@
import { FormItem } from '@formily/antd';
import { Cascader, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import React from 'react';
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 schema = {
type: 'object',
properties: {
input: {
type: 'string',
title: `编辑模式`,
name: 'name1',
enum: options,
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
// changeOnSelect: true,
},
'x-reactions': {
target: 'read',
fulfill: {
state: {
value: '{{$self.value}}',
},
},
},
},
read: {
type: 'string',
title: `阅读模式`,
enum: options,
name: 'name2',
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
changeOnSelect: true,
},
},
},
};
export default () => {
return (
<SchemaComponentProvider components={{ Cascader, FormItem }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
);
};

View File

@ -0,0 +1,123 @@
import { FormItem } from '@formily/antd';
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import { action } from '@formily/reactive';
import { APIClient, APIClientProvider, Cascader, SchemaComponent, SchemaComponentProvider } 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('/china_regions').reply(200, {
data: [
{
value: 'zhejiang',
label: 'Zhejiang',
isLeaf: false,
},
{
value: 'jiangsu',
label: 'Jiangsu',
isLeaf: false,
},
],
});
const useAsyncDataSource = (api: APIClient) => (field) => {
field.loading = true;
api.request({ url: 'china_regions' }).then(
action.bound((res) => {
field.dataSource = res?.data?.data || [];
field.loading = false;
}),
);
};
const useLoadData = (api: APIClient) => {
return () => {
// hook 写在这里
const field = useField<ArrayField>();
return (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
// load options lazily
setTimeout(() => {
targetOption.loading = false;
targetOption.children = [
{
label: `${targetOption.label} Dynamic 1`,
value: 'dynamic1',
},
{
label: `${targetOption.label} Dynamic 2`,
value: 'dynamic2',
},
];
field.dataSource = [...field.dataSource];
}, 500);
};
};
};
const schema = {
type: 'object',
properties: {
input: {
type: 'string',
title: `编辑模式`,
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
changeOnSelectLast: false,
labelInValue: true,
maxLevel: 3,
useLoadData: '{{useLoadData(apiClient)}}',
// fieldNames: {
// label: 'name',
// value: 'code',
// children: 'children',
// },
},
'x-reactions': [
'{{useAsyncDataSource(apiClient)}}',
{
target: 'read',
fulfill: {
state: {
value: '{{$self.value}}',
},
},
},
],
},
read: {
type: 'string',
title: `阅读模式`,
'x-read-pretty': true,
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
changeOnSelectLast: false,
labelInValue: true,
maxLevel: 3,
// fieldNames: {
// label: 'name',
// value: 'code',
// children: 'children',
// },
},
},
},
};
export default () => {
return (
<APIClientProvider apiClient={apiClient}>
<SchemaComponentProvider components={{ Cascader, FormItem }}>
<SchemaComponent scope={{ apiClient, useLoadData, useAsyncDataSource }} schema={schema} />
</SchemaComponentProvider>
</APIClientProvider>
);
};

View File

@ -6,3 +6,32 @@ group:
--- ---
# Cascader # Cascader
## Examples
### Cascader
<code src="./demos/demo1.tsx" />
### Asynchronous Data Source
<code src="./demos/demo2.tsx" />
## API
基于 antd 的 [Cascader](https://ant.design/components/cascader/#API) 附加的一些属性:
- `labelInValue` 是否把每个选项的 label 包装到 value 中
- `changeOnSelectLast` 必须选到最后一级
- `useLoadData` 可调用 hook 的 loadData
```ts
{
useLoadData: (props) => {
// 这里可以写 hook
return function loadData(selectedOptions) {
// Cascader 的 loadData
}
}
}
```

View File

@ -0,0 +1 @@
export * from './Cascader';

View File

@ -1,5 +1,6 @@
export * from './action'; export * from './action';
export * from './block-item'; export * from './block-item';
export * from './cascader';
export * from './checkbox'; export * from './checkbox';
export * from './color-select'; export * from './color-select';
export * from './date-picker'; export * from './date-picker';
@ -15,3 +16,4 @@ export * from './page';
export * from './password'; export * from './password';
export * from './radio'; export * from './radio';
export * from './time-picker'; export * from './time-picker';