From b1068f2d5154eb06c07185031946fa82009dba92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=9B=A8=E6=B0=B4=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=B0=94-Rairn?= <958414905@qq.com> Date: Sun, 23 Apr 2023 18:27:21 +0800 Subject: [PATCH] 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 --- .../src/block-provider/FormFieldProvider.tsx | 10 +- .../collection-manager/CollectionField.tsx | 14 +- .../Configuration/AddCollectionAction.tsx | 2 +- .../interfaces/multipleSelect.ts | 9 +- .../collection-manager/interfaces/percent.ts | 8 + .../collection-manager/interfaces/select.ts | 9 +- .../association-select/AssociationSelect.tsx | 113 +++++--- .../antd/filter/useFilterActionProps.ts | 116 +++++++- .../schema-component/antd/filter/useValues.ts | 7 +- .../antd/form-item/FormItem.tsx | 267 ++++++++++++------ .../antd/form-item/SchemaSettingOptions.tsx | 7 +- .../antd/form-v2/Templates.tsx | 33 ++- .../antd/icon-picker/IconPicker.tsx | 2 +- .../antd/record-picker/InputRecordPicker.tsx | 1 - .../antd/remote-select/RemoteSelect.tsx | 11 +- .../schema-component/antd/select/Select.tsx | 12 +- .../antd/table-v2/FilterDynamicComponent.tsx | 163 +---------- .../schema-component/antd/table-v2/Table.tsx | 6 +- .../antd/table-v2/hooks/useUserVariable.ts | 56 ---- .../schema-component/antd/variable/Input.tsx | 127 +++++---- .../schema-component/common/utils/uitls.tsx | 44 ++- .../schema-component/hooks/useComponent.ts | 2 +- .../LinkageRules/FilterDynamicComponent.tsx | 9 +- .../src/schema-settings/SchemaSettings.tsx | 21 +- .../VariableInput/VariableInput.tsx | 44 +++ .../VariableInput/hooks/useDateVariable.ts | 152 ++++++++++ .../VariableInput/hooks/useUserVariable.ts | 67 +++++ .../VariableInput/hooks/useVariableOptions.ts | 13 + .../map/src/client/components/Designer.tsx | 5 +- .../snapshot-field/src/client/interface.ts | 6 +- 30 files changed, 878 insertions(+), 458 deletions(-) delete mode 100644 packages/core/client/src/schema-component/antd/table-v2/hooks/useUserVariable.ts create mode 100644 packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx create mode 100644 packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts create mode 100644 packages/core/client/src/schema-settings/VariableInput/hooks/useUserVariable.ts create mode 100644 packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts diff --git a/packages/core/client/src/block-provider/FormFieldProvider.tsx b/packages/core/client/src/block-provider/FormFieldProvider.tsx index 989363513..8086ff115 100644 --- a/packages/core/client/src/block-provider/FormFieldProvider.tsx +++ b/packages/core/client/src/block-provider/FormFieldProvider.tsx @@ -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; diff --git a/packages/core/client/src/collection-manager/CollectionField.tsx b/packages/core/client/src/collection-manager/CollectionField.tsx index af8029c6c..c2c479ac5 100644 --- a/packages/core/client/src/collection-manager/CollectionField.tsx +++ b/packages/core/client/src/collection-manager/CollectionField.tsx @@ -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(); 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 = () => { diff --git a/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx index 7c1ed3527..3129772e8 100644 --- a/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/AddCollectionAction.tsx @@ -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) { diff --git a/packages/core/client/src/collection-manager/interfaces/multipleSelect.ts b/packages/core/client/src/collection-manager/interfaces/multipleSelect.ts index e4ecd8711..4bf845fec 100644 --- a/packages/core/client/src/collection-manager/interfaces/multipleSelect.ts +++ b/packages/core/client/src/collection-manager/interfaces/multipleSelect.ts @@ -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; } }, }; diff --git a/packages/core/client/src/collection-manager/interfaces/percent.ts b/packages/core/client/src/collection-manager/interfaces/percent.ts index 7aaef4981..35d42452f 100644 --- a/packages/core/client/src/collection-manager/interfaces/percent.ts +++ b/packages/core/client/src/collection-manager/interfaces/percent.ts @@ -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: { diff --git a/packages/core/client/src/collection-manager/interfaces/select.ts b/packages/core/client/src/collection-manager/interfaces/select.ts index 605c8e7f5..d332f03f4 100644 --- a/packages/core/client/src/collection-manager/interfaces/select.ts +++ b/packages/core/client/src/collection-manager/interfaces/select.ts @@ -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; } }, }; diff --git a/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx index f933b3202..1319e459f 100644 --- a/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-select/AssociationSelect.tsx @@ -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(); const fieldSchema = useFieldSchema(); @@ -390,41 +390,52 @@ AssociationSelect.Designer = function Designer() { }} /> )} - {form && !form?.readPretty && collectionField?.uiSchema?.type && ( - { - 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 && ( )} + {form && + !form?.readPretty && + ['o2m', 'm2m'].includes(collectionField.interface) && + fieldSchema['x-component'] !== 'TableField' && ( + { + 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(); + }} + /> + )} - {form && !form?.readPretty && fieldSchema?.['x-component-props']?.['pattern-disable'] != true && ( + {form && !form?.readPretty && !isPatternDisabled(fieldSchema) && ( { +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 || []; diff --git a/packages/core/client/src/schema-component/antd/filter/useValues.ts b/packages/core/client/src/schema-component/antd/filter/useValues.ts index 2a0f736a9..643975c5d 100644 --- a/packages/core/client/src/schema-component/antd/filter/useValues.ts +++ b/packages/core/client/src/schema-component/antd/filter/useValues.ts @@ -22,7 +22,8 @@ const findOption = (dataIndex = [], options) => { export const useValues = () => { const field = useField(); - 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]; diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index f15bdb99f..88806f22d 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -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(); 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 && ( - + ); + }, + }, + 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 && ( )} + {form && + !form?.readPretty && + ['o2m', 'm2m'].includes(collectionField.interface) && + fieldSchema['x-component'] !== 'TableField' && ( + { + 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' && ( )} - {form && - !form?.readPretty && - collectionField?.interface !== 'o2m' && - fieldSchema?.['x-component-props']?.['pattern-disable'] != true && ( - { - const schema: ISchema = { - ['x-uid']: fieldSchema['x-uid'], - }; + {form && !form?.readPretty && collectionField?.interface !== 'o2m' && !isPatternDisabled(fieldSchema) && ( + { + 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' && ( { 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) ? ( { 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(); } diff --git a/packages/core/client/src/schema-component/antd/icon-picker/IconPicker.tsx b/packages/core/client/src/schema-component/antd/icon-picker/IconPicker.tsx index c63fd3e77..cf183b494 100644 --- a/packages/core/client/src/schema-component/antd/icon-picker/IconPicker.tsx +++ b/packages/core/client/src/schema-component/antd/icon-picker/IconPicker.tsx @@ -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(); diff --git a/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx b/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx index 49c4d19aa..f2636a0fd 100644 --- a/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx +++ b/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx @@ -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); diff --git a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx index a30817e86..df6b2932d 100644 --- a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx +++ b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx @@ -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, diff --git a/packages/core/client/src/schema-component/antd/select/Select.tsx b/packages/core/client/src/schema-component/antd/select/Select.tsx index 34760c038..e434abeee 100644 --- a/packages/core/client/src/schema-component/antd/select/Select.tsx +++ b/packages/core/client/src/schema-component/antd/select/Select.tsx @@ -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 ; + return ; } return ( { props.onChange?.(changed === undefined ? null : changed); diff --git a/packages/core/client/src/schema-component/antd/table-v2/FilterDynamicComponent.tsx b/packages/core/client/src/schema-component/antd/table-v2/FilterDynamicComponent.tsx index 084033b47..258f65237 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/FilterDynamicComponent.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/FilterDynamicComponent.tsx @@ -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 ( - + {renderSchemaComponent()} ); diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index 9bb8aae86..b244a916c 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -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]); diff --git a/packages/core/client/src/schema-component/antd/table-v2/hooks/useUserVariable.ts b/packages/core/client/src/schema-component/antd/table-v2/hooks/useUserVariable.ts deleted file mode 100644 index 0bb8007a0..000000000 --- a/packages/core/client/src/schema-component/antd/table-v2/hooks/useUserVariable.ts +++ /dev/null @@ -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, - }; -}; diff --git a/packages/core/client/src/schema-component/antd/variable/Input.tsx b/packages/core/client/src/schema-component/antd/variable/Input.tsx index fb5f9059a..3bdeaca90 100644 --- a/packages/core/client/src/schema-component/antd/variable/Input.tsx +++ b/packages/core/client/src/schema-component/antd/variable/Input.tsx @@ -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 ( - {variable ? ( -
+
+ {variable ? (
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; + } + } + `} > - - {variableText} - -
- {!disabled ? ( - onChange(null)} +
e.preventDefault()} + onKeyDown={(e) => { + if (e.key !== 'Backspace') { + e.preventDefault(); + return; + } + onChange(null); + }} + className={cx('ant-input', { 'ant-input-disabled': disabled })} + contentEditable={!disabled} + suppressContentEditableWarning > - - - ) : null} -
- ) : children ?? } + + {variableText} + +
+ {!disabled ? ( + onChange(null)} + > + + + ) : null} +
+ ) : ( + children ?? + )} + {options.length > 1 ? ( - {button ?? ( - - )} + {button ?? } ) : null}
diff --git a/packages/core/client/src/schema-component/common/utils/uitls.tsx b/packages/core/client/src/schema-component/common/utils/uitls.tsx index c0b3b9818..ea241c152 100644 --- a/packages/core/client/src/schema-component/common/utils/uitls.tsx +++ b/packages/core/client/src/schema-component/common/utils/uitls.tsx @@ -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; + $date: Record; +}; + +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) { diff --git a/packages/core/client/src/schema-component/hooks/useComponent.ts b/packages/core/client/src/schema-component/hooks/useComponent.ts index f4849298b..880c04525 100644 --- a/packages/core/client/src/schema-component/hooks/useComponent.ts +++ b/packages/core/client/src/schema-component/hooks/useComponent.ts @@ -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; }; diff --git a/packages/core/client/src/schema-settings/LinkageRules/FilterDynamicComponent.tsx b/packages/core/client/src/schema-settings/LinkageRules/FilterDynamicComponent.tsx index 71fc8a0ac..4b354ee08 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/FilterDynamicComponent.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/FilterDynamicComponent.tsx @@ -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 ( diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 3f0f2af58..a863e44da 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -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; +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx b/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx new file mode 100644 index 000000000..355d87d87 --- /dev/null +++ b/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx @@ -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 ( + + + + ); +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts new file mode 100644 index 000000000..a80d44039 --- /dev/null +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts @@ -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, + }); +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useUserVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useUserVariable.ts new file mode 100644 index 000000000..95de69d17 --- /dev/null +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useUserVariable.ts @@ -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]); +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts new file mode 100644 index 000000000..83f5f269e --- /dev/null +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts @@ -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]; +}; diff --git a/packages/plugins/map/src/client/components/Designer.tsx b/packages/plugins/map/src/client/components/Designer.tsx index 1b725664b..157ef701b 100644 --- a/packages/plugins/map/src/client/components/Designer.tsx +++ b/packages/plugins/map/src/client/components/Designer.tsx @@ -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) && ( { !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 [ {