@nocobase/flow-engine 2.1.36 → 2.1.38
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/lib/components/FlowContextSelector.js +7 -1
- package/lib/flowContext.d.ts +5 -1
- package/lib/flowContext.js +18 -6
- package/lib/utils/associationObjectVariable.d.ts +10 -0
- package/lib/utils/associationObjectVariable.js +10 -7
- package/lib/utils/dateVariable.d.ts +22 -0
- package/lib/utils/dateVariable.js +123 -16
- package/lib/utils/index.d.ts +3 -3
- package/lib/utils/index.js +8 -0
- package/lib/utils/params-resolvers.d.ts +3 -0
- package/lib/utils/params-resolvers.js +10 -0
- package/lib/utils/variablesParams.js +5 -0
- package/lib/views/createViewMeta.d.ts +1 -0
- package/lib/views/createViewMeta.js +53 -22
- package/package.json +4 -4
- package/src/__tests__/createViewMeta.popup.test.ts +84 -1
- package/src/__tests__/flowContext.test.ts +8 -0
- package/src/__tests__/objectVariable.test.ts +6 -1
- package/src/components/FlowContextSelector.tsx +7 -1
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +39 -3
- package/src/flowContext.ts +31 -5
- package/src/utils/__tests__/dateVariable.test.ts +57 -4
- package/src/utils/__tests__/variablesParams.test.ts +28 -1
- package/src/utils/associationObjectVariable.ts +9 -6
- package/src/utils/dateVariable.ts +145 -18
- package/src/utils/index.ts +17 -2
- package/src/utils/params-resolvers.ts +12 -0
- package/src/utils/variablesParams.ts +10 -0
- package/src/views/createViewMeta.ts +52 -18
|
@@ -51,13 +51,14 @@ function findFieldByName(collection: Collection | null | undefined, name?: strin
|
|
|
51
51
|
* @param primaryKey 主键字段名
|
|
52
52
|
* @returns 解析出的主键值,无法解析时返回 undefined
|
|
53
53
|
*/
|
|
54
|
-
function
|
|
54
|
+
export function getAssociationFilterByTk(value: unknown, primaryKey: string | string[]) {
|
|
55
55
|
if (value == null) return undefined;
|
|
56
56
|
if (Array.isArray(primaryKey)) {
|
|
57
57
|
if (typeof value !== 'object' || !value) return undefined;
|
|
58
|
-
const
|
|
58
|
+
const record = value as Record<string, unknown>;
|
|
59
|
+
const out: Record<string, unknown> = {};
|
|
59
60
|
for (const k of primaryKey) {
|
|
60
|
-
const v =
|
|
61
|
+
const v = record[k];
|
|
61
62
|
if (typeof v === 'undefined' || v === null) return undefined;
|
|
62
63
|
out[k] = v;
|
|
63
64
|
}
|
|
@@ -65,7 +66,7 @@ function toFilterByTk(value: unknown, primaryKey: string | string[]) {
|
|
|
65
66
|
}
|
|
66
67
|
if (typeof value === 'string' || typeof value === 'number') return value;
|
|
67
68
|
if (typeof value === 'object') {
|
|
68
|
-
return (value as
|
|
69
|
+
return (value as Record<string, unknown>)[primaryKey];
|
|
69
70
|
}
|
|
70
71
|
return undefined;
|
|
71
72
|
}
|
|
@@ -149,7 +150,9 @@ export function createAssociationAwareObjectMetaFactory(
|
|
|
149
150
|
if (associationValue == null) continue;
|
|
150
151
|
|
|
151
152
|
if (Array.isArray(associationValue)) {
|
|
152
|
-
const ids = associationValue
|
|
153
|
+
const ids = associationValue
|
|
154
|
+
.map((item) => getAssociationFilterByTk(item, primaryKey))
|
|
155
|
+
.filter((v) => v != null);
|
|
153
156
|
if (ids.length) {
|
|
154
157
|
params[name] = {
|
|
155
158
|
collection: target,
|
|
@@ -158,7 +161,7 @@ export function createAssociationAwareObjectMetaFactory(
|
|
|
158
161
|
};
|
|
159
162
|
}
|
|
160
163
|
} else {
|
|
161
|
-
const id =
|
|
164
|
+
const id = getAssociationFilterByTk(associationValue, primaryKey);
|
|
162
165
|
if (id != null) {
|
|
163
166
|
params[name] = {
|
|
164
167
|
collection: target,
|
|
@@ -11,7 +11,7 @@ import dayjs from 'dayjs';
|
|
|
11
11
|
|
|
12
12
|
const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const PRESET_KEY_LIST = [
|
|
15
15
|
'today',
|
|
16
16
|
'now',
|
|
17
17
|
'yesterday',
|
|
@@ -28,10 +28,28 @@ const PRESET_KEYS = new Set([
|
|
|
28
28
|
'thisYear',
|
|
29
29
|
'lastYear',
|
|
30
30
|
'nextYear',
|
|
31
|
-
]
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export type CtxDatePreset = (typeof PRESET_KEY_LIST)[number];
|
|
34
|
+
export type CtxDateRelativeDirection = 'next' | 'past';
|
|
35
|
+
export type CtxDateRelativeUnit = 'day' | 'week' | 'month' | 'year';
|
|
36
|
+
|
|
37
|
+
export type CtxDateExpressionConfig =
|
|
38
|
+
| { kind: 'exact'; value: string | [string, string]; format?: string }
|
|
39
|
+
| {
|
|
40
|
+
kind: 'relative';
|
|
41
|
+
direction: CtxDateRelativeDirection;
|
|
42
|
+
amount: number;
|
|
43
|
+
unit: CtxDateRelativeUnit;
|
|
44
|
+
format?: string;
|
|
45
|
+
}
|
|
46
|
+
| { kind: 'preset'; preset: CtxDatePreset; format?: string };
|
|
47
|
+
|
|
48
|
+
const PRESET_KEYS = new Set<string>(PRESET_KEY_LIST);
|
|
32
49
|
|
|
33
50
|
const RELATIVE_DIRECTIONS = new Set(['next', 'past']);
|
|
34
51
|
const RELATIVE_UNITS = new Set(['day', 'week', 'month', 'year']);
|
|
52
|
+
const MAX_DATE_FORMAT_LENGTH = 128;
|
|
35
53
|
|
|
36
54
|
function parseCtxDateSegments(value: string): string[] | null {
|
|
37
55
|
if (typeof value !== 'string') return null;
|
|
@@ -46,8 +64,7 @@ function parseCtxDateSegments(value: string): string[] | null {
|
|
|
46
64
|
.filter(Boolean);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
67
|
+
function isBaseCtxDatePathPrefix(segments: string[]): boolean {
|
|
51
68
|
if (segments[0] !== 'date') return false;
|
|
52
69
|
if (segments.length === 1) return true;
|
|
53
70
|
|
|
@@ -96,6 +113,36 @@ export function isCtxDatePathPrefix(pathSegments: string[]): boolean {
|
|
|
96
113
|
return false;
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
function decodeFormatToken(token: string): string | undefined {
|
|
117
|
+
const raw = String(token || '');
|
|
118
|
+
if (!raw.startsWith('v')) return undefined;
|
|
119
|
+
const decoded = decodeBase64Url(raw.slice(1));
|
|
120
|
+
if (!decoded || decoded.length > MAX_DATE_FORMAT_LENGTH) return undefined;
|
|
121
|
+
return decoded;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function splitFormattedDateSegments(segments: string[]): { baseSegments: string[]; format?: string } | null {
|
|
125
|
+
if (segments[0] !== 'date') return null;
|
|
126
|
+
if (segments[1] !== 'format') return { baseSegments: segments };
|
|
127
|
+
if (segments.length < 4) return null;
|
|
128
|
+
|
|
129
|
+
const format = decodeFormatToken(segments[2]);
|
|
130
|
+
if (!format) return null;
|
|
131
|
+
return { baseSegments: ['date', ...segments.slice(3)], format };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function isCtxDatePathPrefix(pathSegments: string[]): boolean {
|
|
135
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
136
|
+
if (segments[0] !== 'date') return false;
|
|
137
|
+
if (segments.length === 1) return true;
|
|
138
|
+
if (segments[1] !== 'format') return isBaseCtxDatePathPrefix(segments);
|
|
139
|
+
if (segments.length === 2) return true;
|
|
140
|
+
if (segments.length === 3) return typeof decodeFormatToken(segments[2]) === 'string';
|
|
141
|
+
|
|
142
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
143
|
+
return formatted ? isBaseCtxDatePathPrefix(formatted.baseSegments) : false;
|
|
144
|
+
}
|
|
145
|
+
|
|
99
146
|
function withDatePrefix(pathSegments: string[]): string[] {
|
|
100
147
|
if (pathSegments[0] === 'date') {
|
|
101
148
|
return pathSegments;
|
|
@@ -210,27 +257,29 @@ export function isCtxDateExpression(value: unknown): value is string {
|
|
|
210
257
|
export function isCompleteCtxDatePath(pathSegments: string[]): boolean {
|
|
211
258
|
if (!isCtxDatePathPrefix(pathSegments)) return false;
|
|
212
259
|
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
213
|
-
|
|
260
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
261
|
+
if (!formatted) return false;
|
|
262
|
+
const baseSegments = formatted.baseSegments;
|
|
214
263
|
|
|
215
|
-
if (
|
|
216
|
-
return
|
|
264
|
+
if (baseSegments[1] === 'preset') {
|
|
265
|
+
return baseSegments.length === 3 && PRESET_KEYS.has(baseSegments[2]);
|
|
217
266
|
}
|
|
218
267
|
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
268
|
+
if (baseSegments[1] === 'relative') {
|
|
269
|
+
if (baseSegments.length !== 5) return false;
|
|
221
270
|
return (
|
|
222
|
-
RELATIVE_DIRECTIONS.has(
|
|
223
|
-
RELATIVE_UNITS.has(
|
|
224
|
-
typeof parseNumberToken(
|
|
271
|
+
RELATIVE_DIRECTIONS.has(baseSegments[2]) &&
|
|
272
|
+
RELATIVE_UNITS.has(baseSegments[3]) &&
|
|
273
|
+
typeof parseNumberToken(baseSegments[4]) === 'number'
|
|
225
274
|
);
|
|
226
275
|
}
|
|
227
276
|
|
|
228
|
-
if (
|
|
229
|
-
return
|
|
277
|
+
if (baseSegments[1] === 'exact' && baseSegments[2] === 'single' && baseSegments[3] === 'date') {
|
|
278
|
+
return baseSegments.length === 5 && /^v.+/.test(baseSegments[4]);
|
|
230
279
|
}
|
|
231
280
|
|
|
232
|
-
if (
|
|
233
|
-
return
|
|
281
|
+
if (baseSegments[1] === 'exact' && baseSegments[2] === 'range' && baseSegments[3] === 'date') {
|
|
282
|
+
return baseSegments.length === 6 && /^v.+/.test(baseSegments[4]) && /^v.+/.test(baseSegments[5]);
|
|
234
283
|
}
|
|
235
284
|
|
|
236
285
|
return false;
|
|
@@ -238,7 +287,10 @@ export function isCompleteCtxDatePath(pathSegments: string[]): boolean {
|
|
|
238
287
|
|
|
239
288
|
export function parseCtxDateExpression(value: unknown): any {
|
|
240
289
|
if (!isCtxDateExpression(value)) return undefined;
|
|
241
|
-
const
|
|
290
|
+
const rawSegments = withDatePrefix(parseCtxDateSegments(value as string) || []);
|
|
291
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
292
|
+
if (!formatted) return undefined;
|
|
293
|
+
const segments = formatted.baseSegments;
|
|
242
294
|
|
|
243
295
|
if (segments[1] === 'preset' && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
|
|
244
296
|
return { type: segments[2] };
|
|
@@ -276,6 +328,66 @@ export function parseCtxDateExpression(value: unknown): any {
|
|
|
276
328
|
return undefined;
|
|
277
329
|
}
|
|
278
330
|
|
|
331
|
+
export function parseCtxDateExpressionConfig(value: unknown): CtxDateExpressionConfig | undefined {
|
|
332
|
+
if (!isCtxDateExpression(value)) return undefined;
|
|
333
|
+
const segments = withDatePrefix(parseCtxDateSegments(value) || []);
|
|
334
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
335
|
+
if (!formatted) return undefined;
|
|
336
|
+
|
|
337
|
+
const parsed = parseCtxDateExpression(value);
|
|
338
|
+
const formatConfig = formatted.format ? { format: formatted.format } : {};
|
|
339
|
+
if (typeof parsed === 'string') {
|
|
340
|
+
return { kind: 'exact', value: parsed, ...formatConfig };
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(parsed) && parsed.length === 2 && typeof parsed[0] === 'string' && typeof parsed[1] === 'string') {
|
|
343
|
+
return { kind: 'exact', value: [parsed[0], parsed[1]], ...formatConfig };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!parsed || typeof parsed !== 'object') return undefined;
|
|
347
|
+
const typed = parsed as { type?: unknown; unit?: unknown; number?: unknown };
|
|
348
|
+
if (typed.type === 'past' || typed.type === 'next') {
|
|
349
|
+
if (typeof typed.unit !== 'string' || !RELATIVE_UNITS.has(typed.unit) || typeof typed.number !== 'number') {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
kind: 'relative',
|
|
354
|
+
direction: typed.type,
|
|
355
|
+
amount: typed.number,
|
|
356
|
+
unit: typed.unit as CtxDateRelativeUnit,
|
|
357
|
+
...formatConfig,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (typeof typed.type === 'string' && PRESET_KEYS.has(typed.type)) {
|
|
362
|
+
return { kind: 'preset', preset: typed.type as CtxDatePreset, ...formatConfig };
|
|
363
|
+
}
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function serializeCtxDateExpressionConfig(config: CtxDateExpressionConfig): string | undefined {
|
|
368
|
+
let legacyValue: unknown;
|
|
369
|
+
|
|
370
|
+
if (config.kind === 'preset') {
|
|
371
|
+
if (!PRESET_KEYS.has(config.preset)) return undefined;
|
|
372
|
+
legacyValue = { type: config.preset };
|
|
373
|
+
} else if (config.kind === 'relative') {
|
|
374
|
+
if (!RELATIVE_DIRECTIONS.has(config.direction) || !RELATIVE_UNITS.has(config.unit)) return undefined;
|
|
375
|
+
const amount = Math.floor(Number(config.amount));
|
|
376
|
+
if (!Number.isFinite(amount) || amount <= 0) return undefined;
|
|
377
|
+
legacyValue = { type: config.direction, unit: config.unit, number: amount };
|
|
378
|
+
} else {
|
|
379
|
+
legacyValue = config.value;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const expression = serializeCtxDateValue(legacyValue);
|
|
383
|
+
if (!expression || !config.format) return expression;
|
|
384
|
+
|
|
385
|
+
const format = String(config.format);
|
|
386
|
+
if (!format.trim() || format.length > MAX_DATE_FORMAT_LENGTH) return undefined;
|
|
387
|
+
const segments = withDatePrefix(parseCtxDateSegments(expression) || []);
|
|
388
|
+
return toCtxDateExpression(['date', 'format', `v${encodeBase64Url(format)}`, ...segments.slice(1)]);
|
|
389
|
+
}
|
|
390
|
+
|
|
279
391
|
export function serializeCtxDateValue(value: unknown): string | undefined {
|
|
280
392
|
if (isCtxDateExpression(value)) {
|
|
281
393
|
return String(value).trim();
|
|
@@ -327,8 +439,23 @@ export function serializeCtxDateValue(value: unknown): string | undefined {
|
|
|
327
439
|
return undefined;
|
|
328
440
|
}
|
|
329
441
|
|
|
442
|
+
function formatResolvedDateValue(value: unknown, format: string): unknown {
|
|
443
|
+
const formatValue = (item: unknown) => {
|
|
444
|
+
if (typeof item !== 'string') return item;
|
|
445
|
+
const parsed = dayjs(item);
|
|
446
|
+
return parsed.isValid() ? parsed.format(format) : item;
|
|
447
|
+
};
|
|
448
|
+
return Array.isArray(value) ? value.map(formatValue) : formatValue(value);
|
|
449
|
+
}
|
|
450
|
+
|
|
330
451
|
export function resolveCtxDatePath(pathSegments: string[]): any {
|
|
331
|
-
const
|
|
452
|
+
const rawSegments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
453
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
454
|
+
if (!formatted) return undefined;
|
|
455
|
+
if (formatted.format) {
|
|
456
|
+
return formatResolvedDateValue(resolveCtxDatePath(formatted.baseSegments), formatted.format);
|
|
457
|
+
}
|
|
458
|
+
const segments = formatted.baseSegments;
|
|
332
459
|
if (segments[0] !== 'date') return undefined;
|
|
333
460
|
|
|
334
461
|
if (segments[1] === 'preset' && segments.length === 3) {
|
package/src/utils/index.ts
CHANGED
|
@@ -31,7 +31,12 @@ export { defineAction } from './flow-definitions';
|
|
|
31
31
|
export { isInheritedFrom } from './inheritance';
|
|
32
32
|
|
|
33
33
|
// 参数解析器
|
|
34
|
-
export {
|
|
34
|
+
export {
|
|
35
|
+
buildFlowModelResolveDescriptor,
|
|
36
|
+
resolveCreateModelOptions,
|
|
37
|
+
resolveDefaultParams,
|
|
38
|
+
resolveExpressions,
|
|
39
|
+
} from './params-resolvers';
|
|
35
40
|
|
|
36
41
|
// Schema 工具
|
|
37
42
|
export {
|
|
@@ -48,7 +53,11 @@ export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
|
48
53
|
|
|
49
54
|
// Record Proxy 工具
|
|
50
55
|
export { createCollectionContextMeta } from './createCollectionContextMeta';
|
|
51
|
-
export {
|
|
56
|
+
export {
|
|
57
|
+
createAssociationAwareObjectMetaFactory,
|
|
58
|
+
createAssociationSubpathResolver,
|
|
59
|
+
getAssociationFilterByTk,
|
|
60
|
+
} from './associationObjectVariable';
|
|
52
61
|
export {
|
|
53
62
|
buildRecordMeta,
|
|
54
63
|
collectContextParamsForTemplate,
|
|
@@ -83,8 +92,14 @@ export {
|
|
|
83
92
|
isCtxDatePathPrefix,
|
|
84
93
|
isCtxDateExpression,
|
|
85
94
|
parseCtxDateExpression,
|
|
95
|
+
parseCtxDateExpressionConfig,
|
|
86
96
|
resolveCtxDatePath,
|
|
97
|
+
serializeCtxDateExpressionConfig,
|
|
87
98
|
serializeCtxDateValue,
|
|
99
|
+
type CtxDateExpressionConfig,
|
|
100
|
+
type CtxDatePreset,
|
|
101
|
+
type CtxDateRelativeDirection,
|
|
102
|
+
type CtxDateRelativeUnit,
|
|
88
103
|
} from './dateVariable';
|
|
89
104
|
|
|
90
105
|
// 安全全局对象(window/document)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { getValuesByPath } from '@nocobase/shared';
|
|
11
|
+
import { generateFlowModelRdFromToken } from '@nocobase/utils/client';
|
|
11
12
|
import _ from 'lodash';
|
|
12
13
|
import { FlowContext, FlowModelContext, FlowRuntimeContext } from '../flowContext';
|
|
13
14
|
import type { FlowModel } from '../models';
|
|
@@ -78,6 +79,8 @@ export type JSONValue = string | { [key: string]: JSONValue } | JSONValue[];
|
|
|
78
79
|
// =========================
|
|
79
80
|
|
|
80
81
|
type BatchPayload = {
|
|
82
|
+
contractRd?: string;
|
|
83
|
+
rd?: string;
|
|
81
84
|
template: JSONValue;
|
|
82
85
|
contextParams?: ServerContextParams | undefined;
|
|
83
86
|
};
|
|
@@ -170,6 +173,8 @@ export function enqueueVariablesResolve(ctx: FlowRuntimeContext, payload: BatchP
|
|
|
170
173
|
try {
|
|
171
174
|
const batch = items.map((it) => ({
|
|
172
175
|
id: it.id,
|
|
176
|
+
contractRd: it.payload.contractRd,
|
|
177
|
+
rd: it.payload.rd,
|
|
173
178
|
template: it.payload.template,
|
|
174
179
|
contextParams: it.payload.contextParams || {},
|
|
175
180
|
}));
|
|
@@ -218,6 +223,13 @@ export function enqueueVariablesResolve(ctx: FlowRuntimeContext, payload: BatchP
|
|
|
218
223
|
return p;
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
export function buildFlowModelResolveDescriptor(
|
|
227
|
+
ctx: Pick<FlowModelContext, 'api'>,
|
|
228
|
+
flowModelUid?: string | number | null,
|
|
229
|
+
) {
|
|
230
|
+
return generateFlowModelRdFromToken(flowModelUid, ctx?.api?.auth?.token);
|
|
231
|
+
}
|
|
232
|
+
|
|
221
233
|
/**
|
|
222
234
|
* 解析参数中的 {{xxx}} 表达式,自动处理异步属性访问
|
|
223
235
|
*/
|
|
@@ -263,5 +263,15 @@ export async function collectContextParamsForTemplate(
|
|
|
263
263
|
input[key] = built;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
+
|
|
267
|
+
const viewPaths = usage.view || [];
|
|
268
|
+
if (
|
|
269
|
+
!input.view &&
|
|
270
|
+
viewPaths.some((path) => path === 'record' || path.startsWith('record.') || path.startsWith('record['))
|
|
271
|
+
) {
|
|
272
|
+
const recordRef = inferViewRecordRef(ctx);
|
|
273
|
+
if (recordRef) input.view = { record: recordRef };
|
|
274
|
+
}
|
|
275
|
+
|
|
266
276
|
return buildServerContextParams(ctx, input);
|
|
267
277
|
}
|
|
@@ -15,6 +15,13 @@ import type { FlowView } from './FlowView';
|
|
|
15
15
|
|
|
16
16
|
type PopupModelLike = { getStepParams?: (a: string, b: string) => any } | undefined;
|
|
17
17
|
|
|
18
|
+
function buildPopupSourceRecordRef(ref?: Pick<RecordRef, 'associationName' | 'dataSourceKey' | 'sourceId'>) {
|
|
19
|
+
if (ref?.sourceId == null || ref.sourceId === '' || typeof ref.associationName !== 'string') return undefined;
|
|
20
|
+
const collection = ref.associationName.split('.')[0];
|
|
21
|
+
if (!collection) return undefined;
|
|
22
|
+
return { collection, dataSourceKey: ref.dataSourceKey || 'main', filterByTk: ref.sourceId };
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
function isDefined(value: any) {
|
|
19
26
|
return value !== undefined && value !== null;
|
|
20
27
|
}
|
|
@@ -327,13 +334,15 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
327
334
|
const stack = getViewStack(view);
|
|
328
335
|
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
329
336
|
if (currentIndex >= 2) {
|
|
330
|
-
let cur
|
|
337
|
+
let cur = params;
|
|
331
338
|
let level = 1;
|
|
332
339
|
let parentRef = await getParentRecordRef(level, c);
|
|
333
340
|
while (parentRef) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
341
|
+
const parent: PopupVariableParams = { record: parentRef };
|
|
342
|
+
const sourceRecord = buildPopupSourceRecordRef(parentRef);
|
|
343
|
+
if (sourceRecord) parent.sourceRecord = sourceRecord;
|
|
344
|
+
cur.parent = parent;
|
|
345
|
+
cur = parent;
|
|
337
346
|
level += 1;
|
|
338
347
|
parentRef = await getParentRecordRef(level, c);
|
|
339
348
|
}
|
|
@@ -343,20 +352,12 @@ export function createPopupMeta(ctx: FlowContext, anchorView?: FlowView): Proper
|
|
|
343
352
|
}
|
|
344
353
|
|
|
345
354
|
try {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (parentCollectionName) {
|
|
353
|
-
params.sourceRecord = {
|
|
354
|
-
collection: parentCollectionName,
|
|
355
|
-
dataSourceKey: dsKey,
|
|
356
|
-
filterByTk: srcId,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
}
|
|
355
|
+
const sourceRecord = buildPopupSourceRecordRef({
|
|
356
|
+
sourceId: inputArgs?.sourceId,
|
|
357
|
+
associationName: inputArgs?.associationName,
|
|
358
|
+
dataSourceKey: inputArgs?.dataSourceKey,
|
|
359
|
+
});
|
|
360
|
+
if (sourceRecord) params.sourceRecord = sourceRecord;
|
|
360
361
|
} catch (err) {
|
|
361
362
|
c.logger?.debug?.({ err }, '[FlowEngine] buildVariablesParams: infer sourceRecord failed');
|
|
362
363
|
}
|
|
@@ -474,12 +475,14 @@ interface PopupNodeResource {
|
|
|
474
475
|
interface PopupNode {
|
|
475
476
|
uid?: string;
|
|
476
477
|
resource: PopupNodeResource;
|
|
478
|
+
sourceRecord?: unknown;
|
|
477
479
|
parent?: PopupNode;
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined> {
|
|
481
483
|
const stack = getViewStack(view);
|
|
482
484
|
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
485
|
+
const sourceRecord = view?.inputArgs?.parentItem?.value;
|
|
483
486
|
|
|
484
487
|
const openerUids = view?.inputArgs?.openerUids;
|
|
485
488
|
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
@@ -502,6 +505,7 @@ export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promi
|
|
|
502
505
|
filterByTk: args.filterByTk,
|
|
503
506
|
sourceId: args.sourceId,
|
|
504
507
|
},
|
|
508
|
+
...(typeof sourceRecord !== 'undefined' ? { sourceRecord } : {}),
|
|
505
509
|
};
|
|
506
510
|
}
|
|
507
511
|
|
|
@@ -530,6 +534,9 @@ export async function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promi
|
|
|
530
534
|
return node;
|
|
531
535
|
};
|
|
532
536
|
const currentNode = await buildNode(currentIndex);
|
|
537
|
+
if (currentNode && typeof sourceRecord !== 'undefined') {
|
|
538
|
+
currentNode.sourceRecord = sourceRecord;
|
|
539
|
+
}
|
|
533
540
|
return currentNode;
|
|
534
541
|
}
|
|
535
542
|
|
|
@@ -541,6 +548,30 @@ export function registerPopupVariable(ctx: FlowContext, view: FlowView) {
|
|
|
541
548
|
// - 任意层级 parent.parent... 下的 record / sourceRecord 及其子字段
|
|
542
549
|
const POPUP_SERVER_PATH_RE =
|
|
543
550
|
/^(?:record|sourceRecord)(?:\.|$)|^parent(?:\.parent)*(?:\.(?:record|sourceRecord))(?:\.|$)/;
|
|
551
|
+
const shouldResolveSourceRecordOnServer = (path: string): boolean => {
|
|
552
|
+
if (path !== 'sourceRecord' && !path.startsWith('sourceRecord.')) return false;
|
|
553
|
+
|
|
554
|
+
const parentItem = view?.inputArgs?.parentItem;
|
|
555
|
+
if (typeof parentItem?.value === 'undefined') return true;
|
|
556
|
+
|
|
557
|
+
const sourcePath = path === 'sourceRecord' ? '' : path.slice('sourceRecord.'.length);
|
|
558
|
+
if (!sourcePath) return false;
|
|
559
|
+
|
|
560
|
+
const parentItemResolver = view?.inputArgs?.parentItemResolver;
|
|
561
|
+
if (typeof parentItemResolver === 'function') {
|
|
562
|
+
return parentItemResolver(`value.${sourcePath}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const segments = sourcePath.split('.').filter(Boolean);
|
|
566
|
+
let current = parentItem.value;
|
|
567
|
+
for (const segment of segments) {
|
|
568
|
+
if (current === null || typeof current !== 'object' || !(segment in current)) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
current = current[segment];
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
};
|
|
544
575
|
// 始终注册 popup 变量:
|
|
545
576
|
// - 若当前视图无可推断记录,仅在元信息中不呈现 record 字段;
|
|
546
577
|
// - 但仍可依据 navigation 推断并展示上级弹窗信息。
|
|
@@ -549,6 +580,9 @@ export function registerPopupVariable(ctx: FlowContext, view: FlowView) {
|
|
|
549
580
|
meta: createPopupMeta(ctx, view),
|
|
550
581
|
resolveOnServer: (p: string) => {
|
|
551
582
|
try {
|
|
583
|
+
if (p === 'sourceRecord' || p.startsWith('sourceRecord.')) {
|
|
584
|
+
return shouldResolveSourceRecordOnServer(p);
|
|
585
|
+
}
|
|
552
586
|
return !!p && POPUP_SERVER_PATH_RE.test(p);
|
|
553
587
|
} catch (_) {
|
|
554
588
|
return false;
|