refactor(client): add component to support data source select (#3691)
This commit is contained in:
		
							parent
							
								
									d904d13c52
								
							
						
					
					
						commit
						f68b2fb04a
					
				@ -132,10 +132,10 @@ export const useSelfAndChildrenCollections = (collectionName: string) => {
 | 
				
			|||||||
  return options;
 | 
					  return options;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useCollectionFilterOptions = (collection: any) => {
 | 
					export const useCollectionFilterOptions = (collection: any, dataSource?: string) => {
 | 
				
			||||||
  const { getCollectionFields, getInterface } = useCollectionManager_deprecated();
 | 
					  const { getCollectionFields, getInterface } = useCollectionManager_deprecated();
 | 
				
			||||||
  return useMemo(() => {
 | 
					  return useMemo(() => {
 | 
				
			||||||
    const fields = getCollectionFields(collection);
 | 
					    const fields = getCollectionFields(collection, dataSource);
 | 
				
			||||||
    const field2option = (field, depth) => {
 | 
					    const field2option = (field, depth) => {
 | 
				
			||||||
      if (!field.interface) {
 | 
					      if (!field.interface) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@ -165,7 +165,7 @@ export const useCollectionFilterOptions = (collection: any) => {
 | 
				
			|||||||
        option['children'] = children;
 | 
					        option['children'] = children;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (nested) {
 | 
					      if (nested) {
 | 
				
			||||||
        const targetFields = getCollectionFields(field.target);
 | 
					        const targetFields = getCollectionFields(field.target, dataSource);
 | 
				
			||||||
        const options = getOptions(targetFields, depth + 1).filter(Boolean);
 | 
					        const options = getOptions(targetFields, depth + 1).filter(Boolean);
 | 
				
			||||||
        option['children'] = option['children'] || [];
 | 
					        option['children'] = option['children'] || [];
 | 
				
			||||||
        option['children'].push(...options);
 | 
					        option['children'].push(...options);
 | 
				
			||||||
@ -184,7 +184,7 @@ export const useCollectionFilterOptions = (collection: any) => {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    const options = getOptions(fields, 1);
 | 
					    const options = getOptions(fields, 1);
 | 
				
			||||||
    return options;
 | 
					    return options;
 | 
				
			||||||
  }, [_.isString(collection) ? collection : collection?.name]);
 | 
					  }, [_.isString(collection) ? collection : collection?.name, dataSource]);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useCollectionFilterOptionsV2 = (collection: any) => {
 | 
					export const useCollectionFilterOptionsV2 = (collection: any) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -25,3 +25,4 @@ export * from './collectionPlugin';
 | 
				
			|||||||
export * from './mixins/InheritanceCollectionMixin';
 | 
					export * from './mixins/InheritanceCollectionMixin';
 | 
				
			||||||
export * from './sub-table';
 | 
					export * from './sub-table';
 | 
				
			||||||
export * from './CollectionHistoryProvider';
 | 
					export * from './CollectionHistoryProvider';
 | 
				
			||||||
 | 
					export * from './utils';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								packages/core/client/src/collection-manager/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/core/client/src/collection-manager/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					export function parseCollectionName(collection: string) {
 | 
				
			||||||
 | 
					  if (!collection) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const dataSourceCollection = collection.split(':');
 | 
				
			||||||
 | 
					  const collectionName = dataSourceCollection.pop();
 | 
				
			||||||
 | 
					  const dataSourceName = dataSourceCollection[0] ?? 'main';
 | 
				
			||||||
 | 
					  return [dataSourceName, collectionName];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function joinCollectionName(dataSourceName: string, collectionName: string) {
 | 
				
			||||||
 | 
					  if (!dataSourceName || dataSourceName === 'main') {
 | 
				
			||||||
 | 
					    return collectionName;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return `${dataSourceName}:${collectionName}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -851,7 +851,7 @@
 | 
				
			|||||||
  "loading": "加载中",
 | 
					  "loading": "加载中",
 | 
				
			||||||
  "name is required": "名称不能为空",
 | 
					  "name is required": "名称不能为空",
 | 
				
			||||||
  "The {{type}} \"{{name}}\" may have been deleted. Please remove this {{blockType}}.": "{{type}} \"{{name}}\" 可能已被删除。请删除当前{{blockType}}.",
 | 
					  "The {{type}} \"{{name}}\" may have been deleted. Please remove this {{blockType}}.": "{{type}} \"{{name}}\" 可能已被删除。请删除当前{{blockType}}.",
 | 
				
			||||||
  "data source": "数据源",
 | 
					  "Data source": "数据源",
 | 
				
			||||||
  "DataSource": "数据源",
 | 
					  "DataSource": "数据源",
 | 
				
			||||||
  "Data model": "数据模型",
 | 
					  "Data model": "数据模型",
 | 
				
			||||||
  "Security": "认证与安全",
 | 
					  "Security": "认证与安全",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,20 @@
 | 
				
			|||||||
import { connect, mapReadPretty, observer, useField } from '@formily/react';
 | 
					import { connect, mapReadPretty, observer, useField } from '@formily/react';
 | 
				
			||||||
import { Select, SelectProps, Tag } from 'antd';
 | 
					import { Cascader, Select, SelectProps, Tag } from 'antd';
 | 
				
			||||||
import React, { useContext } from 'react';
 | 
					import React, { useCallback, useContext, useMemo } from 'react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { useSelfAndChildrenCollections } from '../../../collection-manager/action-hooks';
 | 
					import { useSelfAndChildrenCollections } from '../../../collection-manager/action-hooks';
 | 
				
			||||||
import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager/hooks';
 | 
					import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager/hooks';
 | 
				
			||||||
import { useCompile } from '../../hooks';
 | 
					import { useCompile } from '../../hooks';
 | 
				
			||||||
import { FilterContext } from '../filter/context';
 | 
					import { FilterContext } from '../filter/context';
 | 
				
			||||||
 | 
					import { useDataSourceManager } from '../../../data-source';
 | 
				
			||||||
 | 
					import { parseCollectionName } from '../../../collection-manager';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type CollectionSelectProps = SelectProps<any, any> & {
 | 
					export type CollectionSelectProps = SelectProps<any, any> & {
 | 
				
			||||||
  filter?: (item: any, index: number, array: any[]) => boolean;
 | 
					  filter?: (item: any, index: number, array: any[]) => boolean;
 | 
				
			||||||
  isTableOid?: boolean;
 | 
					  isTableOid?: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function useOptions({ filter, isTableOid }: CollectionSelectProps) {
 | 
					function useCollectionOptions({ filter, isTableOid }: CollectionSelectProps) {
 | 
				
			||||||
  const compile = useCompile();
 | 
					  const compile = useCompile();
 | 
				
			||||||
  const field: any = useField();
 | 
					  const field: any = useField();
 | 
				
			||||||
  const ctx: any = useContext(FilterContext);
 | 
					  const ctx: any = useContext(FilterContext);
 | 
				
			||||||
@ -29,20 +31,30 @@ function useOptions({ filter, isTableOid }: CollectionSelectProps) {
 | 
				
			|||||||
    typeof filter === 'function'
 | 
					    typeof filter === 'function'
 | 
				
			||||||
      ? ((inheritCollections || currentCollections) as any[]).filter(filter)
 | 
					      ? ((inheritCollections || currentCollections) as any[]).filter(filter)
 | 
				
			||||||
      : inheritCollections || currentCollections;
 | 
					      : inheritCollections || currentCollections;
 | 
				
			||||||
  return filtered
 | 
					  return useMemo(
 | 
				
			||||||
    .filter((item) => !item.hidden)
 | 
					    () =>
 | 
				
			||||||
    .map((item) => ({
 | 
					      filtered
 | 
				
			||||||
      label: compile(item.title || item.label),
 | 
					        .filter((item) => !item.hidden)
 | 
				
			||||||
      value: item.name || item.value,
 | 
					        .map((item) => ({
 | 
				
			||||||
      color: item.category?.color,
 | 
					          label: compile(item.title || item.label),
 | 
				
			||||||
    }));
 | 
					          value: item.name || item.value,
 | 
				
			||||||
 | 
					          color: item.category?.color,
 | 
				
			||||||
 | 
					        })),
 | 
				
			||||||
 | 
					    [filtered],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CollectionSelect = connect(
 | 
					export const CollectionSelect = connect(
 | 
				
			||||||
  (props: CollectionSelectProps) => {
 | 
					  (props: CollectionSelectProps) => {
 | 
				
			||||||
    const { filter, ...others } = props;
 | 
					    const { filter, ...others } = props;
 | 
				
			||||||
    const options = useOptions(props);
 | 
					    const options = useCollectionOptions(props);
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
 | 
					    const optionFilter = useCallback(
 | 
				
			||||||
 | 
					      (input, option) =>
 | 
				
			||||||
 | 
					        (option?.label.toLowerCase() ?? '').includes(input.toLocaleLowerCase()) ||
 | 
				
			||||||
 | 
					        (option?.value.toString().toLowerCase() ?? '').includes(input.toLocaleLowerCase()),
 | 
				
			||||||
 | 
					      [],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <Select
 | 
					      <Select
 | 
				
			||||||
        // @ts-ignore
 | 
					        // @ts-ignore
 | 
				
			||||||
@ -52,10 +64,7 @@ export const CollectionSelect = connect(
 | 
				
			|||||||
        popupMatchSelectWidth={false}
 | 
					        popupMatchSelectWidth={false}
 | 
				
			||||||
        {...others}
 | 
					        {...others}
 | 
				
			||||||
        showSearch
 | 
					        showSearch
 | 
				
			||||||
        filterOption={(input, option) =>
 | 
					        filterOption={optionFilter}
 | 
				
			||||||
          (option?.label.toLowerCase() ?? '').includes(input.toLocaleLowerCase()) ||
 | 
					 | 
				
			||||||
          (option?.value.toString().toLowerCase() ?? '').includes(input.toLocaleLowerCase())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        options={options}
 | 
					        options={options}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@ -65,7 +74,7 @@ export const CollectionSelect = connect(
 | 
				
			|||||||
      (props: CollectionSelectProps) => {
 | 
					      (props: CollectionSelectProps) => {
 | 
				
			||||||
        const { mode } = props;
 | 
					        const { mode } = props;
 | 
				
			||||||
        const compile = useCompile();
 | 
					        const compile = useCompile();
 | 
				
			||||||
        const options = useOptions(props).filter((option) => {
 | 
					        const options = useCollectionOptions(props).filter((option) => {
 | 
				
			||||||
          if (mode === 'multiple') {
 | 
					          if (mode === 'multiple') {
 | 
				
			||||||
            return (props.value ?? []).includes(option.value);
 | 
					            return (props.value ?? []).includes(option.value);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -86,3 +95,88 @@ export const CollectionSelect = connect(
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
  ),
 | 
					  ),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type DataSourceSelectProps = SelectProps<any, any> & {
 | 
				
			||||||
 | 
					  filter?: (item: any, index: number, array: any[]) => boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useDataSourceOptions({ filter }: DataSourceSelectProps) {
 | 
				
			||||||
 | 
					  const compile = useCompile();
 | 
				
			||||||
 | 
					  const dataSourceManager = useDataSourceManager();
 | 
				
			||||||
 | 
					  const dataSources = dataSourceManager.getDataSources();
 | 
				
			||||||
 | 
					  return useMemo(
 | 
				
			||||||
 | 
					    () => [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        label: compile('Main'),
 | 
				
			||||||
 | 
					        value: 'main',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      ...(typeof filter === 'function' ? dataSources.filter(filter) : dataSources).map((item) => ({
 | 
				
			||||||
 | 
					        label: item.displayName,
 | 
				
			||||||
 | 
					        value: item.key,
 | 
				
			||||||
 | 
					      })),
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    [dataSources, filter],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DataSourceSelect = connect((props: DataSourceSelectProps) => {
 | 
				
			||||||
 | 
					  const { filter, ...others } = props;
 | 
				
			||||||
 | 
					  const options = useDataSourceOptions(props);
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  const optionFilter = useCallback(
 | 
				
			||||||
 | 
					    (input, option) =>
 | 
				
			||||||
 | 
					      (option?.label.toLowerCase() ?? '').includes(input.toLocaleLowerCase()) ||
 | 
				
			||||||
 | 
					      (option?.value.toString().toLowerCase() ?? '').includes(input.toLocaleLowerCase()),
 | 
				
			||||||
 | 
					    [],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Select
 | 
				
			||||||
 | 
					      // @ts-ignore
 | 
				
			||||||
 | 
					      role="button"
 | 
				
			||||||
 | 
					      data-testid="select-datasource"
 | 
				
			||||||
 | 
					      placeholder={t('Select data source')}
 | 
				
			||||||
 | 
					      popupMatchSelectWidth={false}
 | 
				
			||||||
 | 
					      {...others}
 | 
				
			||||||
 | 
					      showSearch
 | 
				
			||||||
 | 
					      filterOption={optionFilter}
 | 
				
			||||||
 | 
					      options={options}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DataSourceCollectionCascader = connect((props) => {
 | 
				
			||||||
 | 
					  const dataSourceManager = useDataSourceManager();
 | 
				
			||||||
 | 
					  const compile = useCompile();
 | 
				
			||||||
 | 
					  const { value, onChange, dataSourceFilter, collectionFilter, ...others } = props;
 | 
				
			||||||
 | 
					  const [dataSourceName, collectionName] = parseCollectionName(value);
 | 
				
			||||||
 | 
					  const path = [dataSourceName, collectionName].filter(Boolean);
 | 
				
			||||||
 | 
					  const dataSources = dataSourceManager.getDataSources();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const options = useMemo(() => {
 | 
				
			||||||
 | 
					    return (dataSourceFilter ? dataSources.filter(dataSourceFilter) : dataSources).map((dataSource) => {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        label: compile(dataSource.displayName),
 | 
				
			||||||
 | 
					        value: dataSource.key,
 | 
				
			||||||
 | 
					        children: dataSource.collectionManager.collectionInstancesArr
 | 
				
			||||||
 | 
					          .filter(collectionFilter ?? ((collection) => !collection.hidden))
 | 
				
			||||||
 | 
					          .map((collection) => {
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					              label: compile(collection.title),
 | 
				
			||||||
 | 
					              value: collection.name,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, [dataSources, dataSourceFilter, collectionFilter]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleChange = useCallback(
 | 
				
			||||||
 | 
					    (value) => {
 | 
				
			||||||
 | 
					      if (!value) {
 | 
				
			||||||
 | 
					        return onChange(value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      onChange(value[0] === 'main' ? value[1] : value.join(':'));
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [onChange],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  return <Cascader showSearch {...others} options={options} value={path} onChange={handleChange} />;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -9,3 +9,4 @@ export * from './load-default-actions';
 | 
				
			|||||||
export * from './types';
 | 
					export * from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from './data-source-with-database';
 | 
					export * from './data-source-with-database';
 | 
				
			||||||
 | 
					export * from './utils';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								packages/core/data-source-manager/src/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/core/data-source-manager/src/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					export function parseCollectionName(collection: string) {
 | 
				
			||||||
 | 
					  if (!collection) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const dataSourceCollection = collection.split(':');
 | 
				
			||||||
 | 
					  const collectionName = dataSourceCollection.pop();
 | 
				
			||||||
 | 
					  const dataSourceName = dataSourceCollection[0] ?? 'main';
 | 
				
			||||||
 | 
					  return [dataSourceName, collectionName];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user