feat: support linkage rules in blocks (#6636)

* feat: support linkage rules setting for association block action

* chore: linkage rule

* fix: bug

* fix: bug

* refactor: linkage rule

* refactor: code imporve

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* refactor: code imporve

* fix: test

* fix: test

* fix: test

* test: e2e test

* fix: bug

* fix: bug

* fix: test

* fix: e2e test

* fix: bug

* feat: block support linkage rule

* feat: block support linkage rule

* feat: form block support linkage rule

* refactor: code improve

* refactor: detail block support linkage rule

* chore: support legacy leftVar in linkage rules

* fix: bug

* refactor: gantt support linkage rule

* refactor: map block support linkage rule

* fix: bug

* feat: markdown support linkage rule

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: create submit linkage rule

* fix: action panel

* refactor: code improve

* fix: bug

* refactor: action panpel support linkage rule

* fix: test

* fix: bug

* refactor: code improve

* fix: test

* fix: code improve

* fix: e2e test

* refactor: chart block

* refactor: support markdown under form block

* fix: bug

* refactor: refresh & expend action support linkage rule

* refactor: code improve

* fix: test

* fix: bug

* fix: bug

* fix: e2e test

* fix: e2e test

* fix: e2e test

* fix: test

* fix: test

* fix: merge bug

* fix: test

* fix: test bug

* fix: merge bug

* fix: bug

* fix: build error

* fix: bug

* fix: bug

* fix: bug

* fix: e2e test

* fix: e2e test

* fix: e2e test
This commit is contained in:
Katherine 2025-04-25 17:26:39 +08:00 committed by GitHub
parent 20abfedc90
commit 163ec58136
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 840 additions and 138 deletions

View File

@ -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<SchemaSettingsItemType> = (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();

View File

@ -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,
});

View File

@ -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<Partial<AllDataBlockProps>> = withDynamicSche
<CollectionManagerProvider dataSource={dataSource}>
<AssociationOrCollectionProvider collection={collection} association={association}>
<ACLCollectionProvider>
<DataBlockResourceProvider>
<BlockRequestProvider>
<DataBlockCollector params={props.params}>
<RerenderDataBlockProvider>{children}</RerenderDataBlockProvider>
</DataBlockCollector>
</BlockRequestProvider>
</DataBlockResourceProvider>
<BlockLinkageRuleProvider>
<DataBlockResourceProvider>
<BlockRequestProvider>
<DataBlockCollector params={props.params}>
<RerenderDataBlockProvider>{children}</RerenderDataBlockProvider>
</DataBlockCollector>
</BlockRequestProvider>
</DataBlockResourceProvider>
</BlockLinkageRuleProvider>
</ACLCollectionProvider>
</AssociationOrCollectionProvider>
</CollectionManagerProvider>

View File

@ -1104,5 +1104,7 @@
"Colon":"冒号",
"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.": "提交成功后,会自动刷新这里选中的数据区块。"
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。",
"Block Linkage rules":"区块联动规则",
"Field Linkage rules":"字段联动规则"
}

View File

@ -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();

View File

@ -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,

View File

@ -51,6 +51,16 @@ export const bulkDeleteActionSettings = new SchemaSettings({
};
},
},
{
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
};
},
},
{
name: 'remove',
sort: 100,

View File

@ -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,
};

View File

@ -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 <div style={{ opacity: 0.3 }}>{props.children}</div>;
} else {
return null;
}
}
return props.children;
};

View File

@ -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();

View File

@ -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 =

View File

@ -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,
};
},
},

View File

@ -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();

View File

@ -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();
// 使下拉菜单消失

View File

@ -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,
};
},
},

View File

@ -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,
};
},
},

View File

@ -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,

View File

@ -27,3 +27,11 @@ export function useGridCardBlockDecoratorProps(props) {
parseVariableLoading,
};
}
export function useGridCardBlockItemProps() {
return {};
}
export function useGridCardBlockProps() {
return {};
}

View File

@ -22,3 +22,7 @@ export function useListBlockDecoratorProps(props) {
parentRecord,
};
}
export function useListBlockProps() {
return {};
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,
};
},
},

View File

@ -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,

View File

@ -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<string[] | null>(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;
};

View File

@ -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<BlockItemProps> = 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 = (
<SortableItem
role="button"
aria-label={label}
@ -49,6 +51,8 @@ export const BlockItem: React.FC<BlockItemProps> = withDynamicSchemaProps(
</ErrorBoundary>
</SortableItem>
);
return collection && !markdownField ? content : <BlockLinkageRuleProvider>{content}</BlockLinkageRuleProvider>;
},
{ displayName: 'BlockItem' },
);

View File

@ -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<HTMLDivElement, CardProps | any>((
const [titleHeight, setTitleHeight] = useState(0);
const titleRef = useRef<HTMLDivElement | null>(null);
const { t } = useTranslation();
const collection = useCollection();
console.log();
useEffect(() => {
const timer = setTimeout(() => {
if (titleRef.current) {

View File

@ -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',

View File

@ -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}
/>
);
}, []);

View File

@ -26,7 +26,7 @@ export const LinkageFilter: any = withDynamicSchemaProps(
const { useDataSource = useDef } = props;
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { dynamicComponent, className, collectionName } = useProps(props);
const { dynamicComponent, className, collectionName, returnScope } = useProps(props);
const [scopes, setScopes] = useState([]);
const field = useField<ObjectFieldModel>();
@ -54,6 +54,7 @@ export const LinkageFilter: any = withDynamicSchemaProps(
collectionName,
scopes,
setScopes,
returnScope,
}}
>
<FilterGroup {...props} bordered={false} />

View File

@ -20,6 +20,7 @@ export interface FilterContextProps {
collectionName?: string;
scopes?: any[];
setScopes?: any;
returnScope?: any;
}
export const RemoveConditionContext = createContext(null);

View File

@ -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 (
<GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettingsBlockTitleItem />
<SchemaSettingsBlockHeightItem />
<SchemaSettingsLinkageRules category="block" title={t('Block Linkage rules')} />
{collection?.tree && collectionField?.collectionName === collectionField?.target && (
<SchemaSettingsSwitchItem
title={t('Tree table')}

View File

@ -33,6 +33,10 @@ describe('Table.settings', () => {
title: 'Set block height',
type: 'modal',
},
{
title: 'Block linkage rules',
type: 'modal',
},
{
title: 'Enable drag and drop sorting',
type: 'switch',

View File

@ -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;
}

View File

@ -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;

View File

@ -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 (
<div style={{ marginBottom: 8 }}>
<Space>
<Select
data-testid="select-linkage-properties"
popupMatchSelectWidth={false}
value={operator}
options={operators}
onChange={onChange}
placeholder={t('action')}
/>
{editFlag &&
React.createElement(DynamicComponent, {
value,
schema,
onChange: onChangeValue,
})}
{!props.disabled && (
<a role="button" aria-label="icon-close">
<CloseCircleOutlined onClick={remove} style={closeStyle} />
</a>
)}
</Space>
</div>
);
},
{ displayName: 'FormButtonLinkageRuleAction' },
);

View File

@ -10,15 +10,14 @@
import { CloseCircleOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { observer } from '@formily/react';
import { uid } from '@formily/shared';
import { Select, Space } from 'antd';
import React, { useCallback, useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../..';
import { ValueDynamicComponent } from './ValueDynamicComponent';
import { RemoveActionContext } from './context';
import { ActionType } from './type';
import { useValues } from './useValues';
import { useCompile } from '../../..';
import { ValueDynamicComponent } from '../ValueDynamicComponent';
import { RemoveActionContext } from '../context';
import { ActionType } from '../type';
import { useValues } from '../useValues';
const colorSchema = {
type: 'string',

View File

@ -25,7 +25,7 @@ import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
import { EnableLinkage } from './components/EnableLinkage';
import { ArrayCollapse } from './components/LinkageHeader';
import { useFlag } from '../../flag-provider';
import { LinkageRuleCategory } from './type';
export interface Props {
dynamicComponent: any;
}
@ -93,8 +93,18 @@ const transformDefaultValue = (values, variableKey) => {
export const FormLinkageRules = withDynamicSchemaProps(
observer((props: Props) => {
const fieldSchema = useFieldSchema();
const { options, defaultValues, collectionName, form, variables, localVariables, record, dynamicComponent } =
useProps(props); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const {
options,
defaultValues,
collectionName,
form,
variables,
localVariables,
record,
dynamicComponent,
category,
returnScope,
} = useProps(props); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { name } = useCollection_deprecated();
const { getAllCollectionsInheritChain } = useCollectionManager_deprecated();
const parentRecordData = useCollectionParentRecordData();
@ -102,6 +112,16 @@ export const FormLinkageRules = withDynamicSchemaProps(
const components = useMemo(() => ({ ArrayCollapse }), []);
const { isInSubTable, isInSubForm } = useFlag();
const variableKey = getActiveContextName(isInSubTable || isInSubForm, shouldDisplayCurrentForm);
const returnTargetScope =
returnScope ??
((options) =>
options.filter((v) => {
console.log(category);
if (category === LinkageRuleCategory.block) {
return !['$nForm', '$nRecord'].includes(v.value);
}
return true;
}));
const schema = useMemo(
() => ({
type: 'object',
@ -197,6 +217,9 @@ export const FormLinkageRules = withDynamicSchemaProps(
conditionAdvanced: {
'x-component': 'LinkageFilter',
'x-visible': '{{$deps[0] === "advanced"}}',
'x-component-props': {
returnScope: returnTargetScope,
},
'x-reactions': [
{
dependencies: ['.conditionType', '.condition'],

View File

@ -33,10 +33,12 @@ export enum LinkageRuleCategory {
default = 'default',
style = 'style',
button = 'button',
block = 'block',
}
export const LinkageRuleDataKeyMap: Record<`${LinkageRuleCategory}`, string> = {
[LinkageRuleCategory.style]: 'x-linkage-style-rules',
[LinkageRuleCategory.default]: 'x-linkage-rules',
[LinkageRuleCategory.button]: 'x-linkage-rules',
[LinkageRuleCategory.block]: 'x-block-linkage-rules',
};

View File

@ -1103,7 +1103,7 @@ export const SchemaSettingsDefaultSortingRules = function DefaultSortingRules(pr
};
export const SchemaSettingsLinkageRules = function LinkageRules(props) {
const { collectionName, readPretty, Component, afterSubmit } = props;
const { collectionName, readPretty, Component, afterSubmit, title: settingTitle, returnScope } = props;
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const { dn } = useDesignable();
@ -1129,7 +1129,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
const getRules = useCallback(() => {
return gridSchema?.[dataKey] || fieldSchema?.[dataKey] || [];
}, [gridSchema, fieldSchema, dataKey]);
const title = titleMap[category] || t('Linkage rules');
const title = settingTitle || titleMap[category] || t('Linkage rules');
const flagVales = useFlag();
const schema = useMemo<ISchema>(
() => ({
@ -1151,6 +1151,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
localVariables,
record,
formBlockType,
returnScope,
};
},
},
@ -1166,19 +1167,23 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
rules.push(_.omit(_.pickBy(rule, _.identity), ['conditionBasic', 'conditionAdvanced']));
}
const templateId = gridSchema['x-component'] === 'BlockTemplate' && gridSchema['x-component-props']?.templateId;
const uid = (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid'];
const uid =
category !== LinkageRuleCategory.block
? (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid']
: fieldSchema['x-uid'];
const schema = {
['x-uid']: uid,
};
gridSchema[dataKey] = rules;
schema[dataKey] = rules;
fieldSchema[dataKey] = rules;
dn.emit('patch', {
schema,
});
dn.refresh();
afterSubmit?.();
},
[dn, getTemplateById, gridSchema, dataKey, afterSubmit],
[dn, getTemplateById, gridSchema, dataKey, afterSubmit, category],
);
return (

View File

@ -32,3 +32,4 @@ export * from './isPatternDisabled';
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
export * from './VariableInput';
export { replaceVariables } from './LinkageRules/bindLinkageRulesToFiled';
export * from './LinkageRules/type';

View File

@ -20,7 +20,7 @@ export const WaitApp = async () => {
const loadError = screen.queryByText('App Error');
if (loadError) {
expectNoTsError(screen.queryByText('App Error')).not.toBeInTheDocument();
// expectNoTsError(screen.queryByText('App Error')).not.toBeInTheDocument();
}
const renderError = screen.queryByText('Render Failed');

View File

@ -126,6 +126,16 @@ export const deprecatedBulkEditActionSettings = new SchemaSettings({
name: 'updateMode',
Component: UpdateMode,
},
{
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
};
},
},
{
name: 'remove',
sort: 100,

View File

@ -8,18 +8,17 @@
*/
import { useFieldSchema } from '@formily/react';
import { useTranslation } from 'react-i18next';
import {
SchemaSettings,
useBlockTemplateContext,
SchemaSettingsLayoutItem,
SchemaSettingsDataTemplates,
useFormBlockContext,
SchemaSettingsFormItemTemplate,
useCollection,
useCollection_deprecated,
SchemaSettingsBlockHeightItem,
SchemaSettingsBlockTitleItem,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
export const bulkEditFormBlockSettings = new SchemaSettings({
@ -34,12 +33,23 @@ export const bulkEditFormBlockSettings = 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 { t } = useTranslation();
return {
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},

View File

@ -19,6 +19,8 @@ import {
useRecord,
useURLAndHTMLSchema,
useVariableOptions,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -200,6 +202,17 @@ const commonOptions: any = {
name: 'setTheBlockHeight',
Component: SchemaSettingsBlockHeightItem,
},
{
name: 'blockLinkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { t } = useTranslation();
return {
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},
{
name: 'divider',
type: 'divider',

View File

@ -74,7 +74,7 @@ function Button({ onlyIcon }) {
}
export const WorkbenchAction = withDynamicSchemaProps((props) => {
const { className, ...others } = props;
const { className, targetComponent, iconColor, ...others } = props;
const { styles, cx } = useStyles() as any;
const fieldSchema = useFieldSchema();
const Component = useComponent(props?.targetComponent) || Action;

View File

@ -52,7 +52,7 @@ const ResponsiveSpace = () => {
return (
<Grid columns={itemsPerRow} gap={gap}>
{fieldSchema.mapProperties((s, key) => {
return <NocoBaseRecursionField name={key} schema={s} />;
return <NocoBaseRecursionField name={key} schema={s} key={key} />;
})}
</Grid>
);
@ -61,7 +61,7 @@ const ResponsiveSpace = () => {
return (
<Space wrap size={gap} align="start">
{fieldSchema.mapProperties((s, key) => {
return <NocoBaseRecursionField name={key} schema={s} />;
return <NocoBaseRecursionField name={key} schema={s} key={key} />;
})}
</Space>
);

View File

@ -45,6 +45,16 @@ export const workbenchActionSettingsLink = new SchemaSettings({
name: 'editLink',
Component: SchemaSettingsActionLinkItem,
},
{
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
};
},
},
{
...SchemaSettingAccessControl,
useVisible() {

View File

@ -15,6 +15,9 @@ import {
SchemaSettingsModalItem,
useOpenModeContext,
SchemaSettingsItemType,
useCollection,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import { CustomSchemaSettingsBlockTitleItem } from './SchemaSettingsBlockTitleItem';
import React from 'react';
@ -136,6 +139,17 @@ export const workbenchBlockSettings = new SchemaSettings({
name: 'setTheBlockHeight',
Component: SchemaSettingsBlockHeightItem,
},
{
name: 'blockLinkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { t } = useTranslation();
return {
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},
{
name: 'layout',
Component: ActionPanelLayout,

View File

@ -25,6 +25,9 @@ import {
useFormBlockContext,
usePopupSettings,
useApp,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
useCollection_deprecated,
} from '@nocobase/client';
import React, { useMemo } from 'react';
import { useTranslation } from '../../locale';
@ -65,6 +68,19 @@ export const calendarBlockSettings = 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: 'titleField',
Component: SchemaSettingsSelectItem,

View File

@ -14,7 +14,10 @@ import {
SchemaSettingsSwitchItem,
useDesignable,
useToken,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import { useChartsTranslation } from '../locale';
import { useField, useFieldSchema } from '@formily/react';
@ -25,6 +28,18 @@ export const chartBlockSettings = new SchemaSettings({
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'blockLinkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { t } = useTranslation();
return {
collectionName: name,
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},
{
name: 'background',
Component: () => {

View File

@ -22,7 +22,10 @@ import {
useCompile,
useDesignable,
useFormBlockContext,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import { useGanttBlockContext } from './GanttBlockProvider';
import { useGanttTranslation, useOptions } from './utils';
@ -37,6 +40,19 @@ export const oldGanttSettings = new SchemaSettings({
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'blockLinkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { name } = useCollection_deprecated();
const { t } = useTranslation();
return {
collectionName: name,
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},
{
name: 'titleField',
Component: SchemaSettingsSelectItem,
@ -277,6 +293,19 @@ export const ganttSettings = new SchemaSettings({
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'blockLinkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { name } = useCollection_deprecated();
const { t } = useTranslation();
return {
collectionName: name,
title: t('Block Linkage rules'),
category: LinkageRuleCategory.block,
};
},
},
{
name: 'titleField',
Component: SchemaSettingsSelectItem,

View File

@ -21,6 +21,8 @@ import {
useDesignable,
useFormBlockContext,
SchemaSettingsLayoutItem,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import { useKanbanBlockContext } from './KanbanBlockProvider';
@ -36,6 +38,19 @@ export const kanbanSettings = 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: 'dataScope',
Component: SchemaSettingsDataScope,

View File

@ -27,8 +27,11 @@ import {
useDesignable,
useFormBlockContext,
useColumnSchema,
SchemaSettingsLinkageRules,
LinkageRuleCategory,
} from '@nocobase/client';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useMapTranslation } from '../locale';
import { useMapBlockContext } from './MapBlockProvider';
import { findNestedOption } from './utils';
@ -89,6 +92,19 @@ export const mapBlockSettings = 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: 'mapField',
Component: SchemaSettingsCascaderItem,