@nocobase/client-v2 2.1.0-alpha.30 → 2.1.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/es/flow/actions/linkageRulesFormValueRefresh.d.ts +10 -0
  2. package/es/flow/index.d.ts +1 -0
  3. package/es/flow/models/actions/AssociateActionModel.d.ts +19 -0
  4. package/es/flow/models/actions/AssociationActionUtils.d.ts +17 -0
  5. package/es/flow/models/actions/DisassociateActionModel.d.ts +16 -0
  6. package/es/flow/models/actions/index.d.ts +3 -0
  7. package/es/flow/models/base/GridModel.d.ts +3 -1
  8. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +15 -6
  9. package/es/flow/models/blocks/shared/filterOperators.d.ts +9 -0
  10. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +1 -0
  11. package/es/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.d.ts +9 -0
  12. package/es/flow-compat/data.d.ts +9 -2
  13. package/es/flow-compat/index.d.ts +1 -1
  14. package/es/index.d.ts +1 -0
  15. package/es/index.mjs +90 -90
  16. package/lib/index.js +87 -87
  17. package/package.json +5 -5
  18. package/src/BaseApplication.tsx +1 -1
  19. package/src/__tests__/app.test.tsx +23 -6
  20. package/src/__tests__/globalDeps.test.ts +5 -0
  21. package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +438 -0
  22. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +42 -0
  23. package/src/flow/actions/linkageRules.tsx +8 -1
  24. package/src/flow/actions/linkageRulesFormValueRefresh.ts +492 -0
  25. package/src/flow/actions/linkageRulesRefresh.tsx +4 -2
  26. package/src/flow/actions/titleField.tsx +8 -3
  27. package/src/flow/components/FieldAssignValueInput.tsx +1 -0
  28. package/src/flow/components/filter/LinkageFilterItem.tsx +6 -5
  29. package/src/flow/components/filter/VariableFilterItem.tsx +14 -13
  30. package/src/flow/components/filter/__tests__/LinkageFilterItem.test.tsx +33 -0
  31. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +48 -5
  32. package/src/flow/index.ts +1 -0
  33. package/src/flow/internal/utils/__tests__/titleFieldQuickSync.test.ts +1 -0
  34. package/src/flow/internal/utils/titleFieldQuickSync.ts +2 -2
  35. package/src/flow/models/actions/AssociateActionModel.tsx +196 -0
  36. package/src/flow/models/actions/AssociationActionUtils.ts +90 -0
  37. package/src/flow/models/actions/DisassociateActionModel.tsx +57 -0
  38. package/src/flow/models/actions/FilterActionModel.tsx +17 -9
  39. package/src/flow/models/actions/__tests__/AssociationActionModel.test.ts +250 -0
  40. package/src/flow/models/actions/index.ts +3 -0
  41. package/src/flow/models/base/GridModel.tsx +21 -1
  42. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +98 -0
  43. package/src/flow/models/blocks/details/DetailsItemModel.tsx +3 -0
  44. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +200 -36
  45. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +270 -1
  46. package/src/flow/models/blocks/filter-form/__tests__/customFieldOperators.test.tsx +23 -0
  47. package/src/flow/models/blocks/filter-form/customFieldOperators.ts +12 -1
  48. package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +22 -8
  49. package/src/flow/models/blocks/filter-form/fields/__tests__/FilterFormCustomFieldModel.recordSelect.test.tsx +18 -0
  50. package/src/flow/models/blocks/filter-manager/FilterManager.ts +51 -1
  51. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +75 -0
  52. package/src/flow/models/blocks/form/FormItemModel.tsx +48 -28
  53. package/src/flow/models/blocks/shared/filterOperators.ts +14 -0
  54. package/src/flow/models/blocks/table/TableBlockModel.tsx +19 -3
  55. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +5 -1
  56. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +21 -5
  57. package/src/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.ts +20 -0
  58. package/src/flow/models/fields/DividerItemModel.tsx +30 -15
  59. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +11 -3
  60. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +235 -0
  61. package/src/flow-compat/data.ts +25 -3
  62. package/src/flow-compat/index.ts +7 -1
  63. package/src/index.ts +1 -0
  64. package/src/utils/globalDeps.ts +6 -0
@@ -121,6 +121,8 @@ describe('VariableFilterItem', () => {
121
121
  // Ensure document body for antd portals if needed
122
122
  document.body.innerHTML = '';
123
123
  delete (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
124
+ delete (globalThis as any).__TEST_PATH__;
125
+ delete (globalThis as any).__TEST_META__;
124
126
  });
125
127
 
126
128
  it('returns undefined path for empty left value in converter', () => {
@@ -153,9 +155,53 @@ describe('VariableFilterItem', () => {
153
155
  expect(value.value).toBe('abc');
154
156
  });
155
157
 
158
+ it('uses scoped context dataSourceManager when app dataSourceManager has no field interface manager', async () => {
159
+ const value = observable({ path: '', operator: '', value: '' }) as any;
160
+ const model = CreateModel();
161
+ const getRuntimeFieldInterface = vi.fn((name: string) => ({
162
+ name,
163
+ filterable: {
164
+ operators: [{ value: '$eq', label: 'Equals' }],
165
+ children: [
166
+ {
167
+ name: 'runtimeChild',
168
+ title: 'Runtime child',
169
+ schema: { 'x-component': 'Input' },
170
+ operators: [{ value: '$includes', label: 'contains' }],
171
+ },
172
+ ],
173
+ },
174
+ }));
175
+
176
+ model.context.dataSourceManager.setCollectionFieldInterfaceManager({
177
+ getFieldInterface: getRuntimeFieldInterface,
178
+ });
179
+ (model.context.app as any).dataSourceManager = {};
180
+ (globalThis as any).__TEST_PATH__ = 'assignee';
181
+ (globalThis as any).__TEST_META__ = {
182
+ interface: 'belongsTo',
183
+ uiSchema: { 'x-component': 'RecordPicker' },
184
+ paths: ['collection', 'assignee'],
185
+ name: 'assignee',
186
+ title: 'Assignee',
187
+ type: 'object',
188
+ };
189
+
190
+ render(<VariableFilterItem value={value} model={model} rightAsVariable={false} />);
191
+ const leftVariableInputProps = (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
192
+ const metaTree = await leftVariableInputProps.metaTree();
193
+ const nameNode = metaTree.find((node: any) => node.name === 'name');
194
+ expect(nameNode?.children?.some((child: any) => child.name === 'runtimeChild')).toBe(true);
195
+
196
+ fireEvent.click(screen.getByTestId('variable-input'));
197
+
198
+ await waitFor(() => {
199
+ expect(value.operator).toBe('$eq');
200
+ expect(getRuntimeFieldInterface).toHaveBeenCalledWith('belongsTo');
201
+ });
202
+ });
203
+
156
204
  it('keeps numeric string when x-component uses InputNumber with stringMode', async () => {
157
- const prevMeta = (globalThis as any).__TEST_META__;
158
- const prevPath = (globalThis as any).__TEST_PATH__;
159
205
  (globalThis as any).__TEST_PATH__ = 'price';
160
206
  (globalThis as any).__TEST_META__ = {
161
207
  interface: 'input',
@@ -177,9 +223,6 @@ describe('VariableFilterItem', () => {
177
223
  expect(input.value).toBe('123.45');
178
224
  expect(value.value).toBe('123.45');
179
225
  });
180
-
181
- (globalThis as any).__TEST_META__ = prevMeta;
182
- (globalThis as any).__TEST_PATH__ = prevPath;
183
226
  });
184
227
 
185
228
  it('normalizes synthetic event value when formula field renders Input from app components', async () => {
package/src/flow/index.ts CHANGED
@@ -104,5 +104,6 @@ export * from './admin-shell/AdminLayoutRouteCoordinator';
104
104
  export * from '../settings-center';
105
105
  export { openViewFlow } from './flows/openViewFlow';
106
106
  export { editMarkdownFlow } from './flows/editMarkdownFlow';
107
+ export { resolveDynamicNamePath } from './models/blocks/form/value-runtime/path';
107
108
 
108
109
  export { TextAreaWithContextSelector } from './components/TextAreaWithContextSelector';
@@ -26,6 +26,7 @@ describe('titleFieldQuickSync', () => {
26
26
  expect(isTitleUsableField(dm, { interface: 'formula' } as any)).toBe(false);
27
27
  expect(isTitleUsableField(dm, { interface: 'unknown' } as any)).toBe(false);
28
28
  expect(isTitleUsableField(undefined, { interface: 'input' } as any)).toBe(false);
29
+ expect(isTitleUsableField({} as any, { interface: 'input' } as any)).toBe(false);
29
30
  });
30
31
 
31
32
  it('syncs title field to main data source collection', async () => {
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { DataSourceManager } from '@nocobase/flow-engine';
10
+ import { DataSourceManager, getCollectionFieldInterface } from '@nocobase/flow-engine';
11
11
  import type { CollectionFieldOptions } from '../../../flow-compat';
12
12
  import { DEFAULT_DATA_SOURCE_KEY } from '../../../flow-compat';
13
13
 
@@ -33,7 +33,7 @@ export function isTitleUsableField(
33
33
  ): boolean {
34
34
  const ifaceName = typeof field?.interface === 'string' ? field.interface : undefined;
35
35
  if (!dm || !ifaceName) return false;
36
- return !!dm.collectionFieldInterfaceManager.getFieldInterface(ifaceName)?.titleUsable;
36
+ return !!getCollectionFieldInterface(ifaceName, dm)?.titleUsable;
37
37
  }
38
38
 
39
39
  function resolveDataSourceKey(targetCollection: CollectionLike | null | undefined): string {
@@ -0,0 +1,196 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { FlowModel, FlowModelRenderer, tExpr, useFlowViewContext } from '@nocobase/flow-engine';
11
+ import { useRequest } from 'ahooks';
12
+ import { Button } from 'antd';
13
+ import type { ButtonProps } from 'antd';
14
+ import React from 'react';
15
+ import { SkeletonFallback } from '../../components/SkeletonFallback';
16
+ import { ActionModel, ActionSceneEnum } from '../base';
17
+ import {
18
+ applyAssociateAction,
19
+ getAssociationTargetResourceSettings,
20
+ isAssociationBlockContext,
21
+ } from './AssociationActionUtils';
22
+
23
+ function AssociateSelectorGridRenderer({ options }: { options: any }) {
24
+ const ctx = useFlowViewContext();
25
+ const { data, loading } = useRequest(
26
+ async () => {
27
+ return await ctx.engine.loadOrCreateModel(options, {
28
+ delegateToParent: false,
29
+ delegate: ctx,
30
+ skipSave: !ctx.flowSettingsEnabled,
31
+ });
32
+ },
33
+ {
34
+ refreshDeps: [ctx, options],
35
+ },
36
+ );
37
+
38
+ if (loading || !data?.uid) {
39
+ return <SkeletonFallback style={{ margin: 16 }} />;
40
+ }
41
+ return <FlowModelRenderer model={data as FlowModel} fallback={<SkeletonFallback style={{ margin: 16 }} />} />;
42
+ }
43
+
44
+ function AssociateSelectorContent({ model }: { model: AssociateActionModel }) {
45
+ const ctx = useFlowViewContext();
46
+ const { Header, Footer, type } = ctx.view;
47
+ return (
48
+ <div>
49
+ <Header
50
+ title={
51
+ type === 'dialog' ? (
52
+ <div
53
+ style={{
54
+ padding: `${ctx.themeToken.paddingLG}px ${ctx.themeToken.paddingLG}px 0`,
55
+ marginBottom: -ctx.themeToken.marginSM,
56
+ backgroundColor: 'var(--colorBgLayout)',
57
+ }}
58
+ >
59
+ {ctx.t('Select record')}
60
+ </div>
61
+ ) : (
62
+ ctx.t('Select record')
63
+ )
64
+ }
65
+ />
66
+ <AssociateSelectorGridRenderer
67
+ options={{
68
+ parentId: ctx.view.inputArgs.parentId,
69
+ subKey: 'associate-selector-grid',
70
+ async: true,
71
+ delegateToParent: false,
72
+ subType: 'object',
73
+ use: 'BlockGridModel',
74
+ }}
75
+ />
76
+ <Footer>
77
+ {type === 'dialog' ? (
78
+ <div style={{ padding: `0 ${ctx.themeToken.paddingLG}px ${ctx.themeToken.paddingLG}px` }}>
79
+ <Button
80
+ type="primary"
81
+ onClick={async () => {
82
+ await model.associateSelectedRows();
83
+ ctx.view.close();
84
+ }}
85
+ >
86
+ {ctx.t('Submit')}
87
+ </Button>
88
+ </div>
89
+ ) : (
90
+ <Button
91
+ type="primary"
92
+ onClick={async () => {
93
+ await model.associateSelectedRows();
94
+ ctx.view.close();
95
+ }}
96
+ >
97
+ {ctx.t('Submit')}
98
+ </Button>
99
+ )}
100
+ </Footer>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ export class AssociateActionModel extends ActionModel {
106
+ static scene = ActionSceneEnum.collection;
107
+ static capabilityActionName = 'update';
108
+
109
+ defaultPopupTitle = tExpr('Select record');
110
+ selectedRows: any[] = [];
111
+
112
+ defaultProps: ButtonProps = {
113
+ title: tExpr('Associate'),
114
+ icon: 'LinkOutlined',
115
+ };
116
+
117
+ getAclActionName() {
118
+ return 'update';
119
+ }
120
+
121
+ async associateSelectedRows() {
122
+ await applyAssociateAction(this.context, this.selectedRows);
123
+ this.selectedRows = [];
124
+ }
125
+ }
126
+
127
+ AssociateActionModel.define({
128
+ label: tExpr('Associate'),
129
+ sort: 15,
130
+ hide(ctx) {
131
+ return !isAssociationBlockContext(ctx);
132
+ },
133
+ });
134
+
135
+ AssociateActionModel.registerFlow({
136
+ key: 'associateSettings',
137
+ title: tExpr('Associate settings'),
138
+ on: 'click',
139
+ steps: {
140
+ openSelector: {
141
+ async handler(ctx, params) {
142
+ const blockModel = ctx.blockModel;
143
+ const targetResourceSettings = getAssociationTargetResourceSettings(ctx);
144
+ const openMode = ctx.inputArgs?.isMobileLayout ? 'embed' : ctx.inputArgs?.mode || params?.mode || 'drawer';
145
+ const size = ctx.inputArgs?.size || params?.size || 'medium';
146
+ const sizeToWidthMap: Record<string, Record<string, string | undefined>> = {
147
+ drawer: {
148
+ small: '30%',
149
+ medium: '50%',
150
+ large: '70%',
151
+ },
152
+ dialog: {
153
+ small: '40%',
154
+ medium: '50%',
155
+ large: '80%',
156
+ },
157
+ embed: {},
158
+ };
159
+
160
+ ctx.model.selectedRows = [];
161
+ await ctx.viewer.open({
162
+ type: openMode,
163
+ width: sizeToWidthMap[openMode][size],
164
+ inheritContext: false,
165
+ target: ctx.layoutContentElement,
166
+ inputArgs: {
167
+ parentId: ctx.model.uid,
168
+ scene: 'select',
169
+ dataSourceKey: targetResourceSettings.dataSourceKey,
170
+ collectionName: targetResourceSettings.collectionName,
171
+ rowSelectionProps: {
172
+ type: 'checkbox',
173
+ defaultSelectedRows: () => blockModel?.resource?.getData?.() || [],
174
+ renderCell: undefined,
175
+ selectedRowKeys: undefined,
176
+ onChange: (_, selectedRows) => {
177
+ ctx.model.selectedRows = selectedRows || [];
178
+ },
179
+ },
180
+ },
181
+ content: () => <AssociateSelectorContent model={ctx.model as AssociateActionModel} />,
182
+ styles: {
183
+ content: {
184
+ padding: 0,
185
+ backgroundColor: ctx.model.flowEngine.context.themeToken.colorBgLayout,
186
+ ...(openMode === 'embed' ? { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 } : {}),
187
+ },
188
+ body: {
189
+ padding: 0,
190
+ },
191
+ },
192
+ });
193
+ },
194
+ },
195
+ },
196
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import type { FlowModelContext } from '@nocobase/flow-engine';
11
+
12
+ export const getAssociationBlockResourceSettings = (ctx: FlowModelContext | any) => {
13
+ const blockModel = ctx?.blockModel || ctx?.model?.context?.blockModel;
14
+ return (
15
+ blockModel?.getResourceSettingsInitParams?.() ||
16
+ blockModel?.getStepParams?.('resourceSettings', 'init') ||
17
+ ctx?.model?.getStepParams?.('resourceSettings', 'init')
18
+ );
19
+ };
20
+
21
+ export const isAssociationBlockContext = (ctx: FlowModelContext | any) => {
22
+ return !!getAssociationBlockResourceSettings(ctx)?.associationName;
23
+ };
24
+
25
+ export const getAssociationTargetResourceSettings = (ctx: FlowModelContext | any) => {
26
+ const resourceSettings = getAssociationBlockResourceSettings(ctx);
27
+ const association = ctx?.blockModel?.association || ctx?.model?.context?.blockModel?.association;
28
+ const targetCollection = association?.targetCollection;
29
+
30
+ return {
31
+ dataSourceKey: targetCollection?.dataSourceKey || resourceSettings?.dataSourceKey,
32
+ collectionName: targetCollection?.name || association?.target || resourceSettings?.collectionName,
33
+ };
34
+ };
35
+
36
+ const callAssociationResourceAction = async (resource: any, action: 'add' | 'remove', values: any[]) => {
37
+ if (typeof resource?.[action] === 'function') {
38
+ return await resource[action]({ values });
39
+ }
40
+
41
+ return await resource?.runAction?.(action, {
42
+ data: values,
43
+ });
44
+ };
45
+
46
+ export const applyDisassociateAction = async (ctx: FlowModelContext | any) => {
47
+ const resource = ctx?.blockModel?.resource || ctx?.resource;
48
+ const collection = ctx?.blockModel?.collection || ctx?.collection;
49
+
50
+ if (!isAssociationBlockContext(ctx)) {
51
+ ctx.message?.error?.(ctx.t('No association block selected'));
52
+ return;
53
+ }
54
+ if (!resource) {
55
+ ctx.message?.error?.(ctx.t('No resource selected for disassociation'));
56
+ return;
57
+ }
58
+ if (!ctx.record) {
59
+ ctx.message?.error?.(ctx.t('No record selected for disassociation'));
60
+ return;
61
+ }
62
+
63
+ const filterByTk = collection?.getFilterByTK?.(ctx.record);
64
+ await callAssociationResourceAction(resource, 'remove', [filterByTk]);
65
+ await resource.refresh?.();
66
+ ctx.message?.success?.(ctx.t('Record disassociated successfully'));
67
+ };
68
+
69
+ export const applyAssociateAction = async (ctx: FlowModelContext | any, selectedRows: any[]) => {
70
+ const resource = ctx?.blockModel?.resource || ctx?.resource;
71
+ const collection = ctx?.blockModel?.collection || ctx?.collection;
72
+
73
+ if (!isAssociationBlockContext(ctx)) {
74
+ ctx.message?.error?.(ctx.t('No association block selected'));
75
+ return;
76
+ }
77
+ if (!resource) {
78
+ ctx.message?.error?.(ctx.t('No resource selected for association'));
79
+ return;
80
+ }
81
+ if (!selectedRows?.length) {
82
+ ctx.message?.warning?.(ctx.t('Please select at least one record'));
83
+ return;
84
+ }
85
+
86
+ const values = selectedRows.map((row) => collection?.getFilterByTK?.(row) ?? row).filter((value) => value != null);
87
+ await callAssociationResourceAction(resource, 'add', values);
88
+ await resource.refresh?.();
89
+ ctx.message?.success?.(ctx.t('Record associated successfully'));
90
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { tExpr } from '@nocobase/flow-engine';
11
+ import type { ButtonProps } from 'antd';
12
+ import { ActionModel, ActionSceneEnum } from '../base';
13
+ import { applyDisassociateAction, isAssociationBlockContext } from './AssociationActionUtils';
14
+
15
+ export class DisassociateActionModel extends ActionModel {
16
+ static scene = ActionSceneEnum.record;
17
+ static capabilityActionName = 'update';
18
+
19
+ defaultProps: ButtonProps = {
20
+ type: 'link',
21
+ title: tExpr('Disassociate'),
22
+ icon: 'DisconnectOutlined',
23
+ };
24
+
25
+ getAclActionName() {
26
+ return 'update';
27
+ }
28
+ }
29
+
30
+ DisassociateActionModel.define({
31
+ label: tExpr('Disassociate'),
32
+ sort: 65,
33
+ hide(ctx) {
34
+ return !isAssociationBlockContext(ctx);
35
+ },
36
+ });
37
+
38
+ DisassociateActionModel.registerFlow({
39
+ key: 'disassociateSettings',
40
+ title: tExpr('Disassociate settings'),
41
+ on: 'click',
42
+ steps: {
43
+ confirm: {
44
+ use: 'confirm',
45
+ defaultParams: {
46
+ enable: true,
47
+ title: tExpr('Disassociate record'),
48
+ content: tExpr('Are you sure you want to disassociate it?'),
49
+ },
50
+ },
51
+ disassociate: {
52
+ async handler(ctx) {
53
+ await applyDisassociateAction(ctx);
54
+ },
55
+ },
56
+ },
57
+ });
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { tExpr, MobilePopup, MultiRecordResource, useFlowSettingsContext } from '@nocobase/flow-engine';
11
+ import type { CollectionFieldInterfaceDataSourceManager } from '@nocobase/flow-engine';
11
12
  import { isEmptyFilter, transformFilter } from '@nocobase/utils/client';
12
13
  import { ButtonProps, Popover, Transfer } from 'antd';
13
14
  import React from 'react';
@@ -15,6 +16,7 @@ import { FilterGroup, VariableFilterItem } from '../../components/filter';
15
16
  import { ActionModel, CollectionBlockModel } from '../base';
16
17
  import { FilterContainer } from '../../components/filter/FilterContainer';
17
18
  import _ from 'lodash';
19
+ import { getFlowFieldInterfaceOptions } from '../../../flow-compat';
18
20
 
19
21
  export class FilterActionModel extends ActionModel {
20
22
  static scene = 'collection';
@@ -135,9 +137,11 @@ FilterActionModel.registerFlow({
135
137
  'x-component': (props) => {
136
138
  // eslint-disable-next-line react-hooks/rules-of-hooks
137
139
  const { model } = useFlowSettingsContext();
138
- const dm = model?.context?.app?.dataSourceManager;
139
- const fiMgr = dm?.collectionFieldInterfaceManager;
140
- const filterable = getFilterableFields(model.context.blockModel.collection, fiMgr);
140
+ const filterable = getFilterableFields(
141
+ model.context.blockModel.collection,
142
+ model.context.dataSourceManager,
143
+ model.context.app?.dataSourceManager,
144
+ );
141
145
  const dataSource = filterable.map((field: any) => ({ key: field.name, title: field.title }));
142
146
  return (
143
147
  <Transfer
@@ -157,9 +161,11 @@ FilterActionModel.registerFlow({
157
161
  },
158
162
  defaultParams(ctx) {
159
163
  // 默认仅包含“可筛选”的字段(与 1.0 一致),以避免 JSON 等未提供 operators 的字段出现在默认允许集合中
160
- const dm = ctx?.model?.context?.app?.dataSourceManager;
161
- const fiMgr = dm?.collectionFieldInterfaceManager;
162
- const names = getFilterableFields(ctx.blockModel.collection, fiMgr).map((field: any) => field.name);
164
+ const names = getFilterableFields(
165
+ ctx.blockModel.collection,
166
+ ctx.model?.context?.dataSourceManager,
167
+ ctx.model?.context?.app?.dataSourceManager,
168
+ ).map((field: any) => field.name);
163
169
  return {
164
170
  filterableFieldNames: names || [],
165
171
  };
@@ -304,13 +310,15 @@ FilterActionModel.registerFlow({
304
310
  },
305
311
  });
306
312
 
307
- function getFilterableFields(collection: any, fiMgr: any) {
313
+ function getFilterableFields(
314
+ collection: any,
315
+ ...dataSourceManagers: Array<CollectionFieldInterfaceDataSourceManager | null | undefined>
316
+ ) {
308
317
  const fields = collection?.getFields?.() || [];
309
- if (!fiMgr) return [];
310
318
  return fields.filter((field: any) => {
311
319
  if (!field?.interface) return false;
312
320
  if (field?.filterable === false) return false;
313
- const fi = fiMgr.getFieldInterface(field.interface);
321
+ const fi = getFlowFieldInterfaceOptions(field.interface, ...dataSourceManagers);
314
322
  return !!fi?.filterable;
315
323
  });
316
324
  }