@nocobase/flow-engine 2.0.0-beta.9 → 2.0.0
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/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.js +12 -1
- package/lib/provider.js +5 -5
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/setup.js +6 -0
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/runjsContext.test.ts +10 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/setup.ts +6 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isInheritedFrom,
|
|
14
14
|
resolveDefaultParams,
|
|
15
15
|
resolveStepUiSchema,
|
|
16
|
+
resolveStepDisabledInSettings,
|
|
16
17
|
shouldHideStepInSettings,
|
|
17
18
|
FlowExitException,
|
|
18
19
|
defineAction,
|
|
@@ -1117,4 +1118,98 @@ describe('Utils', () => {
|
|
|
1117
1118
|
consoleSpy.mockRestore();
|
|
1118
1119
|
});
|
|
1119
1120
|
});
|
|
1121
|
+
|
|
1122
|
+
// ==================== resolveStepDisabledInSettings() FUNCTION ====================
|
|
1123
|
+
describe('resolveStepDisabledInSettings()', () => {
|
|
1124
|
+
let mockFlow: any;
|
|
1125
|
+
let mockStep: StepDefinition;
|
|
1126
|
+
|
|
1127
|
+
beforeEach(() => {
|
|
1128
|
+
mockFlow = {
|
|
1129
|
+
key: 'testFlow',
|
|
1130
|
+
title: 'Test Flow',
|
|
1131
|
+
steps: {},
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
mockStep = {
|
|
1135
|
+
key: 'testStep',
|
|
1136
|
+
handler: vi.fn(),
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
test('returns disabled=false when step is falsy', async () => {
|
|
1141
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, null as any);
|
|
1142
|
+
expect(result).toEqual({ disabled: false });
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
test('respects static step.disabledInSettings and disabledReasonInSettings', async () => {
|
|
1146
|
+
mockStep.disabledInSettings = true;
|
|
1147
|
+
mockStep.disabledReasonInSettings = 'legacy reason';
|
|
1148
|
+
|
|
1149
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
|
|
1150
|
+
expect(result).toEqual({ disabled: true, reason: 'legacy reason' });
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test('falls back to action disabled settings when step value is undefined', async () => {
|
|
1154
|
+
const action: ActionDefinition = {
|
|
1155
|
+
name: 'testAction',
|
|
1156
|
+
handler: vi.fn(),
|
|
1157
|
+
disabledInSettings: true,
|
|
1158
|
+
disabledReasonInSettings: 'from action',
|
|
1159
|
+
} as any;
|
|
1160
|
+
|
|
1161
|
+
mockStep.use = 'testAction';
|
|
1162
|
+
mockModel.flowEngine.getAction = vi.fn().mockReturnValue(action);
|
|
1163
|
+
|
|
1164
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
|
|
1165
|
+
expect(result).toEqual({ disabled: true, reason: 'from action' });
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
test('prefers step disabled settings over action values', async () => {
|
|
1169
|
+
const action: ActionDefinition = {
|
|
1170
|
+
name: 'testAction',
|
|
1171
|
+
handler: vi.fn(),
|
|
1172
|
+
disabledInSettings: false,
|
|
1173
|
+
disabledReasonInSettings: 'from action',
|
|
1174
|
+
} as any;
|
|
1175
|
+
|
|
1176
|
+
mockStep.use = 'testAction';
|
|
1177
|
+
mockStep.disabledInSettings = true;
|
|
1178
|
+
mockStep.disabledReasonInSettings = 'from step';
|
|
1179
|
+
mockModel.flowEngine.getAction = vi.fn().mockReturnValue(action);
|
|
1180
|
+
|
|
1181
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
|
|
1182
|
+
expect(result).toEqual({ disabled: true, reason: 'from step' });
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
test('evaluates function disabled settings with FlowRuntimeContext', async () => {
|
|
1186
|
+
const disabledFn = vi.fn().mockResolvedValue(true);
|
|
1187
|
+
const reasonFn = vi.fn().mockResolvedValue('computed reason');
|
|
1188
|
+
mockStep.disabledInSettings = disabledFn as any;
|
|
1189
|
+
mockStep.disabledReasonInSettings = reasonFn as any;
|
|
1190
|
+
|
|
1191
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
|
|
1192
|
+
|
|
1193
|
+
expect(disabledFn).toHaveBeenCalledTimes(1);
|
|
1194
|
+
expect(reasonFn).toHaveBeenCalledTimes(1);
|
|
1195
|
+
const disabledCtx = disabledFn.mock.calls[0][0] as FlowRuntimeContext;
|
|
1196
|
+
const reasonCtx = reasonFn.mock.calls[0][0] as FlowRuntimeContext;
|
|
1197
|
+
expect(disabledCtx).toBeInstanceOf(FlowRuntimeContext);
|
|
1198
|
+
expect(reasonCtx).toBeInstanceOf(FlowRuntimeContext);
|
|
1199
|
+
expect((disabledCtx as any).currentStep).toBeInstanceOf(ContextPathProxy);
|
|
1200
|
+
expect(String((disabledCtx as any).currentStep)).toBe('{{ctx.currentStep}}');
|
|
1201
|
+
expect(result).toEqual({ disabled: true, reason: 'computed reason' });
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
test('returns disabled=false when function disabledInSettings throws', async () => {
|
|
1205
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1206
|
+
mockStep.disabledInSettings = vi.fn().mockRejectedValue(new Error('boom')) as any;
|
|
1207
|
+
|
|
1208
|
+
const result = await resolveStepDisabledInSettings(mockModel, mockFlow, mockStep);
|
|
1209
|
+
|
|
1210
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
1211
|
+
expect(result).toEqual({ disabled: false });
|
|
1212
|
+
consoleSpy.mockRestore();
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1120
1215
|
});
|
|
@@ -71,7 +71,7 @@ function toFilterByTk(value: unknown, primaryKey: string | string[]) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* 创建一个用于“对象类变量”(如 formValues /
|
|
74
|
+
* 创建一个用于“对象类变量”(如 formValues / item)的 `resolveOnServer` 判定函数。
|
|
75
75
|
* 仅当访问路径以“关联字段名”开头(且继续访问其子属性)时,返回 true 交由服务端解析;
|
|
76
76
|
* 否则在前端解析即可。
|
|
77
77
|
*
|
|
@@ -114,7 +114,7 @@ export function createAssociationSubpathResolver(
|
|
|
114
114
|
*
|
|
115
115
|
* @param collectionAccessor 获取集合对象,用于字段/元信息来源
|
|
116
116
|
* @param title 变量组标题(用于 UI 展示)
|
|
117
|
-
* @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.
|
|
117
|
+
* @param valueAccessor 获取当前对象值(如 ctx.form.getFieldsValue() / ctx.item)
|
|
118
118
|
* @returns PropertyMetaFactory
|
|
119
119
|
*/
|
|
120
120
|
export function createAssociationAwareObjectMetaFactory(
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { FlowContext } from '../flowContext';
|
|
10
|
+
import { FlowContext, type FlowContextMethodInfoInput, type PropertyOptions } from '../flowContext';
|
|
11
11
|
import type { ActionDefinition } from '../types';
|
|
12
|
-
import type { PropertyOptions } from '../flowContext';
|
|
13
12
|
|
|
14
13
|
// 临时上下文:
|
|
15
14
|
// - 读:优先从 scoped(临时定义)读取,兜底 parent;
|
|
@@ -51,7 +50,8 @@ export async function createEphemeralContext<TCtx extends FlowContext>(
|
|
|
51
50
|
return (propKey: string, options: any) => (scoped as FlowContext).defineProperty(propKey, options);
|
|
52
51
|
}
|
|
53
52
|
if (key === 'defineMethod') {
|
|
54
|
-
return (name: string, fn: any,
|
|
53
|
+
return (name: string, fn: any, info?: string | FlowContextMethodInfoInput) =>
|
|
54
|
+
(scoped as FlowContext).defineMethod(name, fn, info);
|
|
55
55
|
}
|
|
56
56
|
if (Reflect.has(scopedObj, key)) {
|
|
57
57
|
return Reflect.get(scopedObj, key, receiver);
|
|
@@ -118,7 +118,8 @@ export async function createEphemeralContext<TCtx extends FlowContext>(
|
|
|
118
118
|
return (propKey: string, options: any) => (parent as FlowContext).defineProperty(propKey, options);
|
|
119
119
|
}
|
|
120
120
|
if (key === 'defineMethod') {
|
|
121
|
-
return (name: string, fn: any,
|
|
121
|
+
return (name: string, fn: any, info?: string | FlowContextMethodInfoInput) =>
|
|
122
|
+
(parent as FlowContext).defineMethod(name, fn, info);
|
|
122
123
|
}
|
|
123
124
|
if (Reflect.has(scopedObj, key)) {
|
|
124
125
|
return Reflect.get(scopedObj, key, receiver);
|
|
@@ -0,0 +1,397 @@
|
|
|
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 dayjs from 'dayjs';
|
|
11
|
+
|
|
12
|
+
const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
|
|
13
|
+
|
|
14
|
+
const PRESET_KEYS = new Set([
|
|
15
|
+
'today',
|
|
16
|
+
'now',
|
|
17
|
+
'yesterday',
|
|
18
|
+
'tomorrow',
|
|
19
|
+
'thisWeek',
|
|
20
|
+
'lastWeek',
|
|
21
|
+
'nextWeek',
|
|
22
|
+
'thisMonth',
|
|
23
|
+
'lastMonth',
|
|
24
|
+
'nextMonth',
|
|
25
|
+
'thisQuarter',
|
|
26
|
+
'lastQuarter',
|
|
27
|
+
'nextQuarter',
|
|
28
|
+
'thisYear',
|
|
29
|
+
'lastYear',
|
|
30
|
+
'nextYear',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const RELATIVE_DIRECTIONS = new Set(['next', 'past']);
|
|
34
|
+
const RELATIVE_UNITS = new Set(['day', 'week', 'month', 'year']);
|
|
35
|
+
|
|
36
|
+
function parseCtxDateSegments(value: string): string[] | null {
|
|
37
|
+
if (typeof value !== 'string') return null;
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
const match = trimmed.match(CTX_DATE_REGEX);
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
const rawPath = String(match[1] || '');
|
|
42
|
+
if (!rawPath) return [];
|
|
43
|
+
return rawPath
|
|
44
|
+
.split('.')
|
|
45
|
+
.map((seg) => seg.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isCtxDatePathPrefix(pathSegments: string[]): boolean {
|
|
50
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
51
|
+
if (segments[0] !== 'date') return false;
|
|
52
|
+
if (segments.length === 1) return true;
|
|
53
|
+
|
|
54
|
+
if (segments[1] === 'preset') {
|
|
55
|
+
if (segments.length === 2) return true;
|
|
56
|
+
return segments.length === 3 && PRESET_KEYS.has(segments[2]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (segments[1] === 'relative') {
|
|
60
|
+
if (segments.length === 2) return true;
|
|
61
|
+
if (segments.length === 3) return RELATIVE_DIRECTIONS.has(segments[2]);
|
|
62
|
+
if (segments.length === 4) {
|
|
63
|
+
return RELATIVE_DIRECTIONS.has(segments[2]) && RELATIVE_UNITS.has(segments[3]);
|
|
64
|
+
}
|
|
65
|
+
if (segments.length === 5) {
|
|
66
|
+
return (
|
|
67
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
68
|
+
RELATIVE_UNITS.has(segments[3]) &&
|
|
69
|
+
typeof parseNumberToken(segments[4]) === 'number'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (segments[1] === 'exact') {
|
|
76
|
+
if (segments.length === 2) return true;
|
|
77
|
+
|
|
78
|
+
if (segments[2] === 'single') {
|
|
79
|
+
if (segments.length === 3) return true;
|
|
80
|
+
if (segments.length === 4) return segments[3] === 'date';
|
|
81
|
+
if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (segments[2] === 'range') {
|
|
86
|
+
if (segments.length === 3) return true;
|
|
87
|
+
if (segments.length === 4) return segments[3] === 'date';
|
|
88
|
+
if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
|
|
89
|
+
if (segments.length === 6) return segments[3] === 'date' && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function withDatePrefix(pathSegments: string[]): string[] {
|
|
100
|
+
if (pathSegments[0] === 'date') {
|
|
101
|
+
return pathSegments;
|
|
102
|
+
}
|
|
103
|
+
return ['date', ...pathSegments];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toCtxDateExpression(pathSegments: string[]): string {
|
|
107
|
+
const segs = withDatePrefix(pathSegments);
|
|
108
|
+
return `{{ ctx.${segs.join('.')} }}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function utf8ToBase64(input: string): string {
|
|
112
|
+
const globalBuffer = (globalThis as any)?.Buffer;
|
|
113
|
+
if (globalBuffer && typeof globalBuffer.from === 'function') {
|
|
114
|
+
return globalBuffer.from(input, 'utf8').toString('base64');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof btoa === 'function') {
|
|
118
|
+
const encoded = encodeURIComponent(input).replace(/%([0-9A-F]{2})/g, (_m, p1) =>
|
|
119
|
+
String.fromCharCode(parseInt(p1, 16)),
|
|
120
|
+
);
|
|
121
|
+
return btoa(encoded);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error('No base64 encoder available');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function base64ToUtf8(input: string): string {
|
|
128
|
+
const globalBuffer = (globalThis as any)?.Buffer;
|
|
129
|
+
if (globalBuffer && typeof globalBuffer.from === 'function') {
|
|
130
|
+
return globalBuffer.from(input, 'base64').toString('utf8');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof atob === 'function') {
|
|
134
|
+
const binary = atob(input);
|
|
135
|
+
const encoded = Array.from(binary)
|
|
136
|
+
.map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`)
|
|
137
|
+
.join('');
|
|
138
|
+
return decodeURIComponent(encoded);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error('No base64 decoder available');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeToString(value: any): string | undefined {
|
|
145
|
+
if (value == null) return undefined;
|
|
146
|
+
if (typeof value === 'string') {
|
|
147
|
+
const trimmed = value.trim();
|
|
148
|
+
return trimmed.length ? trimmed : undefined;
|
|
149
|
+
}
|
|
150
|
+
if (dayjs.isDayjs(value)) {
|
|
151
|
+
return value.toISOString();
|
|
152
|
+
}
|
|
153
|
+
if (value instanceof Date) {
|
|
154
|
+
return dayjs(value).toISOString();
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
157
|
+
return String(value);
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseNumberToken(token: string): number | undefined {
|
|
163
|
+
const match = String(token || '').match(/^n(\d+)$/);
|
|
164
|
+
if (!match) return undefined;
|
|
165
|
+
const parsed = Number(match[1]);
|
|
166
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function startOfIsoWeek(date: dayjs.Dayjs): dayjs.Dayjs {
|
|
171
|
+
const day = date.day();
|
|
172
|
+
const offset = day === 0 ? -6 : 1 - day;
|
|
173
|
+
return date.add(offset, 'day').startOf('day');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function startOfQuarter(date: dayjs.Dayjs): dayjs.Dayjs {
|
|
177
|
+
const month = date.month();
|
|
178
|
+
const quarterStartMonth = Math.floor(month / 3) * 3;
|
|
179
|
+
return date.month(quarterStartMonth).startOf('month');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function encodeBase64Url(input: string): string {
|
|
183
|
+
const base64 = utf8ToBase64(String(input || ''));
|
|
184
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function decodeBase64Url(input: string): string | undefined {
|
|
188
|
+
try {
|
|
189
|
+
const raw = String(input || '').replace(/=+$/g, '');
|
|
190
|
+
if (!raw) return undefined;
|
|
191
|
+
if (!/^[A-Za-z0-9_-]+$/.test(raw)) return undefined;
|
|
192
|
+
|
|
193
|
+
const normalized = raw.replace(/-/g, '+').replace(/_/g, '/');
|
|
194
|
+
const padLength = normalized.length % 4 === 0 ? 0 : 4 - (normalized.length % 4);
|
|
195
|
+
const padded = `${normalized}${'='.repeat(padLength)}`;
|
|
196
|
+
const decoded = base64ToUtf8(padded);
|
|
197
|
+
|
|
198
|
+
if (encodeBase64Url(decoded) !== raw) return undefined;
|
|
199
|
+
return decoded;
|
|
200
|
+
} catch (_error) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function isCtxDateExpression(value: unknown): value is string {
|
|
206
|
+
if (typeof value !== 'string') return false;
|
|
207
|
+
return CTX_DATE_REGEX.test(value.trim());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function isCompleteCtxDatePath(pathSegments: string[]): boolean {
|
|
211
|
+
if (!isCtxDatePathPrefix(pathSegments)) return false;
|
|
212
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
213
|
+
if (segments[0] !== 'date') return false;
|
|
214
|
+
|
|
215
|
+
if (segments[1] === 'preset') {
|
|
216
|
+
return segments.length === 3 && PRESET_KEYS.has(segments[2]);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (segments[1] === 'relative') {
|
|
220
|
+
if (segments.length !== 5) return false;
|
|
221
|
+
return (
|
|
222
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
223
|
+
RELATIVE_UNITS.has(segments[3]) &&
|
|
224
|
+
typeof parseNumberToken(segments[4]) === 'number'
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date') {
|
|
229
|
+
return segments.length === 5 && /^v.+/.test(segments[4]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date') {
|
|
233
|
+
return segments.length === 6 && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function parseCtxDateExpression(value: unknown): any {
|
|
240
|
+
if (!isCtxDateExpression(value)) return undefined;
|
|
241
|
+
const segments = withDatePrefix(parseCtxDateSegments(value as string) || []);
|
|
242
|
+
|
|
243
|
+
if (segments[1] === 'preset' && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
|
|
244
|
+
return { type: segments[2] };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
segments[1] === 'relative' &&
|
|
249
|
+
segments.length === 5 &&
|
|
250
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
251
|
+
RELATIVE_UNITS.has(segments[3])
|
|
252
|
+
) {
|
|
253
|
+
const amount = parseNumberToken(segments[4]);
|
|
254
|
+
if (typeof amount === 'number') {
|
|
255
|
+
return { type: segments[2], unit: segments[3], number: amount };
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
|
|
261
|
+
const raw = String(segments[4] || '');
|
|
262
|
+
if (!raw.startsWith('v')) return undefined;
|
|
263
|
+
return decodeBase64Url(raw.slice(1));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
|
|
267
|
+
const leftRaw = String(segments[4] || '');
|
|
268
|
+
const rightRaw = String(segments[5] || '');
|
|
269
|
+
if (!leftRaw.startsWith('v') || !rightRaw.startsWith('v')) return undefined;
|
|
270
|
+
const left = decodeBase64Url(leftRaw.slice(1));
|
|
271
|
+
const right = decodeBase64Url(rightRaw.slice(1));
|
|
272
|
+
if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
|
|
273
|
+
return [left, right];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function serializeCtxDateValue(value: unknown): string | undefined {
|
|
280
|
+
if (isCtxDateExpression(value)) {
|
|
281
|
+
return String(value).trim();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (value == null || value === '') {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
const start = normalizeToString(value[0]);
|
|
290
|
+
const end = normalizeToString(value[1]);
|
|
291
|
+
if (start && end) {
|
|
292
|
+
return toCtxDateExpression([
|
|
293
|
+
'date',
|
|
294
|
+
'exact',
|
|
295
|
+
'range',
|
|
296
|
+
'date',
|
|
297
|
+
`v${encodeBase64Url(start)}`,
|
|
298
|
+
`v${encodeBase64Url(end)}`,
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
if (start) {
|
|
302
|
+
return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(start)}`]);
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (typeof value === 'object' && value) {
|
|
308
|
+
const typed = value as { type?: unknown; unit?: unknown; number?: unknown };
|
|
309
|
+
const type = typeof typed.type === 'string' ? typed.type : '';
|
|
310
|
+
|
|
311
|
+
if (type === 'past' || type === 'next') {
|
|
312
|
+
const unit = typeof typed.unit === 'string' && RELATIVE_UNITS.has(typed.unit) ? typed.unit : 'day';
|
|
313
|
+
const rawNumber = Number(typed.number);
|
|
314
|
+
const number = Number.isFinite(rawNumber) && rawNumber > 0 ? Math.floor(rawNumber) : 1;
|
|
315
|
+
return toCtxDateExpression(['date', 'relative', type, unit, `n${number}`]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (PRESET_KEYS.has(type)) {
|
|
319
|
+
return toCtxDateExpression(['date', 'preset', type]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const single = normalizeToString(value);
|
|
324
|
+
if (single) {
|
|
325
|
+
return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(single)}`]);
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function resolveCtxDatePath(pathSegments: string[]): any {
|
|
331
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
332
|
+
if (segments[0] !== 'date') return undefined;
|
|
333
|
+
|
|
334
|
+
if (segments[1] === 'preset' && segments.length === 3) {
|
|
335
|
+
const key = segments[2];
|
|
336
|
+
if (key === 'now') {
|
|
337
|
+
return dayjs().toISOString();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const now = dayjs();
|
|
341
|
+
|
|
342
|
+
if (key === 'today') return now.format('YYYY-MM-DD');
|
|
343
|
+
if (key === 'yesterday') return now.add(-1, 'day').format('YYYY-MM-DD');
|
|
344
|
+
if (key === 'tomorrow') return now.add(1, 'day').format('YYYY-MM-DD');
|
|
345
|
+
|
|
346
|
+
if (key === 'thisWeek') return startOfIsoWeek(now).format('YYYY-MM-DD');
|
|
347
|
+
if (key === 'lastWeek') return startOfIsoWeek(now.add(-1, 'week')).format('YYYY-MM-DD');
|
|
348
|
+
if (key === 'nextWeek') return startOfIsoWeek(now.add(1, 'week')).format('YYYY-MM-DD');
|
|
349
|
+
|
|
350
|
+
if (key === 'thisMonth') return now.startOf('month').format('YYYY-MM-DD');
|
|
351
|
+
if (key === 'lastMonth') return now.add(-1, 'month').startOf('month').format('YYYY-MM-DD');
|
|
352
|
+
if (key === 'nextMonth') return now.add(1, 'month').startOf('month').format('YYYY-MM-DD');
|
|
353
|
+
|
|
354
|
+
if (key === 'thisQuarter') return startOfQuarter(now).format('YYYY-MM-DD');
|
|
355
|
+
if (key === 'lastQuarter') return startOfQuarter(now.add(-3, 'month')).format('YYYY-MM-DD');
|
|
356
|
+
if (key === 'nextQuarter') return startOfQuarter(now.add(3, 'month')).format('YYYY-MM-DD');
|
|
357
|
+
|
|
358
|
+
if (key === 'thisYear') return now.startOf('year').format('YYYY-MM-DD');
|
|
359
|
+
if (key === 'lastYear') return now.add(-1, 'year').startOf('year').format('YYYY-MM-DD');
|
|
360
|
+
if (key === 'nextYear') return now.add(1, 'year').startOf('year').format('YYYY-MM-DD');
|
|
361
|
+
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
segments[1] === 'relative' &&
|
|
367
|
+
segments.length === 5 &&
|
|
368
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
369
|
+
RELATIVE_UNITS.has(segments[3])
|
|
370
|
+
) {
|
|
371
|
+
const amount = parseNumberToken(segments[4]);
|
|
372
|
+
if (typeof amount !== 'number') return undefined;
|
|
373
|
+
const direction = segments[2] === 'past' ? -1 : 1;
|
|
374
|
+
const unit = segments[3] as dayjs.ManipulateType;
|
|
375
|
+
return dayjs()
|
|
376
|
+
.add(direction * amount, unit)
|
|
377
|
+
.format('YYYY-MM-DD');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
|
|
381
|
+
const token = String(segments[4] || '');
|
|
382
|
+
if (!token.startsWith('v')) return undefined;
|
|
383
|
+
return decodeBase64Url(token.slice(1));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
|
|
387
|
+
const leftToken = String(segments[4] || '');
|
|
388
|
+
const rightToken = String(segments[5] || '');
|
|
389
|
+
if (!leftToken.startsWith('v') || !rightToken.startsWith('v')) return undefined;
|
|
390
|
+
const left = decodeBase64Url(leftToken.slice(1));
|
|
391
|
+
const right = decodeBase64Url(rightToken.slice(1));
|
|
392
|
+
if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
|
|
393
|
+
return [left, right];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
package/src/utils/exceptions.ts
CHANGED
|
@@ -34,3 +34,14 @@ export class FlowExitAllException extends Error {
|
|
|
34
34
|
this.modelUid = modelUid;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 取消当前保存但保持设置弹窗打开
|
|
40
|
+
* 用于“保存前确认”场景,用户取消时不应关闭弹窗也不应提示错误
|
|
41
|
+
*/
|
|
42
|
+
export class FlowCancelSaveException extends Error {
|
|
43
|
+
constructor(message = 'Flow settings save cancelled.') {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'FlowCancelSaveException';
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
export { escapeT, getT, tExpr } from './translation';
|
|
23
23
|
|
|
24
24
|
// 异常类
|
|
25
|
-
export { FlowExitException } from './exceptions';
|
|
25
|
+
export { FlowCancelSaveException, FlowExitException } from './exceptions';
|
|
26
26
|
|
|
27
27
|
// 流程定义相关
|
|
28
28
|
export { defineAction } from './flow-definitions';
|
|
@@ -34,7 +34,13 @@ export { isInheritedFrom } from './inheritance';
|
|
|
34
34
|
export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
|
|
35
35
|
|
|
36
36
|
// Schema 工具
|
|
37
|
-
export {
|
|
37
|
+
export {
|
|
38
|
+
compileUiSchema,
|
|
39
|
+
resolveStepUiSchema,
|
|
40
|
+
resolveStepDisabledInSettings,
|
|
41
|
+
resolveUiMode,
|
|
42
|
+
shouldHideStepInSettings,
|
|
43
|
+
} from './schema-utils';
|
|
38
44
|
|
|
39
45
|
// Runtime Context Steps 设置
|
|
40
46
|
export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
@@ -59,9 +65,34 @@ export { extractPropertyPath, formatPathToVariable, isVariableExpression } from
|
|
|
59
65
|
|
|
60
66
|
export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
|
|
61
67
|
export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
|
|
68
|
+
export {
|
|
69
|
+
decodeBase64Url,
|
|
70
|
+
encodeBase64Url,
|
|
71
|
+
isCompleteCtxDatePath,
|
|
72
|
+
isCtxDatePathPrefix,
|
|
73
|
+
isCtxDateExpression,
|
|
74
|
+
parseCtxDateExpression,
|
|
75
|
+
resolveCtxDatePath,
|
|
76
|
+
serializeCtxDateValue,
|
|
77
|
+
} from './dateVariable';
|
|
62
78
|
|
|
63
79
|
// 安全全局对象(window/document)
|
|
64
|
-
export {
|
|
80
|
+
export {
|
|
81
|
+
createSafeDocument,
|
|
82
|
+
createSafeWindow,
|
|
83
|
+
createSafeNavigator,
|
|
84
|
+
createSafeRunJSGlobals,
|
|
85
|
+
runjsWithSafeGlobals,
|
|
86
|
+
} from './safeGlobals';
|
|
87
|
+
|
|
88
|
+
// RunJS value helpers
|
|
89
|
+
export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
|
|
90
|
+
|
|
91
|
+
// RunJS helpers
|
|
92
|
+
export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
|
|
93
|
+
|
|
94
|
+
// RunJS 代码兼容预处理({{ }})与 JSX 编译
|
|
95
|
+
export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
|
|
65
96
|
|
|
66
97
|
// Ephemeral context helper(用于临时注入属性/方法,避免污染父级 ctx)
|
|
67
98
|
export { createEphemeralContext } from './createEphemeralContext';
|
|
@@ -69,3 +100,6 @@ export { createEphemeralContext } from './createEphemeralContext';
|
|
|
69
100
|
// Filter helpers
|
|
70
101
|
export { pruneFilter } from './pruneFilter';
|
|
71
102
|
export { isBeforeRenderFlow } from './flows';
|
|
103
|
+
|
|
104
|
+
// Module URL resolver
|
|
105
|
+
export { resolveModuleUrl, isCssFile } from './resolveModuleUrl';
|