e10-ebuilder-prototype 0.5.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/README.md +113 -0
- package/dist/api.d.ts +12 -0
- package/dist/api.js +125 -0
- package/dist/application.d.ts +7 -0
- package/dist/application.js +16 -0
- package/dist/archive.d.ts +130 -0
- package/dist/archive.js +151 -0
- package/dist/capture.d.ts +15 -0
- package/dist/capture.js +440 -0
- package/dist/common.d.ts +20 -0
- package/dist/common.js +87 -0
- package/dist/dom.d.mts +1 -0
- package/dist/dom.mjs +58 -0
- package/dist/form-context.d.ts +3 -0
- package/dist/form-context.js +58 -0
- package/dist/form-runtime.d.mts +2 -0
- package/dist/form-runtime.mjs +149 -0
- package/dist/forms.d.ts +51 -0
- package/dist/forms.js +603 -0
- package/dist/html.d.ts +22 -0
- package/dist/html.js +427 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +370 -0
- package/dist/menus.d.ts +32 -0
- package/dist/menus.js +330 -0
- package/dist/model.d.ts +164 -0
- package/dist/model.js +8 -0
- package/dist/offline-store.d.mts +5 -0
- package/dist/offline-store.mjs +80 -0
- package/dist/platform.d.ts +10 -0
- package/dist/platform.js +123 -0
- package/dist/readiness.d.ts +124 -0
- package/dist/readiness.js +529 -0
- package/dist/runtime-support.d.mts +52 -0
- package/dist/runtime-support.mjs +279 -0
- package/dist/site.d.ts +34 -0
- package/dist/site.js +195 -0
- package/dist/store.d.ts +90 -0
- package/dist/store.js +296 -0
- package/dist/templates/form-guide.md +539 -0
- package/dist/templates/index.html +803 -0
- package/dist/templates/placeholder.html +143 -0
- package/dist/templates/workflow-guide.md +95 -0
- package/dist/templates/workflow-presets.json +89 -0
- package/dist/temporary-records.d.ts +15 -0
- package/dist/temporary-records.js +286 -0
- package/dist/vendor/environment-auth.d.ts +61 -0
- package/dist/vendor/environment-auth.js +455 -0
- package/dist/workflow-runtime.d.mts +2 -0
- package/dist/workflow-runtime.mjs +298 -0
- package/dist/workflows.d.ts +28 -0
- package/dist/workflows.js +90 -0
- package/docs/PROTOCOL.md +299 -0
- package/package.json +45 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// Fixed local workflow state semantics. Business DOM, forms and visual layout are host-generated.
|
|
2
|
+
function installWorkflowStore(templates) {
|
|
3
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
4
|
+
const catalog = new Map(templates.map((t) => [t.workflowId, t]));
|
|
5
|
+
const actor = {
|
|
6
|
+
id: 'demo-current-user',
|
|
7
|
+
name: '当前用户(模拟)',
|
|
8
|
+
subordinates: ['demo-subordinate'],
|
|
9
|
+
};
|
|
10
|
+
const empty = () => ({ schema: 1, records: [], comments: [], logs: [], favorites: [] });
|
|
11
|
+
const types = [
|
|
12
|
+
'todo',
|
|
13
|
+
'done',
|
|
14
|
+
'mine',
|
|
15
|
+
'share',
|
|
16
|
+
'subordinates',
|
|
17
|
+
'all',
|
|
18
|
+
'draft',
|
|
19
|
+
'monitor',
|
|
20
|
+
'newflow',
|
|
21
|
+
];
|
|
22
|
+
let queue = Promise.resolve();
|
|
23
|
+
const serial = (fn) => {
|
|
24
|
+
const next = queue.then(fn);
|
|
25
|
+
queue = next.catch(() => { });
|
|
26
|
+
return next;
|
|
27
|
+
};
|
|
28
|
+
const validate = (state) => {
|
|
29
|
+
if (state?.schema !== 1 ||
|
|
30
|
+
!Array.isArray(state.records) ||
|
|
31
|
+
new Set(state.records.map((r) => r.id)).size !== state.records.length)
|
|
32
|
+
throw new Error('流程实例数据无效');
|
|
33
|
+
for (const { id, fields: f } of state.records) {
|
|
34
|
+
const template = catalog.get(f?.workflowId);
|
|
35
|
+
if (typeof id !== 'string' ||
|
|
36
|
+
!id ||
|
|
37
|
+
!template ||
|
|
38
|
+
template.objId !== f.objId ||
|
|
39
|
+
typeof f.recordId !== 'string' ||
|
|
40
|
+
!f.recordId ||
|
|
41
|
+
!['active', 'ended', 'draft'].includes(f.status) ||
|
|
42
|
+
!['pendingIds', 'handledBy', 'sharedWith', 'flags'].every((k) => Array.isArray(f[k])) ||
|
|
43
|
+
typeof f.initiatorId !== 'string')
|
|
44
|
+
throw new Error('流程实例与关联表单不匹配');
|
|
45
|
+
}
|
|
46
|
+
return state;
|
|
47
|
+
};
|
|
48
|
+
const load = async (initial = empty()) => validate(await E10FormStore.load(validate(initial)));
|
|
49
|
+
const save = async (state) => validate(await E10FormStore.save(validate(state)));
|
|
50
|
+
const belongs = (f, category) => {
|
|
51
|
+
const todo = f.status === 'active' && f.pendingIds.includes(actor.id);
|
|
52
|
+
switch (category) {
|
|
53
|
+
case 'todo':
|
|
54
|
+
return todo;
|
|
55
|
+
case 'done':
|
|
56
|
+
return f.status !== 'draft' && f.handledBy.includes(actor.id);
|
|
57
|
+
case 'mine':
|
|
58
|
+
return f.status !== 'draft' && f.initiatorId === actor.id;
|
|
59
|
+
case 'share':
|
|
60
|
+
return f.status !== 'draft' && f.sharedWith.includes(actor.id);
|
|
61
|
+
case 'subordinates':
|
|
62
|
+
return f.status === 'active' && f.pendingIds.some((id) => actor.subordinates.includes(id));
|
|
63
|
+
case 'draft':
|
|
64
|
+
return f.status === 'draft' && f.initiatorId === actor.id;
|
|
65
|
+
case 'all':
|
|
66
|
+
return f.status !== 'draft';
|
|
67
|
+
case 'monitor':
|
|
68
|
+
return f.status !== 'draft' && f.monitorable === true;
|
|
69
|
+
case 'newflow':
|
|
70
|
+
return false;
|
|
71
|
+
default:
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const tabMatches = (f, tab) => {
|
|
76
|
+
if (!tab || tab === '全部')
|
|
77
|
+
return true;
|
|
78
|
+
if (tab === '待处理')
|
|
79
|
+
return (f.status === 'active' &&
|
|
80
|
+
(f.pendingIds.includes(actor.id) ||
|
|
81
|
+
f.pendingIds.some((id) => actor.subordinates.includes(id))));
|
|
82
|
+
if (tab === '审批中')
|
|
83
|
+
return f.status === 'active';
|
|
84
|
+
if (tab === '已结束')
|
|
85
|
+
return f.status === 'ended';
|
|
86
|
+
if (tab === '未读')
|
|
87
|
+
return !f.read;
|
|
88
|
+
if (tab === '关注')
|
|
89
|
+
return !!f.followed;
|
|
90
|
+
if (tab === '已审批')
|
|
91
|
+
return f.handledBy.includes(actor.id);
|
|
92
|
+
return f.flags.includes(tab);
|
|
93
|
+
};
|
|
94
|
+
const filter = (state, category, options = {}) => {
|
|
95
|
+
if (!types.includes(category))
|
|
96
|
+
throw new Error('未知流程分类');
|
|
97
|
+
return validate(state).records.filter(({ fields: f }) => belongs(f, category) &&
|
|
98
|
+
tabMatches(f, options.tab) &&
|
|
99
|
+
(!options.state || options.state === '全部' || tabMatches(f, options.state)) &&
|
|
100
|
+
(!options.participation ||
|
|
101
|
+
options.participation === '全部' ||
|
|
102
|
+
[f.initiatorId, ...f.pendingIds, ...f.handledBy].includes(actor.id)) &&
|
|
103
|
+
(!options.search ||
|
|
104
|
+
`${f.title || ''} ${f.number || ''}`
|
|
105
|
+
.toLowerCase()
|
|
106
|
+
.includes(String(options.search).trim().toLowerCase())));
|
|
107
|
+
};
|
|
108
|
+
const log = (state, id, action) => {
|
|
109
|
+
state.logs ||= [];
|
|
110
|
+
state.logs.push({
|
|
111
|
+
instanceId: id,
|
|
112
|
+
action,
|
|
113
|
+
actor: actor.name,
|
|
114
|
+
at: new Date().toISOString(),
|
|
115
|
+
mockOnly: true,
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
const templateFor = (workflowId) => {
|
|
119
|
+
const template = catalog.get(workflowId);
|
|
120
|
+
if (!template)
|
|
121
|
+
throw new Error('流程模板未取得或不属于当前应用');
|
|
122
|
+
return template;
|
|
123
|
+
};
|
|
124
|
+
Object.defineProperty(window, 'E10WorkflowStore', {
|
|
125
|
+
configurable: true,
|
|
126
|
+
value: Object.freeze({
|
|
127
|
+
actor: clone(actor),
|
|
128
|
+
templates: clone(templates),
|
|
129
|
+
load,
|
|
130
|
+
filter,
|
|
131
|
+
async detail(instanceId) {
|
|
132
|
+
const state = await load(), instance = state.records.find((r) => r.id === instanceId);
|
|
133
|
+
if (!instance)
|
|
134
|
+
throw new Error('流程实例不存在');
|
|
135
|
+
const form = await E10FormStore.forObject(instance.fields.objId).load(empty());
|
|
136
|
+
const record = form.records.find((r) => r.id === instance.fields.recordId);
|
|
137
|
+
if (!record)
|
|
138
|
+
throw new Error('关联的本地表单记录不存在');
|
|
139
|
+
return clone({
|
|
140
|
+
instance,
|
|
141
|
+
record,
|
|
142
|
+
template: templateFor(instance.fields.workflowId),
|
|
143
|
+
comments: (state.comments || []).filter((c) => c.instanceId === instanceId),
|
|
144
|
+
logs: (state.logs || []).filter((l) => l.instanceId === instanceId),
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
saveApplication({ workflowId, instanceId, record, title, draft = false }) {
|
|
148
|
+
return serial(async () => {
|
|
149
|
+
const template = templateFor(workflowId), state = await load();
|
|
150
|
+
let instance = instanceId ? state.records.find((r) => r.id === instanceId) : undefined;
|
|
151
|
+
if (instanceId &&
|
|
152
|
+
(!instance ||
|
|
153
|
+
instance.fields.status !== 'draft' ||
|
|
154
|
+
instance.fields.initiatorId !== actor.id ||
|
|
155
|
+
instance.fields.workflowId !== workflowId))
|
|
156
|
+
throw new Error('只能继续编辑自己的对应流程草稿');
|
|
157
|
+
if (!record?.fields || typeof record.fields !== 'object' || Array.isArray(record.fields))
|
|
158
|
+
throw new Error('表单字段无效');
|
|
159
|
+
const id = instance?.id ||
|
|
160
|
+
'wf-' +
|
|
161
|
+
(globalThis.crypto?.randomUUID?.() ||
|
|
162
|
+
Date.now() + '-' + Math.random().toString(36).slice(2));
|
|
163
|
+
const recordId = instance?.fields.recordId || 'record-' + id;
|
|
164
|
+
const form = await E10FormStore.forObject(template.objId).load(empty());
|
|
165
|
+
const row = { ...clone(record), id: recordId }, position = form.records.findIndex((r) => r.id === recordId);
|
|
166
|
+
if (instance && position < 0)
|
|
167
|
+
throw new Error('草稿关联的表单记录已不存在');
|
|
168
|
+
if (position >= 0)
|
|
169
|
+
form.records[position] = row;
|
|
170
|
+
else
|
|
171
|
+
form.records.push(row);
|
|
172
|
+
if (!instance) {
|
|
173
|
+
instance = {
|
|
174
|
+
id,
|
|
175
|
+
fields: {
|
|
176
|
+
workflowId,
|
|
177
|
+
objId: template.objId,
|
|
178
|
+
recordId,
|
|
179
|
+
initiatorId: actor.id,
|
|
180
|
+
initiator: actor.name,
|
|
181
|
+
initiatedAt: new Date().toISOString(),
|
|
182
|
+
number: 'MOCK-' + id.slice(-8),
|
|
183
|
+
handledBy: [],
|
|
184
|
+
sharedWith: [],
|
|
185
|
+
flags: [],
|
|
186
|
+
read: true,
|
|
187
|
+
followed: false,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
state.records.push(instance);
|
|
191
|
+
}
|
|
192
|
+
Object.assign(instance.fields, {
|
|
193
|
+
title: String(title || template.name),
|
|
194
|
+
status: draft ? 'draft' : 'active',
|
|
195
|
+
currentNode: draft ? '草稿' : '模拟审批节点',
|
|
196
|
+
pendingIds: draft ? [] : ['demo-approver'],
|
|
197
|
+
pendingNames: draft ? [] : ['审批人(模拟)'],
|
|
198
|
+
});
|
|
199
|
+
log(state, id, draft ? '保存草稿(模拟)' : '提交申请(模拟)');
|
|
200
|
+
validate(state);
|
|
201
|
+
await E10FormStore.commit([
|
|
202
|
+
{ objId: template.objId, value: form },
|
|
203
|
+
{ objId: 'workflow', value: state },
|
|
204
|
+
]);
|
|
205
|
+
return clone(instance);
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
markRead(category) {
|
|
209
|
+
return serial(async () => {
|
|
210
|
+
const state = await load(), rows = filter(state, category);
|
|
211
|
+
for (const row of rows)
|
|
212
|
+
row.fields.read = true;
|
|
213
|
+
return save(state);
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
completeBatch(ids, confirmed = false, category = 'todo') {
|
|
217
|
+
return serial(async () => {
|
|
218
|
+
const state = await load();
|
|
219
|
+
if (!confirmed)
|
|
220
|
+
return state;
|
|
221
|
+
if (!Array.isArray(ids) || !ids.length || new Set(ids).size !== ids.length)
|
|
222
|
+
throw new Error('请选择需要办理的流程');
|
|
223
|
+
const available = filter(state, category), selected = ids.map((id) => available.find((r) => r.id === id));
|
|
224
|
+
if (selected.some((r) => !r || r.fields.status !== 'active' || !r.fields.pendingIds.includes(actor.id)))
|
|
225
|
+
throw new Error('所选流程包含当前用户不可办理的模拟记录');
|
|
226
|
+
for (const row of selected) {
|
|
227
|
+
row.fields.handledBy = [...new Set([...row.fields.handledBy, actor.id])];
|
|
228
|
+
row.fields.pendingIds = ['demo-next-reviewer'];
|
|
229
|
+
row.fields.pendingNames = ['下一节点处理人(模拟)'];
|
|
230
|
+
row.fields.currentNode = '下一审批节点(模拟)';
|
|
231
|
+
row.fields.read = true;
|
|
232
|
+
log(state, row.id, '批量提交至下一节点(模拟)');
|
|
233
|
+
}
|
|
234
|
+
return save(state);
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
toggleFavorite(workflowId) {
|
|
238
|
+
return serial(async () => {
|
|
239
|
+
templateFor(workflowId);
|
|
240
|
+
const state = await load(), favorites = new Set(state.favorites || []);
|
|
241
|
+
if (favorites.has(workflowId))
|
|
242
|
+
favorites.delete(workflowId);
|
|
243
|
+
else
|
|
244
|
+
favorites.add(workflowId);
|
|
245
|
+
state.favorites = [...favorites];
|
|
246
|
+
return save(state);
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
toggleFollow(instanceId) {
|
|
250
|
+
return serial(async () => {
|
|
251
|
+
const state = await load(), row = state.records.find((r) => r.id === instanceId);
|
|
252
|
+
if (!row)
|
|
253
|
+
throw new Error('流程实例不存在');
|
|
254
|
+
row.fields.followed = !row.fields.followed;
|
|
255
|
+
return save(state);
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
comment(instanceId, text) {
|
|
259
|
+
return serial(async () => {
|
|
260
|
+
const state = await load();
|
|
261
|
+
if (!state.records.some((r) => r.id === instanceId) || !String(text).trim())
|
|
262
|
+
throw new Error('请选择流程并填写评论');
|
|
263
|
+
state.comments ||= [];
|
|
264
|
+
state.comments.push({
|
|
265
|
+
instanceId,
|
|
266
|
+
text: String(text).trim().slice(0, 5000),
|
|
267
|
+
actor: actor.name,
|
|
268
|
+
at: new Date().toISOString(),
|
|
269
|
+
mockOnly: true,
|
|
270
|
+
});
|
|
271
|
+
return save(state);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
reset(initial, forms = [], confirmed = false) {
|
|
275
|
+
return serial(async () => {
|
|
276
|
+
if (!confirmed)
|
|
277
|
+
return load();
|
|
278
|
+
validate(initial);
|
|
279
|
+
if (forms.some((f) => !templates.some((t) => t.objId === f.objId)))
|
|
280
|
+
throw new Error('重置包含无关表单');
|
|
281
|
+
await E10FormStore.commit([...forms, { objId: 'workflow', value: initial }]);
|
|
282
|
+
return clone(initial);
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
export function workflowRuntime(templates) {
|
|
289
|
+
const json = JSON.stringify(templates)
|
|
290
|
+
.replaceAll('<', '\\u003c')
|
|
291
|
+
.replaceAll('\u2028', '\\u2028')
|
|
292
|
+
.replaceAll('\u2029', '\\u2029');
|
|
293
|
+
return `<script id="e10-workflow-runtime">(${installWorkflowStore.toString()})(${json});</script>`;
|
|
294
|
+
}
|
|
295
|
+
export function attachWorkflowRuntime(source, templates) {
|
|
296
|
+
const clean = source.replace(/<script\b[^>]*\bid\s*=\s*["']e10-workflow-runtime["'][^>]*>[\s\S]*?<\/script\s*>/gi, '');
|
|
297
|
+
return clean.replace(/(<script id="e10-form-runtime">[\s\S]*?<\/script>)/, (match) => match + workflowRuntime(templates));
|
|
298
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WorkflowType } from './model.js';
|
|
2
|
+
export declare const WORKFLOW_TYPES: WorkflowType[];
|
|
3
|
+
export declare const APPROVAL_PATH = "/api/bs/ebuilder/flow/approval/getApprovalListDatasByAppId";
|
|
4
|
+
export declare function workflowType(value: unknown): WorkflowType;
|
|
5
|
+
export interface WorkflowTemplate {
|
|
6
|
+
workflowId: string;
|
|
7
|
+
name: string;
|
|
8
|
+
objId: string;
|
|
9
|
+
formName: string;
|
|
10
|
+
formId: string;
|
|
11
|
+
status?: unknown;
|
|
12
|
+
groupId: string;
|
|
13
|
+
groupName?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WorkflowCatalog {
|
|
16
|
+
status: 'ready';
|
|
17
|
+
source: string;
|
|
18
|
+
rawCount: number;
|
|
19
|
+
templates: WorkflowTemplate[];
|
|
20
|
+
skipped: {
|
|
21
|
+
index: number;
|
|
22
|
+
name?: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
/** E10Client has removed the outer envelope; its data.data array remains here. */
|
|
27
|
+
export declare function normalizeWorkflows(data: any, appId: string, objectIds: Set<string>): WorkflowCatalog;
|
|
28
|
+
export declare function workflowPreset(type: WorkflowType): any;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { CaptureError } from './common.js';
|
|
3
|
+
export const WORKFLOW_TYPES = [
|
|
4
|
+
'todo',
|
|
5
|
+
'done',
|
|
6
|
+
'mine',
|
|
7
|
+
'share',
|
|
8
|
+
'subordinates',
|
|
9
|
+
'all',
|
|
10
|
+
'draft',
|
|
11
|
+
'monitor',
|
|
12
|
+
'newflow',
|
|
13
|
+
];
|
|
14
|
+
export const APPROVAL_PATH = '/api/bs/ebuilder/flow/approval/getApprovalListDatasByAppId';
|
|
15
|
+
export function workflowType(value) {
|
|
16
|
+
if (!WORKFLOW_TYPES.includes(value))
|
|
17
|
+
throw new CaptureError('WORKFLOW_TYPE_UNSUPPORTED', '不支持的流程菜单分类');
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/** E10Client has removed the outer envelope; its data.data array remains here. */
|
|
21
|
+
export function normalizeWorkflows(data, appId, objectIds) {
|
|
22
|
+
if (!data || !Array.isArray(data.data))
|
|
23
|
+
throw new CaptureError('WORKFLOW_CATALOG_INVALID', '流程目录响应缺少 data.data 数组');
|
|
24
|
+
const catalog = {
|
|
25
|
+
status: 'ready',
|
|
26
|
+
source: APPROVAL_PATH,
|
|
27
|
+
rawCount: data.data.length,
|
|
28
|
+
templates: [],
|
|
29
|
+
skipped: [],
|
|
30
|
+
};
|
|
31
|
+
const seen = new Map();
|
|
32
|
+
const decimal = (v) => (typeof v === 'string' || (typeof v === 'number' && Number.isSafeInteger(v))) &&
|
|
33
|
+
/^[0-9]+$/.test(String(v)) &&
|
|
34
|
+
/[1-9]/.test(String(v));
|
|
35
|
+
for (const [index, raw] of data.data.entries()) {
|
|
36
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
37
|
+
throw new CaptureError('WORKFLOW_CATALOG_INVALID', '流程目录包含无效配置');
|
|
38
|
+
const dataset = raw.dataset || {};
|
|
39
|
+
let reason;
|
|
40
|
+
if ([true, 1, '1', 'true'].includes(raw.nonEbForm))
|
|
41
|
+
reason = 'non-eb-form-not-supported';
|
|
42
|
+
else if (!decimal(raw.workflowid) || !decimal(raw.objId))
|
|
43
|
+
reason = 'invalid-workflow-or-object-id';
|
|
44
|
+
else if (typeof dataset !== 'object' || Array.isArray(dataset))
|
|
45
|
+
reason = 'invalid-workflow-dataset';
|
|
46
|
+
else if (dataset.id != null && String(dataset.id) !== String(raw.objId))
|
|
47
|
+
reason = 'workflow-dataset-objId-conflict';
|
|
48
|
+
else if ((dataset.appId != null && String(dataset.appId) !== appId) ||
|
|
49
|
+
(raw.appId != null && String(raw.appId) !== appId) ||
|
|
50
|
+
!objectIds.has(String(raw.objId)))
|
|
51
|
+
reason = 'workflow-target-app-context-required';
|
|
52
|
+
else if (dataset.type != null && dataset.type !== 'FORM')
|
|
53
|
+
reason = 'workflow-dataset-not-form';
|
|
54
|
+
if (reason) {
|
|
55
|
+
catalog.skipped.push({ index, name: raw.workflowname, reason });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const workflowId = String(raw.workflowid), serialized = JSON.stringify(raw);
|
|
59
|
+
if (seen.has(workflowId)) {
|
|
60
|
+
if (seen.get(workflowId) !== serialized)
|
|
61
|
+
throw new CaptureError('WORKFLOW_CATALOG_CONFLICT', '重复流程 ID 对应不同配置');
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
seen.set(workflowId, serialized);
|
|
65
|
+
catalog.templates.push({
|
|
66
|
+
workflowId,
|
|
67
|
+
name: String(raw.workflowname || dataset.text || workflowId),
|
|
68
|
+
objId: String(raw.objId),
|
|
69
|
+
formName: String(dataset.text || raw.objId),
|
|
70
|
+
formId: String(raw.formid || ''),
|
|
71
|
+
status: raw.status,
|
|
72
|
+
groupId: String(raw.groupId ?? ''),
|
|
73
|
+
...(typeof raw.groupName === 'string' && raw.groupName.trim() && String(raw.groupId) !== '-1'
|
|
74
|
+
? { groupName: raw.groupName }
|
|
75
|
+
: {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return catalog;
|
|
79
|
+
}
|
|
80
|
+
export function workflowPreset(type) {
|
|
81
|
+
const presets = JSON.parse(fs.readFileSync(new URL('./templates/workflow-presets.json', import.meta.url), 'utf8'));
|
|
82
|
+
const { extends: parent, ...entry } = presets[workflowType(type)];
|
|
83
|
+
return {
|
|
84
|
+
...(parent ? presets[parent] : {}),
|
|
85
|
+
...entry,
|
|
86
|
+
workflowType: type,
|
|
87
|
+
source: 'prototype-workflow-preset',
|
|
88
|
+
mockOnly: true,
|
|
89
|
+
};
|
|
90
|
+
}
|