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 { useField } from '@formily/react';
|
||||
import { autorun } from '@formily/reactive';
|
||||
import { forEach } from '@nocobase/utils/client';
|
||||
import { Spin } from 'antd';
|
||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||
import { RecordProvider } from '../record-provider';
|
||||
@ -35,7 +36,14 @@ const InternalFormFieldProvider = (props) => {
|
||||
// 当使用数据模板时,会 formBlockCtx.form.values 会被整体赋值,这时候需要同步到 form.values
|
||||
useEffect(() => {
|
||||
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;
|
||||
|
@ -7,12 +7,18 @@ import { useActionContext, useCompile, useComponent, useFormBlockContext, useRec
|
||||
import { CollectionFieldProvider } from './CollectionFieldProvider';
|
||||
import { useCollectionField } from './hooks';
|
||||
|
||||
type Props = {
|
||||
component: any;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
// TODO: 初步适配
|
||||
const InternalField: React.FC = (props) => {
|
||||
const InternalField: React.FC = (props: Props) => {
|
||||
const { component } = props;
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { uiSchema, defaultValue } = useCollectionField();
|
||||
const component = useComponent(uiSchema?.['x-component'] || 'Input');
|
||||
const Component = useComponent(component || uiSchema?.['x-component'] || 'Input');
|
||||
const compile = useCompile();
|
||||
const setFieldProps = (key, value) => {
|
||||
field[key] = typeof field[key] === 'undefined' ? value : field[key];
|
||||
@ -57,12 +63,12 @@ const InternalField: React.FC = (props) => {
|
||||
field.dataSource = uiSchema.enum;
|
||||
const originalProps = compile(uiSchema['x-component-props']) || {};
|
||||
const componentProps = merge(originalProps, field.componentProps || {});
|
||||
field.component = [component, componentProps];
|
||||
field.component = [Component, componentProps];
|
||||
}, [JSON.stringify(uiSchema)]);
|
||||
if (!uiSchema) {
|
||||
return null;
|
||||
}
|
||||
return React.createElement(component, props, props.children);
|
||||
return React.createElement(Component, props, props.children);
|
||||
};
|
||||
|
||||
export const InternalFallbackField = () => {
|
||||
|
@ -13,8 +13,8 @@ import { useResourceActionContext, useResourceContext } from '../ResourceActionP
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import * as components from './components';
|
||||
import { templateOptions } from './templates';
|
||||
import { TemplateSummay } from './components/TemplateSummay';
|
||||
import { templateOptions } from './templates';
|
||||
|
||||
const getSchema = (schema, category, compile): ISchema => {
|
||||
if (!schema) {
|
||||
|
@ -32,9 +32,14 @@ export const multipleSelect: IField = {
|
||||
operators: operators.array,
|
||||
},
|
||||
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)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
schema['x-component-props']['ellipsis'] = true;
|
||||
props['ellipsis'] = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { registerValidateRules } from '@formily/core';
|
||||
import { ISchema } from '@formily/react';
|
||||
import { i18n } from '../../i18n';
|
||||
import { defaultProps, operators, unique } from './properties';
|
||||
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'],
|
||||
hasDefaultValue: true,
|
||||
properties: {
|
||||
|
@ -29,9 +29,14 @@ export const select: IField = {
|
||||
operators: operators.enumType,
|
||||
},
|
||||
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)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
schema['x-component-props']['ellipsis'] = true;
|
||||
props['ellipsis'] = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
useSortFields,
|
||||
} from '../../../collection-manager';
|
||||
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 { removeNullCondition } from '../filter';
|
||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||
@ -94,7 +94,7 @@ export const AssociationSelect = InternalAssociationSelect as unknown as Associa
|
||||
|
||||
AssociationSelect.Designer = function Designer() {
|
||||
const { getCollectionFields, getInterface, getCollectionJoinField, getCollection } = useCollectionManager();
|
||||
const { getField, template } = useCollection();
|
||||
const { getField } = useCollection();
|
||||
const { form } = useFormBlockContext();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
@ -390,41 +390,52 @@ AssociationSelect.Designer = function Designer() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{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,
|
||||
{form &&
|
||||
!form?.readPretty &&
|
||||
isShowDefaultValue(collectionField, getInterface) &&
|
||||
!isPatternDisabled(fieldSchema) && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set default value')}
|
||||
components={{ ArrayCollapse, FormLayout }}
|
||||
width={800}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set default value'),
|
||||
properties: {
|
||||
default: {
|
||||
...(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
|
||||
}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = v.default;
|
||||
} as ISchema
|
||||
}
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
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 && !isSubFormAssociationField && fieldComponentOptions && (
|
||||
<SchemaSettings.SelectItem
|
||||
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
|
||||
title={t('Set the data scope')}
|
||||
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
|
||||
key="pattern"
|
||||
title={t('Pattern')}
|
||||
|
@ -7,13 +7,66 @@ import { useBlockRequestContext } from '../../../block-provider';
|
||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||
|
||||
export const useFilterOptions = (collectionName: string) => {
|
||||
export const useGetFilterOptions = () => {
|
||||
const { getCollectionFields } = useCollectionManager();
|
||||
const fields = getCollectionFields(collectionName);
|
||||
const options=useFilterFieldOptions(fields)
|
||||
const compile = useCompile();
|
||||
const { getChildrenCollections } = useCollectionManager();
|
||||
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);
|
||||
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
||||
options.push({
|
||||
@ -54,6 +107,63 @@ export const useFilterOptions = (collectionName: string) => {
|
||||
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) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const nonfilterable = fieldSchema?.['x-component-props']?.nonfilterable || [];
|
||||
|
@ -22,7 +22,8 @@ const findOption = (dataIndex = [], options) => {
|
||||
|
||||
export const useValues = () => {
|
||||
const field = useField<any>();
|
||||
const { options } = useContext(FilterContext);
|
||||
const { options } = useContext(FilterContext) || {};
|
||||
|
||||
const data2value = () => {
|
||||
field.value = flat.unflatten({
|
||||
[`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
|
||||
@ -32,7 +33,7 @@ export const useValues = () => {
|
||||
field.data = field.data || {};
|
||||
const values = flat(field.value);
|
||||
const path = Object.keys(values).shift() || '';
|
||||
if (!path) {
|
||||
if (!path || !options) {
|
||||
return;
|
||||
}
|
||||
const [fieldPath = '', otherPath = ''] = path.split('.$');
|
||||
@ -50,7 +51,7 @@ export const useValues = () => {
|
||||
useEffect(value2data, [field.path.entire]);
|
||||
return {
|
||||
fields: options,
|
||||
...field.data,
|
||||
...(field?.data || {}),
|
||||
setDataIndex(dataIndex) {
|
||||
const option = findOption(dataIndex, options);
|
||||
const operator = option?.operators?.[0];
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ArrayCollapse, FormLayout, FormItem as Item } from '@formily/antd';
|
||||
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 _ from 'lodash';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
||||
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 { 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 { BlockItem } from '../block-item';
|
||||
import { HTMLEncode } from '../input/shared';
|
||||
@ -33,15 +36,20 @@ const divWrap = (schema: ISchema) => {
|
||||
export const FormItem: any = observer((props: any) => {
|
||||
useEnsureOperatorsValid();
|
||||
|
||||
const field = useField();
|
||||
const field = useField<Field>();
|
||||
const ctx = useContext(BlockRequestContext);
|
||||
const schema = useFieldSchema();
|
||||
const variablesCtx = useVariablesCtx();
|
||||
|
||||
useEffect(() => {
|
||||
if (ctx?.block === 'form') {
|
||||
ctx.field.data = ctx.field.data || {};
|
||||
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
|
||||
ctx.field.data.activeFields.add(schema.name);
|
||||
// 如果默认值是一个变量,则需要解析之后再显示出来
|
||||
if (isVariable(schema?.default)) {
|
||||
field.setInitialValue?.(parseVariables(schema.default, variablesCtx));
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
@ -81,6 +89,8 @@ FormItem.Designer = function Designer() {
|
||||
const { t } = useTranslation();
|
||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||
const compile = useCompile();
|
||||
const variablesCtx = useVariablesCtx();
|
||||
|
||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||
const targetCollection = getCollection(collectionField?.target);
|
||||
const interfaceConfig = getInterface(collectionField?.interface);
|
||||
@ -396,41 +406,91 @@ FormItem.Designer = function Designer() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{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,
|
||||
{form &&
|
||||
!form?.readPretty &&
|
||||
isShowDefaultValue(collectionField, getInterface) &&
|
||||
!isPatternDisabled(fieldSchema) && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set default value')}
|
||||
components={{ ArrayCollapse, FormLayout, VariableInput }}
|
||||
width={800}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set default value'),
|
||||
properties: {
|
||||
// 关系字段不支持设置变量
|
||||
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
|
||||
}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = v.default;
|
||||
} as ISchema
|
||||
}
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
onSubmit={(v) => {
|
||||
const schema: ISchema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
if (field.value !== v.default) {
|
||||
field.value = parseVariables(v.default, variablesCtx);
|
||||
}
|
||||
fieldSchema.default = v.default;
|
||||
schema.default = v.default;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form && !isSubFormAssociationField && fieldComponentOptions && (
|
||||
<SchemaSettings.SelectItem
|
||||
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' && (
|
||||
<SchemaSettings.SwitchItem
|
||||
title={t('Enable link')}
|
||||
@ -497,61 +587,58 @@ FormItem.Designer = function Designer() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{form &&
|
||||
!form?.readPretty &&
|
||||
collectionField?.interface !== 'o2m' &&
|
||||
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'],
|
||||
};
|
||||
{form && !form?.readPretty && collectionField?.interface !== 'o2m' && !isPatternDisabled(fieldSchema) && (
|
||||
<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;
|
||||
}
|
||||
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.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{options.length > 0 && fieldSchema['x-component'] === 'CollectionField' && (
|
||||
<SchemaSettings.SelectItem
|
||||
key="title-field"
|
||||
@ -597,3 +684,7 @@ function isFileCollection(collection: Collection) {
|
||||
}
|
||||
|
||||
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 { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||
import { SchemaSettings } from '../../../schema-settings';
|
||||
import { SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||
import { useOperatorList } from '../filter/useOperators';
|
||||
|
||||
@ -448,10 +448,7 @@ export const EditPattern = () => {
|
||||
readOnlyMode = 'read-pretty';
|
||||
}
|
||||
|
||||
return form &&
|
||||
!form?.readPretty &&
|
||||
collectionField?.interface !== 'o2m' &&
|
||||
fieldSchema?.['x-component-props']?.['pattern-disable'] != true ? (
|
||||
return form && !form?.readPretty && collectionField?.interface !== 'o2m' && !isPatternDisabled(fieldSchema) ? (
|
||||
<SchemaSettings.SelectItem
|
||||
key="pattern"
|
||||
title={t('Pattern')}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { forEach } from '@nocobase/utils/client';
|
||||
import { Select } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient } from '../../../api-client';
|
||||
import { findFormBlock } from '../../../block-provider';
|
||||
import { useCollectionManager } from '../../../collection-manager';
|
||||
|
||||
interface ITemplate {
|
||||
items: {
|
||||
@ -40,26 +42,41 @@ const useDataTemplates = () => {
|
||||
|
||||
export const Templates = ({ style = {}, form }) => {
|
||||
const { templates, display, enabled, defaultTemplate } = useDataTemplates();
|
||||
const { getCollectionField } = useCollectionManager();
|
||||
const [value, setValue] = React.useState(defaultTemplate?.key || 'none');
|
||||
const api = useAPIClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultTemplate) {
|
||||
fetchTemplateData(api, defaultTemplate).then((data) => {
|
||||
if (form) {
|
||||
form.values = data;
|
||||
}
|
||||
});
|
||||
fetchTemplateData(api, defaultTemplate)
|
||||
.then((data) => {
|
||||
if (form) {
|
||||
forEach(data, (value, key) => {
|
||||
if (value) {
|
||||
form.values[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = useCallback(async (value, option) => {
|
||||
setValue(value);
|
||||
if (option.key !== 'none') {
|
||||
if (form) {
|
||||
form.values = await fetchTemplateData(api, option);
|
||||
}
|
||||
fetchTemplateData(api, option).then((data) => {
|
||||
if (form) {
|
||||
forEach(data, (value, key) => {
|
||||
if (value) {
|
||||
form.values[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
form?.reset();
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { isValid } from '@formily/shared';
|
||||
import { Button, Input, Popover } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { hasIcon, Icon, icons } from '../../../icon';
|
||||
import { Icon, hasIcon, icons } from '../../../icon';
|
||||
|
||||
function IconField(props: any) {
|
||||
const layout = useFormLayout();
|
||||
|
@ -236,7 +236,6 @@ const Drawer: React.FunctionComponent<{
|
||||
fieldSchema,
|
||||
options,
|
||||
}) => {
|
||||
console.log('collectionField', options);
|
||||
const getFilter = () => {
|
||||
const targetKey = collectionField.targetKey || 'id';
|
||||
const list = options.map((option) => option[targetKey]).filter(Boolean);
|
||||
|
@ -38,7 +38,7 @@ const InternalRemoteSelect = connect(
|
||||
const firstRun = useRef(false);
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const { getCollectionJoinField, getInterface } = useCollectionManager();
|
||||
const collectionField = getField(fieldSchema.name);
|
||||
const targetField =
|
||||
_targetField ||
|
||||
@ -46,6 +46,13 @@ const InternalRemoteSelect = connect(
|
||||
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(
|
||||
(options) => {
|
||||
try {
|
||||
@ -138,7 +145,7 @@ const InternalRemoteSelect = connect(
|
||||
filter: mergeFilter([
|
||||
{
|
||||
[fieldNames.label]: {
|
||||
$includes: search,
|
||||
[operator]: search,
|
||||
},
|
||||
},
|
||||
service?.params?.filter,
|
||||
|
@ -71,24 +71,18 @@ const InternalSelect = connect(
|
||||
(props: Props) => {
|
||||
const { objectValue, value, ...others } = props;
|
||||
let mode: any = props.multiple ? 'multiple' : props.mode;
|
||||
if (mode === 'links') {
|
||||
if (mode && !['multiple', 'tags'].includes(mode)) {
|
||||
mode = undefined;
|
||||
}
|
||||
const toValue = (v) => {
|
||||
if (['multiple', 'tags'].includes(mode)) {
|
||||
return v || [];
|
||||
}
|
||||
return v;
|
||||
};
|
||||
if (objectValue) {
|
||||
return <ObjectSelect {...others} value={toValue(value)} mode={mode} />;
|
||||
return <ObjectSelect {...others} value={value} mode={mode} />;
|
||||
}
|
||||
return (
|
||||
<AntdSelect
|
||||
showSearch
|
||||
filterOption={filterOption}
|
||||
allowClear
|
||||
value={toValue(value)}
|
||||
value={value}
|
||||
{...others}
|
||||
onChange={(changed) => {
|
||||
props.onChange?.(changed === undefined ? null : changed);
|
||||
|
@ -1,172 +1,13 @@
|
||||
import React from 'react';
|
||||
import { useCompile } from '../..';
|
||||
import { useValues } from '../filter/useValues';
|
||||
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
|
||||
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) {
|
||||
const { value, onChange, renderSchemaComponent } = props;
|
||||
const options = useVariableOptions();
|
||||
const compile = useCompile();
|
||||
const scope = compile(options);
|
||||
|
||||
return (
|
||||
<Variable.Input value={value} onChange={onChange} scope={scope}>
|
||||
<Variable.Input value={value} onChange={onChange} scope={options}>
|
||||
{renderSchemaComponent()}
|
||||
</Variable.Input>
|
||||
);
|
||||
|
@ -4,10 +4,10 @@ import { css } from '@emotion/css';
|
||||
import { ArrayField, Field } from '@formily/core';
|
||||
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { useEventListener, useMemoizedFn } from 'ahooks';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||
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 { DndContext, useDesignable, useTableSize } from '../..';
|
||||
import {
|
||||
@ -213,7 +213,7 @@ export const Table: any = observer((props: any) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (treeTable !== false) {
|
||||
const keys = getIdsWithChildren(field.value?.slice());
|
||||
const keys = getIdsWithChildren(field.value?.slice?.());
|
||||
setAllIncludesChildren(keys);
|
||||
}
|
||||
}, [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 { css, cx } from '@emotion/css';
|
||||
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 React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -117,7 +117,7 @@ export function Input(props) {
|
||||
const compile = useCompile();
|
||||
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 isConstant = typeof parsed === 'string';
|
||||
const type = isConstant ? parsed : '';
|
||||
@ -171,6 +171,7 @@ export function Input(props) {
|
||||
return (
|
||||
<AntInput.Group
|
||||
compact
|
||||
style={style}
|
||||
className={css`
|
||||
width: auto;
|
||||
display: flex !important;
|
||||
@ -186,67 +187,71 @@ export function Input(props) {
|
||||
}
|
||||
`}
|
||||
>
|
||||
{variable ? (
|
||||
<div
|
||||
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 style={{ flex: 1 }}>
|
||||
{variable ? (
|
||||
<div
|
||||
onInput={(e) => e.preventDefault()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Backspace') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
className={css`
|
||||
position: relative;
|
||||
line-height: 0;
|
||||
|
||||
&:hover {
|
||||
.ant-select-clear {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
onChange(null);
|
||||
}}
|
||||
className={cx('ant-input', { 'ant-input-disabled': disabled })}
|
||||
contentEditable={!disabled}
|
||||
suppressContentEditableWarning
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Tag contentEditable={false} color="blue">
|
||||
{variableText}
|
||||
</Tag>
|
||||
</div>
|
||||
{!disabled ? (
|
||||
<span
|
||||
className={cx(
|
||||
'ant-select-clear',
|
||||
css`
|
||||
user-select: 'none';
|
||||
`,
|
||||
)}
|
||||
unselectable="on"
|
||||
aria-hidden
|
||||
onClick={() => onChange(null)}
|
||||
<div
|
||||
onInput={(e) => e.preventDefault()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Backspace') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
onChange(null);
|
||||
}}
|
||||
className={cx('ant-input', { 'ant-input-disabled': disabled })}
|
||||
contentEditable={!disabled}
|
||||
suppressContentEditableWarning
|
||||
>
|
||||
<CloseCircleFilled />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : children ?? <ConstantComponent value={value} onChange={onChange} />}
|
||||
<Tag contentEditable={false} color="blue">
|
||||
{variableText}
|
||||
</Tag>
|
||||
</div>
|
||||
{!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 ? (
|
||||
<Cascader
|
||||
options={options}
|
||||
@ -254,9 +259,7 @@ export function Input(props) {
|
||||
onChange={onSwitch}
|
||||
changeOnSelect
|
||||
>
|
||||
{button ?? (
|
||||
<XButton type={variable ? 'primary' : 'default'} />
|
||||
)}
|
||||
{button ?? <XButton type={variable ? 'primary' : 'default'} />}
|
||||
</Cascader>
|
||||
) : null}
|
||||
</AntInput.Group>
|
||||
|
@ -1,7 +1,46 @@
|
||||
import { every, some, findIndex } from 'lodash';
|
||||
import flat from 'flat';
|
||||
import _, { every, findIndex, some } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { useCurrentUserContext } from '../../../user';
|
||||
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) {
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return null;
|
||||
@ -17,7 +56,8 @@ function getInnermostKeyAndValue(obj) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const getValue = (str, values) => {
|
||||
|
||||
const getValue = (str: string, values) => {
|
||||
const regex = /{{(.*?)}}/;
|
||||
const matches = str?.match?.(regex);
|
||||
if (matches) {
|
||||
|
@ -3,12 +3,12 @@ import { get } from 'lodash';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useComponent = (component: any, defaults?: any) => {
|
||||
const { components } = useContext(SchemaOptionsContext);
|
||||
if (!component) {
|
||||
return defaults;
|
||||
}
|
||||
if (typeof component !== 'string') {
|
||||
return component;
|
||||
}
|
||||
const { components } = useContext(SchemaOptionsContext);
|
||||
return get(components, component) || defaults;
|
||||
};
|
||||
|
@ -2,7 +2,14 @@ import React from 'react';
|
||||
import { Variable } from '../../schema-component';
|
||||
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 scope = useVariableOptions(collectionName);
|
||||
return (
|
||||
|
@ -25,6 +25,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
APIClientProvider,
|
||||
ActionContext,
|
||||
CollectionFieldOptions,
|
||||
CollectionManagerContext,
|
||||
Designable,
|
||||
FormProvider,
|
||||
@ -35,10 +36,10 @@ import {
|
||||
findFormBlock,
|
||||
useAPIClient,
|
||||
useCollection,
|
||||
useLinkageCollectionFilterOptions,
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
useDesignable,
|
||||
useLinkageCollectionFilterOptions,
|
||||
} from '..';
|
||||
import { findFilterTargets, updateFilterTargets } from '../block-provider/hooks';
|
||||
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 {
|
||||
GeneralSchemaDesigner,
|
||||
SchemaSettings,
|
||||
isPatternDisabled,
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useDesignable,
|
||||
useFormBlockContext
|
||||
useFormBlockContext,
|
||||
} from '@nocobase/client';
|
||||
import set from 'lodash/set';
|
||||
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
|
||||
key="pattern"
|
||||
title={t('Pattern')}
|
||||
|
@ -26,7 +26,7 @@ const onTargetFieldChange = (field: Field) => {
|
||||
!targetField.getState().disabled && targetField.setValue([]);
|
||||
};
|
||||
|
||||
function makeFieldsPathOptions(fields, appends = []) {
|
||||
function MakeFieldsPathOptions(fields, appends = []) {
|
||||
const { getCollection } = useCollectionManager();
|
||||
const options = [];
|
||||
fields.forEach((field) => {
|
||||
@ -41,7 +41,7 @@ function makeFieldsPathOptions(fields, appends = []) {
|
||||
options.push({
|
||||
label: field.uiSchema?.title ?? field.name,
|
||||
value: field.name,
|
||||
children: makeFieldsPathOptions(nextCollection.fields, nextAppends),
|
||||
children: MakeFieldsPathOptions(nextCollection.fields, nextAppends),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -120,7 +120,7 @@ export const snapshot: IField = {
|
||||
const { getCollection } = useCollectionManager();
|
||||
const { fields } = getCollection(targetCollection);
|
||||
|
||||
const result = makeFieldsPathOptions(fields, appends);
|
||||
const result = MakeFieldsPathOptions(fields, appends);
|
||||
|
||||
return [
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user