@nocobase/client-v2 2.1.0-beta.24 → 2.1.0-beta.26
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.
- package/es/BaseApplication.d.ts +1 -0
- package/es/flow/actions/dataScopeFilter.d.ts +9 -0
- package/es/flow/actions/linkageRulesFormValueRefresh.d.ts +10 -0
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/utils/rebuildFieldSubModel.d.ts +2 -1
- package/es/flow/models/actions/AssociateActionModel.d.ts +19 -0
- package/es/flow/models/actions/AssociationActionUtils.d.ts +17 -0
- package/es/flow/models/actions/DisassociateActionModel.d.ts +16 -0
- package/es/flow/models/actions/index.d.ts +3 -0
- package/es/flow/models/base/GridModel.d.ts +3 -1
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.d.ts +9 -0
- package/es/flow/models/fields/JSFieldModel.d.ts +5 -0
- package/es/index.d.ts +1 -0
- package/es/index.mjs +101 -101
- package/lib/index.js +99 -99
- package/package.json +6 -5
- package/src/BaseApplication.tsx +4 -0
- package/src/__tests__/globalDeps.test.ts +6 -0
- package/src/__tests__/remotePlugins.test.ts +27 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +158 -0
- package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +438 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +42 -0
- package/src/flow/actions/dataScope.tsx +6 -4
- package/src/flow/actions/dataScopeFilter.ts +70 -0
- package/src/flow/actions/linkageRules.tsx +8 -1
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +492 -0
- package/src/flow/actions/linkageRulesRefresh.tsx +4 -2
- package/src/flow/actions/setTargetDataScope.tsx +6 -5
- package/src/flow/index.ts +1 -0
- package/src/flow/internal/utils/__tests__/rebuildFieldSubModel.test.ts +77 -2
- package/src/flow/internal/utils/rebuildFieldSubModel.ts +21 -5
- package/src/flow/models/actions/AssociateActionModel.tsx +196 -0
- package/src/flow/models/actions/AssociationActionUtils.ts +90 -0
- package/src/flow/models/actions/DisassociateActionModel.tsx +57 -0
- package/src/flow/models/actions/__tests__/AssociationActionModel.test.ts +250 -0
- package/src/flow/models/actions/index.ts +3 -0
- package/src/flow/models/base/GridModel.tsx +21 -1
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +98 -0
- package/src/flow/models/blocks/details/DetailsItemModel.tsx +3 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +9 -5
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormBlockModel.cleanup.test.ts +138 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +22 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +30 -2
- package/src/flow/models/blocks/table/TableBlockModel.tsx +8 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +51 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +49 -0
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +5 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +21 -5
- package/src/flow/models/fields/AssociationFieldModel/recordSelectSettingsUtils.ts +20 -0
- package/src/flow/models/fields/JSFieldModel.tsx +54 -14
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +11 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +235 -0
- package/src/index.ts +1 -0
- package/src/utils/globalDeps.ts +10 -0
- package/src/utils/requirejs.ts +1 -1
|
@@ -12,14 +12,13 @@ import {
|
|
|
12
12
|
defineAction,
|
|
13
13
|
FlowModel,
|
|
14
14
|
MultiRecordResource,
|
|
15
|
-
pruneFilter,
|
|
16
15
|
useFlowContext,
|
|
17
16
|
tExpr,
|
|
18
17
|
} from '@nocobase/flow-engine';
|
|
19
|
-
import { isEmptyFilter
|
|
20
|
-
import _ from 'lodash';
|
|
18
|
+
import { isEmptyFilter } from '@nocobase/utils/client';
|
|
21
19
|
import React from 'react';
|
|
22
20
|
import { FilterGroup, VariableFilterItem } from '../components/filter';
|
|
21
|
+
import { normalizeDataScopeFilter } from './dataScopeFilter';
|
|
23
22
|
|
|
24
23
|
export const setTargetDataScope = defineAction({
|
|
25
24
|
name: 'setTargetDataScope',
|
|
@@ -62,8 +61,10 @@ export const setTargetDataScope = defineAction({
|
|
|
62
61
|
filter: { logic: '$and', items: [] },
|
|
63
62
|
};
|
|
64
63
|
},
|
|
64
|
+
useRawParams: true,
|
|
65
65
|
async handler(ctx, params) {
|
|
66
|
-
const
|
|
66
|
+
const resolvedParams = await ctx.resolveJsonTemplate(params);
|
|
67
|
+
const targetBlockUid = resolvedParams.targetBlockUid;
|
|
67
68
|
if (!targetBlockUid) {
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
@@ -74,7 +75,7 @@ export const setTargetDataScope = defineAction({
|
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
const filter =
|
|
78
|
+
const filter = normalizeDataScopeFilter(params.filter, resolvedParams.filter);
|
|
78
79
|
|
|
79
80
|
if (isEmptyFilter(filter)) {
|
|
80
81
|
resource.removeFilterGroup(`setTargetDataScope_${ctx.model.uid}`);
|
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';
|
|
@@ -35,7 +35,7 @@ describe('rebuildFieldSubModel', () => {
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test('rebuilds field with same uid and
|
|
38
|
+
test('rebuilds field with same uid and direct target field model use', async () => {
|
|
39
39
|
const staleClickHandler = () => null;
|
|
40
40
|
const parent = engine.createModel<DummyParentModel>({
|
|
41
41
|
use: DummyParentModel,
|
|
@@ -66,7 +66,8 @@ describe('rebuildFieldSubModel', () => {
|
|
|
66
66
|
|
|
67
67
|
expect(rebuilt).toBeInstanceOf(DummyTargetFieldModel);
|
|
68
68
|
expect(rebuilt.uid).toBe('field-1');
|
|
69
|
-
expect(getFieldBindingUse(rebuilt)).
|
|
69
|
+
expect(getFieldBindingUse(rebuilt)).toBeUndefined();
|
|
70
|
+
expect(rebuilt.use).toBe('DummyTargetFieldModel');
|
|
70
71
|
expect(rebuilt.props).toMatchObject({ added: 'yes', pattern: 'readPretty' });
|
|
71
72
|
expect((rebuilt.props as any).onClick).toBeUndefined();
|
|
72
73
|
|
|
@@ -106,4 +107,78 @@ describe('rebuildFieldSubModel', () => {
|
|
|
106
107
|
expect(Array.isArray(cols)).toBe(true);
|
|
107
108
|
expect(cols.map((c) => c.uid)).toEqual(['col-1', 'col-2']);
|
|
108
109
|
});
|
|
110
|
+
|
|
111
|
+
test('preserves compatible step params when rebuilding with the same field model use', async () => {
|
|
112
|
+
const parent = engine.createModel<DummyParentModel>({
|
|
113
|
+
use: DummyParentModel,
|
|
114
|
+
uid: 'parent-3',
|
|
115
|
+
subModels: {
|
|
116
|
+
field: {
|
|
117
|
+
use: DummyTargetFieldModel,
|
|
118
|
+
uid: 'field-3',
|
|
119
|
+
stepParams: {
|
|
120
|
+
fieldSettings: { init: { initKey: true } },
|
|
121
|
+
displayFieldSettings: {
|
|
122
|
+
overflowMode: {
|
|
123
|
+
overflowMode: true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
} as any,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await rebuildFieldSubModel({
|
|
132
|
+
parentModel: parent,
|
|
133
|
+
targetUse: 'DummyTargetFieldModel',
|
|
134
|
+
fieldSettingsInit: { fieldPath: 'title' },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const rebuilt = parent.subModels.field as DummyTargetFieldModel;
|
|
138
|
+
expect(rebuilt.stepParams).toEqual({
|
|
139
|
+
fieldSettings: {
|
|
140
|
+
init: { fieldPath: 'title' },
|
|
141
|
+
},
|
|
142
|
+
displayFieldSettings: {
|
|
143
|
+
overflowMode: {
|
|
144
|
+
overflowMode: true,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('drops incompatible step params when rebuilding to a different field model use', async () => {
|
|
151
|
+
const parent = engine.createModel<DummyParentModel>({
|
|
152
|
+
use: DummyParentModel,
|
|
153
|
+
uid: 'parent-4',
|
|
154
|
+
subModels: {
|
|
155
|
+
field: {
|
|
156
|
+
use: FieldModel,
|
|
157
|
+
uid: 'field-4',
|
|
158
|
+
stepParams: {
|
|
159
|
+
fieldBinding: { use: 'FieldModel' },
|
|
160
|
+
fieldSettings: { init: { initKey: true } },
|
|
161
|
+
numberSettings: {
|
|
162
|
+
format: {
|
|
163
|
+
separator: '0,0.00',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
} as any,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await rebuildFieldSubModel({
|
|
172
|
+
parentModel: parent,
|
|
173
|
+
targetUse: 'DummyTargetFieldModel',
|
|
174
|
+
fieldSettingsInit: { fieldPath: 'title' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const rebuilt = parent.subModels.field as DummyTargetFieldModel;
|
|
178
|
+
expect(rebuilt.stepParams).toEqual({
|
|
179
|
+
fieldSettings: {
|
|
180
|
+
init: { fieldPath: 'title' },
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
});
|
|
109
184
|
});
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
/**
|
|
11
11
|
* 通用的字段子模型重建工具:
|
|
12
12
|
* - 保留原有 uid
|
|
13
|
-
* -
|
|
13
|
+
* - 直接重建为目标字段类,保持与 defineChildren 初始创建逻辑一致
|
|
14
14
|
* - 支持同步父项模式(pattern)
|
|
15
|
+
* - 同一字段模型类型下保留已有字段设置;切换到其他字段模型类型时丢弃不兼容设置
|
|
15
16
|
* - 重建后触发 beforeRender(useCache: false)
|
|
16
17
|
*/
|
|
17
18
|
import { FieldModel } from '../../models/base/FieldModel';
|
|
@@ -39,6 +40,16 @@ type RebuildOptions = {
|
|
|
39
40
|
fieldSettingsInit?: unknown;
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
function normalizeModelUse(value: unknown): string | undefined {
|
|
44
|
+
if (typeof value === 'string') {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === 'function' && value.name) {
|
|
48
|
+
return value.name;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
export function getFieldBindingUse(fieldModel?: FieldModel): string | undefined {
|
|
43
54
|
const bindingUse = (fieldModel?.stepParams as FieldStepParams | undefined)?.fieldBinding?.use;
|
|
44
55
|
return typeof bindingUse === 'string' ? bindingUse : undefined;
|
|
@@ -61,13 +72,18 @@ export async function rebuildFieldSubModel({
|
|
|
61
72
|
delete prevSubModels[key];
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
|
-
const
|
|
75
|
+
const currentUse = normalizeModelUse(getFieldBindingUse(fieldModel) || fieldModel?.use);
|
|
76
|
+
const shouldPreserveStepParams = currentUse === targetUse;
|
|
77
|
+
const prevStepParams: FieldStepParams = shouldPreserveStepParams
|
|
78
|
+
? (fieldModel?.stepParams as FieldStepParams) || {}
|
|
79
|
+
: {};
|
|
65
80
|
const nextFieldSettingsInit = fieldSettingsInit ?? parentModel.getFieldSettingsInitParams?.();
|
|
81
|
+
const { fieldBinding: _fieldBinding, ...restStepParams } = prevStepParams;
|
|
66
82
|
|
|
67
83
|
const nextStepParams: FieldStepParams = {
|
|
68
|
-
...
|
|
69
|
-
fieldBinding: { ...prevStepParams.fieldBinding, use: targetUse },
|
|
84
|
+
...restStepParams,
|
|
70
85
|
fieldSettings: {
|
|
86
|
+
...(restStepParams.fieldSettings || {}),
|
|
71
87
|
init: nextFieldSettingsInit,
|
|
72
88
|
},
|
|
73
89
|
};
|
|
@@ -81,7 +97,7 @@ export async function rebuildFieldSubModel({
|
|
|
81
97
|
|
|
82
98
|
const subModel = parentModel.setSubModel('field', {
|
|
83
99
|
uid: fieldUid,
|
|
84
|
-
use:
|
|
100
|
+
use: targetUse,
|
|
85
101
|
props: { ...(defaultProps || {}), ...(pattern ? { pattern } : {}) },
|
|
86
102
|
stepParams: nextStepParams as StepParams,
|
|
87
103
|
// Preserve existing subModels (e.g. SubTable columns) so switching field component back and forth
|
|
@@ -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
|
+
});
|