@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,188 @@
1
+ <template>
2
+ <view :class="['snake-subsection', `snake-subsection--${mode}`, customClass]" :style="rootStyle" role="tablist">
3
+ <view v-if="list.length" class="snake-subsection__slider" :style="sliderStyle" />
4
+ <view
5
+ v-for="(option, index) in list"
6
+ :key="index"
7
+ class="snake-subsection__item"
8
+ :class="{ 'is-disabled': isDisabled(option) }"
9
+ :style="itemStyle(option, index)"
10
+ role="tab"
11
+ :aria-selected="index === activeIndex"
12
+ :aria-disabled="isDisabled(option)"
13
+ :tabindex="isDisabled(option) ? -1 : index === activeIndex ? 0 : -1"
14
+ @click="select(option, index)"
15
+ @keydown.left.prevent="selectAdjacent(index, -1)"
16
+ @keydown.right.prevent="selectAdjacent(index, 1)"
17
+ >
18
+ <text class="snake-subsection__text">{{ optionName(option) }}</text>
19
+ </view>
20
+ </view>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ /**
25
+ * @component Subsection
26
+ * @description 用于在少量互斥选项之间快速切换,支持按钮式滑块和描边分段两种模式。
27
+ * @see /components/subsection
28
+ */
29
+
30
+ import { computed } from 'vue';
31
+ import { addUnit } from '../../libs/function/unit';
32
+ import type { SubsectionEmits, SubsectionItem, SubsectionOption, SubsectionProps } from './types';
33
+
34
+ defineOptions({
35
+ name: 'Subsection',
36
+ options: {
37
+ virtualHost: true,
38
+ styleIsolation: 'shared'
39
+ }
40
+ });
41
+
42
+ const props = withDefaults(defineProps<SubsectionProps>(), {
43
+ list: () => [],
44
+ current: 0,
45
+ mode: 'button',
46
+ activeColor: 'var(--snake-color-primary, #06d290)',
47
+ inactiveColor: 'var(--snake-color-text-secondary, #606266)',
48
+ fontSize: 28,
49
+ height: 70,
50
+ animation: true,
51
+ bold: true,
52
+ bgColor: 'var(--snake-color-surface-muted, #f4f6f5)',
53
+ buttonColor: 'var(--snake-color-surface, #ffffff)',
54
+ customClass: '',
55
+ customStyle: ''
56
+ });
57
+
58
+ const emit = defineEmits<SubsectionEmits>();
59
+
60
+ const activeIndex = computed(() => {
61
+ if (!props.list.length) return 0;
62
+ return Math.max(0, Math.min(props.list.length - 1, Number(props.current) || 0));
63
+ });
64
+
65
+ const rootStyle = computed(() => [
66
+ {
67
+ height: addUnit(props.height, 'rpx'),
68
+ padding: props.mode === 'button' ? '6rpx' : '0',
69
+ borderColor: props.mode === 'subsection' ? props.activeColor : 'transparent',
70
+ backgroundColor: props.mode === 'button' ? props.bgColor : 'transparent'
71
+ },
72
+ props.customStyle
73
+ ]);
74
+
75
+ const sliderStyle = computed(() => {
76
+ const count = Math.max(props.list.length, 1);
77
+ return {
78
+ left: props.mode === 'button' ? '6rpx' : '0',
79
+ width: props.mode === 'button' ? `calc((100% - 12rpx) / ${count})` : `${100 / count}%`,
80
+ top: props.mode === 'button' ? '6rpx' : '0',
81
+ bottom: props.mode === 'button' ? '6rpx' : '0',
82
+ backgroundColor: props.mode === 'button' ? props.buttonColor : props.activeColor,
83
+ borderRadius: props.mode === 'button' ? '8rpx' : sliderRadius.value,
84
+ transform: `translateX(${activeIndex.value * 100}%)`,
85
+ transitionDuration: props.animation ? '0.3s' : '0s'
86
+ };
87
+ });
88
+
89
+ const sliderRadius = computed(() => {
90
+ if (props.list.length <= 1) return '8rpx';
91
+ if (activeIndex.value === 0) return '8rpx 0 0 8rpx';
92
+ if (activeIndex.value === props.list.length - 1) return '0 8rpx 8rpx 0';
93
+ return '0';
94
+ });
95
+
96
+ function optionName(option: SubsectionOption): string {
97
+ return typeof option === 'string' ? option : option.name;
98
+ }
99
+
100
+ function isDisabled(option: SubsectionOption): boolean {
101
+ return typeof option === 'object' && Boolean((option as SubsectionItem).disabled);
102
+ }
103
+
104
+ function itemStyle(option: SubsectionOption, index: number) {
105
+ const selected = index === activeIndex.value;
106
+ return {
107
+ color:
108
+ props.mode === 'subsection' && selected
109
+ ? 'var(--snake-color-white, #ffffff)'
110
+ : selected
111
+ ? props.activeColor
112
+ : props.inactiveColor,
113
+ borderColor: props.mode === 'subsection' ? props.activeColor : 'transparent',
114
+ fontSize: addUnit(props.fontSize, 'rpx'),
115
+ fontWeight: selected && props.bold ? '600' : '400',
116
+ opacity: isDisabled(option) ? 0.45 : 1
117
+ };
118
+ }
119
+
120
+ function select(option: SubsectionOption, index: number) {
121
+ if (isDisabled(option) || index === activeIndex.value) return;
122
+ emit('update:current', index);
123
+ emit('change', index);
124
+ }
125
+
126
+ function selectAdjacent(index: number, direction: -1 | 1) {
127
+ for (let offset = 1; offset <= props.list.length; offset += 1) {
128
+ const target = (index + direction * offset + props.list.length) % props.list.length;
129
+ const option = props.list[target];
130
+ if (option && !isDisabled(option)) {
131
+ select(option, target);
132
+ return;
133
+ }
134
+ }
135
+ }
136
+ </script>
137
+
138
+ <style scoped>
139
+ .snake-subsection {
140
+ position: relative;
141
+ box-sizing: border-box;
142
+ display: flex;
143
+ width: 100%;
144
+ align-items: stretch;
145
+ overflow: hidden;
146
+ border: 1rpx solid transparent;
147
+ border-radius: 10rpx;
148
+ }
149
+
150
+ .snake-subsection__slider {
151
+ position: absolute;
152
+ z-index: 0;
153
+ transition-property: transform, background-color, border-radius;
154
+ transition-timing-function: ease;
155
+ }
156
+
157
+ .snake-subsection__item {
158
+ position: relative;
159
+ z-index: 1;
160
+ box-sizing: border-box;
161
+ display: flex;
162
+ flex: 1;
163
+ min-width: 0;
164
+ align-items: center;
165
+ justify-content: center;
166
+ padding: 0 8rpx;
167
+ border-right: 1rpx solid transparent;
168
+ line-height: 1.2;
169
+ text-align: center;
170
+ transition:
171
+ color 0.3s ease,
172
+ opacity 0.2s ease;
173
+ }
174
+
175
+ .snake-subsection--subsection .snake-subsection__item:not(:last-child) {
176
+ border-right-color: var(--snake-color-primary, #06d290);
177
+ }
178
+
179
+ .snake-subsection__item.is-disabled {
180
+ cursor: not-allowed;
181
+ }
182
+
183
+ .snake-subsection__text {
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ white-space: nowrap;
187
+ }
188
+ </style>
@@ -0,0 +1,54 @@
1
+ import type { UnitValue } from '../../libs/function/unit';
2
+ import type { StyleValue } from '../../types/global';
3
+
4
+ /** SubsectionMode 类型定义。 */
5
+ export type SubsectionMode = 'button' | 'subsection';
6
+
7
+ /** Subsection 数据项。 */
8
+ export interface SubsectionItem {
9
+ /** 数据项名称或唯一标识。 */
10
+ name: string;
11
+ /** 是否禁用交互。 */
12
+ disabled?: boolean;
13
+ }
14
+
15
+ /** Subsection 可选项。 */
16
+ export type SubsectionOption = string | SubsectionItem;
17
+
18
+ /** Subsection 组件的公开属性。 */
19
+ export interface SubsectionProps {
20
+ /** 用于渲染的数据列表。 */
21
+ list?: SubsectionOption[];
22
+ /** 当前激活项索引。 */
23
+ current?: number | string;
24
+ /** 组件展示模式。 */
25
+ mode?: SubsectionMode;
26
+ /** 激活状态颜色。 */
27
+ activeColor?: string;
28
+ /** 未激活状态颜色。 */
29
+ inactiveColor?: string;
30
+ /** 文字尺寸,数值按 rpx 处理。 */
31
+ fontSize?: UnitValue;
32
+ /** 组件高度。 */
33
+ height?: UnitValue;
34
+ /** animation 配置。 */
35
+ animation?: boolean;
36
+ /** 是否使用粗体文字。 */
37
+ bold?: boolean;
38
+ /** 背景颜色。 */
39
+ bgColor?: string;
40
+ /** button color 配置。 */
41
+ buttonColor?: string;
42
+ /** 根节点自定义类名。 */
43
+ customClass?: string;
44
+ /** 根节点自定义样式。 */
45
+ customStyle?: StyleValue;
46
+ }
47
+
48
+ /** Subsection 组件的事件契约。 */
49
+ export type SubsectionEmits = {
50
+ /** change 事件携带的参数。 */
51
+ change: [index: number];
52
+ /** update:current 事件携带的参数。 */
53
+ 'update:current': [index: number];
54
+ };
@@ -0,0 +1,229 @@
1
+ <template>
2
+ <view class="snake-swiper" :class="customClass" :style="rootStyle">
3
+ <swiper
4
+ class="snake-swiper__body"
5
+ :current="currentIndex"
6
+ :autoplay="autoplay"
7
+ :interval="interval"
8
+ :duration="duration"
9
+ :circular="circular"
10
+ :previous-margin="margin"
11
+ :next-margin="effect3d ? margin : nextMargin"
12
+ @change="change"
13
+ @animationfinish="finish"
14
+ >
15
+ <swiper-item v-for="(item, index) in list" :key="index" class="snake-swiper__item">
16
+ <view
17
+ class="snake-swiper__content"
18
+ :style="{
19
+ borderRadius: addUnit(actualRadius, 'rpx'),
20
+ transform: effect3d && index !== active ? 'scaleY(0.9)' : 'scaleY(1)',
21
+ margin: effect3d && index !== active ? '0 20rpx' : 0
22
+ }"
23
+ @click.stop.prevent="emit('click', index)"
24
+ >
25
+ <image class="snake-swiper__image" :src="source(item)" :mode="imgMode as any" />
26
+ <text
27
+ v-if="(showTitle || title) && itemTitle(item)"
28
+ class="snake-swiper__title"
29
+ :style="[titleStyle, { paddingBottom: titlePaddingBottom }]"
30
+ >
31
+ {{ itemTitle(item) }}
32
+ </text>
33
+ </view>
34
+ </swiper-item>
35
+ </swiper>
36
+ <view v-if="showIndicator" class="snake-swiper__indicator" :class="indicatorClass">
37
+ <slot name="indicator" :current="active" :total="list.length">
38
+ <text v-if="mode === 'number'" class="snake-swiper__number">{{ active + 1 }}/{{ list.length }}</text>
39
+ <view v-else-if="mode !== 'none'" class="snake-swiper__dots" :class="`is-${mode}`">
40
+ <view
41
+ v-for="(_, index) in list"
42
+ :key="index"
43
+ class="snake-swiper__dot"
44
+ :class="{ 'is-active': index === active }"
45
+ :style="{ backgroundColor: index === active ? indicatorActiveColor : indicatorColor }"
46
+ />
47
+ </view>
48
+ </slot>
49
+ </view>
50
+ </view>
51
+ </template>
52
+
53
+ <script setup lang="ts">
54
+ /**
55
+ * @component Swiper
56
+ * @description 基于 uni-app swiper 封装,支持图片或对象数据、自定义指示器、标题和 3D 卡片效果。
57
+ * @see /components/swiper
58
+ */
59
+
60
+ import { computed, ref, watch } from 'vue';
61
+ import { addUnit } from '../../libs/function/unit';
62
+ import type { SwiperEmits, SwiperItem, SwiperProps } from './types';
63
+
64
+ defineOptions({ name: 'Swiper', options: { virtualHost: true, styleIsolation: 'shared' } });
65
+ const props = withDefaults(defineProps<SwiperProps>(), {
66
+ list: () => [],
67
+ current: 0,
68
+ height: 250,
69
+ autoplay: true,
70
+ interval: 3000,
71
+ duration: 500,
72
+ circular: true,
73
+ indicator: () => ({}),
74
+ mode: 'round',
75
+ indicatorPos: 'bottomCenter',
76
+ indicatorColor: 'rgba(0,0,0,.3)',
77
+ indicatorActiveColor: 'rgba(255,255,255,.8)',
78
+ name: 'image',
79
+ radius: 8,
80
+ previousMargin: '0px',
81
+ nextMargin: '0px',
82
+ effect3d: false,
83
+ effect3dPreviousMargin: 50,
84
+ imgMode: 'aspectFill',
85
+ showTitle: false,
86
+ bgColor: 'var(--snake-color-bg, #f7f8fa)',
87
+ titleStyle: '',
88
+ customClass: '',
89
+ customStyle: ''
90
+ });
91
+ const emit = defineEmits<SwiperEmits>();
92
+ const active = ref(Number(props.current) || 0);
93
+ const currentIndex = computed(() => Number(props.current) || 0);
94
+ watch(
95
+ () => props.list.length,
96
+ (length, previousLength) => {
97
+ if (length !== previousLength) active.value = 0;
98
+ }
99
+ );
100
+ watch(
101
+ () => props.current,
102
+ current => (active.value = Number(current) || 0)
103
+ );
104
+ const actualRadius = computed(() => props.borderRadius ?? props.radius);
105
+ const rootStyle = computed(() => [
106
+ {
107
+ height: addUnit(props.height, 'rpx'),
108
+ borderRadius: addUnit(actualRadius.value, 'rpx'),
109
+ backgroundColor: props.bgColor
110
+ },
111
+ props.customStyle
112
+ ]);
113
+ const source = (item: SwiperItem) =>
114
+ typeof item === 'string' ? item : String(item?.[props.name] ?? item?.image ?? item?.src ?? item?.url ?? '');
115
+ const itemTitle = (item: SwiperItem) => (typeof item === 'string' ? '' : String(item?.title ?? item?.text ?? ''));
116
+ const showIndicator = computed(() => props.indicator !== false);
117
+ const margin = computed(() => (props.effect3d ? addUnit(props.effect3dPreviousMargin, 'rpx') : props.previousMargin));
118
+ const indicatorClass = computed(() => `snake-swiper__indicator--${props.indicatorPos}`);
119
+ const titlePaddingBottom = computed(() => {
120
+ if (props.mode === 'none') return '12rpx';
121
+ return props.indicatorPos.startsWith('bottom') ? (props.mode === 'number' ? '60rpx' : '40rpx') : '12rpx';
122
+ });
123
+ function change(event: any) {
124
+ active.value = Number(event.detail?.current) || 0;
125
+ emit('change', active.value);
126
+ }
127
+ function finish(event: any) {
128
+ emit('animationfinish', { current: Number(event.detail?.current) || 0, source: event.detail?.source });
129
+ }
130
+ </script>
131
+
132
+ <style scoped>
133
+ .snake-swiper,
134
+ .snake-swiper__body,
135
+ .snake-swiper__item {
136
+ position: relative;
137
+ width: 100%;
138
+ height: 100%;
139
+ overflow: hidden;
140
+ }
141
+ .snake-swiper {
142
+ transform: translateY(0);
143
+ }
144
+ .snake-swiper__content {
145
+ position: relative;
146
+ box-sizing: content-box;
147
+ width: 100%;
148
+ height: 100%;
149
+ overflow: hidden;
150
+ transition: all 0.5s;
151
+ }
152
+ .snake-swiper__image {
153
+ display: block;
154
+ width: 100%;
155
+ height: 100%;
156
+ pointer-events: none;
157
+ }
158
+ .snake-swiper__title {
159
+ position: absolute;
160
+ right: 0;
161
+ bottom: 0;
162
+ left: 0;
163
+ box-sizing: border-box;
164
+ width: 100%;
165
+ padding: 12rpx 24rpx;
166
+ color: rgba(255, 255, 255, 0.9);
167
+ font-size: 28rpx;
168
+ background-color: rgba(0, 0, 0, 0.3);
169
+ }
170
+ .snake-swiper__indicator {
171
+ position: absolute;
172
+ z-index: 2;
173
+ display: flex;
174
+ box-sizing: border-box;
175
+ width: 100%;
176
+ padding: 0 24rpx;
177
+ }
178
+ .snake-swiper__indicator--topLeft {
179
+ top: 12rpx;
180
+ }
181
+ .snake-swiper__indicator--topCenter {
182
+ top: 12rpx;
183
+ justify-content: center;
184
+ }
185
+ .snake-swiper__indicator--topRight {
186
+ top: 12rpx;
187
+ justify-content: flex-end;
188
+ }
189
+ .snake-swiper__indicator--bottomLeft {
190
+ bottom: 12rpx;
191
+ }
192
+ .snake-swiper__indicator--bottomCenter {
193
+ bottom: 12rpx;
194
+ justify-content: center;
195
+ }
196
+ .snake-swiper__indicator--bottomRight {
197
+ bottom: 12rpx;
198
+ justify-content: flex-end;
199
+ }
200
+ .snake-swiper__dots {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 10rpx;
204
+ }
205
+ .snake-swiper__dot {
206
+ width: 12rpx;
207
+ height: 12rpx;
208
+ border-radius: 999px;
209
+ transition:
210
+ width 0.2s ease,
211
+ background-color 0.2s ease;
212
+ }
213
+ .snake-swiper__dots.is-round .snake-swiper__dot.is-active {
214
+ width: 34rpx;
215
+ }
216
+ .snake-swiper__dots.is-rect .snake-swiper__dot,
217
+ .snake-swiper__dots.is-line .snake-swiper__dot {
218
+ width: 26rpx;
219
+ height: 8rpx;
220
+ border-radius: 0;
221
+ }
222
+ .snake-swiper__number {
223
+ padding: 6rpx 16rpx;
224
+ border-radius: 999px;
225
+ color: rgba(255, 255, 255, 0.8);
226
+ font-size: 26rpx;
227
+ background: rgba(0, 0, 0, 0.3);
228
+ }
229
+ </style>
@@ -0,0 +1,79 @@
1
+ import type { UnitValue } from '../../libs/function/unit';
2
+ import type { ImageMode, StyleValue } from '../../types/global';
3
+ /** Swiper 数据项。 */
4
+ export type SwiperItem = string | Record<string, unknown>;
5
+ /** SwiperIndicatorMode 类型定义。 */
6
+ export type SwiperIndicatorMode = 'round' | 'dot' | 'rect' | 'line' | 'number' | 'none';
7
+ /** SwiperIndicatorPosition 类型定义。 */
8
+ export type SwiperIndicatorPosition = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
9
+ /** Swiper 组件的公开属性。 */
10
+ export interface SwiperProps {
11
+ /** 用于渲染的数据列表。 */
12
+ list?: SwiperItem[];
13
+ /** 当前激活项索引。 */
14
+ current?: number | string;
15
+ /** 组件高度。 */
16
+ height?: UnitValue;
17
+ /** 是否自动播放。 */
18
+ autoplay?: boolean;
19
+ /** interval 配置。 */
20
+ interval?: number;
21
+ /** 动画持续时间。 */
22
+ duration?: number;
23
+ /** circular 配置。 */
24
+ circular?: boolean;
25
+ /** indicator 配置。 */
26
+ indicator?: boolean | Record<string, unknown>;
27
+ /** 组件展示模式。 */
28
+ mode?: SwiperIndicatorMode;
29
+ /** indicator pos 配置。 */
30
+ indicatorPos?: SwiperIndicatorPosition;
31
+ /** indicator color 配置。 */
32
+ indicatorColor?: string;
33
+ /** indicator active color 配置。 */
34
+ indicatorActiveColor?: string;
35
+ /** 数据项名称或唯一标识。 */
36
+ name?: string;
37
+ /** 圆角尺寸。 */
38
+ radius?: UnitValue;
39
+ /** 圆角尺寸。 */
40
+ borderRadius?: UnitValue;
41
+ /** previous margin 配置。 */
42
+ previousMargin?: string;
43
+ /** next margin 配置。 */
44
+ nextMargin?: string;
45
+ /** effect3d 配置。 */
46
+ effect3d?: boolean;
47
+ /** effect3d previous margin 配置。 */
48
+ effect3dPreviousMargin?: UnitValue;
49
+ /** 图片裁剪与缩放模式。 */
50
+ imgMode?: ImageMode;
51
+ /** show title 配置。 */
52
+ showTitle?: boolean;
53
+ /** 标题文本。 */
54
+ title?: boolean;
55
+ /** title style 配置。 */
56
+ titleStyle?: StyleValue;
57
+ /** 背景颜色。 */
58
+ bgColor?: string;
59
+ /** 根节点自定义类名。 */
60
+ customClass?: string;
61
+ /** 根节点自定义样式。 */
62
+ customStyle?: StyleValue;
63
+ }
64
+ /** SwiperChange 事件详情。 */
65
+ export interface SwiperChangeDetail {
66
+ /** 当前激活项索引。 */
67
+ current: number;
68
+ /** source 配置。 */
69
+ source?: string;
70
+ }
71
+ /** Swiper 组件的事件契约。 */
72
+ export type SwiperEmits = {
73
+ /** change 事件携带的参数。 */
74
+ change: [current: number];
75
+ /** click 事件携带的参数。 */
76
+ click: [index: number];
77
+ /** animationfinish 事件携带的参数。 */
78
+ animationfinish: [detail: SwiperChangeDetail];
79
+ };
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <view
3
+ class="snake-switch"
4
+ :class="[customClass, { 'is-disabled': disabled || loading }]"
5
+ :style="[trackStyle, customStyle]"
6
+ role="switch"
7
+ :aria-checked="checked"
8
+ :aria-disabled="disabled || loading"
9
+ :tabindex="disabled || loading ? -1 : 0"
10
+ @click="toggle"
11
+ @keydown.enter.prevent="toggle($event)"
12
+ @keydown.space.prevent="toggle($event)"
13
+ >
14
+ <view class="snake-switch__thumb" :style="thumbStyle">
15
+ <snake-loading v-if="loading" :size="numericSize * 0.55" :color="checked ? activeColor : '#909399'" />
16
+ </view>
17
+ </view>
18
+ </template>
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component Switch
22
+ * @description 切换 boolean、字符串或数字状态,支持加载、禁用、自定义值和短震动反馈。
23
+ * @see /components/switch
24
+ */
25
+
26
+ import { computed, inject } from 'vue';
27
+ import { addUnit } from '../../libs/function/unit';
28
+ import { formItemKey } from '../snake-form-item/types';
29
+ import SnakeLoading from '../snake-loading/snake-loading.vue';
30
+ import type { SwitchEmits, SwitchProps } from './types';
31
+ defineOptions({ name: 'SnakeSwitch', options: { virtualHost: true, styleIsolation: 'shared' } });
32
+ const props = withDefaults(defineProps<SwitchProps>(), {
33
+ modelValue: false,
34
+ activeValue: true,
35
+ inactiveValue: false,
36
+ loading: false,
37
+ disabled: false,
38
+ activeColor: 'var(--snake-color-primary,#06D290)',
39
+ inactiveColor: 'var(--snake-color-border,#dcdfe6)',
40
+ space: 2,
41
+ vibrateShort: false,
42
+ customClass: '',
43
+ customStyle: ''
44
+ });
45
+ const emit = defineEmits<SwitchEmits>();
46
+ const formItem = inject(formItemKey, null);
47
+ const checked = computed(() => Object.is(props.modelValue, props.activeValue));
48
+ const resolvedSize = computed(() => {
49
+ const size = props.size || formItem?.size.value || 50;
50
+ return size === 'small' ? 40 : size === 'large' ? 60 : size === 'default' ? 50 : size;
51
+ });
52
+ const numericSize = computed(() => Number.parseFloat(String(resolvedSize.value)) || 50);
53
+ const numericSpace = computed(() => Number.parseFloat(String(props.space)) || 0);
54
+ const trackStyle = computed(() => ({
55
+ width: addUnit(numericSize.value * 1.8, 'rpx'),
56
+ height: addUnit(resolvedSize.value, 'rpx'),
57
+ padding: addUnit(props.space, 'rpx'),
58
+ backgroundColor: checked.value ? props.activeColor : props.inactiveColor
59
+ }));
60
+ const thumbStyle = computed(() => ({
61
+ width: addUnit(numericSize.value - numericSpace.value * 2, 'rpx'),
62
+ height: addUnit(numericSize.value - numericSpace.value * 2, 'rpx'),
63
+ transform: checked.value ? `translateX(${addUnit(numericSize.value * 0.8, 'rpx')})` : 'translateX(0)'
64
+ }));
65
+ function toggle(event: unknown) {
66
+ emit('click', event);
67
+ if (props.disabled || props.loading) return;
68
+ const next = checked.value ? props.inactiveValue : props.activeValue;
69
+ if (props.vibrateShort && typeof uni !== 'undefined' && uni.vibrateShort)
70
+ uni.vibrateShort({ fail: () => undefined });
71
+ emit('update:modelValue', next);
72
+ emit('change', next);
73
+ void formItem?.notify('change');
74
+ }
75
+ </script>
76
+ <style scoped>
77
+ .snake-switch {
78
+ display: inline-flex;
79
+ box-sizing: content-box;
80
+ align-items: center;
81
+ border-radius: 999px;
82
+ transition: background-color 0.2s;
83
+ }
84
+ .snake-switch.is-disabled {
85
+ opacity: 0.55;
86
+ }
87
+ .snake-switch:focus-visible {
88
+ outline: 2px solid var(--snake-color-primary, #06d290);
89
+ outline-offset: 4rpx;
90
+ }
91
+ .snake-switch__thumb {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ border-radius: 50%;
96
+ background: #fff;
97
+ box-shadow: 0 2rpx 8rpx rgba(15, 30, 24, 0.18);
98
+ transition: transform 0.2s;
99
+ }
100
+ </style>
@@ -0,0 +1,41 @@
1
+ import type { UnitValue } from '../../libs/function/unit';
2
+ import type { StyleValue } from '../../types/global';
3
+ import type { FormSize } from '../snake-form/types';
4
+ /** SwitchValue 类型定义。 */
5
+ export type SwitchValue = boolean | string | number;
6
+ /** Switch 组件的公开属性。 */
7
+ export interface SwitchProps {
8
+ /** 双向绑定的当前值。 */
9
+ modelValue?: SwitchValue;
10
+ /** active value 配置。 */
11
+ activeValue?: SwitchValue;
12
+ /** inactive value 配置。 */
13
+ inactiveValue?: SwitchValue;
14
+ /** 是否显示加载状态。 */
15
+ loading?: boolean;
16
+ /** 是否禁用交互。 */
17
+ disabled?: boolean;
18
+ /** 组件尺寸。 */
19
+ size?: UnitValue | FormSize;
20
+ /** 激活状态颜色。 */
21
+ activeColor?: string;
22
+ /** 未激活状态颜色。 */
23
+ inactiveColor?: string;
24
+ /** space 配置。 */
25
+ space?: UnitValue;
26
+ /** vibrate short 配置。 */
27
+ vibrateShort?: boolean;
28
+ /** 根节点自定义类名。 */
29
+ customClass?: string;
30
+ /** 根节点自定义样式。 */
31
+ customStyle?: StyleValue;
32
+ }
33
+ /** Switch 组件的事件契约。 */
34
+ export type SwitchEmits = {
35
+ /** update:modelValue 事件携带的参数。 */
36
+ 'update:modelValue': [value: SwitchValue];
37
+ /** change 事件携带的参数。 */
38
+ change: [value: SwitchValue];
39
+ /** click 事件携带的参数。 */
40
+ click: [event: unknown];
41
+ };