weaver-e9-assistant-cli 1.1.5 → 1.1.11

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/wfsummary.js CHANGED
@@ -84,6 +84,6 @@ export async function getFlowSummary(cfg, requestId) {
84
84
  };
85
85
  }
86
86
  function htmltypeLabel(ht) {
87
- const m = { 1: '文本', 2: '多行文本', 3: '浏览选择', 4: '附件', 5: '选择框', 6: '复选框' };
87
+ const m = { 1: '文本', 2: '多行文本', 3: '浏览选择', 4: '复选框', 5: '选择框', 6: '附件' };
88
88
  return m[ht] ?? `ht=${ht}`;
89
89
  }
package/dist/wfupload.js CHANGED
@@ -17,6 +17,24 @@ export function guessListType(filePath) {
17
17
  const ext = (path.extname(filePath).slice(1) || '').toLowerCase();
18
18
  return IMG_EXT.has(ext) ? 'img' : 'list';
19
19
  }
20
+
21
+ /**
22
+ * 字段的上传类型:E9 前端 `2===field.detailtype ? "img" : "list"`。
23
+ * 不能只看扩展名——.png 传到「附件」字段也应走 list。
24
+ */
25
+ export function fieldListType(field) {
26
+ const detailtype = field?.detailtype ?? field?.raw?.detailtype;
27
+ return Number(detailtype) === 2 ? 'img' : 'list';
28
+ }
29
+
30
+ /**
31
+ * 写进表单字段的上传结果:
32
+ * - 附件字段(list)→ fileid(浏览器实测 hidden input 值为文档 id)
33
+ * - 图片字段(img) → imagefileid(缺失回退 fileid)
34
+ */
35
+ export function uploadFieldValue(up, listType) {
36
+ return listType === 'img' ? (up.imagefileid || up.fileid) : up.fileid;
37
+ }
20
38
  /**
21
39
  * 上传本地文件到表单字段。返回 imagefileid(填进 field<id> 的值)。
22
40
  * listType: 'img'(图片字段) / 'list'(附件字段),不传按扩展名猜。
@@ -25,7 +43,19 @@ export async function uploadFile(cfg, opts) {
25
43
  const { workflowId, fieldId, filePath } = opts;
26
44
  const listType = opts.listType ?? guessListType(filePath);
27
45
  const category = opts.category ?? '21';
28
- const loginuserid = opts.loginuserid ?? '1';
46
+ // 上传接口要求 loginuserid 与当前操作人一致;cookie-only 环境没有 userid 参数时从 loginuuids 取
47
+ const cookieUid = (() => {
48
+ const m = String(cfg.cookie ?? '').match(/loginuuids=([^;]*)/i);
49
+ if (!m)
50
+ return '';
51
+ try {
52
+ return decodeURIComponent(m[1]).split(',')[0].trim();
53
+ }
54
+ catch {
55
+ return '';
56
+ }
57
+ })();
58
+ const loginuserid = opts.loginuserid ?? cookieUid ?? '1';
29
59
  if (!fs.existsSync(filePath))
30
60
  throw new Error(`文件不存在: ${filePath}`);
31
61
  const buf = fs.readFileSync(filePath);
@@ -38,7 +68,9 @@ export async function uploadFile(cfg, opts) {
38
68
  // 直接用 fetch + aiAgentToken(不走 E9Api,因 multipart 需自定义 body)
39
69
  const sess = (await import('./auth-token.js')).AuthTokenSession.fromProfile(cfg);
40
70
  const base = cfg.baseUrl.replace(/\/+$/, '');
41
- const url = `${base}/api/doc/upload/uploadFile2Doc?category=,,${category}&workflowid=${workflowId}&listType=${listType}&wfTestStr=&uploadSrc=wfform&loginuserid=${loginuserid}`;
71
+ // 字段 fileattr.docCategory 可能是完整的 ",,2";调用方也可能只传 "2",统一补前缀
72
+ const docCategory = String(category).startsWith(',') ? String(category) : `,,${category}`;
73
+ const url = `${base}/api/doc/upload/uploadFile2Doc?category=${encodeURIComponent(docCategory)}&workflowid=${workflowId}&listType=${listType}&wfTestStr=&uploadSrc=wfform&loginuserid=${loginuserid}`;
42
74
  const fd = new FormData();
43
75
  fd.append('name', filename);
44
76
  fd.append('isFirstUploadFile', '9');
@@ -47,12 +79,24 @@ export async function uploadFile(cfg, opts) {
47
79
  fd.append('secretLevel', '');
48
80
  fd.append('ts', String(Date.now()));
49
81
  fd.append('file', new Blob([buf], { type: mime }), filename);
50
- const r = await fetch(url, {
82
+ // token 优先;cookie-only / token 失效时回退 Cookie(手工导 cookie 的环境上传附件依赖此路径)
83
+ const buildHeaders = (useToken) => useToken && sess.token
84
+ ? { aiAgentToken: sess.token }
85
+ : { ...(cfg.cookie ? { Cookie: cfg.cookie } : {}), 'X-Requested-With': 'XMLHttpRequest' };
86
+ let r = await fetch(url, {
51
87
  method: 'POST',
52
- headers: { aiAgentToken: sess.token },
88
+ headers: buildHeaders(!!sess.token),
53
89
  body: fd,
54
90
  signal: AbortSignal.timeout(60000),
55
91
  });
92
+ if (!r.ok && sess.token && cfg.cookie) {
93
+ r = await fetch(url, {
94
+ method: 'POST',
95
+ headers: buildHeaders(false),
96
+ body: fd,
97
+ signal: AbortSignal.timeout(60000),
98
+ });
99
+ }
56
100
  const t = await r.text();
57
101
  let j = null;
58
102
  try {
package/dist/workflow.js CHANGED
@@ -222,6 +222,8 @@ export async function getWfList(cfg, opts) {
222
222
  const expectedPages = Math.ceil((Number(total) || 0) / effectivePageSize);
223
223
  if (expectedPages > 500)
224
224
  throw new Error(`负向筛选需读取 ${expectedPages} 页,超过 500 页安全上限;请先增加其它筛选条件。`);
225
+ // ⚠ 不能并发翻页:同一 dataKey 的 table/datas 并发请求在服务端会偶发返回空页,
226
+ // 导致「收不全 → 误报失败」。必须串行。
225
227
  for (let current = 2; current <= expectedPages; current++) {
226
228
  const next = await api.post('/api/ec/dev/table/datas', {
227
229
  dataKey, current: String(current), sortParams: parseSortSpec(opts.sort), pageSize: '100',
@@ -243,8 +245,8 @@ export async function getWfList(cfg, opts) {
243
245
  const filteredTotal = opts.excludeCreatorId ? items.length : total;
244
246
  if (opts.totalOnly)
245
247
  items = [];
246
- // 待办才取当前处理人(已办/我发起无未操作者概念)
247
- if (category === 'todo' && !opts.totalOnly) {
248
+ // 待办才取当前处理人(已办/我发起无未操作者概念);--no-operators 可跳过(该接口在服务端较慢)
249
+ if (category === 'todo' && !opts.totalOnly && opts.withOperators !== false) {
248
250
  try {
249
251
  const datasPayload = JSON.stringify(rows.map((r) => ({
250
252
  requestid: r?.requestid ?? r?.randomField2 ?? '',
package/dist/workplan.js CHANGED
@@ -170,19 +170,34 @@ export async function searchWorkplan(cfg, opts = {}) {
170
170
  if (!dataKey)
171
171
  throw new Error(`workplan search 未拿到 dataKey,getlist 响应: ${(r1.text ?? '').slice(0, 200)}`);
172
172
  const page = opts.page ?? 1;
173
+ // ★ 保持串行:首条请求要完成 TLS 握手(TLS 1.3 实测约 2.3s;CLI 入口已默认压到 1.2),
174
+ // 后续请求复用同一连接仅约 50~150ms。并发发 datas+counts 会新开连接、再付一次握手
175
+ // (实测三段串行 2.6s → 并发 4.9s),故不并行。
173
176
  const r2 = await api.post('/api/ec/dev/table/datas', { dataKey, current: String(page), sortParams: '[]', pageSize: '10' });
174
177
  const columns = extractColumns(r2.json);
175
178
  const rows = extractRows(r2.json);
176
179
  const items = rows.map((r) => normalizeSearchRow(r));
177
180
  const pageSize = pickByPaths(r2.json, ['pageSize', 'data.pageSize']);
178
- let total = null;
181
+ const effectivePageSize = typeof pageSize === 'number' && pageSize > 0 ? pageSize : 10;
182
+ let total = extractTotal(r2.json);
179
183
  let countsResp = null;
180
- try {
181
- const r3 = await api.post('/api/ec/dev/table/counts', { dataKey });
182
- countsResp = r3.json;
183
- total = extractTotal(r3.json);
184
+ // 省掉 counts 请求的两种情况:
185
+ // 1) datas 自带总数;
186
+ // 2) 首页且未填满一页 → 总数就是本页条数(不可能还有下一页);
187
+ // 3) 调用方显式 withCount=false。
188
+ const needCounts = total == null
189
+ && !(page === 1 && items.length < effectivePageSize)
190
+ && opts.withCount !== false;
191
+ if (needCounts) {
192
+ try {
193
+ const r3 = await api.post('/api/ec/dev/table/counts', { dataKey });
194
+ countsResp = r3.json;
195
+ total = extractTotal(r3.json);
196
+ }
197
+ catch { /* counts 失败不阻断 */ }
184
198
  }
185
- catch { /* counts 失败不阻断 */ }
199
+ if (total == null && page === 1 && items.length < effectivePageSize)
200
+ total = items.length;
186
201
  return { dataKey, page, total, pageSize: typeof pageSize === 'number' ? pageSize : null, items, raw: { getlist: r1.json, datas: r2.json, counts: countsResp } };
187
202
  }
188
203
  export function normalizeSearchRow(row) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weaver-e9-assistant-cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.11",
4
4
  "description": "泛微 e-cology E9 HTTP 能力 CLI 封装(待办、微博、新建流程、邮件、公文、日程、考勤、建模台账)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "scripts": {
19
19
  "start": "node dist/cli.js",
20
20
  "e9": "node dist/cli.js",
21
- "build": "node --check dist/cli.js && node --check dist/cube.js && node --check dist/workplan.js && node --check dist/fileutil.js && node --check dist/kq.js && node --check dist/links.js && node --check dist/report.js && node --check dist/workflow.js && node --check dist/parse.js && node --check dist/todosubmit.js && node --check dist/wfsummary.js && node --check dist/wfstart.js && node --check dist/submissionplan.js && node --check dist/submissionstate.js && node --check dist/auth-token.js && node --check dist/todobatch.js && node --check dist/pageopen.js && node --check dist/createcontext.js && node --check dist/querystate.js && node --check dist/wfops.js && node --check dist/subordinate.js && node --check dist/hrm.js && node --check dist/supervise.js && node --check dist/wfagent.js && node --check dist/nodemap.js",
21
+ "build": "node --check dist/cache-store.js && node --check dist/formula.js && node --check dist/teamreport.js && node --check dist/cli.js && node --check dist/cube.js && node --check dist/workplan.js && node --check dist/fileutil.js && node --check dist/kq.js && node --check dist/links.js && node --check dist/report.js && node --check dist/workflow.js && node --check dist/parse.js && node --check dist/todosubmit.js && node --check dist/tododetail.js && node --check dist/wfsummary.js && node --check dist/wfstart.js && node --check dist/submissionplan.js && node --check dist/submissionstate.js && node --check dist/auth-token.js && node --check dist/todobatch.js && node --check dist/pageopen.js && node --check dist/createcontext.js && node --check dist/querystate.js && node --check dist/wfops.js && node --check dist/subordinate.js && node --check dist/hrm.js && node --check dist/supervise.js && node --check dist/wfagent.js && node --check dist/nodemap.js && node --check dist/wfcodeblock.js",
22
22
  "test": "node --test \"tests/**/*.test.js\"",
23
23
  "prepack": "npm test",
24
24
  "release": "node scripts/release.js",