@nocobase/client-v2 2.1.19 → 2.1.21
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/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +0 -1
- package/es/index.mjs +6 -6
- package/lib/index.js +25 -25
- package/package.json +7 -7
- package/src/BaseApplication.tsx +4 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/components/FieldAssignValueInput.tsx +15 -11
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +34 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +13 -18
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +30 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.21",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "2.1.
|
|
31
|
-
"@nocobase/flow-engine": "2.1.
|
|
32
|
-
"@nocobase/sdk": "2.1.
|
|
33
|
-
"@nocobase/shared": "2.1.
|
|
34
|
-
"@nocobase/utils": "2.1.
|
|
30
|
+
"@nocobase/evaluators": "2.1.21",
|
|
31
|
+
"@nocobase/flow-engine": "2.1.21",
|
|
32
|
+
"@nocobase/sdk": "2.1.21",
|
|
33
|
+
"@nocobase/shared": "2.1.21",
|
|
34
|
+
"@nocobase/utils": "2.1.21",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"react-i18next": "^11.15.1",
|
|
47
47
|
"react-router-dom": "^6.30.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "491141417d8338022651c9e99afbd2d84c1e2b0b"
|
|
50
50
|
}
|
package/src/BaseApplication.tsx
CHANGED
|
@@ -431,6 +431,10 @@ export abstract class BaseApplication<
|
|
|
431
431
|
return getSubAppName(this.getPublicPath()) || null;
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
getCdnUrl() {
|
|
435
|
+
return window['__webpack_public_path__'] || this.getPublicPath();
|
|
436
|
+
}
|
|
437
|
+
|
|
434
438
|
getPublicPath() {
|
|
435
439
|
let publicPath = this.options.publicPath || '/';
|
|
436
440
|
if (!publicPath.endsWith('/')) {
|
|
@@ -115,6 +115,7 @@ export abstract class CollectionFieldInterface {
|
|
|
115
115
|
titleUsable?: boolean;
|
|
116
116
|
usePathOptions?(field: any): any;
|
|
117
117
|
hidden?: boolean;
|
|
118
|
+
creatable?: boolean;
|
|
118
119
|
|
|
119
120
|
addComponentOption(componentOption: CollectionFieldInterfaceComponentOption) {
|
|
120
121
|
if (!this.componentOptions) {
|
package/src/flow/FlowPage.tsx
CHANGED
|
@@ -60,7 +60,9 @@ type FlowPageProps = {
|
|
|
60
60
|
type FlowPageViewContext = FlowEngineContext & {
|
|
61
61
|
view?: {
|
|
62
62
|
inputArgs?: {
|
|
63
|
+
filterByTk?: unknown;
|
|
63
64
|
isMobileLayout?: unknown;
|
|
65
|
+
sourceId?: unknown;
|
|
64
66
|
};
|
|
65
67
|
};
|
|
66
68
|
};
|
|
@@ -102,6 +104,10 @@ export const FlowPage = React.memo((props: FlowPageProps & Record<string, unknow
|
|
|
102
104
|
subType: 'object',
|
|
103
105
|
use: pageModelClass,
|
|
104
106
|
};
|
|
107
|
+
const isRuntimeRecordScopedPage =
|
|
108
|
+
!flowEngine.context.flowSettingsEnabled &&
|
|
109
|
+
(typeof ctx?.view?.inputArgs?.filterByTk !== 'undefined' ||
|
|
110
|
+
typeof ctx?.view?.inputArgs?.sourceId !== 'undefined');
|
|
105
111
|
if (shouldInjectDefaultChildTab) {
|
|
106
112
|
const tabTitle = defaultTabTitle || flowEngine.translate?.('Details');
|
|
107
113
|
options['subModels'] = {
|
|
@@ -128,7 +134,9 @@ export const FlowPage = React.memo((props: FlowPageProps & Record<string, unknow
|
|
|
128
134
|
},
|
|
129
135
|
};
|
|
130
136
|
}
|
|
131
|
-
const data =
|
|
137
|
+
const data =
|
|
138
|
+
(isRuntimeRecordScopedPage && (await flowEngine.loadModel({ ...options, refresh: true }))) ||
|
|
139
|
+
(await flowEngine.loadOrCreateModel(options, { skipSave: !flowEngine.context.flowSettingsEnabled }));
|
|
132
140
|
if (data?.uid && onModelLoaded) {
|
|
133
141
|
data.context.addDelegate(ctx);
|
|
134
142
|
bindViewLayoutState(data, ctx);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { render, waitFor } from '@testing-library/react';
|
|
11
11
|
import React from 'react';
|
|
12
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
13
|
import { FlowPage } from '../FlowPage';
|
|
14
14
|
|
|
15
15
|
type TestContextProperty = {
|
|
@@ -71,10 +71,15 @@ const createPageModel = (): TestPageModel => {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const mocks = vi.hoisted(() => ({
|
|
74
|
+
flowSettingsEnabled: false,
|
|
75
|
+
loadModel: vi.fn(),
|
|
76
|
+
loadOrCreateModel: vi.fn(),
|
|
74
77
|
rendererMobileStates: [] as boolean[],
|
|
75
78
|
rendererProps: undefined as RendererProps | undefined,
|
|
76
79
|
model: undefined as TestPageModel | undefined,
|
|
80
|
+
viewFilterByTk: undefined as unknown,
|
|
77
81
|
viewInputMobileLayout: true as boolean | undefined,
|
|
82
|
+
viewSourceId: undefined as unknown,
|
|
78
83
|
contextMobileLayout: undefined as boolean | undefined,
|
|
79
84
|
}));
|
|
80
85
|
|
|
@@ -94,15 +99,20 @@ vi.mock('@nocobase/flow-engine', async () => {
|
|
|
94
99
|
}),
|
|
95
100
|
useFlowEngine: vi.fn(() => ({
|
|
96
101
|
getModelClassAsync: vi.fn(async () => TestPageModelClass),
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
loadModel: mocks.loadModel,
|
|
103
|
+
loadOrCreateModel: mocks.loadOrCreateModel,
|
|
104
|
+
context: {
|
|
105
|
+
flowSettingsEnabled: mocks.flowSettingsEnabled,
|
|
106
|
+
},
|
|
99
107
|
translate: vi.fn((value: string) => value),
|
|
100
108
|
})),
|
|
101
109
|
useFlowModelById: vi.fn(() => mocks.model),
|
|
102
110
|
useFlowViewContext: vi.fn(() => ({
|
|
103
111
|
view: {
|
|
104
112
|
inputArgs: {
|
|
113
|
+
...(typeof mocks.viewFilterByTk !== 'undefined' ? { filterByTk: mocks.viewFilterByTk } : {}),
|
|
105
114
|
...(typeof mocks.viewInputMobileLayout === 'boolean' ? { isMobileLayout: mocks.viewInputMobileLayout } : {}),
|
|
115
|
+
...(typeof mocks.viewSourceId !== 'undefined' ? { sourceId: mocks.viewSourceId } : {}),
|
|
106
116
|
},
|
|
107
117
|
},
|
|
108
118
|
...(typeof mocks.contextMobileLayout === 'boolean' ? { isMobileLayout: mocks.contextMobileLayout } : {}),
|
|
@@ -154,11 +164,21 @@ vi.mock('ahooks', async () => {
|
|
|
154
164
|
});
|
|
155
165
|
|
|
156
166
|
describe('FlowPage', () => {
|
|
167
|
+
beforeEach(() => {
|
|
168
|
+
mocks.loadModel.mockImplementation(async () => null);
|
|
169
|
+
mocks.loadOrCreateModel.mockImplementation(async () => mocks.model);
|
|
170
|
+
});
|
|
171
|
+
|
|
157
172
|
afterEach(() => {
|
|
173
|
+
mocks.flowSettingsEnabled = false;
|
|
174
|
+
mocks.loadModel.mockReset();
|
|
175
|
+
mocks.loadOrCreateModel.mockReset();
|
|
158
176
|
mocks.rendererMobileStates = [];
|
|
159
177
|
mocks.rendererProps = undefined;
|
|
160
178
|
mocks.model = undefined;
|
|
179
|
+
mocks.viewFilterByTk = undefined;
|
|
161
180
|
mocks.viewInputMobileLayout = true;
|
|
181
|
+
mocks.viewSourceId = undefined;
|
|
162
182
|
mocks.contextMobileLayout = undefined;
|
|
163
183
|
});
|
|
164
184
|
|
|
@@ -203,4 +223,31 @@ describe('FlowPage', () => {
|
|
|
203
223
|
expect(mocks.rendererMobileStates[0]).toBe(expected);
|
|
204
224
|
},
|
|
205
225
|
);
|
|
226
|
+
|
|
227
|
+
it('reloads runtime page models for record scoped views', async () => {
|
|
228
|
+
mocks.model = createPageModel();
|
|
229
|
+
mocks.viewFilterByTk = 1;
|
|
230
|
+
mocks.loadModel.mockImplementation(async () => mocks.model);
|
|
231
|
+
|
|
232
|
+
render(<FlowPage onModelLoaded={vi.fn()} />);
|
|
233
|
+
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect(mocks.loadModel).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
expect(mocks.loadModel.mock.calls[0]?.[0]).toMatchObject({ refresh: true });
|
|
238
|
+
expect(mocks.loadOrCreateModel).not.toHaveBeenCalled();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('keeps configured page model cache behavior in flow settings mode', async () => {
|
|
242
|
+
mocks.flowSettingsEnabled = true;
|
|
243
|
+
mocks.model = createPageModel();
|
|
244
|
+
mocks.viewFilterByTk = 1;
|
|
245
|
+
|
|
246
|
+
render(<FlowPage onModelLoaded={vi.fn()} />);
|
|
247
|
+
|
|
248
|
+
await waitFor(() => {
|
|
249
|
+
expect(mocks.loadOrCreateModel).toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
expect(mocks.loadModel).not.toHaveBeenCalled();
|
|
252
|
+
});
|
|
206
253
|
});
|
|
@@ -1028,18 +1028,22 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1028
1028
|
collectionField: effectiveCollectionField,
|
|
1029
1029
|
});
|
|
1030
1030
|
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1031
|
+
const sourceContext = resolved?.itemModel?.context || flowCtx.model?.context;
|
|
1032
|
+
const created = engine?.createModel?.(
|
|
1033
|
+
{
|
|
1034
|
+
use: 'VariableFieldFormModel',
|
|
1035
|
+
subModels: {
|
|
1036
|
+
fields: [
|
|
1037
|
+
{
|
|
1038
|
+
use: effectiveFieldModelUse,
|
|
1039
|
+
stepParams: tempFieldStepParams,
|
|
1040
|
+
props: tempFieldProps,
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
},
|
|
1041
1044
|
},
|
|
1042
|
-
|
|
1045
|
+
sourceContext ? { delegate: sourceContext } : undefined,
|
|
1046
|
+
);
|
|
1043
1047
|
if (!created) return;
|
|
1044
1048
|
|
|
1045
1049
|
// 注入上下文(集合/数据源/字段/区块/资源)
|
|
@@ -0,0 +1,134 @@
|
|
|
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 React from 'react';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { render, waitFor } from '@nocobase/test/client';
|
|
13
|
+
import { FieldAssignValueInput } from '../FieldAssignValueInput';
|
|
14
|
+
|
|
15
|
+
const { mockUseFlowContext, mockGetDefaultBindingByField } = vi.hoisted(() => ({
|
|
16
|
+
mockUseFlowContext: vi.fn(),
|
|
17
|
+
mockGetDefaultBindingByField: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('@nocobase/flow-engine', async () => {
|
|
21
|
+
const actual = await vi.importActual<typeof import('@nocobase/flow-engine')>('@nocobase/flow-engine');
|
|
22
|
+
class MockEditableItemModel extends actual.EditableItemModel {}
|
|
23
|
+
MockEditableItemModel.getDefaultBindingByField = mockGetDefaultBindingByField;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
useFlowContext: () => mockUseFlowContext(),
|
|
28
|
+
VariableInput: () => <div data-testid="variable-input" />,
|
|
29
|
+
FlowModelRenderer: () => <div data-testid="flow-model-renderer" />,
|
|
30
|
+
EditableItemModel: MockEditableItemModel,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('FieldAssignValueInput context', () => {
|
|
35
|
+
it('delegates temporary field model context to the source form context', async () => {
|
|
36
|
+
type MockContext = {
|
|
37
|
+
dataSourceManager: { getDataSource: ReturnType<typeof vi.fn> };
|
|
38
|
+
t: (key: string) => string;
|
|
39
|
+
engine?: { createModel: ReturnType<typeof vi.fn> };
|
|
40
|
+
collection?: MockCollection;
|
|
41
|
+
blockModel?: MockFormModel;
|
|
42
|
+
};
|
|
43
|
+
type MockFieldModel = {
|
|
44
|
+
props: Record<string, unknown>;
|
|
45
|
+
setProps: ReturnType<typeof vi.fn>;
|
|
46
|
+
dispatchEvent: ReturnType<typeof vi.fn>;
|
|
47
|
+
remove: ReturnType<typeof vi.fn>;
|
|
48
|
+
};
|
|
49
|
+
type MockCollectionField = {
|
|
50
|
+
name: string;
|
|
51
|
+
interface: string;
|
|
52
|
+
uiSchema: { enum: Array<{ label: string; value: string }> };
|
|
53
|
+
isAssociationField: () => boolean;
|
|
54
|
+
getComponentProps: () => Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
type MockCollection = {
|
|
57
|
+
dataSourceKey: string;
|
|
58
|
+
name: string;
|
|
59
|
+
getField: (name: string) => MockCollectionField | null;
|
|
60
|
+
getFields: () => MockCollectionField[];
|
|
61
|
+
};
|
|
62
|
+
type MockFormModel = {
|
|
63
|
+
context: MockContext;
|
|
64
|
+
collection: MockCollection;
|
|
65
|
+
subModels: Record<string, unknown>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const sourceContext: MockContext = {
|
|
69
|
+
dataSourceManager: {
|
|
70
|
+
getDataSource: vi.fn(() => ({})),
|
|
71
|
+
},
|
|
72
|
+
t: (key: string) => key,
|
|
73
|
+
};
|
|
74
|
+
const fieldModel: MockFieldModel = {
|
|
75
|
+
props: {},
|
|
76
|
+
setProps: vi.fn((props: Record<string, unknown>) => {
|
|
77
|
+
fieldModel.props = { ...fieldModel.props, ...props };
|
|
78
|
+
}),
|
|
79
|
+
dispatchEvent: vi.fn(),
|
|
80
|
+
remove: vi.fn(),
|
|
81
|
+
};
|
|
82
|
+
const tempRoot = {
|
|
83
|
+
context: {
|
|
84
|
+
defineProperty: vi.fn(),
|
|
85
|
+
},
|
|
86
|
+
subModels: {
|
|
87
|
+
fields: [fieldModel],
|
|
88
|
+
},
|
|
89
|
+
setProps: vi.fn(),
|
|
90
|
+
remove: vi.fn(),
|
|
91
|
+
};
|
|
92
|
+
const engine = {
|
|
93
|
+
createModel: vi.fn(() => tempRoot),
|
|
94
|
+
};
|
|
95
|
+
sourceContext.engine = engine;
|
|
96
|
+
|
|
97
|
+
const collectionField: MockCollectionField = {
|
|
98
|
+
name: 'status',
|
|
99
|
+
interface: 'select',
|
|
100
|
+
uiSchema: {
|
|
101
|
+
enum: [{ label: 'Open', value: 'open' }],
|
|
102
|
+
},
|
|
103
|
+
isAssociationField: () => false,
|
|
104
|
+
getComponentProps: () => ({}),
|
|
105
|
+
};
|
|
106
|
+
const collection: MockCollection = {
|
|
107
|
+
dataSourceKey: 'main',
|
|
108
|
+
name: 'tasks',
|
|
109
|
+
getField: (name: string) => (name === 'status' ? collectionField : null),
|
|
110
|
+
getFields: () => [collectionField],
|
|
111
|
+
};
|
|
112
|
+
const formModel: MockFormModel = {
|
|
113
|
+
context: sourceContext,
|
|
114
|
+
collection,
|
|
115
|
+
subModels: {},
|
|
116
|
+
};
|
|
117
|
+
sourceContext.collection = collection;
|
|
118
|
+
sourceContext.blockModel = formModel;
|
|
119
|
+
|
|
120
|
+
mockGetDefaultBindingByField.mockReturnValue({ modelName: 'SelectFieldModel' });
|
|
121
|
+
mockUseFlowContext.mockReturnValue({
|
|
122
|
+
model: formModel,
|
|
123
|
+
t: (key: string) => key,
|
|
124
|
+
getPropertyMetaTree: vi.fn(async () => []),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
render(<FieldAssignValueInput targetPath="status" value="" onChange={vi.fn()} />);
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(engine.createModel).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
expect(engine.createModel).toHaveBeenCalledWith(expect.any(Object), { delegate: sourceContext });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -432,6 +432,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
432
432
|
|
|
433
433
|
private canApplyFormDefaultValue(name: string, current: any, force?: boolean) {
|
|
434
434
|
if (force) return true;
|
|
435
|
+
if (this.userEditedFieldNames.has(name)) return false;
|
|
435
436
|
if (isEmptyValue(current)) return true;
|
|
436
437
|
if (!this.lastDefaultValueByFieldName.has(name)) return false;
|
|
437
438
|
return isEqual(current, this.lastDefaultValueByFieldName.get(name));
|
|
@@ -301,6 +301,40 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
301
301
|
expect(values.username_user).toBe('Manual');
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
+
it('does not reapply a filter form default value after user clears the field', async () => {
|
|
305
|
+
const { model, values } = createFilterFormDefaultValuesModel([
|
|
306
|
+
{
|
|
307
|
+
key: 'username-default',
|
|
308
|
+
enable: true,
|
|
309
|
+
targetPath: 'username',
|
|
310
|
+
mode: 'default',
|
|
311
|
+
value: 'admin',
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
316
|
+
expect(values.username_user).toBe('admin');
|
|
317
|
+
|
|
318
|
+
values.username_user = undefined;
|
|
319
|
+
(model as any).handleFilterFormValuesChange({ username_user: undefined }, { username_user: undefined });
|
|
320
|
+
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
323
|
+
'formValuesChange',
|
|
324
|
+
{
|
|
325
|
+
changedValues: {
|
|
326
|
+
username_user: undefined,
|
|
327
|
+
},
|
|
328
|
+
allValues: {
|
|
329
|
+
username_user: undefined,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{ debounce: true },
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
expect(values.username_user).toBeUndefined();
|
|
336
|
+
});
|
|
337
|
+
|
|
304
338
|
it('applies fixed values even when the target filter field already has a value', async () => {
|
|
305
339
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
306
340
|
[
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
createRecordResolveOnServerWithLocal,
|
|
23
23
|
} from '@nocobase/flow-engine';
|
|
24
24
|
import { Button, Form, Skeleton, Space } from 'antd';
|
|
25
|
-
import _ from 'lodash';
|
|
26
25
|
import React from 'react';
|
|
27
26
|
import { FieldModel } from '../../base/FieldModel';
|
|
28
27
|
import { FormComponent } from './FormBlockModel';
|
|
@@ -46,8 +45,6 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
46
45
|
declare resource: SingleRecordResource;
|
|
47
46
|
declare collection: Collection;
|
|
48
47
|
|
|
49
|
-
now: number = Date.now();
|
|
50
|
-
|
|
51
48
|
viewContainer: any;
|
|
52
49
|
__onSubmitSuccess;
|
|
53
50
|
_fieldProps: any;
|
|
@@ -89,21 +86,22 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
89
86
|
onOk,
|
|
90
87
|
sourceFieldModelUid,
|
|
91
88
|
} = options;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
const sourceFieldModel = sourceFieldModelUid ? flowEngine.getModel<FlowModel>(sourceFieldModelUid) : undefined;
|
|
90
|
+
const model = flowEngine.createModel(
|
|
91
|
+
{
|
|
92
|
+
use: 'QuickEditFormModel',
|
|
93
|
+
stepParams: {
|
|
94
|
+
quickEditFormSettings: {
|
|
95
|
+
init: {
|
|
96
|
+
dataSourceKey,
|
|
97
|
+
collectionName,
|
|
98
|
+
fieldPath,
|
|
99
|
+
},
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
102
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log('QuickEditFormModel.open2', Date.now() - model.now);
|
|
106
|
-
model.now = Date.now();
|
|
103
|
+
sourceFieldModel ? { delegate: sourceFieldModel.context } : undefined,
|
|
104
|
+
) as QuickEditFormModel;
|
|
107
105
|
|
|
108
106
|
await flowEngine.context.viewer.open({
|
|
109
107
|
type: 'popover',
|
|
@@ -122,7 +120,6 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
122
120
|
model.__onSubmitSuccess = onSuccess;
|
|
123
121
|
model._fieldProps = fieldProps;
|
|
124
122
|
model._onOk = onOk;
|
|
125
|
-
console.log('QuickEditFormModel.open3', Date.now() - model.now);
|
|
126
123
|
return (
|
|
127
124
|
<FlowModelRenderer
|
|
128
125
|
fallback={<Skeleton.Input size="small" />}
|
|
@@ -171,8 +168,6 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
171
168
|
}
|
|
172
169
|
|
|
173
170
|
render() {
|
|
174
|
-
console.log('QuickEditFormModel.open4', Date.now() - this.now);
|
|
175
|
-
|
|
176
171
|
return (
|
|
177
172
|
<FormComponent model={this}>
|
|
178
173
|
<div
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
-
import { FlowEngine, SingleRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { FlowEngine, FlowModel, SingleRecordResource } from '@nocobase/flow-engine';
|
|
12
12
|
import { QuickEditFormModel } from '../QuickEditFormModel';
|
|
13
13
|
|
|
14
14
|
describe('QuickEditFormModel - quick edit save triggers API (regression)', () => {
|
|
@@ -18,6 +18,35 @@ describe('QuickEditFormModel - quick edit save triggers API (regression)', () =>
|
|
|
18
18
|
engine = new FlowEngine();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
it('uses source field context when opening quick edit', async () => {
|
|
22
|
+
engine.registerModels({ QuickEditFormModel });
|
|
23
|
+
engine.context.defineProperty('pageActive', { value: { value: false } });
|
|
24
|
+
engine.context.defineProperty('viewer', { value: { open: vi.fn(async () => undefined) } });
|
|
25
|
+
|
|
26
|
+
const page = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'page' });
|
|
27
|
+
page.context.defineProperty('pageActive', { value: { value: true } });
|
|
28
|
+
const source = engine.createModel<FlowModel>({ use: 'FlowModel', uid: 'source-field', parentId: page.uid });
|
|
29
|
+
|
|
30
|
+
await QuickEditFormModel.open({
|
|
31
|
+
flowEngine: engine,
|
|
32
|
+
target: document.createElement('div'),
|
|
33
|
+
dataSourceKey: 'main',
|
|
34
|
+
collectionName: 'users',
|
|
35
|
+
fieldPath: 'name',
|
|
36
|
+
record: {},
|
|
37
|
+
sourceFieldModelUid: source.uid,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let quickEditModel: QuickEditFormModel | undefined;
|
|
41
|
+
engine.forEachModel((model) => {
|
|
42
|
+
if (model instanceof QuickEditFormModel) {
|
|
43
|
+
quickEditModel = model;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(quickEditModel?.context.pageActive.value).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
21
50
|
it('calls update with filterByTk and merges primary key from ctx.collection/record', async () => {
|
|
22
51
|
// 1) 准备数据源与集合(含主键字段)
|
|
23
52
|
const dsm = engine.context.dataSourceManager;
|
|
@@ -126,6 +126,7 @@ const RenderCell = observer<any>((props) => {
|
|
|
126
126
|
fieldPath: dataIndex,
|
|
127
127
|
record: record,
|
|
128
128
|
fieldProps: { ...columnModel.props, ...columnModel.subModels.field.props },
|
|
129
|
+
sourceFieldModelUid: columnModel.subModels.field.uid,
|
|
129
130
|
onOk: (values) => {
|
|
130
131
|
record[dataIndex] = values[dataIndex];
|
|
131
132
|
// 仅重渲染单元格
|