feat(association field):quick add new (#1953)
* refactor: association select support quieck add * chore: tmp commit * refactor: association select support quick add * feat: firstOrCreate * refactor: locale * refactor: create api * chore: firstOrCreate * feat: updateOrCreate * chore: test * refactor: save mode edit in add new form * feat: values to filter * refactor: loacle improve * refactor: loacle improve * refactor: loacle improve * feat: firstOrCreate http api * refactor: code improve * fix: build error * refactor: local * refactor: locale improve * refactor: useCollectionFieldsOptions * fix: code imprtove * refactor: code improve * refactor: dropdown open * refactor: add new mode * refactor: add new mode code improve * refactor: add new mode code improve * refactor: add new mode code improve --------- Co-authored-by: chareice <chareice@live.com>
This commit is contained in:
		
							parent
							
								
									7abfbe7be4
								
							
						
					
					
						commit
						70890f2f50
					
				@ -143,6 +143,8 @@ export const useCreateActionProps = () => {
 | 
			
		||||
  const currentRecord = useRecord();
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const action = actionField.componentProps.saveMode || 'create';
 | 
			
		||||
  const filterKeys = actionField.componentProps.filterKeys || [];
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const fieldNames = fields.map((field) => field.name);
 | 
			
		||||
@ -167,12 +169,13 @@ export const useCreateActionProps = () => {
 | 
			
		||||
      actionField.data = field.data || {};
 | 
			
		||||
      actionField.data.loading = true;
 | 
			
		||||
      try {
 | 
			
		||||
        const data = await resource.create({
 | 
			
		||||
        const data = await resource[action]({
 | 
			
		||||
          values: {
 | 
			
		||||
            ...values,
 | 
			
		||||
            ...overwriteValues,
 | 
			
		||||
            ...assignedValues,
 | 
			
		||||
          },
 | 
			
		||||
          filterKeys:filterKeys,
 | 
			
		||||
        });
 | 
			
		||||
        actionField.data.loading = false;
 | 
			
		||||
        actionField.data.data = data;
 | 
			
		||||
 | 
			
		||||
@ -260,6 +260,63 @@ export const useLinkageCollectionFilterOptions = (collectionName: string) => {
 | 
			
		||||
  const options = getOptions(fields, 1);
 | 
			
		||||
  return options;
 | 
			
		||||
};
 | 
			
		||||
// 通用
 | 
			
		||||
export const useCollectionFieldsOptions = (collectionName: string, maxDepth = 2, excludes = []) => {
 | 
			
		||||
  const { getCollectionFields, getInterface } = useCollectionManager();
 | 
			
		||||
  const fields = getCollectionFields(collectionName).filter((v) => !excludes.includes(v.interface));
 | 
			
		||||
 | 
			
		||||
  const field2option = (field, depth, prefix?) => {
 | 
			
		||||
    if (!field.interface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const fieldInterface = getInterface(field.interface);
 | 
			
		||||
    if (!fieldInterface?.filterable) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const { nested, children } = fieldInterface.filterable;
 | 
			
		||||
    const value = prefix ? `${prefix}.${field.name}` : field.name;
 | 
			
		||||
    const option = {
 | 
			
		||||
      ...field,
 | 
			
		||||
      name: field.name,
 | 
			
		||||
      title: field?.uiSchema?.title || field.name,
 | 
			
		||||
      schema: field?.uiSchema,
 | 
			
		||||
      key: value,
 | 
			
		||||
    };
 | 
			
		||||
    if (field.target && depth > maxDepth) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (depth > maxDepth) {
 | 
			
		||||
      return option;
 | 
			
		||||
    }
 | 
			
		||||
    if (children?.length) {
 | 
			
		||||
      option['children'] = children.map((v) => {
 | 
			
		||||
        return {
 | 
			
		||||
          ...v,
 | 
			
		||||
          key: `${field.name}.${v.name}`,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (nested) {
 | 
			
		||||
      const targetFields = getCollectionFields(field.target).filter((v) => !excludes.includes(v.interface));
 | 
			
		||||
      const options = getOptions(targetFields, depth + 1, field.name).filter(Boolean);
 | 
			
		||||
      option['children'] = option['children'] || [];
 | 
			
		||||
      option['children'].push(...options);
 | 
			
		||||
    }
 | 
			
		||||
    return option;
 | 
			
		||||
  };
 | 
			
		||||
  const getOptions = (fields, depth, prefix?) => {
 | 
			
		||||
    const options = [];
 | 
			
		||||
    fields.forEach((field) => {
 | 
			
		||||
      const option = field2option(field, depth, prefix);
 | 
			
		||||
      if (option) {
 | 
			
		||||
        options.push(option);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return options;
 | 
			
		||||
  };
 | 
			
		||||
  const options = getOptions(fields, 1);
 | 
			
		||||
  return options;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useFilterDataSource = (options) => {
 | 
			
		||||
  const { name } = useCollection();
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
export { useCollectionFilterOptions, useSortFields, useLinkageCollectionFilterOptions } from './action-hooks';
 | 
			
		||||
export { useCollectionFilterOptions, useSortFields, useLinkageCollectionFilterOptions ,useCollectionFieldsOptions} from './action-hooks';
 | 
			
		||||
export * from './CollectionField';
 | 
			
		||||
export * from './CollectionFieldProvider';
 | 
			
		||||
export * from './CollectionManagerProvider';
 | 
			
		||||
 | 
			
		||||
@ -694,5 +694,14 @@ export default {
 | 
			
		||||
  "Duplicate mode":"Duplicate mode",
 | 
			
		||||
  "Quick duplicate":"Quick duplicate",
 | 
			
		||||
  "Duplicate and continue":"Duplicate and continue",
 | 
			
		||||
  "Please configure the duplicate fields":"Please configure the duplicate fields"
 | 
			
		||||
  "Please configure the duplicate fields":"Please configure the duplicate fields",
 | 
			
		||||
  "Add":"Create",
 | 
			
		||||
  "Add new mode":"Add new mode",
 | 
			
		||||
  "Quick add":"Quick add",
 | 
			
		||||
  "Modal add":"Modal add",
 | 
			
		||||
  "Save mode":"Save mode",
 | 
			
		||||
  "First or create":"First or create",
 | 
			
		||||
  "Update or create":"Update or create",
 | 
			
		||||
  "Find by the following fields":"Find by the following fields",
 | 
			
		||||
  "Create":"Create"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -688,4 +688,6 @@ export default {
 | 
			
		||||
    "Sortable": "Clasificable",
 | 
			
		||||
    "Enable link": "Activar enlace",
 | 
			
		||||
    "Data template": "Plantilla de datos",
 | 
			
		||||
    "Not found":"No encontrado",
 | 
			
		||||
    "Add":"Añadir"
 | 
			
		||||
 };
 | 
			
		||||
 | 
			
		||||
@ -605,5 +605,14 @@ export default {
 | 
			
		||||
  "Duplicate mode":"コピーモード",
 | 
			
		||||
  "Quick duplicate":"今すぐコピー",
 | 
			
		||||
  "Duplicate and continue":"コピーして続行",
 | 
			
		||||
  "Please configure the duplicate fields":"コピーするフィールドを設定してください"
 | 
			
		||||
  "Please configure the duplicate fields":"コピーするフィールドを設定してください",
 | 
			
		||||
  "Add":"追加",
 | 
			
		||||
  "Add new mode":"追加モード",
 | 
			
		||||
  "Quick add":"すばやい",
 | 
			
		||||
  "Modal add":"ポップアップ窓の追加",
 | 
			
		||||
  "Save mode":"保存方法",
 | 
			
		||||
  "First or create":"存在しない場合に追加",
 | 
			
		||||
  "Update or create":"存在しなければ新規、存在すれば更新",
 | 
			
		||||
  "Find by the following fields":"次のフィールドで検索",
 | 
			
		||||
  "Create":"新規のみ"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -668,4 +668,6 @@ export default {
 | 
			
		||||
  'Display data template selector': 'Exibir seletor de modelo de dados',
 | 
			
		||||
  'Form data templates': 'Modelos de dados do formulário',
 | 
			
		||||
  "Data template": "Modelo de dados",
 | 
			
		||||
  "Not found":"Não encontrado",
 | 
			
		||||
  "Add":"Adicionar"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -504,4 +504,6 @@ export default {
 | 
			
		||||
  'Display data template selector': "Отображать селектор шаблона данных",
 | 
			
		||||
  'Form data templates': "Шаблоны данных формы",
 | 
			
		||||
  "Data template": "Шаблон данных",
 | 
			
		||||
  "Not found":"Не найдено",
 | 
			
		||||
  "Add":"Добавить"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -771,5 +771,14 @@ export default {
 | 
			
		||||
  "Duplicate mode":"复制方式",
 | 
			
		||||
  "Quick duplicate":"快速复制",
 | 
			
		||||
  "Duplicate and continue":"复制并继续",
 | 
			
		||||
  "Please configure the duplicate fields":"请配置要复制的字段"
 | 
			
		||||
  "Please configure the duplicate fields":"请配置要复制的字段",
 | 
			
		||||
  "Add":"创建",
 | 
			
		||||
  "Add new mode":"添加方式",
 | 
			
		||||
  "Quick add":"快捷添加",
 | 
			
		||||
  "Modal add":"弹窗添加",
 | 
			
		||||
  'Save mode':"保存方式",
 | 
			
		||||
  "First or create":"不存在时则新增,存在时不处理",
 | 
			
		||||
  "Update or create":"不存在时新增,存在时更新",
 | 
			
		||||
  "Find by the following fields":"通过以下字段查找",
 | 
			
		||||
  "Create":"仅新增"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import { cloneDeep } from 'lodash';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useDesignable } from '../..';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { useCollection, useCollectionManager, useCollectionFieldsOptions } from '../../../collection-manager';
 | 
			
		||||
import { OpenModeSchemaItems } from '../../../schema-items';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useLinkageAction } from './hooks';
 | 
			
		||||
@ -15,6 +15,7 @@ import { requestSettingsSchema } from './utils';
 | 
			
		||||
const Tree = connect(
 | 
			
		||||
  AntdTree,
 | 
			
		||||
  mapProps((props, field: any) => {
 | 
			
		||||
    console.log(props, field);
 | 
			
		||||
    return {
 | 
			
		||||
      ...props,
 | 
			
		||||
      onCheck: (checkedKeys) => {
 | 
			
		||||
@ -57,18 +58,21 @@ export const ActionDesigner = (props) => {
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const isAction = useLinkageAction();
 | 
			
		||||
  const isPopupAction = ['create', 'update', 'view', 'customize:popup','duplicate'].includes(fieldSchema['x-action'] || '');
 | 
			
		||||
  const isPopupAction = ['create', 'update', 'view', 'customize:popup', 'duplicate'].includes(
 | 
			
		||||
    fieldSchema['x-action'] || '',
 | 
			
		||||
  );
 | 
			
		||||
  const isUpdateModePopupAction = ['customize:bulkUpdate', 'customize:bulkEdit'].includes(fieldSchema['x-action']);
 | 
			
		||||
  const [initialSchema, setInitialSchema] = useState<ISchema>();
 | 
			
		||||
  const actionType = fieldSchema['x-action'] ?? '';
 | 
			
		||||
  const isLinkageAction = linkageAction || isAction;
 | 
			
		||||
  const isChildCollectionAction = getChildrenCollections(name).length > 0 && fieldSchema['x-action'] === 'create';
 | 
			
		||||
  const isLink = fieldSchema['x-component'] === 'Action.Link';
 | 
			
		||||
  const isDelete = fieldSchema?.parent?.['x-component'] === 'CollectionField';
 | 
			
		||||
  const isDraggable = fieldSchema?.parent?.['x-component'] !== 'CollectionField';
 | 
			
		||||
  const isDelete = fieldSchema?.parent['x-component'] === 'CollectionField';
 | 
			
		||||
  const isDraggable = fieldSchema?.parent['x-component'] !== 'CollectionField';
 | 
			
		||||
  const isDuplicateAction = fieldSchema['x-action'] === 'duplicate';
 | 
			
		||||
  const { collectionList, getEnableFieldTree, onLoadData, onCheck } = useCollectionState(name);
 | 
			
		||||
  const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
 | 
			
		||||
  const options = useCollectionFieldsOptions(name, 1, ['id']);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const schemaUid = uid();
 | 
			
		||||
    const schema: ISchema = {
 | 
			
		||||
@ -157,6 +161,80 @@ export const ActionDesigner = (props) => {
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        {fieldSchema['x-action'] === 'submit' &&
 | 
			
		||||
          fieldSchema.parent?.['x-initializer'] === 'CreateFormActionInitializers' && (
 | 
			
		||||
            <SchemaSettings.ModalItem
 | 
			
		||||
              title={t('Save mode')}
 | 
			
		||||
              components={{ Tree }}
 | 
			
		||||
              schema={
 | 
			
		||||
                {
 | 
			
		||||
                  type: 'object',
 | 
			
		||||
                  title: t('Save mode'),
 | 
			
		||||
                  properties: {
 | 
			
		||||
                    saveMode: {
 | 
			
		||||
                      'x-decorator': 'FormItem',
 | 
			
		||||
                      'x-component': 'Radio.Group',
 | 
			
		||||
                      title: t('Save mode'),
 | 
			
		||||
                      default: field.componentProps.saveMode || 'create',
 | 
			
		||||
                      enum: [
 | 
			
		||||
                        { value: 'create', label: '{{t("Create")}}' },
 | 
			
		||||
                        { value: 'firstOrCreate', label: '{{t("First or create")}}' },
 | 
			
		||||
                        { value: 'updateOrCreate', label: '{{t("Update or create")}}' },
 | 
			
		||||
                      ],
 | 
			
		||||
                    },
 | 
			
		||||
                    filterKeys: {
 | 
			
		||||
                      type: 'array',
 | 
			
		||||
                      title: '{{ t("Find by the following fields") }}',
 | 
			
		||||
                      required: true,
 | 
			
		||||
                      default: field.componentProps.filterKeys,
 | 
			
		||||
                      'x-decorator': 'FormItem',
 | 
			
		||||
                      'x-component': 'Tree',
 | 
			
		||||
                      'x-component-props': {
 | 
			
		||||
                        treeData: options,
 | 
			
		||||
                        checkable: true,
 | 
			
		||||
                        defaultCheckedKeys: field.componentProps.filterKeys,
 | 
			
		||||
                        rootStyle: {
 | 
			
		||||
                          padding: '8px 0',
 | 
			
		||||
                          border: '1px solid #d9d9d9',
 | 
			
		||||
                          borderRadius: '2px',
 | 
			
		||||
                          maxHeight: '30vh',
 | 
			
		||||
                          overflow: 'auto',
 | 
			
		||||
                          margin: '2px 0',
 | 
			
		||||
                        },
 | 
			
		||||
                      },
 | 
			
		||||
                      'x-reactions': [
 | 
			
		||||
                        {
 | 
			
		||||
                          dependencies: ['.saveMode'],
 | 
			
		||||
                          fulfill: {
 | 
			
		||||
                            state: {
 | 
			
		||||
                              hidden: '{{ $deps[0]==="create"}}',
 | 
			
		||||
                            },
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                      ],
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                } as ISchema
 | 
			
		||||
              }
 | 
			
		||||
              onSubmit={({ saveMode, filterKeys }) => {
 | 
			
		||||
                console.log(saveMode, filterKeys);
 | 
			
		||||
                field.componentProps.saveMode = saveMode;
 | 
			
		||||
                field.componentProps.filterKeys = filterKeys;
 | 
			
		||||
                fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
                fieldSchema['x-component-props'].saveMode = saveMode;
 | 
			
		||||
                fieldSchema['x-component-props'].filterKeys = filterKeys;
 | 
			
		||||
                dn.emit('patch', {
 | 
			
		||||
                  schema: {
 | 
			
		||||
                    ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                    'x-component-props': {
 | 
			
		||||
                      ...fieldSchema['x-component-props'],
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                });
 | 
			
		||||
                dn.refresh();
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        {isLinkageAction && <SchemaSettings.LinkageRules collectionName={name} />}
 | 
			
		||||
        {isDuplicateAction && [
 | 
			
		||||
          <SchemaSettings.ModalItem
 | 
			
		||||
 | 
			
		||||
@ -1,54 +1,97 @@
 | 
			
		||||
import { LoadingOutlined } from '@ant-design/icons';
 | 
			
		||||
import { RecursionField, connect, mapProps, observer, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { Input } from 'antd';
 | 
			
		||||
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { RecursionField, connect, mapProps, observer, useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { Input, Button, message } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { RecordProvider } from '../../../';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
 | 
			
		||||
import useServiceOptions from './hooks';
 | 
			
		||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
 | 
			
		||||
import { useAPIClient, useCollectionManager } from '../../../';
 | 
			
		||||
import { isFunction } from 'mathjs';
 | 
			
		||||
 | 
			
		||||
export type AssociationSelectProps<P = any> = RemoteSelectProps<P> & {
 | 
			
		||||
  action?: string;
 | 
			
		||||
  multiple?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const InternalAssociationSelect = observer(
 | 
			
		||||
  (props: AssociationSelectProps) => {
 | 
			
		||||
    const { fieldNames, objectValue = true } = props;
 | 
			
		||||
    const field: any = useField();
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const service = useServiceOptions(props);
 | 
			
		||||
    const isAllowAddNew = fieldSchema['x-add-new'];
 | 
			
		||||
    const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value;
 | 
			
		||||
 | 
			
		||||
const InternalAssociationSelect = observer((props: AssociationSelectProps) => {
 | 
			
		||||
  const { objectValue = true } = props;
 | 
			
		||||
  const field: any = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { getCollection } = useCollectionManager();
 | 
			
		||||
  const service = useServiceOptions(props);
 | 
			
		||||
  const { options: collectionField } = useAssociationFieldContext();
 | 
			
		||||
  const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value;
 | 
			
		||||
  const addMode = fieldSchema['x-component-props']?.addMode;
 | 
			
		||||
  const isAllowAddNew = fieldSchema['x-add-new'];
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { multiple } = props;
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const api = useAPIClient();
 | 
			
		||||
  const resource = api.resource(collectionField.target);
 | 
			
		||||
  const targetCollection = getCollection(collectionField.target);
 | 
			
		||||
  const handleCreateAction = async (props) => {
 | 
			
		||||
    const { search: value, callBack } = props;
 | 
			
		||||
    const {
 | 
			
		||||
      data: { data },
 | 
			
		||||
    } = await resource.create({
 | 
			
		||||
      values: {
 | 
			
		||||
        [field?.componentProps?.fieldNames?.label || 'id']: value,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    if (data) {
 | 
			
		||||
      if (['m2m', 'o2m'].includes(collectionField?.interface) && multiple !== false) {
 | 
			
		||||
        const values = form.getValuesIn(field.path) || [];
 | 
			
		||||
        values.push(data);
 | 
			
		||||
        form.setValuesIn(field.path, values);
 | 
			
		||||
        field.onInput(values);
 | 
			
		||||
      } else {
 | 
			
		||||
        form.setValuesIn(field.path, data);
 | 
			
		||||
        field.onInput(data);
 | 
			
		||||
      }
 | 
			
		||||
      isFunction(callBack) && callBack?.();
 | 
			
		||||
      message.success(t('Saved successfully'));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  const QuickAddContent = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div key={fieldSchema.name}>
 | 
			
		||||
        <Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
 | 
			
		||||
          <RemoteSelect
 | 
			
		||||
            style={{ width: '100%' }}
 | 
			
		||||
            {...props}
 | 
			
		||||
            objectValue={objectValue}
 | 
			
		||||
            value={value}
 | 
			
		||||
            service={service}
 | 
			
		||||
          ></RemoteSelect>
 | 
			
		||||
 | 
			
		||||
          {isAllowAddNew && (
 | 
			
		||||
            <RecordProvider record={null}>
 | 
			
		||||
              <RecursionField
 | 
			
		||||
                onlyRenderProperties
 | 
			
		||||
                basePath={field.address}
 | 
			
		||||
                schema={fieldSchema}
 | 
			
		||||
                filterProperties={(s) => {
 | 
			
		||||
                  return s['x-component'] === 'Action';
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            </RecordProvider>
 | 
			
		||||
          )}
 | 
			
		||||
        </Input.Group>
 | 
			
		||||
      <div
 | 
			
		||||
        onClick={() => handleCreateAction(props)}
 | 
			
		||||
        style={{ cursor: 'pointer', padding: '5px 12px', color: '#0d0c0c' }}
 | 
			
		||||
      >
 | 
			
		||||
        <PlusOutlined />
 | 
			
		||||
        <span style={{ paddingLeft: 5 }}>{t('Add') + ` “${props.search}” `}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  { displayName: 'InternalAssociationSelect' },
 | 
			
		||||
);
 | 
			
		||||
  };
 | 
			
		||||
  return (
 | 
			
		||||
    <div key={fieldSchema.name}>
 | 
			
		||||
      <Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
 | 
			
		||||
        <RemoteSelect
 | 
			
		||||
          style={{ width: '100%' }}
 | 
			
		||||
          {...props}
 | 
			
		||||
          objectValue={objectValue}
 | 
			
		||||
          value={value}
 | 
			
		||||
          service={service}
 | 
			
		||||
          CustomDropdownRender={addMode === 'quickAdd' && QuickAddContent}
 | 
			
		||||
        ></RemoteSelect>
 | 
			
		||||
 | 
			
		||||
        {(addMode === 'modalAdd' || isAllowAddNew) && (
 | 
			
		||||
          <RecordProvider record={null}>
 | 
			
		||||
            <RecursionField
 | 
			
		||||
              onlyRenderProperties
 | 
			
		||||
              basePath={field.address}
 | 
			
		||||
              schema={fieldSchema}
 | 
			
		||||
              filterProperties={(s) => {
 | 
			
		||||
                return s['x-component'] === 'Action';
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </RecordProvider>
 | 
			
		||||
        )}
 | 
			
		||||
      </Input.Group>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface AssociationSelectInterface {
 | 
			
		||||
  (props: any): React.ReactElement;
 | 
			
		||||
 | 
			
		||||
@ -617,7 +617,7 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
          </div>
 | 
			
		||||
        </SchemaSettings.Item>
 | 
			
		||||
      )}
 | 
			
		||||
      {!field.readPretty && isAssociationField && ['Select', 'Picker'].includes(fieldMode) && (
 | 
			
		||||
      {!field.readPretty && isAssociationField && ['Picker'].includes(fieldMode) && (
 | 
			
		||||
        <SchemaSettings.SwitchItem
 | 
			
		||||
          key="allowAddNew"
 | 
			
		||||
          title={t('Allow add new data')}
 | 
			
		||||
@ -658,6 +658,56 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {!field.readPretty && isAssociationField && ['Select'].includes(fieldMode) && (
 | 
			
		||||
        <SchemaSettings.SelectItem
 | 
			
		||||
          key="add-mode"
 | 
			
		||||
          title={t('Add new mode')}
 | 
			
		||||
          options={[
 | 
			
		||||
            { label: t('None'), value: 'none' },
 | 
			
		||||
            { label: t('Quick add'), value: 'quickAdd' },
 | 
			
		||||
            { label: t('Modal add'), value: 'modalAdd' },
 | 
			
		||||
          ]}
 | 
			
		||||
          value={field.componentProps?.addMode || 'none'}
 | 
			
		||||
          onChange={(mode) => {
 | 
			
		||||
            if (mode === 'modalAdd') {
 | 
			
		||||
              const hasAddNew = fieldSchema.reduceProperties((buf, schema) => {
 | 
			
		||||
                if (schema['x-component'] === 'Action') {
 | 
			
		||||
                  return schema;
 | 
			
		||||
                }
 | 
			
		||||
                return buf;
 | 
			
		||||
              }, null);
 | 
			
		||||
 | 
			
		||||
              if (!hasAddNew) {
 | 
			
		||||
                const addNewActionschema = {
 | 
			
		||||
                  'x-action': 'create',
 | 
			
		||||
                  title: "{{t('Add new')}}",
 | 
			
		||||
                  'x-designer': 'Action.Designer',
 | 
			
		||||
                  'x-component': 'Action',
 | 
			
		||||
                  'x-decorator': 'ACLActionProvider',
 | 
			
		||||
                  'x-component-props': {
 | 
			
		||||
                    openMode: 'drawer',
 | 
			
		||||
                    type: 'default',
 | 
			
		||||
                    component: 'CreateRecordAction',
 | 
			
		||||
                  },
 | 
			
		||||
                };
 | 
			
		||||
                insertAdjacent('afterBegin', addNewActionschema);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            const schema = {
 | 
			
		||||
              ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            };
 | 
			
		||||
            fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
            fieldSchema['x-component-props']['addMode'] = mode;
 | 
			
		||||
            schema['x-component-props'] = fieldSchema['x-component-props'];
 | 
			
		||||
            field.componentProps = field.componentProps || {};
 | 
			
		||||
            field.componentProps.addMode = mode;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema,
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {isAssociationField && IsShowMultipleSwitch() ? (
 | 
			
		||||
        <SchemaSettings.SwitchItem
 | 
			
		||||
          key="multiple"
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { LoadingOutlined } from '@ant-design/icons';
 | 
			
		||||
import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { SelectProps, Tag } from 'antd';
 | 
			
		||||
import { SelectProps, Tag, Empty, Divider } from 'antd';
 | 
			
		||||
import { uniqBy } from 'lodash';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 | 
			
		||||
import { ResourceActionOptions, useRequest } from '../../../api-client';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
@ -21,6 +21,7 @@ export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
 | 
			
		||||
  mapOptions?: (data: any) => RemoteSelectProps['fieldNames'];
 | 
			
		||||
  targetField?: any;
 | 
			
		||||
  service: ResourceActionOptions<P>;
 | 
			
		||||
  CustomDropdownRender?: (v: any) => any;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const InternalRemoteSelect = connect(
 | 
			
		||||
@ -34,12 +35,16 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
      manual = true,
 | 
			
		||||
      mapOptions,
 | 
			
		||||
      targetField: _targetField,
 | 
			
		||||
      CustomDropdownRender,
 | 
			
		||||
      ...others
 | 
			
		||||
    } = props;
 | 
			
		||||
    const [open, setOpen] = useState(false);
 | 
			
		||||
    const firstRun = useRef(false);
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const isQuickAdd = fieldSchema['x-component-props']?.addMode === 'quickAdd';
 | 
			
		||||
    const field = useField();
 | 
			
		||||
    const { getField } = useCollection();
 | 
			
		||||
    const searchData = useRef(null);
 | 
			
		||||
    const { getCollectionJoinField, getInterface } = useCollectionManager();
 | 
			
		||||
    const collectionField = getField(fieldSchema.name);
 | 
			
		||||
    const targetField =
 | 
			
		||||
@ -128,7 +133,6 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
        debounceWait: wait,
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const runDep = useMemo(
 | 
			
		||||
      () =>
 | 
			
		||||
        JSON.stringify({
 | 
			
		||||
@ -137,6 +141,20 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
        }),
 | 
			
		||||
      [service, fieldNames],
 | 
			
		||||
    );
 | 
			
		||||
    const CustomRenderCom = useCallback(() => {
 | 
			
		||||
      if (searchData.current && CustomDropdownRender) {
 | 
			
		||||
        return (
 | 
			
		||||
          <CustomDropdownRender
 | 
			
		||||
            search={searchData.current}
 | 
			
		||||
            callBack={() => {
 | 
			
		||||
              searchData.current = null;
 | 
			
		||||
              setOpen(false);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      return null;
 | 
			
		||||
    }, [searchData.current]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      // Lazy load
 | 
			
		||||
@ -158,6 +176,7 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
          field.componentProps?.service?.params?.filter || service?.params?.filter,
 | 
			
		||||
        ]),
 | 
			
		||||
      });
 | 
			
		||||
      searchData.current = search;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const options = useMemo(() => {
 | 
			
		||||
@ -167,8 +186,10 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
      const valueOptions = (value != null && (Array.isArray(value) ? value : [value])) || [];
 | 
			
		||||
      return uniqBy(data?.data?.concat(valueOptions) || [], fieldNames.value);
 | 
			
		||||
    }, [data?.data, value]);
 | 
			
		||||
    const onDropdownVisibleChange = () => {
 | 
			
		||||
      if (firstRun.current) {
 | 
			
		||||
    const onDropdownVisibleChange = (visible) => {
 | 
			
		||||
      setOpen(visible);
 | 
			
		||||
      searchData.current = null;
 | 
			
		||||
      if (firstRun.current && data?.data.length > 0) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      run();
 | 
			
		||||
@ -176,6 +197,7 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
      <Select
 | 
			
		||||
        open={open}
 | 
			
		||||
        dropdownMatchSelectWidth={false}
 | 
			
		||||
        autoClearSearchValue
 | 
			
		||||
        filterOption={false}
 | 
			
		||||
@ -189,6 +211,22 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
        loading={data! ? loading : true}
 | 
			
		||||
        options={mapOptionsToTags(options)}
 | 
			
		||||
        rawOptions={options}
 | 
			
		||||
        dropdownRender={(menu) => {
 | 
			
		||||
          const isFullMatch = options.some((v) => v[fieldNames.label] === searchData.current);
 | 
			
		||||
          return (
 | 
			
		||||
            <>
 | 
			
		||||
              {isQuickAdd ? (
 | 
			
		||||
                <>
 | 
			
		||||
                  {!(data?.data.length === 0 && searchData?.current) && menu}
 | 
			
		||||
                  {data?.data.length > 0 && searchData?.current && !isFullMatch && <Divider style={{ margin: 0 }} />}
 | 
			
		||||
                  {!isFullMatch && <CustomRenderCom />}
 | 
			
		||||
                </>
 | 
			
		||||
              ) : (
 | 
			
		||||
                menu
 | 
			
		||||
              )}
 | 
			
		||||
            </>
 | 
			
		||||
          );
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user