feat: support use select field (#1105)
* feat: add RemoteSelect * feat: match * feat: support use select field * fix: revert no need code * fix: remove console * feat: reuse select * feat: support multiple value and labelInValue * fix: incorrect import * fix: missing title in RemoteSelect * feat: add association-select * fix: edit * feat: support sort * feat: support filter * fix: break * fix: loss of filter data after closing the drawer * fix: multiple not support in select * fix: some bugs in multiple * feat: support select in o2m,o2o * feat: support more association field * feat: improve * fix: remove unused files * feat: make o2m also support pattern * fix: missing value on edit
This commit is contained in:
parent
4a113774bd
commit
0b6fed79d3
@ -10,7 +10,7 @@ import { assign } from './assign';
|
||||
|
||||
type FunctionService = (...args: any[]) => Promise<any>;
|
||||
|
||||
type ResourceActionOptions<P = any> = {
|
||||
export type ResourceActionOptions<P = any> = {
|
||||
resource?: string;
|
||||
resourceOf?: any;
|
||||
action?: string;
|
||||
|
@ -47,10 +47,19 @@ export const linkTo: IField = {
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { readPretty, block }) {
|
||||
if (block === 'Form') {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
if (schema['x-component'] === 'AssociationSelect') {
|
||||
Object.assign(schema, {
|
||||
type: 'string',
|
||||
'x-designer': 'AssociationSelect.Designer',
|
||||
});
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
return schema
|
||||
} else {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { defaultProps, recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
|
||||
import {
|
||||
defaultProps,
|
||||
recordPickerSelector,
|
||||
recordPickerViewer,
|
||||
relationshipType,
|
||||
reverseFieldProperties,
|
||||
} from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const m2m: IField = {
|
||||
@ -47,20 +53,28 @@ export const m2m: IField = {
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { readPretty, block }) {
|
||||
if (block === 'Form') {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
} else {
|
||||
if (readPretty) {
|
||||
if (schema['x-component'] === 'AssociationSelect') {
|
||||
Object.assign(schema, {
|
||||
type: 'string',
|
||||
'x-designer': 'AssociationSelect.Designer',
|
||||
});
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
}
|
||||
};
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { constraintsProps, recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
|
||||
import {
|
||||
constraintsProps,
|
||||
recordPickerSelector,
|
||||
recordPickerViewer,
|
||||
relationshipType,
|
||||
reverseFieldProperties,
|
||||
} from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const m2o: IField = {
|
||||
@ -44,63 +50,30 @@ export const m2o: IField = {
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
|
||||
if (block === 'Form' && schema['x-component'] === 'FormField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
schema.type = 'void';
|
||||
schema.properties = {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormFieldProvider',
|
||||
'x-decorator-props': {
|
||||
collection: field.target,
|
||||
association: association,
|
||||
resource: association,
|
||||
action,
|
||||
fieldName: field.name,
|
||||
readPretty
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
'x-component-props': {
|
||||
bordered: true,
|
||||
},
|
||||
properties: {
|
||||
[field.name]: {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormFieldProps }}',
|
||||
},
|
||||
properties: {
|
||||
__form_grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'FormItemInitializers',
|
||||
properties: {},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
if (block === 'Form') {
|
||||
schemaInitialize(schema: ISchema, { block, readPretty }) {
|
||||
if (block === 'Form') {
|
||||
if (schema['x-component'] === 'AssociationSelect') {
|
||||
Object.assign(schema, {
|
||||
type: 'string',
|
||||
'x-designer': 'AssociationSelect.Designer',
|
||||
});
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
} else {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
}
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { constraintsProps, recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
|
||||
import {
|
||||
constraintsProps,
|
||||
recordPickerSelector,
|
||||
recordPickerViewer,
|
||||
relationshipType,
|
||||
reverseFieldProperties,
|
||||
} from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
export const o2m: IField = {
|
||||
@ -45,76 +51,72 @@ export const o2m: IField = {
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty }) {
|
||||
if (block === 'Form' && schema['x-component'] === 'TableField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
schema['type'] = 'void';
|
||||
schema['properties'] = {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableFieldProvider',
|
||||
'x-decorator-props': {
|
||||
collection: field.target,
|
||||
association: association,
|
||||
resource: association,
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
if (block === 'Form') {
|
||||
if (schema['x-component'] === 'TableField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
schema['type'] = 'void';
|
||||
schema['properties'] = {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableFieldProvider',
|
||||
'x-decorator-props': {
|
||||
collection: field.target,
|
||||
association: association,
|
||||
resource: association,
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
},
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
fieldName: field.name,
|
||||
},
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
fieldName: field.name,
|
||||
},
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-initializer': 'SubTableActionInitializers',
|
||||
'x-component': 'TableField.ActionBar',
|
||||
'x-component-props': {},
|
||||
},
|
||||
[field.name]: {
|
||||
type: 'array',
|
||||
'x-initializer': 'TableColumnInitializers',
|
||||
'x-component': 'TableV2',
|
||||
'x-component-props': {
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-initializer': 'SubTableActionInitializers',
|
||||
'x-component': 'TableField.ActionBar',
|
||||
'x-component-props': {},
|
||||
},
|
||||
[field.name]: {
|
||||
type: 'array',
|
||||
'x-initializer': 'TableColumnInitializers',
|
||||
'x-component': 'TableV2',
|
||||
'x-component-props': {
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
useProps: '{{ useTableFieldProps }}',
|
||||
},
|
||||
useProps: '{{ useTableFieldProps }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// schema['x-component'] = 'CollectionField';
|
||||
// schema.type = 'array';
|
||||
|
||||
if (block === 'Form') {
|
||||
};
|
||||
} else if (schema['x-component'] === 'AssociationSelect') {
|
||||
Object.assign(schema, {
|
||||
type: 'string',
|
||||
'x-designer': 'AssociationSelect.Designer',
|
||||
});
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
} else {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
// if (readPretty) {
|
||||
// schema['properties'] = {
|
||||
// viewer: cloneDeep(recordPickerViewer),
|
||||
// };
|
||||
// } else {
|
||||
// schema['properties'] = {
|
||||
// selector: cloneDeep(recordPickerSelector),
|
||||
// };
|
||||
// }
|
||||
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
schema['x-component-props']['ellipsis'] = true;
|
||||
|
@ -1,65 +1,77 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { constraintsProps, recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
|
||||
import {
|
||||
constraintsProps,
|
||||
recordPickerSelector,
|
||||
recordPickerViewer,
|
||||
relationshipType,
|
||||
reverseFieldProperties,
|
||||
} from './properties';
|
||||
import { IField } from './types';
|
||||
|
||||
const internalSchameInitialize = (schema: ISchema, { field, block, readPretty, action }) => {
|
||||
if (block === 'Form' && schema['x-component'] === 'FormField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
schema.type = 'void';
|
||||
schema.properties = {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormFieldProvider',
|
||||
'x-decorator-props': {
|
||||
collection: field.target,
|
||||
association: association,
|
||||
resource: association,
|
||||
action: action,
|
||||
fieldName: field.name,
|
||||
readPretty,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
'x-component-props': {
|
||||
bordered: true,
|
||||
},
|
||||
properties: {
|
||||
[field.name]: {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormFieldProps }}',
|
||||
},
|
||||
properties: {
|
||||
__form_grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'FormItemInitializers',
|
||||
properties: {},
|
||||
if (block === 'Form') {
|
||||
if (schema['x-component'] === 'FormField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
schema.type = 'void';
|
||||
schema.properties = {
|
||||
block: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormFieldProvider',
|
||||
'x-decorator-props': {
|
||||
collection: field.target,
|
||||
association: association,
|
||||
resource: association,
|
||||
action: action,
|
||||
fieldName: field.name,
|
||||
readPretty,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
'x-component-props': {
|
||||
bordered: true,
|
||||
},
|
||||
properties: {
|
||||
[field.name]: {
|
||||
type: 'object',
|
||||
'x-component': 'FormV2',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormFieldProps }}',
|
||||
},
|
||||
properties: {
|
||||
__form_grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'FormItemInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
if (block === 'Form') {
|
||||
};
|
||||
} else if (schema['x-component'] === 'AssociationSelect') {
|
||||
Object.assign(schema, {
|
||||
type: 'string',
|
||||
'x-designer': 'AssociationSelect.Designer',
|
||||
});
|
||||
} else {
|
||||
schema.type = 'string';
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
} else {
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
schema.type = 'string';
|
||||
if (readPretty) {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,692 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { ArrayCollapse, ArrayItems, FormLayout } from '@formily/antd';
|
||||
import { Field } from '@formily/core';
|
||||
import { connect, ISchema, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFormBlockContext, useFilterByTk } from '../../../block-provider';
|
||||
import {
|
||||
useCollectionManager,
|
||||
useCollection,
|
||||
useSortFields,
|
||||
useCollectionFilterOptions,
|
||||
} from '../../../collection-manager';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
import { useDesignable, useCompile, useFieldComponentOptions } from '../../hooks';
|
||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||
import { defaultFieldNames } from '../select';
|
||||
import { ReadPretty } from './ReadPretty';
|
||||
import useServiceOptions from './useServiceOptions';
|
||||
|
||||
export type AssociationSelectProps<P = any> = RemoteSelectProps<P> & {
|
||||
action?: string;
|
||||
multiple?: boolean;
|
||||
};
|
||||
|
||||
const divWrap = (schema: ISchema) => {
|
||||
return {
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
properties: {
|
||||
[schema.name || uid()]: schema,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const InternalAssociationSelect = connect(
|
||||
(props: AssociationSelectProps) => {
|
||||
const { fieldNames, objectValue = true } = props;
|
||||
const service = useServiceOptions(props);
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema.name);
|
||||
|
||||
const normalizeValues = useCallback(
|
||||
(obj) => {
|
||||
if (!objectValue && typeof obj === 'object') {
|
||||
return obj[fieldNames.value];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
[objectValue, fieldNames.value],
|
||||
);
|
||||
|
||||
const value = useMemo(() => {
|
||||
if (props.value === undefined || props.value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(props.value)) {
|
||||
return props.value.map(normalizeValues);
|
||||
} else {
|
||||
return normalizeValues(props.value);
|
||||
}
|
||||
}, [props.value, normalizeValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!field.title) {
|
||||
field.title = collectionField.uiSchema.title;
|
||||
}
|
||||
}, collectionField.title);
|
||||
|
||||
return <RemoteSelect {...props} objectValue={objectValue} value={value} service={service}></RemoteSelect>;
|
||||
},
|
||||
mapProps(
|
||||
{
|
||||
dataSource: 'options',
|
||||
loading: true,
|
||||
},
|
||||
(props, field) => {
|
||||
return {
|
||||
...props,
|
||||
fieldNames: { ...defaultFieldNames, ...props.fieldNames, ...field.componentProps.fieldNames },
|
||||
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
|
||||
};
|
||||
},
|
||||
),
|
||||
mapReadPretty(ReadPretty),
|
||||
);
|
||||
|
||||
interface RemoteSelectInterface {
|
||||
(props: any): React.ReactElement;
|
||||
Designer: React.FC;
|
||||
}
|
||||
|
||||
export const AssociationSelect = InternalAssociationSelect as unknown as RemoteSelectInterface;
|
||||
|
||||
AssociationSelect.Designer = () => {
|
||||
const { getCollectionFields, getInterface, getCollectionJoinField } = useCollectionManager();
|
||||
const { getField } = useCollection();
|
||||
const { form } = useFormBlockContext();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { t } = useTranslation();
|
||||
const tk = useFilterByTk();
|
||||
const {} = useCollection();
|
||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||
const compile = useCompile();
|
||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||
const fieldComponentOptions = useFieldComponentOptions();
|
||||
const isSubFormAssocitionField = field.address.segments.includes('__form_grid');
|
||||
const interfaceConfig = getInterface(collectionField?.interface);
|
||||
const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema);
|
||||
const originalTitle = collectionField?.uiSchema?.title;
|
||||
const targetFields = collectionField?.target ? getCollectionFields(collectionField.target) : [];
|
||||
const initialValue = {
|
||||
title: field.title === originalTitle ? undefined : field.title,
|
||||
};
|
||||
const sortFields = useSortFields(collectionField.target);
|
||||
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
||||
const defaultFilter = field.componentProps?.service?.params?.filter || {};
|
||||
const dataSource = useCollectionFilterOptions(collectionField.target);
|
||||
console.log(collectionField?.target, ['CollectionField', 'AssociationSelect'].includes(fieldSchema['x-component']));
|
||||
|
||||
const sort = defaultSort?.map((item: string) => {
|
||||
return item.startsWith('-')
|
||||
? {
|
||||
field: item.substring(1),
|
||||
direction: 'desc',
|
||||
}
|
||||
: {
|
||||
field: item,
|
||||
direction: 'asc',
|
||||
};
|
||||
});
|
||||
if (!field.readPretty) {
|
||||
initialValue['required'] = field.required;
|
||||
}
|
||||
|
||||
const options = targetFields
|
||||
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||
.map((field) => ({
|
||||
value: field?.name,
|
||||
label: compile(field?.uiSchema?.title) || field?.name,
|
||||
}));
|
||||
|
||||
let readOnlyMode = 'editable';
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
readOnlyMode = 'readonly';
|
||||
}
|
||||
if (fieldSchema['x-read-pretty'] === true) {
|
||||
readOnlyMode = 'read-pretty';
|
||||
}
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner>
|
||||
{collectionField && (
|
||||
<SchemaSettings.ModalItem
|
||||
key="edit-field-title"
|
||||
title={t('Edit field title')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Edit field title'),
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Field title'),
|
||||
default: field?.title,
|
||||
description: `${t('Original field title: ')}${collectionField?.uiSchema?.title}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ title }) => {
|
||||
if (title) {
|
||||
field.title = title;
|
||||
fieldSchema.title = title;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
title: fieldSchema.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!field.readPretty && (
|
||||
<SchemaSettings.ModalItem
|
||||
key="edit-description"
|
||||
title={t('Edit description')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Edit description'),
|
||||
properties: {
|
||||
description: {
|
||||
// title: t('Description'),
|
||||
default: field?.description,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ description }) => {
|
||||
field.description = description;
|
||||
fieldSchema.description = description;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
description: fieldSchema.description,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{field.readPretty && (
|
||||
<SchemaSettings.ModalItem
|
||||
key="edit-tooltip"
|
||||
title={t('Edit tooltip')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Edit description'),
|
||||
properties: {
|
||||
tooltip: {
|
||||
default: fieldSchema?.['x-decorator-props']?.tooltip,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ tooltip }) => {
|
||||
field.decoratorProps.tooltip = tooltip;
|
||||
fieldSchema['x-decorator-props'] = fieldSchema['x-decorator-props'] || {};
|
||||
fieldSchema['x-decorator-props']['tooltip'] = tooltip;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!field.readPretty && (
|
||||
<SchemaSettings.SwitchItem
|
||||
key="required"
|
||||
title={t('Required')}
|
||||
checked={fieldSchema.required as boolean}
|
||||
onChange={(required) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
field.required = required;
|
||||
fieldSchema['required'] = required;
|
||||
schema['required'] = required;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form && !form?.readPretty && validateSchema && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set validation rules')}
|
||||
components={{ ArrayCollapse, FormLayout }}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set validation rules'),
|
||||
properties: {
|
||||
rules: {
|
||||
type: 'array',
|
||||
default: fieldSchema?.['x-validator'],
|
||||
'x-component': 'ArrayCollapse',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component-props': {
|
||||
accordion: true,
|
||||
},
|
||||
maxItems: 3,
|
||||
items: {
|
||||
type: 'object',
|
||||
'x-component': 'ArrayCollapse.CollapsePanel',
|
||||
'x-component-props': {
|
||||
header: '{{ t("Validation rule") }}',
|
||||
},
|
||||
properties: {
|
||||
index: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayCollapse.Index',
|
||||
},
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelStyle: {
|
||||
marginTop: '6px',
|
||||
},
|
||||
labelCol: 8,
|
||||
wrapperCol: 16,
|
||||
},
|
||||
properties: {
|
||||
...validateSchema,
|
||||
message: {
|
||||
type: 'string',
|
||||
title: '{{ t("Error message") }}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 2,
|
||||
maxRows: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayCollapse.Remove',
|
||||
},
|
||||
moveUp: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayCollapse.MoveUp',
|
||||
},
|
||||
moveDown: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayCollapse.MoveDown',
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{ t("Add validation rule") }}',
|
||||
'x-component': 'ArrayCollapse.Addition',
|
||||
'x-reactions': {
|
||||
dependencies: ['rules'],
|
||||
fulfill: {
|
||||
state: {
|
||||
disabled: '{{$deps[0].length >= 3}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={(v) => {
|
||||
const rules = [];
|
||||
for (const rule of v.rules) {
|
||||
rules.push(_.pickBy(rule, _.identity));
|
||||
}
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
// return;
|
||||
// if (['number'].includes(collectionField?.interface) && collectionField?.uiSchema?.['x-component-props']?.['stringMode'] === true) {
|
||||
// rules['numberStringMode'] = true;
|
||||
// }
|
||||
if (['percent'].includes(collectionField?.interface)) {
|
||||
for (const rule of rules) {
|
||||
if (!!rule.maxValue || !!rule.minValue) {
|
||||
rule['percentMode'] = true;
|
||||
}
|
||||
|
||||
if (rule.percentFormat) {
|
||||
rule['percentFormats'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const concatValidator = _.concat([], collectionField?.uiSchema?.['x-validator'] || [], rules);
|
||||
field.validator = concatValidator;
|
||||
fieldSchema['x-validator'] = rules;
|
||||
schema['x-validator'] = rules;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form && !form?.readPretty && collectionField?.uiSchema?.type && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set default value')}
|
||||
components={{ ArrayCollapse, FormLayout }}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set default value'),
|
||||
properties: {
|
||||
default: {
|
||||
...collectionField.uiSchema,
|
||||
name: 'default',
|
||||
title: t('Default value'),
|
||||
'x-decorator': 'FormItem',
|
||||
default: fieldSchema.default || collectionField.defaultValue,
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = v.default;
|
||||
}
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form && !isSubFormAssocitionField && fieldComponentOptions && (
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Field component')}
|
||||
options={fieldComponentOptions}
|
||||
value={fieldSchema['x-component']}
|
||||
onChange={(type) => {
|
||||
const schema: ISchema = {
|
||||
name: collectionField.name,
|
||||
type: 'void',
|
||||
required: fieldSchema['required'],
|
||||
description: fieldSchema['description'],
|
||||
default: fieldSchema['default'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': type,
|
||||
'x-validator': fieldSchema['x-validator'],
|
||||
'x-collection-field': fieldSchema['x-collection-field'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
'x-component-props': {
|
||||
...collectionField?.uiSchema?.['x-component-props'],
|
||||
...fieldSchema['x-component-props'],
|
||||
},
|
||||
};
|
||||
|
||||
interfaceConfig?.schemaInitialize?.(schema, {
|
||||
field: collectionField,
|
||||
block: 'Form',
|
||||
readPretty: field.readPretty,
|
||||
action: tk ? 'get' : null,
|
||||
});
|
||||
|
||||
if (type === 'CollectionField') {
|
||||
schema['type'] = 'string';
|
||||
}
|
||||
|
||||
insertAdjacent('beforeBegin', divWrap(schema), {
|
||||
onSuccess: () => {
|
||||
dn.remove(null, {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakRemoveOn: {
|
||||
'x-component': 'Grid',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set the data scope')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set the data scope'),
|
||||
properties: {
|
||||
filter: {
|
||||
default: defaultFilter,
|
||||
// title: '数据范围',
|
||||
enum: dataSource,
|
||||
'x-component': 'Filter',
|
||||
'x-component-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ filter }) => {
|
||||
_.set(field.componentProps, 'service.params.filter', filter);
|
||||
fieldSchema['x-component-props'] = field.componentProps;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-component-props': field.componentProps,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set default sorting rules')}
|
||||
components={{ ArrayItems }}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set default sorting rules'),
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'array',
|
||||
default: sort,
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
space: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
field: {
|
||||
type: 'string',
|
||||
enum: sortFields,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: 260,
|
||||
},
|
||||
},
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {
|
||||
optionType: 'button',
|
||||
},
|
||||
enum: [
|
||||
{
|
||||
label: t('ASC'),
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: t('DESC'),
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: t('Add sort field'),
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ sort }) => {
|
||||
const sortArr = sort.map((item) => {
|
||||
return item.direction === 'desc' ? `-${item.field}` : item.field;
|
||||
});
|
||||
|
||||
_.set(field.componentProps, 'service.params.sort', sortArr);
|
||||
fieldSchema['x-component-props'] = field.componentProps;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-component-props': field.componentProps,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{form && !form?.readPretty && fieldSchema?.['x-component-props']?.['pattern-disable'] != true && (
|
||||
<SchemaSettings.SelectItem
|
||||
key="pattern"
|
||||
title={t('Pattern')}
|
||||
options={[
|
||||
{ label: t('Editable'), value: 'editable' },
|
||||
{ label: t('Readonly'), value: 'readonly' },
|
||||
{ label: t('Easy-reading'), value: 'read-pretty' },
|
||||
]}
|
||||
value={readOnlyMode}
|
||||
onChange={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
|
||||
switch (v) {
|
||||
case 'readonly': {
|
||||
fieldSchema['x-read-pretty'] = false;
|
||||
fieldSchema['x-disabled'] = true;
|
||||
schema['x-read-pretty'] = false;
|
||||
schema['x-disabled'] = true;
|
||||
field.readPretty = false;
|
||||
field.disabled = true;
|
||||
break;
|
||||
}
|
||||
case 'read-pretty': {
|
||||
fieldSchema['x-read-pretty'] = true;
|
||||
fieldSchema['x-disabled'] = false;
|
||||
schema['x-read-pretty'] = true;
|
||||
schema['x-disabled'] = false;
|
||||
field.readPretty = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
fieldSchema['x-read-pretty'] = false;
|
||||
fieldSchema['x-disabled'] = false;
|
||||
schema['x-read-pretty'] = false;
|
||||
schema['x-disabled'] = false;
|
||||
field.readPretty = false;
|
||||
field.disabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collectionField?.target && ['CollectionField', 'AssociationSelect'].includes(fieldSchema['x-component']) && (
|
||||
<SchemaSettings.SelectItem
|
||||
key="title-field"
|
||||
title={t('Title field')}
|
||||
options={options}
|
||||
value={field?.componentProps?.fieldNames?.label}
|
||||
onChange={(label) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
const fieldNames = {
|
||||
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||
...field.componentProps.fieldNames,
|
||||
label,
|
||||
};
|
||||
field.componentProps.fieldNames = fieldNames;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collectionField && <SchemaSettings.Divider />}
|
||||
<SchemaSettings.Remove
|
||||
key="remove"
|
||||
removeParentsIfNoChildren
|
||||
confirm={{
|
||||
title: t('Delete field'),
|
||||
}}
|
||||
breakRemoveOn={{
|
||||
'x-component': 'Grid',
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssociationSelect;
|
@ -0,0 +1,10 @@
|
||||
import { observer } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { RemoteSelect } from '../remote-select';
|
||||
import useServiceOptions from './useServiceOptions';
|
||||
|
||||
export const ReadPretty = observer((props: any) => {
|
||||
const service = useServiceOptions(props);
|
||||
|
||||
return <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>;
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
---
|
||||
nav:
|
||||
path: /client
|
||||
group:
|
||||
path: /schema-components
|
||||
---
|
||||
|
||||
# RemoteSelect
|
||||
|
||||
## Examples
|
||||
|
||||
## API
|
||||
|
||||
基于 Ant Design 的 [Select](https://ant.design/components/select/#API),相关扩展属性有:
|
||||
|
||||
- `objectValue` 值为 object 类型
|
||||
- `fieldNames` 默认值有区别
|
||||
|
||||
```ts
|
||||
export const defaultFieldNames = {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
color: 'color',
|
||||
options: 'children',
|
||||
};
|
||||
```
|
@ -0,0 +1 @@
|
||||
export * from './AssociationSelect';
|
@ -0,0 +1,20 @@
|
||||
import { useFieldSchema } from "@formily/react";
|
||||
import { useMemo } from "react";
|
||||
import { useCollection } from "../../../collection-manager";
|
||||
|
||||
export default function useServiceOptions(props) {
|
||||
const { action = 'list', service } = props
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = useMemo(() => {
|
||||
return getField(fieldSchema.name);
|
||||
}, [fieldSchema.name]);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
resource: collectionField.target,
|
||||
action,
|
||||
...service,
|
||||
};
|
||||
}, [collectionField.target, action, service]);
|
||||
}
|
@ -6,7 +6,7 @@ import { uid } from '@formily/shared';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCompile, useDesignable } from '../..';
|
||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
@ -41,7 +41,9 @@ export const FormItem: any = (props) => {
|
||||
__html: HTMLEncode(field.description).split('\n').join('<br/>'),
|
||||
}}
|
||||
/>
|
||||
) : field.description
|
||||
) : (
|
||||
field.description
|
||||
)
|
||||
}
|
||||
/>
|
||||
</BlockItem>
|
||||
@ -56,13 +58,14 @@ FormItem.Designer = (props) => {
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { t } = useTranslation();
|
||||
const { dn, refresh, insertAdjacent, insertBeforeBegin } = useDesignable();
|
||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||
const compile = useCompile();
|
||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||
const interfaceConfig = getInterface(collectionField?.interface);
|
||||
const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema);
|
||||
const originalTitle = collectionField?.uiSchema?.title;
|
||||
const targetFields = collectionField?.target ? getCollectionFields(collectionField.target) : [];
|
||||
const fieldComponentOptions = useFieldComponentOptions();
|
||||
const isSubFormAssocitionField = field.address.segments.includes('__form_grid');
|
||||
const initialValue = {
|
||||
title: field.title === originalTitle ? undefined : field.title,
|
||||
@ -328,62 +331,61 @@ FormItem.Designer = (props) => {
|
||||
)}
|
||||
{form && !form?.readPretty && collectionField?.uiSchema?.type && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set default value')}
|
||||
components={{ ArrayCollapse, FormLayout }}
|
||||
schema={{
|
||||
type: 'object',
|
||||
title: t('Set default value'),
|
||||
properties: {
|
||||
default: {
|
||||
...collectionField.uiSchema,
|
||||
name: 'default',
|
||||
title: t('Default value'),
|
||||
'x-decorator': 'FormItem',
|
||||
default: fieldSchema.default || collectionField.defaultValue,
|
||||
title={t('Set default value')}
|
||||
components={{ ArrayCollapse, FormLayout }}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set default value'),
|
||||
properties: {
|
||||
default: {
|
||||
...collectionField.uiSchema,
|
||||
name: 'default',
|
||||
title: t('Default value'),
|
||||
'x-decorator': 'FormItem',
|
||||
default: fieldSchema.default || collectionField.defaultValue,
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = v.default;
|
||||
}
|
||||
}
|
||||
} as ISchema}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = v.default;
|
||||
}
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form && !isSubFormAssocitionField && ['o2o', 'oho', 'obo', 'o2m'].includes(collectionField?.interface) && (
|
||||
{form && !isSubFormAssocitionField && fieldComponentOptions && (
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Field component')}
|
||||
options={
|
||||
collectionField?.interface === 'o2m'
|
||||
? [
|
||||
{ label: t('Record picker'), value: 'CollectionField' },
|
||||
{ label: t('Subtable'), value: 'TableField' },
|
||||
]
|
||||
: [
|
||||
{ label: t('Record picker'), value: 'CollectionField' },
|
||||
{ label: t('Subform'), value: 'FormField' },
|
||||
]
|
||||
}
|
||||
options={fieldComponentOptions}
|
||||
value={fieldSchema['x-component']}
|
||||
onChange={(v) => {
|
||||
onChange={(type) => {
|
||||
const schema: ISchema = {
|
||||
name: collectionField.name,
|
||||
type: 'void',
|
||||
// title: compile(collectionField.uiSchema?.title),
|
||||
required: fieldSchema['required'],
|
||||
description: fieldSchema['description'],
|
||||
default: fieldSchema['default'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': v,
|
||||
'x-component-props': {},
|
||||
'x-component': type,
|
||||
'x-validator': fieldSchema['x-validator'],
|
||||
'x-collection-field': fieldSchema['x-collection-field'],
|
||||
'x-decorator-props': fieldSchema['x-decorator-props'],
|
||||
'x-component-props': {
|
||||
...collectionField?.uiSchema?.['x-component-props'],
|
||||
...fieldSchema['x-component-props'],
|
||||
},
|
||||
};
|
||||
|
||||
interfaceConfig?.schemaInitialize?.(schema, {
|
||||
@ -393,10 +395,6 @@ FormItem.Designer = (props) => {
|
||||
action: tk ? 'get' : null,
|
||||
});
|
||||
|
||||
if (v === 'CollectionField') {
|
||||
schema['type'] = 'string';
|
||||
}
|
||||
|
||||
insertAdjacent('beforeBegin', divWrap(schema), {
|
||||
onSuccess: () => {
|
||||
dn.remove(null, {
|
||||
|
@ -28,6 +28,8 @@ export * from './radio';
|
||||
export * from './record-picker';
|
||||
export * from './rich-text';
|
||||
export * from './select';
|
||||
export * from './remote-select';
|
||||
export * from './association-select';
|
||||
export * from './space';
|
||||
export * from './table';
|
||||
export * from './table-v2';
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { observer, useField } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { getValues } from './shared';
|
||||
import { Select, defaultFieldNames } from '../select';
|
||||
import { useRequest } from '../../../api-client';
|
||||
|
||||
export const ReadPretty = observer((props: any) => {
|
||||
const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
|
||||
const field = useField<any>();
|
||||
|
||||
const { data } = useRequest(
|
||||
{
|
||||
action: 'list',
|
||||
...props.service,
|
||||
params: {
|
||||
paginate: false,
|
||||
filter: {
|
||||
[fieldNames.value]: {
|
||||
$in: getValues(field.value, fieldNames),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
refreshDeps: [props.service, field.value],
|
||||
},
|
||||
);
|
||||
|
||||
return <Select.ReadPretty {...props} options={data?.data}></Select.ReadPretty>;
|
||||
});
|
@ -0,0 +1,89 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { SelectProps } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { ResourceActionOptions, useRequest } from '../../../api-client';
|
||||
import { defaultFieldNames, Select } from '../select';
|
||||
import { ReadPretty } from './ReadPretty';
|
||||
|
||||
export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
|
||||
objectValue?: boolean;
|
||||
onChange?: (v: any) => void;
|
||||
target: string;
|
||||
wait?: number;
|
||||
service: ResourceActionOptions<P>;
|
||||
};
|
||||
|
||||
const InternalRemoteSelect = connect(
|
||||
(props: RemoteSelectProps) => {
|
||||
const { fieldNames, service = {}, wait = 300, ...others } = props;
|
||||
|
||||
const { data, run } = useRequest(
|
||||
{
|
||||
action: 'list',
|
||||
...service,
|
||||
params: {
|
||||
pageSize: 30,
|
||||
...service?.params,
|
||||
// search needs
|
||||
filter: {
|
||||
$and: [service?.params?.filter].filter(Boolean),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
debounceWait: wait,
|
||||
refreshDeps: [service],
|
||||
},
|
||||
);
|
||||
|
||||
const onSearch = async (search) => {
|
||||
run({
|
||||
filter: {
|
||||
$and: [
|
||||
{
|
||||
[fieldNames.label]: {
|
||||
$includes: search,
|
||||
},
|
||||
},
|
||||
service?.params?.filter,
|
||||
].filter(Boolean),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
autoClearSearchValue
|
||||
filterOption={false}
|
||||
filterSort={null}
|
||||
fieldNames={fieldNames}
|
||||
onSearch={onSearch}
|
||||
{...others}
|
||||
options={data?.data || []}
|
||||
/>
|
||||
);
|
||||
},
|
||||
mapProps(
|
||||
{
|
||||
dataSource: 'options',
|
||||
loading: true,
|
||||
},
|
||||
(props, field) => {
|
||||
return {
|
||||
...props,
|
||||
fieldNames: { ...defaultFieldNames, ...props.fieldNames, ...field.componentProps.fieldNames },
|
||||
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
|
||||
};
|
||||
},
|
||||
),
|
||||
mapReadPretty(ReadPretty),
|
||||
);
|
||||
|
||||
export const RemoteSelect = InternalRemoteSelect as unknown as typeof InternalRemoteSelect & {
|
||||
ReadPretty: typeof ReadPretty;
|
||||
};
|
||||
|
||||
RemoteSelect.ReadPretty = ReadPretty;
|
||||
export default RemoteSelect;
|
@ -0,0 +1,26 @@
|
||||
---
|
||||
nav:
|
||||
path: /client
|
||||
group:
|
||||
path: /schema-components
|
||||
---
|
||||
|
||||
# RemoteSelect
|
||||
|
||||
## Examples
|
||||
|
||||
## API
|
||||
|
||||
基于 Ant Design 的 [Select](https://ant.design/components/select/#API),相关扩展属性有:
|
||||
|
||||
- `objectValue` 值为 object 类型
|
||||
- `fieldNames` 默认值有区别
|
||||
|
||||
```ts
|
||||
export const defaultFieldNames = {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
color: 'color',
|
||||
options: 'children',
|
||||
};
|
||||
```
|
@ -0,0 +1 @@
|
||||
export * from './RemoteSelect';
|
@ -0,0 +1,7 @@
|
||||
import { castArray } from 'lodash';
|
||||
|
||||
export const getValues = (values, fieldNames) => {
|
||||
return castArray(values)
|
||||
.filter(item => item != null)
|
||||
.map((val) => (typeof val === 'object' ? val[fieldNames.value] : val));
|
||||
};
|
@ -7,7 +7,7 @@ import React from 'react';
|
||||
import { ReadPretty } from './ReadPretty';
|
||||
import { defaultFieldNames, getCurrentOptions } from './shared';
|
||||
|
||||
type Props = SelectProps<any, any> & { objectValue?: boolean; onChange?: (v: any) => void };
|
||||
type Props = SelectProps<any, any> & { objectValue?: boolean; onChange?: (v: any) => void; multiple: boolean };
|
||||
|
||||
const isEmptyObject = (val: any) => !isValid(val) || (typeof val === 'object' && Object.keys(val).length === 0);
|
||||
|
||||
@ -28,7 +28,7 @@ const ObjectSelect = (props: Props) => {
|
||||
value: val[fieldNames.value],
|
||||
};
|
||||
});
|
||||
if (['tags', 'multiple'].includes(mode)) {
|
||||
if (['tags', 'multiple'].includes(mode) || props.multiple) {
|
||||
return current;
|
||||
}
|
||||
return current.shift();
|
||||
@ -53,7 +53,7 @@ const ObjectSelect = (props: Props) => {
|
||||
options,
|
||||
fieldNames,
|
||||
);
|
||||
if (['tags', 'multiple'].includes(mode)) {
|
||||
if (['tags', 'multiple'].includes(mode) || props.multiple) {
|
||||
onChange(current);
|
||||
} else {
|
||||
onChange(current.shift());
|
||||
@ -67,11 +67,13 @@ const ObjectSelect = (props: Props) => {
|
||||
|
||||
const filterOption = (input, option) => (option?.label ?? '').toLowerCase().includes((input || '').toLowerCase());
|
||||
|
||||
export const Select = connect(
|
||||
const InternalSelect = connect(
|
||||
(props: Props) => {
|
||||
const { objectValue, ...others } = props;
|
||||
const mode = props.mode || props.multiple ? 'multiple' : undefined;
|
||||
|
||||
if (objectValue) {
|
||||
return <ObjectSelect {...others} />;
|
||||
return <ObjectSelect {...others} mode={mode} />;
|
||||
}
|
||||
return (
|
||||
<AntdSelect
|
||||
@ -80,9 +82,9 @@ export const Select = connect(
|
||||
allowClear
|
||||
{...others}
|
||||
onChange={(changed) => {
|
||||
props.onChange(changed === undefined ? null : changed);
|
||||
props.onChange?.(changed === undefined ? null : changed);
|
||||
}}
|
||||
value={others.value || undefined}
|
||||
mode={mode}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@ -102,4 +104,10 @@ export const Select = connect(
|
||||
mapReadPretty(ReadPretty),
|
||||
);
|
||||
|
||||
export const Select = InternalSelect as unknown as typeof InternalSelect & {
|
||||
ReadPretty: typeof ReadPretty;
|
||||
};
|
||||
|
||||
Select.ReadPretty = ReadPretty;
|
||||
|
||||
export default Select;
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './Select';
|
||||
export * from './shared'
|
||||
|
@ -26,6 +26,7 @@ export const getCurrentOptions = (values, dataSource, fieldNames) => {
|
||||
.filter((item) => item != null)
|
||||
.map((val) => (typeof val === 'object' ? val[fieldNames.value] : val));
|
||||
const findOptions = (options: any[]) => {
|
||||
if (!options) return []
|
||||
let current = [];
|
||||
for (const value of values) {
|
||||
const option = options.find((v) => v[fieldNames.value] === value) || { value: value, label: value };
|
||||
|
@ -5,4 +5,5 @@ export * from './useDesignable';
|
||||
export * from './useDesigner';
|
||||
export * from './useFieldProps';
|
||||
export * from './useSchemaComponentContext';
|
||||
export * from './useFieldComponentOptions';
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { useFieldSchema } from "@formily/react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useCollectionManager, useCollection } from "../../collection-manager";
|
||||
|
||||
export const useFieldComponentOptions = () => {
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const fieldComponentOptions = useMemo(() => {
|
||||
if (!collectionField?.interface) return;
|
||||
switch (collectionField.interface) {
|
||||
case 'o2m':
|
||||
return [
|
||||
{ label: t('Record picker'), value: 'CollectionField' },
|
||||
{ label: t('Subtable'), value: 'TableField' },
|
||||
{ label: t('Select'), value: 'AssociationSelect' },
|
||||
];
|
||||
|
||||
case 'm2o':
|
||||
case 'm2m':
|
||||
case 'linkTo':
|
||||
return [
|
||||
{ label: t('Record picker'), value: 'CollectionField' },
|
||||
{ label: t('Select'), value: 'AssociationSelect' },
|
||||
]
|
||||
|
||||
default:
|
||||
return [
|
||||
{ label: t('Record picker'), value: 'CollectionField' },
|
||||
{ label: t('Subform'), value: 'FormField' },
|
||||
{ label: t('Select'), value: 'AssociationSelect' },
|
||||
];
|
||||
|
||||
}
|
||||
}, [t, collectionField?.interface]);
|
||||
|
||||
return fieldComponentOptions;
|
||||
};
|
Loading…
Reference in New Issue
Block a user