@nocobase/client-v2 2.1.0-alpha.35 → 2.1.0-alpha.37
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/flow/components/code-editor/index.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +4 -0
- package/es/flow/models/fields/ClickableFieldModel.d.ts +3 -0
- package/es/flow/models/fields/DisplayTitleFieldModel.d.ts +2 -2
- package/es/index.mjs +49 -49
- package/lib/index.js +37 -37
- package/package.json +6 -5
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/flow/actions/__tests__/pattern.test.ts +134 -0
- package/src/flow/actions/__tests__/titleField.test.ts +45 -0
- package/src/flow/actions/pattern.tsx +41 -6
- package/src/flow/actions/titleField.tsx +3 -1
- package/src/flow/components/DynamicFlowsIcon.tsx +87 -13
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +195 -8
- package/src/flow/components/code-editor/index.tsx +12 -8
- package/src/flow/models/blocks/form/FormActionModel.tsx +2 -8
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +144 -3
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +170 -1
- package/src/flow/models/fields/ClickableFieldModel.tsx +46 -2
- package/src/flow/models/fields/DisplayTitleFieldModel.tsx +40 -15
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +180 -2
- package/src/utils/globalDeps.ts +3 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import { render, screen, userEvent, waitFor } from '@nocobase/test/client';
|
|
13
|
-
import { FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID } from '@nocobase/flow-engine';
|
|
13
|
+
import { ActionScene, FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID } from '@nocobase/flow-engine';
|
|
14
14
|
import { DynamicFlowsIcon } from '../DynamicFlowsIcon';
|
|
15
15
|
|
|
16
16
|
const mockState = vi.hoisted(() => ({
|
|
@@ -30,15 +30,31 @@ vi.mock('antd', async (importOriginal) => {
|
|
|
30
30
|
const actual = await importOriginal<typeof import('antd')>();
|
|
31
31
|
return {
|
|
32
32
|
...actual,
|
|
33
|
-
Button: ({ children, onClick, ...props }: any) => (
|
|
33
|
+
Button: ({ children, onClick, icon: _icon, loading: _loading, ...props }: any) => (
|
|
34
34
|
<button type="button" onClick={onClick} {...props}>
|
|
35
35
|
{children}
|
|
36
36
|
</button>
|
|
37
37
|
),
|
|
38
38
|
Collapse: ({ items }: any) => (
|
|
39
|
-
<div data-testid="collapse">
|
|
39
|
+
<div data-testid="collapse">
|
|
40
|
+
{items?.map((item: any) => (
|
|
41
|
+
<div key={item.key}>
|
|
42
|
+
{item.label}
|
|
43
|
+
{item.children}
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
),
|
|
48
|
+
Dropdown: ({ children, menu }: any) => (
|
|
49
|
+
<div>
|
|
50
|
+
{children}
|
|
51
|
+
{menu?.items?.map((item: any) => (
|
|
52
|
+
<button key={item.key} type="button" onClick={item.onClick}>
|
|
53
|
+
{item.label}
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
40
57
|
),
|
|
41
|
-
Dropdown: ({ children }: any) => <div>{children}</div>,
|
|
42
58
|
Empty: ({ description }: any) => <div>{description}</div>,
|
|
43
59
|
Input: (props: any) => <input {...props} />,
|
|
44
60
|
Select: (props: any) => {
|
|
@@ -70,10 +86,16 @@ const openDynamicFlowsEditor = async (model: FlowModel) => {
|
|
|
70
86
|
const embedCall = (model.context.viewer.embed as any).mock.calls.at(-1)?.[0];
|
|
71
87
|
expect(embedCall?.content).toBeTruthy();
|
|
72
88
|
|
|
73
|
-
render(embedCall.content);
|
|
89
|
+
const editor = render(embedCall.content);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
embedCall,
|
|
93
|
+
view: mockState.flowContextValue.view,
|
|
94
|
+
editor,
|
|
95
|
+
};
|
|
74
96
|
};
|
|
75
97
|
|
|
76
|
-
const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean } = {}) => {
|
|
98
|
+
const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean; saveStepParams?: any } = {}) => {
|
|
77
99
|
const engine = new FlowEngine();
|
|
78
100
|
engine.translate = vi.fn((key: string) => key) as any;
|
|
79
101
|
engine.flowSettings.renderStepForm = vi.fn(() => null) as any;
|
|
@@ -95,6 +117,14 @@ const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean }
|
|
|
95
117
|
handler: vi.fn(),
|
|
96
118
|
},
|
|
97
119
|
});
|
|
120
|
+
LocalTestModel.registerActions({
|
|
121
|
+
notify: {
|
|
122
|
+
name: 'notify',
|
|
123
|
+
title: 'Notify',
|
|
124
|
+
scene: ActionScene.DYNAMIC_EVENT_FLOW,
|
|
125
|
+
handler: vi.fn(),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
98
128
|
|
|
99
129
|
const model = new LocalTestModel({
|
|
100
130
|
uid: `test-model-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -122,17 +152,55 @@ const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean }
|
|
|
122
152
|
destroy: vi.fn(),
|
|
123
153
|
},
|
|
124
154
|
});
|
|
155
|
+
model.context.defineProperty('message', {
|
|
156
|
+
value: {
|
|
157
|
+
error: vi.fn(),
|
|
158
|
+
success: vi.fn(),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
vi.spyOn(model, 'saveStepParams').mockImplementation(options.saveStepParams || vi.fn(async () => undefined));
|
|
125
162
|
|
|
126
163
|
return model;
|
|
127
164
|
};
|
|
128
165
|
|
|
166
|
+
const createView = () => {
|
|
167
|
+
let destroyed = false;
|
|
168
|
+
let closingPromise: Promise<boolean | void> | undefined;
|
|
169
|
+
const view = {
|
|
170
|
+
close: vi.fn(function () {
|
|
171
|
+
if (destroyed) {
|
|
172
|
+
return Promise.resolve(true);
|
|
173
|
+
}
|
|
174
|
+
if (closingPromise) {
|
|
175
|
+
return closingPromise;
|
|
176
|
+
}
|
|
177
|
+
closingPromise = (async () => {
|
|
178
|
+
const allowed = await view.beforeClose?.({});
|
|
179
|
+
if (allowed === false) {
|
|
180
|
+
closingPromise = undefined;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
view.destroy();
|
|
184
|
+
return true;
|
|
185
|
+
})();
|
|
186
|
+
return closingPromise;
|
|
187
|
+
}),
|
|
188
|
+
destroy: vi.fn(() => {
|
|
189
|
+
destroyed = true;
|
|
190
|
+
}),
|
|
191
|
+
beforeClose: undefined as any,
|
|
192
|
+
};
|
|
193
|
+
return view;
|
|
194
|
+
};
|
|
195
|
+
|
|
129
196
|
describe('DynamicFlowsIcon', () => {
|
|
130
197
|
beforeEach(() => {
|
|
131
198
|
mockState.capturedSelectProps.length = 0;
|
|
132
199
|
mockState.flowContextValue = {
|
|
133
|
-
|
|
134
|
-
|
|
200
|
+
modal: {
|
|
201
|
+
confirm: vi.fn(),
|
|
135
202
|
},
|
|
203
|
+
view: createView(),
|
|
136
204
|
};
|
|
137
205
|
document.body.innerHTML = `<div id="${GLOBAL_EMBED_CONTAINER_ID}"></div>`;
|
|
138
206
|
});
|
|
@@ -190,4 +258,123 @@ describe('DynamicFlowsIcon', () => {
|
|
|
190
258
|
expect(screen.getByTestId('collapse')).toBeInTheDocument();
|
|
191
259
|
});
|
|
192
260
|
});
|
|
261
|
+
|
|
262
|
+
it('keeps added event flows in draft until saved', async () => {
|
|
263
|
+
const model = createModel();
|
|
264
|
+
|
|
265
|
+
await openDynamicFlowsEditor(model);
|
|
266
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
267
|
+
|
|
268
|
+
expect(model.flowRegistry.getFlows().size).toBe(1);
|
|
269
|
+
expect(model.flowRegistry.hasFlow('flow1')).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('blocks cancel when draft has unsaved changes and confirmation is rejected', async () => {
|
|
273
|
+
const model = createModel();
|
|
274
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(false);
|
|
275
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
276
|
+
|
|
277
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
278
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
279
|
+
|
|
280
|
+
expect(mockState.flowContextValue.modal.confirm).toHaveBeenCalledWith({
|
|
281
|
+
title: 'Unsaved changes',
|
|
282
|
+
content: "Are you sure you don't want to save?",
|
|
283
|
+
okText: 'Confirm',
|
|
284
|
+
cancelText: 'Cancel',
|
|
285
|
+
});
|
|
286
|
+
expect(view.destroy).not.toHaveBeenCalled();
|
|
287
|
+
expect(model.flowRegistry.getFlows().size).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('runs only one unsaved confirmation while cancel is pending', async () => {
|
|
291
|
+
const model = createModel();
|
|
292
|
+
let resolveConfirm: (value: boolean) => void;
|
|
293
|
+
mockState.flowContextValue.modal.confirm.mockImplementation(
|
|
294
|
+
() => new Promise<boolean>((resolve) => (resolveConfirm = resolve)),
|
|
295
|
+
);
|
|
296
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
297
|
+
|
|
298
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
299
|
+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
|
300
|
+
await userEvent.click(cancelButton);
|
|
301
|
+
await userEvent.click(cancelButton);
|
|
302
|
+
|
|
303
|
+
expect(mockState.flowContextValue.modal.confirm).toHaveBeenCalledTimes(1);
|
|
304
|
+
|
|
305
|
+
resolveConfirm(true);
|
|
306
|
+
await waitFor(() => expect(view.destroy).toHaveBeenCalledTimes(1));
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('discards draft changes after confirmed cancel', async () => {
|
|
310
|
+
const model = createModel();
|
|
311
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(true);
|
|
312
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
313
|
+
|
|
314
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
315
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
316
|
+
|
|
317
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
318
|
+
expect(model.flowRegistry.getFlows().size).toBe(1);
|
|
319
|
+
expect(model.flowRegistry.hasFlow('flow1')).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('discards existing event flow edits after confirmed cancel', async () => {
|
|
323
|
+
const model = createModel();
|
|
324
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(true);
|
|
325
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
326
|
+
|
|
327
|
+
const titleInput = screen.getByPlaceholderText('Enter flow title');
|
|
328
|
+
await userEvent.clear(titleInput);
|
|
329
|
+
await userEvent.type(titleInput, 'Changed flow');
|
|
330
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
331
|
+
|
|
332
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
333
|
+
expect(model.flowRegistry.getFlow('flow1')?.title).toBe('Event flow');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('discards added draft steps after confirmed cancel', async () => {
|
|
337
|
+
const model = createModel();
|
|
338
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(true);
|
|
339
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
340
|
+
|
|
341
|
+
expect(model.flowRegistry.getFlow('flow1')?.getSteps().size).toBe(0);
|
|
342
|
+
|
|
343
|
+
await userEvent.click(screen.getByRole('button', { name: 'Notify' }));
|
|
344
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
345
|
+
|
|
346
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
347
|
+
expect(model.flowRegistry.getFlow('flow1')?.getSteps().size).toBe(0);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('does not keep discarded draft changes after reopening the editor', async () => {
|
|
351
|
+
const model = createModel();
|
|
352
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(true);
|
|
353
|
+
|
|
354
|
+
const firstEditor = await openDynamicFlowsEditor(model);
|
|
355
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
356
|
+
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
357
|
+
firstEditor.editor.unmount();
|
|
358
|
+
|
|
359
|
+
mockState.capturedSelectProps.length = 0;
|
|
360
|
+
mockState.flowContextValue.view = createView();
|
|
361
|
+
const secondEditor = await openDynamicFlowsEditor(model);
|
|
362
|
+
|
|
363
|
+
expect(secondEditor.editor.container.querySelectorAll('input[placeholder="Enter flow title"]')).toHaveLength(1);
|
|
364
|
+
expect(model.flowRegistry.getFlows().size).toBe(1);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('saves draft event flows into the real registry', async () => {
|
|
368
|
+
const saveStepParams = vi.fn(async () => undefined);
|
|
369
|
+
const model = createModel({ saveStepParams });
|
|
370
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
371
|
+
|
|
372
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
373
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
374
|
+
|
|
375
|
+
expect(saveStepParams).toHaveBeenCalledTimes(1);
|
|
376
|
+
expect(model.flowRegistry.getFlows().size).toBe(2);
|
|
377
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
378
|
+
expect(model.context.message.success).toHaveBeenCalledWith('Configuration saved');
|
|
379
|
+
});
|
|
193
380
|
});
|
|
@@ -42,6 +42,7 @@ interface CodeEditorProps {
|
|
|
42
42
|
language?: string;
|
|
43
43
|
scene?: string | string[];
|
|
44
44
|
RightExtra?: React.FC<any>;
|
|
45
|
+
showLogs?: boolean;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export * from './types';
|
|
@@ -64,6 +65,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
|
|
|
64
65
|
language,
|
|
65
66
|
scene,
|
|
66
67
|
RightExtra,
|
|
68
|
+
showLogs = true,
|
|
67
69
|
}) => {
|
|
68
70
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
69
71
|
const viewRef = useRef<EditorView | null>(null);
|
|
@@ -249,14 +251,16 @@ export const CodeEditor: React.FC<CodeEditorProps> = ({
|
|
|
249
251
|
completionSource={completionSource}
|
|
250
252
|
viewRef={viewRef}
|
|
251
253
|
/>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
254
|
+
{showLogs ? (
|
|
255
|
+
<LogsPanel
|
|
256
|
+
logs={logs}
|
|
257
|
+
onJumpTo={(line, column) => {
|
|
258
|
+
const view = viewRef.current;
|
|
259
|
+
if (view) jumpTo(view, line, column);
|
|
260
|
+
}}
|
|
261
|
+
tr={tr}
|
|
262
|
+
/>
|
|
263
|
+
) : null}
|
|
260
264
|
<SnippetsDrawer
|
|
261
265
|
open={snippetOpen}
|
|
262
266
|
onClose={() => setSnippetOpen(false)}
|
|
@@ -94,8 +94,6 @@ FormSubmitActionModel.registerFlow({
|
|
|
94
94
|
ctx.model.setProps('loading', true);
|
|
95
95
|
const { submitHandler } = await import('./submitHandler');
|
|
96
96
|
await submitHandler(ctx, params);
|
|
97
|
-
ctx.message.success(ctx.t('Saved successfully'));
|
|
98
|
-
ctx.model.setProps('loading', false);
|
|
99
97
|
} catch (error) {
|
|
100
98
|
ctx.model.setProps('loading', false);
|
|
101
99
|
// 显示保存失败提示
|
|
@@ -107,12 +105,8 @@ FormSubmitActionModel.registerFlow({
|
|
|
107
105
|
}
|
|
108
106
|
},
|
|
109
107
|
},
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (ctx.view) {
|
|
113
|
-
ctx.view.close();
|
|
114
|
-
}
|
|
115
|
-
},
|
|
108
|
+
afterSuccess: {
|
|
109
|
+
use: 'afterSuccess',
|
|
116
110
|
},
|
|
117
111
|
},
|
|
118
112
|
});
|
package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx
CHANGED
|
@@ -32,9 +32,11 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|
|
32
32
|
import React, { useRef, useMemo, useEffect } from 'react';
|
|
33
33
|
import { SubTableFieldModel } from '.';
|
|
34
34
|
import { FieldModel } from '../../../base/FieldModel';
|
|
35
|
+
import { DetailsItemModel } from '../../../blocks/details/DetailsItemModel';
|
|
35
36
|
import { FieldDeletePlaceholder, CustomWidth } from '../../../blocks/table/TableColumnModel';
|
|
36
37
|
import { buildDynamicNamePath } from '../../../blocks/form/dynamicNamePath';
|
|
37
38
|
import { getSubTableRowIdentity } from './rowIdentity';
|
|
39
|
+
import { getFieldBindingUse, rebuildFieldSubModel } from '../../../../internal/utils/rebuildFieldSubModel';
|
|
38
40
|
|
|
39
41
|
export const SUB_TABLE_COLUMN_FIELD_COMPONENT_CONTEXT = 'subTableColumn';
|
|
40
42
|
|
|
@@ -218,6 +220,41 @@ function shouldCommitImmediately(value: any) {
|
|
|
218
220
|
return false;
|
|
219
221
|
}
|
|
220
222
|
|
|
223
|
+
export function isSubTableColumnReadPretty(parent: any) {
|
|
224
|
+
return !!parent?.props?.readPretty || parent?.props?.pattern === 'readPretty';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function isSubTableColumnConfiguredReadPretty(parent: any) {
|
|
228
|
+
return (
|
|
229
|
+
isSubTableColumnReadPretty(parent) ||
|
|
230
|
+
parent?.getStepParams?.('subTableColumnSettings', 'pattern')?.pattern === 'readPretty'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function getSubTableColumnTitleField(parent: any) {
|
|
235
|
+
return (
|
|
236
|
+
parent?.props?.titleField ||
|
|
237
|
+
parent?.subModels?.field?.props?.titleField ||
|
|
238
|
+
parent?.subModels?.field?.props?.fieldNames?.label ||
|
|
239
|
+
parent?.collectionField?.targetCollectionTitleFieldName
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getSubTableColumnReadPrettyFieldProps(parent: any, value: any) {
|
|
244
|
+
const fieldProps: Record<string, any> = { value };
|
|
245
|
+
const titleField = getSubTableColumnTitleField(parent);
|
|
246
|
+
const fieldNames = parent?.props?.fieldNames || parent?.subModels?.field?.props?.fieldNames;
|
|
247
|
+
|
|
248
|
+
if (titleField) {
|
|
249
|
+
fieldProps.titleField = titleField;
|
|
250
|
+
}
|
|
251
|
+
if (fieldNames) {
|
|
252
|
+
fieldProps.fieldNames = fieldNames;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return fieldProps;
|
|
256
|
+
}
|
|
257
|
+
|
|
221
258
|
const FieldModelRendererOptimize = React.memo((props: any) => {
|
|
222
259
|
const { model, onChange, value, commitOnChange, ...rest } = props;
|
|
223
260
|
const pendingValueRef = React.useRef<any>(props?.value);
|
|
@@ -337,8 +374,8 @@ const MemoCell: React.FC<CellProps> = React.memo(
|
|
|
337
374
|
});
|
|
338
375
|
}
|
|
339
376
|
|
|
340
|
-
if (parent
|
|
341
|
-
fork.setProps(
|
|
377
|
+
if (isSubTableColumnReadPretty(parent)) {
|
|
378
|
+
fork.setProps(getSubTableColumnReadPrettyFieldProps(parent, value));
|
|
342
379
|
return <React.Fragment key={id}>{fork.render()}</React.Fragment>;
|
|
343
380
|
}
|
|
344
381
|
|
|
@@ -598,6 +635,24 @@ export class SubTableColumnModel<
|
|
|
598
635
|
(this.parent as any)?.collection?.filterTargetKey ?? (this.parent as any)?.context?.collection?.filterTargetKey;
|
|
599
636
|
const rowIdentity = getSubTableRowIdentity(record, filterTargetKey) ?? `row:${String(rowIdx)}`;
|
|
600
637
|
const rowForkKey = `row:${baseIndexKey}:${rowIdentity}:${String(rowIdx)}`;
|
|
638
|
+
const fieldModel: any = this.subModels.field;
|
|
639
|
+
const cellModeKey = [
|
|
640
|
+
rowForkKey,
|
|
641
|
+
this.props.pattern,
|
|
642
|
+
this.props.readPretty,
|
|
643
|
+
this.props.titleField,
|
|
644
|
+
this.props.__displayFieldRefreshKey,
|
|
645
|
+
fieldModel?.uid,
|
|
646
|
+
fieldModel?.use,
|
|
647
|
+
fieldModel?.constructor?.name,
|
|
648
|
+
fieldModel?.props?.clickToOpen,
|
|
649
|
+
fieldModel?.props?.displayStyle,
|
|
650
|
+
fieldModel?.props?.overflowMode,
|
|
651
|
+
fieldModel?.props?.titleField,
|
|
652
|
+
fieldModel?.props?.fieldNames?.label,
|
|
653
|
+
]
|
|
654
|
+
.filter((item) => item !== undefined && item !== null)
|
|
655
|
+
.join(':');
|
|
601
656
|
const rowFork: any = (() => {
|
|
602
657
|
const fork = this.createFork({}, rowForkKey);
|
|
603
658
|
fork.context.defineProperty('subTableRowFork', {
|
|
@@ -651,7 +706,7 @@ export class SubTableColumnModel<
|
|
|
651
706
|
parentFieldIndex={baseArr}
|
|
652
707
|
parentItem={parentItem}
|
|
653
708
|
rowFork={rowFork}
|
|
654
|
-
memoKey={
|
|
709
|
+
memoKey={cellModeKey}
|
|
655
710
|
width={this.props.width}
|
|
656
711
|
commitOnChange={this.hasFormulaColumn}
|
|
657
712
|
/>
|
|
@@ -683,6 +738,7 @@ SubTableColumnModel.registerFlow({
|
|
|
683
738
|
ctx.model.setProps(collectionField.getComponentProps());
|
|
684
739
|
ctx.model.setProps('title', collectionField.title);
|
|
685
740
|
ctx.model.setProps('dataIndex', collectionField.name);
|
|
741
|
+
await ctx.model.applySubModelsBeforeRenderFlows('field');
|
|
686
742
|
const currentBlockModel = ctx.model.context.blockModel;
|
|
687
743
|
// 避免强依赖 EditFormModel(减少循环依赖风险):仅在存在该能力时调用
|
|
688
744
|
currentBlockModel?.addAppends?.(ctx.model.fieldPath);
|
|
@@ -896,6 +952,91 @@ SubTableColumnModel.registerFlow({
|
|
|
896
952
|
use: 'fieldComponent',
|
|
897
953
|
title: tExpr('Field component'),
|
|
898
954
|
},
|
|
955
|
+
fieldNames: {
|
|
956
|
+
use: 'titleField',
|
|
957
|
+
title: tExpr('Title field'),
|
|
958
|
+
hideInSettings(ctx) {
|
|
959
|
+
return (
|
|
960
|
+
!ctx.collectionField ||
|
|
961
|
+
!ctx.collectionField.isAssociationField() ||
|
|
962
|
+
!isSubTableColumnConfiguredReadPretty(ctx.model) ||
|
|
963
|
+
(ctx.model.subModels.field as any)?.disableTitleField
|
|
964
|
+
);
|
|
965
|
+
},
|
|
966
|
+
defaultParams(ctx) {
|
|
967
|
+
return {
|
|
968
|
+
label: getSubTableColumnTitleField(ctx.model),
|
|
969
|
+
};
|
|
970
|
+
},
|
|
971
|
+
beforeParamsSave: async (ctx, params, previousParams) => {
|
|
972
|
+
if (!ctx.collectionField || !ctx.collectionField.isAssociationField()) {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
if (!isSubTableColumnConfiguredReadPretty(ctx.model)) {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
if (!params.label || params.label === previousParams.label) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const targetCollection = ctx.collectionField.targetCollection;
|
|
983
|
+
const targetCollectionField = targetCollection?.getField(params.label);
|
|
984
|
+
if (!targetCollectionField) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const binding = DetailsItemModel.getDefaultBindingByField(ctx, targetCollectionField);
|
|
989
|
+
const fieldModel: any = ctx.model.subModels.field;
|
|
990
|
+
const currentUse = getFieldBindingUse(fieldModel) || fieldModel?.use;
|
|
991
|
+
const targetUse = binding?.modelName || currentUse;
|
|
992
|
+
const fieldSettingsInit = {
|
|
993
|
+
dataSourceKey: ctx.model.collectionField.dataSourceKey,
|
|
994
|
+
collectionName: targetCollection.name,
|
|
995
|
+
fieldPath: params.label,
|
|
996
|
+
};
|
|
997
|
+
const defaultProps =
|
|
998
|
+
typeof binding?.defaultProps === 'function'
|
|
999
|
+
? binding.defaultProps(ctx, targetCollectionField)
|
|
1000
|
+
: binding?.defaultProps;
|
|
1001
|
+
|
|
1002
|
+
ctx.model.setProps({
|
|
1003
|
+
titleField: params.label,
|
|
1004
|
+
...targetCollectionField.getComponentProps?.(),
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
if (targetUse && targetUse !== currentUse) {
|
|
1008
|
+
await rebuildFieldSubModel({
|
|
1009
|
+
parentModel: ctx.model as any,
|
|
1010
|
+
targetUse,
|
|
1011
|
+
defaultProps: {
|
|
1012
|
+
...(defaultProps || {}),
|
|
1013
|
+
titleField: params.label,
|
|
1014
|
+
},
|
|
1015
|
+
fieldSettingsInit,
|
|
1016
|
+
});
|
|
1017
|
+
} else if (fieldModel) {
|
|
1018
|
+
fieldModel.setProps({
|
|
1019
|
+
...(defaultProps || {}),
|
|
1020
|
+
titleField: params.label,
|
|
1021
|
+
});
|
|
1022
|
+
fieldModel.setStepParams('fieldSettings', 'init', fieldSettingsInit);
|
|
1023
|
+
await fieldModel.dispatchEvent('beforeRender', undefined, { useCache: false });
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
handler(ctx, params) {
|
|
1027
|
+
if (
|
|
1028
|
+
!ctx.collectionField ||
|
|
1029
|
+
!ctx.collectionField.isAssociationField() ||
|
|
1030
|
+
!isSubTableColumnConfiguredReadPretty(ctx.model)
|
|
1031
|
+
) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
ctx.model.setProps({
|
|
1035
|
+
titleField: params.label,
|
|
1036
|
+
...ctx.collectionField.targetCollection?.getField(params.label)?.getComponentProps?.(),
|
|
1037
|
+
});
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
899
1040
|
pattern: {
|
|
900
1041
|
use: 'pattern',
|
|
901
1042
|
},
|