@microi.net/cli 5.8.5 → 5.8.7

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 (208) 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/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/LICENSE +21 -21
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +7 -7
  9. package/assets/feature-matrix.json +138 -138
  10. package/assets/logo.svg +4 -4
  11. package/cordis.patch.yml +1 -1
  12. package/package.json +1 -1
  13. package/scripts/mcp-codex-stdio-adapter.js +0 -0
  14. package/scripts/mcp-server.js +1 -1
  15. package/scripts/microi-cli.js +66 -65
  16. package/scripts/microi-codex-broker.js +450 -450
  17. package/scripts/microi-codex-router.js +618 -618
  18. package/scripts/microi-skills.meta.json +384 -384
  19. package/skills/.microi-skills-version.json +2 -2
  20. package/skills/.progressive-disclosure-manifest.json +21 -21
  21. package/skills/README.md +287 -287
  22. package/skills/ai-engine/SKILL.md +269 -265
  23. package/skills/ai-engine/agents/openai.yaml +4 -4
  24. package/skills/ai-engine/references/ai-employees.md +48 -48
  25. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  26. package/skills/ai-platform-governance/SKILL.md +177 -177
  27. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  28. package/skills/app-store/SKILL.md +525 -525
  29. package/skills/app-store/agents/openai.yaml +4 -4
  30. package/skills/business-blueprint/SKILL.md +193 -193
  31. package/skills/datasource-engine/SKILL.md +93 -93
  32. package/skills/datasource-engine/agents/openai.yaml +4 -4
  33. package/skills/dos-orm/SKILL.md +97 -95
  34. package/skills/dos-orm/references/api-reference.md +229 -229
  35. package/skills/email-engine/SKILL.md +81 -81
  36. package/skills/email-engine/references/v8-email.md +34 -34
  37. package/skills/job-engine/SKILL.md +176 -176
  38. package/skills/job-engine/agents/openai.yaml +4 -4
  39. package/skills/message-notification/SKILL.md +156 -156
  40. package/skills/message-notification/agents/openai.yaml +5 -5
  41. package/skills/message-notification/references/contracts.md +102 -102
  42. package/skills/microi/SKILL.md +14 -14
  43. package/skills/microi-ai-app-auth.js +652 -652
  44. package/skills/microi-ai-application/SKILL.md +115 -115
  45. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  46. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  47. package/skills/microi-client-frontend/SKILL.md +246 -244
  48. 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
  49. 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
  50. 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
  51. package/skills/microi-codex/SKILL.md +102 -100
  52. package/skills/microi-codex-installer/SKILL.md +231 -231
  53. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  54. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  55. package/skills/microi-db-schema/SKILL.md +175 -175
  56. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  57. package/skills/microi-db-schema/references/core-tables.md +695 -695
  58. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  59. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  60. package/skills/microi-db-schema/references/schema.md +646 -646
  61. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  62. package/skills/microi-deployment/SKILL.md +222 -220
  63. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  64. package/skills/microi-docs-coverage/SKILL.md +133 -133
  65. package/skills/microi-docs-coverage/references/capability-map.md +91 -91
  66. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  67. package/skills/microi-form-engine/SKILL.md +333 -333
  68. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  69. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  70. package/skills/microi-form-layout/SKILL.md +205 -205
  71. 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
  72. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  73. 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
  74. package/skills/microi-left-right-layout/SKILL.md +141 -141
  75. package/skills/microi-microservice/SKILL.md +328 -326
  76. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  77. package/skills/microi-mobile-app-quality/SKILL.md +185 -185
  78. 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
  79. 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
  80. package/skills/microi-solution-quotation/SKILL.md +78 -78
  81. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  82. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  83. package/skills/microi-sso/SKILL.md +96 -90
  84. package/skills/microi-sso/references/acceptance.md +49 -49
  85. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  86. package/skills/microi-sso/references/inbound.md +53 -53
  87. package/skills/microi-sso/references/outbound.md +39 -39
  88. package/skills/microi-system-delivery/SKILL.md +137 -137
  89. 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
  90. 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
  91. package/skills/microi-ui/SKILL.md +192 -192
  92. 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
  93. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  94. 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
  95. 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
  96. package/skills/microi.v8.js +1921 -1921
  97. package/skills/module-engine/SKILL.md +255 -255
  98. package/skills/module-engine/references/module-config.md +204 -204
  99. package/skills/ocr-engine/SKILL.md +113 -113
  100. package/skills/ocr-engine/agents/openai.yaml +4 -4
  101. package/skills/page-engine/SKILL.md +206 -206
  102. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  103. 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
  104. 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
  105. package/skills/performance-testing/SKILL.md +221 -221
  106. package/skills/playwright-e2e/SKILL.md +196 -196
  107. 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
  108. 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
  109. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  110. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  111. package/skills/print-engine/SKILL.md +259 -259
  112. package/skills/production-readonly-audit/SKILL.md +41 -41
  113. package/skills/report-engine/SKILL.md +71 -71
  114. package/skills/report-engine/agents/openai.yaml +4 -4
  115. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  116. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  117. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  118. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  119. package/skills/search-engine/SKILL.md +75 -75
  120. package/skills/search-engine/agents/openai.yaml +4 -4
  121. package/skills/spider-engine/SKILL.md +190 -190
  122. package/skills/system-observability/SKILL.md +249 -246
  123. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  124. package/skills/translate-engine/SKILL.md +140 -140
  125. package/skills/translate-engine/agents/openai.yaml +4 -4
  126. package/skills/ui-design/SKILL.md +223 -223
  127. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  128. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  129. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  130. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  131. package/skills/ui-design/references/design-pattern-library.md +184 -184
  132. package/skills/ui-design/references/mci-design-contract.md +163 -163
  133. package/skills/ui-design/references/motion-and-media.md +78 -78
  134. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  135. 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
  136. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  137. 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
  138. 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
  139. 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
  140. 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
  141. 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
  142. 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
  143. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  144. package/skills/unity-integration/SKILL.md +171 -171
  145. package/skills/unity-integration/agents/openai.yaml +4 -4
  146. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  147. package/skills/unity-integration/references/sdk-api.md +82 -82
  148. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  149. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  150. package/skills/v8-api-config/SKILL.md +388 -388
  151. package/skills/v8-cache-pattern/SKILL.md +312 -312
  152. package/skills/v8-crud-api/SKILL.md +178 -178
  153. 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
  154. 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
  155. package/skills/v8-debugging/SKILL.md +284 -284
  156. package/skills/v8-explorer-tree/SKILL.md +228 -228
  157. package/skills/v8-export-import/SKILL.md +219 -219
  158. 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
  159. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  160. 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
  161. package/skills/v8-file-upload/SKILL.md +284 -284
  162. 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
  163. 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
  164. package/skills/v8-formengine-http/SKILL.md +238 -238
  165. package/skills/v8-frontend-events/SKILL.md +180 -180
  166. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  167. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  168. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  169. package/skills/v8-http-integration/SKILL.md +182 -182
  170. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  171. 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
  172. package/skills/v8-image-processing/SKILL.md +190 -190
  173. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  174. package/skills/v8-image-processing/references/api-reference.md +623 -623
  175. package/skills/v8-menu-buttons/SKILL.md +186 -186
  176. 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
  177. 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
  178. 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
  179. package/skills/v8-mongodb/SKILL.md +200 -200
  180. package/skills/v8-mq-mqtt/SKILL.md +176 -176
  181. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  182. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  183. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  184. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  185. package/skills/v8-security/SKILL.md +210 -210
  186. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  187. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  188. package/skills/v8-sql-query/SKILL.md +302 -302
  189. package/skills/v8-table-event/SKILL.md +176 -176
  190. 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
  191. 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
  192. package/skills/v8-tcp-integration/SKILL.md +147 -147
  193. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  194. package/skills/v8-template-engine/SKILL.md +167 -167
  195. package/skills/v8-utilities/SKILL.md +104 -104
  196. package/skills/v8-utilities/references/client-api-index.md +143 -143
  197. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  198. package/skills/v8-utilities/references/server-api-index.md +188 -188
  199. package/skills/v8-workflow/SKILL.md +252 -252
  200. 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
  201. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  202. package/skills/vision-engine/SKILL.md +160 -160
  203. package/skills/vision-engine/agents/openai.yaml +4 -4
  204. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  205. package/skills/workspace-conventions/SKILL.md +273 -273
  206. 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
  207. 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
  208. 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;