@nocobase/client-v2 2.2.0-alpha.5 → 2.2.0-alpha.7
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/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/index.mjs +86 -86
- package/lib/index.js +95 -95
- package/package.json +8 -7
- package/src/BaseApplication.tsx +9 -5
- package/src/__tests__/app.test.tsx +26 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
- package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +2 -2
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +0 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +60 -15
- package/src/flow/components/FlowRoute.tsx +7 -3
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- 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/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +34 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +1 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
|
@@ -11,23 +11,58 @@ import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
|
11
11
|
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
12
12
|
import React, { useEffect } from 'react';
|
|
13
13
|
import { createMemoryRouter, RouterProvider, useParams } from 'react-router-dom';
|
|
14
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
15
|
+
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../../flow-compat';
|
|
15
16
|
import type { LayoutDefinition } from '../../../../layout-manager/types';
|
|
16
17
|
import { AdminLayoutComponent } from '../AdminLayoutComponent';
|
|
17
18
|
import { AdminLayoutModel } from '../AdminLayoutModel';
|
|
19
|
+
import { AdminLayoutMenuItemModel } from '../AdminLayoutMenuModels';
|
|
18
20
|
import { AdminLayoutContent } from '../AdminLayoutSlotModels';
|
|
19
21
|
|
|
22
|
+
const proLayoutLifecycle = vi.hoisted(() => ({
|
|
23
|
+
events: [] as string[],
|
|
24
|
+
routes: [] as Array<{ children?: Array<{ name?: React.ReactNode }> }>,
|
|
25
|
+
}));
|
|
26
|
+
|
|
20
27
|
vi.mock('@ant-design/pro-layout', async () => {
|
|
21
28
|
const ReactModule = await import('react');
|
|
22
29
|
const RouteContext = ReactModule.createContext({ isMobile: false });
|
|
30
|
+
const ProLayoutMock = (props: {
|
|
31
|
+
children?: React.ReactNode;
|
|
32
|
+
route?: { children?: Array<{ name?: React.ReactNode }> };
|
|
33
|
+
}) => {
|
|
34
|
+
ReactModule.useEffect(() => {
|
|
35
|
+
proLayoutLifecycle.events.push('pro-layout:mount');
|
|
36
|
+
return () => {
|
|
37
|
+
proLayoutLifecycle.events.push('pro-layout:unmount');
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
proLayoutLifecycle.routes.push(props.route || {});
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
return ReactModule.createElement(
|
|
43
|
+
RouteContext.Provider,
|
|
44
|
+
{ value: { isMobile: false } },
|
|
26
45
|
ReactModule.createElement(
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
ReactModule.createElement(
|
|
46
|
+
'div',
|
|
47
|
+
{ 'data-testid': 'pro-layout' },
|
|
48
|
+
ReactModule.createElement(
|
|
49
|
+
'nav',
|
|
50
|
+
{ 'data-testid': 'pro-layout-menu' },
|
|
51
|
+
(props.route?.children || []).map((item) =>
|
|
52
|
+
ReactModule.createElement(
|
|
53
|
+
'span',
|
|
54
|
+
{ key: String(item.name), 'data-testid': 'pro-layout-menu-item' },
|
|
55
|
+
item.name,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
props.children,
|
|
30
60
|
),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
default: ProLayoutMock,
|
|
31
66
|
RouteContext,
|
|
32
67
|
};
|
|
33
68
|
});
|
|
@@ -53,6 +88,10 @@ vi.mock('../AppListRender', () => ({
|
|
|
53
88
|
useAppListRender: () => undefined,
|
|
54
89
|
}));
|
|
55
90
|
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
delete (window as Window & { __nocobase_modern_client_prefix__?: string }).__nocobase_modern_client_prefix__;
|
|
93
|
+
});
|
|
94
|
+
|
|
56
95
|
describe('AdminLayoutComponent', () => {
|
|
57
96
|
it('keeps custom admin layout pages alive by layout route name', async () => {
|
|
58
97
|
const layout: LayoutDefinition = {
|
|
@@ -185,4 +224,212 @@ describe('AdminLayoutComponent', () => {
|
|
|
185
224
|
|
|
186
225
|
expect(deactivateLayout).toHaveBeenCalledTimes(1);
|
|
187
226
|
});
|
|
227
|
+
|
|
228
|
+
it('keeps layout content mounted when the menu route tree refreshes', async () => {
|
|
229
|
+
proLayoutLifecycle.events = [];
|
|
230
|
+
proLayoutLifecycle.routes = [];
|
|
231
|
+
const layout: LayoutDefinition = {
|
|
232
|
+
routeName: 'admin',
|
|
233
|
+
routePath: '/admin',
|
|
234
|
+
rootRouteName: 'admin',
|
|
235
|
+
uid: 'admin-layout-model',
|
|
236
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
237
|
+
rootPageModelClass: 'RootPageModel',
|
|
238
|
+
childPageModelClass: 'ChildPageModel',
|
|
239
|
+
authCheck: true,
|
|
240
|
+
};
|
|
241
|
+
const events: string[] = [];
|
|
242
|
+
const engine = new FlowEngine();
|
|
243
|
+
engine.context.defineProperty('routeRepository', {
|
|
244
|
+
value: {
|
|
245
|
+
activateLayout: vi.fn(() => vi.fn()),
|
|
246
|
+
ensureAccessibleLoaded: vi.fn(async () => []),
|
|
247
|
+
listAccessible: () => [],
|
|
248
|
+
subscribe: vi.fn(),
|
|
249
|
+
unsubscribe: vi.fn(),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
engine.context.defineProperty('t', {
|
|
253
|
+
value: (key: string) => key,
|
|
254
|
+
});
|
|
255
|
+
const model = engine.createModel<AdminLayoutModel>({
|
|
256
|
+
uid: layout.uid,
|
|
257
|
+
use: AdminLayoutModel,
|
|
258
|
+
props: {
|
|
259
|
+
layout,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const Page = () => {
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
events.push('page:mount');
|
|
266
|
+
return () => {
|
|
267
|
+
events.push('page:unmount');
|
|
268
|
+
};
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
return <div data-testid="custom-admin-route">Workflow tasks</div>;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const router = createMemoryRouter(
|
|
275
|
+
[
|
|
276
|
+
{
|
|
277
|
+
id: layout.routeName,
|
|
278
|
+
path: layout.routePath,
|
|
279
|
+
element: <AdminLayoutComponent model={model} />,
|
|
280
|
+
children: [
|
|
281
|
+
{
|
|
282
|
+
id: 'admin.workflow.tasks',
|
|
283
|
+
path: 'workflow/tasks/:taskType?/:status?/:popupId?',
|
|
284
|
+
element: <Page />,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
{
|
|
290
|
+
initialEntries: ['/admin/workflow/tasks/approval-apply/pending'],
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
render(
|
|
295
|
+
<FlowEngineProvider engine={engine}>
|
|
296
|
+
<RouterProvider router={router} />
|
|
297
|
+
</FlowEngineProvider>,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
expect(await screen.findByTestId('custom-admin-route')).toBeInTheDocument();
|
|
301
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
302
|
+
|
|
303
|
+
act(() => {
|
|
304
|
+
model.refreshMenuRouteTree();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await act(async () => {
|
|
308
|
+
await router.navigate('/admin/workflow/tasks/approval-apply/pending/1');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(screen.getByTestId('custom-admin-route')).toBeInTheDocument();
|
|
312
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
313
|
+
expect(events).toEqual(['page:mount']);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('updates ProLayout menu routes after accessible routes change without remounting the layout', async () => {
|
|
317
|
+
proLayoutLifecycle.events = [];
|
|
318
|
+
proLayoutLifecycle.routes = [];
|
|
319
|
+
(window as Window & { __nocobase_modern_client_prefix__?: string }).__nocobase_modern_client_prefix__ = 'v2';
|
|
320
|
+
const layout: LayoutDefinition = {
|
|
321
|
+
routeName: 'admin',
|
|
322
|
+
routePath: '/admin',
|
|
323
|
+
rootRouteName: 'admin',
|
|
324
|
+
uid: 'admin-layout-model',
|
|
325
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
326
|
+
rootPageModelClass: 'RootPageModel',
|
|
327
|
+
childPageModelClass: 'ChildPageModel',
|
|
328
|
+
authCheck: true,
|
|
329
|
+
};
|
|
330
|
+
let accessibleRoutes: NocoBaseDesktopRoute[] = [
|
|
331
|
+
{
|
|
332
|
+
id: 1,
|
|
333
|
+
title: 'Visible page',
|
|
334
|
+
schemaUid: 'visible-page',
|
|
335
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
336
|
+
},
|
|
337
|
+
];
|
|
338
|
+
let routeRepositorySubscriber: (() => void) | undefined;
|
|
339
|
+
const events: string[] = [];
|
|
340
|
+
const engine = new FlowEngine();
|
|
341
|
+
engine.registerModels({
|
|
342
|
+
AdminLayoutMenuItemModel,
|
|
343
|
+
});
|
|
344
|
+
engine.context.defineProperty('routeRepository', {
|
|
345
|
+
value: {
|
|
346
|
+
activateLayout: vi.fn(() => vi.fn()),
|
|
347
|
+
ensureAccessibleLoaded: vi.fn(async () => accessibleRoutes),
|
|
348
|
+
listAccessible: () => accessibleRoutes,
|
|
349
|
+
subscribe: vi.fn((subscriber: () => void) => {
|
|
350
|
+
routeRepositorySubscriber = subscriber;
|
|
351
|
+
}),
|
|
352
|
+
unsubscribe: vi.fn(),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
engine.context.defineProperty('app', {
|
|
356
|
+
value: {
|
|
357
|
+
getPublicPath: () => '/v/',
|
|
358
|
+
router: {
|
|
359
|
+
getBasename: () => '/v',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
engine.context.defineProperty('t', {
|
|
364
|
+
value: (key: string) => key,
|
|
365
|
+
});
|
|
366
|
+
const model = engine.createModel<AdminLayoutModel>({
|
|
367
|
+
uid: layout.uid,
|
|
368
|
+
use: AdminLayoutModel,
|
|
369
|
+
props: {
|
|
370
|
+
layout,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const Page = () => {
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
events.push('page:mount');
|
|
377
|
+
return () => {
|
|
378
|
+
events.push('page:unmount');
|
|
379
|
+
};
|
|
380
|
+
}, []);
|
|
381
|
+
|
|
382
|
+
return <div data-testid="custom-admin-route">Workflow tasks</div>;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const router = createMemoryRouter(
|
|
386
|
+
[
|
|
387
|
+
{
|
|
388
|
+
id: layout.routeName,
|
|
389
|
+
path: layout.routePath,
|
|
390
|
+
element: <AdminLayoutComponent model={model} />,
|
|
391
|
+
children: [
|
|
392
|
+
{
|
|
393
|
+
id: 'admin.workflow.tasks',
|
|
394
|
+
path: 'workflow/tasks/:taskType?/:status?/:popupId?',
|
|
395
|
+
element: <Page />,
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
{
|
|
401
|
+
initialEntries: ['/admin/workflow/tasks/approval-apply/pending'],
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
render(
|
|
406
|
+
<FlowEngineProvider engine={engine}>
|
|
407
|
+
<RouterProvider router={router} />
|
|
408
|
+
</FlowEngineProvider>,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
expect(await screen.findByText('Visible page')).toBeInTheDocument();
|
|
412
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
413
|
+
|
|
414
|
+
act(() => {
|
|
415
|
+
accessibleRoutes = [];
|
|
416
|
+
routeRepositorySubscriber?.();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
await waitFor(() => {
|
|
420
|
+
expect(screen.queryByText('Visible page')).not.toBeInTheDocument();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await act(async () => {
|
|
424
|
+
await router.navigate('/admin/workflow/tasks/approval-apply/pending/1');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
expect(screen.getByTestId('custom-admin-route')).toBeInTheDocument();
|
|
428
|
+
expect(proLayoutLifecycle.events).toEqual(['pro-layout:mount']);
|
|
429
|
+
expect(events).toEqual(['page:mount']);
|
|
430
|
+
expect(
|
|
431
|
+
proLayoutLifecycle.routes.some((route) => route.children?.some((item) => item.name === 'Visible page')),
|
|
432
|
+
).toBe(true);
|
|
433
|
+
expect(proLayoutLifecycle.routes.at(-1)?.children?.some((item) => item.name === 'Visible page')).toBe(false);
|
|
434
|
+
});
|
|
188
435
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React, { useMemo, useState } from 'react';
|
|
10
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
11
11
|
import { DatePicker, Select, Space } from 'antd';
|
|
12
12
|
import { inferPickerType } from '../../flow-compat';
|
|
13
13
|
import { dayjs, getDateTimeFormat, getPickerFormat } from '@nocobase/utils/client';
|
|
@@ -29,6 +29,7 @@ export interface FieldAssignExactDatePickerProps {
|
|
|
29
29
|
isRange?: boolean;
|
|
30
30
|
onChange?: (value: ExactDatePickerValue) => void;
|
|
31
31
|
style?: React.CSSProperties;
|
|
32
|
+
disabled?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function getRawSingleValue(value: unknown, isRange: boolean): unknown {
|
|
@@ -114,7 +115,7 @@ function parseRangeValue(value: unknown, format: string): [dayjs.Dayjs, dayjs.Da
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProps> = (props) => {
|
|
117
|
-
const { picker = 'date', format, showTime, timeFormat, value, isRange = false, onChange, style } = props;
|
|
118
|
+
const { picker = 'date', format, showTime, timeFormat, value, isRange = false, onChange, style, disabled } = props;
|
|
118
119
|
const flowCtx = useFlowContext();
|
|
119
120
|
const t = flowCtx.model.translate.bind(flowCtx.model);
|
|
120
121
|
|
|
@@ -124,17 +125,17 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
124
125
|
return inferPickerFromRawValue(rawSingleValue, picker);
|
|
125
126
|
});
|
|
126
127
|
|
|
127
|
-
const getResolvedFormat = (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
() => getResolvedFormat(targetPicker),
|
|
135
|
-
[targetPicker, format, picker, showTime, timeFormat],
|
|
128
|
+
const getResolvedFormat = useCallback(
|
|
129
|
+
(nextPicker: ExactDatePickerMode) => {
|
|
130
|
+
const baseFormat = nextPicker === picker && format ? format : getPickerFormat(nextPicker);
|
|
131
|
+
const dateFormat = nextPicker === 'date' ? stripTimeFromFormat(baseFormat) : baseFormat;
|
|
132
|
+
return getDateTimeFormat(nextPicker, dateFormat, showTime, timeFormat);
|
|
133
|
+
},
|
|
134
|
+
[format, picker, showTime, timeFormat],
|
|
136
135
|
);
|
|
137
136
|
|
|
137
|
+
const resolvedFormat = useMemo(() => getResolvedFormat(targetPicker), [getResolvedFormat, targetPicker]);
|
|
138
|
+
|
|
138
139
|
const singleValue = useMemo(() => {
|
|
139
140
|
if (isRange) return null;
|
|
140
141
|
return parseDateFromRawValue(value, resolvedFormat);
|
|
@@ -156,6 +157,10 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
156
157
|
);
|
|
157
158
|
|
|
158
159
|
const handlePickerTypeChange = (nextPicker: ExactDatePickerMode) => {
|
|
160
|
+
if (disabled) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
setTargetPicker(nextPicker);
|
|
160
165
|
|
|
161
166
|
if (!onChange) {
|
|
@@ -182,7 +187,11 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
182
187
|
inputReadOnly: flowCtx.isMobileLayout,
|
|
183
188
|
showTime: showTime ? { defaultValue: dayjs('00:00:00', 'HH:mm:ss') } : false,
|
|
184
189
|
value: singleValue,
|
|
190
|
+
disabled,
|
|
185
191
|
onChange: (nextDate: dayjs.Dayjs | null) => {
|
|
192
|
+
if (disabled) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
186
195
|
if (nextDate && dayjs.isDayjs(nextDate)) {
|
|
187
196
|
onChange?.(nextDate);
|
|
188
197
|
return;
|
|
@@ -200,7 +209,11 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
200
209
|
inputReadOnly: flowCtx.isMobileLayout,
|
|
201
210
|
showTime: showTime ? { defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss')] } : false,
|
|
202
211
|
value: rangeValue,
|
|
212
|
+
disabled,
|
|
203
213
|
onChange: (nextDates: [dayjs.Dayjs | null, dayjs.Dayjs | null] | null) => {
|
|
214
|
+
if (disabled) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
204
217
|
if (Array.isArray(nextDates) && nextDates[0] && nextDates[1]) {
|
|
205
218
|
onChange?.([nextDates[0], nextDates[1]]);
|
|
206
219
|
return;
|
|
@@ -218,6 +231,7 @@ export const FieldAssignExactDatePicker: React.FC<FieldAssignExactDatePickerProp
|
|
|
218
231
|
value={targetPicker}
|
|
219
232
|
options={pickerOptions}
|
|
220
233
|
onChange={handlePickerTypeChange}
|
|
234
|
+
disabled={disabled}
|
|
221
235
|
/>
|
|
222
236
|
{isRange ? <DatePicker.RangePicker {...rangePickerProps} /> : <DatePicker {...singlePickerProps} />}
|
|
223
237
|
</Space.Compact>
|
|
@@ -344,6 +344,7 @@ interface Props {
|
|
|
344
344
|
/** 是否允许在变量选择器中使用 RunJS。默认 true,保持历史行为。 */
|
|
345
345
|
allowRunJS?: boolean;
|
|
346
346
|
maxAssociationFieldDepth?: number;
|
|
347
|
+
disabled?: boolean;
|
|
347
348
|
}
|
|
348
349
|
|
|
349
350
|
type ResolvedFieldContext = {
|
|
@@ -716,6 +717,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
716
717
|
enableDateVariableAsConstant = false,
|
|
717
718
|
allowRunJS = true,
|
|
718
719
|
maxAssociationFieldDepth = 2,
|
|
720
|
+
disabled = false,
|
|
719
721
|
}) => {
|
|
720
722
|
const flowCtx = useFlowContext<FlowModelContext>();
|
|
721
723
|
const normalizeEventValue = React.useCallback((eventOrValue: unknown) => {
|
|
@@ -1031,18 +1033,22 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1031
1033
|
collectionField: effectiveCollectionField,
|
|
1032
1034
|
});
|
|
1033
1035
|
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1036
|
+
const sourceContext = resolved?.itemModel?.context || flowCtx.model?.context;
|
|
1037
|
+
const created = engine?.createModel?.(
|
|
1038
|
+
{
|
|
1039
|
+
use: 'VariableFieldFormModel',
|
|
1040
|
+
subModels: {
|
|
1041
|
+
fields: [
|
|
1042
|
+
{
|
|
1043
|
+
use: effectiveFieldModelUse,
|
|
1044
|
+
stepParams: tempFieldStepParams,
|
|
1045
|
+
props: tempFieldProps,
|
|
1046
|
+
},
|
|
1047
|
+
],
|
|
1048
|
+
},
|
|
1044
1049
|
},
|
|
1045
|
-
|
|
1050
|
+
sourceContext ? { delegate: sourceContext } : undefined,
|
|
1051
|
+
);
|
|
1046
1052
|
if (!created) return;
|
|
1047
1053
|
|
|
1048
1054
|
// 注入上下文(集合/数据源/字段/区块/资源)
|
|
@@ -1076,7 +1082,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1076
1082
|
fm.setStepParams('selectSettings', 'fieldNames', { label: overrideLabel });
|
|
1077
1083
|
}
|
|
1078
1084
|
fm?.setProps?.({
|
|
1079
|
-
disabled
|
|
1085
|
+
disabled,
|
|
1080
1086
|
readPretty: false,
|
|
1081
1087
|
pattern: 'editable',
|
|
1082
1088
|
updateAssociation: false,
|
|
@@ -1139,6 +1145,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1139
1145
|
preferFormItemFieldModel,
|
|
1140
1146
|
associationFieldNamesOverride?.label,
|
|
1141
1147
|
associationFieldNamesOverride?.value,
|
|
1148
|
+
disabled,
|
|
1142
1149
|
]);
|
|
1143
1150
|
|
|
1144
1151
|
// 当传入 operator / operatorMetaList 时,按 operator schema 适配临时字段的输入组件与 props。
|
|
@@ -1191,6 +1198,9 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1191
1198
|
React.useEffect(() => {
|
|
1192
1199
|
const coercedValue = coerceEmptyValueForRenderer(inputProps?.value);
|
|
1193
1200
|
const handleChange = (ev: any) => {
|
|
1201
|
+
if (inputProps?.disabled) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1194
1204
|
const nextRaw = normalizeEventValue(ev);
|
|
1195
1205
|
const normalizedForStore = operator ? normalizeFilterValueByOperator(operator, nextRaw) : nextRaw;
|
|
1196
1206
|
const nextValue = coerceEmptyValueForRenderer(normalizedForStore);
|
|
@@ -1222,6 +1232,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1222
1232
|
value={inputProps?.value}
|
|
1223
1233
|
onChange={(e) => inputProps?.onChange?.(normalizeEventValue(e))}
|
|
1224
1234
|
placeholder={placeholder}
|
|
1235
|
+
disabled={inputProps?.disabled}
|
|
1225
1236
|
style={withFullWidthStyle(wrapperStyle)}
|
|
1226
1237
|
/>
|
|
1227
1238
|
);
|
|
@@ -1240,6 +1251,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1240
1251
|
const C: React.FC<any> = (inputProps) => {
|
|
1241
1252
|
const wrapperStyle = pickStyle(inputProps?.style);
|
|
1242
1253
|
const raw = inputProps?.value;
|
|
1254
|
+
const isDisabled = Boolean(inputProps?.disabled);
|
|
1243
1255
|
const parsed = isCtxDateExpression(raw) ? parseCtxDateExpression(raw) : raw;
|
|
1244
1256
|
const parsedValue = typeof parsed === 'undefined' ? undefined : parsed;
|
|
1245
1257
|
const { token } = theme.useToken();
|
|
@@ -1264,6 +1276,9 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1264
1276
|
});
|
|
1265
1277
|
|
|
1266
1278
|
const handleSelect = (val: string) => {
|
|
1279
|
+
if (isDisabled) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1267
1282
|
setOpen(false);
|
|
1268
1283
|
if (val === 'exact') {
|
|
1269
1284
|
inputProps?.onChange?.('');
|
|
@@ -1278,10 +1293,16 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1278
1293
|
};
|
|
1279
1294
|
|
|
1280
1295
|
const handleExactSingleChange = (nextValue: any) => {
|
|
1296
|
+
if (isDisabled) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1281
1299
|
inputProps?.onChange?.(nextValue || '');
|
|
1282
1300
|
};
|
|
1283
1301
|
|
|
1284
1302
|
const handleExactRangeChange = (nextValue: any) => {
|
|
1303
|
+
if (isDisabled) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1285
1306
|
inputProps?.onChange?.(nextValue || '');
|
|
1286
1307
|
};
|
|
1287
1308
|
|
|
@@ -1326,7 +1347,11 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1326
1347
|
<Select
|
|
1327
1348
|
options={options}
|
|
1328
1349
|
open={open}
|
|
1329
|
-
onDropdownVisibleChange={
|
|
1350
|
+
onDropdownVisibleChange={(nextOpen) => {
|
|
1351
|
+
if (!isDisabled) {
|
|
1352
|
+
setOpen(nextOpen);
|
|
1353
|
+
}
|
|
1354
|
+
}}
|
|
1330
1355
|
allowClear={false}
|
|
1331
1356
|
style={{
|
|
1332
1357
|
width: '100%',
|
|
@@ -1336,13 +1361,18 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1336
1361
|
value={selectedType}
|
|
1337
1362
|
onChange={handleSelect}
|
|
1338
1363
|
dropdownRender={dropdownRender}
|
|
1364
|
+
disabled={isDisabled}
|
|
1339
1365
|
/>
|
|
1340
1366
|
{['past', 'next'].includes(selectedType) && [
|
|
1341
1367
|
<InputNumber
|
|
1342
1368
|
key="number"
|
|
1343
1369
|
style={{ flex: 1 }}
|
|
1344
1370
|
value={(parsedValue as any)?.number}
|
|
1371
|
+
disabled={isDisabled}
|
|
1345
1372
|
onChange={(nextNumber) => {
|
|
1373
|
+
if (isDisabled) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1346
1376
|
inputProps?.onChange?.({
|
|
1347
1377
|
...(parsedValue as any),
|
|
1348
1378
|
type: selectedType,
|
|
@@ -1355,7 +1385,11 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1355
1385
|
key="unit"
|
|
1356
1386
|
value={(parsedValue as any)?.unit}
|
|
1357
1387
|
style={{ minWidth: 130, maxWidth: 140 }}
|
|
1388
|
+
disabled={isDisabled}
|
|
1358
1389
|
onChange={(nextUnit) => {
|
|
1390
|
+
if (isDisabled) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1359
1393
|
inputProps?.onChange?.({
|
|
1360
1394
|
...(parsedValue as any),
|
|
1361
1395
|
type: selectedType,
|
|
@@ -1378,6 +1412,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1378
1412
|
isRange={isRange}
|
|
1379
1413
|
value={isRange ? exactRangeValue : exactSingleValue}
|
|
1380
1414
|
onChange={isRange ? handleExactRangeChange : handleExactSingleChange}
|
|
1415
|
+
disabled={isDisabled}
|
|
1381
1416
|
style={{ flex: 1 }}
|
|
1382
1417
|
/>
|
|
1383
1418
|
)}
|
|
@@ -1397,7 +1432,12 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1397
1432
|
|
|
1398
1433
|
const RunJSComponent = React.useMemo(() => {
|
|
1399
1434
|
const C: React.FC<any> = (inputProps) => (
|
|
1400
|
-
<RunJSValueEditor
|
|
1435
|
+
<RunJSValueEditor
|
|
1436
|
+
t={flowCtx.t}
|
|
1437
|
+
value={inputProps?.value}
|
|
1438
|
+
onChange={inputProps?.onChange}
|
|
1439
|
+
disabled={inputProps?.disabled}
|
|
1440
|
+
/>
|
|
1401
1441
|
);
|
|
1402
1442
|
return C;
|
|
1403
1443
|
}, [flowCtx]);
|
|
@@ -1443,6 +1483,10 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1443
1483
|
|
|
1444
1484
|
const handleVariableInputChange = React.useCallback(
|
|
1445
1485
|
(nextValue: any) => {
|
|
1486
|
+
if (disabled) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1446
1490
|
if (!useDateVariableConstant) {
|
|
1447
1491
|
onChange(nextValue);
|
|
1448
1492
|
return;
|
|
@@ -1450,7 +1494,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1450
1494
|
|
|
1451
1495
|
onChange(normalizeDateVariableOutput(nextValue, dateVariableComponentProps));
|
|
1452
1496
|
},
|
|
1453
|
-
[dateVariableComponentProps, onChange, useDateVariableConstant],
|
|
1497
|
+
[dateVariableComponentProps, disabled, onChange, useDateVariableConstant],
|
|
1454
1498
|
);
|
|
1455
1499
|
|
|
1456
1500
|
if (!fieldPath) {
|
|
@@ -1465,6 +1509,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
|
|
|
1465
1509
|
metaTree={metaTree}
|
|
1466
1510
|
style={{ width: '100%' }}
|
|
1467
1511
|
clearValue={''}
|
|
1512
|
+
disabled={disabled}
|
|
1468
1513
|
converters={{
|
|
1469
1514
|
renderInputComponent: (meta) => {
|
|
1470
1515
|
const firstPath = meta?.paths?.[0];
|
|
@@ -274,11 +274,12 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
274
274
|
useEffect(() => {
|
|
275
275
|
let active = true;
|
|
276
276
|
const requestId = ++requestIdRef.current;
|
|
277
|
+
const requiresAccessibleRoute = shouldRequireAccessibleRoute(routeLayout);
|
|
277
278
|
|
|
278
279
|
const run = async () => {
|
|
279
280
|
setGuardState({ pageUid, pending: true, allowBridge: false, notFound: false });
|
|
280
281
|
|
|
281
|
-
if (!skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
|
|
282
|
+
if (requiresAccessibleRoute && !skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
|
|
282
283
|
try {
|
|
283
284
|
await routeRepository?.ensureAccessibleLoaded?.();
|
|
284
285
|
} catch (_error) {
|
|
@@ -293,8 +294,11 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
293
294
|
return;
|
|
294
295
|
}
|
|
295
296
|
|
|
296
|
-
const route =
|
|
297
|
-
|
|
297
|
+
const route =
|
|
298
|
+
skipRouteRepositoryCheck || !requiresAccessibleRoute
|
|
299
|
+
? undefined
|
|
300
|
+
: getAccessibleRouteByPageUid(routeRepository, pageUid);
|
|
301
|
+
if (!route && !skipRouteRepositoryCheck && requiresAccessibleRoute) {
|
|
298
302
|
setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
|
|
299
303
|
return;
|
|
300
304
|
}
|
|
@@ -15,6 +15,7 @@ export interface RunJSValueEditorProps {
|
|
|
15
15
|
t?: (key: string) => string;
|
|
16
16
|
value?: unknown;
|
|
17
17
|
onChange?: (value: RunJSValue) => void;
|
|
18
|
+
disabled?: boolean;
|
|
18
19
|
height?: string;
|
|
19
20
|
scene?: string;
|
|
20
21
|
containerStyle?: React.CSSProperties;
|
|
@@ -25,6 +26,7 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
|
|
|
25
26
|
t,
|
|
26
27
|
value,
|
|
27
28
|
onChange,
|
|
29
|
+
disabled,
|
|
28
30
|
height = '200px',
|
|
29
31
|
scene = 'formValue',
|
|
30
32
|
containerStyle = { flex: 1, minWidth: 0 },
|
|
@@ -38,9 +40,15 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
|
|
|
38
40
|
<div style={containerStyle}>
|
|
39
41
|
<CodeEditor
|
|
40
42
|
value={current.code}
|
|
41
|
-
onChange={(code) =>
|
|
43
|
+
onChange={(code) => {
|
|
44
|
+
if (disabled) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
onChange?.({ ...current, code });
|
|
48
|
+
}}
|
|
42
49
|
version={current.version}
|
|
43
50
|
height={height}
|
|
51
|
+
readonly={disabled}
|
|
44
52
|
enableLinter
|
|
45
53
|
placeholder={placeholderText}
|
|
46
54
|
scene={scene}
|