diff --git a/packages/core/client/src/data-source/collection-field/CollectionField.tsx b/packages/core/client/src/data-source/collection-field/CollectionField.tsx
index 4bb5f6df4f..850937fa90 100644
--- a/packages/core/client/src/data-source/collection-field/CollectionField.tsx
+++ b/packages/core/client/src/data-source/collection-field/CollectionField.tsx
@@ -83,6 +83,8 @@ const CollectionFieldInternalField_deprecated: React.FC = (props: Props) => {
setRequired(field, fieldSchema, uiSchema);
// @ts-ignore
field.dataSource = uiSchema.enum;
+ field.data = field.data || {};
+ field.data.dataSource = uiSchema.enum;
const originalProps = compile(uiSchema['x-component-props']) || {};
field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {});
}, [uiSchemaOrigin]);
@@ -109,6 +111,8 @@ const CollectionFieldInternalField = (props) => {
field.readPretty = true;
}
}, [field, fieldSchema]);
+ field.data = field.data || {};
+ field.data.dataSource = uiSchema.enum;
if (!uiSchema) return null;
diff --git a/packages/core/client/src/modules/fields/component/Select/__e2e__/selectOptionsInLinkageRule.test.ts b/packages/core/client/src/modules/fields/component/Select/__e2e__/selectOptionsInLinkageRule.test.ts
new file mode 100644
index 0000000000..0684ebf21a
--- /dev/null
+++ b/packages/core/client/src/modules/fields/component/Select/__e2e__/selectOptionsInLinkageRule.test.ts
@@ -0,0 +1,33 @@
+/**
+ * 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 { expect, test } from '@nocobase/test/e2e';
+import { oneFormWithSelectField } from './templatesOfBug';
+
+test.describe('options of Select field in linkage rule', () => {
+ test('options change with linkage rule ', async ({ page, mockPage }) => {
+ await mockPage(oneFormWithSelectField).goto();
+ // 联动规则控制选项
+ await page.getByLabel('block-item-CardItem-general-').hover();
+ await page.getByLabel('block-item-CollectionField-').click();
+ await expect(page.getByRole('option', { name: 'option2' })).toBeVisible();
+ await expect(page.getByRole('option', { name: 'option3' })).not.toBeVisible();
+ await page.getByRole('option', { name: 'option2' }).click();
+
+ // 去掉联动规则恢复选项
+ await page.getByLabel('block-item-CardItem-general-').hover();
+ await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-general').hover();
+ await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
+ await page.getByRole('switch', { name: 'On Off' }).click();
+ await page.getByRole('button', { name: 'OK' }).click();
+ await page.reload();
+ await expect(page.getByRole('option', { name: 'option2' })).toBeVisible();
+ await expect(page.getByRole('option', { name: 'option3' })).toBeVisible();
+ });
+});
diff --git a/packages/core/client/src/modules/fields/component/Select/__e2e__/templatesOfBug.ts b/packages/core/client/src/modules/fields/component/Select/__e2e__/templatesOfBug.ts
index 1d90fe8ebf..dc2e7f36cf 100644
--- a/packages/core/client/src/modules/fields/component/Select/__e2e__/templatesOfBug.ts
+++ b/packages/core/client/src/modules/fields/component/Select/__e2e__/templatesOfBug.ts
@@ -7,6 +7,8 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
+import { generalWithM2oSingleSelect } from '@nocobase/test/e2e';
+
export const T3867 = {
pageSchema: {
_isJSONSchemaObject: true,
@@ -581,3 +583,176 @@ export const oneFormWithSubTableSelectField = {
},
],
};
+
+export const oneFormWithSelectField = {
+ pageSchema: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Page',
+ properties: {
+ iza2br2wzq4: {
+ type: 'void',
+ 'x-component': 'Grid',
+ 'x-initializer': 'page:addBlock',
+ properties: {
+ dc4lkvej93w: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Grid.Row',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ jrfai5z5a4e: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Grid.Col',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ bph3a0yrmvp: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-acl-action-props': {
+ skipScopeCheck: true,
+ },
+ 'x-acl-action': 'general:create',
+ 'x-decorator': 'FormBlockProvider',
+ 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
+ 'x-decorator-props': {
+ dataSource: 'main',
+ collection: 'general',
+ },
+ 'x-toolbar': 'BlockSchemaToolbar',
+ 'x-settings': 'blockSettings:createForm',
+ 'x-component': 'CardItem',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ '2ef5ea23d4b': {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'FormV2',
+ 'x-use-component-props': 'useCreateFormBlockProps',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ grid: {
+ 'x-uid': 'oqk574y4mx2',
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Grid',
+ 'x-initializer': 'form:configureFields',
+ 'x-app-version': '1.7.0-beta.1',
+ 'x-linkage-rules': [
+ {
+ condition: {
+ $and: [],
+ },
+ actions: [
+ {
+ targetFields: ['singleSelect'],
+ operator: 'options',
+ value: {
+ value: ['option2'],
+ },
+ },
+ ],
+ },
+ ],
+ properties: {
+ '1ijfrrxqs4c': {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Grid.Row',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ '8j09c65cp2x': {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-component': 'Grid.Col',
+ 'x-app-version': '1.7.0-beta.1',
+ properties: {
+ singleSelect: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'string',
+ 'x-toolbar': 'FormItemSchemaToolbar',
+ 'x-settings': 'fieldSettings:FormItem',
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-collection-field': 'general.singleSelect',
+ 'x-component-props': {
+ style: {
+ width: '100%',
+ },
+ },
+ 'x-app-version': '1.7.0-beta.1',
+ 'x-uid': 'dxsbc80ewso',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 'la6qedc9qwx',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 'ijsbxa4yys7',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ kpp1azsxbzy: {
+ _isJSONSchemaObject: true,
+ version: '2.0',
+ type: 'void',
+ 'x-initializer': 'createForm:configureActions',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ layout: 'one-column',
+ },
+ 'x-app-version': '1.7.0-beta.1',
+ 'x-uid': '1n56lepnve8',
+ 'x-async': false,
+ 'x-index': 2,
+ },
+ },
+ 'x-uid': 'lf74cpc3tub',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 'mhxlfdk74jy',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 'obyvsr0s0nx',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 's6jd8p9h28q',
+ 'x-async': false,
+ 'x-index': 1,
+ },
+ },
+ name: '8btun7jzkrk',
+ 'x-uid': 'eyjf89l83a1',
+ 'x-async': true,
+ 'x-index': 1,
+ },
+ },
+ 'x-uid': 'hizrr7jzogr',
+ 'x-async': true,
+ 'x-index': 1,
+ },
+ collections: generalWithM2oSingleSelect,
+};
diff --git a/packages/core/client/src/schema-component/antd/select/Select.tsx b/packages/core/client/src/schema-component/antd/select/Select.tsx
index 269beecddd..e628256791 100644
--- a/packages/core/client/src/schema-component/antd/select/Select.tsx
+++ b/packages/core/client/src/schema-component/antd/select/Select.tsx
@@ -14,6 +14,7 @@ import { isPlainObject } from '@nocobase/utils/client';
import type { SelectProps as AntdSelectProps } from 'antd';
import { Select as AntdSelect, Empty, Spin, Tag } from 'antd';
import React from 'react';
+import { every } from 'lodash';
import { ReadPretty } from './ReadPretty';
import { FieldNames, defaultFieldNames, getCurrentOptions } from './utils';
import { BaseOptionType, DefaultOptionType } from 'antd/es/select';
@@ -188,8 +189,13 @@ const InternalSelect = connect(
dataSource: 'options',
},
(props, field) => {
+ const { value, options } = props;
+ const result = every(options, (k) => k.value !== value)
+ ? field?.data?.dataSource?.find?.((v) => v.value === value)?.label || value
+ : value;
return {
...props,
+ value: result,
fieldNames: { ...defaultFieldNames, ...props.fieldNames },
suffixIcon: field?.['loading'] || field?.['validating'] ? : props.suffixIcon,
};
diff --git a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx
index f238c0a60e..06818fd0e7 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx
+++ b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
import { useCompile } from '../..';
import { DynamicComponent } from './DynamicComponent';
import { ValueDynamicComponent } from './ValueDynamicComponent';
+import { OptionsComponent } from './OptionsComponent';
import { LinkageLogicContext, RemoveActionContext } from './context';
import { ActionType } from './type';
import { useValues } from './useValues';
@@ -107,6 +108,14 @@ export const FormFieldLinkageRuleAction = observer(
collectionName={collectionName}
/>
)}
+ {[ActionType.Options].includes(operator) && (
+
+ )}
{!props.disabled && (
remove()} style={{ color: '#bfbfbf' }} />
diff --git a/packages/core/client/src/schema-settings/LinkageRules/OptionsComponent.tsx b/packages/core/client/src/schema-settings/LinkageRules/OptionsComponent.tsx
new file mode 100644
index 0000000000..758f2e92a3
--- /dev/null
+++ b/packages/core/client/src/schema-settings/LinkageRules/OptionsComponent.tsx
@@ -0,0 +1,45 @@
+/**
+ * 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 { css } from '@emotion/css';
+import React, { useCallback, useMemo } from 'react';
+import { Select } from 'antd';
+
+import { DynamicComponent } from './DynamicComponent';
+
+export type InputModeType = 'constant' | 'express' | 'empty';
+interface ValueDynamicComponentProps {
+ fieldValue: any;
+ schema: any;
+ setValue: (value: any) => void;
+ collectionName: string;
+ inputModes?: Array;
+}
+
+export const OptionsComponent = (props: ValueDynamicComponentProps) => {
+ const { fieldValue, setValue, schema } = props;
+ const handleChangeOfConstant = useCallback(
+ (value) => {
+ setValue({
+ value,
+ });
+ },
+ [setValue],
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts b/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts
index 38728bd0fb..0ffa636052 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/action-hooks.ts
@@ -27,6 +27,12 @@ export const useLinkageCollectionFieldOptions = (collectionName: string, readPre
{ label: t('Required'), value: ActionType.Required, selected: false, schema: {} },
{ label: t('Not required'), value: ActionType.InRequired, selected: false, schema: {} },
{ label: t('Value'), value: ActionType.Value, selected: false, schema: {} },
+ {
+ label: t('Options'),
+ value: ActionType.Options,
+ selected: false,
+ schema: {},
+ },
].filter((v) => {
if (readPretty) {
return [ActionType.Visible, ActionType.None, ActionType.Hidden].includes(v.value);
@@ -48,7 +54,10 @@ export const useLinkageCollectionFieldOptions = (collectionName: string, readPre
operators:
operators?.filter?.((operator) => {
if (nested || children || ['formula', 'richText', 'sequence'].includes(fieldInterface.name)) {
- return operator?.value !== ActionType.Value;
+ return operator?.value !== ActionType.Value && operator?.value !== ActionType.Options;
+ }
+ if (!['select', 'radioGroup', 'multipleSelect', 'checkboxGroup'].includes(fieldInterface.name)) {
+ return operator?.value !== ActionType.Options;
}
return true;
}) || [],
diff --git a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
index cca6b7df71..47c3e61ccc 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
@@ -70,6 +70,7 @@ export function bindLinkageRulesToFiled(
required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false),
pattern: field.initStateOfLinkageRules?.pattern || getTempFieldState(true, field.pattern),
value: field.initStateOfLinkageRules?.value || getTempFieldState(true, field.value || field.initialValue),
+ dataSource: field.initStateOfLinkageRules?.dataSource || getTempFieldState(true, field.dataSource || field.options),
};
return reaction(
@@ -252,6 +253,17 @@ function getSubscriber(
state.display = 'visible';
});
});
+ } else if (fieldName === 'dataSource') {
+ if (_.every(lastState?.value, (v) => v.value !== field.value)) {
+ field.value = field.initialValue;
+ }
+ field[fieldName] = lastState?.value;
+ field.data = field.data || {};
+ requestAnimationFrame(() => {
+ field.setState((state) => {
+ state[fieldName] = lastState?.value;
+ });
+ });
} else {
field[fieldName] = lastState?.value;
field.data = field.data || {};
@@ -290,6 +302,8 @@ function getFieldNameByOperator(operator: ActionType) {
return 'pattern';
case ActionType.Value:
return 'value';
+ case ActionType.Options:
+ return 'dataSource';
default:
return null;
}
@@ -303,6 +317,7 @@ export const collectFieldStateOfLinkageRules = (
const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display];
const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern];
const valueResult = field?.stateOfLinkageRules?.value || [field?.initStateOfLinkageRules?.value];
+ const optionsResult = field?.stateOfLinkageRules?.dataSource || [field?.initStateOfLinkageRules?.dataSource];
const { evaluate } = evaluators.get('formula.js');
const paramsToGetConditionResult = { ruleGroup: condition, variables, localVariables, variableNameOfLeftCondition };
@@ -376,6 +391,16 @@ export const collectFieldStateOfLinkageRules = (
};
}
break;
+ case ActionType.Options:
+ {
+ const data = field.data?.dataSource?.filter((v) => value.value.includes(v.value));
+ optionsResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), data || []));
+ field.stateOfLinkageRules = {
+ ...field.stateOfLinkageRules,
+ dataSource: optionsResult,
+ };
+ }
+ break;
default:
return null;
}
diff --git a/packages/core/client/src/schema-settings/LinkageRules/type.ts b/packages/core/client/src/schema-settings/LinkageRules/type.ts
index f0cc31f984..a50fc97546 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/type.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/type.ts
@@ -22,6 +22,7 @@ export enum ActionType {
Color = 'color',
BackgroundColor = 'backgroundColor',
TextAlign = 'textAlign',
+ Options = 'options',
}
export enum LinkageRuleCategory {
diff --git a/packages/core/client/src/schema-settings/LinkageRules/useValues.ts b/packages/core/client/src/schema-settings/LinkageRules/useValues.ts
index 445a8d0e85..36ab6af699 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/useValues.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/useValues.ts
@@ -46,7 +46,7 @@ export const useValues = (options) => {
const operators = option?.operators;
field.data.operators = operators?.filter((v) => {
if (dataIndex.length > 1) {
- return v.value !== 'value';
+ return v.value !== 'value' && v.value !== 'options';
}
return true;
});
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
index c5f82ae6fa..eb3c172aec 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx
@@ -54,7 +54,6 @@ const InternalField: React.FC = (props) => {
}
field.required = true;
// @ts-ignore
- field.dataSource = uiSchema.enum;
const originalProps = compile(uiSchema['x-component-props']) || {};
const componentProps = merge(originalProps, field.componentProps || {});
field.componentProps = componentProps;
@@ -90,6 +89,7 @@ export const BulkEditField = (props: any) => {
const [value, setValue] = useState(null);
const { getField } = useCollection_deprecated();
const collectionField = getField(fieldSchema.name) || {};
+ const { uiSchema } = collectionField;
useEffect(() => {
field.value = toFormFieldValue({ [type]: value });
if (field.required) {
@@ -102,6 +102,12 @@ export const BulkEditField = (props: any) => {
}
}, [field, type, value]);
+ useEffect(() => {
+ field.dataSource = field.dataSource || uiSchema.enum;
+ field.data = field.data || {};
+ field.data.dataSource = uiSchema.enum;
+ }, [uiSchema]);
+
useEffect(() => {
if (field.value === null) {
setValue(undefined);