@microi.net/cli 5.8.5 → 5.8.6

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