diff --git a/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx b/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx index 3cb1a80436..018818a31f 100644 --- a/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx +++ b/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx @@ -34,20 +34,6 @@ export interface SchemaSettingsChildrenProps { children: SchemaSettingsItemType[]; } -const typeComponentMap = { - item: SchemaSettingsItem, - itemGroup: SchemaSettingsItemGroup, - subMenu: SchemaSettingsSubMenu, - divider: SchemaSettingsDivider, - remove: SchemaSettingsRemove, - select: SchemaSettingsSelectItem, - cascader: SchemaSettingsCascaderItem, - switch: SchemaSettingsSwitchItem, - popup: SchemaSettingsPopupItem, - actionModal: SchemaSettingsActionModalItem, - modal: SchemaSettingsModalItem, -}; - const SchemaSettingsChildErrorFallback: FC< FallbackProps & { title: string; @@ -113,6 +99,19 @@ export const SchemaSettingsChild: FC = (props) => { hideIfNoChildren, componentProps, } = props as any; + const typeComponentMap = { + item: SchemaSettingsItem, + itemGroup: SchemaSettingsItemGroup, + subMenu: SchemaSettingsSubMenu, + divider: SchemaSettingsDivider, + remove: SchemaSettingsRemove, + select: SchemaSettingsSelectItem, + cascader: SchemaSettingsCascaderItem, + switch: SchemaSettingsSwitchItem, + popup: SchemaSettingsPopupItem, + actionModal: SchemaSettingsActionModalItem, + modal: SchemaSettingsModalItem, + }; const useChildrenRes = useChildren(); const useComponentPropsRes = useComponentProps(); const findComponent = useFindComponent(); diff --git a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx index 704c7156f7..2047aeb971 100644 --- a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx +++ b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx @@ -22,8 +22,15 @@ import { useCreateFormBlockProps } from '../modules/blocks/data-blocks/form/hook import { useEditFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockDecoratorProps'; import { useEditFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockProps'; import { useDataFormItemProps } from '../modules/blocks/data-blocks/form/hooks/useDataFormItemProps'; -import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps'; -import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps'; +import { + useGridCardBlockDecoratorProps, + useGridCardBlockItemProps, + useGridCardBlockProps, +} from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps'; +import { + useListBlockDecoratorProps, + useListBlockProps, +} from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps'; import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/table-selector/hooks/useTableSelectorDecoratorProps'; import { TableColumnSchemaToolbar } from '../modules/blocks/data-blocks/table/TableColumnSchemaToolbar'; import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps'; @@ -80,11 +87,14 @@ export const BlockSchemaComponentProvider: React.FC = (props) => { useTableSelectorProps, useTableBlockDecoratorProps, useListBlockDecoratorProps, + useListBlockProps, useTableSelectorDecoratorProps, useCollapseBlockDecoratorProps, useFilterFormBlockProps, useFilterFormBlockDecoratorProps, useGridCardBlockDecoratorProps, + useGridCardBlockItemProps, + useGridCardBlockProps, useFormItemProps, useDataFormItemProps, }} @@ -141,11 +151,14 @@ export class BlockSchemaComponentPlugin extends Plugin { useTableSelectorProps, useTableBlockDecoratorProps, useListBlockDecoratorProps, + useListBlockProps, useTableSelectorDecoratorProps, useCollapseBlockDecoratorProps, useFilterFormBlockProps, useFilterFormBlockDecoratorProps, useGridCardBlockDecoratorProps, + useGridCardBlockProps, + useGridCardBlockItemProps, useFormItemProps, useDataFormItemProps, }); diff --git a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx index 89a9786308..1102d5825c 100644 --- a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx +++ b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx @@ -23,6 +23,7 @@ import { import { CollectionRecord } from '../collection-record'; import { BlockRequestProvider } from './DataBlockRequestProvider'; import { DataBlockResourceProvider } from './DataBlockResourceProvider'; +import { BlockLinkageRuleProvider } from '../../modules/blocks/BlockLinkageRuleProvider'; export interface AllDataBlockProps { collection: string | CollectionOptions; @@ -189,13 +190,15 @@ export const DataBlockProvider: FC> = withDynamicSche - - - - {children} - - - + + + + + {children} + + + + diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 0ef6fe4e5d..ec207c8d53 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1106,5 +1106,7 @@ "No pages yet, please configure first": "暂无页面,请先配置", "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式", "After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。", + "Block Linkage rules":"区块联动规则", + "Field Linkage rules":"字段联动规则", "Reset link expiration": "重置链接有效期" } diff --git a/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts b/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts index 8b2a31eddb..0212e4abe5 100644 --- a/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts +++ b/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts @@ -33,8 +33,9 @@ test.describe('Link', () => { ).toHaveCount(1); await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover(); await page.getByRole('menuitem', { name: 'Edit link' }).click(); + await page.getByLabel('block-item-users-URL').getByLabel('textbox').click(); await page - .getByLabel('block-item-users-table-URL') + .getByLabel('block-item-users-URL') .getByLabel('textbox') .fill(await nocoPage.getUrl()); await page.getByPlaceholder('Name').fill('id'); @@ -102,7 +103,7 @@ test.describe('Link', () => { await page.getByLabel('action-Action.Link-Link-').hover(); await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:link-users').hover(); await page.getByRole('menuitem', { name: 'Edit link' }).click(); - await page.getByLabel('block-item-users-table-URL').getByLabel('textbox').fill(otherPageUrl); + await page.getByLabel('block-item-users-URL').getByLabel('textbox').fill(otherPageUrl); await page.getByRole('button', { name: 'OK', exact: true }).click(); await page.getByLabel('action-Action.Link-Link-').click(); diff --git a/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx b/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx index 9c4d55b34a..886af15e42 100644 --- a/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx +++ b/packages/core/client/src/modules/actions/add-new/addNewActionSettings.tsx @@ -69,6 +69,21 @@ export const addNewActionSettings = new SchemaSettings({ return isChildCollectionAction; }, }, + { + name: 'linkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { association } = useDataBlockProps() || {}; + const { name } = useCollection_deprecated(); + const { getCollectionField } = useCollectionManager_deprecated(); + const associationField = getCollectionField(association); + const { linkageRulesProps } = useSchemaToolbar(); + return { + ...linkageRulesProps, + collectionName: associationField?.collectionName || name, + }; + }, + }, { name: 'delete', sort: 100, diff --git a/packages/core/client/src/modules/actions/bulk-destroy/bulkDeleteActionSettings.tsx b/packages/core/client/src/modules/actions/bulk-destroy/bulkDeleteActionSettings.tsx index f5c2ab2409..8597a75659 100644 --- a/packages/core/client/src/modules/actions/bulk-destroy/bulkDeleteActionSettings.tsx +++ b/packages/core/client/src/modules/actions/bulk-destroy/bulkDeleteActionSettings.tsx @@ -51,6 +51,16 @@ export const bulkDeleteActionSettings = new SchemaSettings({ }; }, }, + { + name: 'linkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { linkageRulesProps } = useSchemaToolbar(); + return { + ...linkageRulesProps, + }; + }, + }, { name: 'remove', sort: 100, diff --git a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx index 34c51a0556..a12f3abd0d 100644 --- a/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx +++ b/packages/core/client/src/modules/actions/link/customizeLinkActionSettings.tsx @@ -15,6 +15,7 @@ import { useDesignable } from '../../../'; import { useSchemaToolbar } from '../../../application'; import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings'; import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer'; +import { useCollectionManager_deprecated } from '../../../collection-manager'; import { SchemaSettingsLinkageRules, SchemaSettingsModalItem, @@ -96,6 +97,9 @@ export const customizeLinkActionSettings = new SchemaSettings({ Component: SchemaSettingsLinkageRules, useComponentProps() { const { linkageRulesProps } = useSchemaToolbar(); + const { association } = useDataBlockProps() || {}; + const { getCollectionField } = useCollectionManager_deprecated(); + const associationField = getCollectionField(association); return { ...linkageRulesProps, }; diff --git a/packages/core/client/src/modules/blocks/BlockLinkageRuleProvider.tsx b/packages/core/client/src/modules/blocks/BlockLinkageRuleProvider.tsx new file mode 100644 index 0000000000..7bb04fcbcd --- /dev/null +++ b/packages/core/client/src/modules/blocks/BlockLinkageRuleProvider.tsx @@ -0,0 +1,100 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useMemo, useEffect, useState } from 'react'; +import { useFieldSchema, useForm } from '@formily/react'; +import { last, isEqual } from 'lodash'; +import { uid } from '@formily/shared'; +import { reaction } from '@formily/reactive'; +import { useLocalVariables, useVariables } from '../../variables'; +import { useReactiveLinkageEffect } from './utils'; +import { useDesignable } from '../../'; +import { forEachLinkageRule } from '../../schema-settings/LinkageRules/forEachLinkageRule'; +import { + getVariableValuesInCondition, + getVariableValuesInExpression, +} from '../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; + +const getLinkageRules = (fieldSchema) => { + if (!fieldSchema) { + return []; + } + let linkageRules = fieldSchema?.['x-block-linkage-rules'] || []; + fieldSchema.mapProperties((schema) => { + if (schema['x-block-linkage-rules']) { + linkageRules = schema['x-block-linkage-rules']; + } + }); + return linkageRules?.filter((k) => !k.disabled); +}; + +export const BlockLinkageRuleProvider = (props) => { + const schema = useFieldSchema(); + const variables = useVariables(); + const localVariables = useLocalVariables(); + const { designable } = useDesignable(); + const form = useForm(); + const linkageRules = useMemo(() => getLinkageRules(schema), [schema]); + const [triggerLinkageUpdate, setTriggerLinkageUpdate] = useState(null); + const displayResult = useReactiveLinkageEffect(linkageRules, variables, localVariables, triggerLinkageUpdate); + const shouldCalculateFormLinkage = schema?.['x-decorator'] === 'FormItem' && !form.readPretty && linkageRules.length; + + useEffect(() => { + if (shouldCalculateFormLinkage) { + const id = uid(); + const disposes = []; + + // 延迟执行,防止一开始获取到的 form.values 值是旧的 + setTimeout(() => { + form.addEffects(id, () => { + forEachLinkageRule(linkageRules, (action, rule) => { + return reaction( + () => { + // 获取条件中的变量值 + const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables }); + // 获取 value 表达式中的变量值 + const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables }); + const result = [variableValuesInCondition, variableValuesInExpression] + .map((item) => JSON.stringify(item)) + .join(','); + return result; + }, + () => { + setTriggerLinkageUpdate(uid()); + }, + { fireImmediately: true, equals: isEqual }, + ); + }); + }); + }); + + // 清理副作用 + return () => { + form.removeEffects(id); + disposes.forEach((dispose) => { + dispose(); + }); + }; + } + }, [linkageRules, shouldCalculateFormLinkage]); + if (!linkageRules.length) { + return props.children; + } + + if (displayResult === null) return null; + if (last(displayResult) === 'hidden') { + if (designable) { + return
{props.children}
; + } else { + return null; + } + } + + return props.children; +}; diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaSettings.test.ts index 4168cb2752..3557653105 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaSettings.test.ts @@ -45,7 +45,7 @@ test.describe('multi data details block schema settings', () => { // 禁用规则,联动规则失效 await page.getByLabel('block-item-CardItem-users-').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:detailsWithPagination-users').hover(); - await page.getByText('Linkage rules').click(); + await page.getByText('Field Linkage rules').click(); await page.getByRole('switch', { name: 'On Off' }).click(); await page.getByRole('button', { name: 'OK' }).click(); await page.reload(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx index 9653bd2d82..35363ab525 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx @@ -14,7 +14,7 @@ import { SchemaSettings } from '../../../../application/schema-settings/SchemaSe import { SchemaSettingsItemType } from '../../../../application/schema-settings/types'; import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider'; import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; -import { useCollection_deprecated, useSortFields } from '../../../../collection-manager'; +import { useSortFields } from '../../../../collection-manager'; import { removeNullCondition, useDesignable } from '../../../../schema-component'; import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; @@ -24,6 +24,8 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; +import { useCollection } from '../../../../data-source'; const commonItems: SchemaSettingsItemType[] = [ { @@ -35,13 +37,28 @@ const commonItems: SchemaSettingsItemType[] = [ Component: SchemaSettingsBlockHeightItem, }, { - name: 'linkageRules', + name: 'fieldLinkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { - const { name } = useCollection_deprecated(); + const { name } = useCollection(); + const { t } = useTranslation(); return { collectionName: name, readPretty: true, + title: t('Field Linkage rules'), + }; + }, + }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, }; }, }, @@ -49,7 +66,7 @@ const commonItems: SchemaSettingsItemType[] = [ name: 'dataScope', Component: SchemaSettingsDataScope, useComponentProps() { - const { name } = useCollection_deprecated(); + const { name } = useCollection(); const fieldSchema = useFieldSchema(); const { form } = useFormBlockContext(); const field = useField(); @@ -83,7 +100,7 @@ const commonItems: SchemaSettingsItemType[] = [ name: 'sortingRules', type: 'modal', useComponentProps() { - const { name } = useCollection_deprecated(); + const { name } = useCollection(); const { t } = useTranslation(); const fieldSchema = useFieldSchema(); const field = useField(); @@ -201,7 +218,7 @@ const commonItems: SchemaSettingsItemType[] = [ name: 'template', Component: SchemaSettingsTemplate, useComponentProps() { - const { name } = useCollection_deprecated(); + const { name } = useCollection(); const fieldSchema = useFieldSchema(); const { componentNamePrefix } = useBlockTemplateContext(); const defaultResource = diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts index 6d7f136c3c..e15933e215 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts @@ -8,14 +8,17 @@ */ import { useFieldSchema } from '@formily/react'; +import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { SchemaSettingsItemType } from '../../../../application/schema-settings/types'; import { useCollection } from '../../../../data-source/collection/CollectionProvider'; +import { useCollection_deprecated } from '../../../../collection-manager'; import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings'; import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; const commonItems: SchemaSettingsItemType[] = [ { @@ -27,13 +30,28 @@ const commonItems: SchemaSettingsItemType[] = [ Component: SchemaSettingsBlockHeightItem, }, { - name: 'linkageRules', + name: 'fieldLinkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { const { name } = useCollection(); + const { t } = useTranslation(); return { collectionName: name, readPretty: true, + title: t('Field Linkage rules'), + }; + }, + }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, }; }, }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts index 86eda66087..56459afec6 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts @@ -310,7 +310,7 @@ test.describe('set default value', () => { // 设置联动规则 await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await page.mouse.move(300, 0); await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); await page.getByText('Add property').click(); @@ -438,7 +438,7 @@ test.describe('set default value', () => { // 设置联动规则 await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await page.mouse.move(300, 0); await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); await page.getByText('Add property').click(); @@ -563,7 +563,7 @@ test.describe('set default value', () => { // 设置联动规则 await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await page.mouse.move(300, 0); await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); await page.getByText('Add property').click(); @@ -701,7 +701,7 @@ test.describe('set default value', () => { // 设置联动规则 await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await page.mouse.move(300, 0); await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); await page.getByText('Add property').click(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/deprecatedVariables.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/deprecatedVariables.test.ts index 1461e81c7e..4c86cc9422 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/deprecatedVariables.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/deprecatedVariables.test.ts @@ -18,7 +18,7 @@ test.describe('deprecated variables', () => { await page.getByLabel('action-Action.Link-Edit').click(); await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await expect(page.getByLabel('variable-tag').getByText('Current record / Nickname')).toBeVisible(); // 2. 但是变量列表中是禁用状态 @@ -57,7 +57,7 @@ test.describe('deprecated variables', () => { // 4. 再次打开弹窗,变量列表中的弃用变量不再显示 await page.getByLabel('block-item-CardItem-users-form').hover(); await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('menuitem', { name: 'Field linkage rules' }).click(); await page.locator('button').filter({ hasText: /^x$/ }).last().click(); await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toBeHidden(); // 使下拉菜单消失 diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx index c16c806c77..e29e77446a 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx @@ -8,6 +8,7 @@ */ import { useFieldSchema } from '@formily/react'; +import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; import { useCollection_deprecated } from '../../../../collection-manager'; @@ -21,6 +22,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; export const createFormBlockSettings = new SchemaSettings({ name: 'blockSettings:createForm', @@ -34,12 +36,27 @@ export const createFormBlockSettings = new SchemaSettings({ Component: SchemaSettingsBlockHeightItem, }, { - name: 'linkageRules', + name: 'fieldLinkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { const { name } = useCollection_deprecated(); + const { t } = useTranslation(); return { collectionName: name, + title: t('Field Linkage rules'), + }; + }, + }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, }; }, }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts index 6b41165721..8ce416a3e6 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts @@ -8,6 +8,7 @@ */ import { useFieldSchema } from '@formily/react'; +import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; import { useCollection_deprecated } from '../../../../collection-manager'; @@ -21,6 +22,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; export const editFormBlockSettings = new SchemaSettings({ name: 'blockSettings:editForm', @@ -34,12 +36,27 @@ export const editFormBlockSettings = new SchemaSettings({ Component: SchemaSettingsBlockHeightItem, }, { - name: 'linkageRules', + name: 'fieldLinkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { const { name } = useCollection_deprecated(); + const { t } = useTranslation(); return { collectionName: name, + title: t('Field Linkage rules'), + }; + }, + }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, }; }, }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts index 074ec0f60d..5310ce5187 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts @@ -24,6 +24,8 @@ import { useBlockTemplateContext } from '../../../../schema-templates/BlockTempl import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem'; import { SetTheCountOfColumnsDisplayedInARow } from './SetTheCountOfColumnsDisplayedInARow'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; export const gridCardBlockSettings = new SchemaSettings({ name: 'blockSettings:gridCard', @@ -32,6 +34,19 @@ export const gridCardBlockSettings = new SchemaSettings({ name: 'setTheBlockHeight', Component: SchemaSettingsBlockHeightItem, }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, + }; + }, + }, { name: 'SetTheCountOfColumnsDisplayedInARow', Component: SetTheCountOfColumnsDisplayedInARow, diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts index 6c70969a56..c4f4605cef 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts @@ -27,3 +27,11 @@ export function useGridCardBlockDecoratorProps(props) { parseVariableLoading, }; } + +export function useGridCardBlockItemProps() { + return {}; +} + +export function useGridCardBlockProps() { + return {}; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps.ts b/packages/core/client/src/modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps.ts index 4944874fb5..833b859966 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps.ts @@ -22,3 +22,7 @@ export function useListBlockDecoratorProps(props) { parentRecord, }; } + +export function useListBlockProps() { + return {}; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts index f664cf58e2..98e784fa77 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts @@ -22,6 +22,8 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; export const listBlockSettings = new SchemaSettings({ name: 'blockSettings:list', @@ -34,6 +36,19 @@ export const listBlockSettings = new SchemaSettings({ name: 'setTheBlockHeight', Component: SchemaSettingsBlockHeightItem, }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, + }; + }, + }, { name: 'SetTheDataScope', Component: SchemaSettingsDataScope, diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx index 6a05614b93..e99f8a4a11 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx @@ -26,6 +26,8 @@ import { setTheDataScopeSchemaSettingsItem } from '../../../../schema-settings/s import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem'; import { SchemaSettingsItemType } from '../../../../application'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; const enabledIndexColumn: SchemaSettingsItemType = { name: 'enableIndexColumn', @@ -64,6 +66,19 @@ export const tableBlockSettings = new SchemaSettings({ name: 'setTheBlockHeight', Component: SchemaSettingsBlockHeightItem, }, + { + name: 'linkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, + }; + }, + }, { name: 'treeTable', type: 'switch', @@ -138,7 +153,6 @@ export const tableBlockSettings = new SchemaSettings({ const { resource } = field.decoratorProps; const collectionField = resource && getCollectionField(resource); const api = useAPIClient(); - return { title: t('Enable drag and drop sorting'), checked: field.decoratorProps.dragSort, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseBlockSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseBlockSettings.ts index 503c1c6ada..6e6ff7ad44 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/collapse/filterCollapseBlockSettings.ts @@ -12,11 +12,14 @@ import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useCollection_deprecated } from '../../../../collection-manager'; import { FilterBlockType } from '../../../../filter-provider'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks'; import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; +import { useCollection } from '../../../../data-source/collection/CollectionProvider'; export const filterCollapseBlockSettings = new SchemaSettings({ name: 'blockSettings:filterCollapse', @@ -29,6 +32,19 @@ export const filterCollapseBlockSettings = new SchemaSettings({ name: 'setTheBlockHeight', Component: SchemaSettingsBlockHeightItem, }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, + }; + }, + }, { name: 'ConvertReferenceToDuplicate', Component: SchemaSettingsTemplate, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts index 64573a1b0f..05ea675a63 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts @@ -19,6 +19,7 @@ import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/Schema import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; export const filterFormBlockSettings = new SchemaSettings({ name: 'blockSettings:filterForm', @@ -48,12 +49,27 @@ export const filterFormBlockSettings = new SchemaSettings({ }, }, { - name: 'linkageRules', + name: 'fieldLinkageRules', Component: SchemaSettingsLinkageRules, useComponentProps() { const { name } = useCollection_deprecated(); + const { t } = useTranslation(); return { collectionName: name, + title: t('Field Linkage rules'), + }; + }, + }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { name } = useCollection(); + const { t } = useTranslation(); + return { + collectionName: name, + title: t('Block Linkage rules'), + category: LinkageRuleCategory.block, }; }, }, diff --git a/packages/core/client/src/modules/blocks/other-blocks/markdown/markdownBlockSettings.ts b/packages/core/client/src/modules/blocks/other-blocks/markdown/markdownBlockSettings.ts index aecb5cd040..f779581ea2 100644 --- a/packages/core/client/src/modules/blocks/other-blocks/markdown/markdownBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/other-blocks/markdown/markdownBlockSettings.ts @@ -7,11 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useField } from '@formily/react'; +import { useField, useFieldSchema } from '@formily/react'; import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsRenderEngine } from '../../../../schema-settings/SchemaSettingsRenderEngine'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; +import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type'; + export const markdownBlockSettings = new SchemaSettings({ name: 'blockSettings:markdown', items: [ @@ -21,7 +24,6 @@ export const markdownBlockSettings = new SchemaSettings({ useComponentProps() { const field = useField(); const { t } = useTranslation(); - return { title: t('Edit markdown'), onClick: () => { @@ -34,6 +36,27 @@ export const markdownBlockSettings = new SchemaSettings({ name: 'setTheBlockHeight', Component: SchemaSettingsBlockHeightItem, }, + { + name: 'blockLinkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const underForm = fieldSchema['x-decorator'] === 'FormItem'; + return { + title: underForm ? t('Linkage rules') : t('Block Linkage rules'), + category: LinkageRuleCategory.block, + returnScope: (options) => { + return options.filter((v) => { + if (!underForm) { + return !['$nForm', '$nRecord'].includes(v.value); + } + return true; + }); + }, + }; + }, + }, { name: 'setBlockTemplate', Component: SchemaSettingsRenderEngine, diff --git a/packages/core/client/src/modules/blocks/utils.ts b/packages/core/client/src/modules/blocks/utils.ts new file mode 100644 index 0000000000..f2fb6599d9 --- /dev/null +++ b/packages/core/client/src/modules/blocks/utils.ts @@ -0,0 +1,91 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useEffect, useState } from 'react'; +import { VariableOption, VariablesContextType } from '../../variables/types'; +import { conditionAnalyses } from '../../schema-component/common/utils/uitls'; +import { useApp } from '../../application'; +import { useCollectionRecord } from '../../data-source'; + +enum ActionType { + Visible = 'visible', + Hidden = 'hidden', +} + +const linkageAction = async ( + { + operator, + condition, + variables, + localVariables, + conditionType, + displayResult, + }: { + operator; + condition; + variables: VariablesContextType; + localVariables: VariableOption[]; + conditionType: 'advanced'; + displayResult: any[]; + }, + jsonLogic: any, +) => { + switch (operator) { + case ActionType.Visible: + if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables, conditionType }, jsonLogic)) { + displayResult.push(ActionType.Visible); + } + return displayResult; + case ActionType.Hidden: + if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables, conditionType }, jsonLogic)) { + displayResult.push(ActionType.Hidden); + } + return displayResult; + default: + return null; + } +}; + +export const useReactiveLinkageEffect = ( + linkageRules: any[], + variables: VariablesContextType, + localVariables: VariableOption[], + triggerLinkageUpdate, +) => { + const app = useApp(); + const jsonLogic = app.jsonLogic; + const [displayResult, setDisplayResult] = useState(null); + const record = useCollectionRecord(); + useEffect(() => { + const runLinkages = async () => { + const result: string[] = []; + + for (const rule of linkageRules.filter((r) => !r.disabled)) { + for (const action of rule.actions || []) { + await linkageAction( + { + operator: action.operator, + condition: rule.condition, + variables, + localVariables, + conditionType: rule.conditionType, + displayResult: result, + }, + jsonLogic, + ); + } + } + setDisplayResult(result); + }; + + runLinkages(); + }, [linkageRules, triggerLinkageUpdate, record]); + + return displayResult; +}; diff --git a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx index deb129be5a..6e6b2c4172 100644 --- a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx +++ b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx @@ -6,10 +6,9 @@ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ - +import React, { useMemo } from 'react'; import { useFieldSchema } from '@formily/react'; import cls from 'classnames'; -import React, { useMemo } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useSchemaToolbarRender } from '../../../application'; import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; @@ -18,6 +17,8 @@ import { useProps } from '../../hooks'; import { ErrorFallback } from '../error-fallback'; import { useStyles } from './BlockItem.style'; import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem'; +import { useCollection } from '../../../data-source'; +import { BlockLinkageRuleProvider } from '../../../modules/blocks/BlockLinkageRuleProvider'; export interface BlockItemProps { name?: string; @@ -35,8 +36,9 @@ export const BlockItem: React.FC = withDynamicSchemaProps( const { render } = useSchemaToolbarRender(fieldSchema); const { getAriaLabel } = useGetAriaLabelOfBlockItem(props.name); const label = useMemo(() => getAriaLabel(), [getAriaLabel]); - - return ( + const collection = useCollection(); + const markdownField = fieldSchema['x-decorator'] === 'FormItem' && fieldSchema['x-block-linkage-rules']; + const content = ( = withDynamicSchemaProps( ); + + return collection && !markdownField ? content : {content}; }, { displayName: 'BlockItem' }, ); diff --git a/packages/core/client/src/schema-component/antd/block-item/BlockItemCard.tsx b/packages/core/client/src/schema-component/antd/block-item/BlockItemCard.tsx index a6e0e804ca..8845d27153 100644 --- a/packages/core/client/src/schema-component/antd/block-item/BlockItemCard.tsx +++ b/packages/core/client/src/schema-component/antd/block-item/BlockItemCard.tsx @@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next'; import { useToken } from '../../../style'; import { MarkdownReadPretty } from '../markdown'; import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant'; +import { useCollection } from '../../../data-source'; export const BlockItemCardContext = createContext({}); @@ -25,6 +26,8 @@ export const BlockItemCard = React.forwardRef(( const [titleHeight, setTitleHeight] = useState(0); const titleRef = useRef(null); const { t } = useTranslation(); + const collection = useCollection(); + console.log(); useEffect(() => { const timer = setTimeout(() => { if (titleRef.current) { diff --git a/packages/core/client/src/schema-component/antd/form/__tests__/form.settings.test.tsx b/packages/core/client/src/schema-component/antd/form/__tests__/form.settings.test.tsx index a40cbb5eb3..737a3d173d 100644 --- a/packages/core/client/src/schema-component/antd/form/__tests__/form.settings.test.tsx +++ b/packages/core/client/src/schema-component/antd/form/__tests__/form.settings.test.tsx @@ -152,65 +152,65 @@ describe('form.settings', () => { title: 'Edit block title', type: 'modal', }, - { - title: 'Linkage rules', - type: 'modal', - modalChecker: { - modalTitle: 'Linkage rules', - contentText: 'Add linkage rule', - async customCheck() { - // await userEvent.click(screen.getByText('Add linkage rule')); - // await waitFor(() => { - // expect(screen.queryByText('Add condition')).toBeInTheDocument(); - // }) - // await userEvent.click(screen.getByText('Add condition')); - // await waitFor(() => { - // expect(screen.queryByText('Select field')).toBeInTheDocument(); - // }) - // await userEvent.click(screen.getByText('Select field')); - // await waitFor(() => { - // expect(screen.queryByTitle('Username')).toBeInTheDocument(); - // }) - // await userEvent.click(screen.getByTitle('Username')); - // const dialog = screen.queryByRole('dialog'); - // await userEvent.type(dialog.querySelectorAll('.ant-input')[1], '1'); - // const properties = screen.queryByTestId('select-linkage-property-field'); - // await userEvent.click(screen.getByText('Select field')); - // await waitFor(() => { - // expect(properties.querySelector(`[title=Nickname]`)).toBeInTheDocument(); - // }) - // await userEvent.click(properties.querySelector(`[title=Nickname]`)); - // await userEvent.click(screen.getByText('action')); - // await waitFor(() => { - // expect(screen.queryByText('Hidden')).toBeInTheDocument(); - // }) - // await userEvent.click(screen.getByText('Hidden')); - }, - // async afterSubmit() { - // await checkSchema({ - // "x-linkage-rules": [ - // { - // "condition": { - // "$and": [ - // { - // "username": {} - // } - // ] - // }, - // "actions": [ - // { - // "targetFields": [ - // "nickname" - // ], - // "operator": "none" - // } - // ] - // } - // ] - // }) - // }, - }, - }, + // { + // title: 'Linkage rules', + // type: 'modal', + // modalChecker: { + // modalTitle: 'Linkage rules', + // contentText: 'Add linkage rule', + // async customCheck() { + // // await userEvent.click(screen.getByText('Add linkage rule')); + // // await waitFor(() => { + // // expect(screen.queryByText('Add condition')).toBeInTheDocument(); + // // }) + // // await userEvent.click(screen.getByText('Add condition')); + // // await waitFor(() => { + // // expect(screen.queryByText('Select field')).toBeInTheDocument(); + // // }) + // // await userEvent.click(screen.getByText('Select field')); + // // await waitFor(() => { + // // expect(screen.queryByTitle('Username')).toBeInTheDocument(); + // // }) + // // await userEvent.click(screen.getByTitle('Username')); + // // const dialog = screen.queryByRole('dialog'); + // // await userEvent.type(dialog.querySelectorAll('.ant-input')[1], '1'); + // // const properties = screen.queryByTestId('select-linkage-property-field'); + // // await userEvent.click(screen.getByText('Select field')); + // // await waitFor(() => { + // // expect(properties.querySelector(`[title=Nickname]`)).toBeInTheDocument(); + // // }) + // // await userEvent.click(properties.querySelector(`[title=Nickname]`)); + // // await userEvent.click(screen.getByText('action')); + // // await waitFor(() => { + // // expect(screen.queryByText('Hidden')).toBeInTheDocument(); + // // }) + // // await userEvent.click(screen.getByText('Hidden')); + // }, + // // async afterSubmit() { + // // await checkSchema({ + // // "x-linkage-rules": [ + // // { + // // "condition": { + // // "$and": [ + // // { + // // "username": {} + // // } + // // ] + // // }, + // // "actions": [ + // // { + // // "targetFields": [ + // // "nickname" + // // ], + // // "operator": "none" + // // } + // // ] + // // } + // // ] + // // }) + // // }, + // }, + // }, { title: 'Form data templates', type: 'modal', diff --git a/packages/core/client/src/schema-component/antd/linkageFilter/DynamicComponent.tsx b/packages/core/client/src/schema-component/antd/linkageFilter/DynamicComponent.tsx index 995043a1c9..a42cfc03ca 100644 --- a/packages/core/client/src/schema-component/antd/linkageFilter/DynamicComponent.tsx +++ b/packages/core/client/src/schema-component/antd/linkageFilter/DynamicComponent.tsx @@ -46,7 +46,7 @@ interface Props { export const DynamicComponent = (props: Props) => { const { setScopes, nullable, constantAbel, changeOnSelect, readOnly = false } = props; - const { disabled } = useContext(FilterContext) || {}; + const { disabled, returnScope } = useContext(FilterContext) || {}; const record = useCollectionRecordData(); const variables = useVariables(); const localVariables = useLocalVariables(); @@ -68,6 +68,7 @@ export const DynamicComponent = (props: Props) => { localVariables, getAllCollectionsInheritChain, })} + returnScope={returnScope} /> ); }, []); diff --git a/packages/core/client/src/schema-component/antd/linkageFilter/LinkageFilter.tsx b/packages/core/client/src/schema-component/antd/linkageFilter/LinkageFilter.tsx index eb3003c584..0ab8849f29 100644 --- a/packages/core/client/src/schema-component/antd/linkageFilter/LinkageFilter.tsx +++ b/packages/core/client/src/schema-component/antd/linkageFilter/LinkageFilter.tsx @@ -26,7 +26,7 @@ export const LinkageFilter: any = withDynamicSchemaProps( const { useDataSource = useDef } = props; // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema - const { dynamicComponent, className, collectionName } = useProps(props); + const { dynamicComponent, className, collectionName, returnScope } = useProps(props); const [scopes, setScopes] = useState([]); const field = useField(); @@ -54,6 +54,7 @@ export const LinkageFilter: any = withDynamicSchemaProps( collectionName, scopes, setScopes, + returnScope, }} > diff --git a/packages/core/client/src/schema-component/antd/linkageFilter/context.ts b/packages/core/client/src/schema-component/antd/linkageFilter/context.ts index 44be0a9730..966866fd8b 100644 --- a/packages/core/client/src/schema-component/antd/linkageFilter/context.ts +++ b/packages/core/client/src/schema-component/antd/linkageFilter/context.ts @@ -20,6 +20,7 @@ export interface FilterContextProps { collectionName?: string; scopes?: any[]; setScopes?: any; + returnScope?: any; } export const RemoveConditionContext = createContext(null); diff --git a/packages/core/client/src/schema-component/antd/linkageFilter/useValues.ts b/packages/core/client/src/schema-component/antd/linkageFilter/useValues.ts index 2f365fbadb..57fbd07501 100644 --- a/packages/core/client/src/schema-component/antd/linkageFilter/useValues.ts +++ b/packages/core/client/src/schema-component/antd/linkageFilter/useValues.ts @@ -44,7 +44,7 @@ const findOption = (str, options) => { // 进入下一层 children 查找 if (Array.isArray(option.children) || option.isLeaf === false) { - currentOptions = option.children || option.field.children; + currentOptions = option.children || option?.field?.children || []; } else { return option; // 没有 children 直接返回 } diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx index 83e6839373..9f35006f79 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx @@ -27,6 +27,7 @@ import { SchemaSettingsRemove, SchemaSettingsSelectItem, SchemaSettingsSwitchItem, + SchemaSettingsLinkageRules, } from '../../../schema-settings'; import { SchemaSettingsBlockHeightItem } from '../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSettingsBlockTitleItem'; @@ -127,10 +128,12 @@ export const TableBlockDesigner = () => { [dn, field.decoratorProps, fieldSchema, service], ); const api = useAPIClient(); + return ( + {collection?.tree && collectionField?.collectionName === collectionField?.target && ( { title: 'Set block height', type: 'modal', }, + { + title: 'Block linkage rules', + type: 'modal', + }, { title: 'Enable drag and drop sorting', type: 'switch', diff --git a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx index 8efada27cf..c0ff6debe9 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleActionGroup.tsx @@ -15,8 +15,10 @@ import { useTranslation } from 'react-i18next'; import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps'; import { useProps } from '../../schema-component/hooks/useProps'; import { FormButtonLinkageRuleAction, FormFieldLinkageRuleAction } from './LinkageRuleAction'; -import { FieldStyleLinkageRuleAction } from './FieldStyleLinkageRuleAction'; +import { FieldStyleLinkageRuleAction } from './components/FieldStyleLinkageRuleAction'; +import { BlockLinkageRuleAction } from './components/BlockLinkageRuleAction'; import { RemoveActionContext } from './context'; + export const LinkageRuleActions = observer( (props: any): any => { const { linkageOptions, category, elementType } = props; @@ -28,6 +30,7 @@ export const LinkageRuleActions = observer( button: FormButtonLinkageRuleAction, field: FormFieldLinkageRuleAction, style: FieldStyleLinkageRuleAction, + block: BlockLinkageRuleAction, }; return field?.value?.map((item, index) => { return ( @@ -41,7 +44,7 @@ export const LinkageRuleActions = observer( ); export interface LinkageRuleActionGroupProps { - type: 'button' | 'field' | 'style'; + type: 'button' | 'field' | 'style' | 'block'; linkageOptions: any; collectionName: string; } diff --git a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts index 2a3cbbeb52..eaa2afcfbd 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts +++ b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts @@ -122,7 +122,7 @@ function getFieldValuesInCondition({ linkageRules, formValues }) { }); } -function getVariableValuesInCondition({ +export function getVariableValuesInCondition({ linkageRules, localVariables, }: { @@ -166,7 +166,7 @@ function getVariableValuesInCondition({ }); } -function getVariableValuesInExpression({ action, localVariables }) { +export function getVariableValuesInExpression({ action, localVariables }) { const actionValue = action.value; const mode = actionValue?.mode; const value = actionValue?.value || actionValue?.result; diff --git a/packages/core/client/src/schema-settings/LinkageRules/components/BlockLinkageRuleAction.tsx b/packages/core/client/src/schema-settings/LinkageRules/components/BlockLinkageRuleAction.tsx new file mode 100644 index 0000000000..6d7f39352d --- /dev/null +++ b/packages/core/client/src/schema-settings/LinkageRules/components/BlockLinkageRuleAction.tsx @@ -0,0 +1,86 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { CloseCircleOutlined } from '@ant-design/icons'; +import { css } from '@emotion/css'; +import { TreeSelect } from '@formily/antd-v5'; +import { observer } from '@formily/react'; +import { uid } from '@formily/shared'; +import { Select, Space } from 'antd'; +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCompile } from '../../..'; +import { DynamicComponent } from '../DynamicComponent'; +import { RemoveActionContext } from '../context'; +import { ActionType } from '../type'; +import { useValues } from '../useValues'; + +export const BlockLinkageRuleAction = observer( + (props: any) => { + const { value, options } = props; + const { t } = useTranslation(); + const compile = useCompile(); + const [editFlag, setEditFlag] = useState(false); + const remove = useContext(RemoveActionContext); + const { schema, operator, setOperator, setValue } = useValues(options); + const operators = useMemo( + () => + compile([ + { label: t('Visible'), value: ActionType.Visible, schema: {} }, + { label: t('Hidden'), value: ActionType.Hidden, schema: {} }, + ]), + [compile, t], + ); + + const onChange = useCallback( + (value) => { + const flag = [ActionType.Value].includes(value); + setEditFlag(flag); + setOperator(value); + }, + [setOperator], + ); + + const onChangeValue = useCallback( + (value) => { + setValue(value); + }, + [setValue], + ); + + const closeStyle = useMemo(() => ({ color: '#bfbfbf' }), []); + + return ( +
+ +