feat: improve cascader component
This commit is contained in:
		
							parent
							
								
									e9ddd4fd7e
								
							
						
					
					
						commit
						69d47a0331
					
				@ -48,7 +48,7 @@ const plugins = [
 | 
			
		||||
  '@nocobase/plugin-export',
 | 
			
		||||
  '@nocobase/plugin-system-settings',
 | 
			
		||||
  // '@nocobase/plugin-automations',
 | 
			
		||||
  // '@nocobase/plugin-china-region',
 | 
			
		||||
  '@nocobase/plugin-china-region',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
for (const plugin of plugins) {
 | 
			
		||||
 | 
			
		||||
@ -90,5 +90,9 @@ import * as uiSchema from './ui-schema';
 | 
			
		||||
    default: process.env.STORAGE_TYPE === 'ali-oss',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 导入地域数据
 | 
			
		||||
  const ChinaRegion = database.getModel('china_regions');
 | 
			
		||||
  ChinaRegion && await ChinaRegion.importData();
 | 
			
		||||
 | 
			
		||||
  await database.close();
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,10 @@
 | 
			
		||||
import { createForm } from '@formily/core';
 | 
			
		||||
import { ArrayField, createForm } from '@formily/core';
 | 
			
		||||
import {
 | 
			
		||||
  createSchemaField,
 | 
			
		||||
  FormProvider,
 | 
			
		||||
  Schema,
 | 
			
		||||
  SchemaOptionsContext,
 | 
			
		||||
  useField,
 | 
			
		||||
  useFieldSchema,
 | 
			
		||||
  useForm,
 | 
			
		||||
} from '@formily/react';
 | 
			
		||||
@ -18,7 +19,12 @@ import { useMemo } from 'react';
 | 
			
		||||
import { CodeOutlined } from '@ant-design/icons';
 | 
			
		||||
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 { ArrayTable } from '../../schemas/array-table';
 | 
			
		||||
@ -27,7 +33,10 @@ import { AddNew } from '../../schemas/add-new';
 | 
			
		||||
import { Cascader } from '../../schemas/cascader';
 | 
			
		||||
import { Checkbox } from '../../schemas/checkbox';
 | 
			
		||||
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 { Filter } from '../../schemas/filter';
 | 
			
		||||
import { Form } from '../../schemas/form';
 | 
			
		||||
@ -54,6 +63,9 @@ import { Chart } from '../../schemas/chart';
 | 
			
		||||
import { useDesignableSwitchContext } from '../../constate/DesignableSwitch';
 | 
			
		||||
import { action } from '@formily/reactive';
 | 
			
		||||
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 });
 | 
			
		||||
 | 
			
		||||
@ -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({
 | 
			
		||||
  scope: {
 | 
			
		||||
    Table,
 | 
			
		||||
    useAsyncDataSource,
 | 
			
		||||
    ChinaRegion: {
 | 
			
		||||
      loadData: loadChinaRegionData,
 | 
			
		||||
      useDataSource: useChinaRegionDataSource,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  components: {
 | 
			
		||||
    Card,
 | 
			
		||||
@ -163,7 +233,10 @@ export function findPropertyByPath(schema: Schema, path?: any): Schema {
 | 
			
		||||
  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) => {
 | 
			
		||||
    if (name === target.name) {
 | 
			
		||||
      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) => {
 | 
			
		||||
    const property = target.parent.properties[name];
 | 
			
		||||
    property.parent.removeProperty(property.name);
 | 
			
		||||
@ -211,8 +287,8 @@ export function useDesignable(path?: any) {
 | 
			
		||||
  const schemaPath = path || useSchemaPath();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  let current;
 | 
			
		||||
  let currentSchema = current =
 | 
			
		||||
    findPropertyByPath(schema, schemaPath) || ({} as Schema);
 | 
			
		||||
  let currentSchema = (current =
 | 
			
		||||
    findPropertyByPath(schema, schemaPath) || ({} as Schema));
 | 
			
		||||
  if (!currentSchema) {
 | 
			
		||||
    currentSchema = fieldSchema;
 | 
			
		||||
  }
 | 
			
		||||
@ -221,7 +297,10 @@ export function useDesignable(path?: any) {
 | 
			
		||||
  }
 | 
			
		||||
  // console.log('useDesignable', { schema, schemaPath, currentSchema });
 | 
			
		||||
  const options = useContext(SchemaOptionsContext);
 | 
			
		||||
  let DesignableBar = get(options.components, currentSchema['x-designable-bar']);
 | 
			
		||||
  let DesignableBar = get(
 | 
			
		||||
    options.components,
 | 
			
		||||
    currentSchema['x-designable-bar'],
 | 
			
		||||
  );
 | 
			
		||||
  if (!designable) {
 | 
			
		||||
    DesignableBar = () => null;
 | 
			
		||||
  }
 | 
			
		||||
@ -267,7 +346,10 @@ export function useDesignable(path?: any) {
 | 
			
		||||
      refresh();
 | 
			
		||||
      return target.properties[property.name];
 | 
			
		||||
    },
 | 
			
		||||
    appendChild: (property: ISchema | FormilyISchema, targetPath?: any): Schema => {
 | 
			
		||||
    appendChild: (
 | 
			
		||||
      property: ISchema | FormilyISchema,
 | 
			
		||||
      targetPath?: any,
 | 
			
		||||
    ): Schema => {
 | 
			
		||||
      let target = currentSchema;
 | 
			
		||||
      if (targetPath) {
 | 
			
		||||
        target = findPropertyByPath(schema, targetPath);
 | 
			
		||||
@ -294,7 +376,10 @@ export function useDesignable(path?: any) {
 | 
			
		||||
      refresh();
 | 
			
		||||
      return target.properties[property.name];
 | 
			
		||||
    },
 | 
			
		||||
    insertAfter: (property: ISchema | FormilyISchema, targetPath?: any): Schema => {
 | 
			
		||||
    insertAfter: (
 | 
			
		||||
      property: ISchema | FormilyISchema,
 | 
			
		||||
      targetPath?: any,
 | 
			
		||||
    ): Schema => {
 | 
			
		||||
      let target = currentSchema;
 | 
			
		||||
      if (targetPath) {
 | 
			
		||||
        target = findPropertyByPath(schema, targetPath);
 | 
			
		||||
@ -360,7 +445,7 @@ export function useDesignable(path?: any) {
 | 
			
		||||
          remove(s.parent);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      console.log({ removed })
 | 
			
		||||
      console.log({ removed });
 | 
			
		||||
      remove(target);
 | 
			
		||||
      refresh();
 | 
			
		||||
      return removed;
 | 
			
		||||
@ -533,7 +618,7 @@ export const SchemaRenderer = (props: SchemaRendererProps) => {
 | 
			
		||||
  // useEffect(() => {
 | 
			
		||||
  //   refresh(uid());
 | 
			
		||||
  // }, [designable]);
 | 
			
		||||
  console.log({ designable }, 'designable')
 | 
			
		||||
  console.log({ designable }, 'designable');
 | 
			
		||||
  const defaultRender = ({ schema }) => (
 | 
			
		||||
    <FormProvider form={form}>
 | 
			
		||||
      <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
 | 
			
		||||
 | 
			
		||||
### 基本用法
 | 
			
		||||
 | 
			
		||||
<code src="./demos/demo1.tsx" />
 | 
			
		||||
 | 
			
		||||
### 省市区级联
 | 
			
		||||
 | 
			
		||||
```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} />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
```
 | 
			
		||||
<code src="./demos/demo2.tsx" />
 | 
			
		||||
@ -1,13 +1,113 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { connect, mapReadPretty, mapProps } from '@formily/react'
 | 
			
		||||
import { Cascader as AntdCascader } from 'antd'
 | 
			
		||||
import { Display } from '../display'
 | 
			
		||||
import { LoadingOutlined } from '@ant-design/icons'
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  connect,
 | 
			
		||||
  mapReadPretty,
 | 
			
		||||
  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(
 | 
			
		||||
  (props) => {
 | 
			
		||||
    console.log('props.title', props.title);
 | 
			
		||||
    return <AntdCascader {...props}/>
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
    const field = useField<ArrayField>();
 | 
			
		||||
    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(
 | 
			
		||||
    {
 | 
			
		||||
@ -22,10 +122,39 @@ export const Cascader = connect(
 | 
			
		||||
          ) : (
 | 
			
		||||
            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 });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ),
 | 
			
		||||
  mapReadPretty(Display.Cascader)
 | 
			
		||||
)
 | 
			
		||||
    // 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',
 | 
			
		||||
      // title,
 | 
			
		||||
      '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-designable-bar': 'Cascader.DesignableBar',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user