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 currentRecord = useRecord();
|
||||||
const currentUserContext = useCurrentUserContext();
|
const currentUserContext = useCurrentUserContext();
|
||||||
const currentUser = currentUserContext?.data?.data;
|
const currentUser = currentUserContext?.data?.data;
|
||||||
|
const action = actionField.componentProps.saveMode || 'create';
|
||||||
|
const filterKeys = actionField.componentProps.filterKeys || [];
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
const fieldNames = fields.map((field) => field.name);
|
const fieldNames = fields.map((field) => field.name);
|
||||||
@ -167,12 +169,13 @@ export const useCreateActionProps = () => {
|
|||||||
actionField.data = field.data || {};
|
actionField.data = field.data || {};
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
try {
|
try {
|
||||||
const data = await resource.create({
|
const data = await resource[action]({
|
||||||
values: {
|
values: {
|
||||||
...values,
|
...values,
|
||||||
...overwriteValues,
|
...overwriteValues,
|
||||||
...assignedValues,
|
...assignedValues,
|
||||||
},
|
},
|
||||||
|
filterKeys:filterKeys,
|
||||||
});
|
});
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
actionField.data.data = data;
|
actionField.data.data = data;
|
||||||
|
@ -260,6 +260,63 @@ export const useLinkageCollectionFilterOptions = (collectionName: string) => {
|
|||||||
const options = getOptions(fields, 1);
|
const options = getOptions(fields, 1);
|
||||||
return options;
|
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) => {
|
export const useFilterDataSource = (options) => {
|
||||||
const { name } = useCollection();
|
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 './CollectionField';
|
||||||
export * from './CollectionFieldProvider';
|
export * from './CollectionFieldProvider';
|
||||||
export * from './CollectionManagerProvider';
|
export * from './CollectionManagerProvider';
|
||||||
|
@ -694,5 +694,14 @@ export default {
|
|||||||
"Duplicate mode":"Duplicate mode",
|
"Duplicate mode":"Duplicate mode",
|
||||||
"Quick duplicate":"Quick duplicate",
|
"Quick duplicate":"Quick duplicate",
|
||||||
"Duplicate and continue":"Duplicate and continue",
|
"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",
|
"Sortable": "Clasificable",
|
||||||
"Enable link": "Activar enlace",
|
"Enable link": "Activar enlace",
|
||||||
"Data template": "Plantilla de datos",
|
"Data template": "Plantilla de datos",
|
||||||
|
"Not found":"No encontrado",
|
||||||
|
"Add":"Añadir"
|
||||||
};
|
};
|
||||||
|
@ -605,5 +605,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":"コピーするフィールドを設定してください",
|
||||||
|
"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',
|
'Display data template selector': 'Exibir seletor de modelo de dados',
|
||||||
'Form data templates': 'Modelos de dados do formulário',
|
'Form data templates': 'Modelos de dados do formulário',
|
||||||
"Data template": "Modelo de dados",
|
"Data template": "Modelo de dados",
|
||||||
|
"Not found":"Não encontrado",
|
||||||
|
"Add":"Adicionar"
|
||||||
};
|
};
|
||||||
|
@ -504,4 +504,6 @@ export default {
|
|||||||
'Display data template selector': "Отображать селектор шаблона данных",
|
'Display data template selector': "Отображать селектор шаблона данных",
|
||||||
'Form data templates': "Шаблоны данных формы",
|
'Form data templates': "Шаблоны данных формы",
|
||||||
"Data template": "Шаблон данных",
|
"Data template": "Шаблон данных",
|
||||||
|
"Not found":"Не найдено",
|
||||||
|
"Add":"Добавить"
|
||||||
}
|
}
|
||||||
|
@ -771,5 +771,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":"请配置要复制的字段",
|
||||||
|
"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 React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDesignable } from '../..';
|
import { useDesignable } from '../..';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager, useCollectionFieldsOptions } from '../../../collection-manager';
|
||||||
import { OpenModeSchemaItems } from '../../../schema-items';
|
import { OpenModeSchemaItems } from '../../../schema-items';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
@ -15,6 +15,7 @@ import { requestSettingsSchema } from './utils';
|
|||||||
const Tree = connect(
|
const Tree = connect(
|
||||||
AntdTree,
|
AntdTree,
|
||||||
mapProps((props, field: any) => {
|
mapProps((props, field: any) => {
|
||||||
|
console.log(props, field);
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
onCheck: (checkedKeys) => {
|
onCheck: (checkedKeys) => {
|
||||||
@ -57,18 +58,21 @@ export const ActionDesigner = (props) => {
|
|||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isAction = useLinkageAction();
|
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 isUpdateModePopupAction = ['customize:bulkUpdate', 'customize:bulkEdit'].includes(fieldSchema['x-action']);
|
||||||
const [initialSchema, setInitialSchema] = useState<ISchema>();
|
const [initialSchema, setInitialSchema] = useState<ISchema>();
|
||||||
const actionType = fieldSchema['x-action'] ?? '';
|
const actionType = fieldSchema['x-action'] ?? '';
|
||||||
const isLinkageAction = linkageAction || isAction;
|
const isLinkageAction = linkageAction || isAction;
|
||||||
const isChildCollectionAction = getChildrenCollections(name).length > 0 && fieldSchema['x-action'] === 'create';
|
const isChildCollectionAction = getChildrenCollections(name).length > 0 && fieldSchema['x-action'] === 'create';
|
||||||
const isLink = fieldSchema['x-component'] === 'Action.Link';
|
const isLink = fieldSchema['x-component'] === 'Action.Link';
|
||||||
const isDelete = fieldSchema?.parent?.['x-component'] === 'CollectionField';
|
const isDelete = fieldSchema?.parent['x-component'] === 'CollectionField';
|
||||||
const isDraggable = fieldSchema?.parent?.['x-component'] !== 'CollectionField';
|
const isDraggable = fieldSchema?.parent['x-component'] !== 'CollectionField';
|
||||||
const isDuplicateAction = fieldSchema['x-action'] === 'duplicate';
|
const isDuplicateAction = fieldSchema['x-action'] === 'duplicate';
|
||||||
const { collectionList, getEnableFieldTree, onLoadData, onCheck } = useCollectionState(name);
|
const { collectionList, getEnableFieldTree, onLoadData, onCheck } = useCollectionState(name);
|
||||||
const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
|
const duplicateValues = cloneDeep(fieldSchema['x-component-props'].duplicateFields || []);
|
||||||
|
const options = useCollectionFieldsOptions(name, 1, ['id']);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const schemaUid = uid();
|
const schemaUid = uid();
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -157,6 +161,80 @@ export const ActionDesigner = (props) => {
|
|||||||
dn.refresh();
|
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} />}
|
{isLinkageAction && <SchemaSettings.LinkageRules collectionName={name} />}
|
||||||
{isDuplicateAction && [
|
{isDuplicateAction && [
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
|
@ -1,25 +1,69 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { RecursionField, connect, mapProps, observer, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, connect, mapProps, observer, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { Input } from 'antd';
|
import { Input, Button, message } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RecordProvider } from '../../../';
|
import { RecordProvider } from '../../../';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
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> & {
|
export type AssociationSelectProps<P = any> = RemoteSelectProps<P> & {
|
||||||
action?: string;
|
action?: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalAssociationSelect = observer(
|
const InternalAssociationSelect = observer((props: AssociationSelectProps) => {
|
||||||
(props: AssociationSelectProps) => {
|
const { objectValue = true } = props;
|
||||||
const { fieldNames, objectValue = true } = props;
|
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { getCollection } = useCollectionManager();
|
||||||
const service = useServiceOptions(props);
|
const service = useServiceOptions(props);
|
||||||
const isAllowAddNew = fieldSchema['x-add-new'];
|
const { options: collectionField } = useAssociationFieldContext();
|
||||||
const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value;
|
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
|
||||||
|
onClick={() => handleCreateAction(props)}
|
||||||
|
style={{ cursor: 'pointer', padding: '5px 12px', color: '#0d0c0c' }}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
<span style={{ paddingLeft: 5 }}>{t('Add') + ` “${props.search}” `}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div key={fieldSchema.name}>
|
<div key={fieldSchema.name}>
|
||||||
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
|
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
|
||||||
@ -29,9 +73,10 @@ const InternalAssociationSelect = observer(
|
|||||||
objectValue={objectValue}
|
objectValue={objectValue}
|
||||||
value={value}
|
value={value}
|
||||||
service={service}
|
service={service}
|
||||||
|
CustomDropdownRender={addMode === 'quickAdd' && QuickAddContent}
|
||||||
></RemoteSelect>
|
></RemoteSelect>
|
||||||
|
|
||||||
{isAllowAddNew && (
|
{(addMode === 'modalAdd' || isAllowAddNew) && (
|
||||||
<RecordProvider record={null}>
|
<RecordProvider record={null}>
|
||||||
<RecursionField
|
<RecursionField
|
||||||
onlyRenderProperties
|
onlyRenderProperties
|
||||||
@ -46,9 +91,7 @@ const InternalAssociationSelect = observer(
|
|||||||
</Input.Group>
|
</Input.Group>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
{ displayName: 'InternalAssociationSelect' },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface AssociationSelectInterface {
|
interface AssociationSelectInterface {
|
||||||
(props: any): React.ReactElement;
|
(props: any): React.ReactElement;
|
||||||
|
@ -617,7 +617,7 @@ FormItem.Designer = function Designer() {
|
|||||||
</div>
|
</div>
|
||||||
</SchemaSettings.Item>
|
</SchemaSettings.Item>
|
||||||
)}
|
)}
|
||||||
{!field.readPretty && isAssociationField && ['Select', 'Picker'].includes(fieldMode) && (
|
{!field.readPretty && isAssociationField && ['Picker'].includes(fieldMode) && (
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
key="allowAddNew"
|
key="allowAddNew"
|
||||||
title={t('Allow add new data')}
|
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() ? (
|
{isAssociationField && IsShowMultipleSwitch() ? (
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
key="multiple"
|
key="multiple"
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react';
|
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 { uniqBy } from 'lodash';
|
||||||
import moment from 'moment';
|
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 { ResourceActionOptions, useRequest } from '../../../api-client';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
@ -21,6 +21,7 @@ export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
|
|||||||
mapOptions?: (data: any) => RemoteSelectProps['fieldNames'];
|
mapOptions?: (data: any) => RemoteSelectProps['fieldNames'];
|
||||||
targetField?: any;
|
targetField?: any;
|
||||||
service: ResourceActionOptions<P>;
|
service: ResourceActionOptions<P>;
|
||||||
|
CustomDropdownRender?: (v: any) => any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalRemoteSelect = connect(
|
const InternalRemoteSelect = connect(
|
||||||
@ -34,12 +35,16 @@ const InternalRemoteSelect = connect(
|
|||||||
manual = true,
|
manual = true,
|
||||||
mapOptions,
|
mapOptions,
|
||||||
targetField: _targetField,
|
targetField: _targetField,
|
||||||
|
CustomDropdownRender,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const firstRun = useRef(false);
|
const firstRun = useRef(false);
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const isQuickAdd = fieldSchema['x-component-props']?.addMode === 'quickAdd';
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
|
const searchData = useRef(null);
|
||||||
const { getCollectionJoinField, getInterface } = useCollectionManager();
|
const { getCollectionJoinField, getInterface } = useCollectionManager();
|
||||||
const collectionField = getField(fieldSchema.name);
|
const collectionField = getField(fieldSchema.name);
|
||||||
const targetField =
|
const targetField =
|
||||||
@ -128,7 +133,6 @@ const InternalRemoteSelect = connect(
|
|||||||
debounceWait: wait,
|
debounceWait: wait,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const runDep = useMemo(
|
const runDep = useMemo(
|
||||||
() =>
|
() =>
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@ -137,6 +141,20 @@ const InternalRemoteSelect = connect(
|
|||||||
}),
|
}),
|
||||||
[service, fieldNames],
|
[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(() => {
|
useEffect(() => {
|
||||||
// Lazy load
|
// Lazy load
|
||||||
@ -158,6 +176,7 @@ const InternalRemoteSelect = connect(
|
|||||||
field.componentProps?.service?.params?.filter || service?.params?.filter,
|
field.componentProps?.service?.params?.filter || service?.params?.filter,
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
searchData.current = search;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
@ -167,8 +186,10 @@ const InternalRemoteSelect = connect(
|
|||||||
const valueOptions = (value != null && (Array.isArray(value) ? value : [value])) || [];
|
const valueOptions = (value != null && (Array.isArray(value) ? value : [value])) || [];
|
||||||
return uniqBy(data?.data?.concat(valueOptions) || [], fieldNames.value);
|
return uniqBy(data?.data?.concat(valueOptions) || [], fieldNames.value);
|
||||||
}, [data?.data, value]);
|
}, [data?.data, value]);
|
||||||
const onDropdownVisibleChange = () => {
|
const onDropdownVisibleChange = (visible) => {
|
||||||
if (firstRun.current) {
|
setOpen(visible);
|
||||||
|
searchData.current = null;
|
||||||
|
if (firstRun.current && data?.data.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
run();
|
run();
|
||||||
@ -176,6 +197,7 @@ const InternalRemoteSelect = connect(
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
open={open}
|
||||||
dropdownMatchSelectWidth={false}
|
dropdownMatchSelectWidth={false}
|
||||||
autoClearSearchValue
|
autoClearSearchValue
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
@ -189,6 +211,22 @@ const InternalRemoteSelect = connect(
|
|||||||
loading={data! ? loading : true}
|
loading={data! ? loading : true}
|
||||||
options={mapOptionsToTags(options)}
|
options={mapOptionsToTags(options)}
|
||||||
rawOptions={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