@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,371 @@
1
+ <template>
2
+ <view class="snake-select" :class="customClass" :style="customStyle">
3
+ <snake-popup
4
+ v-model="popupValue"
5
+ mode="bottom"
6
+ :close-on-click-overlay="maskCloseAble"
7
+ :safe-area-inset-bottom="safeAreaInsetBottom"
8
+ :z-index="popupZIndex"
9
+ @click-overlay="overlayClick"
10
+ >
11
+ <view class="snake-select__panel">
12
+ <view class="snake-select__header" @touchmove.stop.prevent="">
13
+ <view
14
+ class="snake-select__header__cancel snake-select__header__btn"
15
+ :style="{ color: cancelColor }"
16
+ hover-class="snake-select__btn--hover"
17
+ :hover-stay-time="150"
18
+ @click="getResult('cancel')"
19
+ >
20
+ {{ cancelTextValue }}
21
+ </view>
22
+ <view class="snake-select__header__title snake-select__ellipsis">{{ title }}</view>
23
+ <view
24
+ class="snake-select__header__confirm snake-select__header__btn"
25
+ :style="{ color: moving ? cancelColor : confirmColor }"
26
+ hover-class="snake-select__btn--hover"
27
+ :hover-stay-time="150"
28
+ @touchmove.stop=""
29
+ @click.stop="getResult('confirm')"
30
+ >
31
+ {{ confirmTextValue }}
32
+ </view>
33
+ </view>
34
+ <view class="snake-select__body">
35
+ <picker-view
36
+ v-if="modelValue && readyToRender"
37
+ :value="defaultSelector"
38
+ class="snake-select__body__picker-view"
39
+ mask-class="snake-select__mask"
40
+ indicator-class="snake-select__indicator"
41
+ @pickstart="pickstart"
42
+ @pickend="pickend"
43
+ @change="columnChange"
44
+ >
45
+ <picker-view-column v-for="(item, index) in columnData" :key="index">
46
+ <view
47
+ v-for="(item1, index1) in item"
48
+ :key="index1"
49
+ class="snake-select__body__picker-view__item"
50
+ >
51
+ <view class="snake-select__ellipsis">{{ item1[labelName] }}</view>
52
+ </view>
53
+ </picker-view-column>
54
+ </picker-view>
55
+ </view>
56
+ </view>
57
+ </snake-popup>
58
+ </view>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ /**
63
+ * @component Select
64
+ * @description 列选择器,用于单列、多列、多列联动的选择场景,基于原生 picker-view 与 Popup 实现。
65
+ * @see /components/select
66
+ */
67
+
68
+ import { computed, ref, watch } from 'vue';
69
+ import zIndex from '../../libs/config/zIndex';
70
+ import { useLocale } from '../../libs/hooks/useLocale';
71
+ import Popup from '../snake-popup/snake-popup.vue';
72
+ import { selectProps, type SelectEmits, type SelectItem, type SelectResult } from './types';
73
+
74
+ defineOptions({ name: 'SnakeSelect', options: { virtualHost: true, styleIsolation: 'shared' } });
75
+ const props = defineProps(selectProps);
76
+ const emit = defineEmits<SelectEmits>();
77
+ const { t } = useLocale('select');
78
+
79
+ // 用于列改变时保存当前索引,下一次变化时比较得出是哪一列发生了变化
80
+ const defaultSelector = ref<number[]>([0]);
81
+ // picker-view 的列数据
82
+ const columnData = ref<SelectItem[][]>([]);
83
+ // 控制 picker 是否渲染(等待初始化完成),确保原生 picker-view 在数据就绪后才创建
84
+ const readyToRender = ref(false);
85
+ // 保存用户上次确认的索引,首次打开会使用 props.defaultValue
86
+ const savedSelector = ref<number[] | null>(
87
+ props.defaultValue && props.defaultValue.length ? props.defaultValue.slice() : null
88
+ );
89
+ // 每次队列发生变化时,保存选择的结果
90
+ const selectValue = ref<SelectResult[]>([]);
91
+ // 上一次列变化时的 index
92
+ const lastSelectIndex = ref<number[]>([]);
93
+ // 列数
94
+ const columnNum = ref(0);
95
+ // 列是否还在滑动中,微信小程序滑动中点确定结果可能不准确
96
+ const moving = ref(false);
97
+ // 用户有传递层级值时优先使用
98
+ const popupZIndex = computed(() => Number(props.zIndex) || zIndex.popup);
99
+
100
+ const popupValue = computed({
101
+ get: () => props.modelValue,
102
+ set: (val: boolean) => emit('update:modelValue', val)
103
+ });
104
+
105
+ const cancelTextValue = computed(() => props.cancelText || t('cancelText'));
106
+ const confirmTextValue = computed(() => props.confirmText || t('confirmText'));
107
+
108
+ watch(
109
+ () => props.modelValue,
110
+ val => {
111
+ if (val) {
112
+ // 同步调用 init(),确保 columnData 在 picker-view 渲染前就填充完毕
113
+ init();
114
+ readyToRender.value = true;
115
+ } else {
116
+ // 关闭弹窗时复位,下次打开时 picker-view 会重新创建,确保数据刷新
117
+ readyToRender.value = false;
118
+ }
119
+ },
120
+ { immediate: true }
121
+ );
122
+
123
+ /** 标识滑动开始,只有微信小程序才有这样的事件。 */
124
+ function pickstart() {
125
+ // #ifdef MP-WEIXIN
126
+ moving.value = true;
127
+ // #endif
128
+ }
129
+ /** 标识滑动结束。 */
130
+ function pickend() {
131
+ // #ifdef MP-WEIXIN
132
+ moving.value = false;
133
+ // #endif
134
+ }
135
+
136
+ function init() {
137
+ setColumnNum();
138
+ setDefaultSelector();
139
+ setColumnData();
140
+ selectValue.value = [];
141
+ setSelectValue();
142
+ }
143
+
144
+ /** 获取默认选中列下标。 */
145
+ function setDefaultSelector() {
146
+ let useDefault: number[] | null = null;
147
+ if (props.preserveSelection) {
148
+ useDefault = savedSelector.value && savedSelector.value.length ? savedSelector.value : props.defaultValue;
149
+ } else {
150
+ useDefault = props.defaultValue && props.defaultValue.length ? props.defaultValue : savedSelector.value;
151
+ }
152
+ defaultSelector.value =
153
+ useDefault && useDefault.length === columnNum.value ? useDefault.slice() : Array(columnNum.value).fill(0);
154
+ lastSelectIndex.value = [...defaultSelector.value];
155
+ }
156
+
157
+ /** 计算列数。 */
158
+ function setColumnNum() {
159
+ if (props.mode === 'single-column') columnNum.value = 1;
160
+ else if (props.mode === 'mutil-column') columnNum.value = props.list.length;
161
+ else if (props.mode === 'mutil-column-auto') {
162
+ let num = 1;
163
+ let column: SelectItem[] | unknown = props.list;
164
+ // 只要有元素并且第一个元素有 children 属性,继续遍历
165
+ while (Array.isArray(column) && column[0] && typeof column[0] === 'object' && props.childName in column[0]) {
166
+ column = (column[0] as SelectItem)[props.childName];
167
+ num++;
168
+ }
169
+ columnNum.value = num;
170
+ }
171
+ }
172
+
173
+ /** 获取需要展示在 picker 中的列数据。 */
174
+ function setColumnData() {
175
+ const data: SelectItem[][] = [];
176
+ selectValue.value = [];
177
+ if (props.mode === 'mutil-column-auto') {
178
+ let column: SelectItem[] | unknown = props.list[defaultSelector.value.length ? defaultSelector.value[0] : 0];
179
+ for (let i = 0; i < columnNum.value; i++) {
180
+ if (i === 0) {
181
+ data[i] = is2DList(props.list) ? props.list[i] || [] : props.list;
182
+ column = column && typeof column === 'object' ? (column as SelectItem)[props.childName] : [];
183
+ } else {
184
+ data[i] = Array.isArray(column) ? column : [];
185
+ column =
186
+ Array.isArray(column) &&
187
+ column[defaultSelector.value[i]] &&
188
+ typeof column[defaultSelector.value[i]] === 'object'
189
+ ? (column[defaultSelector.value[i]] as SelectItem)[props.childName]
190
+ : [];
191
+ }
192
+ }
193
+ } else if (props.mode === 'single-column') {
194
+ data[0] = Array.isArray(props.list) && !is2DList(props.list) ? props.list : [];
195
+ } else if (props.mode === 'mutil-column') {
196
+ if (is2DList(props.list)) data.push(...props.list);
197
+ else data.push(props.list);
198
+ }
199
+ columnData.value = data;
200
+ }
201
+
202
+ /** 获取默认选中的值,没有设置 defaultValue 就默认选中每列的第一个。 */
203
+ function setSelectValue() {
204
+ for (let i = 0; i < columnNum.value; i++) {
205
+ const tmp = columnData.value[i][defaultSelector.value[i]];
206
+ const result: SelectResult = {
207
+ value: tmp ? tmp[props.valueName] : null,
208
+ label: tmp ? tmp[props.labelName] : null
209
+ };
210
+ if (tmp && tmp.extra !== undefined) result.extra = tmp.extra;
211
+ selectValue.value.push(result);
212
+ }
213
+ }
214
+
215
+ /** 列变化处理。 */
216
+ function columnChange(event: { detail: { value: number[] } }) {
217
+ let index = -1;
218
+ const columnIndex = event.detail.value;
219
+ selectValue.value = [];
220
+ defaultSelector.value = columnIndex;
221
+ if (props.mode === 'mutil-column-auto') {
222
+ // 对比前后两个数组,寻找变更的是哪一列
223
+ lastSelectIndex.value.map((val, idx) => {
224
+ if (val !== columnIndex[idx]) index = idx;
225
+ });
226
+ for (let i = index + 1; i < columnNum.value; i++) {
227
+ // 变化列的下一列数据取自上一列选中项的 children,后续列默认选中第一个
228
+ const prevCol = columnData.value[i - 1];
229
+ const prevIdx = i - 1 === index ? columnIndex[index] : 0;
230
+ columnData.value[i] =
231
+ Array.isArray(prevCol) && prevCol[prevIdx] && typeof prevCol[prevIdx] === 'object'
232
+ ? ((prevCol[prevIdx] as SelectItem)[props.childName] as SelectItem[])
233
+ : [];
234
+ defaultSelector.value[i] = 0;
235
+ }
236
+ // 历遍过程中 columnData 连锁变化可能触发多次 columnChange,只有数据稳定后的结果才是正确的
237
+ columnIndex.map((_item, idx) => {
238
+ const data = columnData.value[idx][columnIndex[idx]];
239
+ const result: SelectResult = {
240
+ value: data ? data[props.valueName] : null,
241
+ label: data ? data[props.labelName] : null
242
+ };
243
+ if (data && data.extra !== undefined) result.extra = data.extra;
244
+ selectValue.value.push(result);
245
+ });
246
+ lastSelectIndex.value = [...columnIndex];
247
+ } else if (props.mode === 'single-column') {
248
+ const data = columnData.value[0][columnIndex[0]];
249
+ const result: SelectResult = {
250
+ value: data ? data[props.valueName] : null,
251
+ label: data ? data[props.labelName] : null
252
+ };
253
+ if (data && data.extra !== undefined) result.extra = data.extra;
254
+ selectValue.value.push(result);
255
+ } else if (props.mode === 'mutil-column') {
256
+ columnIndex.map((_item, idx) => {
257
+ const data = columnData.value[idx][columnIndex[idx]];
258
+ const result: SelectResult = {
259
+ value: data ? data[props.valueName] : null,
260
+ label: data ? data[props.labelName] : null
261
+ };
262
+ if (data && data.extra !== undefined) result.extra = data.extra;
263
+ selectValue.value.push(result);
264
+ });
265
+ }
266
+ }
267
+
268
+ /** 关闭选择器。 */
269
+ function closeSelect() {
270
+ emit('update:modelValue', false);
271
+ }
272
+
273
+ /** 点击遮罩关闭时发出 close 事件,供外部感知非确认/取消的关闭路径。 */
274
+ function overlayClick() {
275
+ if (props.maskCloseAble) emit('close');
276
+ }
277
+
278
+ /** 点击确定或取消。 */
279
+ function getResult(event: 'confirm' | 'cancel') {
280
+ // #ifdef MP-WEIXIN
281
+ if (moving.value) return;
282
+ // #endif
283
+ if (event === 'confirm') emit('confirm', selectValue.value);
284
+ else emit('cancel', selectValue.value);
285
+ // 用户确认后保存已确认的索引,作为下次打开时的默认值
286
+ if (event === 'confirm') {
287
+ savedSelector.value = defaultSelector.value ? defaultSelector.value.slice() : null;
288
+ }
289
+ closeSelect();
290
+ }
291
+
292
+ /** 类型守卫:是否为二维列表。 */
293
+ function is2DList(list: SelectItem[] | SelectItem[][]): list is SelectItem[][] {
294
+ return Array.isArray(list) && list.length > 0 && Array.isArray(list[0]);
295
+ }
296
+ </script>
297
+
298
+ <style scoped>
299
+ .snake-select__panel {
300
+ width: 100%;
301
+ }
302
+ .snake-select__header {
303
+ position: relative;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: space-between;
307
+ height: 80rpx;
308
+ }
309
+ .snake-select__header::after {
310
+ content: '';
311
+ position: absolute;
312
+ right: 0;
313
+ bottom: 0;
314
+ left: 0;
315
+ border-bottom: 1rpx solid var(--snake-color-border, #e4e7ed);
316
+ transform: scaleY(0.5);
317
+ }
318
+ .snake-select__header__title {
319
+ color: var(--snake-color-text-secondary, #606266);
320
+ }
321
+ .snake-select__header__btn {
322
+ min-width: 150rpx;
323
+ padding: 20rpx 30rpx;
324
+ }
325
+ .snake-select__btn--hover {
326
+ opacity: 0.6;
327
+ }
328
+ .snake-select__body {
329
+ overflow: hidden;
330
+ width: 100%;
331
+ height: 500rpx;
332
+ background-color: var(--snake-color-surface, #fff);
333
+ }
334
+ .snake-select__body__picker-view {
335
+ box-sizing: border-box;
336
+ height: 100%;
337
+ }
338
+ .snake-select__body__picker-view__item {
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ padding: 0 8rpx;
343
+ font-size: 32rpx;
344
+ color: var(--snake-color-text, #303133);
345
+ }
346
+ .snake-select__ellipsis {
347
+ overflow: hidden;
348
+ text-overflow: ellipsis;
349
+ white-space: nowrap;
350
+ }
351
+ </style>
352
+
353
+ <style>
354
+ /* picker-view 原生内部节点只能通过全局类名设置样式 */
355
+ .snake-select__mask {
356
+ background-image:
357
+ linear-gradient(
358
+ 180deg,
359
+ rgba(var(--snake-color-surface-rgb, 255, 255, 255), 0.95),
360
+ rgba(var(--snake-color-surface-rgb, 255, 255, 255), 0.6)
361
+ ),
362
+ linear-gradient(
363
+ 0deg,
364
+ rgba(var(--snake-color-surface-rgb, 255, 255, 255), 0.95),
365
+ rgba(var(--snake-color-surface-rgb, 255, 255, 255), 0.6)
366
+ );
367
+ }
368
+ .snake-select__indicator {
369
+ border: rgba(var(--snake-color-surface-rgb, 255, 255, 255), 0.6);
370
+ }
371
+ </style>
@@ -0,0 +1,75 @@
1
+ import type { ExtractPropTypes, PropType } from 'vue';
2
+ import type { StyleValue } from '../../types/global';
3
+
4
+ /** SelectMode 类型定义。 */
5
+ export type SelectMode = 'single-column' | 'mutil-column' | 'mutil-column-auto';
6
+ /** SelectItem 选择列表数据项。 */
7
+ export type SelectItem = Record<string, unknown>;
8
+ /** SelectResult 确认/取消回调中的单项结果。 */
9
+ export interface SelectResult {
10
+ /** 数据项的 value 字段。 */
11
+ value: unknown;
12
+ /** 数据项的 label 字段。 */
13
+ label: unknown;
14
+ /** 数据项额外携带的参数。 */
15
+ extra?: unknown;
16
+ }
17
+
18
+ /** selectProps 运行时属性及默认值。 */
19
+ export const selectProps = {
20
+ /** 根节点自定义样式。 */
21
+ customStyle: { type: [String, Object, Array] as PropType<StyleValue>, default: '' },
22
+ /** 根节点自定义类名。 */
23
+ customClass: { type: String, default: '' },
24
+ /** 列数据。 */
25
+ list: { type: Array as PropType<SelectItem[] | SelectItem[][]>, default: () => [] },
26
+ /** 是否显示边框。 */
27
+ border: { type: Boolean, default: true },
28
+ /** 双向绑定控制选择器的弹出与收起。 */
29
+ modelValue: { type: Boolean, default: false },
30
+ /** 取消按钮的颜色。 */
31
+ cancelColor: { type: String, default: 'var(--snake-color-text-secondary, #606266)' },
32
+ /** 确认按钮的颜色。 */
33
+ confirmColor: { type: String, default: 'var(--snake-color-primary, #06D290)' },
34
+ /** 弹出时的层级,0 表示使用弹层默认层级。 */
35
+ zIndex: { type: [Number, String] as PropType<number | string>, default: 0 },
36
+ /** 是否开启底部安全区适配。 */
37
+ safeAreaInsetBottom: { type: Boolean, default: false },
38
+ /** 是否允许通过点击遮罩关闭选择器。 */
39
+ maskCloseAble: { type: Boolean, default: true },
40
+ /** 默认选中的下标数组。 */
41
+ defaultValue: { type: Array as PropType<number[]>, default: () => [0] },
42
+ /** 是否保留用户上次确认的选择。 */
43
+ preserveSelection: { type: Boolean, default: true },
44
+ /** 模式选择,single-column 单列,mutil-column 多列,mutil-column-auto 多列联动。 */
45
+ mode: { type: String as PropType<SelectMode>, default: 'single-column' },
46
+ /** 自定义 value 属性名。 */
47
+ valueName: { type: String, default: 'value' },
48
+ /** 自定义 label 属性名。 */
49
+ labelName: { type: String, default: 'label' },
50
+ /** 自定义多列联动模式的 children 属性名。 */
51
+ childName: { type: String, default: 'children' },
52
+ /** 顶部标题。 */
53
+ title: { type: String, default: '' },
54
+ /** 取消按钮的文字。 */
55
+ cancelText: { type: String, default: '' },
56
+ /** 确认按钮的文字。 */
57
+ confirmText: { type: String, default: '' }
58
+ } as const;
59
+
60
+ /** Select 组件的公开属性。 */
61
+ export type SelectProps = ExtractPropTypes<typeof selectProps>;
62
+
63
+ /** Select 组件的事件契约。 */
64
+ export type SelectEmits = {
65
+ /** 双向绑定值更新。 */
66
+ 'update:modelValue': [value: boolean];
67
+ /** 点击确认按钮,返回当前选择的值。 */
68
+ confirm: [value: SelectResult[]];
69
+ /** 点击取消按钮,返回当前选择的值。 */
70
+ cancel: [value: SelectResult[]];
71
+ /** 点击遮罩等非确认/取消方式关闭时触发。 */
72
+ close: [];
73
+ /** 点击触发入口时触发(兼容声明)。 */
74
+ click: [];
75
+ };
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <view class="snake-skeleton" :style="rootStyle">
3
+ <template v-if="loading">
4
+ <slot v-if="$slots.template" name="template" />
5
+ <view v-else class="snake-skeleton__wrapper">
6
+ <SkeletonItem
7
+ v-if="avatar"
8
+ variant="circle"
9
+ :width="avatarSize"
10
+ :height="avatarSize"
11
+ :radius="avatarRadius"
12
+ custom-style="flex: none; margin-right: 15px"
13
+ />
14
+ <view class="snake-skeleton__content">
15
+ <SkeletonItem v-if="title" variant="text" :width="titleWidth" :height="titleHeight" />
16
+ <SkeletonItem
17
+ v-for="(item, index) in rowItems"
18
+ :key="index"
19
+ variant="text"
20
+ :width="item.width"
21
+ :height="item.height"
22
+ :custom-style="{ marginTop: item.marginTop }"
23
+ />
24
+ </view>
25
+ </view>
26
+ </template>
27
+ <slot v-else />
28
+ </view>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ /**
33
+ * @component Skeleton
34
+ * @description 在首次加载数据时展示结构占位,支持快速段落模式和自定义骨架模板。
35
+ * @see /components/skeleton
36
+ */
37
+
38
+ import { computed, provide } from 'vue';
39
+ import SkeletonItem from '../snake-skeleton-item/snake-skeleton-item.vue';
40
+ import { SNAKE_SKELETON_CONTEXT } from '../snake-skeleton-item/types';
41
+ import type { SkeletonProps, SkeletonSize } from './types';
42
+
43
+ defineOptions({ name: 'Skeleton', options: { virtualHost: true, styleIsolation: 'shared' } });
44
+
45
+ const props = withDefaults(defineProps<SkeletonProps>(), {
46
+ loading: true,
47
+ animated: true,
48
+ title: true,
49
+ titleWidth: '40%',
50
+ titleHeight: 18,
51
+ rows: 3,
52
+ rowsWidth: '100%',
53
+ rowsHeight: 16,
54
+ avatar: false,
55
+ avatarSize: 32,
56
+ avatarShape: 'circle',
57
+ customStyle: ''
58
+ });
59
+
60
+ defineSlots<{
61
+ template?: () => unknown;
62
+ default?: () => unknown;
63
+ }>();
64
+
65
+ provide(SNAKE_SKELETON_CONTEXT, { animated: computed(() => props.animated) });
66
+
67
+ const rootStyle = computed(() => props.customStyle);
68
+ const avatarRadius = computed(() => (props.avatarShape === 'circle' ? '50%' : '4px'));
69
+ const rowItems = computed(() => {
70
+ const count = Math.max(0, Math.floor(Number(props.rows) || 0));
71
+ return Array.from({ length: count }, (_, index) => ({
72
+ width: resolveRowValue(props.rowsWidth, index, count, '100%', true),
73
+ height: resolveRowValue(props.rowsHeight, index, count, 16, false),
74
+ marginTop: props.title || index > 0 ? (index === 0 ? '20px' : '12px') : '0px'
75
+ }));
76
+ });
77
+
78
+ function resolveRowValue(
79
+ value: SkeletonSize | SkeletonSize[],
80
+ index: number,
81
+ count: number,
82
+ fallback: SkeletonSize,
83
+ shortenLastRow: boolean
84
+ ): SkeletonSize {
85
+ if (Array.isArray(value)) {
86
+ return value[index] ?? (shortenLastRow && index === count - 1 ? '70%' : fallback);
87
+ }
88
+ return shortenLastRow && index === count - 1 ? '70%' : value;
89
+ }
90
+ </script>
91
+
92
+ <style scoped>
93
+ .snake-skeleton {
94
+ width: 100%;
95
+ }
96
+ .snake-skeleton__wrapper {
97
+ display: flex;
98
+ width: 100%;
99
+ }
100
+ .snake-skeleton__content {
101
+ min-width: 0;
102
+ flex: 1;
103
+ }
104
+ </style>
@@ -0,0 +1,32 @@
1
+ import type { StyleValue } from '../../types/global';
2
+
3
+ /** Skeleton 支持的尺寸值。 */
4
+ export type SkeletonSize = number | string;
5
+
6
+ /** Skeleton 组件的公开属性。 */
7
+ export interface SkeletonProps {
8
+ /** 是否显示骨架内容。 */
9
+ loading?: boolean;
10
+ /** 是否启用流光动画。 */
11
+ animated?: boolean;
12
+ /** 是否显示默认标题占位。 */
13
+ title?: boolean;
14
+ /** 默认标题宽度。 */
15
+ titleWidth?: SkeletonSize;
16
+ /** 默认标题高度。 */
17
+ titleHeight?: SkeletonSize;
18
+ /** 默认段落行数。 */
19
+ rows?: number;
20
+ /** 默认段落宽度;数组可逐行配置。 */
21
+ rowsWidth?: SkeletonSize | SkeletonSize[];
22
+ /** 默认段落高度;数组可逐行配置。 */
23
+ rowsHeight?: SkeletonSize | SkeletonSize[];
24
+ /** 是否显示默认头像占位。 */
25
+ avatar?: boolean;
26
+ /** 默认头像尺寸。 */
27
+ avatarSize?: SkeletonSize;
28
+ /** 默认头像形状。 */
29
+ avatarShape?: 'circle' | 'square';
30
+ /** 根节点自定义样式。 */
31
+ customStyle?: StyleValue;
32
+ }
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <view
3
+ class="snake-skeleton-item"
4
+ :class="[`snake-skeleton-item--${variant}`, { 'snake-skeleton-item--animated': isAnimated }]"
5
+ :style="itemStyle"
6
+ />
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ /**
11
+ * @component SkeletonItem
12
+ * @description 用于组合自定义骨架模板的基础占位单元。
13
+ * @see /components/skeleton
14
+ */
15
+
16
+ import { computed, inject, type CSSProperties } from 'vue';
17
+ import { addUnit } from '../../libs/function/unit';
18
+ import { SNAKE_SKELETON_CONTEXT, type SkeletonItemProps } from './types';
19
+
20
+ defineOptions({ name: 'SkeletonItem', options: { virtualHost: true, styleIsolation: 'shared' } });
21
+
22
+ const props = withDefaults(defineProps<SkeletonItemProps>(), {
23
+ variant: 'text',
24
+ width: undefined,
25
+ height: undefined,
26
+ radius: undefined,
27
+ animated: undefined,
28
+ customStyle: ''
29
+ });
30
+
31
+ const context = inject(SNAKE_SKELETON_CONTEXT, undefined);
32
+ const isAnimated = computed(() => props.animated ?? context?.animated.value ?? true);
33
+
34
+ interface VariantDefaults {
35
+ width: number | string;
36
+ height: number | string;
37
+ radius: number | string;
38
+ }
39
+
40
+ const itemStyle = computed(() => {
41
+ const defaults = variantDefaults[props.variant];
42
+ return [
43
+ {
44
+ width: addUnit(props.width ?? defaults.width),
45
+ height: addUnit(props.height ?? defaults.height),
46
+ borderRadius: props.radius === undefined ? defaults.radius : addUnit(props.radius)
47
+ } as CSSProperties,
48
+ props.customStyle
49
+ ];
50
+ });
51
+
52
+ const variantDefaults: Record<NonNullable<SkeletonItemProps['variant']>, VariantDefaults> = {
53
+ text: { width: '100%', height: '16px', radius: '4px' },
54
+ rect: { width: '100%', height: '100px', radius: '8px' },
55
+ circle: { width: '32px', height: '32px', radius: '50%' },
56
+ image: { width: '100%', height: '100px', radius: '8px' }
57
+ };
58
+ </script>
59
+
60
+ <style scoped>
61
+ .snake-skeleton-item {
62
+ box-sizing: border-box;
63
+ flex: none;
64
+ overflow: hidden;
65
+ background-color: var(--snake-skeleton-color, #f1f2f4);
66
+ }
67
+ .snake-skeleton-item--animated {
68
+ background-image: linear-gradient(
69
+ 90deg,
70
+ var(--snake-skeleton-color, #f1f2f4) 25%,
71
+ var(--snake-skeleton-highlight, #e5e7eb) 37%,
72
+ var(--snake-skeleton-color, #f1f2f4) 63%
73
+ );
74
+ background-size: 400% 100%;
75
+ animation: snake-skeleton-shimmer 1.8s ease infinite;
76
+ }
77
+ @keyframes snake-skeleton-shimmer {
78
+ 0% {
79
+ background-position: 100% 50%;
80
+ }
81
+ 100% {
82
+ background-position: 0 50%;
83
+ }
84
+ }
85
+ @media (prefers-reduced-motion: reduce) {
86
+ .snake-skeleton-item--animated {
87
+ animation: none;
88
+ }
89
+ }
90
+ </style>