feat: support to use variables to set default value (#1726)
* fix: fix the default value being overwriitten * feat: supports association fields to set default value * feat: support to toggle multiple * feat: support to use variables to set default value * refactor: migrate hooks * fix: fix the title disappearing * feat: suport to use current-user variable * fix: fix error * fix: one to one and one to many should not support to set default value * fix: fix error when searching * style: set the width of modal * fix: should not support to set default value for system fields * refactor: fix lint error * style: optimize inputNumber width * refactor: extract to one function * refactor: extract function * fix: should not display default when field is readPretty * style: optimize width of select * fix: avoid error * fix: should not display multiple option when subtable * fix: should not display default when attachment * fix: chinaRegion * chore: optimize code * fix: build error * fix: multiple should be true by default * refactor: optimize code * fix: fix Select multiple mode * refactor: optimize code * fix: width
This commit is contained in:
parent
eac3c6f0c8
commit
b1068f2d51
@ -1,6 +1,7 @@
|
|||||||
import { createForm, onFormValuesChange } from '@formily/core';
|
import { createForm, onFormValuesChange } from '@formily/core';
|
||||||
import { useField } from '@formily/react';
|
import { useField } from '@formily/react';
|
||||||
import { autorun } from '@formily/reactive';
|
import { autorun } from '@formily/reactive';
|
||||||
|
import { forEach } from '@nocobase/utils/client';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||||
import { RecordProvider } from '../record-provider';
|
import { RecordProvider } from '../record-provider';
|
||||||
@ -35,7 +36,14 @@ const InternalFormFieldProvider = (props) => {
|
|||||||
// 当使用数据模板时,会 formBlockCtx.form.values 会被整体赋值,这时候需要同步到 form.values
|
// 当使用数据模板时,会 formBlockCtx.form.values 会被整体赋值,这时候需要同步到 form.values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dispose = autorun(() => {
|
const dispose = autorun(() => {
|
||||||
form.values = formBlockCtx?.form?.values[fieldName] || form.values;
|
const data = formBlockCtx?.form?.values[fieldName] || {};
|
||||||
|
// 先清空表单值,再赋值,避免当值为空时,表单未被清空
|
||||||
|
form.reset();
|
||||||
|
forEach(data, (value, key) => {
|
||||||
|
if (value) {
|
||||||
|
form.values[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return dispose;
|
return dispose;
|
||||||
|
@ -7,12 +7,18 @@ import { useActionContext, useCompile, useComponent, useFormBlockContext, useRec
|
|||||||
import { CollectionFieldProvider } from './CollectionFieldProvider';
|
import { CollectionFieldProvider } from './CollectionFieldProvider';
|
||||||
import { useCollectionField } from './hooks';
|
import { useCollectionField } from './hooks';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
component: any;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: 初步适配
|
// TODO: 初步适配
|
||||||
const InternalField: React.FC = (props) => {
|
const InternalField: React.FC = (props: Props) => {
|
||||||
|
const { component } = props;
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { uiSchema, defaultValue } = useCollectionField();
|
const { uiSchema, defaultValue } = useCollectionField();
|
||||||
const component = useComponent(uiSchema?.['x-component'] || 'Input');
|
const Component = useComponent(component || uiSchema?.['x-component'] || 'Input');
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const setFieldProps = (key, value) => {
|
const setFieldProps = (key, value) => {
|
||||||
field[key] = typeof field[key] === 'undefined' ? value : field[key];
|
field[key] = typeof field[key] === 'undefined' ? value : field[key];
|
||||||
@ -57,12 +63,12 @@ const InternalField: React.FC = (props) => {
|
|||||||
field.dataSource = uiSchema.enum;
|
field.dataSource = uiSchema.enum;
|
||||||
const originalProps = compile(uiSchema['x-component-props']) || {};
|
const originalProps = compile(uiSchema['x-component-props']) || {};
|
||||||
const componentProps = merge(originalProps, field.componentProps || {});
|
const componentProps = merge(originalProps, field.componentProps || {});
|
||||||
field.component = [component, componentProps];
|
field.component = [Component, componentProps];
|
||||||
}, [JSON.stringify(uiSchema)]);
|
}, [JSON.stringify(uiSchema)]);
|
||||||
if (!uiSchema) {
|
if (!uiSchema) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return React.createElement(component, props, props.children);
|
return React.createElement(Component, props, props.children);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InternalFallbackField = () => {
|
export const InternalFallbackField = () => {
|
||||||
|
@ -13,8 +13,8 @@ import { useResourceActionContext, useResourceContext } from '../ResourceActionP
|
|||||||
import { useCancelAction } from '../action-hooks';
|
import { useCancelAction } from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks';
|
import { useCollectionManager } from '../hooks';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { templateOptions } from './templates';
|
|
||||||
import { TemplateSummay } from './components/TemplateSummay';
|
import { TemplateSummay } from './components/TemplateSummay';
|
||||||
|
import { templateOptions } from './templates';
|
||||||
|
|
||||||
const getSchema = (schema, category, compile): ISchema => {
|
const getSchema = (schema, category, compile): ISchema => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -32,9 +32,14 @@ export const multipleSelect: IField = {
|
|||||||
operators: operators.array,
|
operators: operators.array,
|
||||||
},
|
},
|
||||||
schemaInitialize(schema: ISchema, { block }) {
|
schemaInitialize(schema: ISchema, { block }) {
|
||||||
|
const props = (schema['x-component-props'] = schema['x-component-props'] || {});
|
||||||
|
props.style = {
|
||||||
|
...(props.style || {}),
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
if (['Table', 'Kanban'].includes(block)) {
|
if (['Table', 'Kanban'].includes(block)) {
|
||||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
props['ellipsis'] = true;
|
||||||
schema['x-component-props']['ellipsis'] = true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { registerValidateRules } from '@formily/core';
|
import { registerValidateRules } from '@formily/core';
|
||||||
|
import { ISchema } from '@formily/react';
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
import { defaultProps, operators, unique } from './properties';
|
import { defaultProps, operators, unique } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
@ -67,6 +68,13 @@ export const percent: IField = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
|
||||||
|
const props = (schema['x-component-props'] = schema['x-component-props'] || {});
|
||||||
|
schema['x-component-props'].style = {
|
||||||
|
...(props.style || {}),
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
},
|
||||||
availableTypes: ['float'],
|
availableTypes: ['float'],
|
||||||
hasDefaultValue: true,
|
hasDefaultValue: true,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -29,9 +29,14 @@ export const select: IField = {
|
|||||||
operators: operators.enumType,
|
operators: operators.enumType,
|
||||||
},
|
},
|
||||||
schemaInitialize(schema: ISchema, { block }) {
|
schemaInitialize(schema: ISchema, { block }) {
|
||||||
|
const props = (schema['x-component-props'] = schema['x-component-props'] || {});
|
||||||
|
props.style = {
|
||||||
|
...(props.style || {}),
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
if (['Table', 'Kanban'].includes(block)) {
|
if (['Table', 'Kanban'].includes(block)) {
|
||||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
props['ellipsis'] = true;
|
||||||
schema['x-component-props']['ellipsis'] = true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
useSortFields,
|
useSortFields,
|
||||||
} from '../../../collection-manager';
|
} from '../../../collection-manager';
|
||||||
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
||||||
import { useCompile, useDesignable, useFieldComponentOptions, useFieldTitle } from '../../hooks';
|
import { useCompile, useDesignable, useFieldComponentOptions, useFieldTitle } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||||
@ -94,7 +94,7 @@ export const AssociationSelect = InternalAssociationSelect as unknown as Associa
|
|||||||
|
|
||||||
AssociationSelect.Designer = function Designer() {
|
AssociationSelect.Designer = function Designer() {
|
||||||
const { getCollectionFields, getInterface, getCollectionJoinField, getCollection } = useCollectionManager();
|
const { getCollectionFields, getInterface, getCollectionJoinField, getCollection } = useCollectionManager();
|
||||||
const { getField, template } = useCollection();
|
const { getField } = useCollection();
|
||||||
const { form } = useFormBlockContext();
|
const { form } = useFormBlockContext();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -390,41 +390,52 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form && !form?.readPretty && collectionField?.uiSchema?.type && (
|
{form &&
|
||||||
<SchemaSettings.ModalItem
|
!form?.readPretty &&
|
||||||
title={t('Set default value')}
|
isShowDefaultValue(collectionField, getInterface) &&
|
||||||
components={{ ArrayCollapse, FormLayout }}
|
!isPatternDisabled(fieldSchema) && (
|
||||||
schema={
|
<SchemaSettings.ModalItem
|
||||||
{
|
title={t('Set default value')}
|
||||||
type: 'object',
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
title: t('Set default value'),
|
width={800}
|
||||||
properties: {
|
schema={
|
||||||
default: {
|
{
|
||||||
...collectionField.uiSchema,
|
type: 'object',
|
||||||
name: 'default',
|
title: t('Set default value'),
|
||||||
title: t('Default value'),
|
properties: {
|
||||||
'x-decorator': 'FormItem',
|
default: {
|
||||||
default: fieldSchema.default || collectionField.defaultValue,
|
...(fieldSchema || {}),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component-props': {
|
||||||
|
...fieldSchema['x-component-props'],
|
||||||
|
component: collectionField?.target ? 'AssociationSelect' : undefined,
|
||||||
|
service: {
|
||||||
|
resource: collectionField?.target,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'default',
|
||||||
|
title: t('Default value'),
|
||||||
|
default: fieldSchema.default || collectionField.defaultValue,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
} as ISchema
|
||||||
} 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;
|
onSubmit={(v) => {
|
||||||
schema.default = v.default;
|
const schema: ISchema = {
|
||||||
dn.emit('patch', {
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
schema,
|
};
|
||||||
});
|
if (field.value !== v.default) {
|
||||||
refresh();
|
field.value = v.default;
|
||||||
}}
|
}
|
||||||
/>
|
fieldSchema.default = v.default;
|
||||||
)}
|
schema.default = v.default;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{form && !isSubFormAssociationField && fieldComponentOptions && (
|
{form && !isSubFormAssociationField && fieldComponentOptions && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
title={t('Field component')}
|
title={t('Field component')}
|
||||||
@ -474,6 +485,36 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{form &&
|
||||||
|
!form?.readPretty &&
|
||||||
|
['o2m', 'm2m'].includes(collectionField.interface) &&
|
||||||
|
fieldSchema['x-component'] !== 'TableField' && (
|
||||||
|
<SchemaSettings.SwitchItem
|
||||||
|
key="multiple"
|
||||||
|
title={t('Multiple')}
|
||||||
|
checked={
|
||||||
|
fieldSchema['x-component-props']?.multiple === undefined
|
||||||
|
? true
|
||||||
|
: fieldSchema['x-component-props'].multiple
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
field.componentProps = field.componentProps || {};
|
||||||
|
|
||||||
|
fieldSchema['x-component-props'].multiple = value;
|
||||||
|
field.componentProps.multiple = value;
|
||||||
|
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Set the data scope')}
|
title={t('Set the data scope')}
|
||||||
schema={
|
schema={
|
||||||
@ -594,7 +635,7 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{form && !form?.readPretty && fieldSchema?.['x-component-props']?.['pattern-disable'] != true && (
|
{form && !form?.readPretty && !isPatternDisabled(fieldSchema) && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
key="pattern"
|
key="pattern"
|
||||||
title={t('Pattern')}
|
title={t('Pattern')}
|
||||||
|
@ -7,13 +7,66 @@ import { useBlockRequestContext } from '../../../block-provider';
|
|||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
|
||||||
export const useFilterOptions = (collectionName: string) => {
|
export const useGetFilterOptions = () => {
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
const fields = getCollectionFields(collectionName);
|
|
||||||
const options=useFilterFieldOptions(fields)
|
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { getChildrenCollections } = useCollectionManager();
|
const { getChildrenCollections } = useCollectionManager();
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
|
const getFilterFieldOptions = useGetFilterFieldOptions();
|
||||||
|
|
||||||
|
return (collectionName) => {
|
||||||
|
const fields = getCollectionFields(collectionName);
|
||||||
|
const options = getFilterFieldOptions(fields);
|
||||||
|
const childrenCollections = getChildrenCollections(collection.name);
|
||||||
|
|
||||||
|
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
||||||
|
options.push({
|
||||||
|
name: 'tableoid',
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Table OID(Inheritance)")}}',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: [{ value: collection.name, label: compile(collection.title) }].concat(
|
||||||
|
childrenCollections.map((v) => {
|
||||||
|
return {
|
||||||
|
value: v.name,
|
||||||
|
label: compile(v.title),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
operators: [
|
||||||
|
{
|
||||||
|
label: '{{t("contains")}}',
|
||||||
|
value: '$childIn',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("does not contain")}}',
|
||||||
|
value: '$childNotIn',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFilterOptions = (collectionName: string) => {
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { getChildrenCollections } = useCollectionManager();
|
||||||
|
const collection = useCollection();
|
||||||
|
const fields = getCollectionFields(collectionName);
|
||||||
|
const options = useFilterFieldOptions(fields);
|
||||||
|
|
||||||
const childrenCollections = getChildrenCollections(collection.name);
|
const childrenCollections = getChildrenCollections(collection.name);
|
||||||
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
||||||
options.push({
|
options.push({
|
||||||
@ -54,6 +107,63 @@ export const useFilterOptions = (collectionName: string) => {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetFilterFieldOptions = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
||||||
|
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||||
|
const field2option = (field, depth) => {
|
||||||
|
if (nonfilterable.length && depth === 1 && nonfilterable.includes(field.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!field.interface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fieldInterface = getInterface(field.interface);
|
||||||
|
if (!fieldInterface.filterable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { nested, children, operators } = fieldInterface.filterable;
|
||||||
|
const option = {
|
||||||
|
name: field.name,
|
||||||
|
type: field.type,
|
||||||
|
target: field.target,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
schema: field?.uiSchema,
|
||||||
|
operators:
|
||||||
|
operators?.filter?.((operator) => {
|
||||||
|
return !operator?.visible || operator.visible(field);
|
||||||
|
}) || [],
|
||||||
|
};
|
||||||
|
if (field.target && depth > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (depth > 2) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
if (children?.length) {
|
||||||
|
option['children'] = children;
|
||||||
|
}
|
||||||
|
if (nested) {
|
||||||
|
const targetFields = getCollectionFields(field.target);
|
||||||
|
const options = getOptions(targetFields, depth + 1).filter(Boolean);
|
||||||
|
option['children'] = option['children'] || [];
|
||||||
|
option['children'].push(...options);
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
};
|
||||||
|
const getOptions = (fields, depth) => {
|
||||||
|
const options = [];
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const option = field2option(field, depth);
|
||||||
|
if (option) {
|
||||||
|
options.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
return (fields) => getOptions(fields, 1);
|
||||||
|
};
|
||||||
|
|
||||||
export const useFilterFieldOptions = (fields) => {
|
export const useFilterFieldOptions = (fields) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
||||||
|
@ -22,7 +22,8 @@ const findOption = (dataIndex = [], options) => {
|
|||||||
|
|
||||||
export const useValues = () => {
|
export const useValues = () => {
|
||||||
const field = useField<any>();
|
const field = useField<any>();
|
||||||
const { options } = useContext(FilterContext);
|
const { options } = useContext(FilterContext) || {};
|
||||||
|
|
||||||
const data2value = () => {
|
const data2value = () => {
|
||||||
field.value = flat.unflatten({
|
field.value = flat.unflatten({
|
||||||
[`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
|
[`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
|
||||||
@ -32,7 +33,7 @@ export const useValues = () => {
|
|||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
const values = flat(field.value);
|
const values = flat(field.value);
|
||||||
const path = Object.keys(values).shift() || '';
|
const path = Object.keys(values).shift() || '';
|
||||||
if (!path) {
|
if (!path || !options) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [fieldPath = '', otherPath = ''] = path.split('.$');
|
const [fieldPath = '', otherPath = ''] = path.split('.$');
|
||||||
@ -50,7 +51,7 @@ export const useValues = () => {
|
|||||||
useEffect(value2data, [field.path.entire]);
|
useEffect(value2data, [field.path.entire]);
|
||||||
return {
|
return {
|
||||||
fields: options,
|
fields: options,
|
||||||
...field.data,
|
...(field?.data || {}),
|
||||||
setDataIndex(dataIndex) {
|
setDataIndex(dataIndex) {
|
||||||
const option = findOption(dataIndex, options);
|
const option = findOption(dataIndex, options);
|
||||||
const operator = option?.operators?.[0];
|
const operator = option?.operators?.[0];
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayCollapse, FormLayout, FormItem as Item } from '@formily/antd';
|
import { ArrayCollapse, FormLayout, FormItem as Item } from '@formily/antd';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useContext, useEffect } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
||||||
import { BlockRequestContext, useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
import { BlockRequestContext, useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||||
import { Collection, useCollection, useCollectionManager } from '../../../collection-manager';
|
import { Collection, CollectionFieldOptions, useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
||||||
|
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
|
||||||
|
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
|
||||||
|
import { SchemaComponent } from '../../core';
|
||||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||||
import { BlockItem } from '../block-item';
|
import { BlockItem } from '../block-item';
|
||||||
import { HTMLEncode } from '../input/shared';
|
import { HTMLEncode } from '../input/shared';
|
||||||
@ -33,15 +36,20 @@ const divWrap = (schema: ISchema) => {
|
|||||||
export const FormItem: any = observer((props: any) => {
|
export const FormItem: any = observer((props: any) => {
|
||||||
useEnsureOperatorsValid();
|
useEnsureOperatorsValid();
|
||||||
|
|
||||||
const field = useField();
|
const field = useField<Field>();
|
||||||
const ctx = useContext(BlockRequestContext);
|
const ctx = useContext(BlockRequestContext);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
|
const variablesCtx = useVariablesCtx();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ctx?.block === 'form') {
|
if (ctx?.block === 'form') {
|
||||||
ctx.field.data = ctx.field.data || {};
|
ctx.field.data = ctx.field.data || {};
|
||||||
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
|
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
|
||||||
ctx.field.data.activeFields.add(schema.name);
|
ctx.field.data.activeFields.add(schema.name);
|
||||||
|
// 如果默认值是一个变量,则需要解析之后再显示出来
|
||||||
|
if (isVariable(schema?.default)) {
|
||||||
|
field.setInitialValue?.(parseVariables(schema.default, variablesCtx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
@ -81,6 +89,8 @@ FormItem.Designer = function Designer() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const variablesCtx = useVariablesCtx();
|
||||||
|
|
||||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
const targetCollection = getCollection(collectionField?.target);
|
const targetCollection = getCollection(collectionField?.target);
|
||||||
const interfaceConfig = getInterface(collectionField?.interface);
|
const interfaceConfig = getInterface(collectionField?.interface);
|
||||||
@ -396,41 +406,91 @@ FormItem.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form && !form?.readPretty && collectionField?.uiSchema?.type && (
|
{form &&
|
||||||
<SchemaSettings.ModalItem
|
!form?.readPretty &&
|
||||||
title={t('Set default value')}
|
isShowDefaultValue(collectionField, getInterface) &&
|
||||||
components={{ ArrayCollapse, FormLayout }}
|
!isPatternDisabled(fieldSchema) && (
|
||||||
schema={
|
<SchemaSettings.ModalItem
|
||||||
{
|
title={t('Set default value')}
|
||||||
type: 'object',
|
components={{ ArrayCollapse, FormLayout, VariableInput }}
|
||||||
title: t('Set default value'),
|
width={800}
|
||||||
properties: {
|
schema={
|
||||||
default: {
|
{
|
||||||
...collectionField?.uiSchema,
|
type: 'object',
|
||||||
name: 'default',
|
title: t('Set default value'),
|
||||||
title: t('Default value'),
|
properties: {
|
||||||
'x-decorator': 'FormItem',
|
// 关系字段不支持设置变量
|
||||||
default: fieldSchema.default || collectionField?.defaultValue,
|
default: collectionField?.target
|
||||||
|
? {
|
||||||
|
...(fieldSchema || {}),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component-props': {
|
||||||
|
...fieldSchema['x-component-props'],
|
||||||
|
component:
|
||||||
|
collectionField?.target && collectionField.interface !== 'chinaRegion'
|
||||||
|
? 'AssociationSelect'
|
||||||
|
: undefined,
|
||||||
|
service: {
|
||||||
|
resource: collectionField?.target,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'default',
|
||||||
|
title: t('Default value'),
|
||||||
|
default: getFieldDefaultValue(fieldSchema, collectionField),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...(fieldSchema || {}),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'VariableInput',
|
||||||
|
'x-component-props': {
|
||||||
|
...(fieldSchema?.['x-component-props'] || {}),
|
||||||
|
collectionName: collectionField?.collectionName,
|
||||||
|
schema: collectionField?.uiSchema,
|
||||||
|
renderSchemaComponent: function Com(props) {
|
||||||
|
const s = _.cloneDeep(fieldSchema) || ({} as Schema);
|
||||||
|
s.title = '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
...(s || {}),
|
||||||
|
'x-component-props': {
|
||||||
|
...s['x-component-props'],
|
||||||
|
onChange: props.onChange,
|
||||||
|
value: props.value,
|
||||||
|
defaultValue: getFieldDefaultValue(s, collectionField),
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'default',
|
||||||
|
title: t('Default value'),
|
||||||
|
default: getFieldDefaultValue(fieldSchema, collectionField),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
} as ISchema
|
||||||
} 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;
|
onSubmit={(v) => {
|
||||||
schema.default = v.default;
|
const schema: ISchema = {
|
||||||
dn.emit('patch', {
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
schema,
|
};
|
||||||
});
|
if (field.value !== v.default) {
|
||||||
refresh();
|
field.value = parseVariables(v.default, variablesCtx);
|
||||||
}}
|
}
|
||||||
/>
|
fieldSchema.default = v.default;
|
||||||
)}
|
schema.default = v.default;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{form && !isSubFormAssociationField && fieldComponentOptions && (
|
{form && !isSubFormAssociationField && fieldComponentOptions && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
title={t('Field component')}
|
title={t('Field component')}
|
||||||
@ -476,6 +536,36 @@ FormItem.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{form &&
|
||||||
|
!form?.readPretty &&
|
||||||
|
['o2m', 'm2m'].includes(collectionField.interface) &&
|
||||||
|
fieldSchema['x-component'] !== 'TableField' && (
|
||||||
|
<SchemaSettings.SwitchItem
|
||||||
|
key="multiple"
|
||||||
|
title={t('Multiple')}
|
||||||
|
checked={
|
||||||
|
fieldSchema['x-component-props']?.multiple === undefined
|
||||||
|
? true
|
||||||
|
: fieldSchema['x-component-props'].multiple
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
field.componentProps = field.componentProps || {};
|
||||||
|
|
||||||
|
fieldSchema['x-component-props'].multiple = value;
|
||||||
|
field.componentProps.multiple = value;
|
||||||
|
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{field.readPretty && options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && (
|
{field.readPretty && options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && (
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Enable link')}
|
title={t('Enable link')}
|
||||||
@ -497,61 +587,58 @@ FormItem.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form &&
|
{form && !form?.readPretty && collectionField?.interface !== 'o2m' && !isPatternDisabled(fieldSchema) && (
|
||||||
!form?.readPretty &&
|
<SchemaSettings.SelectItem
|
||||||
collectionField?.interface !== 'o2m' &&
|
key="pattern"
|
||||||
fieldSchema?.['x-component-props']?.['pattern-disable'] != true && (
|
title={t('Pattern')}
|
||||||
<SchemaSettings.SelectItem
|
options={[
|
||||||
key="pattern"
|
{ label: t('Editable'), value: 'editable' },
|
||||||
title={t('Pattern')}
|
{ label: t('Readonly'), value: 'readonly' },
|
||||||
options={[
|
{ label: t('Easy-reading'), value: 'read-pretty' },
|
||||||
{ label: t('Editable'), value: 'editable' },
|
]}
|
||||||
{ label: t('Readonly'), value: 'readonly' },
|
value={readOnlyMode}
|
||||||
{ label: t('Easy-reading'), value: 'read-pretty' },
|
onChange={(v) => {
|
||||||
]}
|
const schema: ISchema = {
|
||||||
value={readOnlyMode}
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
onChange={(v) => {
|
};
|
||||||
const schema: ISchema = {
|
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case 'readonly': {
|
case 'readonly': {
|
||||||
fieldSchema['x-read-pretty'] = false;
|
fieldSchema['x-read-pretty'] = false;
|
||||||
fieldSchema['x-disabled'] = true;
|
fieldSchema['x-disabled'] = true;
|
||||||
schema['x-read-pretty'] = false;
|
schema['x-read-pretty'] = false;
|
||||||
schema['x-disabled'] = true;
|
schema['x-disabled'] = true;
|
||||||
field.readPretty = false;
|
field.readPretty = false;
|
||||||
field.disabled = true;
|
field.disabled = true;
|
||||||
break;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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', {
|
dn.emit('patch', {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
|
|
||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && (
|
{options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
key="title-field"
|
key="title-field"
|
||||||
@ -597,3 +684,7 @@ function isFileCollection(collection: Collection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FormItem.FilterFormDesigner = FilterFormDesigner;
|
FormItem.FilterFormDesigner = FilterFormDesigner;
|
||||||
|
|
||||||
|
export function getFieldDefaultValue(fieldSchema: ISchema, collectionField: CollectionFieldOptions) {
|
||||||
|
return fieldSchema?.default || collectionField?.defaultValue;
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ import React from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { SchemaSettings } from '../../../schema-settings';
|
import { SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||||
import { useOperatorList } from '../filter/useOperators';
|
import { useOperatorList } from '../filter/useOperators';
|
||||||
|
|
||||||
@ -448,10 +448,7 @@ export const EditPattern = () => {
|
|||||||
readOnlyMode = 'read-pretty';
|
readOnlyMode = 'read-pretty';
|
||||||
}
|
}
|
||||||
|
|
||||||
return form &&
|
return form && !form?.readPretty && collectionField?.interface !== 'o2m' && !isPatternDisabled(fieldSchema) ? (
|
||||||
!form?.readPretty &&
|
|
||||||
collectionField?.interface !== 'o2m' &&
|
|
||||||
fieldSchema?.['x-component-props']?.['pattern-disable'] != true ? (
|
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
key="pattern"
|
key="pattern"
|
||||||
title={t('Pattern')}
|
title={t('Pattern')}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { forEach } from '@nocobase/utils/client';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAPIClient } from '../../../api-client';
|
import { useAPIClient } from '../../../api-client';
|
||||||
import { findFormBlock } from '../../../block-provider';
|
import { findFormBlock } from '../../../block-provider';
|
||||||
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
|
|
||||||
interface ITemplate {
|
interface ITemplate {
|
||||||
items: {
|
items: {
|
||||||
@ -40,26 +42,41 @@ const useDataTemplates = () => {
|
|||||||
|
|
||||||
export const Templates = ({ style = {}, form }) => {
|
export const Templates = ({ style = {}, form }) => {
|
||||||
const { templates, display, enabled, defaultTemplate } = useDataTemplates();
|
const { templates, display, enabled, defaultTemplate } = useDataTemplates();
|
||||||
|
const { getCollectionField } = useCollectionManager();
|
||||||
const [value, setValue] = React.useState(defaultTemplate?.key || 'none');
|
const [value, setValue] = React.useState(defaultTemplate?.key || 'none');
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultTemplate) {
|
if (defaultTemplate) {
|
||||||
fetchTemplateData(api, defaultTemplate).then((data) => {
|
fetchTemplateData(api, defaultTemplate)
|
||||||
if (form) {
|
.then((data) => {
|
||||||
form.values = data;
|
if (form) {
|
||||||
}
|
forEach(data, (value, key) => {
|
||||||
});
|
if (value) {
|
||||||
|
form.values[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = useCallback(async (value, option) => {
|
const handleChange = useCallback(async (value, option) => {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
if (option.key !== 'none') {
|
if (option.key !== 'none') {
|
||||||
if (form) {
|
fetchTemplateData(api, option).then((data) => {
|
||||||
form.values = await fetchTemplateData(api, option);
|
if (form) {
|
||||||
}
|
forEach(data, (value, key) => {
|
||||||
|
if (value) {
|
||||||
|
form.values[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
form?.reset();
|
form?.reset();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { isValid } from '@formily/shared';
|
|||||||
import { Button, Input, Popover } from 'antd';
|
import { Button, Input, Popover } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { hasIcon, Icon, icons } from '../../../icon';
|
import { Icon, hasIcon, icons } from '../../../icon';
|
||||||
|
|
||||||
function IconField(props: any) {
|
function IconField(props: any) {
|
||||||
const layout = useFormLayout();
|
const layout = useFormLayout();
|
||||||
|
@ -236,7 +236,6 @@ const Drawer: React.FunctionComponent<{
|
|||||||
fieldSchema,
|
fieldSchema,
|
||||||
options,
|
options,
|
||||||
}) => {
|
}) => {
|
||||||
console.log('collectionField', options);
|
|
||||||
const getFilter = () => {
|
const getFilter = () => {
|
||||||
const targetKey = collectionField.targetKey || 'id';
|
const targetKey = collectionField.targetKey || 'id';
|
||||||
const list = options.map((option) => option[targetKey]).filter(Boolean);
|
const list = options.map((option) => option[targetKey]).filter(Boolean);
|
||||||
|
@ -38,7 +38,7 @@ const InternalRemoteSelect = connect(
|
|||||||
const firstRun = useRef(false);
|
const firstRun = useRef(false);
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField, getInterface } = useCollectionManager();
|
||||||
const collectionField = getField(fieldSchema.name);
|
const collectionField = getField(fieldSchema.name);
|
||||||
const targetField =
|
const targetField =
|
||||||
_targetField ||
|
_targetField ||
|
||||||
@ -46,6 +46,13 @@ const InternalRemoteSelect = connect(
|
|||||||
fieldNames?.label &&
|
fieldNames?.label &&
|
||||||
getCollectionJoinField(`${collectionField.target}.${fieldNames.label}`));
|
getCollectionJoinField(`${collectionField.target}.${fieldNames.label}`));
|
||||||
|
|
||||||
|
const operator = useMemo(() => {
|
||||||
|
if (targetField?.interface) {
|
||||||
|
return getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes';
|
||||||
|
}
|
||||||
|
return '$includes';
|
||||||
|
}, [targetField]);
|
||||||
|
|
||||||
const mapOptionsToTags = useCallback(
|
const mapOptionsToTags = useCallback(
|
||||||
(options) => {
|
(options) => {
|
||||||
try {
|
try {
|
||||||
@ -138,7 +145,7 @@ const InternalRemoteSelect = connect(
|
|||||||
filter: mergeFilter([
|
filter: mergeFilter([
|
||||||
{
|
{
|
||||||
[fieldNames.label]: {
|
[fieldNames.label]: {
|
||||||
$includes: search,
|
[operator]: search,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
service?.params?.filter,
|
service?.params?.filter,
|
||||||
|
@ -71,24 +71,18 @@ const InternalSelect = connect(
|
|||||||
(props: Props) => {
|
(props: Props) => {
|
||||||
const { objectValue, value, ...others } = props;
|
const { objectValue, value, ...others } = props;
|
||||||
let mode: any = props.multiple ? 'multiple' : props.mode;
|
let mode: any = props.multiple ? 'multiple' : props.mode;
|
||||||
if (mode === 'links') {
|
if (mode && !['multiple', 'tags'].includes(mode)) {
|
||||||
mode = undefined;
|
mode = undefined;
|
||||||
}
|
}
|
||||||
const toValue = (v) => {
|
|
||||||
if (['multiple', 'tags'].includes(mode)) {
|
|
||||||
return v || [];
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
if (objectValue) {
|
if (objectValue) {
|
||||||
return <ObjectSelect {...others} value={toValue(value)} mode={mode} />;
|
return <ObjectSelect {...others} value={value} mode={mode} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={filterOption}
|
filterOption={filterOption}
|
||||||
allowClear
|
allowClear
|
||||||
value={toValue(value)}
|
value={value}
|
||||||
{...others}
|
{...others}
|
||||||
onChange={(changed) => {
|
onChange={(changed) => {
|
||||||
props.onChange?.(changed === undefined ? null : changed);
|
props.onChange?.(changed === undefined ? null : changed);
|
||||||
|
@ -1,172 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useCompile } from '../..';
|
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
|
||||||
import { useValues } from '../filter/useValues';
|
|
||||||
import { Variable } from '../variable';
|
import { Variable } from '../variable';
|
||||||
import { useUserVariable } from './hooks/useUserVariable';
|
|
||||||
|
|
||||||
const useVariableOptions = () => {
|
|
||||||
const { operator, schema } = useValues();
|
|
||||||
const operatorValue = operator?.value || '';
|
|
||||||
const userVariable = useUserVariable({ schema, operator });
|
|
||||||
|
|
||||||
if (!operator || !schema) return [];
|
|
||||||
|
|
||||||
const disabled = !['DatePicker', 'DatePicker.RangePicker'].includes(schema['x-component']);
|
|
||||||
const dateOptions = [
|
|
||||||
{
|
|
||||||
key: 'now',
|
|
||||||
value: 'now',
|
|
||||||
label: `{{t("Now")}}`,
|
|
||||||
disabled: schema['x-component'] !== 'DatePicker' || operatorValue === '$dateBetween',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'yesterday',
|
|
||||||
value: 'yesterday',
|
|
||||||
label: `{{t("Yesterday")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'today',
|
|
||||||
value: 'today',
|
|
||||||
label: `{{t("Today")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tomorrow',
|
|
||||||
value: 'tomorrow',
|
|
||||||
label: `{{t("Tomorrow")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lastIsoWeek',
|
|
||||||
value: 'lastIsoWeek',
|
|
||||||
label: `{{t("Last week")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'thisIsoWeek',
|
|
||||||
value: 'thisIsoWeek',
|
|
||||||
label: `{{t("This week")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'nextIsoWeek',
|
|
||||||
value: 'nextIsoWeek',
|
|
||||||
label: `{{t("Next week")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lastMonth',
|
|
||||||
value: 'lastMonth',
|
|
||||||
label: `{{t("Last month")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'thisMonth',
|
|
||||||
value: 'thisMonth',
|
|
||||||
label: `{{t("This month")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'nextMonth',
|
|
||||||
value: 'nextMonth',
|
|
||||||
label: `{{t("Next month")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lastQuarter',
|
|
||||||
value: 'lastQuarter',
|
|
||||||
label: `{{t("Last quarter")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'thisQuarter',
|
|
||||||
value: 'thisQuarter',
|
|
||||||
label: `{{t("This quarter")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'nextQuarter',
|
|
||||||
value: 'nextQuarter',
|
|
||||||
label: `{{t("Next quarter")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lastYear',
|
|
||||||
value: 'lastYear',
|
|
||||||
label: `{{t("Last year")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'thisYear',
|
|
||||||
value: 'thisYear',
|
|
||||||
label: `{{t("This year")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'nextYear',
|
|
||||||
value: 'nextYear',
|
|
||||||
label: `{{t("Next year")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'last7Days',
|
|
||||||
value: 'last7Days',
|
|
||||||
label: `{{t("Last 7 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'next7Days',
|
|
||||||
value: 'next7Days',
|
|
||||||
label: `{{t("Next 7 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'last30Days',
|
|
||||||
value: 'last30Days',
|
|
||||||
label: `{{t("Last 30 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'next30Days',
|
|
||||||
value: 'next30Days',
|
|
||||||
label: `{{t("Next 30 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'last90Days',
|
|
||||||
value: 'last90Days',
|
|
||||||
label: `{{t("Last 90 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'next90Days',
|
|
||||||
value: 'next90Days',
|
|
||||||
label: `{{t("Next 90 days")}}`,
|
|
||||||
disabled,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
userVariable,
|
|
||||||
{
|
|
||||||
label: `{{t("Date variables")}}`,
|
|
||||||
value: '$date',
|
|
||||||
key: '$date',
|
|
||||||
disabled: dateOptions.every((option) => option.disabled),
|
|
||||||
children: dateOptions,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function FilterDynamicComponent(props) {
|
export function FilterDynamicComponent(props) {
|
||||||
const { value, onChange, renderSchemaComponent } = props;
|
const { value, onChange, renderSchemaComponent } = props;
|
||||||
const options = useVariableOptions();
|
const options = useVariableOptions();
|
||||||
const compile = useCompile();
|
|
||||||
const scope = compile(options);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Variable.Input value={value} onChange={onChange} scope={scope}>
|
<Variable.Input value={value} onChange={onChange} scope={options}>
|
||||||
{renderSchemaComponent()}
|
{renderSchemaComponent()}
|
||||||
</Variable.Input>
|
</Variable.Input>
|
||||||
);
|
);
|
||||||
|
@ -4,10 +4,10 @@ import { css } from '@emotion/css';
|
|||||||
import { ArrayField, Field } from '@formily/core';
|
import { ArrayField, Field } from '@formily/core';
|
||||||
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import { reaction } from '@formily/reactive';
|
import { reaction } from '@formily/reactive';
|
||||||
import { useEventListener, useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
import { default as classNames, default as cls } from 'classnames';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DndContext, useDesignable, useTableSize } from '../..';
|
import { DndContext, useDesignable, useTableSize } from '../..';
|
||||||
import {
|
import {
|
||||||
@ -213,7 +213,7 @@ export const Table: any = observer((props: any) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (treeTable !== false) {
|
if (treeTable !== false) {
|
||||||
const keys = getIdsWithChildren(field.value?.slice());
|
const keys = getIdsWithChildren(field.value?.slice?.());
|
||||||
setAllIncludesChildren(keys);
|
setAllIncludesChildren(keys);
|
||||||
}
|
}
|
||||||
}, [field.value]);
|
}, [field.value]);
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import { useFilterOptions } from '../../filter';
|
|
||||||
|
|
||||||
interface GetOptionsParams {
|
|
||||||
schema: any;
|
|
||||||
operator: string;
|
|
||||||
maxDepth: number;
|
|
||||||
count?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useOptions = (collectionName: string, { schema, operator, maxDepth, count = 1 }: GetOptionsParams) => {
|
|
||||||
if (count > maxDepth) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = useFilterOptions(collectionName).map((option) => {
|
|
||||||
if ((option.type !== 'belongsTo' && option.type !== 'hasOne') || !option.target) {
|
|
||||||
return {
|
|
||||||
key: option.name,
|
|
||||||
value: option.name,
|
|
||||||
label: option.title,
|
|
||||||
// TODO: 现在是通过组件的名称来过滤能够被选择的选项,这样的坏处是不够精确,后续可以优化
|
|
||||||
disabled: schema?.['x-component'] !== option.schema?.['x-component'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const children =
|
|
||||||
useOptions(option.target, {
|
|
||||||
schema,
|
|
||||||
operator,
|
|
||||||
maxDepth,
|
|
||||||
count: count + 1,
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: option.name,
|
|
||||||
value: option.name,
|
|
||||||
label: option.title,
|
|
||||||
children,
|
|
||||||
disabled: children.every((child) => child.disabled),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUserVariable = ({ schema, operator }) => {
|
|
||||||
const options = useOptions('users', { schema, operator, maxDepth: 3 }) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: `{{t("Current user")}}`,
|
|
||||||
value: '$user',
|
|
||||||
key: '$user',
|
|
||||||
disabled: options.every((option) => option.disabled),
|
|
||||||
children: options,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,7 +1,7 @@
|
|||||||
import { CloseCircleFilled } from '@ant-design/icons';
|
import { CloseCircleFilled } from '@ant-design/icons';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm } from '@formily/react';
|
||||||
import { Button, Cascader, DatePicker, Input as AntInput, InputNumber, Select, Tag } from 'antd';
|
import { Input as AntInput, Cascader, DatePicker, InputNumber, Select, Tag } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -117,7 +117,7 @@ export function Input(props) {
|
|||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
|
|
||||||
const { value = '', scope, onChange, children, button, useTypedConstant } = props;
|
const { value = '', scope, onChange, children, button, useTypedConstant, style } = props;
|
||||||
const parsed = parseValue(value);
|
const parsed = parseValue(value);
|
||||||
const isConstant = typeof parsed === 'string';
|
const isConstant = typeof parsed === 'string';
|
||||||
const type = isConstant ? parsed : '';
|
const type = isConstant ? parsed : '';
|
||||||
@ -171,6 +171,7 @@ export function Input(props) {
|
|||||||
return (
|
return (
|
||||||
<AntInput.Group
|
<AntInput.Group
|
||||||
compact
|
compact
|
||||||
|
style={style}
|
||||||
className={css`
|
className={css`
|
||||||
width: auto;
|
width: auto;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
@ -186,67 +187,71 @@ export function Input(props) {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{variable ? (
|
<div style={{ flex: 1 }}>
|
||||||
<div
|
{variable ? (
|
||||||
className={css`
|
|
||||||
position: relative;
|
|
||||||
line-height: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.ant-select-clear {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input {
|
|
||||||
overflow: auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
${disabled ? '' : 'padding-right: 28px;'}
|
|
||||||
|
|
||||||
.ant-tag {
|
|
||||||
display: inline;
|
|
||||||
line-height: 19px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 7px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
onInput={(e) => e.preventDefault()}
|
className={css`
|
||||||
onKeyDown={(e) => {
|
position: relative;
|
||||||
if (e.key !== 'Backspace') {
|
line-height: 0;
|
||||||
e.preventDefault();
|
|
||||||
return;
|
&:hover {
|
||||||
|
.ant-select-clear {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onChange(null);
|
|
||||||
}}
|
.ant-input {
|
||||||
className={cx('ant-input', { 'ant-input-disabled': disabled })}
|
overflow: auto;
|
||||||
contentEditable={!disabled}
|
white-space: nowrap;
|
||||||
suppressContentEditableWarning
|
${disabled ? '' : 'padding-right: 28px;'}
|
||||||
|
|
||||||
|
.ant-tag {
|
||||||
|
display: inline;
|
||||||
|
line-height: 19px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<Tag contentEditable={false} color="blue">
|
<div
|
||||||
{variableText}
|
onInput={(e) => e.preventDefault()}
|
||||||
</Tag>
|
onKeyDown={(e) => {
|
||||||
</div>
|
if (e.key !== 'Backspace') {
|
||||||
{!disabled ? (
|
e.preventDefault();
|
||||||
<span
|
return;
|
||||||
className={cx(
|
}
|
||||||
'ant-select-clear',
|
onChange(null);
|
||||||
css`
|
}}
|
||||||
user-select: 'none';
|
className={cx('ant-input', { 'ant-input-disabled': disabled })}
|
||||||
`,
|
contentEditable={!disabled}
|
||||||
)}
|
suppressContentEditableWarning
|
||||||
unselectable="on"
|
|
||||||
aria-hidden
|
|
||||||
onClick={() => onChange(null)}
|
|
||||||
>
|
>
|
||||||
<CloseCircleFilled />
|
<Tag contentEditable={false} color="blue">
|
||||||
</span>
|
{variableText}
|
||||||
) : null}
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
) : children ?? <ConstantComponent value={value} onChange={onChange} />}
|
{!disabled ? (
|
||||||
|
<span
|
||||||
|
className={cx(
|
||||||
|
'ant-select-clear',
|
||||||
|
css`
|
||||||
|
user-select: 'none';
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
unselectable="on"
|
||||||
|
aria-hidden
|
||||||
|
onClick={() => onChange(null)}
|
||||||
|
>
|
||||||
|
<CloseCircleFilled />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children ?? <ConstantComponent value={value} onChange={onChange} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{options.length > 1 ? (
|
{options.length > 1 ? (
|
||||||
<Cascader
|
<Cascader
|
||||||
options={options}
|
options={options}
|
||||||
@ -254,9 +259,7 @@ export function Input(props) {
|
|||||||
onChange={onSwitch}
|
onChange={onSwitch}
|
||||||
changeOnSelect
|
changeOnSelect
|
||||||
>
|
>
|
||||||
{button ?? (
|
{button ?? <XButton type={variable ? 'primary' : 'default'} />}
|
||||||
<XButton type={variable ? 'primary' : 'default'} />
|
|
||||||
)}
|
|
||||||
</Cascader>
|
</Cascader>
|
||||||
) : null}
|
) : null}
|
||||||
</AntInput.Group>
|
</AntInput.Group>
|
||||||
|
@ -1,7 +1,46 @@
|
|||||||
import { every, some, findIndex } from 'lodash';
|
|
||||||
import flat from 'flat';
|
import flat from 'flat';
|
||||||
|
import _, { every, findIndex, some } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { useCurrentUserContext } from '../../../user';
|
||||||
import jsonLogic from '../../common/utils/logic';
|
import jsonLogic from '../../common/utils/logic';
|
||||||
|
|
||||||
|
type VariablesCtx = {
|
||||||
|
/** 当前登录的用户 */
|
||||||
|
$user: Record<string, any>;
|
||||||
|
$date: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVariablesCtx = (): VariablesCtx => {
|
||||||
|
const { data } = useCurrentUserContext() || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
$user: data?.data || {},
|
||||||
|
$date: {
|
||||||
|
now: () => moment().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isVariable = (str: unknown) => {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const regex = /{{(.*?)}}/;
|
||||||
|
const matches = str?.match?.(regex);
|
||||||
|
return matches ? true : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseVariables = (str: string, ctx: VariablesCtx) => {
|
||||||
|
const regex = /{{(.*?)}}/;
|
||||||
|
const matches = str?.match?.(regex);
|
||||||
|
if (matches) {
|
||||||
|
const result = _.get(ctx, matches[1]);
|
||||||
|
return _.isFunction(result) ? result() : result;
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function getInnermostKeyAndValue(obj) {
|
function getInnermostKeyAndValue(obj) {
|
||||||
if (typeof obj !== 'object' || obj === null) {
|
if (typeof obj !== 'object' || obj === null) {
|
||||||
return null;
|
return null;
|
||||||
@ -17,7 +56,8 @@ function getInnermostKeyAndValue(obj) {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const getValue = (str, values) => {
|
|
||||||
|
const getValue = (str: string, values) => {
|
||||||
const regex = /{{(.*?)}}/;
|
const regex = /{{(.*?)}}/;
|
||||||
const matches = str?.match?.(regex);
|
const matches = str?.match?.(regex);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
|
@ -3,12 +3,12 @@ import { get } from 'lodash';
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
export const useComponent = (component: any, defaults?: any) => {
|
export const useComponent = (component: any, defaults?: any) => {
|
||||||
|
const { components } = useContext(SchemaOptionsContext);
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
if (typeof component !== 'string') {
|
if (typeof component !== 'string') {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
const { components } = useContext(SchemaOptionsContext);
|
|
||||||
return get(components, component) || defaults;
|
return get(components, component) || defaults;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,14 @@ import React from 'react';
|
|||||||
import { Variable } from '../../schema-component';
|
import { Variable } from '../../schema-component';
|
||||||
import { useVariableOptions } from './Variables';
|
import { useVariableOptions } from './Variables';
|
||||||
|
|
||||||
export function FilterDynamicComponent(props) {
|
type Props = {
|
||||||
|
value: any;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
renderSchemaComponent: () => React.ReactNode;
|
||||||
|
collectionName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FilterDynamicComponent(props: Props) {
|
||||||
const { value, onChange, renderSchemaComponent, collectionName } = props;
|
const { value, onChange, renderSchemaComponent, collectionName } = props;
|
||||||
const scope = useVariableOptions(collectionName);
|
const scope = useVariableOptions(collectionName);
|
||||||
return (
|
return (
|
||||||
|
@ -25,6 +25,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import {
|
import {
|
||||||
APIClientProvider,
|
APIClientProvider,
|
||||||
ActionContext,
|
ActionContext,
|
||||||
|
CollectionFieldOptions,
|
||||||
CollectionManagerContext,
|
CollectionManagerContext,
|
||||||
Designable,
|
Designable,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
@ -35,10 +36,10 @@ import {
|
|||||||
findFormBlock,
|
findFormBlock,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCollection,
|
useCollection,
|
||||||
useLinkageCollectionFilterOptions,
|
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useCompile,
|
useCompile,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
|
useLinkageCollectionFilterOptions,
|
||||||
} from '..';
|
} from '..';
|
||||||
import { findFilterTargets, updateFilterTargets } from '../block-provider/hooks';
|
import { findFilterTargets, updateFilterTargets } from '../block-provider/hooks';
|
||||||
import { FilterBlockType, isSameCollection, useSupportedBlocks } from '../filter-provider/utils';
|
import { FilterBlockType, isSameCollection, useSupportedBlocks } from '../filter-provider/utils';
|
||||||
@ -1127,3 +1128,21 @@ SchemaSettings.EnableChildCollections = function EnableChildCollections(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 是否显示默认值配置项
|
||||||
|
export const isShowDefaultValue = (collectionField: CollectionFieldOptions, getInterface) => {
|
||||||
|
return (
|
||||||
|
!['o2o', 'oho', 'obo', 'o2m', 'attachment'].includes(collectionField?.interface) &&
|
||||||
|
!isSystemField(collectionField, getInterface)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否是系统字段
|
||||||
|
export const isSystemField = (collectionField: CollectionFieldOptions, getInterface) => {
|
||||||
|
const i = getInterface?.(collectionField?.interface);
|
||||||
|
return i?.group === 'systemInfo';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPatternDisabled = (fieldSchema: Schema) => {
|
||||||
|
return fieldSchema?.['x-component-props']?.['pattern-disable'] == true;
|
||||||
|
};
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Variable, useCompile } from '../../schema-component';
|
||||||
|
import { useUserVariable } from './hooks/useUserVariable';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: any;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
collectionName: string;
|
||||||
|
renderSchemaComponent?: (props: any) => any;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
schema: any;
|
||||||
|
operator: any;
|
||||||
|
children: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VariableInput = (props: Props) => {
|
||||||
|
const { value, onChange, renderSchemaComponent: RenderSchemaComponent, style, schema } = props;
|
||||||
|
const compile = useCompile();
|
||||||
|
const userVariable = useUserVariable({ schema, level: 1 });
|
||||||
|
const scope = useMemo(() => {
|
||||||
|
return [
|
||||||
|
userVariable,
|
||||||
|
compile({
|
||||||
|
label: `{{t("Date variables")}}`,
|
||||||
|
value: '$date',
|
||||||
|
key: '$date',
|
||||||
|
disabled: schema['x-component'] !== 'DatePicker',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'now',
|
||||||
|
value: 'now',
|
||||||
|
label: `{{t("Now")}}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Variable.Input value={value} onChange={onChange} scope={scope} style={style}>
|
||||||
|
<RenderSchemaComponent value={value} onChange={onChange} />
|
||||||
|
</Variable.Input>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,152 @@
|
|||||||
|
import { useCompile } from '../../../schema-component';
|
||||||
|
|
||||||
|
export const useDateVariable = ({ operator, schema }) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const operatorValue = operator?.value || '';
|
||||||
|
|
||||||
|
if (!operator || !schema) return null;
|
||||||
|
|
||||||
|
const disabled = !['DatePicker', 'DatePicker.RangePicker'].includes(schema['x-component']);
|
||||||
|
const dateOptions = [
|
||||||
|
{
|
||||||
|
key: 'now',
|
||||||
|
value: 'now',
|
||||||
|
label: `{{t("Now")}}`,
|
||||||
|
disabled: schema['x-component'] !== 'DatePicker' || operatorValue === '$dateBetween',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'yesterday',
|
||||||
|
value: 'yesterday',
|
||||||
|
label: `{{t("Yesterday")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'today',
|
||||||
|
value: 'today',
|
||||||
|
label: `{{t("Today")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tomorrow',
|
||||||
|
value: 'tomorrow',
|
||||||
|
label: `{{t("Tomorrow")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastIsoWeek',
|
||||||
|
value: 'lastIsoWeek',
|
||||||
|
label: `{{t("Last week")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'thisIsoWeek',
|
||||||
|
value: 'thisIsoWeek',
|
||||||
|
label: `{{t("This week")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nextIsoWeek',
|
||||||
|
value: 'nextIsoWeek',
|
||||||
|
label: `{{t("Next week")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastMonth',
|
||||||
|
value: 'lastMonth',
|
||||||
|
label: `{{t("Last month")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'thisMonth',
|
||||||
|
value: 'thisMonth',
|
||||||
|
label: `{{t("This month")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nextMonth',
|
||||||
|
value: 'nextMonth',
|
||||||
|
label: `{{t("Next month")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastQuarter',
|
||||||
|
value: 'lastQuarter',
|
||||||
|
label: `{{t("Last quarter")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'thisQuarter',
|
||||||
|
value: 'thisQuarter',
|
||||||
|
label: `{{t("This quarter")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nextQuarter',
|
||||||
|
value: 'nextQuarter',
|
||||||
|
label: `{{t("Next quarter")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastYear',
|
||||||
|
value: 'lastYear',
|
||||||
|
label: `{{t("Last year")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'thisYear',
|
||||||
|
value: 'thisYear',
|
||||||
|
label: `{{t("This year")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nextYear',
|
||||||
|
value: 'nextYear',
|
||||||
|
label: `{{t("Next year")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'last7Days',
|
||||||
|
value: 'last7Days',
|
||||||
|
label: `{{t("Last 7 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'next7Days',
|
||||||
|
value: 'next7Days',
|
||||||
|
label: `{{t("Next 7 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'last30Days',
|
||||||
|
value: 'last30Days',
|
||||||
|
label: `{{t("Last 30 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'next30Days',
|
||||||
|
value: 'next30Days',
|
||||||
|
label: `{{t("Next 30 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'last90Days',
|
||||||
|
value: 'last90Days',
|
||||||
|
label: `{{t("Last 90 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'next90Days',
|
||||||
|
value: 'next90Days',
|
||||||
|
label: `{{t("Next 90 days")}}`,
|
||||||
|
disabled,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return compile({
|
||||||
|
label: `{{t("Date variables")}}`,
|
||||||
|
value: '$date',
|
||||||
|
key: '$date',
|
||||||
|
disabled: dateOptions.every((option) => option.disabled),
|
||||||
|
children: dateOptions,
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useCompile, useGetFilterOptions } from '../../../schema-component';
|
||||||
|
|
||||||
|
interface GetOptionsParams {
|
||||||
|
schema: any;
|
||||||
|
operator?: string;
|
||||||
|
maxDepth: number;
|
||||||
|
count?: number;
|
||||||
|
getFilterOptions: (collectionName: string) => any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChildren = (options: any[], { schema, operator, maxDepth, count = 1, getFilterOptions }: GetOptionsParams) => {
|
||||||
|
if (count > maxDepth) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = options.map((option) => {
|
||||||
|
if ((option.type !== 'belongsTo' && option.type !== 'hasOne') || !option.target) {
|
||||||
|
return {
|
||||||
|
key: option.name,
|
||||||
|
value: option.name,
|
||||||
|
label: option.title,
|
||||||
|
// TODO: 现在是通过组件的名称来过滤能够被选择的选项,这样的坏处是不够精确,后续可以优化
|
||||||
|
disabled: schema?.['x-component'] !== option.schema?.['x-component'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const children =
|
||||||
|
getChildren(getFilterOptions(option.target), {
|
||||||
|
schema,
|
||||||
|
operator,
|
||||||
|
maxDepth,
|
||||||
|
count: count + 1,
|
||||||
|
getFilterOptions,
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: option.name,
|
||||||
|
value: option.name,
|
||||||
|
label: option.title,
|
||||||
|
children,
|
||||||
|
disabled: children.every((child) => child.disabled),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserVariable = ({ operator, schema, level }: { operator?: any; schema: any; level?: number }) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const getFilterOptions = useGetFilterOptions();
|
||||||
|
|
||||||
|
const children = useMemo(
|
||||||
|
() => getChildren(getFilterOptions('users'), { schema, operator, maxDepth: level || 3, getFilterOptions }) || [],
|
||||||
|
[operator, schema],
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return compile({
|
||||||
|
label: `{{t("Current user")}}`,
|
||||||
|
value: '$user',
|
||||||
|
key: '$user',
|
||||||
|
disabled: children.every((option) => option.disabled),
|
||||||
|
children: children,
|
||||||
|
});
|
||||||
|
}, [children]);
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
import { useValues } from '../../../schema-component/antd/filter/useValues';
|
||||||
|
import { useDateVariable } from './useDateVariable';
|
||||||
|
import { useUserVariable } from './useUserVariable';
|
||||||
|
|
||||||
|
export const useVariableOptions = () => {
|
||||||
|
const { operator, schema } = useValues();
|
||||||
|
const userVariable = useUserVariable({ operator, schema });
|
||||||
|
const dateVariable = useDateVariable({ operator, schema });
|
||||||
|
|
||||||
|
if (!operator || !schema) return [];
|
||||||
|
|
||||||
|
return [userVariable, dateVariable];
|
||||||
|
};
|
@ -3,10 +3,11 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import {
|
import {
|
||||||
GeneralSchemaDesigner,
|
GeneralSchemaDesigner,
|
||||||
SchemaSettings,
|
SchemaSettings,
|
||||||
|
isPatternDisabled,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
useFormBlockContext
|
useFormBlockContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -155,7 +156,7 @@ const Designer = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form && !form?.readPretty && fieldSchema?.['x-component-props']?.['pattern-disable'] != true && (
|
{form && !form?.readPretty && !isPatternDisabled(fieldSchema) && (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
key="pattern"
|
key="pattern"
|
||||||
title={t('Pattern')}
|
title={t('Pattern')}
|
||||||
|
@ -26,7 +26,7 @@ const onTargetFieldChange = (field: Field) => {
|
|||||||
!targetField.getState().disabled && targetField.setValue([]);
|
!targetField.getState().disabled && targetField.setValue([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
function makeFieldsPathOptions(fields, appends = []) {
|
function MakeFieldsPathOptions(fields, appends = []) {
|
||||||
const { getCollection } = useCollectionManager();
|
const { getCollection } = useCollectionManager();
|
||||||
const options = [];
|
const options = [];
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
@ -41,7 +41,7 @@ function makeFieldsPathOptions(fields, appends = []) {
|
|||||||
options.push({
|
options.push({
|
||||||
label: field.uiSchema?.title ?? field.name,
|
label: field.uiSchema?.title ?? field.name,
|
||||||
value: field.name,
|
value: field.name,
|
||||||
children: makeFieldsPathOptions(nextCollection.fields, nextAppends),
|
children: MakeFieldsPathOptions(nextCollection.fields, nextAppends),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -120,7 +120,7 @@ export const snapshot: IField = {
|
|||||||
const { getCollection } = useCollectionManager();
|
const { getCollection } = useCollectionManager();
|
||||||
const { fields } = getCollection(targetCollection);
|
const { fields } = getCollection(targetCollection);
|
||||||
|
|
||||||
const result = makeFieldsPathOptions(fields, appends);
|
const result = MakeFieldsPathOptions(fields, appends);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user