weaver-e9-assistant-cli 1.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/dist/args.js +36 -0
- package/dist/auth-token.js +690 -0
- package/dist/auth.js +187 -0
- package/dist/blog.js +469 -0
- package/dist/cli.js +4041 -0
- package/dist/client.js +199 -0
- package/dist/config.js +124 -0
- package/dist/createcontext.js +41 -0
- package/dist/crypto.js +139 -0
- package/dist/cube.js +1633 -0
- package/dist/email.js +838 -0
- package/dist/fileutil.js +20 -0
- package/dist/kq.js +204 -0
- package/dist/links.js +34 -0
- package/dist/odoc.js +194 -0
- package/dist/pageopen.js +29 -0
- package/dist/parse.js +159 -0
- package/dist/querystate.js +38 -0
- package/dist/render.js +21 -0
- package/dist/report.js +50 -0
- package/dist/submissionplan.js +43 -0
- package/dist/submissionstate.js +81 -0
- package/dist/todoaskidea.js +54 -0
- package/dist/todobatch.js +154 -0
- package/dist/todocc.js +128 -0
- package/dist/todoforward.js +66 -0
- package/dist/todoreject.js +215 -0
- package/dist/todosubmit.js +277 -0
- package/dist/todotransfer.js +54 -0
- package/dist/util.js +25 -0
- package/dist/wfcreate.js +102 -0
- package/dist/wfform.js +194 -0
- package/dist/wfops.js +40 -0
- package/dist/wfreceiver.js +43 -0
- package/dist/wfstart.js +717 -0
- package/dist/wfsummary.js +89 -0
- package/dist/wfupload.js +78 -0
- package/dist/wfuser.js +60 -0
- package/dist/workflow.js +461 -0
- package/dist/workplan.js +394 -0
- package/package.json +25 -0
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
import { extractDataKey, extractRows, extractTotal, extractColumns, normalizeRow, pickByPaths, stripHtml } from './parse.js';
|
|
3
|
+
/**
|
|
4
|
+
* 复刻 E9 流程中心取数链路。支持 todo(待办)/done(已办)/mine(我发起) 三大分类。
|
|
5
|
+
*
|
|
6
|
+
* 1) getWfListParams(viewcondition) -> 拿 dataKey(todo 有;done/mine 返空,靠 splitPageKey)
|
|
7
|
+
* 2) splitPageKey(viewcondition + 过滤项 + viewScope) -> 刷新 dataKey(三大分类都靠这步拿 dataKey)
|
|
8
|
+
* 3) ec/dev/table/datas(dataKey, current=页码) -> 数据行
|
|
9
|
+
* 4) ec/dev/table/counts(dataKey) -> 总数
|
|
10
|
+
*
|
|
11
|
+
* 实跑校准(2026-08-25):
|
|
12
|
+
* - todo: viewScope=doing, complete=0, menuIds=1,13
|
|
13
|
+
* - done: viewScope=done, complete=2, menuIds=1,90(getWfListParams 返空,splitPageKey 拿 dataKey)
|
|
14
|
+
* - mine: viewScope=mine, complete=2, menuIds=1,14
|
|
15
|
+
* viewScope/complete/menuIds 来自 HAR 抓包。
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_FILTERS = {
|
|
18
|
+
defaultTabVal: '0',
|
|
19
|
+
requestname: '',
|
|
20
|
+
wfcode: '',
|
|
21
|
+
workflowid: '',
|
|
22
|
+
createdateselect: '0',
|
|
23
|
+
createdatefrom: '',
|
|
24
|
+
createdateto: '',
|
|
25
|
+
creatertype: '0',
|
|
26
|
+
workcode: '',
|
|
27
|
+
doingStatus: '0',
|
|
28
|
+
ownerdepartmentid: '',
|
|
29
|
+
creatersubcompanyid: '',
|
|
30
|
+
workflowtype: '',
|
|
31
|
+
requestlevel: '',
|
|
32
|
+
recievedateselect: '0',
|
|
33
|
+
recievedatefrom: '',
|
|
34
|
+
recievedateto: '',
|
|
35
|
+
wfstatu: '1',
|
|
36
|
+
nodetype: '',
|
|
37
|
+
unophrmid: '',
|
|
38
|
+
docids: '',
|
|
39
|
+
createrid: '',
|
|
40
|
+
operatedateselect: '0',
|
|
41
|
+
operatedatefrom: '',
|
|
42
|
+
operatedateto: '',
|
|
43
|
+
archivestatus: '0',
|
|
44
|
+
secLevel: '',
|
|
45
|
+
crmids: '',
|
|
46
|
+
proids: '',
|
|
47
|
+
};
|
|
48
|
+
/** 各分类的 viewScope/complete/menuIds/默认viewcondition(来自 HAR 抓包实跑确认)。
|
|
49
|
+
* menuIds 可由 cfg 覆盖:cfg.doneMenuIds/mineMenuIds;viewcondition 可由 opts 覆盖(子 tab)。 */
|
|
50
|
+
export const CATEGORY_SCOPE = {
|
|
51
|
+
todo: { viewScope: 'doing', complete: '0', menuIdsKey: 'menuIds', defaultViewcondition: '5' },
|
|
52
|
+
done: { viewScope: 'done', complete: '2', menuIdsKey: 'doneMenuIds', defaultMenuIds: '1,90', defaultViewcondition: '10' },
|
|
53
|
+
mine: { viewScope: 'mine', complete: '2', menuIdsKey: 'mineMenuIds', defaultMenuIds: '1,14', defaultViewcondition: '16' },
|
|
54
|
+
};
|
|
55
|
+
/** 排序字段映射表 */
|
|
56
|
+
const SORT_FIELD_MAP = {
|
|
57
|
+
"发起时间": "t1.createdate,t1.createtime",
|
|
58
|
+
"接收时间": "receivedate,receivetime",
|
|
59
|
+
"流程标题": "t1.requestname",
|
|
60
|
+
"流程编号": "t1.requestmark",
|
|
61
|
+
"紧急程度": "t1.requestlevel",
|
|
62
|
+
"当前节点": "t1.currentnodename",
|
|
63
|
+
"操作时间": "t1.operatedate,t1.operatetime",
|
|
64
|
+
"结束时间": "t1.lastoperatedate,t1.lastoperatetime",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function parseSortSpec(spec) {
|
|
68
|
+
if (!spec)
|
|
69
|
+
return '[]';
|
|
70
|
+
const items = spec.split(",").map(s => s.trim()).filter(Boolean);
|
|
71
|
+
const arr = items.map(item => {
|
|
72
|
+
const [field, order] = item.split(":").map(s => s.trim());
|
|
73
|
+
const orderkey = SORT_FIELD_MAP[field] || field;
|
|
74
|
+
const sortOrder = order === "asc" ? "ascend" : "descend";
|
|
75
|
+
return { orderkey, sortOrder };
|
|
76
|
+
});
|
|
77
|
+
return JSON.stringify(arr);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 时间范围解析 */
|
|
81
|
+
export function parseTimeRange(range) {
|
|
82
|
+
if (!range)
|
|
83
|
+
return { from: '', to: '' };
|
|
84
|
+
const parts = range.trim().split(/\s+/);
|
|
85
|
+
if (parts.length === 1)
|
|
86
|
+
return { from: parts[0], to: parts[0] };
|
|
87
|
+
return { from: parts[0] || "", to: parts[1] || "" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 子分类 viewcondition 映射表 */
|
|
91
|
+
export const TODO_TABS = {
|
|
92
|
+
"全部": "0", "待处理": "5", "待阅": "6", "被退回": "7",
|
|
93
|
+
"转发": "4", "未读": "1", "新反馈": "2", "已查阅": "3", "关注": "37",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const DONE_TABS = {
|
|
97
|
+
"全部": "10", "关注": "38",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const MINE_TABS = {
|
|
101
|
+
"全部": "16", "审批中": "17", "已结束": "18", "关注": "39",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export function resolveSubCategory(spec, category) {
|
|
105
|
+
if (!spec)
|
|
106
|
+
return undefined;
|
|
107
|
+
// 直接是数字,直接返回
|
|
108
|
+
if (/^\d+$/.test(spec))
|
|
109
|
+
return spec;
|
|
110
|
+
const map = category === 'todo' ? TODO_TABS : category === 'done' ? DONE_TABS : MINE_TABS;
|
|
111
|
+
const key = Object.keys(map).find(k => k === spec || k.includes(spec));
|
|
112
|
+
return key ? map[key] : undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function getWfList(cfg, opts) {
|
|
116
|
+
// 多流程筛选不依赖后端是否支持逗号分隔:分别查询后合并,避免漏结果。
|
|
117
|
+
const workflowIds = String(opts.workflow ?? '').split(',').map((id) => id.trim()).filter(Boolean);
|
|
118
|
+
if (!opts._singleWorkflow && workflowIds.length > 1) {
|
|
119
|
+
const batches = await Promise.all(workflowIds.map((workflow) => getWfList(cfg, { ...opts, workflow, _singleWorkflow: true })));
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
const items = batches.flatMap((r) => r.items).filter((item) => {
|
|
122
|
+
const id = String(item.requestId ?? '');
|
|
123
|
+
if (!id || seen.has(id))
|
|
124
|
+
return !id;
|
|
125
|
+
seen.add(id);
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
category: opts.category ?? 'todo',
|
|
130
|
+
dataKey: '',
|
|
131
|
+
items,
|
|
132
|
+
columns: batches.find((r) => r.columns.length)?.columns ?? [],
|
|
133
|
+
total: batches.reduce((sum, r) => sum + (Number(r.total) || 0), 0),
|
|
134
|
+
page: opts.page ?? 1,
|
|
135
|
+
pageSize: batches.find((r) => r.pageSize)?.pageSize ?? null,
|
|
136
|
+
multiWorkflow: workflowIds,
|
|
137
|
+
raw: { batches: batches.map((r) => r.raw) },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const { page = 1, keyword = '', debug = false, category = 'todo' } = opts;
|
|
141
|
+
const viewcondition = resolveSubCategory(opts.subCategory, category) ?? opts.viewcondition ?? CATEGORY_SCOPE[category]?.defaultViewcondition ?? '5';
|
|
142
|
+
const api = await createApiFromProfile(cfg);
|
|
143
|
+
const scope = CATEGORY_SCOPE[category] ?? CATEGORY_SCOPE.todo;
|
|
144
|
+
const menuIds = String(cfg[scope.menuIdsKey] ?? scope.defaultMenuIds ?? cfg.menuIds ?? '1,13');
|
|
145
|
+
const menuPathIds = String(cfg[scope.menuIdsKey] ?? cfg.menuPathIds ?? menuIds);
|
|
146
|
+
// 1) getWfListParams(todo 返 dataKey;done/mine 返空,靠 splitPageKey)
|
|
147
|
+
const r1 = await api.post('/api/workflow/reqlist/getWfListParams', buildScopeBody(viewcondition, scope, menuIds, menuPathIds));
|
|
148
|
+
if (debug)
|
|
149
|
+
logStep('getWfListParams', r1);
|
|
150
|
+
// 2) splitPageKey(带过滤项;keyword 进 requestname;done/mine 需带 date2during=0)—— 三大分类都靠这步拿 dataKey
|
|
151
|
+
const filters = buildFilters(opts);
|
|
152
|
+
if (category !== 'todo')
|
|
153
|
+
filters.date2during = '0';
|
|
154
|
+
const r2 = await api.post('/api/workflow/reqlist/splitPageKey', buildSplitBody(viewcondition, filters, scope, menuIds, menuPathIds));
|
|
155
|
+
if (debug)
|
|
156
|
+
logStep('splitPageKey', r2);
|
|
157
|
+
// dataKey 优先取 splitPageKey 的响应,取不到再回退 getWfListParams
|
|
158
|
+
let dataKey = extractDataKey(r2.json) ?? extractDataKey(r1.json);
|
|
159
|
+
if (!dataKey) {
|
|
160
|
+
throw new Error(`未能从响应中提取 dataKey(category=${category})。\n` +
|
|
161
|
+
'用 --raw 查看真实响应,或确认 cookie 是否已过期。\n' +
|
|
162
|
+
`getWfListParams 响应: ${truncate(r1.text)}\n` +
|
|
163
|
+
`splitPageKey 响应: ${truncate(r2.text)}`);
|
|
164
|
+
}
|
|
165
|
+
// total-only 用于语音/聊天中的快速确认:跳过大结果集的数据请求,只取数量。
|
|
166
|
+
let r3 = null;
|
|
167
|
+
if (!opts.totalOnly || opts.excludeCreatorId) {
|
|
168
|
+
const sortParams = parseSortSpec(opts.sort);
|
|
169
|
+
r3 = await api.post('/api/ec/dev/table/datas', {
|
|
170
|
+
dataKey,
|
|
171
|
+
current: String(page),
|
|
172
|
+
sortParams,
|
|
173
|
+
// 显式每页 10 条,与 printTodoItems 的“展示前 10 条”一致,避免 E9 默认 30 条/页导致
|
|
174
|
+
// --page N 按 30 条跳页、本页第 11~30 条永远看不到、翻页时条数跳变成 30 的错位。
|
|
175
|
+
pageSize: opts.pageSize ?? '10',
|
|
176
|
+
});
|
|
177
|
+
if (debug)
|
|
178
|
+
logStep('ec/dev/table/datas', r3);
|
|
179
|
+
}
|
|
180
|
+
// 4) ec/dev/table/counts(total-only 的唯一数据读取)
|
|
181
|
+
const r4 = await api.post('/api/ec/dev/table/counts', { dataKey });
|
|
182
|
+
if (debug)
|
|
183
|
+
logStep('ec/dev/table/counts', r4);
|
|
184
|
+
let rows = r3 ? extractRows(r3.json) : [];
|
|
185
|
+
const columns = r3 ? extractColumns(r3.json) : [];
|
|
186
|
+
const total = extractTotal(r4.json) ?? (r3 ? extractTotal(r3.json) : null);
|
|
187
|
+
let pageSize = r3 ? pickNum(r3.json, ['pageSize', 'data.pageSize']) : null;
|
|
188
|
+
let fetchedPages = r3 ? 1 : 0;
|
|
189
|
+
// E9 没有“不等于当前创建人”接口。需要排除时必须先拉完整结果集,不能只过滤当前页。
|
|
190
|
+
if (r3 && opts.excludeCreatorId) {
|
|
191
|
+
const firstPageRows = page === 1 ? rows : extractRows((await api.post('/api/ec/dev/table/datas', {
|
|
192
|
+
dataKey, current: '1', sortParams: parseSortSpec(opts.sort), pageSize: '100',
|
|
193
|
+
})).json);
|
|
194
|
+
rows = firstPageRows;
|
|
195
|
+
// 负向筛选固定 100/页全量拉取(下方循环 pageSize:'100'),页数基准不随列表分页 pageSize=10 变化,
|
|
196
|
+
// 否则 expectedPages 会被放大 10 倍、误触发 500 页安全上限。
|
|
197
|
+
const effectivePageSize = 100;
|
|
198
|
+
const expectedPages = Math.ceil((Number(total) || 0) / effectivePageSize);
|
|
199
|
+
if (expectedPages > 500)
|
|
200
|
+
throw new Error(`负向筛选需读取 ${expectedPages} 页,超过 500 页安全上限;请先增加其它筛选条件。`);
|
|
201
|
+
for (let current = 2; current <= expectedPages; current++) {
|
|
202
|
+
const next = await api.post('/api/ec/dev/table/datas', {
|
|
203
|
+
dataKey, current: String(current), sortParams: parseSortSpec(opts.sort), pageSize: '100',
|
|
204
|
+
});
|
|
205
|
+
const nextRows = extractRows(next.json);
|
|
206
|
+
rows.push(...nextRows);
|
|
207
|
+
fetchedPages++;
|
|
208
|
+
if (!nextRows.length)
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
if (Number(total) > rows.length)
|
|
212
|
+
throw new Error(`负向筛选未能读取完整列表:服务端共 ${total} 条,实际只取到 ${rows.length} 条。`);
|
|
213
|
+
pageSize = effectivePageSize;
|
|
214
|
+
}
|
|
215
|
+
let items = rows.map((r) => normalizeRow(r, columns));
|
|
216
|
+
const sourceTotal = total;
|
|
217
|
+
if (opts.excludeCreatorId)
|
|
218
|
+
items = items.filter((item) => String(item.creatorId ?? '') !== String(opts.excludeCreatorId));
|
|
219
|
+
const filteredTotal = opts.excludeCreatorId ? items.length : total;
|
|
220
|
+
if (opts.totalOnly)
|
|
221
|
+
items = [];
|
|
222
|
+
// 待办才取当前处理人(已办/我发起无未操作者概念)
|
|
223
|
+
if (category === 'todo' && !opts.totalOnly) {
|
|
224
|
+
try {
|
|
225
|
+
const datasPayload = JSON.stringify(rows.map((r) => ({
|
|
226
|
+
requestid: r?.requestid ?? r?.randomField2 ?? '',
|
|
227
|
+
userid: r?.userid ?? r?.randomField4 ?? '',
|
|
228
|
+
unoperators: r?.unoperators ?? '',
|
|
229
|
+
})).filter((d) => d.requestid));
|
|
230
|
+
const r5 = await api.post('/api/workflow/reqlist/getUnoperators', {
|
|
231
|
+
datas: datasPayload, actiontype: 'batch', showAll: 'false', dataKey,
|
|
232
|
+
});
|
|
233
|
+
const opsMap = r5.json ?? {};
|
|
234
|
+
for (const row of rows) {
|
|
235
|
+
const uid = String(row?.userid ?? row?.randomField4 ?? '');
|
|
236
|
+
const rid = String(row?.requestid ?? row?.randomField2 ?? '');
|
|
237
|
+
const key = `${uid}${rid}loding`;
|
|
238
|
+
const item = items.find((candidate) => String(candidate.requestId ?? '') === rid);
|
|
239
|
+
if (item && opsMap[key])
|
|
240
|
+
item.currentOperator = stripHtml(opsMap[key]).replace(/\s+/g, ' ').trim() || undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch { /* 取处理人失败不影响主列表 */ }
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
category,
|
|
247
|
+
dataKey,
|
|
248
|
+
items,
|
|
249
|
+
columns,
|
|
250
|
+
total: filteredTotal,
|
|
251
|
+
sourceTotal,
|
|
252
|
+
page,
|
|
253
|
+
pageSize,
|
|
254
|
+
fetchedPages,
|
|
255
|
+
excludedCreatorId: opts.excludeCreatorId || undefined,
|
|
256
|
+
raw: { getWfListParams: r1.json, splitPageKey: r2.json, datas: r3?.json ?? null, counts: r4.json },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 面向用户的待办概览:把需要本人处理、转发/抄送和关注拆开显示。
|
|
262
|
+
* 每个分类独立分页;同一 requestId 只保留在优先级更高的分类,避免重复打扰用户。
|
|
263
|
+
*/
|
|
264
|
+
export async function getSmartTodoGroups(cfg, opts = {}) {
|
|
265
|
+
const groupDefs = [
|
|
266
|
+
{ key: 'action', title: '需您亲自处理', subCategory: '待处理' },
|
|
267
|
+
{ key: 'forwarded', title: '转发和抄送给您的流程', subCategory: '转发' },
|
|
268
|
+
{ key: 'following', title: '您关注的流程', subCategory: '关注' },
|
|
269
|
+
];
|
|
270
|
+
const results = await Promise.all(groupDefs.map(async (group) => ({
|
|
271
|
+
...group,
|
|
272
|
+
result: await getWfList(cfg, { ...opts, category: 'todo', subCategory: group.subCategory, _singleWorkflow: false }),
|
|
273
|
+
})));
|
|
274
|
+
return mergeSmartTodoGroups(results, opts.page ?? 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 纯函数,便于测试分组去重规则。 */
|
|
278
|
+
export function mergeSmartTodoGroups(results, page = 1) {
|
|
279
|
+
const seen = new Set();
|
|
280
|
+
const groups = results.map((group) => ({
|
|
281
|
+
...group,
|
|
282
|
+
items: group.result.items.filter((item) => {
|
|
283
|
+
const id = String(item.requestId ?? '');
|
|
284
|
+
if (!id || seen.has(id))
|
|
285
|
+
return !id;
|
|
286
|
+
seen.add(id);
|
|
287
|
+
return true;
|
|
288
|
+
}),
|
|
289
|
+
}));
|
|
290
|
+
return {
|
|
291
|
+
groups,
|
|
292
|
+
actionTotal: Number(groups.find((group) => group.key === 'action')?.result?.total) || 0,
|
|
293
|
+
categoryTotals: Object.fromEntries(groups.map((group) => [group.key, Number(group.result.total) || 0])),
|
|
294
|
+
// 仅用于诊断,名称明确说明是分类相加,绝不能称为“待处理总数”。
|
|
295
|
+
categoryTotalSum: groups.reduce((sum, group) => sum + (Number(group.result.total) || 0), 0),
|
|
296
|
+
page,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** 解析 doingBaseInfo.treedata 中的流程类型节点。 */
|
|
301
|
+
export function parseWorkflowTypes(tree) {
|
|
302
|
+
const out = [];
|
|
303
|
+
const visit = (node, path = []) => {
|
|
304
|
+
if (Array.isArray(node)) {
|
|
305
|
+
node.forEach((item) => visit(item, path));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (!node || typeof node !== 'object')
|
|
309
|
+
return;
|
|
310
|
+
const name = stripHtml(node.name ?? node.title ?? node.label ?? '').trim();
|
|
311
|
+
const domid = String(node.domid ?? node.domId ?? '');
|
|
312
|
+
const id = domid.startsWith('type_') ? domid.slice(5) : (node.type === 'type' ? String(node.key ?? node.id ?? '') : '');
|
|
313
|
+
const nextPath = name ? [...path, name] : path;
|
|
314
|
+
if (id)
|
|
315
|
+
out.push({ id, name, path: nextPath.join(' / ') });
|
|
316
|
+
for (const key of ['childs', 'children', 'nodes', 'data'])
|
|
317
|
+
if (node[key]) visit(node[key], nextPath);
|
|
318
|
+
};
|
|
319
|
+
visit(tree);
|
|
320
|
+
return [...new Map(out.filter((item) => item.name).map((item) => [item.id, item])).values()];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function matchWorkflowTypes(types, spec) {
|
|
324
|
+
const q = String(spec ?? '').trim().toLowerCase();
|
|
325
|
+
if (!q)
|
|
326
|
+
return [];
|
|
327
|
+
const ranked = types.map((item) => {
|
|
328
|
+
const name = String(item.name ?? '').trim().toLowerCase();
|
|
329
|
+
const score = name === q ? 3 : name.startsWith(q) ? 2 : name.includes(q) ? 1 : 0;
|
|
330
|
+
return { ...item, score };
|
|
331
|
+
}).filter((item) => item.score > 0);
|
|
332
|
+
return ranked.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name, 'zh-CN'));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function getWorkflowTypes(cfg, opts = {}) {
|
|
336
|
+
const category = opts.category ?? 'todo';
|
|
337
|
+
const scope = CATEGORY_SCOPE[category] ?? CATEGORY_SCOPE.todo;
|
|
338
|
+
const viewcondition = opts.viewcondition ?? scope.defaultViewcondition;
|
|
339
|
+
const menuIds = String(cfg[scope.menuIdsKey] ?? scope.defaultMenuIds ?? cfg.menuIds ?? '1,13');
|
|
340
|
+
const menuPathIds = String(cfg[scope.menuIdsKey] ?? cfg.menuPathIds ?? menuIds);
|
|
341
|
+
const api = await createApiFromProfile(cfg);
|
|
342
|
+
const response = await api.post('/api/workflow/reqlist/doingBaseInfo', {
|
|
343
|
+
...buildScopeBody(viewcondition, scope, menuIds, menuPathIds), actiontype: 'baseinfo',
|
|
344
|
+
});
|
|
345
|
+
return { items: parseWorkflowTypes(response.json?.treedata ?? response.json?.data?.treedata), raw: response.json };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function resolveWorkflowType(cfg, spec, opts = {}) {
|
|
349
|
+
if (/^\d+$/.test(String(spec ?? '').trim()))
|
|
350
|
+
return { id: String(spec).trim(), name: String(spec).trim(), candidates: [] };
|
|
351
|
+
const result = await getWorkflowTypes(cfg, opts);
|
|
352
|
+
const candidates = matchWorkflowTypes(result.items, spec);
|
|
353
|
+
if (candidates.length === 1 || (candidates[0]?.score === 3 && candidates[1]?.score !== 3))
|
|
354
|
+
return { ...candidates[0], candidates };
|
|
355
|
+
if (!candidates.length)
|
|
356
|
+
throw new Error(`找不到流程类型“${spec}”。`);
|
|
357
|
+
const error = new Error(`流程类型“${spec}”匹配到多项,请指定类型 ID:\n${candidates.map((item) => `- ${item.name} [${item.id}]`).join('\n')}`);
|
|
358
|
+
error.candidates = candidates;
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** 从 opts 构造 DEFAULT_FILTERS 覆盖 */
|
|
363
|
+
function buildFilters(opts) {
|
|
364
|
+
const f = { ...DEFAULT_FILTERS, requestname: opts.keyword ?? '' };
|
|
365
|
+
if (opts.workflow)
|
|
366
|
+
f.workflowid = opts.workflow;
|
|
367
|
+
if (opts.creator)
|
|
368
|
+
f.createrid = opts.creator;
|
|
369
|
+
if (opts.creatorDept)
|
|
370
|
+
f.ownerdepartmentid = opts.creatorDept;
|
|
371
|
+
if (opts.creatorSubcom)
|
|
372
|
+
f.creatersubcompanyid = opts.creatorSubcom;
|
|
373
|
+
if (opts.createTime) {
|
|
374
|
+
const ct = parseTimeRange(opts.createTime);
|
|
375
|
+
if (ct.from) {
|
|
376
|
+
f.createdateselect = '1';
|
|
377
|
+
f.createdatefrom = ct.from;
|
|
378
|
+
f.createdateto = ct.to;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (opts.receiveTime) {
|
|
382
|
+
const rt = parseTimeRange(opts.receiveTime);
|
|
383
|
+
if (rt.from) {
|
|
384
|
+
f.recievedateselect = '1';
|
|
385
|
+
f.recievedatefrom = rt.from;
|
|
386
|
+
f.recievedateto = rt.to;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (opts.operateTime) {
|
|
390
|
+
const ot = parseTimeRange(opts.operateTime);
|
|
391
|
+
if (ot.from) {
|
|
392
|
+
f.operatedateselect = '1';
|
|
393
|
+
f.operatedatefrom = ot.from;
|
|
394
|
+
f.operatedateto = ot.to;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (opts.requestLevel)
|
|
398
|
+
f.requestlevel = opts.requestLevel;
|
|
399
|
+
if (opts.requestMark)
|
|
400
|
+
f.wfcode = opts.requestMark;
|
|
401
|
+
if (opts.workflowType)
|
|
402
|
+
f.workflowtype = opts.workflowType;
|
|
403
|
+
if (opts.nodeId)
|
|
404
|
+
f.nodetype = opts.nodeId;
|
|
405
|
+
if (opts.flowStatus)
|
|
406
|
+
f.wfstatu = opts.flowStatus;
|
|
407
|
+
return f;
|
|
408
|
+
}
|
|
409
|
+
function pickNum(obj, paths) {
|
|
410
|
+
const v = pickByPaths(obj, paths);
|
|
411
|
+
if (typeof v === 'number')
|
|
412
|
+
return v;
|
|
413
|
+
if (typeof v === 'string' && /^\d+$/.test(v))
|
|
414
|
+
return parseInt(v, 10);
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
/** getWfListParams 的提交体(按分类的 viewScope/complete)。 */
|
|
418
|
+
function buildScopeBody(viewcondition, scope, menuIds, menuPathIds) {
|
|
419
|
+
return {
|
|
420
|
+
method: 'all',
|
|
421
|
+
offical: '',
|
|
422
|
+
officalType: '-1',
|
|
423
|
+
hideNoDataTab: 'false',
|
|
424
|
+
viewScope: scope.viewScope,
|
|
425
|
+
complete: scope.complete,
|
|
426
|
+
viewcondition,
|
|
427
|
+
menuIds,
|
|
428
|
+
menuPathIds,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* splitPageKey 的提交体(带过滤项 + 分类 viewScope)。直接构造 Record,值保持原样——
|
|
433
|
+
* 由 api.post 的 URLSearchParams 统一单次编码,避免二次编码 bug。
|
|
434
|
+
*/
|
|
435
|
+
export function buildSplitBody(viewcondition, filters, scope, menuIds, menuPathIds) {
|
|
436
|
+
return {
|
|
437
|
+
method: 'all',
|
|
438
|
+
offical: '',
|
|
439
|
+
officalType: '-1',
|
|
440
|
+
hideNoDataTab: 'false',
|
|
441
|
+
viewScope: scope.viewScope,
|
|
442
|
+
complete: scope.complete,
|
|
443
|
+
viewcondition,
|
|
444
|
+
...filters,
|
|
445
|
+
menuIds,
|
|
446
|
+
menuPathIds,
|
|
447
|
+
actiontype: 'splitpage',
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/** @deprecated 用 buildScopeBody 替代(保留向后兼容)。 */
|
|
451
|
+
export function buildGetWfListParamsBody(viewcondition, cfg) {
|
|
452
|
+
return buildScopeBody(viewcondition, CATEGORY_SCOPE.todo, String(cfg.menuIds), String(cfg.menuPathIds ?? cfg.menuIds));
|
|
453
|
+
}
|
|
454
|
+
function logStep(name, r) {
|
|
455
|
+
console.error(`[debug] ${name} -> status=${r.status} json=${r.json ? 'yes' : 'no'} len=${r.text.length}`);
|
|
456
|
+
}
|
|
457
|
+
function truncate(s, n = 300) {
|
|
458
|
+
if (!s)
|
|
459
|
+
return '(空)';
|
|
460
|
+
return s.length > n ? s.slice(0, n) + '…(已截断)' : s;
|
|
461
|
+
}
|