@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,185 @@
1
+ <template>
2
+ <view
3
+ v-if="filters.topFilter"
4
+ id="filtersId"
5
+ class="filter-sorts"
6
+ :style="[{ backgroundColor: bgColor }, customStyle]"
7
+ >
8
+ <view class="filter-sorts__toolbar" role="toolbar" aria-label="商品排序与筛选">
9
+ <template v-for="(item, index) in filters.topFilter" :key="index">
10
+ <view
11
+ v-if="item.type === 'click'"
12
+ class="filter-sorts__item"
13
+ :class="{
14
+ 'filter-sorts__item--active': filterParams['sort'] === item.value
15
+ }"
16
+ role="button"
17
+ tabindex="0"
18
+ :aria-pressed="filterParams['sort'] === item.value"
19
+ @click="sortSelectHandle('sort', item.value ?? '')"
20
+ @keydown.enter.prevent="sortSelectHandle('sort', item.value ?? '')"
21
+ @keydown.space.prevent="sortSelectHandle('sort', item.value ?? '')"
22
+ >
23
+ {{ item.name }}
24
+ </view>
25
+ <snake-button-sort
26
+ v-if="item.type === 'sort'"
27
+ :title="item.name"
28
+ @change="(e: any) => sortSelectHandle('price', e.value)"
29
+ ></snake-button-sort>
30
+ </template>
31
+ <snake-drop-menu ref="dropMenuRef" custom-style="flex: 1; min-width: 0; background: transparent;">
32
+ <snake-drop-menu-item
33
+ title="筛选"
34
+ custom-style="height: 70vh"
35
+ :before-toggle="handleBeforeToggle"
36
+ @close="dropMenuClose"
37
+ >
38
+ <filterPage
39
+ :show="showDrawer"
40
+ :filters="filters"
41
+ :defaultParams="filterParams"
42
+ @confirm="filterConfirm"
43
+ ></filterPage>
44
+ </snake-drop-menu-item>
45
+ </snake-drop-menu>
46
+ </view>
47
+ </view>
48
+ </template>
49
+ <script lang="ts" setup>
50
+ /**
51
+ * @component FilterSorts
52
+ * @description 组合顶部快捷排序、价格排序和多维筛选面板,并向父级输出统一查询参数。
53
+ */
54
+
55
+ import { getCurrentInstance, onMounted, ref } from 'vue';
56
+ import filterPage from './filter-page.vue';
57
+ import type { FilterSortsEmits, FilterSortsProps } from './types';
58
+
59
+ defineOptions({
60
+ name: 'filter-sorts',
61
+ options: {
62
+ virtualHost: true,
63
+ addGlobalClass: true,
64
+ styleIsolation: 'shared'
65
+ }
66
+ });
67
+
68
+ const props = withDefaults(defineProps<FilterSortsProps>(), {
69
+ filters: () => ({}),
70
+ defaultFilter: () => ({}),
71
+ bgColor: 'transparent',
72
+ sort: '',
73
+ showTabs: true,
74
+ goodsListLength: 0,
75
+ customStyle: () => ({})
76
+ });
77
+
78
+ const emit = defineEmits<FilterSortsEmits>();
79
+
80
+ const instance = getCurrentInstance();
81
+
82
+ const dropMenuRef = ref<{ updateDropMenuRect: () => void; colseAll: () => void } | null>(null);
83
+
84
+ const showDrawer = ref(false);
85
+ const filterParams = ref<Record<string, string | number | boolean>>({
86
+ sort: 'auto',
87
+ price: ''
88
+ });
89
+
90
+ const dropMenuOpen = () => {
91
+ showDrawer.value = true;
92
+ };
93
+ const dropMenuClose = async () => {
94
+ showDrawer.value = false;
95
+ };
96
+ // 异步打开
97
+ const handleBeforeToggle = async ({ resolve }: { resolve: (value: boolean) => void }) => {
98
+ let scrollTop = 0;
99
+
100
+ uni.createSelectorQuery()
101
+ .in(instance)
102
+ .selectViewport()
103
+ .scrollOffset(res => {
104
+ scrollTop = (res as UniApp.NodeInfo).scrollTop || 0;
105
+ })
106
+ .exec();
107
+
108
+ const query = uni.createSelectorQuery().in(instance);
109
+ query.select('#filtersId').boundingClientRect();
110
+ query.exec(res => {
111
+ const navbarHeight = 44;
112
+ const tabsHeight = props.showTabs ? 42 : 0;
113
+ scrollTop = scrollTop + (res[0]?.top || 0) - navbarHeight - tabsHeight;
114
+ // #ifndef H5
115
+ scrollTop = scrollTop - (uni.$snake.sys().statusBarHeight || 0);
116
+ // #endif
117
+ if (scrollTop > 0) {
118
+ uni.pageScrollTo({
119
+ scrollTop,
120
+ duration: 0,
121
+ success: () => {
122
+ dropMenuRef.value?.updateDropMenuRect();
123
+ dropMenuOpen();
124
+ resolve(true);
125
+ }
126
+ });
127
+ } else {
128
+ dropMenuRef.value?.updateDropMenuRect();
129
+ dropMenuOpen();
130
+ resolve(true);
131
+ }
132
+ });
133
+ };
134
+
135
+ // 排序选择
136
+ const sortSelectHandle = (propName: string | number, value: string) => {
137
+ filterParams.value[propName] = value;
138
+ dropMenuRef.value?.colseAll();
139
+ emitChange();
140
+ };
141
+
142
+ // 确认筛选
143
+ const filterConfirm = (e: Record<string, string | number | boolean>) => {
144
+ filterParams.value = {
145
+ sort: filterParams.value.sort,
146
+ price: filterParams.value.price,
147
+ ...e
148
+ };
149
+ dropMenuRef.value?.colseAll();
150
+ emitChange();
151
+ };
152
+ const emitChange = () => {
153
+ emit('change', filterParams.value);
154
+ };
155
+
156
+ onMounted(async () => {
157
+ await uni.$snake.sleep();
158
+ filterParams.value.sort = props.sort;
159
+ });
160
+ </script>
161
+
162
+ <style lang="scss" scoped>
163
+ ::v-deep .u-drawer-content {
164
+ height: 100% !important;
165
+ }
166
+
167
+ .filter-sorts__toolbar {
168
+ display: flex;
169
+ flex: 1;
170
+ align-items: center;
171
+ }
172
+
173
+ .filter-sorts__item {
174
+ display: flex;
175
+ flex: 1;
176
+ align-items: center;
177
+ justify-content: center;
178
+ font-size: 26rpx;
179
+ }
180
+
181
+ .filter-sorts__item--active {
182
+ color: var(--snake-color-primary, #06d290);
183
+ font-weight: 500;
184
+ }
185
+ </style>
@@ -0,0 +1,57 @@
1
+ import type { StyleValue } from '../../types/global';
2
+
3
+ /** Filter 可选项。 */
4
+ export interface FilterOption extends Record<string, unknown> {
5
+ /** 数据项名称或唯一标识。 */
6
+ name?: string;
7
+ /** 当前值。 */
8
+ value?: string | number;
9
+ /** selected 配置。 */
10
+ selected?: boolean;
11
+ }
12
+
13
+ /** FilterGroup 类型定义。 */
14
+ export interface FilterGroup extends Record<string, unknown> {
15
+ /** 数据项名称或唯一标识。 */
16
+ name?: string;
17
+ /** 当前值。 */
18
+ value?: string;
19
+ /** 组件类型或展示模式。 */
20
+ type?: string;
21
+ /** 用于渲染的数据列表。 */
22
+ list?: FilterOption[];
23
+ }
24
+
25
+ /** FilterSorts 配置数据。 */
26
+ export interface FilterSortsData extends Record<string, unknown> {
27
+ /** top filter 配置。 */
28
+ topFilter?: FilterGroup[];
29
+ /** main filter 配置。 */
30
+ mainFilter?: FilterGroup[];
31
+ /** brand filter 配置。 */
32
+ brandFilter?: Record<string, FilterOption[]>;
33
+ }
34
+
35
+ /** FilterSorts 组件的公开属性。 */
36
+ export interface FilterSortsProps<Filters extends FilterSortsData = FilterSortsData> {
37
+ /** 筛选项配置数据。 */
38
+ filters?: Filters;
39
+ /** default filter 配置。 */
40
+ defaultFilter?: Record<string, any>;
41
+ /** 背景颜色。 */
42
+ bgColor?: string;
43
+ /** sort 配置。 */
44
+ sort?: string;
45
+ /** show tabs 配置。 */
46
+ showTabs?: boolean;
47
+ /** goods list length 配置。 */
48
+ goodsListLength?: number;
49
+ /** 根节点自定义样式。 */
50
+ customStyle?: StyleValue;
51
+ }
52
+
53
+ /** FilterSorts 组件的事件契约。 */
54
+ export type FilterSortsEmits = {
55
+ /** change 事件携带的参数。 */
56
+ change: [params: Record<string, string | number | boolean>];
57
+ };
@@ -0,0 +1,157 @@
1
+ <template>
2
+ <view
3
+ class="floating-bubble"
4
+ :class="{ 'floating-bubble--slot': hasSlot }"
5
+ :animation="animationData"
6
+ :style="rootStyle"
7
+ v-if="visible"
8
+ >
9
+ <view class="floating-bubble__wrap" :style="wrapStyle" @click="handleNavigate">
10
+ <view v-if="!hasSlot" class="floating-bubble__close" @click.stop.prevent="close">
11
+ <snake-icon name="close-circle-fill" size="28" color="rgba(7, 7, 7, 0.2)" />
12
+ </view>
13
+ <image v-if="!hasSlot" class="floating-bubble__logo" :src="datas.src" />
14
+ <slot v-else />
15
+ </view>
16
+ </view>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * @component FloatingBubble
22
+ * @description 页面右侧的悬浮气泡。图片模式支持点击跳转与关闭(持久化),插槽模式承载自定义内容;滚动时向屏幕外收起,停止后恢复。
23
+ */
24
+
25
+ import { computed, getCurrentInstance, onBeforeUnmount, onMounted, ref, useSlots, watch } from 'vue';
26
+ import type { FloatingBubbleProps } from './types';
27
+
28
+ defineOptions({ name: 'FloatingBubble', options: { virtualHost: true, styleIsolation: 'shared' } });
29
+
30
+ const props = withDefaults(defineProps<FloatingBubbleProps>(), {
31
+ datas: () => ({}),
32
+ scrollTop: 0,
33
+ bottom: '80rpx',
34
+ background: 'transparent',
35
+ zIndex: 6
36
+ });
37
+ const emit = defineEmits<{ click: [] }>();
38
+
39
+ const slots = useSlots();
40
+ const hasSlot = computed(() => !!slots.default);
41
+
42
+ const instance = getCurrentInstance();
43
+ const closeList = ref<Array<string | number>>([]);
44
+ const animationData = ref<Record<string, unknown>>({});
45
+ const animation = ref<ReturnType<typeof uni.createAnimation>>();
46
+ const size = ref({ width: 0, right: 0 });
47
+ const off = ref(false);
48
+ let timer: ReturnType<typeof setTimeout> | undefined;
49
+
50
+ const isShow = computed(() => !closeList.value.includes(props.datas?.uid ?? ''));
51
+ const visible = computed(() => (hasSlot.value || Boolean(props.datas?.src)) && isShow.value);
52
+
53
+ const rootStyle = computed(() => {
54
+ const style: Record<string, any> = { zIndex: props.zIndex };
55
+ if (hasSlot.value) {
56
+ style.bottom = props.bottom;
57
+ style.background = props.background;
58
+ } else {
59
+ style.right = `${props.datas?.right ?? 0}%`;
60
+ style.bottom = `${props.datas?.bottom ?? 20}%`;
61
+ }
62
+ return style;
63
+ });
64
+
65
+ const wrapStyle = computed(() =>
66
+ hasSlot.value ? {} : { width: `${props.datas?.width ?? 96}rpx`, height: `${props.datas?.height ?? 96}rpx` }
67
+ );
68
+
69
+ watch(() => props.scrollTop, declick);
70
+
71
+ onMounted(async () => {
72
+ closeList.value = uni.getStorageSync('suspensionList') || [];
73
+ animation.value = uni.createAnimation({ duration: 1000, timingFunction: 'ease' });
74
+ const rect = await uni.$snake.getRect('.floating-bubble', instance);
75
+ if (rect) size.value = { width: rect.width, right: rect.right ?? 0 };
76
+ });
77
+
78
+ onBeforeUnmount(() => clearTimeout(timer));
79
+
80
+ function declick() {
81
+ if (!off.value) {
82
+ rotateAndScale();
83
+ off.value = true;
84
+ }
85
+ }
86
+
87
+ function rotateAndScale() {
88
+ if (!animation.value) return;
89
+ const screenWidth = uni.$snake.sys().screenWidth;
90
+ const x = size.value.width / 2 + screenWidth - size.value.right;
91
+ animation.value.translateX(x).step();
92
+ animationData.value = animation.value.export();
93
+ timer = setTimeout(() => {
94
+ off.value = false;
95
+ norotateAndScale();
96
+ }, 2000);
97
+ }
98
+
99
+ function norotateAndScale() {
100
+ if (!animation.value) return;
101
+ animation.value.translateX(0).step();
102
+ animationData.value = animation.value.export();
103
+ }
104
+
105
+ function close() {
106
+ const uid = props.datas?.uid;
107
+ if (uid !== undefined && !closeList.value.includes(uid)) closeList.value.push(uid);
108
+ uni.setStorageSync('suspensionList', closeList.value);
109
+ }
110
+
111
+ function reopen() {
112
+ const uid = props.datas?.uid;
113
+ if (uid === undefined) return;
114
+ closeList.value = closeList.value.filter(item => item !== uid);
115
+ uni.setStorageSync('suspensionList', closeList.value);
116
+ }
117
+
118
+ function handleNavigate() {
119
+ emit('click');
120
+ if (!hasSlot.value && props.datas?.url) uni.$snake.navTo(props.datas.url);
121
+ }
122
+
123
+ defineExpose({ reopen });
124
+ </script>
125
+
126
+ <style lang="scss" scoped>
127
+ .floating-bubble {
128
+ position: fixed;
129
+ right: 0;
130
+ bottom: 20%;
131
+ z-index: 6;
132
+ padding-bottom: 0rpx;
133
+ padding-bottom: constant(safe-area-inset-bottom);
134
+ padding-bottom: env(safe-area-inset-bottom);
135
+
136
+ &--slot {
137
+ right: 28rpx;
138
+ }
139
+
140
+ &__wrap {
141
+ position: relative;
142
+ }
143
+
144
+ &__close {
145
+ position: absolute;
146
+ top: 0;
147
+ right: 0;
148
+ z-index: 12;
149
+ color: rgba(7, 7, 7, 0.2);
150
+ }
151
+
152
+ &__logo {
153
+ width: 100%;
154
+ height: 100%;
155
+ }
156
+ }
157
+ </style>
@@ -0,0 +1,43 @@
1
+ /** FloatingBubble 配置数据。 */
2
+ export interface FloatingBubbleData {
3
+ /** uid 配置。 */
4
+ uid?: string | number;
5
+ /** 资源地址。 */
6
+ src?: string;
7
+ /** 点击后跳转地址。 */
8
+ url?: string;
9
+ /** right 配置。 */
10
+ right?: number;
11
+ /** bottom 配置。 */
12
+ bottom?: number;
13
+ /** 组件宽度。 */
14
+ width?: number;
15
+ /** 组件高度。 */
16
+ height?: number;
17
+ }
18
+
19
+ /** FloatingBubble 组件的公开属性。 */
20
+ export interface FloatingBubbleProps {
21
+ /** CMS 装修组件配置数据。 */
22
+ datas?: FloatingBubbleData;
23
+ /** 页面或容器当前滚动距离。 */
24
+ scrollTop?: number;
25
+ /** 插槽模式下底部定位偏移。 */
26
+ bottom?: string;
27
+ /** 插槽模式下容器背景。 */
28
+ background?: string;
29
+ /** 层级。 */
30
+ zIndex?: number | string;
31
+ }
32
+
33
+ /** FloatingBubble 实例公开的操作。 */
34
+ export interface FloatingBubbleExpose {
35
+ /** 重新打开已关闭的悬浮入口。 */
36
+ reopen(): void;
37
+ }
38
+
39
+ /** FloatingBubble 点击入口时发出的事件。 */
40
+ export interface FloatingBubbleEmits {
41
+ /** 用户点击悬浮入口。 */
42
+ click: [];
43
+ }
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <view>
3
+ <!-- #ifdef MP-WEIXIN -->
4
+ <official-account></official-account>
5
+ <!-- #endif -->
6
+ </view>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ /**
11
+ * @component Follow
12
+ * @description 在微信小程序环境中展示官方账号关注组件。
13
+ */
14
+
15
+ defineOptions({ name: 'Follow', options: { virtualHost: true, styleIsolation: 'shared' } });
16
+ </script>
17
+
18
+ <style></style>
@@ -0,0 +1,2 @@
1
+ /** Follow 组件的公开属性。 */
2
+ export type FollowProps = Record<string, never>;
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <view class="snake-form" :class="customClass" :style="customStyle"><slot /></view>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ /**
7
+ * @component Form
8
+ * @description 管理表单数据、校验规则与字段实例。
9
+ * @see /components/form
10
+ */
11
+
12
+ import { computed, provide, ref, toRef } from 'vue';
13
+ import { toast } from '../../libs/function/toast';
14
+ import { formKey } from './types';
15
+ import type { FormCallback, FormError, FormProps, FormRules } from './types';
16
+
17
+ defineOptions({ name: 'SnakeForm', options: { virtualHost: true, styleIsolation: 'shared' } });
18
+ const props = withDefaults(defineProps<FormProps>(), {
19
+ model: () => ({}),
20
+ rules: () => ({}),
21
+ errorType: () => ['message', 'toast'],
22
+ borderBottom: true,
23
+ labelPosition: 'left',
24
+ labelWidth: 90,
25
+ labelAlign: 'left',
26
+ labelStyle: () => ({}),
27
+ size: 'default',
28
+ customClass: '',
29
+ customStyle: ''
30
+ });
31
+
32
+ interface FormField {
33
+ prop: string;
34
+ validation(trigger?: string, callback?: (message: string) => void): void;
35
+ resetField(): void;
36
+ }
37
+
38
+ const fields = ref<FormField[]>([]);
39
+ const rules = ref<FormRules>(props.rules);
40
+ const model = computed(() => props.model);
41
+
42
+ function setRules(newRules: FormRules) {
43
+ rules.value = newRules;
44
+ }
45
+
46
+ function addField(field: FormField) {
47
+ if (!fields.value.includes(field)) fields.value.push(field);
48
+ }
49
+
50
+ function removeField(field: Pick<FormField, 'prop'>) {
51
+ fields.value = fields.value.filter(item => item.prop !== field.prop);
52
+ }
53
+
54
+ function resetFields() {
55
+ fields.value.forEach(field => field.resetField?.());
56
+ }
57
+
58
+ function runValidation(targetFields: FormField[], callback?: FormCallback, showToast = false): Promise<boolean> {
59
+ return new Promise(resolve => {
60
+ let valid = true;
61
+ let count = 0;
62
+ const errors: FormError[] = [];
63
+ if (targetFields.length === 0) {
64
+ resolve(true);
65
+ callback?.(true, []);
66
+ return;
67
+ }
68
+ targetFields.forEach(field => {
69
+ field.validation('', message => {
70
+ if (message) {
71
+ valid = false;
72
+ errors.push({ prop: field.prop, message });
73
+ }
74
+ if (++count !== targetFields.length) return;
75
+ resolve(valid);
76
+ if (
77
+ showToast &&
78
+ !props.errorType.includes('none') &&
79
+ props.errorType.includes('toast') &&
80
+ errors.length > 0
81
+ ) {
82
+ toast(errors[0].message);
83
+ }
84
+ callback?.(valid, errors);
85
+ });
86
+ });
87
+ });
88
+ }
89
+
90
+ function validate(callback?: FormCallback): Promise<boolean> {
91
+ return runValidation(fields.value, callback, true);
92
+ }
93
+
94
+ function validateField(prop: string | string[], callback?: FormCallback): Promise<boolean> {
95
+ const names = Array.isArray(prop) ? prop : [prop];
96
+ return runValidation(
97
+ fields.value.filter(field => names.includes(field.prop)),
98
+ callback
99
+ );
100
+ }
101
+
102
+ // Snake 输入控件通过注入读取表单尺寸和错误配置;字段生命周期由公开方法驱动。
103
+ provide(formKey, {
104
+ model: toRef(props, 'model'),
105
+ rules,
106
+ size: toRef(props, 'size'),
107
+ errorType: toRef(props, 'errorType'),
108
+ borderBottom: toRef(props, 'borderBottom'),
109
+ labelPosition: toRef(props, 'labelPosition'),
110
+ labelWidth: toRef(props, 'labelWidth'),
111
+ labelAlign: toRef(props, 'labelAlign'),
112
+ labelStyle: toRef(props, 'labelStyle'),
113
+ register: field => addField(field as unknown as FormField),
114
+ unregister: field => removeField(field)
115
+ });
116
+
117
+ defineExpose({ setRules, resetFields, validate, validateField, addField, removeField, fields, rules, props, model });
118
+ </script>
119
+
120
+ <style scoped>
121
+ .snake-form {
122
+ width: 100%;
123
+ }
124
+ </style>
@@ -0,0 +1,96 @@
1
+ import type { InjectionKey, Ref } from 'vue';
2
+ import type { StyleValue } from '../../types/global';
3
+ import type { FormError, FormRules, FormTrigger } from '../../libs/form';
4
+
5
+ /** 表单控件尺寸。 */
6
+ export type FormSize = 'small' | 'default' | 'large';
7
+ /** 标签位置。 */
8
+ export type FormLabelPosition = 'left' | 'top';
9
+ /** 标签对齐方式。 */
10
+ export type FormLabelAlign = 'left' | 'center' | 'right';
11
+ /** 错误提示方式。 */
12
+ export type FormErrorType = 'message' | 'border' | 'border-bottom' | 'none' | 'toast';
13
+
14
+ /** Form 组件的公开属性。 */
15
+ export interface FormProps {
16
+ /** 表单数据对象。 */
17
+ model?: Record<string, any>;
18
+ /** 表单校验规则。 */
19
+ rules?: FormRules;
20
+ /** 错误提示方式。 */
21
+ errorType?: FormErrorType[];
22
+ /** 是否显示表单项底边。 */
23
+ borderBottom?: boolean;
24
+ /** 标签位置。 */
25
+ labelPosition?: FormLabelPosition;
26
+ /** 标签宽度。 */
27
+ labelWidth?: string | number;
28
+ /** 标签对齐方式。 */
29
+ labelAlign?: FormLabelAlign;
30
+ /** 标签自定义样式。 */
31
+ labelStyle?: Record<string, any>;
32
+ /** 表单控件尺寸。 */
33
+ size?: FormSize;
34
+ /** 根节点自定义类名。 */
35
+ customClass?: string;
36
+ /** 根节点自定义样式。 */
37
+ customStyle?: StyleValue;
38
+ }
39
+
40
+ /** 表单校验回调。 */
41
+ export type FormCallback = (valid: boolean, errors: FormError[]) => void;
42
+
43
+ /** Form 通过模板 ref 暴露的接口。 */
44
+ export interface FormExpose {
45
+ /** 动态设置校验规则。 */
46
+ setRules(rules: FormRules): void;
47
+ /** 重置所有已注册字段。 */
48
+ resetFields(): void;
49
+ /** 校验全部字段。 */
50
+ validate(callback?: FormCallback): Promise<boolean>;
51
+ /** 校验一个或多个指定字段。 */
52
+ validateField(props: string | string[], callback?: FormCallback): Promise<boolean>;
53
+ }
54
+
55
+ /** Form 注册的单个字段协议。 */
56
+ export interface FormField {
57
+ /** 字段在表单模型中的路径。 */
58
+ prop: string;
59
+ /** 按触发方式校验字段,返回错误列表。 */
60
+ validate(trigger?: FormTrigger): Promise<FormError[]>;
61
+ /** 将字段恢复为初始值。 */
62
+ reset(): void;
63
+ /** 清空字段的校验状态。 */
64
+ clear(): void;
65
+ }
66
+
67
+ /** Form 向 form-item 注入的配对上下文(formKey 通道)。 */
68
+ export interface FormContext {
69
+ /** 表单数据对象。 */
70
+ model: Ref<Record<string, unknown>>;
71
+ /** 校验规则。 */
72
+ rules: Ref<FormRules>;
73
+ /** 控件尺寸。 */
74
+ size: Ref<FormSize>;
75
+ /** 错误提示方式。 */
76
+ errorType: Ref<FormErrorType[]>;
77
+ /** 是否显示表单项底边。 */
78
+ borderBottom: Ref<boolean>;
79
+ /** 标签位置。 */
80
+ labelPosition: Ref<FormLabelPosition>;
81
+ /** 标签宽度。 */
82
+ labelWidth: Ref<string | number>;
83
+ /** 标签对齐方式。 */
84
+ labelAlign: Ref<FormLabelAlign>;
85
+ /** 标签自定义样式。 */
86
+ labelStyle: Ref<StyleValue>;
87
+ /** 注册一个字段。 */
88
+ register(field: FormField): void;
89
+ /** 注销一个字段。 */
90
+ unregister(field: FormField): void;
91
+ }
92
+
93
+ /** Form ↔ FormItem 配对注入键。 */
94
+ export const formKey: InjectionKey<FormContext> = Symbol('snake-form');
95
+
96
+ export type { FormError, FormRule, FormRules, FormTrigger } from '../../libs/form';