@puresnake/snake-ui 0.0.0-stage → 0.0.2

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 +83 -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 +134 -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,31 @@
1
+ /**
2
+ * function 基础层桶入口:全量展开供 foundation 聚合为 $snake;
3
+ * 宿主按需引入单个模块时走 `libs/function/xxx` 子路径。
4
+ */
5
+ export * from './unit';
6
+ export * from './sleep';
7
+ export * from './guid';
8
+ export * from './getRect';
9
+ export * from './toast';
10
+ export * from './system';
11
+ export * from './route';
12
+ export * from './queryParams';
13
+ export * from './timeFormat';
14
+ export * from './timeFrom';
15
+ export * from './color';
16
+ export * from './deepClone';
17
+ export * from './deepMerge';
18
+ export * from './debounce';
19
+ export * from './throttle';
20
+ export * from './random';
21
+ export * from './randomArray';
22
+ export * from './trim';
23
+ export * from './test';
24
+ export * from './style';
25
+ export * from './format';
26
+ export * from './clipboard';
27
+ export * from './type2icon';
28
+ export * from './emitter';
29
+ export * from './getParent';
30
+ export * from './$parent';
31
+ export * from './styleUtils';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * URL 查询串工具:queryParams 负责「对象 → 串」拼接(与 uView Pro 的
3
+ * queryParams 语义一致),getQueryParams 负责「串 → 对象」解析。
4
+ */
5
+
6
+ /** 数组参数的序列化格式:indices 下标 / brackets 中括号 / repeat 重复键 / comma 逗号合并 */
7
+ export type QueryArrayFormat = 'indices' | 'brackets' | 'repeat' | 'comma';
8
+
9
+ const ARRAY_FORMATS = ['indices', 'brackets', 'repeat', 'comma'];
10
+
11
+ /**
12
+ * 把对象拼接成 URL 查询串(uview 的 queryParams 兼容实现)。
13
+ *
14
+ * - 空值('' / undefined / null)参数直接跳过;
15
+ * - 数组按 arrayFormat 序列化,默认 brackets(key[]=v1&key[]=v2);
16
+ * - 普通对象 JSON 序列化并 encodeURIComponent;
17
+ * - 其余值经模板字符串原样输出、不做额外编码,与 uview 行为一致。
18
+ *
19
+ * @param data 参数对象
20
+ * @param isPrefix 是否自动加上 "?",默认 true
21
+ * @param arrayFormat 数组格式,默认 brackets
22
+ */
23
+ export function queryParams(
24
+ data: Record<string, unknown> = {},
25
+ isPrefix = true,
26
+ arrayFormat: QueryArrayFormat = 'brackets'
27
+ ): string {
28
+ const prefix = isPrefix ? '?' : '';
29
+ const result: string[] = [];
30
+ const format = (ARRAY_FORMATS as string[]).includes(arrayFormat) ? arrayFormat : 'brackets';
31
+
32
+ Object.keys(data).forEach(key => {
33
+ const value = data[key];
34
+ // 过滤空参数,与 uview 行为一致
35
+ if (value === '' || value === undefined || value === null) return;
36
+
37
+ if (Array.isArray(value)) {
38
+ if (format === 'indices') {
39
+ value.forEach((item, index) => result.push(`${key}[${index}]=${item}`));
40
+ } else if (format === 'brackets') {
41
+ value.forEach(item => result.push(`${key}[]=${item}`));
42
+ } else if (format === 'repeat') {
43
+ value.forEach(item => result.push(`${key}=${item}`));
44
+ } else {
45
+ result.push(`${key}=${value.join(',')}`);
46
+ }
47
+ return;
48
+ }
49
+
50
+ if (value !== null && typeof value === 'object') {
51
+ result.push(`${key}=${encodeURIComponent(JSON.stringify(value))}`);
52
+ return;
53
+ }
54
+
55
+ result.push(`${key}=${value}`);
56
+ });
57
+
58
+ return prefix + result.join('&');
59
+ }
60
+
61
+ /**
62
+ * 解析 url 的 query 串为对象:取第一个 ? 之后、# 之前的部分,
63
+ * key / value 均做 URL 解码并把 + 还原为空格;参数段内再含 = 时值会被截断。
64
+ */
65
+ export function getQueryParams(url: string): Record<string, string> {
66
+ const query = url.split('?')[1]?.split('#')[0] || '';
67
+ return query.split('&').reduce<Record<string, string>>((result, part) => {
68
+ if (!part) return result;
69
+ const [key, value = ''] = part.split('=');
70
+ result[decodeURIComponent(key.replace(/\+/g, ' '))] = decodeURIComponent(value.replace(/\+/g, ' '));
71
+ return result;
72
+ }, {});
73
+ }
@@ -0,0 +1,6 @@
1
+ /** 生成 [min, max] 闭区间随机整数;两参大小颠倒时自动交换,默认 [0, 1]。 */
2
+ export function random(min = 0, max = 1): number {
3
+ const lower = Math.ceil(Math.min(min, max));
4
+ const upper = Math.floor(Math.max(min, max));
5
+ return Math.floor(Math.random() * (upper - lower + 1)) + lower;
6
+ }
@@ -0,0 +1,14 @@
1
+ import { random } from './random';
2
+
3
+ /**
4
+ * Fisher–Yates 洗牌:返回打乱后的新数组,不改动原数组。
5
+ * 分布均匀,且无原地副作用。
6
+ */
7
+ export function randomArray<T>(values: readonly T[]): T[] {
8
+ const result = [...values];
9
+ for (let index = result.length - 1; index > 0; index -= 1) {
10
+ const next = random(0, index);
11
+ [result[index], result[next]] = [result[next], result[index]];
12
+ }
13
+ return result;
14
+ }
@@ -0,0 +1,155 @@
1
+ import { getQueryParams } from './queryParams';
2
+
3
+ /**
4
+ * 路由跳转双入口:
5
+ * - route():uni 跳转 API 的 Promise 化封装,带别名映射与入参校验;
6
+ * - navTo():面向 CMS / 运营链接,识别外链、跨小程序、微信客服三类特殊协议。
7
+ */
8
+
9
+ export type RouteType = 'navigateTo' | 'to' | 'redirect' | 'redirectTo' | 'switchTab' | 'tab' | 'reLaunch' | 'navigateBack' | 'back';
10
+ export type RouteParamValue = string | number | boolean | null | undefined;
11
+ export type RouteParams = Record<string, RouteParamValue | readonly RouteParamValue[]>;
12
+
13
+ /** route() 的跳转配置;type 支持 uni 方法名与短别名(to / redirect / tab / back)。 */
14
+ export interface RouteOptions {
15
+ type?: RouteType;
16
+ url?: string;
17
+ params?: RouteParams;
18
+ delta?: number;
19
+ animationType?: string;
20
+ animationDuration?: number;
21
+ }
22
+
23
+ type NavigationMethod = 'navigateTo' | 'redirectTo' | 'switchTab' | 'reLaunch' | 'navigateBack';
24
+
25
+ // 调用方别名 → uni 实际跳转方法
26
+ const routeTypeMap: Record<RouteType, NavigationMethod> = {
27
+ navigateTo: 'navigateTo',
28
+ to: 'navigateTo',
29
+ redirect: 'redirectTo',
30
+ redirectTo: 'redirectTo',
31
+ switchTab: 'switchTab',
32
+ tab: 'switchTab',
33
+ reLaunch: 'reLaunch',
34
+ navigateBack: 'navigateBack',
35
+ back: 'navigateBack'
36
+ };
37
+
38
+ /**
39
+ * 往 url 追加查询参数:数组值展开为同名多段,null / undefined 跳过,
40
+ * 已有 query 用 & 衔接,hash 段原样保留在末尾。
41
+ */
42
+ function appendQueryParams(url: string, params?: RouteParams): string {
43
+ if (!params) return url;
44
+ const entries: string[] = [];
45
+ Object.entries(params).forEach(([key, rawValue]) => {
46
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
47
+ values.forEach(value => {
48
+ if (value === null || value === undefined) return;
49
+ entries.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
50
+ });
51
+ });
52
+ if (!entries.length) return url;
53
+ const hashIndex = url.indexOf('#');
54
+ const path = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
55
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
56
+ const separator = path.includes('?') ? (path.endsWith('?') || path.endsWith('&') ? '' : '&') : '?';
57
+ return `${path}${separator}${entries.join('&')}${hash}`;
58
+ }
59
+
60
+ function routeError(message: string): Error {
61
+ return new Error(`[Snake UI] ${message}`);
62
+ }
63
+
64
+ /**
65
+ * Promise 化路由跳转:成功 resolve;type 不认识、平台缺 API 或跳转失败均 reject。
66
+ * navigateBack 只认 delta(取整、至少 1);switchTab 不追加参数(uni 该 API 不支持 query);
67
+ * animationType / animationDuration 仅 navigateTo / redirectTo 透传(App 端生效)。
68
+ */
69
+ export function route(url: string, params?: RouteParams): Promise<void>;
70
+ export function route(options: RouteOptions): Promise<void>;
71
+ export function route(input: string | RouteOptions, params?: RouteParams): Promise<void> {
72
+ const options: RouteOptions = typeof input === 'string' ? { url: input, params } : input || {};
73
+ const requestedType = options.type || 'navigateTo';
74
+ const method = routeTypeMap[requestedType];
75
+ if (!method) return Promise.reject(routeError(`Unsupported route type: ${String(requestedType)}.`));
76
+
77
+ const platformMethod = (uni as unknown as Record<string, unknown>)[method];
78
+ if (typeof platformMethod !== 'function') return Promise.reject(routeError(`${method} is unavailable on this platform.`));
79
+
80
+ let navigationOptions: Record<string, unknown>;
81
+ if (method === 'navigateBack') {
82
+ navigationOptions = { delta: Math.max(1, Math.trunc(Number(options.delta) || 1)) };
83
+ } else {
84
+ const url = String(options.url || '').trim();
85
+ if (!url) return Promise.reject(routeError(`${method} requires a non-empty url.`));
86
+ navigationOptions = { url: method === 'switchTab' ? url : appendQueryParams(url, options.params) };
87
+ if (method === 'navigateTo' || method === 'redirectTo') {
88
+ if (options.animationType !== undefined) navigationOptions.animationType = options.animationType;
89
+ if (options.animationDuration !== undefined) navigationOptions.animationDuration = options.animationDuration;
90
+ }
91
+ }
92
+
93
+ return new Promise((resolve, reject) => {
94
+ (platformMethod as (options: Record<string, unknown>) => void)({
95
+ ...navigationOptions,
96
+ success: () => resolve(),
97
+ fail: (error: { errMsg?: string }) => reject(routeError(error?.errMsg || `${method} failed.`))
98
+ });
99
+ });
100
+ }
101
+
102
+ function parseJsonObject(raw: string | undefined): Record<string, unknown> {
103
+ if (!raw) return {};
104
+ try {
105
+ const parsed = JSON.parse(raw);
106
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
107
+ } catch {
108
+ return {};
109
+ }
110
+ }
111
+
112
+ /**
113
+ * CMS / 运营链接跳转:识别三类特殊协议(http 外链、/miniprogram、/serviceChat,
114
+ * 详见各分支注释)后落到 uni.navigateTo,失败自动降级 switchTab(兼容 tabbar 页)。
115
+ */
116
+ export function navTo(url?: string): void {
117
+ if (!url) return;
118
+ if (/^https?:\/\//i.test(url)) {
119
+ // H5 外链沿用浏览器跳转,其余平台交由业务方提供 web-view 页面。
120
+ // #ifdef H5
121
+ window.location.href = url;
122
+ // #endif
123
+ return;
124
+ }
125
+
126
+ // 小程序打开其他小程序:/miniprogram?appId=wx...&path=pages/...&extra={...}
127
+ // 运行时探测跨小程序 API,避免平台条件编译差异。
128
+ if (url.includes('/miniprogram')) {
129
+ if (typeof uni.navigateToMiniProgram !== 'function') return;
130
+ const query = getQueryParams(url);
131
+ const appId = query.wxAppId || query.aliAppId || query.appId || '';
132
+ if (!appId) return;
133
+ uni.navigateToMiniProgram({
134
+ appId,
135
+ path: query.path || '',
136
+ extraData: parseJsonObject(query.extra),
137
+ envVersion: (query.envVersion as 'release' | 'develop' | 'trial') || 'release',
138
+ fail: () => {}
139
+ });
140
+ return;
141
+ }
142
+
143
+ // 微信客服:/serviceChat?url=...&corpId=...
144
+ if (url.includes('/serviceChat')) {
145
+ if (typeof uni.openCustomerServiceChat !== 'function') return;
146
+ const query = getQueryParams(url);
147
+ uni.openCustomerServiceChat({
148
+ extInfo: { url: query.url || '' },
149
+ corpId: query.corpId || ''
150
+ });
151
+ return;
152
+ }
153
+
154
+ uni.navigateTo({ url, fail: () => uni.switchTab({ url }) });
155
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 延时等待:把 setTimeout 包成 Promise,用 `await sleep(ms)` 让逻辑按节奏暂停。
3
+ * @param delay 等待时长,单位 ms,默认 0
4
+ */
5
+ export function sleep(delay = 0): Promise<void> {
6
+ return new Promise(resolve => setTimeout(resolve, delay));
7
+ }
@@ -0,0 +1,58 @@
1
+ import type { CSSProperties } from 'vue';
2
+
3
+ /**
4
+ * 样式值归一化与串化。与 styleUtils 的分工:这里不过滤无效值,
5
+ * 需要 'null' / 'undefined' 字符串清洗的合并场景用 styleUtils.mergeStyles。
6
+ */
7
+
8
+ /** 可归一为样式声明集合的输入:声明字符串、样式对象(含 CSSProperties)或其数组。 */
9
+ export type StyleSource = string | CSSProperties | Record<string, unknown> | StyleSource[] | null | undefined;
10
+
11
+ /** 驼峰 / 空格 / 下划线分隔转短横线小写(CSS 属性名风格)。 */
12
+ export function kebabCase(value: string): string {
13
+ return value
14
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
15
+ .replace(/[\s_]+/g, '-')
16
+ .toLowerCase();
17
+ }
18
+
19
+ /**
20
+ * 样式输入归一化:target='object'(默认)统一返回样式对象,声明串按 ; 与首个 : 拆解;
21
+ * target='string' 统一返回样式串(非字符串输入经 toStyle 转换)。
22
+ */
23
+ export function addStyle(style: StyleSource, target: 'object' | 'string' = 'object'): Record<string, string> | string {
24
+ if (target === 'string') return typeof style === 'string' ? style : toStyle(style);
25
+ if (Array.isArray(style)) return toStyle(style);
26
+ if (typeof style !== 'string') {
27
+ if (!style) return {};
28
+ return Object.fromEntries(Object.entries(style).map(([key, value]) => [key, String(value)]));
29
+ }
30
+ return style.split(';').reduce<Record<string, string>>((result, declaration) => {
31
+ const separator = declaration.indexOf(':');
32
+ if (separator < 0) return result;
33
+ const key = declaration.slice(0, separator).trim();
34
+ const value = declaration.slice(separator + 1).trim();
35
+ if (key && value) result[key] = value;
36
+ return result;
37
+ }, {});
38
+ }
39
+
40
+ /**
41
+ * 合并多份样式输入为一条 `k:v;k:v` 串:键统一短横线小写,
42
+ * 后出现的同键覆盖先出现的(Map 去重),null / 空串值跳过。
43
+ */
44
+ export function toStyle(...styles: Array<StyleSource>): string {
45
+ // 兼容旧用法:单个数组整体传入时(如 [object, string])解构展开
46
+ if (styles.length === 1 && Array.isArray(styles[0])) {
47
+ styles = (styles[0] as StyleSource[]).slice();
48
+ }
49
+ const declarations = new Map<string, string>();
50
+ styles.forEach(style => {
51
+ const source = typeof style === 'string' ? (addStyle(style) as Record<string, string>) : style;
52
+ if (!source) return;
53
+ Object.entries(source).forEach(([key, value]) => {
54
+ if (value != null && value !== '') declarations.set(kebabCase(key), String(value));
55
+ });
56
+ });
57
+ return Array.from(declarations, ([key, value]) => `${key}:${value}`).join(';');
58
+ }
@@ -0,0 +1,83 @@
1
+ import type { CSSProperties } from 'vue';
2
+ import { trim } from './trim';
3
+ import { test } from './test';
4
+
5
+ /**
6
+ * 样式合并的「清洗」版:合并前过滤 'null' / 'undefined' 字符串等无效值,
7
+ * 适合拼接来源不可控的样式(如接口下发的 customStyle);基础归一化见 style.ts。
8
+ */
9
+
10
+ /** 过滤空值与 'null' / 'undefined' 字符串,这类脏值常见于接口透传。 */
11
+ function isValidValue(value: unknown): boolean {
12
+ if (test.isEmpty(value)) return false;
13
+ if (typeof value === 'string') {
14
+ const trimmed = trim(value).toLowerCase();
15
+ return trimmed !== 'null' && trimmed !== 'undefined' && trimmed !== '';
16
+ }
17
+ return true;
18
+ }
19
+
20
+ /** 将 CSS 字符串解析为驼峰键对象。 */
21
+ function cssStrToObj(str: string): Record<string, string> {
22
+ const result: Record<string, string> = {};
23
+ if (!isValidValue(str)) return result;
24
+ const declarations = trim(String(str)).split(';');
25
+ declarations.forEach(decl => {
26
+ const [prop, ...values] = decl.split(':').map(segment => segment.trim());
27
+ const value = values.join(':');
28
+ if (prop && value) {
29
+ const camelProp = prop.replace(/-([a-z])/g, (_match, letter: string) => letter.toUpperCase());
30
+ result[camelProp] = value;
31
+ }
32
+ });
33
+ return result;
34
+ }
35
+
36
+ /** 将驼峰键对象转换为 CSS 字符串。 */
37
+ function cssObjToStr(obj: CSSProperties): string {
38
+ if (!isValidValue(obj) || typeof obj !== 'object') return '';
39
+ return (
40
+ Object.entries(obj)
41
+ .map(([key, value]) => {
42
+ if (!isValidValue(value)) return '';
43
+ // 驼峰转短横线
44
+ const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();
45
+ return `${kebab}: ${value}`;
46
+ })
47
+ .filter(Boolean)
48
+ .join('; ') + ';'
49
+ );
50
+ }
51
+
52
+ /**
53
+ * 合并多个 CSS 输入(对象或字符串),返回一个 CSS 对象,后参数覆盖先参数。
54
+ */
55
+ export function mergeStyles(...args: (object | string)[]): CSSProperties {
56
+ const result = {} as Record<string, any>;
57
+ for (let i = 0; i < args.length; i++) {
58
+ const arg = args[i];
59
+ // 保持历史语义:遇到无效来源立即停止,后续样式不会再参与合并,而不是简单跳过该项。
60
+ if (!isValidValue(arg)) return result as CSSProperties;
61
+ if (typeof arg === 'string') {
62
+ Object.assign(result, cssStrToObj(arg));
63
+ } else if (test.object(arg)) {
64
+ const cleanedObj: Record<string, any> = {};
65
+ Object.keys(arg).forEach(key => {
66
+ const value = (arg as Record<string, any>)[key];
67
+ if (isValidValue(value)) {
68
+ cleanedObj[key] = value;
69
+ }
70
+ });
71
+ Object.assign(result, cleanedObj);
72
+ }
73
+ }
74
+ return result as CSSProperties;
75
+ }
76
+
77
+ /**
78
+ * 合并多个 CSS 输入并返回 CSS 字符串。
79
+ */
80
+ export function mergeCssStrings(...args: (object | string)[]): string {
81
+ const obj = mergeStyles(...args);
82
+ return cssObjToStr(obj);
83
+ }
@@ -0,0 +1,14 @@
1
+ /** 获取系统信息:每次实时读取,不做缓存(窗口尺寸、主题等可能变化)。 */
2
+ export function getSystemInfo(): UniApp.GetSystemInfoResult {
3
+ return uni.getSystemInfoSync();
4
+ }
5
+
6
+ /** sys 是 getSystemInfo 的短别名。 */
7
+ export function sys(): UniApp.GetSystemInfoResult {
8
+ return getSystemInfo();
9
+ }
10
+
11
+ /** 当前操作系统平台(统一小写,如 'ios' / 'android' / 'devtools')。 */
12
+ export function os(): string {
13
+ return String(sys().platform || '').toLowerCase();
14
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * 常用校验套件,便于组件移植直接引用。
3
+ * snake 原有校验器语义保持不变:number 为 Number.isFinite 版、object 为纯对象判定、
4
+ * empty 对 null/''/空数组/空对象返回 true。
5
+ */
6
+
7
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
8
+ if (!value || Object.prototype.toString.call(value) !== '[object Object]') return false;
9
+ const prototype = Object.getPrototypeOf(value);
10
+ return prototype === null || prototype === Object.prototype;
11
+ }
12
+
13
+ /** 验证电子邮箱格式。 */
14
+ function email(value: unknown): boolean {
15
+ return /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/.test(
16
+ String(value)
17
+ );
18
+ }
19
+
20
+ /** 验证手机号格式(大陆 1[3-9] 段)。 */
21
+ function mobile(value: unknown): boolean {
22
+ return /^1[3-9]\d{9}$/.test(String(value));
23
+ }
24
+
25
+ /** 验证 URL 格式。 */
26
+ function url(value: unknown): boolean {
27
+ return /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?/.test(String(value));
28
+ }
29
+
30
+ /** 验证可被 Date 解析的日期格式。 */
31
+ function date(value: unknown): boolean {
32
+ return !/Invalid|NaN/.test(new Date(String(value)).toString());
33
+ }
34
+
35
+ /** 验证 ISO 类型的日期格式(yyyy/mm/dd 或 yyyy-mm-dd)。 */
36
+ function dateISO(value: unknown): boolean {
37
+ return /^\d{4}[/-](0?[1-9]|1[012])[/-](0?[1-9]|[12][0-9]|3[01])$/.test(String(value));
38
+ }
39
+
40
+ /** 验证十进制数字(snake 语义:Number.isFinite,非正则)。 */
41
+ function number(value: unknown): boolean {
42
+ return value !== '' && Number.isFinite(Number(value));
43
+ }
44
+
45
+ /** 验证纯整数。 */
46
+ function digits(value: unknown): boolean {
47
+ return /^\d+$/.test(String(value));
48
+ }
49
+
50
+ /** 验证身份证号码。 */
51
+ function idCard(value: unknown): boolean {
52
+ return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(String(value));
53
+ }
54
+
55
+ /** 验证车牌号(含新能源 8 位与传统能源 7 位)。 */
56
+ function carNo(value: unknown): boolean {
57
+ const input = String(value);
58
+ // 新能源车牌
59
+ const xreg =
60
+ /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
61
+ // 旧车牌
62
+ const creg =
63
+ /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
64
+ if (input.length === 7) return creg.test(input);
65
+ if (input.length === 8) return xreg.test(input);
66
+ return false;
67
+ }
68
+
69
+ /** 验证金额(最多 2 位小数,支持千分位)。 */
70
+ function amount(value: unknown): boolean {
71
+ return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(String(value));
72
+ }
73
+
74
+ /** 验证纯中文。 */
75
+ function chinese(value: unknown): boolean {
76
+ return /^[一-龥]+$/gi.test(String(value));
77
+ }
78
+
79
+ /** 验证纯字母。 */
80
+ function letter(value: unknown): boolean {
81
+ return /^[a-zA-Z]*$/.test(String(value));
82
+ }
83
+
84
+ /** 验证字母或数字。 */
85
+ function enOrNum(value: unknown): boolean {
86
+ return /^[0-9a-zA-Z]*$/g.test(String(value));
87
+ }
88
+
89
+ /** 验证是否包含某个值。 */
90
+ function contains(value: unknown, param: string): boolean {
91
+ return String(value).indexOf(param) >= 0;
92
+ }
93
+
94
+ /** 验证数值范围 [min, max]。 */
95
+ function range(value: number, param: [number, number]): boolean {
96
+ return value >= param[0] && value <= param[1];
97
+ }
98
+
99
+ /** 验证字符串长度范围 [min, max]。 */
100
+ function rangeLength(value: string, param: [number, number]): boolean {
101
+ return value.length >= param[0] && value.length <= param[1];
102
+ }
103
+
104
+ /** 验证固定电话。 */
105
+ function landline(value: unknown): boolean {
106
+ return /^\d{3,4}-\d{7,8}(-\d{3,4})?$/.test(String(value));
107
+ }
108
+
109
+ /** 判断是否为空(snake 语义:null/undefined/''/空数组/空对象)。 */
110
+ function empty(value: unknown): boolean {
111
+ return (
112
+ value == null ||
113
+ value === '' ||
114
+ (Array.isArray(value) && value.length === 0) ||
115
+ (isPlainObject(value) && Object.keys(value).length === 0)
116
+ );
117
+ }
118
+
119
+ /** 验证 JSON 字符串。 */
120
+ function jsonString(value: unknown): boolean {
121
+ if (typeof value !== 'string') return false;
122
+ try {
123
+ const parsed = JSON.parse(value);
124
+ return typeof parsed === 'object' && parsed !== null;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ /** 验证短信验证码(默认 6 位数字)。 */
131
+ function code(value: unknown, len = 6): boolean {
132
+ return new RegExp(`^\\d{${len}}$`).test(String(value));
133
+ }
134
+
135
+ /** 判断是否函数。 */
136
+ function func(value: unknown): value is (...args: unknown[]) => unknown {
137
+ return typeof value === 'function';
138
+ }
139
+
140
+ /** 判断是否 Promise 形对象。 */
141
+ function promise(value: unknown): boolean {
142
+ return (
143
+ !!value &&
144
+ typeof value === 'object' &&
145
+ func((value as { then?: unknown }).then) &&
146
+ func((value as { catch?: unknown }).catch)
147
+ );
148
+ }
149
+
150
+ /** 判断是否图片格式地址。 */
151
+ function image(value: unknown): boolean {
152
+ return /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i.test(String(value).split('?')[0]);
153
+ }
154
+
155
+ /** 判断是否视频格式地址。 */
156
+ function video(value: unknown): boolean {
157
+ return /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i.test(String(value).split('?')[0]);
158
+ }
159
+
160
+ /** 判断是否正则对象。 */
161
+ function regExp(value: unknown): value is RegExp {
162
+ return !!value && Object.prototype.toString.call(value) === '[object RegExp]';
163
+ }
164
+
165
+ /** 验证字符串类型。 */
166
+ function string(value: unknown): value is string {
167
+ return typeof value === 'string';
168
+ }
169
+
170
+ export const test = {
171
+ email,
172
+ mobile,
173
+ url,
174
+ date,
175
+ dateISO,
176
+ number,
177
+ digits,
178
+ idCard,
179
+ carNo,
180
+ amount,
181
+ chinese,
182
+ letter,
183
+ enOrNum,
184
+ contains,
185
+ range,
186
+ rangeLength,
187
+ empty,
188
+ isEmpty: empty,
189
+ jsonString,
190
+ landline,
191
+ object: isPlainObject,
192
+ array: Array.isArray,
193
+ code,
194
+ func,
195
+ promise,
196
+ video,
197
+ image,
198
+ regExp,
199
+ string
200
+ };
201
+
202
+ export type TestSuite = typeof test;