mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 06:46:38 +00:00
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