@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,36 @@
1
+ import type { CancelableFunction } from './debounce';
2
+
3
+ /**
4
+ * 节流:wait 窗口内最多执行一次,且窗口结束时会补发最后一次尾随调用,
5
+ * 保证「最后一次触发不丢」。
6
+ * @param callback 目标回调,参数由包装函数透传
7
+ * @param wait 节流窗口,单位 ms,默认 300
8
+ * @returns 带 cancel() 的包装函数;cancel 同时重置计时基准
9
+ */
10
+ export function throttle<Args extends unknown[]>(callback: (...args: Args) => void, wait = 300): CancelableFunction<Args> {
11
+ let lastCall = 0;
12
+ let timer: ReturnType<typeof setTimeout> | undefined;
13
+ const wrapped = ((...args: Args) => {
14
+ const remaining = wait - (Date.now() - lastCall);
15
+ if (remaining <= 0) {
16
+ if (timer) clearTimeout(timer);
17
+ timer = undefined;
18
+ lastCall = Date.now();
19
+ callback(...args);
20
+ return;
21
+ }
22
+ if (!timer) {
23
+ timer = setTimeout(() => {
24
+ timer = undefined;
25
+ lastCall = Date.now();
26
+ callback(...args);
27
+ }, remaining);
28
+ }
29
+ }) as CancelableFunction<Args>;
30
+ wrapped.cancel = () => {
31
+ if (timer) clearTimeout(timer);
32
+ timer = undefined;
33
+ lastCall = 0;
34
+ };
35
+ return wrapped;
36
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 时间格式化,占位符区分大小写:yyyy 年、mm 月、dd 日、hh 时、MM 分、ss 秒
3
+ * (分钟使用大写 MM)。非法日期返回空串。
4
+ * @param value 时间戳 / 日期字符串 / Date;10 位数字按秒级时间戳升位为毫秒
5
+ * @param pattern 格式模板,默认 'yyyy-mm-dd hh:MM:ss'
6
+ */
7
+ export function formatTime(value: string | number | Date, pattern = 'yyyy-mm-dd hh:MM:ss'): string {
8
+ // 10 位数字/字符串按秒级时间戳处理,统一换算为毫秒
9
+ let timeValue: string | number | Date = value;
10
+ if ((typeof timeValue === 'number' || typeof timeValue === 'string') && String(timeValue).length === 10) {
11
+ timeValue = Number(timeValue) * 1000;
12
+ }
13
+ const date = timeValue instanceof Date ? timeValue : new Date(timeValue);
14
+ if (Number.isNaN(date.getTime())) return '';
15
+ const parts: Record<string, string> = {
16
+ yyyy: String(date.getFullYear()),
17
+ mm: String(date.getMonth() + 1).padStart(2, '0'),
18
+ dd: String(date.getDate()).padStart(2, '0'),
19
+ hh: String(date.getHours()).padStart(2, '0'),
20
+ MM: String(date.getMinutes()).padStart(2, '0'),
21
+ ss: String(date.getSeconds()).padStart(2, '0')
22
+ };
23
+ return pattern.replace(/yyyy|mm|dd|hh|MM|ss/g, token => parts[token]);
24
+ }
25
+
26
+ // timeFormat 是 formatTime 的别名。
27
+ export { formatTime as timeFormat };
@@ -0,0 +1,27 @@
1
+ import { formatTime } from './timeFormat';
2
+
3
+ /** 归一化为 Date:数字或纯数字串在绝对值 < 1e12 时按秒级时间戳升位为毫秒。 */
4
+ function normalizeTime(value: string | number | Date): Date {
5
+ if (value instanceof Date) return new Date(value.getTime());
6
+ if (typeof value === 'number' || /^\d+(?:\.\d+)?$/.test(String(value).trim())) {
7
+ const numeric = Number(value);
8
+ return new Date(Math.abs(numeric) < 1e12 ? numeric * 1000 : numeric);
9
+ }
10
+ return new Date(value);
11
+ }
12
+
13
+ /**
14
+ * 时间转为「多久之前」:刚刚 / n分钟前 / n小时前 / n天前,超过 30 天回落为 yyyy-mm-dd。
15
+ * @param now 当前时间基准,默认 Date.now();注入固定值便于测试
16
+ */
17
+ export function timeFrom(value: string | number | Date, now = Date.now()): string {
18
+ const date = normalizeTime(value);
19
+ const timestamp = date.getTime();
20
+ if (!Number.isFinite(timestamp)) return '';
21
+ const seconds = Math.max(0, Math.floor((now - timestamp) / 1000));
22
+ if (seconds < 60) return '刚刚';
23
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟前`;
24
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}小时前`;
25
+ if (seconds < 2592000) return `${Math.floor(seconds / 86400)}天前`;
26
+ return formatTime(date, 'yyyy-mm-dd');
27
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 无图标 toast(icon: 'none',最多展示两行)。
3
+ * 默认时长 2000ms,不支持传 icon / 配置对象。
4
+ * @param title 提示文案;空值静默忽略
5
+ * @param duration 展示时长,单位 ms
6
+ */
7
+ export function toast(title: string, duration = 2000): void {
8
+ if (!title) return;
9
+ uni.showToast({ title, icon: 'none', duration });
10
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 去除字符串空格。
3
+ * @param value 输入字符串
4
+ * @param position 去除位置:'both'(默认)| 'left' | 'right' | 'all'
5
+ */
6
+ export function trim(value: string, position: 'both' | 'left' | 'right' | 'all' = 'both'): string {
7
+ if (position === 'left') return value.replace(/^\s+/, '');
8
+ if (position === 'right') return value.replace(/\s+$/, '');
9
+ if (position === 'all') return value.replace(/\s+/g, '');
10
+ return value.trim();
11
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 语义类型 → 图标名,供 toast / modal 等结果类组件取配套图标;
3
+ * 未识别的 type 落到 info-circle。
4
+ * warning 映射为 'warning'。
5
+ */
6
+ export function type2icon(type: string): string {
7
+ return (
8
+ ({ success: 'checkmark-circle', warning: 'warning', error: 'close-circle', info: 'info-circle' } as Record<string, string>)[type] ||
9
+ 'info-circle'
10
+ );
11
+ }
@@ -0,0 +1,45 @@
1
+ export type UnitValue = number | string | null | undefined;
2
+
3
+ const BARE_NUMBER_PATTERN = /^-?\d+(?:\.\d+)?$/;
4
+
5
+ /**
6
+ * 给数值补单位:裸数字(含多值简写中的每一段)追加 unit,其余原样返回;
7
+ * null / undefined / 空串统一归一为 "0" + unit。
8
+ * 空值不透传,而是归零。
9
+ */
10
+ export function addUnit(value: UnitValue = 0, unit = 'rpx'): string {
11
+ if (value === null || value === undefined || value === '') return `0${unit}`;
12
+ if (typeof value === 'number') return `${value}${unit}`;
13
+ const normalized = String(value).trim();
14
+ // 多值(空格分隔,用于 margin/padding 等简写):逐段套用单值规则,已有单位的段保持原样。
15
+ if (normalized.includes(' ')) {
16
+ return normalized
17
+ .split(/\s+/)
18
+ .map(segment => (BARE_NUMBER_PATTERN.test(segment) ? `${segment}${unit}` : segment))
19
+ .join(' ');
20
+ }
21
+ return BARE_NUMBER_PATTERN.test(normalized) ? `${normalized}${unit}` : normalized;
22
+ }
23
+
24
+ // node 直载测试等无 uni 环境返回 0,由 pxToRpx 归入非法视口兜底
25
+ function getViewportWidth(): number {
26
+ if (typeof uni === 'undefined') return 0;
27
+ return Number(uni.getSystemInfoSync().windowWidth);
28
+ }
29
+
30
+ /**
31
+ * px 换算 rpx(750 设计稿基准)。
32
+ * @param value 像素值
33
+ * @param viewportWidth 视口宽度,默认实时读取;注入固定值便于测试与固定稿计算
34
+ * @returns 形如 "375rpx" 的字符串;value 或视口宽度非法时返回 "0rpx"
35
+ */
36
+ export function pxToRpx(value: UnitValue = 0, viewportWidth = getViewportWidth()): string {
37
+ const numericValue = Number(value);
38
+ const numericViewportWidth = Number(viewportWidth);
39
+
40
+ if (!Number.isFinite(numericValue) || !Number.isFinite(numericViewportWidth) || numericViewportWidth <= 0) {
41
+ return '0rpx';
42
+ }
43
+
44
+ return `${(numericValue * 750) / numericViewportWidth}rpx`;
45
+ }
@@ -0,0 +1,11 @@
1
+ export * from './useRect';
2
+ export * from './useTheme';
3
+ export * from './useColor';
4
+ export * from './useLocale';
5
+ export * from './useRouter';
6
+ export * from './useDebounce';
7
+ export * from './useThrottle';
8
+ export * from './useToast';
9
+ export * from './useModal';
10
+ export * from './useParent';
11
+ export * from './useChildren';
@@ -0,0 +1,125 @@
1
+ import {
2
+ provide,
3
+ reactive,
4
+ getCurrentInstance,
5
+ type VNode,
6
+ type InjectionKey,
7
+ type VNodeNormalizedChildren,
8
+ type ComponentPublicInstance,
9
+ type ComponentInternalInstance
10
+ } from 'vue';
11
+
12
+ /**
13
+ * uni-app 的小程序 Vue 运行时没有导出 `isVNode`,但 VNode 本身保持 Vue 的
14
+ * `__v_isVNode` 标记。使用该标记避免生成对缺失运行时导出的静态导入。
15
+ */
16
+ const isVNode = (value: unknown): value is VNode =>
17
+ Boolean(value && typeof value === 'object' && (value as { __v_isVNode?: unknown }).__v_isVNode === true);
18
+
19
+ export function flattenVNodes(children: VNode | VNodeNormalizedChildren): VNode[] {
20
+ const result: VNode[] = [];
21
+
22
+ const traverse = (children: VNode | VNodeNormalizedChildren) => {
23
+ const vNode = Array.isArray(children) ? children : [children];
24
+ vNode.forEach(child => {
25
+ if (Array.isArray(child)) {
26
+ traverse(child);
27
+ } else if (isVNode(child) && child.component?.subTree) {
28
+ result.push(child);
29
+ traverse(child.component.subTree);
30
+ } else if (isVNode(child) && Array.isArray(child.children)) {
31
+ traverse(child.children);
32
+ } else if (isVNode(child)) {
33
+ result.push(child);
34
+ }
35
+ });
36
+ };
37
+
38
+ traverse(children);
39
+
40
+ return result;
41
+ }
42
+
43
+ const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
44
+ const index = vnodes.indexOf(vnode);
45
+ if (index === -1) {
46
+ // 异步渲染等场景下 vnode 引用可能已变化;带 key 时退回 type + key 匹配以保持模板顺序。
47
+ return vnodes.findIndex(item => vnode.key !== undefined && vnode.key !== null && item.type === vnode.type && item.key === vnode.key);
48
+ }
49
+ return index;
50
+ };
51
+
52
+ /**
53
+ * 注册顺序可能与渲染顺序不一致(v-for / slot / 异步渲染),按父组件 vnode 树重排,
54
+ * 保证 children 数组与模板中的子组件顺序一致。
55
+ */
56
+ export function sortChildren(
57
+ parent: ComponentInternalInstance,
58
+ publicChildren: ComponentPublicInstance[],
59
+ internalChildren: ComponentInternalInstance[]
60
+ ) {
61
+ const vnodes = parent && parent.subTree && parent.subTree.children ? flattenVNodes(parent.subTree) : [];
62
+ internalChildren.sort((a, b) => findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode));
63
+
64
+ const orderedPublicChildren = internalChildren.map(item => item.proxy!);
65
+
66
+ publicChildren.sort((a, b) => {
67
+ const getIndex = (comp: ComponentPublicInstance) => {
68
+ const uid = comp.$!.uid;
69
+ return orderedPublicChildren.findIndex(i => i.$!.uid === uid);
70
+ };
71
+
72
+ return getIndex(a) - getIndex(b);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * 父组件侧的关系 hook:linkChildren 时 provide 出 link / unlink / children 集合与业务上下文,
78
+ * 子组件经 {@link useParent} 注入。children 为公开代理数组(exposed 的 ref 已解包),
79
+ * 并始终按模板渲染顺序排列。
80
+ *
81
+ * Child 为子项公开代理的业务视图(按 defineExpose 的形态声明纯接口即可);
82
+ * 需要自定义 Child 时两个泛型必须同时显式给出,否则 ProvideValue 推断会被关闭。
83
+ */
84
+ export function useChildren<Child = ComponentPublicInstance, ProvideValue = never>(key: InjectionKey<ProvideValue>) {
85
+ const publicChildren: Child[] = reactive([]);
86
+ const internalChildren: ComponentInternalInstance[] = reactive([]);
87
+ const parent = getCurrentInstance()!;
88
+
89
+ const linkChildren = (value: ProvideValue = {} as ProvideValue) => {
90
+ const link = (child: ComponentInternalInstance) => {
91
+ if (child.proxy) {
92
+ if (internalChildren.indexOf(child) === -1) {
93
+ internalChildren.push(child);
94
+ publicChildren.push(child.proxy as Child);
95
+ sortChildren(parent, publicChildren as ComponentPublicInstance[], internalChildren);
96
+ }
97
+ }
98
+ };
99
+
100
+ const unlink = (child: ComponentInternalInstance) => {
101
+ const index = internalChildren.indexOf(child);
102
+ // unlink 仅应由已 link 的子组件调用;未注册实例的 index 为 -1,调用方不得传入。
103
+ publicChildren.splice(index, 1);
104
+ internalChildren.splice(index, 1);
105
+ };
106
+
107
+ provide(
108
+ key,
109
+ Object.assign(
110
+ {
111
+ link,
112
+ unlink,
113
+ children: publicChildren,
114
+ internalChildren
115
+ },
116
+ value
117
+ )
118
+ );
119
+ };
120
+
121
+ return {
122
+ children: publicChildren,
123
+ linkChildren
124
+ };
125
+ }
@@ -0,0 +1,5 @@
1
+ import { setThemeColor, color } from '../config/theme';
2
+
3
+ export function useColor() {
4
+ return { color: color, setColor: setThemeColor };
5
+ }
@@ -0,0 +1,5 @@
1
+ import { debounce } from '../function';
2
+
3
+ export function useDebounce<Args extends unknown[]>(callback: (...args: Args) => void, wait?: number) {
4
+ return debounce(callback, wait);
5
+ }
@@ -0,0 +1,13 @@
1
+ import { currentLocale, initLocale, setLocale, locales, translate as translateMessage } from '../../locale';
2
+
3
+ export function useLocale(namespace?: string) {
4
+ const translate = (key: string, replacements?: Record<string, unknown> | unknown[], locale?: string) =>
5
+ translateMessage(namespace ? `${namespace}.${key}` : key, replacements, locale);
6
+ return {
7
+ currentLocale: currentLocale,
8
+ locales: locales,
9
+ t: translate,
10
+ initLocales: initLocale,
11
+ setLocale: setLocale
12
+ };
13
+ }
@@ -0,0 +1,131 @@
1
+ import {
2
+ SNAKE_MODAL_EVENT_SHOW,
3
+ SNAKE_MODAL_EVENT_HIDE,
4
+ SNAKE_MODAL_EVENT_CLEAR_LOADING,
5
+ SNAKE_MODAL_GLOBAL_EVENT_SHOW,
6
+ SNAKE_MODAL_GLOBAL_EVENT_HIDE,
7
+ SNAKE_MODAL_GLOBAL_EVENT_CLEAR_LOADING,
8
+ getEventWithCurrentPage,
9
+ type ModalPayload
10
+ } from '../config/modal';
11
+ import { emitter } from '../function/emitter';
12
+
13
+ export type UseModalShowOptions = ModalPayload;
14
+
15
+ export type UseModal = {
16
+ /**
17
+ * 显示 modal
18
+ * - show('内容')
19
+ * - show({ title, content, showCancelButton, ... })
20
+ */
21
+ show: (contentOrOptions: string | UseModalShowOptions) => void;
22
+ /**
23
+ * 显示 confirm 类型的 modal(带确认和取消按钮)
24
+ * - confirm('内容')
25
+ * - confirm({ title, content, onConfirm, onCancel })
26
+ */
27
+ confirm: (contentOrOptions: string | UseModalShowOptions) => void;
28
+ /** 关闭 modal */
29
+ close: () => void;
30
+ /** 清除 loading 状态 */
31
+ clearLoading: () => void;
32
+ };
33
+
34
+ export type UseModalOptions = {
35
+ /** 是否使用全局根部 <snake-modal global /> */
36
+ global?: boolean;
37
+ /** 是否使用页面级 <snake-modal page /> 或某个页面的 <snake-modal page="pageId" /> */
38
+ page?: boolean | string;
39
+ };
40
+
41
+ function normalize(contentOrOptions: string | UseModalShowOptions): UseModalShowOptions {
42
+ if (typeof contentOrOptions === 'string') {
43
+ return { content: contentOrOptions };
44
+ }
45
+ return contentOrOptions || {};
46
+ }
47
+
48
+ function getPage(optionsOrGlobal: UseModalOptions | boolean): string {
49
+ if (typeof optionsOrGlobal === 'boolean') {
50
+ return '';
51
+ }
52
+ if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
53
+ return optionsOrGlobal.page;
54
+ }
55
+ return '';
56
+ }
57
+
58
+ /**
59
+ * Modal 函数式调用
60
+ * @description 需要页面/应用中至少存在一个 <snake-modal global /> 或 <snake-modal page /> 实例用于承接事件;不影响原 v-model 调用方式。
61
+ *
62
+ * 支持两种调用方式:
63
+ * - 应用级 useModal() / useModal(true) / useModal({ global: true })
64
+ * - 页面级 useModal(false) / useModal({ page: true }) / useModal({ page: 'pageId' })(pageId 应和页面中 <snake-modal page="pageId" /> 的 page 属性一致)
65
+ */
66
+ export function useModal(optionsOrGlobal: UseModalOptions | boolean = true): UseModal {
67
+ const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
68
+ const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
69
+
70
+ const showEvent = isGlobal
71
+ ? SNAKE_MODAL_GLOBAL_EVENT_SHOW
72
+ : isPage
73
+ ? getEventWithCurrentPage(SNAKE_MODAL_EVENT_SHOW, getPage(optionsOrGlobal))
74
+ : '';
75
+ const hideEvent = isGlobal
76
+ ? SNAKE_MODAL_GLOBAL_EVENT_HIDE
77
+ : isPage
78
+ ? getEventWithCurrentPage(SNAKE_MODAL_EVENT_HIDE, getPage(optionsOrGlobal))
79
+ : '';
80
+ const clearLoadingEvent = isGlobal
81
+ ? SNAKE_MODAL_GLOBAL_EVENT_CLEAR_LOADING
82
+ : isPage
83
+ ? getEventWithCurrentPage(SNAKE_MODAL_EVENT_CLEAR_LOADING, getPage(optionsOrGlobal))
84
+ : '';
85
+
86
+ function emitShow(payload: UseModalShowOptions) {
87
+ if (showEvent) {
88
+ emitter.emit(showEvent, payload);
89
+ }
90
+ }
91
+
92
+ function emitHide() {
93
+ if (hideEvent) {
94
+ emitter.emit(hideEvent, undefined);
95
+ }
96
+ }
97
+
98
+ function emitClearLoading() {
99
+ if (clearLoadingEvent) {
100
+ emitter.emit(clearLoadingEvent, undefined);
101
+ }
102
+ }
103
+
104
+ function show(contentOrOptions: string | UseModalShowOptions) {
105
+ emitShow(normalize(contentOrOptions));
106
+ }
107
+
108
+ function confirm(contentOrOptions: string | UseModalShowOptions) {
109
+ const options = normalize(contentOrOptions);
110
+ emitShow({
111
+ ...options,
112
+ showCancelButton: options.showCancelButton ?? true,
113
+ showConfirmButton: options.showConfirmButton ?? true
114
+ });
115
+ }
116
+
117
+ function clearLoading() {
118
+ emitClearLoading();
119
+ }
120
+
121
+ function close() {
122
+ emitHide();
123
+ }
124
+
125
+ return {
126
+ show,
127
+ confirm,
128
+ close,
129
+ clearLoading
130
+ };
131
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ ref,
3
+ shallowRef,
4
+ inject,
5
+ computed,
6
+ onUnmounted,
7
+ onMounted,
8
+ getCurrentInstance,
9
+ type InjectionKey,
10
+ type ComponentPublicInstance,
11
+ type ComponentInternalInstance,
12
+ type Ref,
13
+ type ShallowRef
14
+ } from 'vue';
15
+
16
+ /**
17
+ * useChildren 的 linkChildren 在业务上下文之外附加的注册通道,
18
+ * 子侧经 inject 拿到的实际形态。
19
+ */
20
+ export type RelationParent<T> = T & {
21
+ children: ComponentPublicInstance[];
22
+ link(child: ComponentInternalInstance): void;
23
+ unlink(child: ComponentInternalInstance): void;
24
+ internalChildren: ComponentInternalInstance[];
25
+ };
26
+
27
+ type UseParentResult<T> = {
28
+ parent: ShallowRef<RelationParent<T> | null>;
29
+ index: Ref<number>;
30
+ };
31
+
32
+ /**
33
+ * 子组件侧的关系 hook:按 InjectionKey 注入父上下文,自动注册 / 注销自身,
34
+ * 并暴露响应式下标。配套 {@link useChildren} 使用(provide/inject 底座)。
35
+ * 参数须保持裸 `InjectionKey<T>` 形态,嵌套泛型会让 T 推断失效。
36
+ */
37
+ export function useParent<T>(key: InjectionKey<T>): UseParentResult<T> {
38
+ const result: UseParentResult<T> = {
39
+ parent: shallowRef<RelationParent<T> | null>(null),
40
+ index: ref<number>(-1)
41
+ };
42
+ // #ifdef MP-TOUTIAO
43
+ onMounted(() => {
44
+ // #endif
45
+ // 注入值在运行时携带 link / unlink(见 useChildren.linkChildren),此处按 RelationParent 视图读取
46
+ result.parent.value = inject<RelationParent<T> | null>(key, null);
47
+
48
+ if (result.parent.value) {
49
+ const instance = getCurrentInstance()!;
50
+ const { link, unlink, internalChildren } = result.parent.value;
51
+
52
+ link(instance);
53
+ onUnmounted(() => unlink(instance));
54
+
55
+ result.index = computed(() => internalChildren.indexOf(instance));
56
+ }
57
+ // #ifdef MP-TOUTIAO
58
+ });
59
+ // #endif
60
+
61
+ return result;
62
+ }
@@ -0,0 +1,44 @@
1
+ import { getCurrentInstance, nextTick, ref } from 'vue';
2
+
3
+ /**
4
+ * useRect - 获取元素的位置信息(响应式版本)。
5
+ * 基础层的 function/getRect.ts 因 node 直载测试约束保持无 vue 依赖,
6
+ * 响应式能力(rect ref + refresh)由本 hook 提供。
7
+ * @param selector 选择器(如 #id 或 .class)
8
+ * @param all 是否获取所有匹配元素
9
+ * @returns rect 响应式的节点信息,refresh 主动刷新方法
10
+ */
11
+ export function useRect(selector: string | null = null, all = false) {
12
+ const rect = ref<any>(all ? [] : null);
13
+ const instance = getCurrentInstance();
14
+
15
+ async function getRect(realSelector: string | null = null, delay = 0): Promise<any> {
16
+ const targetSelector = realSelector || selector;
17
+ if (!targetSelector) return rect.value;
18
+ await nextTick();
19
+ return new Promise(resolve => {
20
+ setTimeout(() => {
21
+ uni.createSelectorQuery()
22
+ .in(instance?.proxy)
23
+ [all ? 'selectAll' : 'select'](targetSelector as string)
24
+ .boundingClientRect((res: any) => {
25
+ rect.value = res;
26
+ resolve(res);
27
+ })
28
+ .exec();
29
+ }, delay);
30
+ });
31
+ }
32
+
33
+ function refresh(selector?: string | null, delay?: number): Promise<any> {
34
+ return getRect(selector, delay);
35
+ }
36
+
37
+ return {
38
+ rect,
39
+ getRect,
40
+ refresh
41
+ };
42
+ }
43
+
44
+ export default useRect;
@@ -0,0 +1,5 @@
1
+ import { navTo, route } from '../function/route';
2
+
3
+ export function useRouter() {
4
+ return { route, navTo };
5
+ }
@@ -0,0 +1,31 @@
1
+ import { computed } from 'vue';
2
+ import {
3
+ currentTheme,
4
+ initTheme,
5
+ isDarkMode,
6
+ setDarkMode,
7
+ setTheme,
8
+ setThemeColor,
9
+ color,
10
+ cssVars,
11
+ darkMode,
12
+ themes,
13
+ toggleDarkMode
14
+ } from '../config/theme';
15
+
16
+ export function useTheme() {
17
+ return {
18
+ currentTheme: currentTheme,
19
+ themes: themes,
20
+ darkMode: darkMode,
21
+ color: color,
22
+ cssVars: cssVars,
23
+ themeStyle: computed(() => cssVars.value),
24
+ initTheme: initTheme,
25
+ setTheme: setTheme,
26
+ setThemeColor: setThemeColor,
27
+ setDarkMode: setDarkMode,
28
+ toggleDarkMode: toggleDarkMode,
29
+ isInDarkMode: isDarkMode
30
+ };
31
+ }
@@ -0,0 +1,5 @@
1
+ import { throttle } from '../function';
2
+
3
+ export function useThrottle<Args extends unknown[]>(callback: (...args: Args) => void, wait?: number) {
4
+ return throttle(callback, wait);
5
+ }