@puresnake/snake-ui 0.0.0-stage → 0.0.1

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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/changelog.md +1 -0
  3. package/components/snake-activity-list/snake-activity-list.vue +365 -0
  4. package/components/snake-activity-list/types.ts +29 -0
  5. package/components/snake-auxiliary-segmentation/snake-auxiliary-segmentation.vue +44 -0
  6. package/components/snake-auxiliary-segmentation/types.ts +5 -0
  7. package/components/snake-button/snake-button.vue +357 -0
  8. package/components/snake-button/types.ts +103 -0
  9. package/components/snake-button-sort/snake-button-sort.vue +72 -0
  10. package/components/snake-button-sort/types.ts +22 -0
  11. package/components/snake-calendar/snake-calendar.vue +998 -0
  12. package/components/snake-calendar/types.ts +189 -0
  13. package/components/snake-cell-group/snake-cell-group.vue +55 -0
  14. package/components/snake-cell-group/types.ts +19 -0
  15. package/components/snake-cell-item/snake-cell-item.vue +159 -0
  16. package/components/snake-cell-item/types.ts +66 -0
  17. package/components/snake-checkbox/snake-checkbox.vue +264 -0
  18. package/components/snake-checkbox/types.ts +70 -0
  19. package/components/snake-checkbox-group/snake-checkbox-group.vue +154 -0
  20. package/components/snake-checkbox-group/types.ts +52 -0
  21. package/components/snake-cms/blocks/v2/cms-v2-carousel.vue +87 -0
  22. package/components/snake-cms/blocks/v2/cms-v2-coupon.vue +25 -0
  23. package/components/snake-cms/blocks/v2/cms-v2-custom.vue +12 -0
  24. package/components/snake-cms/blocks/v2/cms-v2-divider.vue +33 -0
  25. package/components/snake-cms/blocks/v2/cms-v2-floatbutton.vue +28 -0
  26. package/components/snake-cms/blocks/v2/cms-v2-goods-list.vue +150 -0
  27. package/components/snake-cms/blocks/v2/cms-v2-hotzone.vue +41 -0
  28. package/components/snake-cms/blocks/v2/cms-v2-images.vue +41 -0
  29. package/components/snake-cms/blocks/v2/cms-v2-magiccube.vue +93 -0
  30. package/components/snake-cms/blocks/v2/cms-v2-notice.vue +80 -0
  31. package/components/snake-cms/blocks/v2/cms-v2-popup.vue +46 -0
  32. package/components/snake-cms/blocks/v2/cms-v2-richtext.vue +18 -0
  33. package/components/snake-cms/blocks/v2/cms-v2-search.vue +115 -0
  34. package/components/snake-cms/blocks/v2/cms-v2-video.vue +25 -0
  35. package/components/snake-cms/blocks/v2/shared.ts +84 -0
  36. package/components/snake-cms/snake-cms.vue +409 -0
  37. package/components/snake-cms/types.ts +50 -0
  38. package/components/snake-commodity-search/snake-commodity-search.vue +234 -0
  39. package/components/snake-commodity-search/types.ts +11 -0
  40. package/components/snake-config-provider/snake-config-provider.vue +61 -0
  41. package/components/snake-config-provider/types.ts +22 -0
  42. package/components/snake-count-down/snake-count-down.vue +231 -0
  43. package/components/snake-count-down/types.ts +103 -0
  44. package/components/snake-coupon/snake-coupon.vue +209 -0
  45. package/components/snake-coupon/types.ts +21 -0
  46. package/components/snake-custom-module/snake-custom-module.vue +25 -0
  47. package/components/snake-custom-module/types.ts +11 -0
  48. package/components/snake-drop-menu/snake-drop-menu.vue +123 -0
  49. package/components/snake-drop-menu/types.ts +95 -0
  50. package/components/snake-drop-menu-item/snake-drop-menu-item.vue +181 -0
  51. package/components/snake-drop-menu-item/types.ts +48 -0
  52. package/components/snake-empty/snake-empty.vue +100 -0
  53. package/components/snake-empty/types.ts +48 -0
  54. package/components/snake-filter-sorts/filter-page.vue +695 -0
  55. package/components/snake-filter-sorts/snake-filter-sorts.vue +185 -0
  56. package/components/snake-filter-sorts/types.ts +57 -0
  57. package/components/snake-floating-bubble/snake-floating-bubble.vue +157 -0
  58. package/components/snake-floating-bubble/types.ts +43 -0
  59. package/components/snake-follow/snake-follow.vue +18 -0
  60. package/components/snake-follow/types.ts +2 -0
  61. package/components/snake-form/snake-form.vue +124 -0
  62. package/components/snake-form/types.ts +96 -0
  63. package/components/snake-form-item/snake-form-item.vue +331 -0
  64. package/components/snake-form-item/types.ts +70 -0
  65. package/components/snake-goods-list/snake-goods-list.vue +792 -0
  66. package/components/snake-goods-list/types.ts +55 -0
  67. package/components/snake-graphic-navigation/snake-graphic-navigation.vue +198 -0
  68. package/components/snake-graphic-navigation/types.ts +43 -0
  69. package/components/snake-icon/icon-names.ts +222 -0
  70. package/components/snake-icon/snake-icon.vue +220 -0
  71. package/components/snake-icon/types.ts +45 -0
  72. package/components/snake-image/snake-image.vue +230 -0
  73. package/components/snake-image/types.ts +70 -0
  74. package/components/snake-images/snake-images.vue +80 -0
  75. package/components/snake-images/types.ts +5 -0
  76. package/components/snake-index-anchor/snake-index-anchor.vue +83 -0
  77. package/components/snake-index-anchor/types.ts +14 -0
  78. package/components/snake-index-list/anchor-registry.ts +80 -0
  79. package/components/snake-index-list/snake-index-list.vue +259 -0
  80. package/components/snake-index-list/types.ts +115 -0
  81. package/components/snake-index-list-panel/snake-index-list-panel.vue +277 -0
  82. package/components/snake-index-list-panel/types.ts +10 -0
  83. package/components/snake-input/snake-input.vue +225 -0
  84. package/components/snake-input/types.ts +99 -0
  85. package/components/snake-jump-applet/snake-jump-applet.vue +99 -0
  86. package/components/snake-jump-applet/types.ts +17 -0
  87. package/components/snake-line/snake-line.vue +42 -0
  88. package/components/snake-line/types.ts +30 -0
  89. package/components/snake-line-progress/snake-line-progress.vue +105 -0
  90. package/components/snake-line-progress/types.ts +26 -0
  91. package/components/snake-link/snake-link.vue +74 -0
  92. package/components/snake-link/types.ts +30 -0
  93. package/components/snake-list-switching/components/tree-select/index.vue +156 -0
  94. package/components/snake-list-switching/snake-list-switching.vue +1285 -0
  95. package/components/snake-list-switching/types.ts +11 -0
  96. package/components/snake-loading/snake-loading.vue +241 -0
  97. package/components/snake-loading/types.ts +34 -0
  98. package/components/snake-loadmore/snake-loadmore.vue +103 -0
  99. package/components/snake-loadmore/types.ts +56 -0
  100. package/components/snake-magic-cube/snake-magic-cube.vue +622 -0
  101. package/components/snake-magic-cube/types.ts +5 -0
  102. package/components/snake-modal/snake-modal.vue +434 -0
  103. package/components/snake-modal/types.ts +71 -0
  104. package/components/snake-navbar/snake-navbar.vue +194 -0
  105. package/components/snake-navbar/types.ts +54 -0
  106. package/components/snake-no-network/snake-no-network.vue +125 -0
  107. package/components/snake-no-network/types.ts +27 -0
  108. package/components/snake-notice/snake-notice.vue +44 -0
  109. package/components/snake-notice/types.ts +5 -0
  110. package/components/snake-notice-bar/snake-notice-bar.vue +336 -0
  111. package/components/snake-notice-bar/types.ts +82 -0
  112. package/components/snake-overlay/snake-overlay.vue +73 -0
  113. package/components/snake-overlay/types.ts +35 -0
  114. package/components/snake-page-popup/snake-page-popup.vue +146 -0
  115. package/components/snake-page-popup/types.ts +28 -0
  116. package/components/snake-parse/audio/audio.vue +286 -0
  117. package/components/snake-parse/audio/context.js +11 -0
  118. package/components/snake-parse/audio/index.js +34 -0
  119. package/components/snake-parse/node/node.vue +830 -0
  120. package/components/snake-parse/parser.js +1480 -0
  121. package/components/snake-parse/snake-parse.vue +548 -0
  122. package/components/snake-parse/static/app-plus/snake-parse/js/handler.js +109 -0
  123. package/components/snake-parse/static/app-plus/snake-parse/js/uni.webview.js +0 -0
  124. package/components/snake-parse/static/app-plus/snake-parse/js/uni.webview.min.js +1 -0
  125. package/components/snake-parse/static/app-plus/snake-parse/local.html +3 -0
  126. package/components/snake-parse/style/index.js +132 -0
  127. package/components/snake-parse/style/parser.js +175 -0
  128. package/components/snake-parse/types.ts +49 -0
  129. package/components/snake-picture-ads/snake-picture-ads.vue +292 -0
  130. package/components/snake-picture-ads/types.ts +5 -0
  131. package/components/snake-popup/snake-popup.vue +348 -0
  132. package/components/snake-popup/types.ts +88 -0
  133. package/components/snake-qr-code/qrcode.ts +1214 -0
  134. package/components/snake-qr-code/snake-qr-code.vue +408 -0
  135. package/components/snake-qr-code/types.ts +78 -0
  136. package/components/snake-radio/snake-radio.vue +271 -0
  137. package/components/snake-radio/types.ts +64 -0
  138. package/components/snake-radio-group/snake-radio-group.vue +98 -0
  139. package/components/snake-radio-group/types.ts +48 -0
  140. package/components/snake-read-more/snake-read-more.vue +140 -0
  141. package/components/snake-read-more/types.ts +53 -0
  142. package/components/snake-rich-text/snake-rich-text.vue +24 -0
  143. package/components/snake-rich-text/types.ts +5 -0
  144. package/components/snake-root-portal/snake-root-portal.vue +92 -0
  145. package/components/snake-root-portal/types.ts +9 -0
  146. package/components/snake-search/snake-search.vue +202 -0
  147. package/components/snake-search/types.ts +84 -0
  148. package/components/snake-select/snake-select.vue +371 -0
  149. package/components/snake-select/types.ts +75 -0
  150. package/components/snake-skeleton/snake-skeleton.vue +104 -0
  151. package/components/snake-skeleton/types.ts +32 -0
  152. package/components/snake-skeleton-item/snake-skeleton-item.vue +90 -0
  153. package/components/snake-skeleton-item/types.ts +30 -0
  154. package/components/snake-slider/snake-slider.vue +249 -0
  155. package/components/snake-slider/types.ts +59 -0
  156. package/components/snake-sticky/snake-sticky.vue +178 -0
  157. package/components/snake-sticky/types.ts +48 -0
  158. package/components/snake-subsection/snake-subsection.vue +188 -0
  159. package/components/snake-subsection/types.ts +54 -0
  160. package/components/snake-swiper/snake-swiper.vue +229 -0
  161. package/components/snake-swiper/types.ts +79 -0
  162. package/components/snake-switch/snake-switch.vue +100 -0
  163. package/components/snake-switch/types.ts +41 -0
  164. package/components/snake-tabbar/snake-tabbar.vue +306 -0
  165. package/components/snake-tabbar/types.ts +93 -0
  166. package/components/snake-tabs/snake-tabs.vue +305 -0
  167. package/components/snake-tabs/types.ts +79 -0
  168. package/components/snake-text/snake-text.vue +292 -0
  169. package/components/snake-text/types.ts +100 -0
  170. package/components/snake-textarea/snake-textarea.vue +171 -0
  171. package/components/snake-textarea/types.ts +79 -0
  172. package/components/snake-toast/snake-toast.vue +330 -0
  173. package/components/snake-toast/types.ts +55 -0
  174. package/components/snake-tooltip/position.ts +33 -0
  175. package/components/snake-tooltip/snake-tooltip.vue +339 -0
  176. package/components/snake-tooltip/types.ts +80 -0
  177. package/components/snake-transition/snake-transition.vue +315 -0
  178. package/components/snake-transition/types.ts +65 -0
  179. package/components/snake-upload/snake-upload.vue +1801 -0
  180. package/components/snake-upload/types.ts +256 -0
  181. package/components/snake-videos/snake-videos.vue +122 -0
  182. package/components/snake-videos/types.ts +7 -0
  183. package/iconfont.css +881 -0
  184. package/index.d.ts +7 -0
  185. package/index.scss +26 -0
  186. package/index.ts +94 -0
  187. package/libs/cms/blocks.ts +103 -0
  188. package/libs/cms/config.ts +25 -0
  189. package/libs/cms/index.ts +7 -0
  190. package/libs/cms/normalize.ts +135 -0
  191. package/libs/cms/resolve.ts +12 -0
  192. package/libs/cms/types.ts +164 -0
  193. package/libs/cms/use-commerce.ts +385 -0
  194. package/libs/cms/v2.ts +193 -0
  195. package/libs/config/index.ts +4 -0
  196. package/libs/config/modal.ts +93 -0
  197. package/libs/config/state.ts +33 -0
  198. package/libs/config/theme.ts +351 -0
  199. package/libs/config/toast.ts +71 -0
  200. package/libs/config/types.ts +135 -0
  201. package/libs/config/zIndex.ts +44 -0
  202. package/libs/css/color.scss +105 -0
  203. package/libs/css/common.scss +171 -0
  204. package/libs/css/style.h5.scss +7 -0
  205. package/libs/css/style.mp.scss +7 -0
  206. package/libs/css/style.theme.scss +70 -0
  207. package/libs/css/style.vue.scss +140 -0
  208. package/libs/form/async-validator.min.d.ts +75 -0
  209. package/libs/form/async-validator.min.js +3 -0
  210. package/libs/form/index.ts +3 -0
  211. package/libs/form/path.ts +40 -0
  212. package/libs/form/types.ts +51 -0
  213. package/libs/form/validate.ts +149 -0
  214. package/libs/foundation.ts +96 -0
  215. package/libs/function/$parent.ts +33 -0
  216. package/libs/function/clipboard.ts +13 -0
  217. package/libs/function/color.ts +55 -0
  218. package/libs/function/debounce.ts +34 -0
  219. package/libs/function/deepClone.ts +30 -0
  220. package/libs/function/deepMerge.ts +25 -0
  221. package/libs/function/emitter.ts +30 -0
  222. package/libs/function/format.ts +27 -0
  223. package/libs/function/getParent.ts +53 -0
  224. package/libs/function/getRect.ts +29 -0
  225. package/libs/function/guid.ts +11 -0
  226. package/libs/function/index.ts +31 -0
  227. package/libs/function/queryParams.ts +73 -0
  228. package/libs/function/random.ts +6 -0
  229. package/libs/function/randomArray.ts +14 -0
  230. package/libs/function/route.ts +155 -0
  231. package/libs/function/sleep.ts +7 -0
  232. package/libs/function/style.ts +58 -0
  233. package/libs/function/styleUtils.ts +83 -0
  234. package/libs/function/system.ts +14 -0
  235. package/libs/function/test.ts +202 -0
  236. package/libs/function/throttle.ts +36 -0
  237. package/libs/function/timeFormat.ts +27 -0
  238. package/libs/function/timeFrom.ts +27 -0
  239. package/libs/function/toast.ts +10 -0
  240. package/libs/function/trim.ts +11 -0
  241. package/libs/function/type2icon.ts +11 -0
  242. package/libs/function/unit.ts +45 -0
  243. package/libs/hooks/index.ts +11 -0
  244. package/libs/hooks/useChildren.ts +125 -0
  245. package/libs/hooks/useColor.ts +5 -0
  246. package/libs/hooks/useDebounce.ts +5 -0
  247. package/libs/hooks/useLocale.ts +13 -0
  248. package/libs/hooks/useModal.ts +131 -0
  249. package/libs/hooks/useParent.ts +62 -0
  250. package/libs/hooks/useRect.ts +44 -0
  251. package/libs/hooks/useRouter.ts +5 -0
  252. package/libs/hooks/useTheme.ts +31 -0
  253. package/libs/hooks/useThrottle.ts +5 -0
  254. package/libs/hooks/useToast.ts +116 -0
  255. package/libs/index.ts +18 -0
  256. package/libs/request/auto-http.ts +1 -0
  257. package/libs/request/index.ts +343 -0
  258. package/libs/runtime/accessors.ts +55 -0
  259. package/libs/runtime/context.ts +40 -0
  260. package/libs/runtime/index.ts +18 -0
  261. package/libs/runtime/types.ts +49 -0
  262. package/libs/util/calendar.ts +6 -0
  263. package/locale/index.ts +130 -0
  264. package/locale/lang/en-US.ts +67 -0
  265. package/locale/lang/zh-CN.ts +67 -0
  266. package/package.json +130 -5
  267. package/plugins/index.ts +1 -0
  268. package/plugins/root/README.md +86 -0
  269. package/plugins/root/index.ts +123 -0
  270. package/plugins/root/page.ts +77 -0
  271. package/plugins/root/root.ts +53 -0
  272. package/plugins/root/utils.ts +137 -0
  273. package/readme.md +79 -0
  274. package/theme.scss +118 -0
  275. package/types/components.d.ts +236 -0
  276. package/types/global.d.ts +21 -0
  277. package/types/index.d.ts +6 -0
  278. package/types/uni-app.d.ts +16 -0
  279. package/README.md +0 -3
@@ -0,0 +1,116 @@
1
+ import {
2
+ SNAKE_TOAST_EVENT_HIDE,
3
+ SNAKE_TOAST_EVENT_SHOW,
4
+ SNAKE_TOAST_GLOBAL_EVENT_HIDE,
5
+ SNAKE_TOAST_GLOBAL_EVENT_SHOW,
6
+ getEventWithCurrentPage,
7
+ type ToastPayload
8
+ } from '../config/toast';
9
+ import { emitter } from '../function/emitter';
10
+
11
+ export type UseToastShowOptions = ToastPayload;
12
+
13
+ export type UseToast = {
14
+ /**
15
+ * 显示 toast
16
+ * - show('文本')
17
+ * - show({ title, type, duration, ... })
18
+ */
19
+ show: (titleOrOptions: string | UseToastShowOptions) => void;
20
+ /** 关闭 toast */
21
+ close: () => void;
22
+ /** 成功 */
23
+ success: (titleOrOptions: string | UseToastShowOptions) => void;
24
+ /** 错误 */
25
+ error: (titleOrOptions: string | UseToastShowOptions) => void;
26
+ /** 警告 */
27
+ warning: (titleOrOptions: string | UseToastShowOptions) => void;
28
+ /** 信息 */
29
+ info: (titleOrOptions: string | UseToastShowOptions) => void;
30
+ /** 加载中(默认常驻,需 close 关闭) */
31
+ loading: (titleOrOptions: string | UseToastShowOptions) => void;
32
+ };
33
+
34
+ export type UseToastOptions = {
35
+ /** 是否使用全局根部 <snake-toast global />,默认 true;为 false 时走页面级 <snake-toast /> */
36
+ global?: boolean;
37
+ /** 是否使用页面级 <snake-toast page /> 或某个页面的 <snake-toast page="pageId" /> */
38
+ page?: boolean | string;
39
+ };
40
+
41
+ function normalize(titleOrOptions: string | UseToastShowOptions): UseToastShowOptions {
42
+ if (typeof titleOrOptions === 'string') return { title: titleOrOptions };
43
+ return titleOrOptions || {};
44
+ }
45
+
46
+ function getPage(optionsOrGlobal: UseToastOptions | boolean): string {
47
+ if (typeof optionsOrGlobal === 'boolean') {
48
+ return '';
49
+ }
50
+ if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
51
+ return optionsOrGlobal.page;
52
+ }
53
+ return '';
54
+ }
55
+
56
+ /**
57
+ * Toast 函数式调用
58
+ * @description 需要页面/应用中至少存在一个 <snake-toast global /> 或 <snake-toast page /> 实例用于承接事件;不影响原 ref 调用方式。
59
+ *
60
+ * 支持两种调用方式:
61
+ * - 应用级 useToast() / useToast(true) / useToast({ global: true })
62
+ * - 页面级 useToast(false) / useToast({ page: true }) / useToast({ page: 'pageId' })(pageId 应和页面中 <snake-toast page="pageId" /> 的 page 属性一致)
63
+ */
64
+ export function useToast(optionsOrGlobal: UseToastOptions | boolean = true): UseToast {
65
+ const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
66
+ const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
67
+ const showEvent = isGlobal
68
+ ? SNAKE_TOAST_GLOBAL_EVENT_SHOW
69
+ : isPage
70
+ ? getEventWithCurrentPage(SNAKE_TOAST_EVENT_SHOW, getPage(optionsOrGlobal))
71
+ : '';
72
+ const hideEvent = isGlobal
73
+ ? SNAKE_TOAST_GLOBAL_EVENT_HIDE
74
+ : isPage
75
+ ? getEventWithCurrentPage(SNAKE_TOAST_EVENT_HIDE, getPage(optionsOrGlobal))
76
+ : '';
77
+
78
+ function emitShow(payload: UseToastShowOptions) {
79
+ if (showEvent) {
80
+ emitter.emit(showEvent, payload);
81
+ }
82
+ }
83
+
84
+ function emitHide() {
85
+ if (hideEvent) {
86
+ emitter.emit(hideEvent, undefined);
87
+ }
88
+ }
89
+
90
+ function show(titleOrOptions: string | UseToastShowOptions) {
91
+ emitShow(normalize(titleOrOptions));
92
+ }
93
+
94
+ function close() {
95
+ emitHide();
96
+ }
97
+
98
+ function withType(type: string, titleOrOptions: string | UseToastShowOptions) {
99
+ const options = normalize(titleOrOptions);
100
+ emitShow({ ...options, type });
101
+ }
102
+
103
+ return {
104
+ show,
105
+ close,
106
+ success: (v: string | UseToastShowOptions) => withType('success', v),
107
+ error: (v: string | UseToastShowOptions) => withType('error', v),
108
+ warning: (v: string | UseToastShowOptions) => withType('warning', v),
109
+ info: (v: string | UseToastShowOptions) => withType('info', v),
110
+ loading: (v: string | UseToastShowOptions) => {
111
+ const options = normalize(v);
112
+ // loading 通常需要常驻,除非用户显式传 duration
113
+ emitShow({ ...options, loading: true, duration: options.duration ?? 0 });
114
+ }
115
+ };
116
+ }
package/libs/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export {
2
+ RuntimeError,
3
+ configureRuntime,
4
+ getRuntimeConfig,
5
+ getRuntimeOptions,
6
+ requireCache,
7
+ requireRequest,
8
+ resetRuntime,
9
+ runtimeOptionsKey
10
+ } from './runtime';
11
+ export type { CacheAdapter, RequestAdapter, RuntimeConfig, RuntimeOptions } from './runtime';
12
+ export * from './foundation';
13
+ export * from './config';
14
+ export * from './function';
15
+ export * from './hooks';
16
+ export * from './request';
17
+ export * from './cms';
18
+ export * from './form';
@@ -0,0 +1 @@
1
+ export { AutoHttp, isFunction, isPromise } from './index';
@@ -0,0 +1,343 @@
1
+ import type { RequestAdapter } from '../runtime';
2
+ import type { App } from 'vue';
3
+ import { deepMerge } from '../function';
4
+ import { translate } from '../../locale';
5
+ import type {
6
+ RequestConfig,
7
+ RequestInterceptor,
8
+ RequestMeta,
9
+ RequestMethod,
10
+ RequestOptions,
11
+ RequestResponse,
12
+ RequestSetup,
13
+ RequestTransport
14
+ } from '../config/types';
15
+
16
+ const defaultConfig: Required<Omit<RequestConfig, 'baseUrl'>> & Pick<RequestConfig, 'baseUrl'> = {
17
+ baseUrl: '',
18
+ header: {},
19
+ timeout: 60000,
20
+ dataType: 'json',
21
+ responseType: 'text',
22
+ method: 'POST',
23
+ meta: { loading: false, toast: false, originalData: true }
24
+ };
25
+
26
+ export class RequestError extends Error {
27
+ readonly kind: 'canceled' | 'http' | 'transport' | 'interceptor';
28
+ readonly config?: RequestOptions;
29
+ readonly response?: unknown;
30
+ readonly statusCode?: number;
31
+ /** 原始宿主网络错误,例如支付宝小程序返回的 errMsg。 */
32
+ readonly platformError?: unknown;
33
+
34
+ constructor(
35
+ message: string,
36
+ options: {
37
+ kind?: RequestError['kind'];
38
+ config?: RequestOptions;
39
+ response?: unknown;
40
+ statusCode?: number;
41
+ platformError?: unknown;
42
+ cause?: unknown;
43
+ } = {}
44
+ ) {
45
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
46
+ this.name = 'RequestError';
47
+ this.kind = options.kind || 'transport';
48
+ this.config = options.config;
49
+ this.response = options.response;
50
+ this.statusCode = options.statusCode;
51
+ this.platformError = options.platformError;
52
+ }
53
+ }
54
+
55
+ function joinUrl(baseUrl: string, url: string): string {
56
+ if (!baseUrl || /^https?:\/\//i.test(url)) return url;
57
+ return `${baseUrl.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
58
+ }
59
+
60
+ function isHttpResponse(value: unknown): value is RequestResponse {
61
+ return Boolean(value && typeof value === 'object' && typeof (value as RequestResponse).statusCode === 'number');
62
+ }
63
+
64
+ function isSuccessfulStatus(statusCode: number): boolean {
65
+ return statusCode >= 200 && statusCode < 300;
66
+ }
67
+
68
+ function getErrorMessage(error: unknown, fallback: string): string {
69
+ if (typeof error === 'string' && error) return error;
70
+ if (error && typeof error === 'object') {
71
+ const message =
72
+ (error as { errMsg?: unknown; message?: unknown }).errMsg || (error as { message?: unknown }).message;
73
+ if (typeof message === 'string' && message) return message;
74
+ }
75
+ return fallback;
76
+ }
77
+
78
+ let loadingCount = 0;
79
+
80
+ function showLoading(): void {
81
+ loadingCount += 1;
82
+ if (loadingCount > 1) return;
83
+ if (typeof uni !== 'undefined' && typeof uni.showLoading === 'function') {
84
+ uni.showLoading({ title: translate('common.loading'), mask: true });
85
+ }
86
+ }
87
+
88
+ function hideLoading(): void {
89
+ loadingCount = Math.max(0, loadingCount - 1);
90
+ if (loadingCount === 0 && typeof uni !== 'undefined' && typeof uni.hideLoading === 'function') uni.hideLoading();
91
+ }
92
+
93
+ function showError(message: string): void {
94
+ if (typeof uni !== 'undefined' && typeof uni.showToast === 'function') {
95
+ uni.showToast({ title: message, icon: 'none' });
96
+ }
97
+ }
98
+
99
+ function uniTransport(options: RequestOptions): Promise<RequestResponse> {
100
+ return new Promise((resolve, reject) => {
101
+ if (typeof uni === 'undefined' || typeof uni.request !== 'function') {
102
+ reject(new RequestError('[Snake UI] uni.request is unavailable.', { kind: 'transport', config: options }));
103
+ return;
104
+ }
105
+ // meta、baseUrl 和 params 是 Snake 的内部配置,不应透传给各小程序运行时。
106
+ const { baseUrl: _baseUrl, meta: _meta, params: _params, ...platformOptions } = options;
107
+ uni.request({
108
+ ...platformOptions,
109
+ method: options.method || 'GET',
110
+ data: options.data ?? options.params,
111
+ // 支付宝小程序会把 HTTP 401 等非成功状态送入 fail,但 complete 仍能提供 statusCode。
112
+ // 只要收到 HTTP 状态就交给上层响应拦截器;没有状态码时才是网络层失败。
113
+ complete: result => {
114
+ if (isHttpResponse(result)) {
115
+ resolve(result);
116
+ return;
117
+ }
118
+ reject(
119
+ new RequestError(getErrorMessage(result, translate('network.error')), {
120
+ kind: 'transport',
121
+ config: options,
122
+ platformError: result,
123
+ cause: result
124
+ })
125
+ );
126
+ }
127
+ });
128
+ });
129
+ }
130
+
131
+ export class Request {
132
+ config: RequestConfig = deepMerge(defaultConfig, {});
133
+ interceptor: RequestInterceptor = {};
134
+ private transport?: RequestTransport;
135
+ private legacyAdapter?: RequestAdapter;
136
+
137
+ setConfig(config: RequestConfig = {}): void {
138
+ this.config = deepMerge(this.config, config);
139
+ }
140
+
141
+ setInterceptor(interceptor: RequestInterceptor = {}): void {
142
+ this.interceptor = { ...this.interceptor, ...interceptor };
143
+ }
144
+
145
+ setTransport(transport?: RequestTransport): void {
146
+ this.transport = transport;
147
+ }
148
+
149
+ setLegacyAdapter(adapter?: RequestAdapter): void {
150
+ this.legacyAdapter = adapter;
151
+ }
152
+
153
+ private mergeOptions(options: RequestOptions): RequestOptions {
154
+ const meta = deepMerge((this.config.meta || {}) as RequestMeta, options.meta || {});
155
+ const header = deepMerge((this.config.header || {}) as Record<string, unknown>, options.header || {});
156
+ return {
157
+ ...this.config,
158
+ ...options,
159
+ url: joinUrl(this.config.baseUrl || '', options.url),
160
+ method: options.method || 'POST',
161
+ header,
162
+ meta
163
+ };
164
+ }
165
+
166
+ private async send(options: RequestOptions): Promise<RequestResponse | unknown> {
167
+ // 自定义 transport 优先;legacy adapter 仅兼容 GET/POST,其余方法仍交由 uni.request。
168
+ if (this.transport) return this.transport(options);
169
+ if (this.legacyAdapter && options.method === 'GET') {
170
+ return {
171
+ data: await this.legacyAdapter.get(options.url, options.params || (options.data as any)),
172
+ statusCode: 200
173
+ };
174
+ }
175
+ if (this.legacyAdapter && options.method === 'POST') {
176
+ return {
177
+ data: await this.legacyAdapter.post(options.url, (options.data || {}) as Record<string, unknown>),
178
+ statusCode: 200
179
+ };
180
+ }
181
+ return uniTransport(options);
182
+ }
183
+
184
+ async request<T = unknown>(options: RequestOptions): Promise<T> {
185
+ let config = this.mergeOptions(options);
186
+ if (this.interceptor.request) {
187
+ let intercepted: RequestOptions | false;
188
+ try {
189
+ intercepted = await this.interceptor.request(config);
190
+ } catch (error) {
191
+ throw new RequestError('[Snake UI] request interceptor failed.', {
192
+ kind: 'interceptor',
193
+ config,
194
+ cause: error
195
+ });
196
+ }
197
+ if (intercepted === false) {
198
+ throw new RequestError(translate('network.canceled'), { kind: 'canceled', config });
199
+ }
200
+ config = intercepted;
201
+ }
202
+ const meta = config.meta || {};
203
+ if (meta.loading) showLoading();
204
+ try {
205
+ const raw = await this.send(config);
206
+ if (isHttpResponse(raw)) raw.config = config;
207
+ // originalData=true 时 interceptor 接收完整平台响应,状态码校验也由它负责;否则本层校验并仅传 data。
208
+ if (isHttpResponse(raw) && !meta.originalData && !isSuccessfulStatus(raw.statusCode)) {
209
+ throw new RequestError(`HTTP ${raw.statusCode}`, {
210
+ kind: 'http',
211
+ config,
212
+ response: raw,
213
+ statusCode: raw.statusCode
214
+ });
215
+ }
216
+ const response = meta.originalData || !isHttpResponse(raw) ? raw : raw.data;
217
+ if (!this.interceptor.response) return response as T;
218
+ let intercepted: T | false;
219
+ try {
220
+ intercepted = await this.interceptor.response(response, config);
221
+ } catch (error) {
222
+ throw new RequestError('[Snake UI] response interceptor failed.', {
223
+ kind: 'interceptor',
224
+ config,
225
+ response,
226
+ cause: error
227
+ });
228
+ }
229
+ if (intercepted === false) {
230
+ throw new RequestError('[Snake UI] response rejected by interceptor.', {
231
+ kind: 'interceptor',
232
+ config,
233
+ response
234
+ });
235
+ }
236
+ return intercepted as T;
237
+ } catch (error) {
238
+ const normalized =
239
+ error instanceof RequestError
240
+ ? error
241
+ : new RequestError(getErrorMessage(error, translate('network.error')), {
242
+ kind: 'transport',
243
+ config,
244
+ platformError: error,
245
+ cause: error
246
+ });
247
+ if (meta.toast) showError(normalized.message);
248
+ throw normalized;
249
+ } finally {
250
+ if (meta.loading) hideLoading();
251
+ }
252
+ }
253
+
254
+ get<T = unknown>(url: string, data: Record<string, unknown> = {}, options: Partial<RequestOptions> = {}) {
255
+ return this.request<T>({ ...options, url, data, params: data, method: 'GET' });
256
+ }
257
+
258
+ post<T = unknown>(url: string, data: unknown = {}, options: Partial<RequestOptions> = {}) {
259
+ return this.request<T>({ ...options, url, data, method: 'POST' });
260
+ }
261
+
262
+ put<T = unknown>(url: string, data: unknown = {}, options: Partial<RequestOptions> = {}) {
263
+ return this.request<T>({ ...options, url, data, method: 'PUT' });
264
+ }
265
+
266
+ delete<T = unknown>(url: string, data: unknown = {}, options: Partial<RequestOptions> = {}) {
267
+ return this.request<T>({ ...options, url, data, method: 'DELETE' });
268
+ }
269
+
270
+ reset(): void {
271
+ this.config = deepMerge(defaultConfig, {});
272
+ this.interceptor = {};
273
+ this.transport = undefined;
274
+ this.legacyAdapter = undefined;
275
+ loadingCount = 0;
276
+ }
277
+ }
278
+
279
+ /** 共享请求单例。 */
280
+ export const http = new Request();
281
+
282
+ export interface HttpPluginOptions {
283
+ requestConfig?: RequestConfig;
284
+ interceptor?: RequestInterceptor;
285
+ }
286
+
287
+ /**
288
+ * Installs the shared request singleton as `$http`.
289
+ */
290
+ export const httpPlugin = {
291
+ install(app: App, options: HttpPluginOptions = {}): void {
292
+ if (options.interceptor) http.setInterceptor(options.interceptor);
293
+ if (options.requestConfig) http.setConfig(options.requestConfig);
294
+ (app.config.globalProperties as Record<string, unknown>).$http = http;
295
+ }
296
+ };
297
+
298
+ export function configureHttp(setup?: RequestSetup, adapter?: RequestAdapter): Request {
299
+ if (setup?.config) http.setConfig(setup.config);
300
+ if (setup?.interceptor) http.setInterceptor(setup.interceptor);
301
+ if (setup && 'transport' in setup) http.setTransport(setup.transport);
302
+ if (adapter) http.setLegacyAdapter(adapter);
303
+ return http;
304
+ }
305
+
306
+ export function isFunction(value: unknown): value is (...args: any[]) => unknown {
307
+ return typeof value === 'function';
308
+ }
309
+
310
+ export function isPromise(value: unknown): value is Promise<unknown> {
311
+ return Boolean(
312
+ value &&
313
+ typeof (value as Promise<unknown>).then === 'function' &&
314
+ typeof (value as Promise<unknown>).catch === 'function'
315
+ );
316
+ }
317
+
318
+ export class AutoHttp {
319
+ static Builder = class<T = unknown> {
320
+ constructor(private readonly instance: Request | ((options: RequestOptions) => Promise<T>)) {}
321
+
322
+ dispatch(urlConfig: Record<string, RequestOptions>, extra: Record<string, unknown> = {}): Record<string, any> {
323
+ const api: Record<string, any> = {};
324
+ for (const name of Object.keys(urlConfig)) api[name] = this.use.bind(this, urlConfig[name]);
325
+ return { ...api, ...extra };
326
+ }
327
+
328
+ use(
329
+ urlConfig: RequestOptions,
330
+ config: Partial<Omit<RequestOptions, 'url'>> & { url?: string | (() => string) } = {}
331
+ ): Promise<T> {
332
+ const url: string = isFunction(config.url)
333
+ ? `${urlConfig.url}${config.url()}`
334
+ : (config.url ?? urlConfig.url);
335
+ const { url: _url, ...overrides } = config;
336
+ const method = (config.method ?? urlConfig.method ?? 'GET') as RequestMethod;
337
+ const options = deepMerge(urlConfig, { ...overrides, url, method }) as RequestOptions;
338
+ return this.instance instanceof Request ? this.instance.request<T>(options) : this.instance(options);
339
+ }
340
+ };
341
+ }
342
+
343
+ export default httpPlugin;
@@ -0,0 +1,55 @@
1
+ import { getRuntimeOptions } from './context';
2
+ import { RuntimeError, type CacheAdapter, type RequestAdapter, type RuntimeConfig } from './types';
3
+
4
+ /**
5
+ * 面向组件的运行时访问器。
6
+ * 所有缺失能力都在这里附带组件名报错,调用方无需各自判断或复制错误文案。
7
+ */
8
+ export function requireRequest(component: string): RequestAdapter {
9
+ const request = getRuntimeOptions().request;
10
+ if (!request) throw new RuntimeError(component, 'request');
11
+ return request;
12
+ }
13
+
14
+ export function requireCache(component: string): CacheAdapter {
15
+ const cache = getRuntimeOptions().cache;
16
+ if (!cache) throw new RuntimeError(component, 'cache');
17
+ return cache;
18
+ }
19
+
20
+ export function getRuntimeConfig(component: string): Readonly<RuntimeConfig> {
21
+ const config = getRuntimeOptions().config;
22
+ if (!config) throw new RuntimeError(component, 'config');
23
+ return config;
24
+ }
25
+
26
+ /** 为组件创建带组件名的请求访问门面,统一保留缺失能力报错上下文。 */
27
+ export function createRequestClient(component: string) {
28
+ return {
29
+ get<T = unknown>(url: string, params?: Record<string, unknown>): Promise<T> {
30
+ return requireRequest(component).get<T>(url, params);
31
+ },
32
+ post<T = unknown>(url: string, data?: Record<string, unknown>): Promise<T> {
33
+ return requireRequest(component).post<T>(url, data);
34
+ }
35
+ };
36
+ }
37
+
38
+ /** 为组件创建带组件名的缓存访问门面,统一保留缺失能力报错上下文。 */
39
+ export function createCacheClient(component: string) {
40
+ return {
41
+ get<T = unknown>(key: string): T | undefined {
42
+ return requireCache(component).get<T>(key);
43
+ },
44
+ set<T = unknown>(key: string, value: T, ttlSeconds?: number): void {
45
+ requireCache(component).set(key, value, ttlSeconds);
46
+ },
47
+ delete(key: string): void {
48
+ requireCache(component).delete(key);
49
+ }
50
+ };
51
+ }
52
+
53
+ export function getConfig(component: string): Readonly<RuntimeConfig> {
54
+ return getRuntimeConfig(component);
55
+ }
@@ -0,0 +1,40 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { RuntimeOptions } from './types';
3
+
4
+ /**
5
+ * 运行时上下文只保存宿主应用注入的可选能力。
6
+ * 它是模块级状态,供非 Vue 组件场景读取;Vue 组件同时可通过 runtimeOptionsKey 注入。
7
+ */
8
+ const runtime: RuntimeOptions = {};
9
+
10
+ /** 提供给 Vue 组件树的只读运行时上下文 key。 */
11
+ export const runtimeOptionsKey: InjectionKey<Readonly<RuntimeOptions>> = Symbol('snake-ui-options');
12
+
13
+ /**
14
+ * 合并宿主注入的能力。运行时上下文不负责主题、语言等 UI 初始化,
15
+ * 那些职责由 foundation 单独处理。
16
+ */
17
+ export function configureRuntime(options: RuntimeOptions = {}): Readonly<RuntimeOptions> {
18
+ if (options.request !== undefined) runtime.request = options.request;
19
+ if (options.cache !== undefined) runtime.cache = options.cache;
20
+ if (options.config !== undefined) {
21
+ runtime.config = {
22
+ ...runtime.config,
23
+ ...options.config
24
+ };
25
+ }
26
+ if (options.cms !== undefined) runtime.cms = options.cms;
27
+ return runtime;
28
+ }
29
+
30
+ export function getRuntimeOptions(): Readonly<RuntimeOptions> {
31
+ return runtime;
32
+ }
33
+
34
+ /** 仅用于测试或重新初始化场景,清除全部宿主注入能力。 */
35
+ export function resetRuntime(): void {
36
+ delete runtime.request;
37
+ delete runtime.cache;
38
+ delete runtime.config;
39
+ delete runtime.cms;
40
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 宿主运行时能力的唯一入口。
3
+ *
4
+ * 这里仅管理组件库需要由业务应用提供的 request、cache、config、cms。
5
+ * 主题、语言、HTTP 初始化和 `$snake` 全局 API 由 foundation 与根入口负责,
6
+ * 避免把“运行时依赖注入”和“UI 初始化”混在同一个模块。
7
+ */
8
+ export { configureRuntime, getRuntimeOptions, resetRuntime, runtimeOptionsKey } from './context';
9
+ export {
10
+ createCacheClient,
11
+ createRequestClient,
12
+ getConfig,
13
+ getRuntimeConfig,
14
+ requireCache,
15
+ requireRequest
16
+ } from './accessors';
17
+ export { RuntimeError } from './types';
18
+ export type { CacheAdapter, RequestAdapter, RuntimeConfig, RuntimeOptions } from './types';
@@ -0,0 +1,49 @@
1
+ import type { DebugLevel, LocaleOption, RequestSetup, ThemeOption } from '../config/types';
2
+ import type { CmsOptions } from '../cms/types';
3
+
4
+ /** 由宿主项目实现的最小请求能力;Snake UI 不绑定某个 HTTP 库。 */
5
+ export interface RequestAdapter {
6
+ get<T = unknown>(url: string, params?: Record<string, unknown>): Promise<T>;
7
+ post<T = unknown>(url: string, data?: Record<string, unknown>): Promise<T>;
8
+ }
9
+
10
+ export interface CacheAdapter {
11
+ get<T = unknown>(key: string): T | undefined;
12
+ set<T = unknown>(key: string, value: T, ttlSeconds?: number): void;
13
+ delete(key: string): void;
14
+ }
15
+
16
+ export interface RuntimeConfig {
17
+ baseUrl?: string;
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ /**
22
+ * 宿主应用在安装 Snake UI 时提供的运行期能力。
23
+ * request、cache、config 与 cms 会存入 runtime;其余字段由 UI 初始化阶段消费。
24
+ */
25
+ export interface RuntimeOptions {
26
+ request?: RequestAdapter;
27
+ cache?: CacheAdapter;
28
+ config?: RuntimeConfig;
29
+ theme?: ThemeOption;
30
+ locale?: LocaleOption;
31
+ http?: RequestSetup;
32
+ debugMode?: boolean | DebugLevel | DebugLevel[];
33
+ cms?: CmsOptions;
34
+ }
35
+
36
+ export class RuntimeError extends Error {
37
+ readonly component: string;
38
+ readonly capability: keyof RuntimeOptions;
39
+
40
+ constructor(component: string, capability: keyof RuntimeOptions) {
41
+ super(
42
+ `[Snake UI] ${component} requires the "${capability}" adapter. ` +
43
+ `Configure it with app.use(SnakeUI, { ${capability}: ... }).`
44
+ );
45
+ this.name = 'RuntimeError';
46
+ this.component = component;
47
+ this.capability = capability;
48
+ }
49
+ }