feat: improve cascader component
This commit is contained in:
parent
e9ddd4fd7e
commit
69d47a0331
@ -48,7 +48,7 @@ const plugins = [
|
|||||||
'@nocobase/plugin-export',
|
'@nocobase/plugin-export',
|
||||||
'@nocobase/plugin-system-settings',
|
'@nocobase/plugin-system-settings',
|
||||||
// '@nocobase/plugin-automations',
|
// '@nocobase/plugin-automations',
|
||||||
// '@nocobase/plugin-china-region',
|
'@nocobase/plugin-china-region',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
|
@ -90,5 +90,9 @@ import * as uiSchema from './ui-schema';
|
|||||||
default: process.env.STORAGE_TYPE === 'ali-oss',
|
default: process.env.STORAGE_TYPE === 'ali-oss',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 导入地域数据
|
||||||
|
const ChinaRegion = database.getModel('china_regions');
|
||||||
|
ChinaRegion && await ChinaRegion.importData();
|
||||||
|
|
||||||
await database.close();
|
await database.close();
|
||||||
})();
|
})();
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { createForm } from '@formily/core';
|
import { ArrayField, createForm } from '@formily/core';
|
||||||
import {
|
import {
|
||||||
createSchemaField,
|
createSchemaField,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
Schema,
|
Schema,
|
||||||
SchemaOptionsContext,
|
SchemaOptionsContext,
|
||||||
|
useField,
|
||||||
useFieldSchema,
|
useFieldSchema,
|
||||||
useForm,
|
useForm,
|
||||||
} from '@formily/react';
|
} from '@formily/react';
|
||||||
@ -18,7 +19,12 @@ import { useMemo } from 'react';
|
|||||||
import { CodeOutlined } from '@ant-design/icons';
|
import { CodeOutlined } from '@ant-design/icons';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
|
|
||||||
import { ArrayItems, ArrayCollapse, FormLayout, FormItem as FormilyFormItem } from '@formily/antd';
|
import {
|
||||||
|
ArrayItems,
|
||||||
|
ArrayCollapse,
|
||||||
|
FormLayout,
|
||||||
|
FormItem as FormilyFormItem,
|
||||||
|
} from '@formily/antd';
|
||||||
|
|
||||||
import { Space, Card, Modal, Spin } from 'antd';
|
import { Space, Card, Modal, Spin } from 'antd';
|
||||||
import { ArrayTable } from '../../schemas/array-table';
|
import { ArrayTable } from '../../schemas/array-table';
|
||||||
@ -27,7 +33,10 @@ import { AddNew } from '../../schemas/add-new';
|
|||||||
import { Cascader } from '../../schemas/cascader';
|
import { Cascader } from '../../schemas/cascader';
|
||||||
import { Checkbox } from '../../schemas/checkbox';
|
import { Checkbox } from '../../schemas/checkbox';
|
||||||
import { ColorSelect } from '../../schemas/color-select';
|
import { ColorSelect } from '../../schemas/color-select';
|
||||||
import { DatabaseField, DatabaseCollection } from '../../schemas/database-field';
|
import {
|
||||||
|
DatabaseField,
|
||||||
|
DatabaseCollection,
|
||||||
|
} from '../../schemas/database-field';
|
||||||
import { DatePicker } from '../../schemas/date-picker';
|
import { DatePicker } from '../../schemas/date-picker';
|
||||||
import { Filter } from '../../schemas/filter';
|
import { Filter } from '../../schemas/filter';
|
||||||
import { Form } from '../../schemas/form';
|
import { Form } from '../../schemas/form';
|
||||||
@ -54,6 +63,9 @@ import { Chart } from '../../schemas/chart';
|
|||||||
import { useDesignableSwitchContext } from '../../constate/DesignableSwitch';
|
import { useDesignableSwitchContext } from '../../constate/DesignableSwitch';
|
||||||
import { action } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
import { ISchema, FormilyISchema } from '../../schemas';
|
import { ISchema, FormilyISchema } from '../../schemas';
|
||||||
|
import { Resource } from '../../resource';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { CascaderOptionType } from 'antd/lib/cascader';
|
||||||
|
|
||||||
export const BlockContext = createContext({ dragRef: null });
|
export const BlockContext = createContext({ dragRef: null });
|
||||||
|
|
||||||
@ -74,10 +86,68 @@ export const useAsyncDataSource = (service: any) => (field: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useChinaRegionDataSource = ({ onSuccess }) => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const maxLevel = field.componentProps.maxLevel || 3;
|
||||||
|
const resource = Resource.make('china_regions');
|
||||||
|
console.log('useChinaRegionDataSource');
|
||||||
|
useRequest(
|
||||||
|
() =>
|
||||||
|
resource.list({
|
||||||
|
perPage: -1,
|
||||||
|
filter: {
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
formatResult: (data) =>
|
||||||
|
data?.data?.map((item) => {
|
||||||
|
if (maxLevel !== 1) {
|
||||||
|
item.isLeaf = false;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
onSuccess,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadChinaRegionData = (
|
||||||
|
selectedOptions: CascaderOptionType[],
|
||||||
|
field: ArrayField,
|
||||||
|
) => {
|
||||||
|
const maxLevel = field.componentProps.maxLevel || 3;
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
targetOption.loading = true;
|
||||||
|
const resource = Resource.make('china_regions');
|
||||||
|
resource
|
||||||
|
.list({
|
||||||
|
perPage: -1,
|
||||||
|
filter: {
|
||||||
|
parent_code: targetOption['code'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
targetOption.loading = false;
|
||||||
|
targetOption.children =
|
||||||
|
data?.data?.map((item) => {
|
||||||
|
if (maxLevel > item.level) {
|
||||||
|
item.isLeaf = false;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}) || [];
|
||||||
|
field.dataSource = [...field.dataSource];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const SchemaField = createSchemaField({
|
export const SchemaField = createSchemaField({
|
||||||
scope: {
|
scope: {
|
||||||
Table,
|
Table,
|
||||||
useAsyncDataSource,
|
useAsyncDataSource,
|
||||||
|
ChinaRegion: {
|
||||||
|
loadData: loadChinaRegionData,
|
||||||
|
useDataSource: useChinaRegionDataSource,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Card,
|
Card,
|
||||||
@ -163,7 +233,10 @@ export function findPropertyByPath(schema: Schema, path?: any): Schema {
|
|||||||
return property;
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPropertyBefore(target: Schema, data: ISchema | FormilyISchema) {
|
export function addPropertyBefore(
|
||||||
|
target: Schema,
|
||||||
|
data: ISchema | FormilyISchema,
|
||||||
|
) {
|
||||||
Object.keys(target.parent.properties).forEach((name) => {
|
Object.keys(target.parent.properties).forEach((name) => {
|
||||||
if (name === target.name) {
|
if (name === target.name) {
|
||||||
target.parent.addProperty(data.name, data);
|
target.parent.addProperty(data.name, data);
|
||||||
@ -174,7 +247,10 @@ export function addPropertyBefore(target: Schema, data: ISchema | FormilyISchema
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPropertyAfter(target: Schema, data: ISchema | FormilyISchema) {
|
export function addPropertyAfter(
|
||||||
|
target: Schema,
|
||||||
|
data: ISchema | FormilyISchema,
|
||||||
|
) {
|
||||||
Object.keys(target.parent.properties).forEach((name) => {
|
Object.keys(target.parent.properties).forEach((name) => {
|
||||||
const property = target.parent.properties[name];
|
const property = target.parent.properties[name];
|
||||||
property.parent.removeProperty(property.name);
|
property.parent.removeProperty(property.name);
|
||||||
@ -211,8 +287,8 @@ export function useDesignable(path?: any) {
|
|||||||
const schemaPath = path || useSchemaPath();
|
const schemaPath = path || useSchemaPath();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
let current;
|
let current;
|
||||||
let currentSchema = current =
|
let currentSchema = (current =
|
||||||
findPropertyByPath(schema, schemaPath) || ({} as Schema);
|
findPropertyByPath(schema, schemaPath) || ({} as Schema));
|
||||||
if (!currentSchema) {
|
if (!currentSchema) {
|
||||||
currentSchema = fieldSchema;
|
currentSchema = fieldSchema;
|
||||||
}
|
}
|
||||||
@ -221,7 +297,10 @@ export function useDesignable(path?: any) {
|
|||||||
}
|
}
|
||||||
// console.log('useDesignable', { schema, schemaPath, currentSchema });
|
// console.log('useDesignable', { schema, schemaPath, currentSchema });
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
let DesignableBar = get(options.components, currentSchema['x-designable-bar']);
|
let DesignableBar = get(
|
||||||
|
options.components,
|
||||||
|
currentSchema['x-designable-bar'],
|
||||||
|
);
|
||||||
if (!designable) {
|
if (!designable) {
|
||||||
DesignableBar = () => null;
|
DesignableBar = () => null;
|
||||||
}
|
}
|
||||||
@ -267,7 +346,10 @@ export function useDesignable(path?: any) {
|
|||||||
refresh();
|
refresh();
|
||||||
return target.properties[property.name];
|
return target.properties[property.name];
|
||||||
},
|
},
|
||||||
appendChild: (property: ISchema | FormilyISchema, targetPath?: any): Schema => {
|
appendChild: (
|
||||||
|
property: ISchema | FormilyISchema,
|
||||||
|
targetPath?: any,
|
||||||
|
): Schema => {
|
||||||
let target = currentSchema;
|
let target = currentSchema;
|
||||||
if (targetPath) {
|
if (targetPath) {
|
||||||
target = findPropertyByPath(schema, targetPath);
|
target = findPropertyByPath(schema, targetPath);
|
||||||
@ -294,7 +376,10 @@ export function useDesignable(path?: any) {
|
|||||||
refresh();
|
refresh();
|
||||||
return target.properties[property.name];
|
return target.properties[property.name];
|
||||||
},
|
},
|
||||||
insertAfter: (property: ISchema | FormilyISchema, targetPath?: any): Schema => {
|
insertAfter: (
|
||||||
|
property: ISchema | FormilyISchema,
|
||||||
|
targetPath?: any,
|
||||||
|
): Schema => {
|
||||||
let target = currentSchema;
|
let target = currentSchema;
|
||||||
if (targetPath) {
|
if (targetPath) {
|
||||||
target = findPropertyByPath(schema, targetPath);
|
target = findPropertyByPath(schema, targetPath);
|
||||||
@ -360,7 +445,7 @@ export function useDesignable(path?: any) {
|
|||||||
remove(s.parent);
|
remove(s.parent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
console.log({ removed })
|
console.log({ removed });
|
||||||
remove(target);
|
remove(target);
|
||||||
refresh();
|
refresh();
|
||||||
return removed;
|
return removed;
|
||||||
@ -533,7 +618,7 @@ export const SchemaRenderer = (props: SchemaRendererProps) => {
|
|||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// refresh(uid());
|
// refresh(uid());
|
||||||
// }, [designable]);
|
// }, [designable]);
|
||||||
console.log({ designable }, 'designable')
|
console.log({ designable }, 'designable');
|
||||||
const defaultRender = ({ schema }) => (
|
const defaultRender = ({ schema }) => (
|
||||||
<FormProvider form={form}>
|
<FormProvider form={form}>
|
||||||
<SchemaField
|
<SchemaField
|
||||||
|
78
packages/client/src/schemas/cascader/demos/demo1.tsx
Normal file
78
packages/client/src/schemas/cascader/demos/demo1.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaRenderer } from '../..';
|
||||||
|
|
||||||
|
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 <SchemaRenderer schema={schema} />;
|
||||||
|
};
|
136
packages/client/src/schemas/cascader/demos/demo2.tsx
Normal file
136
packages/client/src/schemas/cascader/demos/demo2.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SchemaRenderer } from '../..';
|
||||||
|
import { CascaderOptionType } from 'antd/lib/cascader';
|
||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { action } from '@formily/reactive';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRequest } from 'ahooks';
|
||||||
|
import { Resource } from '@nocobase/client/src/resource';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'zhejiang',
|
||||||
|
label: 'Zhejiang',
|
||||||
|
isLeaf: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiangsu',
|
||||||
|
label: 'Jiangsu',
|
||||||
|
isLeaf: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'string',
|
||||||
|
title: `编辑模式`,
|
||||||
|
name: 'name1',
|
||||||
|
// enum: options,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Cascader',
|
||||||
|
'x-component-props': {
|
||||||
|
changeOnSelect: true,
|
||||||
|
loadData: '{{ loadData }}',
|
||||||
|
useDataSource: '{{ useDataSource }}',
|
||||||
|
labelInValue: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'code',
|
||||||
|
children: 'children',
|
||||||
|
},
|
||||||
|
// changeOnSelect: true,
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
// '{{useAsyncDataSource(loadOptions)}}',
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
loadData: '{{ loadData }}',
|
||||||
|
useDataSource: '{{ useDataSource }}',
|
||||||
|
labelInValue: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'code',
|
||||||
|
children: 'children',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDataSource = ({ onSuccess }) => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const maxLevel = field.componentProps.maxLevel || 3;
|
||||||
|
const resource = Resource.make('china_regions');
|
||||||
|
useRequest(
|
||||||
|
() =>
|
||||||
|
resource.list({
|
||||||
|
perPage: -1,
|
||||||
|
filter: {
|
||||||
|
level: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
formatResult: (data) =>
|
||||||
|
data?.data?.map((item) => {
|
||||||
|
if (maxLevel !== 1) {
|
||||||
|
item.isLeaf = false;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
onSuccess,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = (selectedOptions: CascaderOptionType[], field: ArrayField) => {
|
||||||
|
const maxLevel = field.componentProps.maxLevel || 3;
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
targetOption.loading = true;
|
||||||
|
const resource = Resource.make('china_regions');
|
||||||
|
resource
|
||||||
|
.list({
|
||||||
|
perPage: -1,
|
||||||
|
filter: {
|
||||||
|
parent_code: targetOption['code'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
targetOption.loading = false;
|
||||||
|
targetOption.children =
|
||||||
|
data?.data?.map((item) => {
|
||||||
|
if (maxLevel > item.level) {
|
||||||
|
item.isLeaf = false;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}) || [];
|
||||||
|
field.dataSource = [...field.dataSource];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaRenderer debug scope={{ useDataSource, loadData }} schema={schema} />
|
||||||
|
);
|
||||||
|
};
|
@ -23,90 +23,10 @@ group:
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
### 省市区级联
|
### 省市区级联
|
||||||
|
|
||||||
```tsx
|
<code src="./demos/demo2.tsx" />
|
||||||
/**
|
|
||||||
* title: 省市区级联
|
|
||||||
*/
|
|
||||||
import React from 'react';
|
|
||||||
import { SchemaRenderer } from '../';
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<SchemaRenderer schema={schema} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
@ -1,13 +1,113 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { connect, mapReadPretty, mapProps } from '@formily/react'
|
import {
|
||||||
import { Cascader as AntdCascader } from 'antd'
|
connect,
|
||||||
import { Display } from '../display'
|
mapReadPretty,
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
mapProps,
|
||||||
|
useField,
|
||||||
|
observer,
|
||||||
|
} from '@formily/react';
|
||||||
|
import { Cascader as AntdCascader } from 'antd';
|
||||||
|
import { Display } from '../display';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { CascaderOptionType } from 'antd/lib/cascader';
|
||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { toArr } from '@formily/shared';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
const defaultFieldNames = {
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
export const Cascader = connect(
|
export const Cascader = connect(
|
||||||
(props) => {
|
(props: any) => {
|
||||||
console.log('props.title', props.title);
|
const field = useField<ArrayField>();
|
||||||
return <AntdCascader {...props}/>
|
const {
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
loadData,
|
||||||
|
labelInValue,
|
||||||
|
useDataSource,
|
||||||
|
fieldNames = defaultFieldNames,
|
||||||
|
...others
|
||||||
|
} = 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();
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 这里没有用 x-reactions 是因为 readyPretty=true 时不需要
|
||||||
|
if (useDataSource) {
|
||||||
|
useDataSource({
|
||||||
|
onSuccess: (data) => {
|
||||||
|
field.dataSource = data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (loadData) {
|
||||||
|
Object.assign(others, {
|
||||||
|
loadData: (selectedOptions: CascaderOptionType[]) => {
|
||||||
|
// 将 field 传给 loadData
|
||||||
|
loadData(selectedOptions, field);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AntdCascader
|
||||||
|
{...others}
|
||||||
|
value={toValue()}
|
||||||
|
fieldNames={fieldNames}
|
||||||
|
displayRender={displayRender}
|
||||||
|
onChange={(value, selectedOptions) => {
|
||||||
|
if (labelInValue) {
|
||||||
|
onChange(
|
||||||
|
selectedOptions.map((option) =>
|
||||||
|
omit(option, [fieldNames.children]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
console.log({ value, selectedOptions });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
mapProps(
|
mapProps(
|
||||||
{
|
{
|
||||||
@ -22,10 +122,39 @@ export const Cascader = connect(
|
|||||||
) : (
|
) : (
|
||||||
props.suffixIcon
|
props.suffixIcon
|
||||||
),
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
mapReadPretty((props) => {
|
||||||
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
// console.log({ data });
|
||||||
mapReadPretty(Display.Cascader)
|
return (
|
||||||
)
|
<div>
|
||||||
|
{data.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{typeof item === 'object' ? item[fieldNames.label] : item}
|
||||||
|
{len > index + 1 && ' / '}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export default Cascader
|
export default Cascader;
|
||||||
|
@ -16,7 +16,18 @@ export const chinaRegion: FieldOptions = {
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
// title,
|
// title,
|
||||||
'x-component': 'Cascader',
|
'x-component': 'Cascader',
|
||||||
'x-component-props': {},
|
'x-component-props': {
|
||||||
|
changeOnSelect: true,
|
||||||
|
loadData: '{{ ChinaRegion.loadData }}',
|
||||||
|
useDataSource: '{{ ChinaRegion.useDataSource }}',
|
||||||
|
labelInValue: true,
|
||||||
|
maxLevel: 3,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'name',
|
||||||
|
value: 'code',
|
||||||
|
children: 'children',
|
||||||
|
},
|
||||||
|
},
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-designable-bar': 'Cascader.DesignableBar',
|
'x-designable-bar': 'Cascader.DesignableBar',
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user