@microi.net/cli 4.6.4 → 4.6.8

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.
Files changed (39) hide show
  1. package/dist/mcp-server.js +98 -92
  2. package/dist/microi-cli.js +177 -26
  3. package/dist/microi-skills.meta.json +148 -142
  4. package/dist/microi.skills/.microi-skills-version.json +2 -2
  5. package/dist/microi.skills/README.md +3 -2
  6. package/dist/microi.skills/ai-engine/SKILL.md +38 -11
  7. package/dist/microi.skills/app-store/SKILL.md +134 -104
  8. package/dist/microi.skills/microi-ai-application/SKILL.md +8 -0
  9. package/dist/microi.skills/microi-client-frontend/SKILL.md +413 -398
  10. package/dist/microi.skills/microi-db-schema/SKILL.md +165 -165
  11. package/dist/microi.skills/microi-deployment/SKILL.md +29 -3
  12. package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +4 -3
  13. package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +10 -3
  14. package/dist/microi.skills/microi-form-engine/SKILL.md +165 -165
  15. package/dist/microi.skills/microi-microservice/SKILL.md +24 -0
  16. package/dist/microi.skills/microi-system-delivery/SKILL.md +207 -200
  17. package/dist/microi.skills/microi-ui/SKILL.md +330 -330
  18. package/dist/microi.skills/microi.v8.js +1818 -1758
  19. package/dist/microi.skills/ocr-engine/SKILL.md +111 -0
  20. package/dist/microi.skills/ocr-engine/agents/openai.yaml +4 -0
  21. package/dist/microi.skills/page-engine/SKILL.md +2 -0
  22. package/dist/microi.skills/performance-testing/SKILL.md +2 -2
  23. package/dist/microi.skills/playwright-e2e/SKILL.md +14 -40
  24. package/dist/microi.skills/print-engine/SKILL.md +9 -3
  25. package/dist/microi.skills/report-engine/SKILL.md +1 -1
  26. package/dist/microi.skills/translate-engine/SKILL.md +47 -5
  27. package/dist/microi.skills/ui-design/SKILL.md +1596 -1596
  28. package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +199 -199
  29. package/dist/microi.skills/ui-design/references/design-pattern-library.md +184 -184
  30. package/dist/microi.skills/ui-design/references/mci-design-contract.md +163 -163
  31. package/dist/microi.skills/v8-file-upload/SKILL.md +8 -0
  32. package/dist/microi.skills/v8-frontend-events/SKILL.md +4 -1
  33. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +28 -22
  34. package/dist/microi.skills/v8-http-integration/SKILL.md +22 -1
  35. package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +2 -1
  36. package/dist/microi.skills/v8-security/SKILL.md +7 -6
  37. package/dist/microi.skills/v8-utilities/references/server-api-index.md +1 -0
  38. package/dist/microi.skills/workspace-conventions/SKILL.md +15 -23
  39. package/package.json +1 -1
@@ -1,1758 +1,1818 @@
1
- /*
2
- * Microi V8 前端标准开发包。
3
- * 面向 Vue 3 与 uni-app 项目,不强依赖固定的界面库或状态管理方案。
4
- * 统一封装吾码接口引擎、表单引擎、文件服务、登录态与旧版 V8 前端接口。
5
- */
6
-
7
- // 默认把这些状态码视为登录态失效,便于各端统一跳转或清理缓存。
8
- const DEFAULT_AUTH_CODES = [401, -1, 1001, 1002];
9
-
10
- // 禁用常见占位图和外部二维码资源,避免前端误把临时素材带到正式项目。
11
- const DEFAULT_BLOCKED_ASSET = /(qrserver\.com|create-qr-code|picsum\.photos|placehold\.co|placeholder\.com|dummyimage\.com)/i;
12
-
13
- // 兼容浏览器、uni-app、小程序运行时以及测试环境中的全局对象读取。
14
- function getGlobalValue(key) {
15
- try {
16
- if (typeof globalThis !== 'undefined' && globalThis[key] !== undefined) return globalThis[key];
17
- } catch (e) {}
18
- return undefined;
19
- }
20
-
21
- function getUni() {
22
- try {
23
- if (typeof uni !== 'undefined' && uni && typeof uni === 'object') return uni;
24
- } catch (e) {}
25
- const runtimeUni = getGlobalValue('uni');
26
- return runtimeUni && typeof runtimeUni === 'object' ? runtimeUni : null;
27
- }
28
-
29
- function hasWindow() {
30
- return typeof window !== 'undefined' && !!window;
31
- }
32
-
33
- // 下面这些方法只做路径与查询参数拼装,不参与业务语义判断。
34
- function normalizeBase(url) {
35
- return String(url || '').replace(/\/+$/, '');
36
- }
37
-
38
- function trimLeftSlash(value) {
39
- return String(value || '').replace(/^\/+/, '');
40
- }
41
-
42
- function joinUrl(base, path) {
43
- const value = String(path || '');
44
- if (/^(https?:|data:|blob:|file:)/i.test(value)) return value;
45
- return `${normalizeBase(base)}/${trimLeftSlash(value)}`;
46
- }
47
-
48
- function appendQuery(url, key, value) {
49
- if (!value || new RegExp(`[?&]${key}=`, 'i').test(url)) return url;
50
- const sep = url.indexOf('?') >= 0 ? '&' : '?';
51
- return `${url}${sep}${key}=${encodeURIComponent(value)}`;
52
- }
53
-
54
- function appendQueryObject(url, data) {
55
- if (!data || typeof data !== 'object' || Array.isArray(data)) return url;
56
- const parts = [];
57
- Object.keys(data).forEach((key) => {
58
- const value = data[key];
59
- if (value === undefined || value === null || value === '') return;
60
- const serialized = typeof value === 'object' ? JSON.stringify(value) : String(value);
61
- parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`);
62
- });
63
- if (!parts.length) return url;
64
- return `${url}${url.indexOf('?') >= 0 ? '&' : '?'}${parts.join('&')}`;
65
- }
66
-
67
- function parseMaybeJson(value, fallback = {}) {
68
- if (typeof value !== 'string') return value == null ? fallback : value;
69
- const text = value.trim();
70
- if (!text) return fallback;
71
- try {
72
- return JSON.parse(text);
73
- } catch (e) {
74
- return fallback;
75
- }
76
- }
77
-
78
- // 没有 uni 或浏览器缓存时退回内存缓存,保证单元测试和服务端渲染不会崩溃。
79
- function createMemoryStorage() {
80
- const cache = new Map();
81
- return {
82
- get(key) {
83
- return cache.has(key) ? cache.get(key) : '';
84
- },
85
- set(key, value) {
86
- cache.set(key, value);
87
- },
88
- remove(key) {
89
- cache.delete(key);
90
- }
91
- };
92
- }
93
-
94
- function createDefaultStorage() {
95
- const runtimeUni = getUni();
96
- if (runtimeUni && typeof runtimeUni.getStorageSync === 'function') {
97
- return {
98
- get(key) {
99
- try {
100
- return runtimeUni.getStorageSync(key) || '';
101
- } catch (e) {
102
- return '';
103
- }
104
- },
105
- set(key, value) {
106
- try {
107
- runtimeUni.setStorageSync(key, value);
108
- } catch (e) {}
109
- },
110
- remove(key) {
111
- try {
112
- runtimeUni.removeStorageSync(key);
113
- } catch (e) {}
114
- }
115
- };
116
- }
117
-
118
- if (hasWindow() && window.localStorage) {
119
- return {
120
- get(key) {
121
- try {
122
- return window.localStorage.getItem(key) || '';
123
- } catch (e) {
124
- return '';
125
- }
126
- },
127
- set(key, value) {
128
- try {
129
- window.localStorage.setItem(key, value);
130
- } catch (e) {}
131
- },
132
- remove(key) {
133
- try {
134
- window.localStorage.removeItem(key);
135
- } catch (e) {}
136
- }
137
- };
138
- }
139
-
140
- return createMemoryStorage();
141
- }
142
-
143
- function serializeUser(value) {
144
- if (!value) return '';
145
- return typeof value === 'string' ? value : JSON.stringify(value);
146
- }
147
-
148
- function deserializeUser(value) {
149
- if (!value) return null;
150
- if (typeof value === 'object') return value;
151
- return parseMaybeJson(value, null);
152
- }
153
-
154
- // 吾码文件字段可能来自上传控件、HDFS 接口、字符串或 JSON 字符串,这里统一抽取可用路径。
155
- function extractUploadPath(value) {
156
- if (!value) return '';
157
- if (typeof value === 'object') {
158
- const raw = Array.isArray(value) ? (value[0] || {}) : value;
159
- if (typeof raw === 'string') return extractUploadPath(raw);
160
- return raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL ||
161
- raw.FullUrl || raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
162
- }
163
-
164
- const text = String(value || '').trim();
165
- if (!text) return '';
166
- if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
167
- return extractUploadPath(parseMaybeJson(text, text));
168
- }
169
- return text;
170
- }
171
-
172
- function normalizeUploadValue(value) {
173
- if (!value) return [];
174
- if (Array.isArray(value)) return value.map(extractUploadPath).filter(Boolean);
175
- if (typeof value === 'object') {
176
- const path = extractUploadPath(value);
177
- return path ? [path] : [];
178
- }
179
-
180
- const text = String(value || '').trim();
181
- if (!text) return [];
182
- if ((text.startsWith('[') && text.endsWith(']')) || (text.startsWith('{') && text.endsWith('}'))) {
183
- const parsed = parseMaybeJson(text, null);
184
- if (Array.isArray(parsed)) return parsed.map(extractUploadPath).filter(Boolean);
185
- const path = extractUploadPath(parsed);
186
- return path ? [path] : [];
187
- }
188
- return [text];
189
- }
190
-
191
- function normalizeUploadData(body) {
192
- const raw = Array.isArray(body && body.Data) ? (body.Data[0] || {}) : ((body && body.Data) || {});
193
- const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || raw.Url || raw.FileUrl || '';
194
- const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || '';
195
- return { ...raw, Path: path, Url: url };
196
- }
197
-
198
- function normalizeClientUploadPath(value) {
199
- let path = String(value || 'upload').trim().replace(/\\/g, '/');
200
- if (/^(https?:|data:|blob:|file:)/i.test(path)) throw new Error('上传路径不合法。');
201
- path = path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/{2,}/g, '/');
202
- if (!path || path.startsWith('~') || path.includes('..') || path.includes(':')) {
203
- throw new Error('上传路径不合法。');
204
- }
205
- const parts = path.split('/').filter(Boolean);
206
- if (!parts.length || parts.some((item) => item === '.' || item === '..')) {
207
- throw new Error('上传路径不合法。');
208
- }
209
- return parts.join('/');
210
- }
211
-
212
- function normalizeFileUrlData(data, assetUrl, fallback = '') {
213
- const raw = Array.isArray(data) ? (data[0] || '') : (data || '');
214
- if (typeof raw === 'string') return assetUrl(raw || fallback);
215
- const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || raw.FullUrl || '';
216
- const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
217
- return assetUrl(url || path || fallback);
218
- }
219
-
220
- function getHeaderValue(headers, key) {
221
- if (!headers) return '';
222
- const lower = key.toLowerCase();
223
- if (typeof headers.get === 'function') {
224
- const value = headers.get(key) || headers.get(lower);
225
- if (value) return value;
226
- }
227
- for (const name of Object.keys(headers)) {
228
- if (String(name).toLowerCase() === lower) return headers[name];
229
- }
230
- return '';
231
- }
232
-
233
- function setSingletonHeader(headers, key, value) {
234
- const lower = String(key).toLowerCase();
235
- Object.keys(headers).forEach((name) => {
236
- if (String(name).toLowerCase() === lower) delete headers[name];
237
- });
238
- if (value !== undefined && value !== null && value !== '') headers[key] = value;
239
- }
240
-
241
- function normalizeBearer(value) {
242
- const text = String(value || '').trim();
243
- return /^Bearer\s+/i.test(text) ? text.replace(/^Bearer\s+/i, '') : text;
244
- }
245
-
246
- // 兼容浏览器上传对象、组件包装对象和 uni-app 临时文件路径。
247
- function isUploadFileLike(value) {
248
- if (!value) return false;
249
- if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
250
- return typeof value.arrayBuffer === 'function';
251
- }
252
-
253
- function pickUploadFileLike(value) {
254
- if (!value) return null;
255
- if (isUploadFileLike(value)) return value;
256
- if (typeof value !== 'object') return null;
257
- const keys = ['file', 'raw', 'blob', 'originFileObj', 'tempFile', 'data'];
258
- for (const key of keys) {
259
- const picked = pickUploadFileLike(value[key]);
260
- if (picked) return picked;
261
- }
262
- return null;
263
- }
264
-
265
- function pickUploadFileName(value) {
266
- if (!value) return '';
267
- if (typeof value === 'object') {
268
- if (value.name) return String(value.name);
269
- const keys = ['file', 'raw', 'blob', 'originFileObj', 'tempFile', 'data'];
270
- for (const key of keys) {
271
- const name = pickUploadFileName(value[key]);
272
- if (name) return name;
273
- }
274
- const path = value.path || value.tempFilePath || value.url || value.src || value.localUrl || value.fullPath || '';
275
- if (path) return inferUploadFileName(path);
276
- }
277
- return '';
278
- }
279
-
280
- function inferUploadFileName(value) {
281
- const text = String(value || '').split('?')[0].split('#')[0];
282
- const name = decodeURIComponent((text.split('/').pop() || '').trim());
283
- return name && name.indexOf(':') < 0 ? name : '';
284
- }
285
-
286
- function pickUploadFileSource(filePath, options = {}) {
287
- const candidates = [options.file, filePath];
288
- for (const item of candidates) {
289
- if (!item) continue;
290
- if (typeof item === 'string') return item;
291
- if (typeof item === 'object') {
292
- const path = item.path || item.tempFilePath || item.url || item.src || item.localUrl || item.fullPath || '';
293
- if (path) return String(path);
294
- }
295
- }
296
- return '';
297
- }
298
-
299
- async function resolveFetchUploadFile(filePath, options = {}) {
300
- const direct = pickUploadFileLike(options.file) || pickUploadFileLike(filePath);
301
- const name = options.fileName || pickUploadFileName(options.file) || pickUploadFileName(filePath) || inferUploadFileName(filePath) || 'file';
302
- if (direct) return { file: direct, name };
303
-
304
- const source = pickUploadFileSource(filePath, options);
305
- if (source && typeof fetch === 'function' && /^(blob:|data:)/i.test(source)) {
306
- const res = await fetch(source);
307
- const blob = await res.blob();
308
- return { file: blob, name };
309
- }
310
- return { file: null, name };
311
- }
312
-
313
- // 控制接口并发,适合列表页批量请求时给后端和小程序运行时减压。
314
- function createQueue(maxConcurrent) {
315
- const limit = Number(maxConcurrent || 0);
316
- if (!limit || limit <= 0) {
317
- return async function runNow(task) {
318
- return task();
319
- };
320
- }
321
-
322
- let active = 0;
323
- const waiting = [];
324
- function release() {
325
- if (waiting.length) {
326
- const next = waiting.shift();
327
- active += 1;
328
- next();
329
- } else {
330
- active = Math.max(0, active - 1);
331
- }
332
- }
333
-
334
- return function runQueued(task) {
335
- return new Promise((resolve, reject) => {
336
- const start = () => {
337
- Promise.resolve()
338
- .then(task)
339
- .then(resolve, reject)
340
- .finally(release);
341
- };
342
- if (active < limit) {
343
- active += 1;
344
- start();
345
- } else {
346
- waiting.push(start);
347
- }
348
- });
349
- };
350
- }
351
-
352
- function defaultToast(message) {
353
- const runtimeUni = getUni();
354
- if (runtimeUni && typeof runtimeUni.showToast === 'function') {
355
- runtimeUni.showToast({ title: String(message || ''), icon: 'none' });
356
- return;
357
- }
358
- if (hasWindow() && typeof window.alert === 'function') window.alert(String(message || ''));
359
- }
360
-
361
- function defaultConfirm(message) {
362
- const runtimeUni = getUni();
363
- if (runtimeUni && typeof runtimeUni.showModal === 'function') {
364
- return new Promise((resolve) => {
365
- runtimeUni.showModal({
366
- title: '',
367
- content: String(message || ''),
368
- success: (res) => resolve(!!res.confirm),
369
- fail: () => resolve(false)
370
- });
371
- });
372
- }
373
- if (hasWindow() && typeof window.confirm === 'function') return Promise.resolve(window.confirm(String(message || '')));
374
- return Promise.resolve(true);
375
- }
376
-
377
- // fetch 的超时需要 AbortController;不支持时由运行时自身处理。
378
- function createFetchTimeout(timeout) {
379
- if (typeof AbortController === 'undefined') return {};
380
- const controller = new AbortController();
381
- const timer = setTimeout(() => controller.abort(), Number(timeout || 30000));
382
- return { signal: controller.signal, cleanup: () => clearTimeout(timer) };
383
- }
384
-
385
- function getSafeArea() {
386
- const runtimeUni = getUni();
387
- if (runtimeUni && typeof runtimeUni.getSystemInfoSync === 'function') {
388
- try {
389
- const info = runtimeUni.getSystemInfoSync();
390
- const insets = info.safeAreaInsets || {};
391
- const safeArea = info.safeArea || {};
392
- return {
393
- top: Number(insets.top || safeArea.top || info.statusBarHeight || 0),
394
- bottom: Number(insets.bottom || 0),
395
- left: Number(insets.left || 0),
396
- right: Number(insets.right || 0),
397
- statusBarHeight: Number(info.statusBarHeight || 0),
398
- windowHeight: Number(info.windowHeight || 0),
399
- windowWidth: Number(info.windowWidth || 0),
400
- platform: info.platform || ''
401
- };
402
- } catch (e) {}
403
- }
404
- return { top: 0, bottom: 0, left: 0, right: 0, statusBarHeight: 0, windowHeight: 0, windowWidth: 0, platform: '' };
405
- }
406
-
407
- // 常用日期、数字与显示格式化,兼容旧版前端 V8 写法。
408
- function formatDate(value, format = 'yyyy-MM-dd HH:mm:ss') {
409
- const date = value instanceof Date ? value : new Date(value || Date.now());
410
- if (Number.isNaN(date.getTime())) return '';
411
- const pad = (num, len = 2) => String(num).padStart(len, '0');
412
- const map = {
413
- yyyy: date.getFullYear(),
414
- MM: pad(date.getMonth() + 1),
415
- dd: pad(date.getDate()),
416
- HH: pad(date.getHours()),
417
- mm: pad(date.getMinutes()),
418
- ss: pad(date.getSeconds()),
419
- SSS: pad(date.getMilliseconds(), 3)
420
- };
421
- return Object.keys(map).reduce((text, key) => text.replace(new RegExp(key, 'g'), map[key]), format);
422
- }
423
-
424
- function toNumber(value, fallback = 0) {
425
- const num = Number(value);
426
- return Number.isFinite(num) ? num : fallback;
427
- }
428
-
429
- function maskPhone(value) {
430
- const text = String(value || '');
431
- return text.length >= 7 ? `${text.slice(0, 3)}****${text.slice(-4)}` : text;
432
- }
433
-
434
- function formatCompactNumber(value, digits = 2) {
435
- const num = toNumber(value, 0);
436
- const abs = Math.abs(num);
437
- if (abs >= 100000000) return `${(num / 100000000).toFixed(digits).replace(/\.?0+$/, '')}亿`;
438
- if (abs >= 10000) return `${(num / 10000).toFixed(digits).replace(/\.?0+$/, '')}万`;
439
- return `${num}`;
440
- }
441
-
442
- function addTime(value, unit, number) {
443
- const date = value instanceof Date ? new Date(value.getTime()) : new Date(value || Date.now());
444
- const amount = Number(number || 0);
445
- switch (unit) {
446
- case 's':
447
- date.setSeconds(date.getSeconds() + amount);
448
- break;
449
- case 'n':
450
- case 'm':
451
- date.setMinutes(date.getMinutes() + amount);
452
- break;
453
- case 'h':
454
- date.setHours(date.getHours() + amount);
455
- break;
456
- case 'd':
457
- date.setDate(date.getDate() + amount);
458
- break;
459
- case 'w':
460
- date.setDate(date.getDate() + amount * 7);
461
- break;
462
- case 'q':
463
- date.setMonth(date.getMonth() + amount * 3);
464
- break;
465
- case 'M':
466
- date.setMonth(date.getMonth() + amount);
467
- break;
468
- case 'y':
469
- date.setFullYear(date.getFullYear() + amount);
470
- break;
471
- default:
472
- date.setMilliseconds(date.getMilliseconds() + amount);
473
- break;
474
- }
475
- return date;
476
- }
477
-
478
- function diffTime(value, unit, value2) {
479
- const d1 = value instanceof Date ? value : new Date(value);
480
- const d2 = value2 instanceof Date ? value2 : new Date(value2 || Date.now());
481
- const t1 = d1.getTime();
482
- const t2 = d2.getTime();
483
- const year = d2.getFullYear() - d1.getFullYear();
484
- const map = {
485
- y: year,
486
- q: year * 4 + Math.floor(d2.getMonth() / 4) - Math.floor(d1.getMonth() / 4),
487
- M: year * 12 + d2.getMonth() - d1.getMonth(),
488
- m: year * 12 + d2.getMonth() - d1.getMonth(),
489
- ms: t2 - t1,
490
- w: Math.floor((t2 + 345600000) / 604800000) - Math.floor((t1 + 345600000) / 604800000),
491
- d: Math.floor(t2 / 86400000) - Math.floor(t1 / 86400000),
492
- h: Math.floor(t2 / 3600000) - Math.floor(t1 / 3600000),
493
- n: Math.floor(t2 / 60000) - Math.floor(t1 / 60000),
494
- s: Math.floor(t2 / 1000) - Math.floor(t1 / 1000)
495
- };
496
- return map[unit];
497
- }
498
-
499
- // 老项目里常用 Date.prototype.Format/AddTime/DiffTime,这里只在缺失时补齐。
500
- function installDatePrototypeCompat() {
501
- if (typeof Date === 'undefined' || !Date.prototype) return;
502
- if (typeof Date.prototype.Format !== 'function') {
503
- Object.defineProperty(Date.prototype, 'Format', {
504
- configurable: true,
505
- writable: true,
506
- value(format) {
507
- return format ? formatDate(this, format) : this;
508
- }
509
- });
510
- }
511
- if (typeof Date.prototype.AddTime !== 'function') {
512
- Object.defineProperty(Date.prototype, 'AddTime', {
513
- configurable: true,
514
- writable: true,
515
- value(unit, number) {
516
- return addTime(this, unit, number);
517
- }
518
- });
519
- }
520
- if (typeof Date.prototype.DiffTime !== 'function') {
521
- Object.defineProperty(Date.prototype, 'DiffTime', {
522
- configurable: true,
523
- writable: true,
524
- value(unit, time2) {
525
- return diffTime(this, unit, time2);
526
- }
527
- });
528
- }
529
- }
530
-
531
- export function createMicroiV8(options = {}) {
532
- // 运行时配置可通过 createMicroiV8(options) 或 client.configure(next) 覆盖。
533
- let config = {
534
- apiBase: '',
535
- webBase: '',
536
- fileServer: '',
537
- osClient: '',
538
- token: '',
539
- clientType: getUni() ? 'Mobile' : 'PC',
540
- did: '',
541
- didKey: 'microi_did',
542
- tokenKey: 'microi_token',
543
- userKey: 'microi_user',
544
- loginUrl: '',
545
- formQueryEngineKey: '',
546
- timeout: 30000,
547
- maxConcurrent: 0,
548
- appendOsClientQuery: false,
549
- authCodes: DEFAULT_AUTH_CODES,
550
- blockedAssetPattern: DEFAULT_BLOCKED_ASSET,
551
- translate: (message) => message,
552
- requestAdapter: null,
553
- onAuthExpired: null,
554
- onTokenChanged: null,
555
- toast: null,
556
- confirm: null,
557
- ...options
558
- };
559
-
560
- const storage = options.storage || createDefaultStorage();
561
- let runQueued = createQueue(config.maxConcurrent);
562
- let refreshTokenPromise = null;
563
- let tokenMaintenanceTimer = null;
564
- let stopBrowserResumeListeners = null;
565
-
566
- // 更新配置后立即刷新并发队列,保证 maxConcurrent 热更新生效。
567
- function configure(next = {}) {
568
- config = { ...config, ...next };
569
- if (Object.prototype.hasOwnProperty.call(next, 'maxConcurrent')) {
570
- runQueued = createQueue(config.maxConcurrent);
571
- }
572
- return client;
573
- }
574
-
575
- function tr(message) {
576
- try {
577
- return config.translate ? config.translate(message) : message;
578
- } catch (e) {
579
- return message;
580
- }
581
- }
582
-
583
- function toast(message) {
584
- if (!message) return;
585
- const text = tr(message);
586
- if (typeof config.toast === 'function') return config.toast(text);
587
- return defaultToast(text);
588
- }
589
-
590
- function confirm(message) {
591
- if (typeof config.confirm === 'function') return config.confirm(tr(message));
592
- return defaultConfirm(tr(message));
593
- }
594
-
595
- function getToken() {
596
- return config.token || storage.get(config.tokenKey) || '';
597
- }
598
-
599
- function getDid() {
600
- if (config.did) return String(config.did);
601
- const stored = storage.get(config.didKey);
602
- if (stored) return String(stored);
603
- const prefix = String(config.clientType || 'Client').replace(/[^a-z0-9_-]/gi, '') || 'Client';
604
- const random = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
605
- ? crypto.randomUUID()
606
- : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
607
- const did = `${prefix}:${random}`;
608
- storage.set(config.didKey, did);
609
- return did;
610
- }
611
-
612
- function setToken(token) {
613
- const previousToken = getToken();
614
- const nextToken = token || '';
615
- config = { ...config, token: nextToken };
616
- storage.set(config.tokenKey, nextToken);
617
- if (nextToken !== previousToken && typeof config.onTokenChanged === 'function') {
618
- try {
619
- config.onTokenChanged(nextToken, previousToken, client);
620
- } catch (e) {}
621
- }
622
- }
623
-
624
- function clearToken() {
625
- config = { ...config, token: '' };
626
- storage.remove(config.tokenKey);
627
- storage.remove(config.userKey);
628
- }
629
-
630
- function setUser(user) {
631
- storage.set(config.userKey, serializeUser(user));
632
- }
633
-
634
- function getUser() {
635
- return deserializeUser(storage.get(config.userKey));
636
- }
637
-
638
- function isAuthExpired(body, statusCode) {
639
- if (Number(statusCode) === 401) return true;
640
- const code = body && body.Code;
641
- return config.authCodes.indexOf(code) >= 0;
642
- }
643
-
644
- function readTokenClaims(token = getToken()) {
645
- try {
646
- const normalized = normalizeBearer(token);
647
- const parts = normalized.split('.');
648
- if (parts.length < 2) return null;
649
- const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
650
- if (typeof Buffer !== 'undefined') {
651
- return JSON.parse(Buffer.from(payload, 'base64').toString('utf-8'));
652
- }
653
- const padded = payload + '='.repeat((4 - payload.length % 4) % 4);
654
- const binary = atob(padded);
655
- const bytes = Array.from(binary, (char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`).join('');
656
- return JSON.parse(decodeURIComponent(bytes));
657
- } catch (e) {
658
- return null;
659
- }
660
- }
661
-
662
- function shouldRefreshToken(token = getToken()) {
663
- const claims = readTokenClaims(token);
664
- const expiresAt = Number(claims && claims.exp);
665
- if (!Number.isFinite(expiresAt) || expiresAt <= 0) return true;
666
- const issuedAt = Number(claims.MicroiTokenIssuedAt || claims.iat);
667
- const now = Math.floor(Date.now() / 1000);
668
- const lifetime = Number.isFinite(issuedAt) && issuedAt > 0 && expiresAt > issuedAt
669
- ? expiresAt - issuedAt
670
- : Math.max(0, expiresAt - now);
671
- const lead = Math.min(24 * 60 * 60, Math.max(5 * 60, Math.floor(lifetime / 10)));
672
- return expiresAt - now <= lead;
673
- }
674
-
675
- function handleReturnedToken(headers) {
676
- const auth = getHeaderValue(headers, 'authorization') || getHeaderValue(headers, 'token');
677
- const token = normalizeBearer(auth);
678
- if (token) setToken(token);
679
- }
680
-
681
- function handleAuthExpired(body) {
682
- clearToken();
683
- if (typeof config.onAuthExpired === 'function') {
684
- config.onAuthExpired(body, client);
685
- }
686
- }
687
-
688
- // 所有相对地址默认走 apiBase,必要时自动追加 OsClient。
689
- function buildUrl(url) {
690
- let fullUrl = /^(https?:|data:|blob:|file:)/i.test(String(url || '')) ? String(url) : joinUrl(config.apiBase, url);
691
- if (config.appendOsClientQuery && config.osClient && fullUrl.indexOf('/apiengine/') < 0) {
692
- fullUrl = appendQuery(fullUrl, 'OsClient', config.osClient);
693
- }
694
- return fullUrl;
695
- }
696
-
697
- // 普通请求统一携带 osclient、Token 与 Authorization,减少各项目重复拼装。
698
- function buildHeaders(options = {}) {
699
- const token = options.auth === false ? '' : getToken();
700
- const headers = {
701
- 'Content-Type': 'application/json',
702
- ...(options.header || {}),
703
- ...(options.headers || {})
704
- };
705
- if (config.osClient) setSingletonHeader(headers, 'osclient', config.osClient);
706
- const did = getDid();
707
- if (did) setSingletonHeader(headers, 'did', did);
708
- if (token) {
709
- setSingletonHeader(headers, 'Token', token);
710
- setSingletonHeader(headers, 'Authorization', `Bearer ${token}`);
711
- }
712
- if (options.apiEngine) headers.apiengine = '1';
713
- return headers;
714
- }
715
-
716
- function buildUploadHeaders(options = {}) {
717
- const headers = buildHeaders(options);
718
- Object.keys(headers).forEach((key) => {
719
- if (String(key).toLowerCase() === 'content-type') delete headers[key];
720
- });
721
- return headers;
722
- }
723
-
724
- // 请求核心:优先走自定义适配器,其次 uni.request,最后回退 fetch。
725
- async function request(options = {}) {
726
- const method = String(options.method || 'POST').toUpperCase();
727
- let fullUrl = buildUrl(options.url || options.path || '');
728
- const headers = buildHeaders(options);
729
- const data = options.data === undefined ? {} : options.data;
730
- const timeout = options.timeout || config.timeout;
731
-
732
- const perform = async () => {
733
- let response;
734
- if (typeof config.requestAdapter === 'function') {
735
- response = await config.requestAdapter({ ...options, url: fullUrl, method, data, header: headers, headers, timeout });
736
- } else {
737
- const runtimeUni = getUni();
738
- if (runtimeUni && typeof runtimeUni.request === 'function') {
739
- response = await new Promise((resolve, reject) => {
740
- runtimeUni.request({
741
- url: fullUrl,
742
- method,
743
- data,
744
- header: headers,
745
- timeout,
746
- success: resolve,
747
- fail: reject
748
- });
749
- });
750
- } else if (typeof fetch === 'function') {
751
- if ((method === 'GET' || method === 'HEAD') && data && typeof data === 'object') {
752
- fullUrl = appendQueryObject(fullUrl, data);
753
- }
754
- const timer = createFetchTimeout(timeout);
755
- try {
756
- const fetchOptions = {
757
- method,
758
- headers,
759
- signal: timer.signal
760
- };
761
- if (method !== 'GET' && method !== 'HEAD') fetchOptions.body = typeof data === 'string' ? data : JSON.stringify(data || {});
762
- const res = await fetch(fullUrl, fetchOptions);
763
- const text = await res.text();
764
- const resultData = parseMaybeJson(text, text);
765
- const resultHeaders = {};
766
- res.headers.forEach((value, key) => { resultHeaders[key] = value; });
767
- response = { statusCode: res.status, data: resultData, header: resultHeaders, headers: resultHeaders };
768
- } finally {
769
- if (typeof timer.cleanup === 'function') timer.cleanup();
770
- }
771
- } else {
772
- throw new Error('未找到 MicroiV8 请求适配器。');
773
- }
774
- }
775
-
776
- const statusCode = response.statusCode || response.status || 200;
777
- const body = response.data === undefined ? response.body : response.data;
778
- const headersReturned = response.header || response.headers || {};
779
- handleReturnedToken(headersReturned);
780
-
781
- if (options.auth !== false && isAuthExpired(body, statusCode)) {
782
- handleAuthExpired(body);
783
- if (options.silentError !== true) toast((body && body.Msg) || '登录已过期');
784
- throw body || new Error('登录已过期');
785
- }
786
-
787
- if (statusCode >= 400) {
788
- const error = body || new Error(`请求失败: ${statusCode}`);
789
- if (options.silentError !== true) toast((body && body.Msg) || `请求失败: ${statusCode}`);
790
- throw error;
791
- }
792
-
793
- if (options.checkCode && body && body.Code !== 1) {
794
- if (options.silentError !== true) toast(body.Msg || '请求失败');
795
- throw body;
796
- }
797
-
798
- return body;
799
- };
800
-
801
- return runQueued(perform);
802
- }
803
-
804
- function get(url, data = {}, options = {}) {
805
- return request({ ...options, url, data, method: 'GET' });
806
- }
807
-
808
- function post(url, data = {}, options = {}) {
809
- return request({ ...options, url, data, method: 'POST' });
810
- }
811
-
812
- function postForm(url, data = {}, options = {}) {
813
- const body = new URLSearchParams();
814
- const formData = config.osClient && data.OsClient === undefined
815
- ? { OsClient: config.osClient, ...data }
816
- : data;
817
- Object.keys(formData || {}).forEach((key) => {
818
- const value = formData[key];
819
- if (value !== undefined && value !== null) body.set(key, String(value));
820
- });
821
- return request({
822
- ...options,
823
- url,
824
- data: body.toString(),
825
- method: 'POST',
826
- headers: {
827
- 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
828
- ...(options.headers || {})
829
- }
830
- });
831
- }
832
-
833
- async function refreshToken() {
834
- if (refreshTokenPromise) return refreshTokenPromise;
835
- const oldToken = getToken();
836
- if (!oldToken) return { Code: 1001, Msg: '请求未携带Token,请重新登录。' };
837
-
838
- refreshTokenPromise = request({
839
- url: '/api/SysUser/refreshToken',
840
- method: 'POST',
841
- auth: false,
842
- checkCode: false,
843
- silentError: true,
844
- headers: {
845
- Authorization: `Bearer ${normalizeBearer(oldToken)}`,
846
- Token: normalizeBearer(oldToken)
847
- },
848
- data: {
849
- authorization: normalizeBearer(oldToken),
850
- OsClient: config.osClient || undefined,
851
- _ClientType: config.clientType || undefined
852
- }
853
- }).then((result) => {
854
- if (result && result.Code !== 1 && isAuthExpired(result)) {
855
- handleAuthExpired(result);
856
- }
857
- return result;
858
- }).finally(() => {
859
- refreshTokenPromise = null;
860
- });
861
- return refreshTokenPromise;
862
- }
863
-
864
- async function resumeAuthSession(force = false) {
865
- const token = getToken();
866
- if (!token) return { Code: 1001, Msg: '请求未携带Token,请重新登录。' };
867
- if (!force && !shouldRefreshToken(token)) return { Code: 1, Data: { Refreshed: false } };
868
- return refreshToken();
869
- }
870
-
871
- function stopTokenMaintenance() {
872
- if (tokenMaintenanceTimer) {
873
- clearInterval(tokenMaintenanceTimer);
874
- tokenMaintenanceTimer = null;
875
- }
876
- if (typeof stopBrowserResumeListeners === 'function') {
877
- stopBrowserResumeListeners();
878
- stopBrowserResumeListeners = null;
879
- }
880
- }
881
-
882
- function startTokenMaintenance(options = {}) {
883
- stopTokenMaintenance();
884
- const intervalMs = Math.max(60 * 1000, Number(options.intervalMs || 60 * 1000));
885
- const maintain = () => { void resumeAuthSession(false); };
886
- tokenMaintenanceTimer = setInterval(maintain, intervalMs);
887
- if (hasWindow()) {
888
- const onResume = () => {
889
- if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
890
- maintain();
891
- };
892
- document.addEventListener('visibilitychange', onResume);
893
- window.addEventListener('focus', onResume);
894
- window.addEventListener('pageshow', onResume);
895
- stopBrowserResumeListeners = () => {
896
- document.removeEventListener('visibilitychange', onResume);
897
- window.removeEventListener('focus', onResume);
898
- window.removeEventListener('pageshow', onResume);
899
- };
900
- }
901
- maintain();
902
- return stopTokenMaintenance;
903
- }
904
-
905
- // 资源地址统一过滤占位图,并兼容 HDFS 私有文件、FileServer 和绝对地址。
906
- function assetUrl(value) {
907
- const picked = extractUploadPath(value);
908
- if (!picked || isBlockedAsset(picked)) return '';
909
- if (/^(https?:|data:|blob:|file:)/i.test(picked)) return picked;
910
- if (/^\/?file\//i.test(picked)) return joinUrl(config.apiBase, picked);
911
- if (/^\//.test(picked) || /^[a-z0-9_-]+\//i.test(picked)) return joinUrl(config.fileServer || config.apiBase, picked);
912
- return picked;
913
- }
914
-
915
- function isBlockedAsset(value) {
916
- return config.blockedAssetPattern ? config.blockedAssetPattern.test(String(value || '')) : false;
917
- }
918
-
919
- async function resolveFileUrl(filePathName) {
920
- const path = extractUploadPath(filePathName);
921
- if (!path || isBlockedAsset(path)) return '';
922
- if (/^(https?:|blob:|data:|file:)/i.test(path)) return assetUrl(path);
923
-
924
- async function requestPrivate(action) {
925
- try {
926
- const body = await post(`/api/HDFS/${action}?FilePathName=${encodeURIComponent(path)}`, { OsClient: config.osClient }, {
927
- checkCode: false,
928
- silentError: true
929
- });
930
- if (body && body.Code === 1 && body.Data) return normalizeFileUrlData(body.Data, assetUrl, path);
931
- } catch (e) {}
932
- return '';
933
- }
934
-
935
- return (await requestPrivate('GetPrivateFileUrl')) || (await requestPrivate('MallFileUrl')) || assetUrl(path);
936
- }
937
-
938
- // 文件上传同时支持 uni.uploadFile 与浏览器 fetch/FormData。
939
- async function uploadFile(filePath, options = {}) {
940
- const runtimeUni = getUni();
941
- const action = options.action || (options.anonymous ? 'UniappUploadAnonymous' : 'UniappUpload');
942
- const rawFormData = options.formData || {};
943
- const uploadData = {
944
- ...rawFormData,
945
- OsClient: config.osClient,
946
- Limit: options.limit === false ? 'false' : 'true',
947
- Preview: options.preview === false ? 'false' : 'true',
948
- Multiple: options.multiple ? 'true' : 'false'
949
- };
950
- uploadData.Path = normalizeClientUploadPath(options.path || uploadData.Path || uploadData.path || 'upload');
951
- delete uploadData.path;
952
-
953
- let body;
954
- const fetchSource = pickUploadFileSource(filePath, options);
955
- const canFetchUpload = typeof fetch === 'function' && typeof FormData !== 'undefined' &&
956
- (!!pickUploadFileLike(options.file) || !!pickUploadFileLike(filePath) || /^(blob:|data:)/i.test(fetchSource));
957
- const uploadByFetch = async () => {
958
- const picked = await resolveFetchUploadFile(filePath, options);
959
- const file = picked.file;
960
- if (!file) throw new Error('未提供上传文件。');
961
- const formData = new FormData();
962
- Object.keys(uploadData).forEach((key) => formData.append(key, uploadData[key]));
963
- formData.append(options.name || 'file', file, picked.name || (file && file.name) || 'file');
964
- const res = await fetch(buildUrl(options.url || `/api/HDFS/${action}`), {
965
- method: 'POST',
966
- headers: buildUploadHeaders({ ...options, headers: options.headers || {} }),
967
- body: formData
968
- });
969
- handleReturnedToken(res.headers);
970
- const text = await res.text();
971
- return parseMaybeJson(text, text);
972
- };
973
-
974
- if (options.preferFetch === true && canFetchUpload) {
975
- try {
976
- body = await uploadByFetch();
977
- } catch (e) {
978
- if (!(runtimeUni && typeof runtimeUni.uploadFile === 'function')) throw e;
979
- }
980
- }
981
- if (!body) {
982
- if (runtimeUni && typeof runtimeUni.uploadFile === 'function') {
983
- try {
984
- body = await new Promise((resolve, reject) => {
985
- runtimeUni.uploadFile({
986
- url: buildUrl(options.url || `/api/HDFS/${action}`),
987
- filePath,
988
- name: options.name || 'file',
989
- header: buildUploadHeaders({ ...options, headers: options.headers || {} }),
990
- formData: uploadData,
991
- success: (res) => {
992
- handleReturnedToken(res.header || res.headers || {});
993
- resolve(parseMaybeJson(res.data, res.data));
994
- },
995
- fail: reject
996
- });
997
- });
998
- } catch (e) {
999
- if (!canFetchUpload) throw e;
1000
- body = await uploadByFetch();
1001
- }
1002
- } else if (canFetchUpload) {
1003
- body = await uploadByFetch();
1004
- } else {
1005
- throw new Error('未找到 MicroiV8 上传适配器。');
1006
- }
1007
- }
1008
-
1009
- if (!body || body.Code !== 1) {
1010
- if (options.silentError !== true) toast((body && body.Msg) || '上传失败');
1011
- throw body || new Error('上传失败');
1012
- }
1013
-
1014
- const data = normalizeUploadData(body);
1015
- if (!data.Path) {
1016
- const error = { Code: 0, Msg: '上传返回文件路径为空' };
1017
- if (options.silentError !== true) toast(error.Msg);
1018
- throw error;
1019
- }
1020
- if (!data.Url && options.resolveUrl !== false) data.Url = await resolveFileUrl(data.Path);
1021
- return { ...body, Data: data };
1022
- }
1023
-
1024
- function apiEngineRun(key, data = {}, options = {}) {
1025
- const body = { ...(data || {}) };
1026
- if (config.osClient && body.OsClient === undefined) body.OsClient = config.osClient;
1027
- return post(`/apiengine/${key}`, body, { apiEngine: true, checkCode: options.checkCode !== false, ...options });
1028
- }
1029
-
1030
- function apiEngineRunLegacy(key, data = {}, options = {}) {
1031
- const body = { ApiEngineKey: key, OsClient: config.osClient, ...(data || {}) };
1032
- return post('/api/ApiEngine/Run', body, { checkCode: false, ...options });
1033
- }
1034
-
1035
- function formEngineRequest(action, table, data = {}, options = {}) {
1036
- const actionKey = String(action || '').toLowerCase();
1037
- const readActions = ['gettabledata', 'getformdata', 'gettabledatatree'];
1038
- const isRead = readActions.indexOf(actionKey) >= 0;
1039
- const body = { OsClient: config.osClient, FormEngineKey: table, ...(data || {}) };
1040
-
1041
- if (isRead && config.formQueryEngineKey && options.readUseQueryEngine !== false) {
1042
- return apiEngineRun(config.formQueryEngineKey, { Action: actionKey, ...body }, { checkCode: false, ...options });
1043
- }
1044
-
1045
- return post(`/api/formengine/${actionKey}-${table}`, body, { checkCode: false, ...options });
1046
- }
1047
-
1048
- function formEngineAnonymous(action, table, data = {}, options = {}) {
1049
- const name = String(action || '');
1050
- const body = { FormEngineKey: table, OsClient: config.osClient, ...(data || {}) };
1051
- return post(`/api/FormEngine/${name}`, body, { auth: false, checkCode: false, ...options });
1052
- }
1053
-
1054
- function withCallback(promise, callback) {
1055
- if (typeof callback === 'function') {
1056
- promise.then((result) => callback(result)).catch((error) => callback(error));
1057
- }
1058
- return promise;
1059
- }
1060
-
1061
- // 兼容旧版 FormEngine 调用:既支持 (table, row, callback),也支持完整参数对象。
1062
- function normalizeLegacyFormArgs(first, second, third, rowModelMode = false) {
1063
- let data = {};
1064
- let callback = third;
1065
- if (typeof first === 'string') {
1066
- const source = second && typeof second === 'object' ? second : {};
1067
- data.FormEngineKey = first;
1068
- if (rowModelMode) {
1069
- data._RowModel = {};
1070
- Object.keys(source).forEach((key) => {
1071
- if (key === 'Id') data.Id = source[key];
1072
- else data._RowModel[key] = source[key];
1073
- });
1074
- } else {
1075
- data = { ...data, ...source };
1076
- }
1077
- if (typeof second === 'function') callback = second;
1078
- } else {
1079
- data = first && typeof first === 'object' ? { ...first } : {};
1080
- callback = typeof second === 'function' ? second : third;
1081
- }
1082
- if (config.osClient && data.OsClient === undefined) data.OsClient = config.osClient;
1083
- return { data, callback };
1084
- }
1085
-
1086
- async function legacyPost(url, data = {}, callback, option = {}) {
1087
- const body = await request({
1088
- url,
1089
- data: data || {},
1090
- method: option.Method || 'POST',
1091
- headers: option.Header || option.Headers || {},
1092
- apiEngine: !!option.IsApiEngine,
1093
- auth: option.Auth !== false,
1094
- timeout: option.Timeout,
1095
- checkCode: false,
1096
- silentError: option.SilentError === true
1097
- });
1098
- const result = body && typeof body === 'object' ? { ...body, Headers: body.Headers || {} } : body;
1099
- if (typeof callback === 'function') callback(result, result && result.Headers);
1100
- return result;
1101
- }
1102
-
1103
- async function legacyGet(url, data = {}, callback, option = {}) {
1104
- const body = await request({
1105
- url,
1106
- data: data || {},
1107
- method: 'GET',
1108
- headers: option.Header || option.Headers || {},
1109
- apiEngine: !!option.IsApiEngine,
1110
- auth: option.Auth !== false,
1111
- timeout: option.Timeout,
1112
- responseType: option.ResponseType,
1113
- checkCode: false,
1114
- silentError: option.SilentError === true
1115
- });
1116
- const result = option.ResponseType === 'arraybuffer'
1117
- ? { Code: 1, Data: body, Headers: {} }
1118
- : (body && typeof body === 'object' ? { ...body, Headers: body.Headers || {} } : body);
1119
- if (typeof callback === 'function') callback(result, result && result.Headers);
1120
- return result;
1121
- }
1122
-
1123
- async function legacyRawRequest(param = {}) {
1124
- const method = param.Method || param.method || 'POST';
1125
- const url = param.Url || param.url || param.path || '';
1126
- const data = param.Data || param.Param || param.data || {};
1127
- const body = await request({
1128
- url,
1129
- data,
1130
- method,
1131
- headers: param.Header || param.Headers || param.headers || {},
1132
- apiEngine: !!param.IsApiEngine,
1133
- auth: param.Auth !== false,
1134
- timeout: param.Timeout,
1135
- responseType: param.ResponseType,
1136
- checkCode: false,
1137
- silentError: param.SilentError === true
1138
- });
1139
- return { data: body, headers: body && body.Headers ? body.Headers : {} };
1140
- }
1141
-
1142
- function legacyOpen(url) {
1143
- const runtimeUni = getUni();
1144
- if (runtimeUni && typeof runtimeUni.navigateTo === 'function') {
1145
- runtimeUni.navigateTo({ url });
1146
- return;
1147
- }
1148
- if (hasWindow()) window.location.href = url;
1149
- }
1150
-
1151
- function legacyNavigateTo(url, isVerify) {
1152
- if (isVerify && !legacyIsLogin()) {
1153
- if (config.loginUrl) legacyOpen(config.loginUrl);
1154
- else toast('请登录');
1155
- return;
1156
- }
1157
- legacyOpen(url);
1158
- }
1159
-
1160
- function legacyGetCurrentUser(refresh, callback) {
1161
- if (refresh) {
1162
- legacyPost('/api/SysUser/getCurrentUser', {}, (result) => {
1163
- if (result && result.Code) legacySetCurrentUser(result.Data || {});
1164
- if (typeof callback === 'function') callback(result);
1165
- });
1166
- }
1167
- return getUser() || deserializeUser(storage.get('CurrentUser')) || {};
1168
- }
1169
-
1170
- function legacySetCurrentUser(user) {
1171
- setUser(user || {});
1172
- storage.set('CurrentUser', serializeUser(user || {}));
1173
- }
1174
-
1175
- function legacyGetToken() {
1176
- return getToken() || storage.get('Token') || storage.get('authorization') || '';
1177
- }
1178
-
1179
- function legacySetToken(token) {
1180
- setToken(token || '');
1181
- storage.set('Token', token || '');
1182
- storage.set('authorization', token || '');
1183
- storage.set('TokenExpires', token ? formatDate(addTime(new Date(), 'm', 15), 'yyyy-MM-dd HH:mm:ss') : '');
1184
- if (!token) legacySetCurrentUser({});
1185
- }
1186
-
1187
- function legacyIsLogin() {
1188
- const user = legacyGetCurrentUser();
1189
- return !!(legacyGetToken() && user && user.Id);
1190
- }
1191
-
1192
- function legacyGetUrlQuery(property, pageInstance) {
1193
- let query = null;
1194
- if (pageInstance) {
1195
- query = (pageInstance.$mp && pageInstance.$mp.query) ||
1196
- (pageInstance.$scope && pageInstance.$scope.options) ||
1197
- (pageInstance.$page && pageInstance.$page.options) ||
1198
- (pageInstance.$options && pageInstance.$options.pageQuery) ||
1199
- null;
1200
- }
1201
- if (!query && hasWindow()) {
1202
- query = {};
1203
- const params = new URLSearchParams(window.location.search || '');
1204
- params.forEach((value, key) => { query[key] = value; });
1205
- }
1206
- return property ? (query && query[property]) : query;
1207
- }
1208
-
1209
- function legacyGetStrLength(value) {
1210
- const text = String(value || '');
1211
- const chinese = text.match(/[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/g);
1212
- return (chinese ? chinese.length * 2 : 0) + text.length - (chinese ? chinese.length : 0);
1213
- }
1214
-
1215
- function legacyTips(text, isSuccess = true, timeOrOption = {}) {
1216
- const option = typeof timeOrOption === 'object' ? timeOrOption : { Time: timeOrOption };
1217
- const runtimeUni = getUni();
1218
- if (runtimeUni && typeof runtimeUni.showToast === 'function') {
1219
- runtimeUni.showToast({
1220
- title: String(text || ''),
1221
- icon: option.Icon || (isSuccess === false ? 'none' : 'success'),
1222
- duration: option.Time || (isSuccess === false ? 2000 : 1000)
1223
- });
1224
- return;
1225
- }
1226
- toast(text);
1227
- }
1228
-
1229
- function legacyConfirmTips(content, callback, option = {}) {
1230
- const runtimeUni = getUni();
1231
- if (runtimeUni && typeof runtimeUni.showModal === 'function') {
1232
- runtimeUni.showModal({
1233
- title: option.Title || '提示',
1234
- content: String(content || ''),
1235
- showCancel: option.ShowCancel === false ? false : true,
1236
- confirmColor: option.OKColor || '#5677fc',
1237
- confirmText: option.OKText || '确定',
1238
- success(res) {
1239
- if (res.confirm && typeof callback === 'function') callback(res);
1240
- if (!res.confirm && typeof option.CancelCallback === 'function') option.CancelCallback(res);
1241
- }
1242
- });
1243
- return;
1244
- }
1245
- confirm(content).then((ok) => {
1246
- if (ok && typeof callback === 'function') callback();
1247
- if (!ok && typeof option.CancelCallback === 'function') option.CancelCallback();
1248
- });
1249
- }
1250
-
1251
- function legacyLoading(title, mask = true) {
1252
- const runtimeUni = getUni();
1253
- if (runtimeUni && typeof runtimeUni.showLoading === 'function') {
1254
- runtimeUni.showLoading({ title: title || '请稍候...', mask });
1255
- }
1256
- }
1257
-
1258
- function legacyHideLoading() {
1259
- const runtimeUni = getUni();
1260
- if (runtimeUni && typeof runtimeUni.hideLoading === 'function') runtimeUni.hideLoading();
1261
- }
1262
-
1263
- async function legacyUpload(param = {}, callback) {
1264
- if (!param.File && !param.file && !param.filePath) {
1265
- const error = { Code: 0, Msg: '前端参数错误!' };
1266
- if (typeof callback === 'function') callback(error);
1267
- return error;
1268
- }
1269
- try {
1270
- legacyLoading('上传中...');
1271
- const result = await uploadFile(param.File || param.filePath || param.file, {
1272
- file: param.FileObject || param.fileObject || param.file,
1273
- fileName: param.FileName || param.fileName,
1274
- path: param.Path || param.path || 'upload',
1275
- limit: param.Limit,
1276
- preview: param.Preview,
1277
- anonymous: !!param._Anonymous,
1278
- name: param.Name || param.name || 'file',
1279
- formData: param
1280
- });
1281
- if (typeof callback === 'function') callback(result);
1282
- return result;
1283
- } catch (error) {
1284
- const result = error && error.Code !== undefined ? error : { Code: 0, Data: error, Msg: error && error.message ? error.message : '上传失败' };
1285
- if (typeof callback === 'function') callback(result);
1286
- return result;
1287
- } finally {
1288
- legacyHideLoading();
1289
- }
1290
- }
1291
-
1292
- function base64ToBlob(dataURI) {
1293
- const byteString = atob(String(dataURI).split(',')[1] || '');
1294
- const mimeString = String(dataURI).split(',')[0].split(':')[1].split(';')[0];
1295
- const buffer = new ArrayBuffer(byteString.length);
1296
- const view = new Uint8Array(buffer);
1297
- for (let i = 0; i < byteString.length; i += 1) view[i] = byteString.charCodeAt(i);
1298
- return new Blob([buffer], { type: mimeString });
1299
- }
1300
-
1301
- function base64ToFile(dataurl, filename = 'file') {
1302
- const blob = base64ToBlob(dataurl);
1303
- if (typeof File !== 'undefined') return new File([blob], filename, { type: blob.type });
1304
- blob.name = filename;
1305
- return blob;
1306
- }
1307
-
1308
- function legacyDownload(url, option = {}, callback) {
1309
- const runtimeUni = getUni();
1310
- if (runtimeUni && typeof runtimeUni.downloadFile === 'function') {
1311
- legacyLoading('下载中...');
1312
- return new Promise((resolve) => {
1313
- runtimeUni.downloadFile({
1314
- url,
1315
- ...(option || {}),
1316
- success(res) {
1317
- const result = { Code: res.statusCode === 200 ? 1 : 0, Data: res, Msg: res.errMsg || '' };
1318
- if (typeof callback === 'function') callback(result);
1319
- resolve(result);
1320
- },
1321
- fail(err) {
1322
- const result = { Code: 0, Data: err, Msg: err.errMsg || '下载失败' };
1323
- if (typeof callback === 'function') callback(result);
1324
- resolve(result);
1325
- },
1326
- complete() {
1327
- legacyHideLoading();
1328
- }
1329
- });
1330
- });
1331
- }
1332
- return legacyGet(url, {}, callback, option);
1333
- }
1334
-
1335
- function install(app, options = {}) {
1336
- if (Object.keys(options).length) configure(options);
1337
- if (!app || !app.config) return client;
1338
- app.config.globalProperties.$V8 = client;
1339
- app.config.globalProperties.$Microi = client;
1340
- app.config.globalProperties.V8 = client;
1341
- if (typeof app.provide === 'function') app.provide('MicroiV8', client);
1342
- return client;
1343
- }
1344
-
1345
- // 现代接口:新项目优先使用这些小写方法和命名空间。
1346
- const client = {
1347
- get config() {
1348
- return config;
1349
- },
1350
- storage,
1351
- configure,
1352
- install,
1353
- request,
1354
- get,
1355
- post,
1356
- postForm,
1357
- toast,
1358
- confirm,
1359
- getToken,
1360
- setToken,
1361
- clearToken,
1362
- removeToken: clearToken,
1363
- getDid,
1364
- readTokenClaims,
1365
- shouldRefreshToken,
1366
- refreshToken,
1367
- resumeAuthSession,
1368
- startTokenMaintenance,
1369
- stopTokenMaintenance,
1370
- getUser,
1371
- setUser,
1372
- setCurrentUser: setUser,
1373
- getCurrentUser: getUser,
1374
- assetUrl,
1375
- sanitizeAssetUrl: assetUrl,
1376
- resolveAssetUrl: assetUrl,
1377
- resolveAvatarUrl: resolveFileUrl,
1378
- resolveFileUrl,
1379
- isBlockedAsset,
1380
- extractUploadPath,
1381
- normalizeUploadValue,
1382
- uploadFile,
1383
- getSafeArea,
1384
- formatDate,
1385
- toNumber,
1386
- maskPhone,
1387
- formatCompactNumber,
1388
- ApiEngine: {
1389
- Run: apiEngineRun,
1390
- RunLegacy: apiEngineRunLegacy
1391
- },
1392
- FormEngine: {
1393
- Request: formEngineRequest,
1394
- GetTableData: (table, data, options) => formEngineRequest('gettabledata', table, data, options),
1395
- GetFormData: (table, data, options) => formEngineRequest('getformdata', table, data, options),
1396
- GetTableDataTree: (table, data, options) => formEngineRequest('gettabledatatree', table, data, options),
1397
- AddFormData: (table, data, options) => formEngineRequest('addformdata', table, data, options),
1398
- UptFormData: (table, data, options) => formEngineRequest('uptformdata', table, data, options),
1399
- DelFormData: (table, data, options) => formEngineRequest('delformdata', table, data, options),
1400
- GetTableDataAnonymous: (table, data, options) => formEngineAnonymous('GetTableDataAnonymous', table, data, options),
1401
- GetFormDataAnonymous: (table, data, options) => formEngineAnonymous('GetFormDataAnonymous', table, data, options),
1402
- GetTableDataTreeAnonymous: (table, data, options) => formEngineAnonymous('GetTableDataTreeAnonymous', table, data, options)
1403
- }
1404
- };
1405
-
1406
- // 旧版前端 V8 依赖的后端接口路径,保留原名称以减少迁移成本。
1407
- const legacyApi = {
1408
- MicroiInit: '/apiengine/microi-init',
1409
- GetSysConfig: '/api/DiyTable/getSysConfig',
1410
- Login: '/api/SysUser/login',
1411
- AddFormData: '/api/FormEngine/addFormData',
1412
- AddFormDataBatch: '/api/FormEngine/addFormDataBatch',
1413
- DelFormData: '/api/FormEngine/delFormData',
1414
- DelFormDataBatch: '/api/FormEngine/delFormDataBatch',
1415
- DelFormDataByWhere: '/api/FormEngine/delFormDataByWhere',
1416
- UptFormData: '/api/FormEngine/uptFormData',
1417
- UptFormDataBatch: '/api/FormEngine/uptFormDataBatch',
1418
- UptFormDataByWhere: '/api/FormEngine/uptFormDataByWhere',
1419
- GetFormData: '/api/FormEngine/getFormData',
1420
- GetFormDataAnonymous: '/api/FormEngine/getFormDataAnonymous',
1421
- GetTableData: '/api/FormEngine/getTableData',
1422
- GetTableDataAnonymous: '/api/FormEngine/GetTableDataAnonymous',
1423
- GetTableDataTree: '/api/FormEngine/getTableDataTree',
1424
- GetTableDataTreeAnonymous: '/api/FormEngine/getTableDataTreeAnonymous',
1425
- ApiEngineRun: '/api/ApiEngine/run',
1426
- ModuleEngineRun: '/api/ModuleEngine/run',
1427
- RefreshToken: '/api/SysUser/refreshToken',
1428
- RefreshLoginUser: '/api/SysUser/refreshLoginUser',
1429
- Upload: '/api/HDFS/Upload',
1430
- UploadAnonymous: '/api/HDFS/uploadAnonymous',
1431
- UniappUpload: '/api/HDFS/UniappUpload',
1432
- UniappUploadAnonymous: '/api/HDFS/uniappUploadAnonymous',
1433
- GetCurrentUser: '/api/SysUser/getCurrentUser',
1434
- GetDateTimeNow: '/api/os/getDateTimeNow',
1435
- AddSysLog: '/api/SysLog/addSysLog',
1436
- GetOsClientByDomain: '/api/Os/getOsClientByDomain',
1437
- ApiEngine: {}
1438
- };
1439
-
1440
- // 旧版接口:尽量保持历史项目里的调用名、字段名和回调形态。
1441
- Object.assign(client, {
1442
- Store: null,
1443
- IDE: getUni() ? 'UniApp' : 'PCVue3',
1444
- AppLogo: '',
1445
- AppKey: '',
1446
- H5Url: config.webBase || '',
1447
- DateTimeNow: new Date(),
1448
- ClientType: getUni() ? 'H5' : 'Web',
1449
- ClientSystem: '',
1450
- PageUrlLogin: config.loginUrl || '',
1451
- PageSizes: [10, 20, 50, 100],
1452
- SysConfig: {},
1453
- SafeArea: getSafeArea(),
1454
- Api: legacyApi,
1455
- Extend: {
1456
- Open: legacyOpen,
1457
- DateTimeFormat: formatDate,
1458
- DateDiff: diffTime,
1459
- Add0(value, length) {
1460
- return String(value || '').padStart(Number(length || 0), '0');
1461
- }
1462
- },
1463
- Window: {},
1464
- Form: {},
1465
- IsNull(value) {
1466
- return value === null || value === undefined || value === '' || value === 'undefined' || value === 'null';
1467
- },
1468
- IsNotNull(value) {
1469
- return !client.IsNull(value);
1470
- },
1471
- FormSet(fieldName, value) {
1472
- client.Form[fieldName] = value;
1473
- },
1474
- Run(v8Code) {
1475
- return Function('V8', `"use strict"; return (async function(){${v8Code || ''}\n}).call(V8);`)(client);
1476
- },
1477
- Open: legacyOpen,
1478
- GetFileServerUrl: assetUrl,
1479
- GetStorageSync: storage.get,
1480
- SetStorageSync: storage.set,
1481
- GetOsClientByDomain: async function getOsClientByDomain(getCache) {
1482
- const cached = getCache ? storage.get('OsClient') : '';
1483
- if (cached) {
1484
- configure({ osClient: cached });
1485
- return { Code: 1, Data: { OsClient: cached } };
1486
- }
1487
- const domain = hasWindow() ? window.location.host.toLowerCase() : '';
1488
- const result = await legacyPost(legacyApi.GetOsClientByDomain, { Domain: domain }, null, { SilentError: true });
1489
- if (result && result.Code === 1 && result.Data && result.Data.OsClient) {
1490
- configure({ osClient: result.Data.OsClient });
1491
- storage.set('OsClient', result.Data.OsClient);
1492
- }
1493
- return result;
1494
- },
1495
- GetSysConfig: async function getSysConfig(refresh) {
1496
- if (!refresh) {
1497
- const cached = storage.get('SysConfig');
1498
- if (cached) return parseMaybeJson(cached, {});
1499
- }
1500
- const result = await legacyPost(legacyApi.GetSysConfig, {
1501
- OsClient: config.osClient,
1502
- _SearchEqual: { IsEnable: 1 }
1503
- }, null, { SilentError: true });
1504
- if (result && result.Code === 1) {
1505
- const model = result.Data || {};
1506
- client.SysConfig = model;
1507
- if (model.FileServer) configure({ fileServer: model.FileServer });
1508
- if (model.H5Url) client.H5Url = model.H5Url;
1509
- if (model.AppLogo) client.AppLogo = model.AppLogo;
1510
- storage.set('SysConfig', JSON.stringify(model));
1511
- return model;
1512
- }
1513
- return null;
1514
- },
1515
- GetSysConfigSync() {
1516
- return client.SysConfig && Object.keys(client.SysConfig).length
1517
- ? client.SysConfig
1518
- : parseMaybeJson(storage.get('SysConfig'), {});
1519
- },
1520
- SetSysConfig(sysConfig) {
1521
- const model = typeof sysConfig === 'string' ? parseMaybeJson(sysConfig, {}) : (sysConfig || {});
1522
- client.SysConfig = model;
1523
- storage.set('SysConfig', JSON.stringify(model));
1524
- },
1525
- InitDateTimeTimer: null,
1526
- InitDateTimeNow() {
1527
- return legacyPost(legacyApi.GetDateTimeNow, {}, (result) => {
1528
- if (result && result.Code) {
1529
- client.DateTimeNow = new Date(result.Data);
1530
- if (client.InitDateTimeTimer) clearInterval(client.InitDateTimeTimer);
1531
- client.InitDateTimeTimer = setInterval(() => {
1532
- client.DateTimeNow = addTime(client.DateTimeNow, 's', 1);
1533
- }, 1000);
1534
- }
1535
- });
1536
- },
1537
- RefreshLoginUser: async function refreshLoginUser() {
1538
- const result = await legacyPost(legacyApi.RefreshLoginUser, {});
1539
- if (result && result.Code) legacySetCurrentUser(result.Data || {});
1540
- return result;
1541
- },
1542
- RefreshToken: async function refreshToken(callback) {
1543
- const token = legacyGetToken();
1544
- if (!token) return { Code: 0, Msg: 'Token 为空。' };
1545
- const result = await client.refreshToken();
1546
- if (result && result.Code) legacySetCurrentUser(result.Data || {});
1547
- if (typeof callback === 'function') callback(result);
1548
- return result;
1549
- },
1550
- ArrayBufferToBase64(arrayBuffer) {
1551
- const bytes = new Uint8Array(arrayBuffer);
1552
- let binary = '';
1553
- bytes.forEach((byte) => { binary += String.fromCharCode(byte); });
1554
- if (typeof btoa === 'function') return btoa(binary);
1555
- return binary;
1556
- },
1557
- GetCurrentUser: legacyGetCurrentUser,
1558
- SetCurrentUser: legacySetCurrentUser,
1559
- GetToken: legacyGetToken,
1560
- SetToken: legacySetToken,
1561
- Login(param) {
1562
- const loginParam = { ...(param || {}) };
1563
- if (!loginParam._ClientType) loginParam._ClientType = config.clientType || (getUni() ? 'Mobile' : 'PC');
1564
- return legacyPost(legacyApi.Login, loginParam, (result) => {
1565
- if (result && result.Code) legacySetCurrentUser(result.Data || {});
1566
- }, { DataType: 'form' });
1567
- },
1568
- Logout() {
1569
- legacySetToken('');
1570
- },
1571
- Tips: legacyTips,
1572
- Msg: legacyTips,
1573
- GetStrLength: legacyGetStrLength,
1574
- ConfirmTips: legacyConfirmTips,
1575
- IsAndroid() {
1576
- return String(getSafeArea().platform || '').toLowerCase() === 'android';
1577
- },
1578
- IsPhoneX() {
1579
- const area = getSafeArea();
1580
- return area.bottom > 0;
1581
- },
1582
- Loading: legacyLoading,
1583
- ShowLoading: legacyLoading,
1584
- HideLoading: legacyHideLoading,
1585
- Post: legacyPost,
1586
- PostAsync: legacyPost,
1587
- Get: legacyGet,
1588
- PostAll(allParams = [], callback) {
1589
- return withCallback(Promise.all(allParams.map((item) => legacyPost(item.Url || item.url, item.Data || item.Param || item.data || {}, null, item))), callback);
1590
- },
1591
- request(options = {}) {
1592
- if (options && (options.Url || options.Data || options.Method || options.Param)) return legacyRawRequest(options);
1593
- return request(options);
1594
- },
1595
- GetClientType() {
1596
- return getUni() ? 'H5' : 'Web';
1597
- },
1598
- GetClientSystem() {
1599
- return getSafeArea().platform || '';
1600
- },
1601
- AddSysLog(param) {
1602
- return legacyPost(legacyApi.AddSysLog, param || {}, null, { DataType: 'form', SilentError: true });
1603
- },
1604
- CheckResult(result) {
1605
- if (!result || typeof result !== 'object') return false;
1606
- if (result.Code !== 1) {
1607
- legacyTips(result.Msg || '操作失败', false, 3000);
1608
- return false;
1609
- }
1610
- return true;
1611
- },
1612
- IsLogin: legacyIsLogin,
1613
- NavigateTo: legacyNavigateTo,
1614
- RouterPush: legacyNavigateTo,
1615
- Upload: legacyUpload,
1616
- UploadAnonymous(param, callback) {
1617
- return legacyUpload({ ...(param || {}), _Anonymous: true }, callback);
1618
- },
1619
- Download: legacyDownload,
1620
- DownloadFile: legacyDownload,
1621
- ImgBase64ToFile: base64ToFile,
1622
- ImgBase63ToBlob: base64ToBlob,
1623
- HidePhone: maskPhone,
1624
- ImgExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'],
1625
- IsImg(link = '') {
1626
- const clean = String(link || '').split('?')[0].toLowerCase();
1627
- return client.ImgExtensions.some((ext) => clean.endsWith(ext));
1628
- },
1629
- NavigateToMiniProgram(appId, path, param, callback) {
1630
- const runtimeUni = getUni();
1631
- if (runtimeUni && typeof runtimeUni.navigateToMiniProgram === 'function') {
1632
- runtimeUni.navigateToMiniProgram({
1633
- appId,
1634
- path,
1635
- extraData: param,
1636
- success: (res) => callback && callback({ Code: 1, Data: res }),
1637
- fail: (err) => callback && callback({ Code: 0, Data: err, Msg: err.errMsg })
1638
- });
1639
- }
1640
- },
1641
- GetUrlQuery: legacyGetUrlQuery,
1642
- GetCountStr(count) {
1643
- return formatCompactNumber(count, Number(count) < 100000 ? 2 : 1);
1644
- }
1645
- });
1646
-
1647
- // 这些属性在旧项目中常被直接赋值,因此保留取值器和赋值器同步到 config。
1648
- Object.defineProperties(client, {
1649
- ApiBase: {
1650
- get: () => config.apiBase,
1651
- set: (value) => configure({ apiBase: value })
1652
- },
1653
- OsClient: {
1654
- get: () => config.osClient,
1655
- set: (value) => configure({ osClient: value })
1656
- },
1657
- FileServer: {
1658
- get: () => config.fileServer,
1659
- set: (value) => configure({ fileServer: value })
1660
- }
1661
- });
1662
-
1663
- // 新旧两套接口引擎调用方式并存:字符串 key 走新路由,对象参数兼容旧路由。
1664
- Object.assign(client.ApiEngine, {
1665
- Run(urlOrKey, dataOrCallback, callback) {
1666
- if (typeof urlOrKey === 'string') {
1667
- const data = dataOrCallback && typeof dataOrCallback === 'object' ? dataOrCallback : {};
1668
- const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1669
- return withCallback(apiEngineRun(urlOrKey, data, { checkCode: false }), cb);
1670
- }
1671
- const param = urlOrKey || {};
1672
- const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1673
- const key = param.ApiEngineKey || param.apiEngineKey;
1674
- return withCallback(key ? apiEngineRun(key, param, { checkCode: false }) : apiEngineRunLegacy('', param, { checkCode: false }), cb);
1675
- },
1676
- RunDirect: apiEngineRun,
1677
- RunLegacy: apiEngineRunLegacy
1678
- });
1679
-
1680
- // 模块引擎和表单引擎沿用旧版命名,内部统一走 legacyPost。
1681
- client.ModuleEngine = {
1682
- Run(moduleKeyOrParam, dataOrCallback, callback) {
1683
- const data = typeof moduleKeyOrParam === 'string'
1684
- ? { ModuleEngineKey: moduleKeyOrParam, ...(dataOrCallback || {}) }
1685
- : (moduleKeyOrParam || {});
1686
- const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1687
- return withCallback(legacyPost(legacyApi.ModuleEngineRun, data), cb);
1688
- }
1689
- };
1690
-
1691
- Object.assign(client.FormEngine, {
1692
- AddFormData(first, second, third) {
1693
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, true);
1694
- return withCallback(legacyPost(legacyApi.AddFormData, data), callback);
1695
- },
1696
- AddFormDataBatch(param, callback) {
1697
- return withCallback(legacyPost(legacyApi.AddFormDataBatch, param || {}), callback);
1698
- },
1699
- AddTableData(param, callback) {
1700
- return withCallback(legacyPost(legacyApi.AddFormDataBatch, param || {}), callback);
1701
- },
1702
- DelFormData(param, callback) {
1703
- return withCallback(legacyPost(legacyApi.DelFormData, param || {}), callback);
1704
- },
1705
- DelFormDataBatch(param, callback) {
1706
- return withCallback(legacyPost(legacyApi.DelFormDataBatch, param || {}), callback);
1707
- },
1708
- DelFormDataByWhere(param, callback) {
1709
- return withCallback(legacyPost(legacyApi.DelFormDataByWhere, param || {}), callback);
1710
- },
1711
- UptFormData(first, second, third) {
1712
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, true);
1713
- return withCallback(legacyPost(legacyApi.UptFormData, data), callback);
1714
- },
1715
- UptFormDataByWhere(param, callback) {
1716
- return withCallback(legacyPost(legacyApi.UptFormDataByWhere, param || {}), callback);
1717
- },
1718
- UptFormDataBatch(param, callback) {
1719
- return withCallback(legacyPost(legacyApi.UptFormDataBatch, param || {}), callback);
1720
- },
1721
- UptTableData(param, callback) {
1722
- return withCallback(legacyPost(legacyApi.UptFormDataBatch, param || {}), callback);
1723
- },
1724
- GetFormData(first, second, third) {
1725
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1726
- return withCallback(legacyPost(`${legacyApi.GetFormData}-${data.FormEngineKey || ''}`, data), callback);
1727
- },
1728
- GetFormDataAnonymous(first, second, third) {
1729
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1730
- return withCallback(legacyPost(legacyApi.GetFormDataAnonymous, data, null, { Auth: false }), callback);
1731
- },
1732
- GetTableData(first, second, third) {
1733
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1734
- return withCallback(legacyPost(legacyApi.GetTableData, data), callback);
1735
- },
1736
- GetTableDataAnonymous(first, second, third) {
1737
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1738
- return withCallback(legacyPost(legacyApi.GetTableDataAnonymous, data, null, { Auth: false }), callback);
1739
- },
1740
- GetTableDataTree(first, second, third) {
1741
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1742
- return withCallback(legacyPost(legacyApi.GetTableDataTree, data), callback);
1743
- },
1744
- GetTableDataTreeAnonymous(first, second, third) {
1745
- const { data, callback } = normalizeLegacyFormArgs(first, second, third, false);
1746
- return withCallback(legacyPost(legacyApi.GetTableDataTreeAnonymous, data, null, { Auth: false }), callback);
1747
- }
1748
- });
1749
-
1750
- installDatePrototypeCompat();
1751
- return client;
1752
- }
1753
-
1754
- export const V8 = createMicroiV8();
1755
- export const MicroiV8 = V8;
1756
- export const installMicroiV8 = (app, options = {}) => V8.install(app, options);
1757
-
1758
- export default V8;
1
+ /*
2
+ * Microi V8 前端标准开发包。
3
+ * 面向 Vue 3 与 uni-app 项目,不强依赖固定的界面库或状态管理方案。
4
+ * 统一封装吾码接口引擎、表单引擎、文件服务、登录态与旧版 V8 前端接口。
5
+ */
6
+
7
+ // 默认把这些状态码视为登录态失效,便于各端统一跳转或清理缓存。
8
+ const DEFAULT_AUTH_CODES = [401, -1, 1001, 1002];
9
+
10
+ // 禁用常见占位图和外部二维码资源,避免前端误把临时素材带到正式项目。
11
+ const DEFAULT_BLOCKED_ASSET = /(qrserver\.com|create-qr-code|picsum\.photos|placehold\.co|placeholder\.com|dummyimage\.com)/i;
12
+
13
+ // 兼容浏览器、uni-app、小程序运行时以及测试环境中的全局对象读取。
14
+ function getGlobalValue(key) {
15
+ try {
16
+ if (typeof globalThis !== 'undefined' && globalThis[key] !== undefined) return globalThis[key];
17
+ } catch (e) {}
18
+ return undefined;
19
+ }
20
+
21
+ function getUni() {
22
+ try {
23
+ if (typeof uni !== 'undefined' && uni && typeof uni === 'object') return uni;
24
+ } catch (e) {}
25
+ const runtimeUni = getGlobalValue('uni');
26
+ return runtimeUni && typeof runtimeUni === 'object' ? runtimeUni : null;
27
+ }
28
+
29
+ function hasWindow() {
30
+ return typeof window !== 'undefined' && !!window;
31
+ }
32
+
33
+ // 下面这些方法只做路径与查询参数拼装,不参与业务语义判断。
34
+ function normalizeBase(url) {
35
+ return String(url || '').replace(/\/+$/, '');
36
+ }
37
+
38
+ function trimLeftSlash(value) {
39
+ return String(value || '').replace(/^\/+/, '');
40
+ }
41
+
42
+ function joinUrl(base, path) {
43
+ const value = String(path || '');
44
+ if (/^(https?:|data:|blob:|file:)/i.test(value)) return value;
45
+ return `${normalizeBase(base)}/${trimLeftSlash(value)}`;
46
+ }
47
+
48
+ function appendQuery(url, key, value) {
49
+ if (!value || new RegExp(`[?&]${key}=`, 'i').test(url)) return url;
50
+ const sep = url.indexOf('?') >= 0 ? '&' : '?';
51
+ return `${url}${sep}${key}=${encodeURIComponent(value)}`;
52
+ }
53
+
54
+ function appendQueryObject(url, data) {
55
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return url;
56
+ const parts = [];
57
+ Object.keys(data).forEach((key) => {
58
+ const value = data[key];
59
+ if (value === undefined || value === null || value === '') return;
60
+ const serialized = typeof value === 'object' ? JSON.stringify(value) : String(value);
61
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(serialized)}`);
62
+ });
63
+ if (!parts.length) return url;
64
+ return `${url}${url.indexOf('?') >= 0 ? '&' : '?'}${parts.join('&')}`;
65
+ }
66
+
67
+ function parseMaybeJson(value, fallback = {}) {
68
+ if (typeof value !== 'string') return value == null ? fallback : value;
69
+ const text = value.trim();
70
+ if (!text) return fallback;
71
+ try {
72
+ return JSON.parse(text);
73
+ } catch (e) {
74
+ return fallback;
75
+ }
76
+ }
77
+
78
+ // 没有 uni 或浏览器缓存时退回内存缓存,保证单元测试和服务端渲染不会崩溃。
79
+ function createMemoryStorage() {
80
+ const cache = new Map();
81
+ return {
82
+ get(key) {
83
+ return cache.has(key) ? cache.get(key) : '';
84
+ },
85
+ set(key, value) {
86
+ cache.set(key, value);
87
+ },
88
+ remove(key) {
89
+ cache.delete(key);
90
+ }
91
+ };
92
+ }
93
+
94
+ function createDefaultStorage() {
95
+ const runtimeUni = getUni();
96
+ if (runtimeUni && typeof runtimeUni.getStorageSync === 'function') {
97
+ return {
98
+ get(key) {
99
+ try {
100
+ return runtimeUni.getStorageSync(key) || '';
101
+ } catch (e) {
102
+ return '';
103
+ }
104
+ },
105
+ set(key, value) {
106
+ try {
107
+ runtimeUni.setStorageSync(key, value);
108
+ } catch (e) {}
109
+ },
110
+ remove(key) {
111
+ try {
112
+ runtimeUni.removeStorageSync(key);
113
+ } catch (e) {}
114
+ }
115
+ };
116
+ }
117
+
118
+ if (hasWindow() && window.localStorage) {
119
+ return {
120
+ get(key) {
121
+ try {
122
+ return window.localStorage.getItem(key) || '';
123
+ } catch (e) {
124
+ return '';
125
+ }
126
+ },
127
+ set(key, value) {
128
+ try {
129
+ window.localStorage.setItem(key, value);
130
+ } catch (e) {}
131
+ },
132
+ remove(key) {
133
+ try {
134
+ window.localStorage.removeItem(key);
135
+ } catch (e) {}
136
+ }
137
+ };
138
+ }
139
+
140
+ return createMemoryStorage();
141
+ }
142
+
143
+ function serializeUser(value) {
144
+ if (!value) return '';
145
+ return typeof value === 'string' ? value : JSON.stringify(value);
146
+ }
147
+
148
+ function deserializeUser(value) {
149
+ if (!value) return null;
150
+ if (typeof value === 'object') return value;
151
+ return parseMaybeJson(value, null);
152
+ }
153
+
154
+ // 吾码文件字段可能来自上传控件、HDFS 接口、字符串或 JSON 字符串,这里统一抽取可用路径。
155
+ function extractUploadPath(value) {
156
+ if (!value) return '';
157
+ if (typeof value === 'object') {
158
+ const raw = Array.isArray(value) ? (value[0] || {}) : value;
159
+ if (typeof raw === 'string') return extractUploadPath(raw);
160
+ return raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL ||
161
+ raw.FullUrl || raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
162
+ }
163
+
164
+ const text = String(value || '').trim();
165
+ if (!text) return '';
166
+ if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
167
+ return extractUploadPath(parseMaybeJson(text, text));
168
+ }
169
+ return text;
170
+ }
171
+
172
+ function normalizeUploadValue(value) {
173
+ if (!value) return [];
174
+ if (Array.isArray(value)) return value.map(extractUploadPath).filter(Boolean);
175
+ if (typeof value === 'object') {
176
+ const path = extractUploadPath(value);
177
+ return path ? [path] : [];
178
+ }
179
+
180
+ const text = String(value || '').trim();
181
+ if (!text) return [];
182
+ if ((text.startsWith('[') && text.endsWith(']')) || (text.startsWith('{') && text.endsWith('}'))) {
183
+ const parsed = parseMaybeJson(text, null);
184
+ if (Array.isArray(parsed)) return parsed.map(extractUploadPath).filter(Boolean);
185
+ const path = extractUploadPath(parsed);
186
+ return path ? [path] : [];
187
+ }
188
+ return [text];
189
+ }
190
+
191
+ function normalizeUploadData(body) {
192
+ const raw = Array.isArray(body && body.Data) ? (body.Data[0] || {}) : ((body && body.Data) || {});
193
+ const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || raw.Url || raw.FileUrl || '';
194
+ const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || '';
195
+ return { ...raw, Path: path, Url: url };
196
+ }
197
+
198
+ function normalizeClientUploadPath(value) {
199
+ let path = String(value || 'upload').trim().replace(/\\/g, '/');
200
+ if (/^(https?:|data:|blob:|file:)/i.test(path)) throw new Error('上传路径不合法。');
201
+ path = path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/{2,}/g, '/');
202
+ if (!path || path.startsWith('~') || path.includes('..') || path.includes(':')) {
203
+ throw new Error('上传路径不合法。');
204
+ }
205
+ const parts = path.split('/').filter(Boolean);
206
+ if (!parts.length || parts.some((item) => item === '.' || item === '..')) {
207
+ throw new Error('上传路径不合法。');
208
+ }
209
+ return parts.join('/');
210
+ }
211
+
212
+ function normalizeFileUrlData(data, assetUrl, fallback = '') {
213
+ const raw = Array.isArray(data) ? (data[0] || '') : (data || '');
214
+ if (typeof raw === 'string') return assetUrl(raw || fallback);
215
+ const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || raw.FullUrl || '';
216
+ const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
217
+ return assetUrl(url || path || fallback);
218
+ }
219
+
220
+ function getHeaderValue(headers, key) {
221
+ if (!headers) return '';
222
+ const lower = key.toLowerCase();
223
+ if (typeof headers.get === 'function') {
224
+ const value = headers.get(key) || headers.get(lower);
225
+ if (value) return value;
226
+ }
227
+ for (const name of Object.keys(headers)) {
228
+ if (String(name).toLowerCase() === lower) return headers[name];
229
+ }
230
+ return '';
231
+ }
232
+
233
+ function setSingletonHeader(headers, key, value) {
234
+ const lower = String(key).toLowerCase();
235
+ Object.keys(headers).forEach((name) => {
236
+ if (String(name).toLowerCase() === lower) delete headers[name];
237
+ });
238
+ if (value !== undefined && value !== null && value !== '') headers[key] = value;
239
+ }
240
+
241
+ function normalizeBearer(value) {
242
+ const text = String(value || '').trim();
243
+ return /^Bearer\s+/i.test(text) ? text.replace(/^Bearer\s+/i, '') : text;
244
+ }
245
+
246
+ // 兼容浏览器上传对象、组件包装对象和 uni-app 临时文件路径。
247
+ function isUploadFileLike(value) {
248
+ if (!value) return false;
249
+ if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
250
+ return typeof value.arrayBuffer === 'function';
251
+ }
252
+
253
+ function pickUploadFileLike(value) {
254
+ if (!value) return null;
255
+ if (isUploadFileLike(value)) return value;
256
+ if (typeof value !== 'object') return null;
257
+ const keys = ['file', 'raw', 'blob', 'originFileObj', 'tempFile', 'data'];
258
+ for (const key of keys) {
259
+ const picked = pickUploadFileLike(value[key]);
260
+ if (picked) return picked;
261
+ }
262
+ return null;
263
+ }
264
+
265
+ function pickUploadFileName(value) {
266
+ if (!value) return '';
267
+ if (typeof value === 'object') {
268
+ if (value.name) return String(value.name);
269
+ const keys = ['file', 'raw', 'blob', 'originFileObj', 'tempFile', 'data'];
270
+ for (const key of keys) {
271
+ const name = pickUploadFileName(value[key]);
272
+ if (name) return name;
273
+ }
274
+ const path = value.path || value.tempFilePath || value.url || value.src || value.localUrl || value.fullPath || '';
275
+ if (path) return inferUploadFileName(path);
276
+ }
277
+ return '';
278
+ }
279
+
280
+ function inferUploadFileName(value) {
281
+ const text = String(value || '').split('?')[0].split('#')[0];
282
+ const name = decodeURIComponent((text.split('/').pop() || '').trim());
283
+ return name && name.indexOf(':') < 0 ? name : '';
284
+ }
285
+
286
+ function pickUploadFileSource(filePath, options = {}) {
287
+ const candidates = [options.file, filePath];
288
+ for (const item of candidates) {
289
+ if (!item) continue;
290
+ if (typeof item === 'string') return item;
291
+ if (typeof item === 'object') {
292
+ const path = item.path || item.tempFilePath || item.url || item.src || item.localUrl || item.fullPath || '';
293
+ if (path) return String(path);
294
+ }
295
+ }
296
+ return '';
297
+ }
298
+
299
+ async function resolveFetchUploadFile(filePath, options = {}) {
300
+ const direct = pickUploadFileLike(options.file) || pickUploadFileLike(filePath);
301
+ const name = options.fileName || pickUploadFileName(options.file) || pickUploadFileName(filePath) || inferUploadFileName(filePath) || 'file';
302
+ if (direct) return { file: direct, name };
303
+
304
+ const source = pickUploadFileSource(filePath, options);
305
+ if (source && typeof fetch === 'function' && /^(blob:|data:)/i.test(source)) {
306
+ const res = await fetch(source);
307
+ const blob = await res.blob();
308
+ return { file: blob, name };
309
+ }
310
+ return { file: null, name };
311
+ }
312
+
313
+ // 控制接口并发,适合列表页批量请求时给后端和小程序运行时减压。
314
+ function createQueue(maxConcurrent) {
315
+ const limit = Number(maxConcurrent || 0);
316
+ if (!limit || limit <= 0) {
317
+ return async function runNow(task) {
318
+ return task();
319
+ };
320
+ }
321
+
322
+ let active = 0;
323
+ const waiting = [];
324
+ function release() {
325
+ if (waiting.length) {
326
+ const next = waiting.shift();
327
+ active += 1;
328
+ next();
329
+ } else {
330
+ active = Math.max(0, active - 1);
331
+ }
332
+ }
333
+
334
+ return function runQueued(task) {
335
+ return new Promise((resolve, reject) => {
336
+ const start = () => {
337
+ Promise.resolve()
338
+ .then(task)
339
+ .then(resolve, reject)
340
+ .finally(release);
341
+ };
342
+ if (active < limit) {
343
+ active += 1;
344
+ start();
345
+ } else {
346
+ waiting.push(start);
347
+ }
348
+ });
349
+ };
350
+ }
351
+
352
+ function defaultToast(message) {
353
+ const runtimeUni = getUni();
354
+ if (runtimeUni && typeof runtimeUni.showToast === 'function') {
355
+ runtimeUni.showToast({ title: String(message || ''), icon: 'none' });
356
+ return;
357
+ }
358
+ if (hasWindow() && typeof window.alert === 'function') window.alert(String(message || ''));
359
+ }
360
+
361
+ function defaultConfirm(message) {
362
+ const runtimeUni = getUni();
363
+ if (runtimeUni && typeof runtimeUni.showModal === 'function') {
364
+ return new Promise((resolve) => {
365
+ runtimeUni.showModal({
366
+ title: '',
367
+ content: String(message || ''),
368
+ success: (res) => resolve(!!res.confirm),
369
+ fail: () => resolve(false)
370
+ });
371
+ });
372
+ }
373
+ if (hasWindow() && typeof window.confirm === 'function') return Promise.resolve(window.confirm(String(message || '')));
374
+ return Promise.resolve(true);
375
+ }
376
+
377
+ // fetch 的超时需要 AbortController;不支持时由运行时自身处理。
378
+ function createFetchTimeout(timeout) {
379
+ if (typeof AbortController === 'undefined') return {};
380
+ const controller = new AbortController();
381
+ const timer = setTimeout(() => controller.abort(), Number(timeout || 30000));
382
+ return { signal: controller.signal, cleanup: () => clearTimeout(timer) };
383
+ }
384
+
385
+ function getSafeArea() {
386
+ const runtimeUni = getUni();
387
+ if (runtimeUni && typeof runtimeUni.getSystemInfoSync === 'function') {
388
+ try {
389
+ const info = runtimeUni.getSystemInfoSync();
390
+ const insets = info.safeAreaInsets || {};
391
+ const safeArea = info.safeArea || {};
392
+ return {
393
+ top: Number(insets.top || safeArea.top || info.statusBarHeight || 0),
394
+ bottom: Number(insets.bottom || 0),
395
+ left: Number(insets.left || 0),
396
+ right: Number(insets.right || 0),
397
+ statusBarHeight: Number(info.statusBarHeight || 0),
398
+ windowHeight: Number(info.windowHeight || 0),
399
+ windowWidth: Number(info.windowWidth || 0),
400
+ platform: info.platform || ''
401
+ };
402
+ } catch (e) {}
403
+ }
404
+ return { top: 0, bottom: 0, left: 0, right: 0, statusBarHeight: 0, windowHeight: 0, windowWidth: 0, platform: '' };
405
+ }
406
+
407
+ // 常用日期、数字与显示格式化,兼容旧版前端 V8 写法。
408
+ function formatDate(value, format = 'yyyy-MM-dd HH:mm:ss') {
409
+ const date = value instanceof Date ? value : new Date(value || Date.now());
410
+ if (Number.isNaN(date.getTime())) return '';
411
+ const pad = (num, len = 2) => String(num).padStart(len, '0');
412
+ const map = {
413
+ yyyy: date.getFullYear(),
414
+ MM: pad(date.getMonth() + 1),
415
+ dd: pad(date.getDate()),
416
+ HH: pad(date.getHours()),
417
+ mm: pad(date.getMinutes()),
418
+ ss: pad(date.getSeconds()),
419
+ SSS: pad(date.getMilliseconds(), 3)
420
+ };
421
+ return Object.keys(map).reduce((text, key) => text.replace(new RegExp(key, 'g'), map[key]), format);
422
+ }
423
+
424
+ function toNumber(value, fallback = 0) {
425
+ const num = Number(value);
426
+ return Number.isFinite(num) ? num : fallback;
427
+ }
428
+
429
+ function maskPhone(value) {
430
+ const text = String(value || '');
431
+ return text.length >= 7 ? `${text.slice(0, 3)}****${text.slice(-4)}` : text;
432
+ }
433
+
434
+ function formatCompactNumber(value, digits = 2) {
435
+ const num = toNumber(value, 0);
436
+ const abs = Math.abs(num);
437
+ if (abs >= 100000000) return `${(num / 100000000).toFixed(digits).replace(/\.?0+$/, '')}亿`;
438
+ if (abs >= 10000) return `${(num / 10000).toFixed(digits).replace(/\.?0+$/, '')}万`;
439
+ return `${num}`;
440
+ }
441
+
442
+ function addTime(value, unit, number) {
443
+ const date = value instanceof Date ? new Date(value.getTime()) : new Date(value || Date.now());
444
+ const amount = Number(number || 0);
445
+ switch (unit) {
446
+ case 's':
447
+ date.setSeconds(date.getSeconds() + amount);
448
+ break;
449
+ case 'n':
450
+ case 'm':
451
+ date.setMinutes(date.getMinutes() + amount);
452
+ break;
453
+ case 'h':
454
+ date.setHours(date.getHours() + amount);
455
+ break;
456
+ case 'd':
457
+ date.setDate(date.getDate() + amount);
458
+ break;
459
+ case 'w':
460
+ date.setDate(date.getDate() + amount * 7);
461
+ break;
462
+ case 'q':
463
+ date.setMonth(date.getMonth() + amount * 3);
464
+ break;
465
+ case 'M':
466
+ date.setMonth(date.getMonth() + amount);
467
+ break;
468
+ case 'y':
469
+ date.setFullYear(date.getFullYear() + amount);
470
+ break;
471
+ default:
472
+ date.setMilliseconds(date.getMilliseconds() + amount);
473
+ break;
474
+ }
475
+ return date;
476
+ }
477
+
478
+ function diffTime(value, unit, value2) {
479
+ const d1 = value instanceof Date ? value : new Date(value);
480
+ const d2 = value2 instanceof Date ? value2 : new Date(value2 || Date.now());
481
+ const t1 = d1.getTime();
482
+ const t2 = d2.getTime();
483
+ const year = d2.getFullYear() - d1.getFullYear();
484
+ const map = {
485
+ y: year,
486
+ q: year * 4 + Math.floor(d2.getMonth() / 4) - Math.floor(d1.getMonth() / 4),
487
+ M: year * 12 + d2.getMonth() - d1.getMonth(),
488
+ m: year * 12 + d2.getMonth() - d1.getMonth(),
489
+ ms: t2 - t1,
490
+ w: Math.floor((t2 + 345600000) / 604800000) - Math.floor((t1 + 345600000) / 604800000),
491
+ d: Math.floor(t2 / 86400000) - Math.floor(t1 / 86400000),
492
+ h: Math.floor(t2 / 3600000) - Math.floor(t1 / 3600000),
493
+ n: Math.floor(t2 / 60000) - Math.floor(t1 / 60000),
494
+ s: Math.floor(t2 / 1000) - Math.floor(t1 / 1000)
495
+ };
496
+ return map[unit];
497
+ }
498
+
499
+ // 老项目里常用 Date.prototype.Format/AddTime/DiffTime,这里只在缺失时补齐。
500
+ function installDatePrototypeCompat() {
501
+ if (typeof Date === 'undefined' || !Date.prototype) return;
502
+ if (typeof Date.prototype.Format !== 'function') {
503
+ Object.defineProperty(Date.prototype, 'Format', {
504
+ configurable: true,
505
+ writable: true,
506
+ value(format) {
507
+ return format ? formatDate(this, format) : this;
508
+ }
509
+ });
510
+ }
511
+ if (typeof Date.prototype.AddTime !== 'function') {
512
+ Object.defineProperty(Date.prototype, 'AddTime', {
513
+ configurable: true,
514
+ writable: true,
515
+ value(unit, number) {
516
+ return addTime(this, unit, number);
517
+ }
518
+ });
519
+ }
520
+ if (typeof Date.prototype.DiffTime !== 'function') {
521
+ Object.defineProperty(Date.prototype, 'DiffTime', {
522
+ configurable: true,
523
+ writable: true,
524
+ value(unit, time2) {
525
+ return diffTime(this, unit, time2);
526
+ }
527
+ });
528
+ }
529
+ }
530
+
531
+ export function createMicroiV8(options = {}) {
532
+ // 运行时配置可通过 createMicroiV8(options) 或 client.configure(next) 覆盖。
533
+ let config = {
534
+ apiBase: '',
535
+ webBase: '',
536
+ fileServer: '',
537
+ osClient: '',
538
+ token: '',
539
+ clientType: getUni() ? 'Mobile' : 'PC',
540
+ did: '',
541
+ didKey: 'microi_did',
542
+ tokenKey: 'microi_token',
543
+ userKey: 'microi_user',
544
+ loginUrl: '',
545
+ formQueryEngineKey: '',
546
+ timeout: 30000,
547
+ maxConcurrent: 0,
548
+ appendOsClientQuery: false,
549
+ authCodes: DEFAULT_AUTH_CODES,
550
+ blockedAssetPattern: DEFAULT_BLOCKED_ASSET,
551
+ translate: (message) => message,
552
+ requestAdapter: null,
553
+ onAuthExpired: null,
554
+ onTokenChanged: null,
555
+ toast: null,
556
+ confirm: null,
557
+ ...options
558
+ };
559
+
560
+ const storage = options.storage || createDefaultStorage();
561
+ let runQueued = createQueue(config.maxConcurrent);
562
+ let refreshTokenPromise = null;
563
+ let tokenMaintenanceTimer = null;
564
+ let stopBrowserResumeListeners = null;
565
+
566
+ // 更新配置后立即刷新并发队列,保证 maxConcurrent 热更新生效。
567
+ function configure(next = {}) {
568
+ config = { ...config, ...next };
569
+ if (Object.prototype.hasOwnProperty.call(next, 'maxConcurrent')) {
570
+ runQueued = createQueue(config.maxConcurrent);
571
+ }
572
+ return client;
573
+ }
574
+
575
+ function tr(message) {
576
+ try {
577
+ return config.translate ? config.translate(message) : message;
578
+ } catch (e) {
579
+ return message;
580
+ }
581
+ }
582
+
583
+ function toast(message) {
584
+ if (!message) return;
585
+ const text = tr(message);
586
+ if (typeof config.toast === 'function') return config.toast(text);
587
+ return defaultToast(text);
588
+ }
589
+
590
+ function confirm(message) {
591
+ if (typeof config.confirm === 'function') return config.confirm(tr(message));
592
+ return defaultConfirm(tr(message));
593
+ }
594
+
595
+ function getToken() {
596
+ return config.token || storage.get(config.tokenKey) || '';
597
+ }
598
+
599
+ function getDid() {
600
+ if (config.did) return String(config.did);
601
+ const stored = storage.get(config.didKey);
602
+ if (stored) return String(stored);
603
+ const prefix = String(config.clientType || 'Client').replace(/[^a-z0-9_-]/gi, '') || 'Client';
604
+ const random = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
605
+ ? crypto.randomUUID()
606
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
607
+ const did = `${prefix}:${random}`;
608
+ storage.set(config.didKey, did);
609
+ return did;
610
+ }
611
+
612
+ function setToken(token) {
613
+ const previousToken = getToken();
614
+ const nextToken = token || '';
615
+ config = { ...config, token: nextToken };
616
+ storage.set(config.tokenKey, nextToken);
617
+ if (nextToken !== previousToken && typeof config.onTokenChanged === 'function') {
618
+ try {
619
+ config.onTokenChanged(nextToken, previousToken, client);
620
+ } catch (e) {}
621
+ }
622
+ }
623
+
624
+ function clearToken() {
625
+ config = { ...config, token: '' };
626
+ storage.remove(config.tokenKey);
627
+ storage.remove(config.userKey);
628
+ }
629
+
630
+ function setUser(user) {
631
+ storage.set(config.userKey, serializeUser(user));
632
+ }
633
+
634
+ function getUser() {
635
+ return deserializeUser(storage.get(config.userKey));
636
+ }
637
+
638
+ function isAuthExpired(body, statusCode) {
639
+ if (Number(statusCode) === 401) return true;
640
+ const code = body && body.Code;
641
+ return config.authCodes.indexOf(code) >= 0;
642
+ }
643
+
644
+ function readTokenClaims(token = getToken()) {
645
+ try {
646
+ const normalized = normalizeBearer(token);
647
+ const parts = normalized.split('.');
648
+ if (parts.length < 2) return null;
649
+ const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
650
+ if (typeof Buffer !== 'undefined') {
651
+ return JSON.parse(Buffer.from(payload, 'base64').toString('utf-8'));
652
+ }
653
+ const padded = payload + '='.repeat((4 - payload.length % 4) % 4);
654
+ const binary = atob(padded);
655
+ const bytes = Array.from(binary, (char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`).join('');
656
+ return JSON.parse(decodeURIComponent(bytes));
657
+ } catch (e) {
658
+ return null;
659
+ }
660
+ }
661
+
662
+ function shouldRefreshToken(token = getToken()) {
663
+ const claims = readTokenClaims(token);
664
+ const expiresAt = Number(claims && claims.exp);
665
+ if (!Number.isFinite(expiresAt) || expiresAt <= 0) return true;
666
+ const issuedAt = Number(claims.MicroiTokenIssuedAt || claims.iat);
667
+ const now = Math.floor(Date.now() / 1000);
668
+ const lifetime = Number.isFinite(issuedAt) && issuedAt > 0 && expiresAt > issuedAt
669
+ ? expiresAt - issuedAt
670
+ : Math.max(0, expiresAt - now);
671
+ const lead = Math.min(24 * 60 * 60, Math.max(5 * 60, Math.floor(lifetime / 10)));
672
+ return expiresAt - now <= lead;
673
+ }
674
+
675
+ function handleReturnedToken(headers) {
676
+ const auth = getHeaderValue(headers, 'authorization') || getHeaderValue(headers, 'token');
677
+ const token = normalizeBearer(auth);
678
+ if (token) setToken(token);
679
+ }
680
+
681
+ function handleAuthExpired(body) {
682
+ clearToken();
683
+ if (typeof config.onAuthExpired === 'function') {
684
+ config.onAuthExpired(body, client);
685
+ }
686
+ }
687
+
688
+ // 所有相对地址默认走 apiBase,必要时自动追加 OsClient。
689
+ function buildUrl(url) {
690
+ let fullUrl = /^(https?:|data:|blob:|file:)/i.test(String(url || '')) ? String(url) : joinUrl(config.apiBase, url);
691
+ if (config.appendOsClientQuery && config.osClient && fullUrl.indexOf('/apiengine/') < 0) {
692
+ fullUrl = appendQuery(fullUrl, 'OsClient', config.osClient);
693
+ }
694
+ return fullUrl;
695
+ }
696
+
697
+ // 普通请求统一携带 osclient、Token 与 Authorization,减少各项目重复拼装。
698
+ function buildHeaders(options = {}) {
699
+ const token = options.auth === false ? '' : getToken();
700
+ const headers = {
701
+ 'Content-Type': 'application/json',
702
+ ...(options.header || {}),
703
+ ...(options.headers || {})
704
+ };
705
+ if (config.osClient) setSingletonHeader(headers, 'osclient', config.osClient);
706
+ const did = getDid();
707
+ if (did) setSingletonHeader(headers, 'did', did);
708
+ if (token) {
709
+ setSingletonHeader(headers, 'Token', token);
710
+ setSingletonHeader(headers, 'Authorization', `Bearer ${token}`);
711
+ }
712
+ if (options.apiEngine) headers.apiengine = '1';
713
+ return headers;
714
+ }
715
+
716
+ function buildUploadHeaders(options = {}) {
717
+ const headers = buildHeaders(options);
718
+ Object.keys(headers).forEach((key) => {
719
+ if (String(key).toLowerCase() === 'content-type') delete headers[key];
720
+ });
721
+ return headers;
722
+ }
723
+
724
+ // 请求核心:优先走自定义适配器,其次 uni.request,最后回退 fetch。
725
+ async function request(options = {}) {
726
+ const method = String(options.method || 'POST').toUpperCase();
727
+ let fullUrl = buildUrl(options.url || options.path || '');
728
+ const headers = buildHeaders(options);
729
+ const data = options.data === undefined ? {} : options.data;
730
+ const timeout = options.timeout || config.timeout;
731
+
732
+ const perform = async () => {
733
+ let response;
734
+ if (typeof config.requestAdapter === 'function') {
735
+ response = await config.requestAdapter({ ...options, url: fullUrl, method, data, header: headers, headers, timeout });
736
+ } else {
737
+ const runtimeUni = getUni();
738
+ if (runtimeUni && typeof runtimeUni.request === 'function') {
739
+ response = await new Promise((resolve, reject) => {
740
+ runtimeUni.request({
741
+ url: fullUrl,
742
+ method,
743
+ data,
744
+ header: headers,
745
+ timeout,
746
+ success: resolve,
747
+ fail: reject
748
+ });
749
+ });
750
+ } else if (typeof fetch === 'function') {
751
+ if ((method === 'GET' || method === 'HEAD') && data && typeof data === 'object') {
752
+ fullUrl = appendQueryObject(fullUrl, data);
753
+ }
754
+ const timer = createFetchTimeout(timeout);
755
+ try {
756
+ const fetchOptions = {
757
+ method,
758
+ headers,
759
+ signal: timer.signal
760
+ };
761
+ if (method !== 'GET' && method !== 'HEAD') fetchOptions.body = typeof data === 'string' ? data : JSON.stringify(data || {});
762
+ const res = await fetch(fullUrl, fetchOptions);
763
+ const text = await res.text();
764
+ const resultData = parseMaybeJson(text, text);
765
+ const resultHeaders = {};
766
+ res.headers.forEach((value, key) => { resultHeaders[key] = value; });
767
+ response = { statusCode: res.status, data: resultData, header: resultHeaders, headers: resultHeaders };
768
+ } finally {
769
+ if (typeof timer.cleanup === 'function') timer.cleanup();
770
+ }
771
+ } else {
772
+ throw new Error('未找到 MicroiV8 请求适配器。');
773
+ }
774
+ }
775
+
776
+ const statusCode = response.statusCode || response.status || 200;
777
+ const body = response.data === undefined ? response.body : response.data;
778
+ const headersReturned = response.header || response.headers || {};
779
+ handleReturnedToken(headersReturned);
780
+
781
+ if (options.auth !== false && isAuthExpired(body, statusCode)) {
782
+ handleAuthExpired(body);
783
+ if (options.silentError !== true) toast((body && body.Msg) || '登录已过期');
784
+ throw body || new Error('登录已过期');
785
+ }
786
+
787
+ if (statusCode >= 400) {
788
+ const error = body || new Error(`请求失败: ${statusCode}`);
789
+ if (options.silentError !== true) toast((body && body.Msg) || `请求失败: ${statusCode}`);
790
+ throw error;
791
+ }
792
+
793
+ if (options.checkCode && body && body.Code !== 1) {
794
+ if (options.silentError !== true) toast(body.Msg || '请求失败');
795
+ throw body;
796
+ }
797
+
798
+ return body;
799
+ };
800
+
801
+ return runQueued(perform);
802
+ }
803
+
804
+ function get(url, data = {}, options = {}) {
805
+ return request({ ...options, url, data, method: 'GET' });
806
+ }
807
+
808
+ function post(url, data = {}, options = {}) {
809
+ return request({ ...options, url, data, method: 'POST' });
810
+ }
811
+
812
+ function postForm(url, data = {}, options = {}) {
813
+ const body = new URLSearchParams();
814
+ const formData = config.osClient && data.OsClient === undefined
815
+ ? { OsClient: config.osClient, ...data }
816
+ : data;
817
+ Object.keys(formData || {}).forEach((key) => {
818
+ const value = formData[key];
819
+ if (value !== undefined && value !== null) body.set(key, String(value));
820
+ });
821
+ return request({
822
+ ...options,
823
+ url,
824
+ data: body.toString(),
825
+ method: 'POST',
826
+ headers: {
827
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
828
+ ...(options.headers || {})
829
+ }
830
+ });
831
+ }
832
+
833
+ async function refreshToken() {
834
+ if (refreshTokenPromise) return refreshTokenPromise;
835
+ const oldToken = getToken();
836
+ if (!oldToken) return { Code: 1001, Msg: '请求未携带Token,请重新登录。' };
837
+
838
+ refreshTokenPromise = request({
839
+ url: '/api/SysUser/refreshToken',
840
+ method: 'POST',
841
+ auth: false,
842
+ checkCode: false,
843
+ silentError: true,
844
+ headers: {
845
+ Authorization: `Bearer ${normalizeBearer(oldToken)}`,
846
+ Token: normalizeBearer(oldToken)
847
+ },
848
+ data: {
849
+ authorization: normalizeBearer(oldToken),
850
+ OsClient: config.osClient || undefined,
851
+ _ClientType: config.clientType || undefined
852
+ }
853
+ }).then((result) => {
854
+ if (result && result.Code !== 1 && isAuthExpired(result)) {
855
+ handleAuthExpired(result);
856
+ }
857
+ return result;
858
+ }).finally(() => {
859
+ refreshTokenPromise = null;
860
+ });
861
+ return refreshTokenPromise;
862
+ }
863
+
864
+ async function resumeAuthSession(force = false) {
865
+ const token = getToken();
866
+ if (!token) return { Code: 1001, Msg: '请求未携带Token,请重新登录。' };
867
+ if (!force && !shouldRefreshToken(token)) return { Code: 1, Data: { Refreshed: false } };
868
+ return refreshToken();
869
+ }
870
+
871
+ function stopTokenMaintenance() {
872
+ if (tokenMaintenanceTimer) {
873
+ clearInterval(tokenMaintenanceTimer);
874
+ tokenMaintenanceTimer = null;
875
+ }
876
+ if (typeof stopBrowserResumeListeners === 'function') {
877
+ stopBrowserResumeListeners();
878
+ stopBrowserResumeListeners = null;
879
+ }
880
+ }
881
+
882
+ function startTokenMaintenance(options = {}) {
883
+ stopTokenMaintenance();
884
+ const intervalMs = Math.max(60 * 1000, Number(options.intervalMs || 60 * 1000));
885
+ const maintain = () => { void resumeAuthSession(false); };
886
+ tokenMaintenanceTimer = setInterval(maintain, intervalMs);
887
+ if (hasWindow()) {
888
+ const onResume = () => {
889
+ if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
890
+ maintain();
891
+ };
892
+ document.addEventListener('visibilitychange', onResume);
893
+ window.addEventListener('focus', onResume);
894
+ window.addEventListener('pageshow', onResume);
895
+ stopBrowserResumeListeners = () => {
896
+ document.removeEventListener('visibilitychange', onResume);
897
+ window.removeEventListener('focus', onResume);
898
+ window.removeEventListener('pageshow', onResume);
899
+ };
900
+ }
901
+ maintain();
902
+ return stopTokenMaintenance;
903
+ }
904
+
905
+ // 资源地址统一过滤占位图,并兼容 HDFS 私有文件、FileServer 和绝对地址。
906
+ function assetUrl(value) {
907
+ const picked = extractUploadPath(value);
908
+ if (!picked || isBlockedAsset(picked)) return '';
909
+ if (/^(https?:|data:|blob:|file:)/i.test(picked)) return picked;
910
+ if (/^\/?file\//i.test(picked)) return joinUrl(config.apiBase, picked);
911
+ if (/^\//.test(picked) || /^[a-z0-9_-]+\//i.test(picked)) return joinUrl(config.fileServer || config.apiBase, picked);
912
+ return picked;
913
+ }
914
+
915
+ function isBlockedAsset(value) {
916
+ return config.blockedAssetPattern ? config.blockedAssetPattern.test(String(value || '')) : false;
917
+ }
918
+
919
+ async function resolveFileUrl(filePathName) {
920
+ const path = extractUploadPath(filePathName);
921
+ if (!path || isBlockedAsset(path)) return '';
922
+ if (/^(https?:|blob:|data:|file:)/i.test(path)) return assetUrl(path);
923
+
924
+ async function requestPrivate(action) {
925
+ try {
926
+ const body = await post(`/api/HDFS/${action}?FilePathName=${encodeURIComponent(path)}`, { OsClient: config.osClient }, {
927
+ checkCode: false,
928
+ silentError: true
929
+ });
930
+ if (body && body.Code === 1 && body.Data) return normalizeFileUrlData(body.Data, assetUrl, path);
931
+ } catch (e) {}
932
+ return '';
933
+ }
934
+
935
+ return (await requestPrivate('GetPrivateFileUrl')) || (await requestPrivate('MallFileUrl')) || assetUrl(path);
936
+ }
937
+
938
+ // 文件上传同时支持 uni.uploadFile 与浏览器 fetch/FormData。
939
+ async function uploadFile(filePath, options = {}) {
940
+ const runtimeUni = getUni();
941
+ const action = options.action || (options.anonymous ? 'UniappUploadAnonymous' : 'UniappUpload');
942
+ const rawFormData = options.formData || {};
943
+ const uploadData = {
944
+ ...rawFormData,
945
+ OsClient: config.osClient,
946
+ Limit: options.limit === false ? 'false' : 'true',
947
+ Preview: options.preview === false ? 'false' : 'true',
948
+ Multiple: options.multiple ? 'true' : 'false'
949
+ };
950
+ uploadData.Path = normalizeClientUploadPath(options.path || uploadData.Path || uploadData.path || 'upload');
951
+ delete uploadData.path;
952
+
953
+ let body;
954
+ const fetchSource = pickUploadFileSource(filePath, options);
955
+ const canFetchUpload = typeof fetch === 'function' && typeof FormData !== 'undefined' &&
956
+ (!!pickUploadFileLike(options.file) || !!pickUploadFileLike(filePath) || /^(blob:|data:)/i.test(fetchSource));
957
+ const uploadByFetch = async () => {
958
+ const picked = await resolveFetchUploadFile(filePath, options);
959
+ const file = picked.file;
960
+ if (!file) throw new Error('未提供上传文件。');
961
+ const formData = new FormData();
962
+ Object.keys(uploadData).forEach((key) => formData.append(key, uploadData[key]));
963
+ formData.append(options.name || 'file', file, picked.name || (file && file.name) || 'file');
964
+ const res = await fetch(buildUrl(options.url || `/api/HDFS/${action}`), {
965
+ method: 'POST',
966
+ headers: buildUploadHeaders({ ...options, headers: options.headers || {} }),
967
+ body: formData
968
+ });
969
+ handleReturnedToken(res.headers);
970
+ const text = await res.text();
971
+ return parseMaybeJson(text, text);
972
+ };
973
+
974
+ if (options.preferFetch === true && canFetchUpload) {
975
+ try {
976
+ body = await uploadByFetch();
977
+ } catch (e) {
978
+ if (!(runtimeUni && typeof runtimeUni.uploadFile === 'function')) throw e;
979
+ }
980
+ }
981
+ if (!body) {
982
+ if (runtimeUni && typeof runtimeUni.uploadFile === 'function') {
983
+ try {
984
+ body = await new Promise((resolve, reject) => {
985
+ runtimeUni.uploadFile({
986
+ url: buildUrl(options.url || `/api/HDFS/${action}`),
987
+ filePath,
988
+ name: options.name || 'file',
989
+ header: buildUploadHeaders({ ...options, headers: options.headers || {} }),
990
+ formData: uploadData,
991
+ success: (res) => {
992
+ handleReturnedToken(res.header || res.headers || {});
993
+ resolve(parseMaybeJson(res.data, res.data));
994
+ },
995
+ fail: reject
996
+ });
997
+ });
998
+ } catch (e) {
999
+ if (!canFetchUpload) throw e;
1000
+ body = await uploadByFetch();
1001
+ }
1002
+ } else if (canFetchUpload) {
1003
+ body = await uploadByFetch();
1004
+ } else {
1005
+ throw new Error('未找到 MicroiV8 上传适配器。');
1006
+ }
1007
+ }
1008
+
1009
+ if (!body || body.Code !== 1) {
1010
+ if (options.silentError !== true) toast((body && body.Msg) || '上传失败');
1011
+ throw body || new Error('上传失败');
1012
+ }
1013
+
1014
+ const data = normalizeUploadData(body);
1015
+ if (!data.Path) {
1016
+ const error = { Code: 0, Msg: '上传返回文件路径为空' };
1017
+ if (options.silentError !== true) toast(error.Msg);
1018
+ throw error;
1019
+ }
1020
+ if (!data.Url && options.resolveUrl !== false) data.Url = await resolveFileUrl(data.Path);
1021
+ return { ...body, Data: data };
1022
+ }
1023
+
1024
+ function apiEngineRun(key, data = {}, options = {}) {
1025
+ const body = { ...(data || {}) };
1026
+ if (config.osClient && body.OsClient === undefined) body.OsClient = config.osClient;
1027
+ return post(`/apiengine/${key}`, body, { apiEngine: true, checkCode: options.checkCode !== false, ...options });
1028
+ }
1029
+
1030
+ function apiEngineRunLegacy(key, data = {}, options = {}) {
1031
+ const body = { ApiEngineKey: key, OsClient: config.osClient, ...(data || {}) };
1032
+ return post('/api/ApiEngine/Run', body, { checkCode: false, ...options });
1033
+ }
1034
+
1035
+ function formEngineRequest(action, table, data = {}, options = {}) {
1036
+ const actionKey = String(action || '').toLowerCase();
1037
+ const readActions = ['gettabledata', 'getformdata', 'gettabledatatree'];
1038
+ const isRead = readActions.indexOf(actionKey) >= 0;
1039
+ const body = { OsClient: config.osClient, FormEngineKey: table, ...(data || {}) };
1040
+
1041
+ if (isRead && config.formQueryEngineKey && options.readUseQueryEngine !== false) {
1042
+ return apiEngineRun(config.formQueryEngineKey, { Action: actionKey, ...body }, { checkCode: false, ...options });
1043
+ }
1044
+
1045
+ return post(`/api/formengine/${actionKey}-${table}`, body, { checkCode: false, ...options });
1046
+ }
1047
+
1048
+ function formEngineAnonymous(action, table, data = {}, options = {}) {
1049
+ const name = String(action || '');
1050
+ const body = { FormEngineKey: table, OsClient: config.osClient, ...(data || {}) };
1051
+ return post(`/api/FormEngine/${name}`, body, { auth: false, checkCode: false, ...options });
1052
+ }
1053
+
1054
+ function withCallback(promise, callback) {
1055
+ if (typeof callback === 'function') {
1056
+ promise.then((result) => callback(result)).catch((error) => callback(error));
1057
+ }
1058
+ return promise;
1059
+ }
1060
+
1061
+ // 兼容旧版 FormEngine 调用:既支持 (table, row, callback),也支持完整参数对象。
1062
+ function normalizeLegacyFormArgs(first, second, third, rowModelMode = false) {
1063
+ let data = {};
1064
+ let callback = third;
1065
+ if (typeof first === 'string') {
1066
+ const source = second && typeof second === 'object' ? second : {};
1067
+ data.FormEngineKey = first;
1068
+ if (rowModelMode) {
1069
+ data._RowModel = {};
1070
+ Object.keys(source).forEach((key) => {
1071
+ if (key === 'Id') data.Id = source[key];
1072
+ else data._RowModel[key] = source[key];
1073
+ });
1074
+ } else {
1075
+ data = { ...data, ...source };
1076
+ }
1077
+ if (typeof second === 'function') callback = second;
1078
+ } else {
1079
+ data = first && typeof first === 'object' ? { ...first } : {};
1080
+ callback = typeof second === 'function' ? second : third;
1081
+ }
1082
+ if (config.osClient && data.OsClient === undefined) data.OsClient = config.osClient;
1083
+ return { data, callback };
1084
+ }
1085
+
1086
+ // zhy:表单写入的权限、租户和执行控制参数必须保留在请求外层,不能混入业务表单字段。
1087
+ const formWriteOuterKeys = new Set([
1088
+ 'FormEngineKey', 'OsClient', 'Id', '_SysMenuId', '_TableChildAuth',
1089
+ '_InvokeType', '_NotSaveField', '_NoLineForAdd', '_ForceUpt', '_DataLog', '_Lang'
1090
+ ]);
1091
+
1092
+ // zhy:同时兼容新版小写请求选项与旧 SDK 的首字母大写选项,避免切换标准接口后丢失请求头。
1093
+ function normalizeFormWriteOptions(options = {}) {
1094
+ return {
1095
+ ...options,
1096
+ Header: options.Header || options.Headers || options.header || options.headers || {},
1097
+ Auth: options.Auth !== undefined ? options.Auth : options.auth,
1098
+ Timeout: options.Timeout || options.timeout,
1099
+ SilentError: options.SilentError === true || options.silentError === true
1100
+ };
1101
+ }
1102
+
1103
+ // zhy:字符串更新重载先读取服务端最新完整记录,再合并局部修改,满足客户端表单事件的完整数据契约。
1104
+ function formEngineUpdate(first, second, third) {
1105
+ if (typeof first !== 'string') {
1106
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1107
+ return withCallback(legacyPost(legacyApi.UptFormData, data), callback);
1108
+ }
1109
+
1110
+ const source = second && typeof second === 'object' ? { ...second } : {};
1111
+ const callback = typeof third === 'function' ? third : null;
1112
+ const options = normalizeFormWriteOptions(third && typeof third === 'object' ? third : {});
1113
+ const rowId = source.Id;
1114
+ const outer = { FormEngineKey: first, Id: rowId };
1115
+ const patch = {};
1116
+
1117
+ // zhy:拆分外层控制参数与业务字段,并兼容调用方显式传入 _RowModel/_FormData。
1118
+ Object.keys(source).forEach((key) => {
1119
+ if (key === '_FormData' || key === '_RowModel') return;
1120
+ if (formWriteOuterKeys.has(key)) outer[key] = source[key];
1121
+ else patch[key] = source[key];
1122
+ });
1123
+ Object.assign(patch, source._RowModel || {}, source._FormData || {});
1124
+ if (config.osClient && outer.OsClient === undefined) outer.OsClient = config.osClient;
1125
+
1126
+ const promise = (async () => {
1127
+ if (rowId === undefined || rowId === null || rowId === '') {
1128
+ return { Code: 0, Msg: 'UptFormData requires Id.' };
1129
+ }
1130
+ // zhy:携带相同菜单和子表授权上下文读取最新记录,防止绕过行级权限或使用页面旧快照覆盖新数据。
1131
+ const readParam = {
1132
+ FormEngineKey: first,
1133
+ Id: rowId,
1134
+ ...(outer._SysMenuId ? { _SysMenuId: outer._SysMenuId } : {}),
1135
+ ...(outer._TableChildAuth ? { _TableChildAuth: outer._TableChildAuth } : {})
1136
+ };
1137
+ const current = await legacyPost(legacyApi.GetFormData, readParam, null, options);
1138
+ if (!current || Number(current.Code) !== 1 || !current.Data) return current;
1139
+ // zhy:以服务端最新记录为基准合并 patch,并按 PC 平台一致的完整 _FormData 契约提交。
1140
+ const formData = { ...current.Data, ...patch, Id: rowId };
1141
+ return legacyPost(legacyApi.UptFormData, { ...outer, _FormData: formData }, null, options);
1142
+ })();
1143
+ return withCallback(promise, callback);
1144
+ }
1145
+
1146
+ async function legacyPost(url, data = {}, callback, option = {}) {
1147
+ const body = await request({
1148
+ url,
1149
+ data: data || {},
1150
+ method: option.Method || 'POST',
1151
+ headers: option.Header || option.Headers || {},
1152
+ apiEngine: !!option.IsApiEngine,
1153
+ auth: option.Auth !== false,
1154
+ timeout: option.Timeout,
1155
+ checkCode: false,
1156
+ silentError: option.SilentError === true
1157
+ });
1158
+ const result = body && typeof body === 'object' ? { ...body, Headers: body.Headers || {} } : body;
1159
+ if (typeof callback === 'function') callback(result, result && result.Headers);
1160
+ return result;
1161
+ }
1162
+
1163
+ async function legacyGet(url, data = {}, callback, option = {}) {
1164
+ const body = await request({
1165
+ url,
1166
+ data: data || {},
1167
+ method: 'GET',
1168
+ headers: option.Header || option.Headers || {},
1169
+ apiEngine: !!option.IsApiEngine,
1170
+ auth: option.Auth !== false,
1171
+ timeout: option.Timeout,
1172
+ responseType: option.ResponseType,
1173
+ checkCode: false,
1174
+ silentError: option.SilentError === true
1175
+ });
1176
+ const result = option.ResponseType === 'arraybuffer'
1177
+ ? { Code: 1, Data: body, Headers: {} }
1178
+ : (body && typeof body === 'object' ? { ...body, Headers: body.Headers || {} } : body);
1179
+ if (typeof callback === 'function') callback(result, result && result.Headers);
1180
+ return result;
1181
+ }
1182
+
1183
+ async function legacyRawRequest(param = {}) {
1184
+ const method = param.Method || param.method || 'POST';
1185
+ const url = param.Url || param.url || param.path || '';
1186
+ const data = param.Data || param.Param || param.data || {};
1187
+ const body = await request({
1188
+ url,
1189
+ data,
1190
+ method,
1191
+ headers: param.Header || param.Headers || param.headers || {},
1192
+ apiEngine: !!param.IsApiEngine,
1193
+ auth: param.Auth !== false,
1194
+ timeout: param.Timeout,
1195
+ responseType: param.ResponseType,
1196
+ checkCode: false,
1197
+ silentError: param.SilentError === true
1198
+ });
1199
+ return { data: body, headers: body && body.Headers ? body.Headers : {} };
1200
+ }
1201
+
1202
+ function legacyOpen(url) {
1203
+ const runtimeUni = getUni();
1204
+ if (runtimeUni && typeof runtimeUni.navigateTo === 'function') {
1205
+ runtimeUni.navigateTo({ url });
1206
+ return;
1207
+ }
1208
+ if (hasWindow()) window.location.href = url;
1209
+ }
1210
+
1211
+ function legacyNavigateTo(url, isVerify) {
1212
+ if (isVerify && !legacyIsLogin()) {
1213
+ if (config.loginUrl) legacyOpen(config.loginUrl);
1214
+ else toast('请登录');
1215
+ return;
1216
+ }
1217
+ legacyOpen(url);
1218
+ }
1219
+
1220
+ function legacyGetCurrentUser(refresh, callback) {
1221
+ if (refresh) {
1222
+ legacyPost('/api/SysUser/getCurrentUser', {}, (result) => {
1223
+ if (result && result.Code) legacySetCurrentUser(result.Data || {});
1224
+ if (typeof callback === 'function') callback(result);
1225
+ });
1226
+ }
1227
+ return getUser() || deserializeUser(storage.get('CurrentUser')) || {};
1228
+ }
1229
+
1230
+ function legacySetCurrentUser(user) {
1231
+ setUser(user || {});
1232
+ storage.set('CurrentUser', serializeUser(user || {}));
1233
+ }
1234
+
1235
+ function legacyGetToken() {
1236
+ return getToken() || storage.get('Token') || storage.get('authorization') || '';
1237
+ }
1238
+
1239
+ function legacySetToken(token) {
1240
+ setToken(token || '');
1241
+ storage.set('Token', token || '');
1242
+ storage.set('authorization', token || '');
1243
+ storage.set('TokenExpires', token ? formatDate(addTime(new Date(), 'm', 15), 'yyyy-MM-dd HH:mm:ss') : '');
1244
+ if (!token) legacySetCurrentUser({});
1245
+ }
1246
+
1247
+ function legacyIsLogin() {
1248
+ const user = legacyGetCurrentUser();
1249
+ return !!(legacyGetToken() && user && user.Id);
1250
+ }
1251
+
1252
+ function legacyGetUrlQuery(property, pageInstance) {
1253
+ let query = null;
1254
+ if (pageInstance) {
1255
+ query = (pageInstance.$mp && pageInstance.$mp.query) ||
1256
+ (pageInstance.$scope && pageInstance.$scope.options) ||
1257
+ (pageInstance.$page && pageInstance.$page.options) ||
1258
+ (pageInstance.$options && pageInstance.$options.pageQuery) ||
1259
+ null;
1260
+ }
1261
+ if (!query && hasWindow()) {
1262
+ query = {};
1263
+ const params = new URLSearchParams(window.location.search || '');
1264
+ params.forEach((value, key) => { query[key] = value; });
1265
+ }
1266
+ return property ? (query && query[property]) : query;
1267
+ }
1268
+
1269
+ function legacyGetStrLength(value) {
1270
+ const text = String(value || '');
1271
+ const chinese = text.match(/[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/g);
1272
+ return (chinese ? chinese.length * 2 : 0) + text.length - (chinese ? chinese.length : 0);
1273
+ }
1274
+
1275
+ function legacyTips(text, isSuccess = true, timeOrOption = {}) {
1276
+ const option = typeof timeOrOption === 'object' ? timeOrOption : { Time: timeOrOption };
1277
+ const runtimeUni = getUni();
1278
+ if (runtimeUni && typeof runtimeUni.showToast === 'function') {
1279
+ runtimeUni.showToast({
1280
+ title: String(text || ''),
1281
+ icon: option.Icon || (isSuccess === false ? 'none' : 'success'),
1282
+ duration: option.Time || (isSuccess === false ? 2000 : 1000)
1283
+ });
1284
+ return;
1285
+ }
1286
+ toast(text);
1287
+ }
1288
+
1289
+ function legacyConfirmTips(content, callback, option = {}) {
1290
+ const runtimeUni = getUni();
1291
+ if (runtimeUni && typeof runtimeUni.showModal === 'function') {
1292
+ runtimeUni.showModal({
1293
+ title: option.Title || '提示',
1294
+ content: String(content || ''),
1295
+ showCancel: option.ShowCancel === false ? false : true,
1296
+ confirmColor: option.OKColor || '#5677fc',
1297
+ confirmText: option.OKText || '确定',
1298
+ success(res) {
1299
+ if (res.confirm && typeof callback === 'function') callback(res);
1300
+ if (!res.confirm && typeof option.CancelCallback === 'function') option.CancelCallback(res);
1301
+ }
1302
+ });
1303
+ return;
1304
+ }
1305
+ confirm(content).then((ok) => {
1306
+ if (ok && typeof callback === 'function') callback();
1307
+ if (!ok && typeof option.CancelCallback === 'function') option.CancelCallback();
1308
+ });
1309
+ }
1310
+
1311
+ function legacyLoading(title, mask = true) {
1312
+ const runtimeUni = getUni();
1313
+ if (runtimeUni && typeof runtimeUni.showLoading === 'function') {
1314
+ runtimeUni.showLoading({ title: title || '请稍候...', mask });
1315
+ }
1316
+ }
1317
+
1318
+ function legacyHideLoading() {
1319
+ const runtimeUni = getUni();
1320
+ if (runtimeUni && typeof runtimeUni.hideLoading === 'function') runtimeUni.hideLoading();
1321
+ }
1322
+
1323
+ async function legacyUpload(param = {}, callback) {
1324
+ if (!param.File && !param.file && !param.filePath) {
1325
+ const error = { Code: 0, Msg: '前端参数错误!' };
1326
+ if (typeof callback === 'function') callback(error);
1327
+ return error;
1328
+ }
1329
+ try {
1330
+ legacyLoading('上传中...');
1331
+ const result = await uploadFile(param.File || param.filePath || param.file, {
1332
+ file: param.FileObject || param.fileObject || param.file,
1333
+ fileName: param.FileName || param.fileName,
1334
+ path: param.Path || param.path || 'upload',
1335
+ limit: param.Limit,
1336
+ preview: param.Preview,
1337
+ anonymous: !!param._Anonymous,
1338
+ name: param.Name || param.name || 'file',
1339
+ formData: param
1340
+ });
1341
+ if (typeof callback === 'function') callback(result);
1342
+ return result;
1343
+ } catch (error) {
1344
+ const result = error && error.Code !== undefined ? error : { Code: 0, Data: error, Msg: error && error.message ? error.message : '上传失败' };
1345
+ if (typeof callback === 'function') callback(result);
1346
+ return result;
1347
+ } finally {
1348
+ legacyHideLoading();
1349
+ }
1350
+ }
1351
+
1352
+ function base64ToBlob(dataURI) {
1353
+ const byteString = atob(String(dataURI).split(',')[1] || '');
1354
+ const mimeString = String(dataURI).split(',')[0].split(':')[1].split(';')[0];
1355
+ const buffer = new ArrayBuffer(byteString.length);
1356
+ const view = new Uint8Array(buffer);
1357
+ for (let i = 0; i < byteString.length; i += 1) view[i] = byteString.charCodeAt(i);
1358
+ return new Blob([buffer], { type: mimeString });
1359
+ }
1360
+
1361
+ function base64ToFile(dataurl, filename = 'file') {
1362
+ const blob = base64ToBlob(dataurl);
1363
+ if (typeof File !== 'undefined') return new File([blob], filename, { type: blob.type });
1364
+ blob.name = filename;
1365
+ return blob;
1366
+ }
1367
+
1368
+ function legacyDownload(url, option = {}, callback) {
1369
+ const runtimeUni = getUni();
1370
+ if (runtimeUni && typeof runtimeUni.downloadFile === 'function') {
1371
+ legacyLoading('下载中...');
1372
+ return new Promise((resolve) => {
1373
+ runtimeUni.downloadFile({
1374
+ url,
1375
+ ...(option || {}),
1376
+ success(res) {
1377
+ const result = { Code: res.statusCode === 200 ? 1 : 0, Data: res, Msg: res.errMsg || '' };
1378
+ if (typeof callback === 'function') callback(result);
1379
+ resolve(result);
1380
+ },
1381
+ fail(err) {
1382
+ const result = { Code: 0, Data: err, Msg: err.errMsg || '下载失败' };
1383
+ if (typeof callback === 'function') callback(result);
1384
+ resolve(result);
1385
+ },
1386
+ complete() {
1387
+ legacyHideLoading();
1388
+ }
1389
+ });
1390
+ });
1391
+ }
1392
+ return legacyGet(url, {}, callback, option);
1393
+ }
1394
+
1395
+ function install(app, options = {}) {
1396
+ if (Object.keys(options).length) configure(options);
1397
+ if (!app || !app.config) return client;
1398
+ app.config.globalProperties.$V8 = client;
1399
+ app.config.globalProperties.$Microi = client;
1400
+ app.config.globalProperties.V8 = client;
1401
+ if (typeof app.provide === 'function') app.provide('MicroiV8', client);
1402
+ return client;
1403
+ }
1404
+
1405
+ // 现代接口:新项目优先使用这些小写方法和命名空间。
1406
+ const client = {
1407
+ get config() {
1408
+ return config;
1409
+ },
1410
+ storage,
1411
+ configure,
1412
+ install,
1413
+ request,
1414
+ get,
1415
+ post,
1416
+ postForm,
1417
+ toast,
1418
+ confirm,
1419
+ getToken,
1420
+ setToken,
1421
+ clearToken,
1422
+ removeToken: clearToken,
1423
+ getDid,
1424
+ readTokenClaims,
1425
+ shouldRefreshToken,
1426
+ refreshToken,
1427
+ resumeAuthSession,
1428
+ startTokenMaintenance,
1429
+ stopTokenMaintenance,
1430
+ getUser,
1431
+ setUser,
1432
+ setCurrentUser: setUser,
1433
+ getCurrentUser: getUser,
1434
+ assetUrl,
1435
+ sanitizeAssetUrl: assetUrl,
1436
+ resolveAssetUrl: assetUrl,
1437
+ resolveAvatarUrl: resolveFileUrl,
1438
+ resolveFileUrl,
1439
+ isBlockedAsset,
1440
+ extractUploadPath,
1441
+ normalizeUploadValue,
1442
+ uploadFile,
1443
+ getSafeArea,
1444
+ formatDate,
1445
+ toNumber,
1446
+ maskPhone,
1447
+ formatCompactNumber,
1448
+ ApiEngine: {
1449
+ Run: apiEngineRun,
1450
+ RunLegacy: apiEngineRunLegacy
1451
+ },
1452
+ FormEngine: {
1453
+ Request: formEngineRequest,
1454
+ GetTableData: (table, data, options) => formEngineRequest('gettabledata', table, data, options),
1455
+ GetFormData: (table, data, options) => formEngineRequest('getformdata', table, data, options),
1456
+ GetTableDataTree: (table, data, options) => formEngineRequest('gettabledatatree', table, data, options),
1457
+ AddFormData: (table, data, options) => formEngineRequest('addformdata', table, data, options),
1458
+ UptFormData: (table, data, options) => formEngineRequest('uptformdata', table, data, options),
1459
+ DelFormData: (table, data, options) => formEngineRequest('delformdata', table, data, options),
1460
+ GetTableDataAnonymous: (table, data, options) => formEngineAnonymous('GetTableDataAnonymous', table, data, options),
1461
+ GetFormDataAnonymous: (table, data, options) => formEngineAnonymous('GetFormDataAnonymous', table, data, options),
1462
+ GetTableDataTreeAnonymous: (table, data, options) => formEngineAnonymous('GetTableDataTreeAnonymous', table, data, options)
1463
+ }
1464
+ };
1465
+
1466
+ // 旧版前端 V8 依赖的后端接口路径,保留原名称以减少迁移成本。
1467
+ const legacyApi = {
1468
+ MicroiInit: '/apiengine/microi-init',
1469
+ GetSysConfig: '/api/DiyTable/getSysConfig',
1470
+ Login: '/api/SysUser/login',
1471
+ AddFormData: '/api/FormEngine/addFormData',
1472
+ AddFormDataBatch: '/api/FormEngine/addFormDataBatch',
1473
+ DelFormData: '/api/FormEngine/delFormData',
1474
+ DelFormDataBatch: '/api/FormEngine/delFormDataBatch',
1475
+ DelFormDataByWhere: '/api/FormEngine/delFormDataByWhere',
1476
+ UptFormData: '/api/FormEngine/uptFormData',
1477
+ UptFormDataBatch: '/api/FormEngine/uptFormDataBatch',
1478
+ UptFormDataByWhere: '/api/FormEngine/uptFormDataByWhere',
1479
+ GetFormData: '/api/FormEngine/getFormData',
1480
+ GetFormDataAnonymous: '/api/FormEngine/getFormDataAnonymous',
1481
+ GetTableData: '/api/FormEngine/getTableData',
1482
+ GetTableDataAnonymous: '/api/FormEngine/GetTableDataAnonymous',
1483
+ GetTableDataTree: '/api/FormEngine/getTableDataTree',
1484
+ GetTableDataTreeAnonymous: '/api/FormEngine/getTableDataTreeAnonymous',
1485
+ ApiEngineRun: '/api/ApiEngine/run',
1486
+ ModuleEngineRun: '/api/ModuleEngine/run',
1487
+ RefreshToken: '/api/SysUser/refreshToken',
1488
+ RefreshLoginUser: '/api/SysUser/refreshLoginUser',
1489
+ Upload: '/api/HDFS/Upload',
1490
+ UploadAnonymous: '/api/HDFS/uploadAnonymous',
1491
+ UniappUpload: '/api/HDFS/UniappUpload',
1492
+ UniappUploadAnonymous: '/api/HDFS/uniappUploadAnonymous',
1493
+ GetCurrentUser: '/api/SysUser/getCurrentUser',
1494
+ GetDateTimeNow: '/api/os/getDateTimeNow',
1495
+ AddSysLog: '/api/SysLog/addSysLog',
1496
+ GetOsClientByDomain: '/api/Os/getOsClientByDomain',
1497
+ ApiEngine: {}
1498
+ };
1499
+
1500
+ // 旧版接口:尽量保持历史项目里的调用名、字段名和回调形态。
1501
+ Object.assign(client, {
1502
+ Store: null,
1503
+ IDE: getUni() ? 'UniApp' : 'PCVue3',
1504
+ AppLogo: '',
1505
+ AppKey: '',
1506
+ H5Url: config.webBase || '',
1507
+ DateTimeNow: new Date(),
1508
+ ClientType: getUni() ? 'H5' : 'Web',
1509
+ ClientSystem: '',
1510
+ PageUrlLogin: config.loginUrl || '',
1511
+ PageSizes: [10, 20, 50, 100],
1512
+ SysConfig: {},
1513
+ SafeArea: getSafeArea(),
1514
+ Api: legacyApi,
1515
+ Extend: {
1516
+ Open: legacyOpen,
1517
+ DateTimeFormat: formatDate,
1518
+ DateDiff: diffTime,
1519
+ Add0(value, length) {
1520
+ return String(value || '').padStart(Number(length || 0), '0');
1521
+ }
1522
+ },
1523
+ Window: {},
1524
+ Form: {},
1525
+ IsNull(value) {
1526
+ return value === null || value === undefined || value === '' || value === 'undefined' || value === 'null';
1527
+ },
1528
+ IsNotNull(value) {
1529
+ return !client.IsNull(value);
1530
+ },
1531
+ FormSet(fieldName, value) {
1532
+ client.Form[fieldName] = value;
1533
+ },
1534
+ Run(v8Code) {
1535
+ return Function('V8', `"use strict"; return (async function(){${v8Code || ''}\n}).call(V8);`)(client);
1536
+ },
1537
+ Open: legacyOpen,
1538
+ GetFileServerUrl: assetUrl,
1539
+ GetStorageSync: storage.get,
1540
+ SetStorageSync: storage.set,
1541
+ GetOsClientByDomain: async function getOsClientByDomain(getCache) {
1542
+ const cached = getCache ? storage.get('OsClient') : '';
1543
+ if (cached) {
1544
+ configure({ osClient: cached });
1545
+ return { Code: 1, Data: { OsClient: cached } };
1546
+ }
1547
+ const domain = hasWindow() ? window.location.host.toLowerCase() : '';
1548
+ const result = await legacyPost(legacyApi.GetOsClientByDomain, { Domain: domain }, null, { SilentError: true });
1549
+ if (result && result.Code === 1 && result.Data && result.Data.OsClient) {
1550
+ configure({ osClient: result.Data.OsClient });
1551
+ storage.set('OsClient', result.Data.OsClient);
1552
+ }
1553
+ return result;
1554
+ },
1555
+ GetSysConfig: async function getSysConfig(refresh) {
1556
+ if (!refresh) {
1557
+ const cached = storage.get('SysConfig');
1558
+ if (cached) return parseMaybeJson(cached, {});
1559
+ }
1560
+ const result = await legacyPost(legacyApi.GetSysConfig, {
1561
+ OsClient: config.osClient,
1562
+ _SearchEqual: { IsEnable: 1 }
1563
+ }, null, { SilentError: true });
1564
+ if (result && result.Code === 1) {
1565
+ const model = result.Data || {};
1566
+ client.SysConfig = model;
1567
+ if (model.FileServer) configure({ fileServer: model.FileServer });
1568
+ if (model.H5Url) client.H5Url = model.H5Url;
1569
+ if (model.AppLogo) client.AppLogo = model.AppLogo;
1570
+ storage.set('SysConfig', JSON.stringify(model));
1571
+ return model;
1572
+ }
1573
+ return null;
1574
+ },
1575
+ GetSysConfigSync() {
1576
+ return client.SysConfig && Object.keys(client.SysConfig).length
1577
+ ? client.SysConfig
1578
+ : parseMaybeJson(storage.get('SysConfig'), {});
1579
+ },
1580
+ SetSysConfig(sysConfig) {
1581
+ const model = typeof sysConfig === 'string' ? parseMaybeJson(sysConfig, {}) : (sysConfig || {});
1582
+ client.SysConfig = model;
1583
+ storage.set('SysConfig', JSON.stringify(model));
1584
+ },
1585
+ InitDateTimeTimer: null,
1586
+ InitDateTimeNow() {
1587
+ return legacyPost(legacyApi.GetDateTimeNow, {}, (result) => {
1588
+ if (result && result.Code) {
1589
+ client.DateTimeNow = new Date(result.Data);
1590
+ if (client.InitDateTimeTimer) clearInterval(client.InitDateTimeTimer);
1591
+ client.InitDateTimeTimer = setInterval(() => {
1592
+ client.DateTimeNow = addTime(client.DateTimeNow, 's', 1);
1593
+ }, 1000);
1594
+ }
1595
+ });
1596
+ },
1597
+ RefreshLoginUser: async function refreshLoginUser() {
1598
+ const result = await legacyPost(legacyApi.RefreshLoginUser, {});
1599
+ if (result && result.Code) legacySetCurrentUser(result.Data || {});
1600
+ return result;
1601
+ },
1602
+ RefreshToken: async function refreshToken(callback) {
1603
+ const token = legacyGetToken();
1604
+ if (!token) return { Code: 0, Msg: 'Token 为空。' };
1605
+ const result = await client.refreshToken();
1606
+ if (result && result.Code) legacySetCurrentUser(result.Data || {});
1607
+ if (typeof callback === 'function') callback(result);
1608
+ return result;
1609
+ },
1610
+ ArrayBufferToBase64(arrayBuffer) {
1611
+ const bytes = new Uint8Array(arrayBuffer);
1612
+ let binary = '';
1613
+ bytes.forEach((byte) => { binary += String.fromCharCode(byte); });
1614
+ if (typeof btoa === 'function') return btoa(binary);
1615
+ return binary;
1616
+ },
1617
+ GetCurrentUser: legacyGetCurrentUser,
1618
+ SetCurrentUser: legacySetCurrentUser,
1619
+ GetToken: legacyGetToken,
1620
+ SetToken: legacySetToken,
1621
+ Login(param) {
1622
+ const loginParam = { ...(param || {}) };
1623
+ if (!loginParam._ClientType) loginParam._ClientType = config.clientType || (getUni() ? 'Mobile' : 'PC');
1624
+ return legacyPost(legacyApi.Login, loginParam, (result) => {
1625
+ if (result && result.Code) legacySetCurrentUser(result.Data || {});
1626
+ }, { DataType: 'form' });
1627
+ },
1628
+ Logout() {
1629
+ legacySetToken('');
1630
+ },
1631
+ Tips: legacyTips,
1632
+ Msg: legacyTips,
1633
+ GetStrLength: legacyGetStrLength,
1634
+ ConfirmTips: legacyConfirmTips,
1635
+ IsAndroid() {
1636
+ return String(getSafeArea().platform || '').toLowerCase() === 'android';
1637
+ },
1638
+ IsPhoneX() {
1639
+ const area = getSafeArea();
1640
+ return area.bottom > 0;
1641
+ },
1642
+ Loading: legacyLoading,
1643
+ ShowLoading: legacyLoading,
1644
+ HideLoading: legacyHideLoading,
1645
+ Post: legacyPost,
1646
+ PostAsync: legacyPost,
1647
+ Get: legacyGet,
1648
+ PostAll(allParams = [], callback) {
1649
+ return withCallback(Promise.all(allParams.map((item) => legacyPost(item.Url || item.url, item.Data || item.Param || item.data || {}, null, item))), callback);
1650
+ },
1651
+ request(options = {}) {
1652
+ if (options && (options.Url || options.Data || options.Method || options.Param)) return legacyRawRequest(options);
1653
+ return request(options);
1654
+ },
1655
+ GetClientType() {
1656
+ return getUni() ? 'H5' : 'Web';
1657
+ },
1658
+ GetClientSystem() {
1659
+ return getSafeArea().platform || '';
1660
+ },
1661
+ AddSysLog(param) {
1662
+ return legacyPost(legacyApi.AddSysLog, param || {}, null, { DataType: 'form', SilentError: true });
1663
+ },
1664
+ CheckResult(result) {
1665
+ if (!result || typeof result !== 'object') return false;
1666
+ if (result.Code !== 1) {
1667
+ legacyTips(result.Msg || '操作失败', false, 3000);
1668
+ return false;
1669
+ }
1670
+ return true;
1671
+ },
1672
+ IsLogin: legacyIsLogin,
1673
+ NavigateTo: legacyNavigateTo,
1674
+ RouterPush: legacyNavigateTo,
1675
+ Upload: legacyUpload,
1676
+ UploadAnonymous(param, callback) {
1677
+ return legacyUpload({ ...(param || {}), _Anonymous: true }, callback);
1678
+ },
1679
+ Download: legacyDownload,
1680
+ DownloadFile: legacyDownload,
1681
+ ImgBase64ToFile: base64ToFile,
1682
+ ImgBase63ToBlob: base64ToBlob,
1683
+ HidePhone: maskPhone,
1684
+ ImgExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'],
1685
+ IsImg(link = '') {
1686
+ const clean = String(link || '').split('?')[0].toLowerCase();
1687
+ return client.ImgExtensions.some((ext) => clean.endsWith(ext));
1688
+ },
1689
+ NavigateToMiniProgram(appId, path, param, callback) {
1690
+ const runtimeUni = getUni();
1691
+ if (runtimeUni && typeof runtimeUni.navigateToMiniProgram === 'function') {
1692
+ runtimeUni.navigateToMiniProgram({
1693
+ appId,
1694
+ path,
1695
+ extraData: param,
1696
+ success: (res) => callback && callback({ Code: 1, Data: res }),
1697
+ fail: (err) => callback && callback({ Code: 0, Data: err, Msg: err.errMsg })
1698
+ });
1699
+ }
1700
+ },
1701
+ GetUrlQuery: legacyGetUrlQuery,
1702
+ GetCountStr(count) {
1703
+ return formatCompactNumber(count, Number(count) < 100000 ? 2 : 1);
1704
+ }
1705
+ });
1706
+
1707
+ // 这些属性在旧项目中常被直接赋值,因此保留取值器和赋值器同步到 config。
1708
+ Object.defineProperties(client, {
1709
+ ApiBase: {
1710
+ get: () => config.apiBase,
1711
+ set: (value) => configure({ apiBase: value })
1712
+ },
1713
+ OsClient: {
1714
+ get: () => config.osClient,
1715
+ set: (value) => configure({ osClient: value })
1716
+ },
1717
+ FileServer: {
1718
+ get: () => config.fileServer,
1719
+ set: (value) => configure({ fileServer: value })
1720
+ }
1721
+ });
1722
+
1723
+ // 新旧两套接口引擎调用方式并存:字符串 key 走新路由,对象参数兼容旧路由。
1724
+ Object.assign(client.ApiEngine, {
1725
+ Run(urlOrKey, dataOrCallback, callback) {
1726
+ if (typeof urlOrKey === 'string') {
1727
+ const data = dataOrCallback && typeof dataOrCallback === 'object' ? dataOrCallback : {};
1728
+ const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1729
+ return withCallback(apiEngineRun(urlOrKey, data, { checkCode: false }), cb);
1730
+ }
1731
+ const param = urlOrKey || {};
1732
+ const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1733
+ const key = param.ApiEngineKey || param.apiEngineKey;
1734
+ return withCallback(key ? apiEngineRun(key, param, { checkCode: false }) : apiEngineRunLegacy('', param, { checkCode: false }), cb);
1735
+ },
1736
+ RunDirect: apiEngineRun,
1737
+ RunLegacy: apiEngineRunLegacy
1738
+ });
1739
+
1740
+ // 模块引擎和表单引擎沿用旧版命名,内部统一走 legacyPost。
1741
+ client.ModuleEngine = {
1742
+ Run(moduleKeyOrParam, dataOrCallback, callback) {
1743
+ const data = typeof moduleKeyOrParam === 'string'
1744
+ ? { ModuleEngineKey: moduleKeyOrParam, ...(dataOrCallback || {}) }
1745
+ : (moduleKeyOrParam || {});
1746
+ const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
1747
+ return withCallback(legacyPost(legacyApi.ModuleEngineRun, data), cb);
1748
+ }
1749
+ };
1750
+
1751
+ Object.assign(client.FormEngine, {
1752
+ AddFormData(first, second, third) {
1753
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third, true);
1754
+ return withCallback(legacyPost(legacyApi.AddFormData, data), callback);
1755
+ },
1756
+ AddFormDataBatch(param, callback) {
1757
+ return withCallback(legacyPost(legacyApi.AddFormDataBatch, param || {}), callback);
1758
+ },
1759
+ AddTableData(param, callback) {
1760
+ return withCallback(legacyPost(legacyApi.AddFormDataBatch, param || {}), callback);
1761
+ },
1762
+ DelFormData(param, callback) {
1763
+ return withCallback(legacyPost(legacyApi.DelFormData, param || {}), callback);
1764
+ },
1765
+ DelFormDataBatch(param, callback) {
1766
+ return withCallback(legacyPost(legacyApi.DelFormDataBatch, param || {}), callback);
1767
+ },
1768
+ DelFormDataByWhere(param, callback) {
1769
+ return withCallback(legacyPost(legacyApi.DelFormDataByWhere, param || {}), callback);
1770
+ },
1771
+ UptFormData(first, second, third) {
1772
+ // zhy:统一委托公共更新适配器,保证 UniApp、H5 和其它独立前端使用同一写入契约。
1773
+ return formEngineUpdate(first, second, third);
1774
+ },
1775
+ UptFormDataByWhere(param, callback) {
1776
+ return withCallback(legacyPost(legacyApi.UptFormDataByWhere, param || {}), callback);
1777
+ },
1778
+ UptFormDataBatch(param, callback) {
1779
+ return withCallback(legacyPost(legacyApi.UptFormDataBatch, param || {}), callback);
1780
+ },
1781
+ UptTableData(param, callback) {
1782
+ return withCallback(legacyPost(legacyApi.UptFormDataBatch, param || {}), callback);
1783
+ },
1784
+ GetFormData(first, second, third) {
1785
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1786
+ return withCallback(legacyPost(`${legacyApi.GetFormData}-${data.FormEngineKey || ''}`, data), callback);
1787
+ },
1788
+ GetFormDataAnonymous(first, second, third) {
1789
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1790
+ return withCallback(legacyPost(legacyApi.GetFormDataAnonymous, data, null, { Auth: false }), callback);
1791
+ },
1792
+ GetTableData(first, second, third) {
1793
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1794
+ return withCallback(legacyPost(legacyApi.GetTableData, data), callback);
1795
+ },
1796
+ GetTableDataAnonymous(first, second, third) {
1797
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1798
+ return withCallback(legacyPost(legacyApi.GetTableDataAnonymous, data, null, { Auth: false }), callback);
1799
+ },
1800
+ GetTableDataTree(first, second, third) {
1801
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1802
+ return withCallback(legacyPost(legacyApi.GetTableDataTree, data), callback);
1803
+ },
1804
+ GetTableDataTreeAnonymous(first, second, third) {
1805
+ const { data, callback } = normalizeLegacyFormArgs(first, second, third);
1806
+ return withCallback(legacyPost(legacyApi.GetTableDataTreeAnonymous, data, null, { Auth: false }), callback);
1807
+ }
1808
+ });
1809
+
1810
+ installDatePrototypeCompat();
1811
+ return client;
1812
+ }
1813
+
1814
+ export const V8 = createMicroiV8();
1815
+ export const MicroiV8 = V8;
1816
+ export const installMicroiV8 = (app, options = {}) => V8.install(app, options);
1817
+
1818
+ export default V8;