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
package/dist/menus.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { CaptureError, id } from './common.js';
|
|
2
|
+
import { E10Client, parseApiJson } from './api.js';
|
|
3
|
+
import { parseApp, parseForms, parsePages } from './platform.js';
|
|
4
|
+
import { workflowType } from './workflows.js';
|
|
5
|
+
export function navigationKey(value) {
|
|
6
|
+
if (!/^\d+(?:-\d+)*$/.test(value) || value.length > 240)
|
|
7
|
+
throw new CaptureError('MENU_KEY_INVALID', '菜单标识无效');
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
const decimal = (value) => id(typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value);
|
|
11
|
+
const object = (value) => {
|
|
12
|
+
if (value == null || value === '')
|
|
13
|
+
return {};
|
|
14
|
+
const result = typeof value === 'string' ? parseApiJson(value) : value;
|
|
15
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
16
|
+
throw new CaptureError('MENU_CONFIG_INVALID', '菜单配置必须为对象');
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
function urlTarget(value, origin, appId) {
|
|
20
|
+
if (typeof value !== 'string' || !value)
|
|
21
|
+
return;
|
|
22
|
+
if (!value.startsWith('/') && !/^https?:\/\//.test(value))
|
|
23
|
+
return;
|
|
24
|
+
const url = new URL(value, origin);
|
|
25
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
26
|
+
url.username ||
|
|
27
|
+
url.password ||
|
|
28
|
+
/[\\\r\n]/.test(value))
|
|
29
|
+
throw new CaptureError('MENU_URL_INVALID', '菜单地址无效');
|
|
30
|
+
let match = url.pathname.match(/^\/sp\/ebdpage\/view\/(\d+)(?:\/page\/\1)?\/?$/);
|
|
31
|
+
if (match)
|
|
32
|
+
return { kind: 'page', pageId: match[1] };
|
|
33
|
+
match = url.pathname.match(/^\/sp\/appbuilder\/combinedpage_preview\/(\d+)(?:\/(\d+))?\/?$/);
|
|
34
|
+
if (match)
|
|
35
|
+
return { kind: 'combined', appId: match[1], combinationId: match[2] };
|
|
36
|
+
match = url.pathname.match(/^\/sp\/ebdfpage\/workflow\/(\d+)_([a-z]+)\/?$/);
|
|
37
|
+
if (match) {
|
|
38
|
+
if (match[1] !== appId)
|
|
39
|
+
throw new CaptureError('MENU_APP_MISMATCH', '流程菜单指向其它应用');
|
|
40
|
+
for (const [key, expected] of [
|
|
41
|
+
['appId', appId],
|
|
42
|
+
['pageId', match[2]],
|
|
43
|
+
])
|
|
44
|
+
if (url.searchParams.getAll(key).some((value) => value !== expected))
|
|
45
|
+
throw new CaptureError('MENU_URL_CONFLICT', '流程菜单类型或应用 ID 冲突');
|
|
46
|
+
return { kind: 'workflow', workflowType: workflowType(match[2]) };
|
|
47
|
+
}
|
|
48
|
+
match = url.pathname.match(/^\/sp\/ebdfpage\/(list|search|viewport)\/(?:(\d+)_)?(\d+)\/?$/);
|
|
49
|
+
let target;
|
|
50
|
+
if (match) {
|
|
51
|
+
if (match[2] && match[2] !== appId)
|
|
52
|
+
throw new CaptureError('MENU_APP_MISMATCH', '列表菜单指向其它应用');
|
|
53
|
+
target = { kind: match[1] === 'viewport' ? 'nlist' : 'list', viewId: match[3] };
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
match = url.pathname.match(/^\/sp\/ebdfpage\/card\/([012])\/(\d+)\/(\d+)\/?$/);
|
|
57
|
+
if (match)
|
|
58
|
+
target = {
|
|
59
|
+
kind: 'layout',
|
|
60
|
+
objId: match[2],
|
|
61
|
+
mode: { '0': 'view', '1': 'add', '2': 'edit' }[match[1]],
|
|
62
|
+
recordId: match[3],
|
|
63
|
+
layoutId: '',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!target)
|
|
67
|
+
return;
|
|
68
|
+
for (const key of ['appId', 'objId', 'listId', 'pageId', 'layoutId']) {
|
|
69
|
+
const values = url.searchParams.getAll(key);
|
|
70
|
+
if (!values.length)
|
|
71
|
+
continue;
|
|
72
|
+
if (new Set(values).size !== 1)
|
|
73
|
+
throw new CaptureError('MENU_URL_CONFLICT', `菜单 ${key} 冲突`);
|
|
74
|
+
const value = decimal(values[0]);
|
|
75
|
+
if (key === 'appId' && value !== appId)
|
|
76
|
+
throw new CaptureError('MENU_APP_MISMATCH', '菜单指向其它应用');
|
|
77
|
+
if ((key === 'listId' || key === 'pageId') &&
|
|
78
|
+
target.kind !== 'layout' &&
|
|
79
|
+
target.viewId !== value)
|
|
80
|
+
throw new CaptureError('MENU_URL_CONFLICT', '菜单列表 ID 冲突');
|
|
81
|
+
if (key === 'objId') {
|
|
82
|
+
if (target.objId && target.objId !== value)
|
|
83
|
+
throw new CaptureError('MENU_URL_CONFLICT', '菜单表单 ID 冲突');
|
|
84
|
+
target.objId = value;
|
|
85
|
+
}
|
|
86
|
+
if (key === 'layoutId' && target.kind === 'layout')
|
|
87
|
+
target.layoutId = value;
|
|
88
|
+
}
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
91
|
+
function targetFor(raw, origin, appId) {
|
|
92
|
+
const type = String(raw.pageType || '').toUpperCase();
|
|
93
|
+
const direct = urlTarget(raw.menuUrl || (type === 'LINK' ? raw.pageId : undefined), origin, appId);
|
|
94
|
+
const alternate = type === 'LINK' && raw.menuUrl ? urlTarget(raw.pageId, origin, appId) : undefined;
|
|
95
|
+
if (alternate && direct && JSON.stringify(alternate) !== JSON.stringify(direct))
|
|
96
|
+
throw new CaptureError('MENU_URL_CONFLICT', 'LINK 菜单的两个目标地址冲突');
|
|
97
|
+
if (direct?.kind === 'combined') {
|
|
98
|
+
if (direct.appId !== appId)
|
|
99
|
+
throw new CaptureError('MENU_APP_MISMATCH', '组合菜单指向其它应用');
|
|
100
|
+
const declared = raw.combinationId && String(raw.combinationId) !== '0'
|
|
101
|
+
? decimal(raw.combinationId)
|
|
102
|
+
: undefined;
|
|
103
|
+
if (declared && direct.combinationId && declared !== direct.combinationId)
|
|
104
|
+
throw new CaptureError('MENU_URL_CONFLICT', '组合页面 ID 冲突');
|
|
105
|
+
return { ...direct, combinationId: declared || direct.combinationId };
|
|
106
|
+
}
|
|
107
|
+
const remote = String(raw.remoteAppId || raw.appId || '0');
|
|
108
|
+
if (remote !== '0' && remote !== appId)
|
|
109
|
+
throw new CaptureError('MENU_APP_MISMATCH', '菜单指向其它应用');
|
|
110
|
+
let declared;
|
|
111
|
+
if (type === 'WORKFLOW')
|
|
112
|
+
declared = { kind: 'workflow', workflowType: workflowType(raw.pageId) };
|
|
113
|
+
if (type === 'PAGE')
|
|
114
|
+
declared = { kind: 'page', pageId: decimal(raw.pageId) };
|
|
115
|
+
if (type === 'SEARCH')
|
|
116
|
+
declared = { kind: 'list', viewId: decimal(raw.pageId) };
|
|
117
|
+
if (type === 'VIEWPORT') {
|
|
118
|
+
const tabs = object(raw.tabs);
|
|
119
|
+
if (tabs.pageViewType && String(tabs.pageViewType).toLowerCase() !== 'nlist')
|
|
120
|
+
return;
|
|
121
|
+
declared = { kind: 'nlist', viewId: decimal(raw.pageId) };
|
|
122
|
+
}
|
|
123
|
+
if (type === 'LAYOUT') {
|
|
124
|
+
const match = String(raw.pageId).match(/^([012])[_/](\d+)$/);
|
|
125
|
+
if (!match)
|
|
126
|
+
throw new CaptureError('MENU_LAYOUT_INVALID', '布局菜单的 pageId 无法识别');
|
|
127
|
+
declared = {
|
|
128
|
+
kind: 'layout',
|
|
129
|
+
objId: match[2],
|
|
130
|
+
mode: { '0': 'view', '1': 'add', '2': 'edit' }[match[1]],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (direct && declared) {
|
|
134
|
+
if (direct.kind !== declared.kind ||
|
|
135
|
+
['pageId', 'viewId', 'objId', 'mode', 'workflowType'].some((key) => direct[key] &&
|
|
136
|
+
declared[key] &&
|
|
137
|
+
direct[key] !== declared[key]))
|
|
138
|
+
throw new CaptureError('MENU_URL_CONFLICT', '菜单类型或 ID 与地址冲突');
|
|
139
|
+
}
|
|
140
|
+
const target = direct || declared;
|
|
141
|
+
if (target?.kind === 'layout') {
|
|
142
|
+
const layout = raw.selectLayout;
|
|
143
|
+
const layoutId = layout == null ||
|
|
144
|
+
['', '{}', 'null'].includes(String(layout)) ||
|
|
145
|
+
(typeof layout === 'object' && !Object.keys(layout).length)
|
|
146
|
+
? ''
|
|
147
|
+
: decimal(layout);
|
|
148
|
+
if (layoutId && target.layoutId && layoutId !== target.layoutId)
|
|
149
|
+
throw new CaptureError('MENU_URL_CONFLICT', '布局 ID 冲突');
|
|
150
|
+
target.layoutId = layoutId || target.layoutId || '';
|
|
151
|
+
}
|
|
152
|
+
return target;
|
|
153
|
+
}
|
|
154
|
+
export function parseMenus(data, appId, origin, prefix = '') {
|
|
155
|
+
if (!data || typeof data !== 'object' || (data.id != null && String(data.id) !== appId))
|
|
156
|
+
throw new CaptureError('MENUS_INVALID', '菜单应用信息无效');
|
|
157
|
+
const rows = data.ebAppMenus ?? data.cusAppMenus;
|
|
158
|
+
if (!Array.isArray(rows))
|
|
159
|
+
throw new CaptureError('MENUS_INVALID', '菜单接口缺少菜单数组');
|
|
160
|
+
const skipped = [], forms = [];
|
|
161
|
+
const nodes = new Map();
|
|
162
|
+
const visit = (items, parent = '0') => {
|
|
163
|
+
for (const raw of items) {
|
|
164
|
+
if (!raw || typeof raw !== 'object')
|
|
165
|
+
throw new CaptureError('MENUS_INVALID', '菜单项无效');
|
|
166
|
+
const menuId = decimal(raw.id), key = navigationKey(prefix ? `${prefix}-${menuId}` : menuId);
|
|
167
|
+
if (nodes.has(menuId))
|
|
168
|
+
throw new CaptureError('MENU_DUPLICATE', `重复菜单 ${menuId}`);
|
|
169
|
+
const name = raw.menuName || raw.content || raw.showName || raw.pageName || menuId;
|
|
170
|
+
if (typeof name !== 'string')
|
|
171
|
+
throw new CaptureError('MENUS_INVALID', '菜单名称无效');
|
|
172
|
+
const order = Number(raw.showOrder ?? raw.sort ?? nodes.size);
|
|
173
|
+
if (!Number.isFinite(order))
|
|
174
|
+
throw new CaptureError('MENUS_INVALID', '菜单顺序无效');
|
|
175
|
+
const node = {
|
|
176
|
+
id: key,
|
|
177
|
+
menuId,
|
|
178
|
+
name,
|
|
179
|
+
order,
|
|
180
|
+
default: raw.defaultMenuSelect === true,
|
|
181
|
+
icon: raw.icon,
|
|
182
|
+
openMode: raw.openMode,
|
|
183
|
+
params: raw.pageParam ?? raw.params,
|
|
184
|
+
children: [],
|
|
185
|
+
};
|
|
186
|
+
const type = String(raw.pageType || '').toUpperCase();
|
|
187
|
+
const excluded = ['SETTING', 'SETTINGS'].includes(type) ||
|
|
188
|
+
(type === 'BASE' && String(raw.pageId).toLowerCase() === 'recycle');
|
|
189
|
+
const denied = [true, 1, '1'].includes(raw.noPermissionMenu);
|
|
190
|
+
const target = excluded || denied ? undefined : targetFor(raw, origin, appId);
|
|
191
|
+
if (!target)
|
|
192
|
+
skipped.push({
|
|
193
|
+
id: key,
|
|
194
|
+
name,
|
|
195
|
+
type,
|
|
196
|
+
reason: denied
|
|
197
|
+
? 'no-permission'
|
|
198
|
+
: excluded
|
|
199
|
+
? 'excluded-system-menu'
|
|
200
|
+
: 'unsupported-menu-type',
|
|
201
|
+
});
|
|
202
|
+
if (target?.kind === 'page')
|
|
203
|
+
node.target = { kind: 'page', id: target.pageId };
|
|
204
|
+
else if (target?.kind === 'combined')
|
|
205
|
+
node.target = { kind: 'combined', id: key };
|
|
206
|
+
else if (target) {
|
|
207
|
+
forms.push({
|
|
208
|
+
...target,
|
|
209
|
+
id: key,
|
|
210
|
+
menuId,
|
|
211
|
+
name,
|
|
212
|
+
appId,
|
|
213
|
+
params: node.params,
|
|
214
|
+
openMode: node.openMode,
|
|
215
|
+
});
|
|
216
|
+
node.target = { kind: 'form', id: key };
|
|
217
|
+
}
|
|
218
|
+
nodes.set(menuId, {
|
|
219
|
+
node,
|
|
220
|
+
parent: String(raw.pid ?? raw.parentId ?? parent),
|
|
221
|
+
blocked: excluded || denied,
|
|
222
|
+
target,
|
|
223
|
+
});
|
|
224
|
+
if (Array.isArray(raw.children) && !excluded && !denied)
|
|
225
|
+
visit(raw.children, menuId);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
visit(rows);
|
|
229
|
+
const roots = [];
|
|
230
|
+
for (const entry of nodes.values()) {
|
|
231
|
+
let cursor = entry.parent;
|
|
232
|
+
const seen = new Set([entry.node.menuId]);
|
|
233
|
+
while (nodes.has(cursor)) {
|
|
234
|
+
if (seen.has(cursor))
|
|
235
|
+
throw new CaptureError('MENU_CYCLE', '菜单父子关系循环');
|
|
236
|
+
seen.add(cursor);
|
|
237
|
+
cursor = nodes.get(cursor).parent;
|
|
238
|
+
}
|
|
239
|
+
if (nodes.has(entry.parent))
|
|
240
|
+
nodes.get(entry.parent).node.children.push(entry.node);
|
|
241
|
+
else
|
|
242
|
+
roots.push(entry.node);
|
|
243
|
+
}
|
|
244
|
+
const prune = (items) => items
|
|
245
|
+
.filter((node) => !nodes.get(node.menuId).blocked)
|
|
246
|
+
.map((node) => ({ ...node, children: prune(node.children) }))
|
|
247
|
+
.filter((node) => node.target || node.children.length)
|
|
248
|
+
.sort((a, b) => a.order - b.order);
|
|
249
|
+
const menus = prune(roots), retained = new Set(menuItems(menus).map((node) => node.id));
|
|
250
|
+
for (const entry of nodes.values())
|
|
251
|
+
if (!retained.has(entry.node.id) && entry.node.target)
|
|
252
|
+
skipped.push({
|
|
253
|
+
id: entry.node.id,
|
|
254
|
+
name: entry.node.name,
|
|
255
|
+
type: entry.node.target.kind,
|
|
256
|
+
reason: 'excluded-parent-menu',
|
|
257
|
+
});
|
|
258
|
+
const groups = new Set(menuItems(menus)
|
|
259
|
+
.filter((node) => !node.target && node.children.length)
|
|
260
|
+
.map((node) => node.id));
|
|
261
|
+
return {
|
|
262
|
+
menus,
|
|
263
|
+
forms: forms.filter((form) => retained.has(form.id)),
|
|
264
|
+
skipped: skipped.filter((item) => !groups.has(item.id)),
|
|
265
|
+
combinations: [...nodes.values()]
|
|
266
|
+
.filter((e) => retained.has(e.node.id) && e.target?.kind === 'combined')
|
|
267
|
+
.map((e) => ({ key: e.node.id, target: e.target })),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
export function menuItems(menus) {
|
|
271
|
+
return menus.flatMap((node) => [node, ...menuItems(node.children), ...menuItems(node.tabs || [])]);
|
|
272
|
+
}
|
|
273
|
+
export async function discoverMenus(auth, appId) {
|
|
274
|
+
const client = new E10Client(auth);
|
|
275
|
+
const responses = await Promise.allSettled([
|
|
276
|
+
client.request('GET', `/api/cusapp/app/allMenus?appId=${appId}&terminalType=PC&isFront=0`),
|
|
277
|
+
client.request('GET', `/api/bs/ebuilder/app/info?id=${appId}`),
|
|
278
|
+
]);
|
|
279
|
+
for (const result of responses)
|
|
280
|
+
if (result.status === 'rejected')
|
|
281
|
+
throw result.reason;
|
|
282
|
+
const [raw, app] = responses.map((result) => result.value);
|
|
283
|
+
const appName = parseApp({ code: 200, data: app }, appId);
|
|
284
|
+
const parsed = parseMenus(raw, appId, client.origin);
|
|
285
|
+
for (const combination of parsed.combinations) {
|
|
286
|
+
const node = menuItems(parsed.menus).find((n) => n.id === combination.key);
|
|
287
|
+
const data = await client.request('GET', `/api/cusapp/app/getCombMenus?appId=${appId}&pid=${node.menuId}&type=1&terminalType=PC&showType=0&isComb=0&isFront=0`);
|
|
288
|
+
if (String(data?.appId) !== appId ||
|
|
289
|
+
!Array.isArray(data.menuEntities) ||
|
|
290
|
+
(combination.target.combinationId && String(data.id) !== combination.target.combinationId))
|
|
291
|
+
throw new CaptureError('COMBINED_INVALID', '组合菜单归属或结构无效');
|
|
292
|
+
const tabs = parseMenus({ id: appId, ebAppMenus: data.menuEntities }, appId, client.origin, node.id);
|
|
293
|
+
if (tabs.combinations.length)
|
|
294
|
+
throw new CaptureError('COMBINED_NESTED', '暂不支持嵌套组合页面');
|
|
295
|
+
if (!tabs.menus.length)
|
|
296
|
+
throw new CaptureError('COMBINED_EMPTY', '组合菜单没有受支持的页面或列表');
|
|
297
|
+
node.tabs = tabs.menus;
|
|
298
|
+
parsed.forms.push(...tabs.forms);
|
|
299
|
+
parsed.skipped.push(...tabs.skipped);
|
|
300
|
+
}
|
|
301
|
+
const pageIds = new Set(menuItems(parsed.menus)
|
|
302
|
+
.filter((n) => n.target?.kind === 'page')
|
|
303
|
+
.map((n) => n.target.id));
|
|
304
|
+
const metadata = await Promise.allSettled([
|
|
305
|
+
pageIds.size
|
|
306
|
+
? client.request('GET', `/api/bs/ebuilder/page/list?appid=${appId}`)
|
|
307
|
+
: Promise.resolve([]),
|
|
308
|
+
parsed.forms.length
|
|
309
|
+
? client.request('GET', `/api/bs/ebuilder/form/obj/getList/${appId}?apid=${appId}`)
|
|
310
|
+
: Promise.resolve([]),
|
|
311
|
+
]);
|
|
312
|
+
for (const result of metadata)
|
|
313
|
+
if (result.status === 'rejected')
|
|
314
|
+
throw result.reason;
|
|
315
|
+
const [pagesRaw, formsRaw] = metadata.map((result) => result.value);
|
|
316
|
+
if (!Array.isArray(pagesRaw))
|
|
317
|
+
throw new CaptureError('PAGE_LIST_INVALID', '页面清单无效');
|
|
318
|
+
const pages = parsePages({ code: 200, data: pagesRaw.filter((p) => pageIds.has(String(p?.id))) }, appId, client.origin);
|
|
319
|
+
if (pages.length !== pageIds.size)
|
|
320
|
+
throw new CaptureError('MENU_PAGE_MISSING', '已发布菜单对应的页面不在当前应用中');
|
|
321
|
+
const byId = new Map(pages.map((page) => [page.id, page]));
|
|
322
|
+
return {
|
|
323
|
+
appName,
|
|
324
|
+
menus: parsed.menus,
|
|
325
|
+
skippedMenus: parsed.skipped,
|
|
326
|
+
pages: [...pageIds].map((id) => byId.get(id)),
|
|
327
|
+
forms: parseForms({ code: 200, data: formsRaw }, appId, client.origin),
|
|
328
|
+
formPages: parsed.forms,
|
|
329
|
+
};
|
|
330
|
+
}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface PageItem {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
appId: string;
|
|
5
|
+
url: string;
|
|
6
|
+
terminal: 'PC' | 'MOBILE';
|
|
7
|
+
type?: 'PAGE' | 'ECODE_HTML' | 'ECODE_REACT';
|
|
8
|
+
}
|
|
9
|
+
export interface FormItem {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
appId: string;
|
|
13
|
+
url: string;
|
|
14
|
+
}
|
|
15
|
+
export interface MenuItem {
|
|
16
|
+
/** A navigation key; combined children include their container's key. */
|
|
17
|
+
id: string;
|
|
18
|
+
menuId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
order: number;
|
|
21
|
+
default: boolean;
|
|
22
|
+
icon?: unknown;
|
|
23
|
+
openMode?: unknown;
|
|
24
|
+
params?: unknown;
|
|
25
|
+
target?: {
|
|
26
|
+
kind: 'page' | 'form' | 'combined';
|
|
27
|
+
id: string;
|
|
28
|
+
};
|
|
29
|
+
children: MenuItem[];
|
|
30
|
+
tabs?: MenuItem[];
|
|
31
|
+
}
|
|
32
|
+
export interface SkippedMenu {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
type: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
export type WorkflowType = 'todo' | 'done' | 'mine' | 'share' | 'subordinates' | 'all' | 'draft' | 'monitor' | 'newflow';
|
|
39
|
+
export interface FormPage {
|
|
40
|
+
id: string;
|
|
41
|
+
menuId: string;
|
|
42
|
+
name: string;
|
|
43
|
+
appId: string;
|
|
44
|
+
kind: 'list' | 'nlist' | 'layout' | 'workflow';
|
|
45
|
+
workflowType?: WorkflowType;
|
|
46
|
+
viewId?: string;
|
|
47
|
+
objId?: string;
|
|
48
|
+
layoutId?: string;
|
|
49
|
+
mode?: 'view' | 'add' | 'edit';
|
|
50
|
+
recordId?: string;
|
|
51
|
+
params?: unknown;
|
|
52
|
+
openMode?: unknown;
|
|
53
|
+
}
|
|
54
|
+
export interface FormCollection {
|
|
55
|
+
id: string;
|
|
56
|
+
status: 'running' | 'succeeded' | 'failed';
|
|
57
|
+
attempt: number;
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
objId?: string;
|
|
60
|
+
kind?: 'workflow';
|
|
61
|
+
objectIds?: string[];
|
|
62
|
+
sha256?: string;
|
|
63
|
+
warnings?: string[];
|
|
64
|
+
error?: {
|
|
65
|
+
code: string;
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export interface Settings {
|
|
70
|
+
concurrency: number;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
timeoutMs: number;
|
|
74
|
+
stableMs: number;
|
|
75
|
+
retries: number;
|
|
76
|
+
}
|
|
77
|
+
export declare const defaults: Settings;
|
|
78
|
+
export interface TaskState {
|
|
79
|
+
schema: 1;
|
|
80
|
+
version: string;
|
|
81
|
+
appId: string;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
requestedOrigin?: string;
|
|
84
|
+
allowTemporaryRecords?: boolean;
|
|
85
|
+
settings: Settings;
|
|
86
|
+
environment?: {
|
|
87
|
+
origin: string;
|
|
88
|
+
identityHash: string;
|
|
89
|
+
};
|
|
90
|
+
pages?: PageItem[];
|
|
91
|
+
discoveredAt?: string;
|
|
92
|
+
htmlRequired?: boolean;
|
|
93
|
+
siteRequired?: boolean;
|
|
94
|
+
appName?: string;
|
|
95
|
+
forms?: FormItem[];
|
|
96
|
+
/** Absent on legacy tasks, which retain their original discovery/placeholder flow. */
|
|
97
|
+
menuRequired?: boolean;
|
|
98
|
+
menus?: MenuItem[];
|
|
99
|
+
formPages?: FormPage[];
|
|
100
|
+
skippedMenus?: SkippedMenu[];
|
|
101
|
+
archive?: {
|
|
102
|
+
path: string;
|
|
103
|
+
sha256: string;
|
|
104
|
+
completedAt: string;
|
|
105
|
+
partial: boolean;
|
|
106
|
+
site?: {
|
|
107
|
+
entry: string;
|
|
108
|
+
files: {
|
|
109
|
+
path: string;
|
|
110
|
+
sha256: string;
|
|
111
|
+
}[];
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface HtmlResult {
|
|
116
|
+
id: string;
|
|
117
|
+
kind?: 'page' | 'form';
|
|
118
|
+
status: 'running' | 'succeeded' | 'failed';
|
|
119
|
+
token: string;
|
|
120
|
+
attempt: number;
|
|
121
|
+
sourceSha256: string;
|
|
122
|
+
promptVersion: 1 | 2 | 3 | 4 | 5;
|
|
123
|
+
startedAt: string;
|
|
124
|
+
finishedAt?: string;
|
|
125
|
+
file?: string;
|
|
126
|
+
sha256?: string;
|
|
127
|
+
bytes?: number;
|
|
128
|
+
error?: {
|
|
129
|
+
code: string;
|
|
130
|
+
message: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export interface Attempt {
|
|
134
|
+
number: number;
|
|
135
|
+
stage: string;
|
|
136
|
+
startedAt: string;
|
|
137
|
+
finishedAt?: string;
|
|
138
|
+
elapsedMs?: number;
|
|
139
|
+
status: 'running' | 'succeeded' | 'failed';
|
|
140
|
+
error?: {
|
|
141
|
+
code: string;
|
|
142
|
+
message: string;
|
|
143
|
+
details?: unknown;
|
|
144
|
+
};
|
|
145
|
+
diagnostic?: string;
|
|
146
|
+
evidence?: unknown;
|
|
147
|
+
cleanupError?: string;
|
|
148
|
+
}
|
|
149
|
+
export interface PageResult {
|
|
150
|
+
id: string;
|
|
151
|
+
name: string;
|
|
152
|
+
url: string;
|
|
153
|
+
status: 'running' | 'succeeded' | 'failed';
|
|
154
|
+
history: Attempt[];
|
|
155
|
+
warnings?: {
|
|
156
|
+
code: string;
|
|
157
|
+
message: string;
|
|
158
|
+
}[];
|
|
159
|
+
file?: string;
|
|
160
|
+
sha256?: string;
|
|
161
|
+
width?: number;
|
|
162
|
+
height?: number;
|
|
163
|
+
bytes?: number;
|
|
164
|
+
}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Shared by the shell and standalone transport. A linked workflow/form commit is one
|
|
2
|
+
// localStorage replacement; unavailable storage keeps the whole transaction in memory.
|
|
3
|
+
export function createOfflineStorage(appId) {
|
|
4
|
+
const key = 'e10-prototype:v2:' + appId;
|
|
5
|
+
let memory = { schema: 2, objects: {} }, volatile = false;
|
|
6
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
7
|
+
const valid = (value) => value?.schema === 1 &&
|
|
8
|
+
Array.isArray(value.records) &&
|
|
9
|
+
value.records.every((row) => row &&
|
|
10
|
+
typeof row.id === 'string' &&
|
|
11
|
+
row.fields &&
|
|
12
|
+
typeof row.fields === 'object' &&
|
|
13
|
+
!Array.isArray(row.fields));
|
|
14
|
+
const check = (scope, value) => {
|
|
15
|
+
if (!scope.startsWith(appId + ':') ||
|
|
16
|
+
!/^\d+:(?:\d+|workflow)$/.test(scope) ||
|
|
17
|
+
!valid(value) ||
|
|
18
|
+
JSON.stringify(value).length > 2_000_000)
|
|
19
|
+
throw new Error('本地数据范围、格式或容量无效');
|
|
20
|
+
};
|
|
21
|
+
const read = () => {
|
|
22
|
+
if (!volatile)
|
|
23
|
+
try {
|
|
24
|
+
const stored = JSON.parse(localStorage.getItem(key));
|
|
25
|
+
if (stored?.schema === 2 &&
|
|
26
|
+
stored.objects &&
|
|
27
|
+
typeof stored.objects === 'object' &&
|
|
28
|
+
!Array.isArray(stored.objects))
|
|
29
|
+
memory = stored;
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
return clone(memory);
|
|
33
|
+
};
|
|
34
|
+
const persist = (next) => {
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(key, JSON.stringify(next));
|
|
37
|
+
volatile = false;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
volatile = true;
|
|
41
|
+
}
|
|
42
|
+
memory = next;
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
valid,
|
|
46
|
+
operate(scope, operation, value) {
|
|
47
|
+
check(scope, value);
|
|
48
|
+
if (!['load', 'save', 'reset'].includes(operation))
|
|
49
|
+
throw new Error('未知本地操作');
|
|
50
|
+
const next = read();
|
|
51
|
+
if (operation === 'load' && !valid(next.objects[scope])) {
|
|
52
|
+
try {
|
|
53
|
+
const previous = JSON.parse(localStorage.getItem('e10-prototype:v1:' + scope));
|
|
54
|
+
if (valid(previous))
|
|
55
|
+
next.objects[scope] = previous;
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
}
|
|
59
|
+
if (operation !== 'load' || !valid(next.objects[scope]))
|
|
60
|
+
next.objects[scope] = clone(value);
|
|
61
|
+
persist(next);
|
|
62
|
+
return clone(next.objects[scope]);
|
|
63
|
+
},
|
|
64
|
+
commit(entries) {
|
|
65
|
+
if (!Array.isArray(entries) ||
|
|
66
|
+
!entries.length ||
|
|
67
|
+
entries.length > 100 ||
|
|
68
|
+
new Set(entries.map((e) => e.scope)).size !== entries.length ||
|
|
69
|
+
JSON.stringify(entries).length > 2_000_000)
|
|
70
|
+
throw new Error('本地联合保存参数无效');
|
|
71
|
+
for (const entry of entries)
|
|
72
|
+
check(entry.scope, entry.value);
|
|
73
|
+
const next = read();
|
|
74
|
+
for (const entry of entries)
|
|
75
|
+
next.objects[entry.scope] = clone(entry.value);
|
|
76
|
+
persist(next);
|
|
77
|
+
return clone(entries);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PageItem, FormItem } from './model.js';
|
|
2
|
+
import type { E10AuthContext } from './vendor/environment-auth.js';
|
|
3
|
+
export declare function parsePages(payload: unknown, appId: string, origin: string): PageItem[];
|
|
4
|
+
export declare function parseApp(payload: unknown, appId: string): string;
|
|
5
|
+
export declare function parseForms(payload: unknown, appId: string, origin: string): FormItem[];
|
|
6
|
+
export declare function fetchCatalog(auth: E10AuthContext, appId: string): Promise<{
|
|
7
|
+
appName: string;
|
|
8
|
+
forms: FormItem[];
|
|
9
|
+
}>;
|
|
10
|
+
export declare function fetchPages(auth: E10AuthContext, appId: string): Promise<PageItem[]>;
|