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(Display.Cascader)
 | 
					  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 });
 | 
				
			||||||
 | 
					    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