@puresnake/snake-ui 0.0.0-stage → 0.0.1

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 +1 -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 +130 -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,385 @@
1
+ import { computed, ref } from 'vue';
2
+ import type { CmsNormalizedComponent, CmsOptions, CmsPageInfo, CmsStateChange, CmsStatus } from './types';
3
+ import { CmsError } from './types';
4
+ import { getRuntimeOptions } from '../runtime';
5
+
6
+ type Dictionary = Record<string, any>;
7
+
8
+ export interface CmsCommerceControllerOptions {
9
+ pageInfo: () => CmsPageInfo;
10
+ component: () => CmsNormalizedComponent | undefined;
11
+ options: () => CmsOptions;
12
+ onStateChange?: (state: CmsStateChange) => void;
13
+ onError?: (error: CmsError) => void;
14
+ }
15
+
16
+ function valuesToQuery(input: unknown): Dictionary {
17
+ if (!input || typeof input !== 'object') return {};
18
+ return Object.fromEntries(Object.entries(input as Dictionary).map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : value]));
19
+ }
20
+
21
+ export function useCmsCommerce(input: CmsCommerceControllerOptions) {
22
+ const goodsList = ref<Dictionary[]>([]);
23
+ const filtersData = ref<Dictionary>({});
24
+ const activityList = ref<Dictionary[]>([]);
25
+ const activityCurrent = ref<string | number>('001');
26
+ const tabsCurrent = ref(0);
27
+ const auctionSetParams = ref<Dictionary>({});
28
+ const status = ref<CmsStatus>('idle');
29
+ const page = ref(1);
30
+ const goodsParams = ref<Dictionary>({});
31
+ const filterParams = ref<Dictionary>({});
32
+ const tabsParams = ref<Dictionary>({});
33
+ // 每次初始化、刷新或失效都会推进代际;异步请求返回时只允许当前代际写入状态,避免旧响应覆盖新筛选结果。
34
+ let generation = 0;
35
+ let busy = false;
36
+
37
+ function styleOf(component: CmsNormalizedComponent): Dictionary {
38
+ if (component.version !== 2) return component.setStyle;
39
+ const data = component.props || {};
40
+ const query = data.query || {};
41
+ const display = data.display || {};
42
+ const bars = data.bars || {};
43
+ const style = data.style || {};
44
+ return {
45
+ goodsListType: query.source === 'activity' ? 'activity' : 'activityCondition',
46
+ listType: display.cardType || 'sale',
47
+ subType: display.subType || '',
48
+ attendanceShow: display.showAttendance !== false,
49
+ commodityType: Number(display.columns) === 3 ? 1 : 0,
50
+ activitySessionShow: Boolean(bars.activitySession),
51
+ filterShow: bars.filter,
52
+ priceFilterShow: Boolean(bars.priceFilter),
53
+ tabShow: Boolean(bars.tabs?.enabled),
54
+ tabslist: Array.isArray(bars.tabs?.list) ? bars.tabs.list : [],
55
+ stickyDisabled: !bars.sticky,
56
+ pageMargin: style.pageMargin,
57
+ imageMargin: style.gap,
58
+ backgroundColor: style.backgroundColor,
59
+ listMode: data.empty?.mode,
60
+ emptySrc: data.empty?.image,
61
+ emptyText: data.empty?.text,
62
+ emptyColor: data.empty?.color,
63
+ filterParams: {
64
+ ...query,
65
+ pageSize: query.pageSize || 20,
66
+ activityUserRule: query.userRule,
67
+ sellingDay: query.sellingDays,
68
+ brandNames: Array.isArray(query.brands) ? query.brands.map((brand: Dictionary) => brand.name) : [],
69
+ genderFilter: query.gender
70
+ }
71
+ };
72
+ }
73
+
74
+ const goodsLoadingStatus = computed(() => {
75
+ if (status.value === 'initial-loading' || status.value === 'loading-more') return 'loading';
76
+ if (status.value === 'no-more') return 'nomore';
77
+ return 'loadmore';
78
+ });
79
+
80
+ function snapshot(): CmsStateChange {
81
+ return { status: status.value, page: page.value, itemCount: goodsList.value.length };
82
+ }
83
+
84
+ function setStatus(next: CmsStatus): void {
85
+ status.value = next;
86
+ input.onStateChange?.(snapshot());
87
+ }
88
+
89
+ function context(component: CmsNormalizedComponent, currentGeneration: number) {
90
+ return { component, pageInfo: input.pageInfo(), generation: currentGeneration };
91
+ }
92
+
93
+ function cacheKey(component: CmsNormalizedComponent): string {
94
+ const pageInfo = input.pageInfo();
95
+ const pageId = pageInfo.pageId ?? pageInfo.id ?? 'default';
96
+ return `${input.options().cachePrefix || 'snake-ui:cms'}:${pageId}:${component.id}:filters`;
97
+ }
98
+
99
+ function restoreFilters(component: CmsNormalizedComponent): void {
100
+ const cache = getRuntimeOptions().cache;
101
+ if (!cache) return;
102
+ // 优先读取按页面和组件隔离的新键;旧的全局 filterParams 键仅用于兼容存量缓存,写入始终使用新键。
103
+ const cached = cache.get(cacheKey(component)) ?? cache.get('filterParams');
104
+ filterParams.value = valuesToQuery(cached);
105
+ }
106
+
107
+ function persistFilters(component: CmsNormalizedComponent, value: Dictionary): void {
108
+ getRuntimeOptions().cache?.set(cacheKey(component), value);
109
+ }
110
+
111
+ function buildFilterRequest(component: CmsNormalizedComponent, mode = ''): Dictionary {
112
+ const style = styleOf(component);
113
+ const params: Dictionary = {};
114
+ if (style.goodsListType === 'activity' || goodsParams.value.activityId) {
115
+ params.activityId = style.filterParams?.activityId || goodsParams.value.activityId || '';
116
+ }
117
+ if (style.listType === 'auction' && !['forSale', 'downBuy'].includes(mode)) {
118
+ params.activityType = 'auction';
119
+ params.subType = style.subType || '';
120
+ }
121
+ if (goodsParams.value.categoryId) params.categoryId = goodsParams.value.categoryId;
122
+ return params;
123
+ }
124
+
125
+ function buildGoodsRequest(component: CmsNormalizedComponent, mode = ''): Dictionary {
126
+ const style = styleOf(component);
127
+ const configured = style.filterParams || {};
128
+ const params: Dictionary = {
129
+ page: page.value,
130
+ size: configured.pageSize || 10,
131
+ key: style.key || '',
132
+ low: configured.low || '',
133
+ high: configured.high || '',
134
+ sellingDay: configured.sellingDay || '',
135
+ cateId: configured.categoryId || goodsParams.value.categoryId || '',
136
+ sort: configured.sort,
137
+ showOutOfStock: goodsParams.value.activityStatus === 'init' ? 1 : configured.showOutOfStock || 0
138
+ };
139
+ if (style.goodsListType === 'activity') params.activityId = configured.activityId || '';
140
+ if (goodsParams.value.activityId) params.activityId = goodsParams.value.activityId;
141
+ if (style.goodsListType === 'activityCondition' && configured.activityType) {
142
+ params.activityType = configured.activityType;
143
+ params.activityStatus = configured.activityStatus;
144
+ params.activityUserRule = configured.activityUserRule || '';
145
+ params.activitySubType = style.subType || '';
146
+ }
147
+ if (goodsParams.value.activityStatus) params.activityStatus = goodsParams.value.activityStatus;
148
+ Object.assign(params, tabsParams.value, filterParams.value);
149
+ for (const [target, source] of [
150
+ ['brandFilter', configured.brandNames],
151
+ ['genderFilter', configured.genderFilter]
152
+ ] as const) {
153
+ if (!Array.isArray(source) || !source.length) continue;
154
+ params[target] = params[target] ? `${params[target]},${source.join(',')}` : source.join(',');
155
+ }
156
+ if (['forSale', 'downBuy'].includes(mode)) {
157
+ delete params.activityType;
158
+ delete params.activityStatus;
159
+ delete params.activitySubType;
160
+ }
161
+ if (mode === 'downBuy') params.activityType = 'down_buy';
162
+ return params;
163
+ }
164
+
165
+ async function loadActivities(component: CmsNormalizedComponent, currentGeneration: number): Promise<void> {
166
+ const service = input.options().services?.commerce;
167
+ const style = styleOf(component);
168
+ if (!service?.getActivities || !style.activitySessionShow || style.listType !== 'auction') return;
169
+ let result;
170
+ try {
171
+ result = await service.getActivities({
172
+ ...context(component, currentGeneration),
173
+ params: { type: 'auction', status: 'initAndIng', subType: style.subType || '' }
174
+ });
175
+ } catch (cause) {
176
+ throw new CmsError('activities', 'CMS activities failed to load.', cause);
177
+ }
178
+ if (currentGeneration !== generation) return;
179
+ const items = result.items || [];
180
+ // “全部”是基于首个活动构造的前端虚拟项,id=001 不对应真实活动,请求时会转换为空 activityId。
181
+ const all = items.length ? [{ ...items[0], id: '001', squareImage: input.options().assets?.activityAll }, ...items] : [];
182
+ activityList.value = all;
183
+ auctionSetParams.value = all[0] || {};
184
+ const requested = input.pageInfo().activityId;
185
+ const selected = requested === undefined ? undefined : all.find(item => item.id === requested);
186
+ if (selected) {
187
+ activityCurrent.value = selected.id;
188
+ auctionSetParams.value = selected;
189
+ goodsParams.value.activityId = selected.id === '001' ? '' : selected.id;
190
+ }
191
+ }
192
+
193
+ async function loadFilters(component: CmsNormalizedComponent, currentGeneration: number, mode = '') {
194
+ const service = input.options().services?.commerce;
195
+ const style = styleOf(component);
196
+ if (!service?.getFilters || !(style.filterShow || style.filterShow === 'custom')) return;
197
+ let result;
198
+ try {
199
+ result = await service.getFilters({
200
+ ...context(component, currentGeneration),
201
+ params: buildFilterRequest(component, mode)
202
+ });
203
+ } catch (cause) {
204
+ throw new CmsError('filters', 'CMS filters failed to load.', cause);
205
+ }
206
+ if (currentGeneration === generation) filtersData.value = result || {};
207
+ }
208
+
209
+ async function loadGoods(component: CmsNormalizedComponent, currentGeneration: number, append: boolean) {
210
+ const service = input.options().services?.commerce;
211
+ if (!service) throw new CmsError('goods', 'A CMS commerce service is required for goodslist.');
212
+ const request = buildGoodsRequest(component);
213
+ let result;
214
+ try {
215
+ result = await service.getGoods({ ...context(component, currentGeneration), params: request });
216
+ } catch (cause) {
217
+ throw new CmsError('goods', 'CMS goods failed to load.', cause);
218
+ }
219
+ if (currentGeneration !== generation) return;
220
+ const style = styleOf(component);
221
+ // 首次无商品时,forSale/downBuy 可改写活动参数再次请求,作为运营配置的降级展示策略。
222
+ if (!append && (result.total || 0) <= 0 && ['forSale', 'downBuy'].includes(style.listMode)) {
223
+ const mode = style.listMode;
224
+ await loadFilters(component, currentGeneration, mode);
225
+ try {
226
+ result = await service.getGoods({
227
+ ...context(component, currentGeneration),
228
+ params: buildGoodsRequest(component, mode)
229
+ });
230
+ } catch (cause) {
231
+ throw new CmsError('goods', 'CMS fallback goods failed to load.', cause);
232
+ }
233
+ }
234
+ if (currentGeneration !== generation) return;
235
+ const items = result.items || [];
236
+ goodsList.value = append ? goodsList.value.concat(items) : items;
237
+ const pageSize = Number(style.filterParams?.pageSize || 10);
238
+ const hasMore = result.hasMore ?? items.length >= pageSize;
239
+ if (hasMore) {
240
+ page.value += 1;
241
+ setStatus('ready');
242
+ } else {
243
+ setStatus(goodsList.value.length ? 'no-more' : 'empty');
244
+ }
245
+ }
246
+
247
+ function fail(stage: CmsError['stage'], cause: unknown): void {
248
+ const error = cause instanceof CmsError ? cause : new CmsError(stage, cause instanceof Error ? cause.message : String(cause), cause);
249
+ setStatus('error');
250
+ input.onError?.(error);
251
+ }
252
+
253
+ async function init(): Promise<void> {
254
+ const component = input.component();
255
+ generation += 1;
256
+ const currentGeneration = generation;
257
+ page.value = 1;
258
+ goodsList.value = [];
259
+ filtersData.value = {};
260
+ activityList.value = [];
261
+ goodsParams.value = {};
262
+ tabsParams.value = {};
263
+ activityCurrent.value = '001';
264
+ tabsCurrent.value = 0;
265
+ if (!component) {
266
+ setStatus('ready');
267
+ return;
268
+ }
269
+ restoreFilters(component);
270
+ busy = true;
271
+ setStatus('initial-loading');
272
+ try {
273
+ await loadActivities(component, currentGeneration);
274
+ await loadFilters(component, currentGeneration);
275
+ await loadGoods(component, currentGeneration, false);
276
+ } catch (cause) {
277
+ if (currentGeneration === generation) fail('goods', cause);
278
+ } finally {
279
+ if (currentGeneration === generation) busy = false;
280
+ }
281
+ }
282
+
283
+ async function loadMore(): Promise<void> {
284
+ const component = input.component();
285
+ if (!component || busy || status.value !== 'ready') return;
286
+ const currentGeneration = generation;
287
+ busy = true;
288
+ setStatus('loading-more');
289
+ try {
290
+ await loadGoods(component, currentGeneration, true);
291
+ } catch (cause) {
292
+ if (currentGeneration === generation) fail('goods', cause);
293
+ } finally {
294
+ if (currentGeneration === generation) busy = false;
295
+ }
296
+ }
297
+
298
+ async function refresh(): Promise<void> {
299
+ const component = input.component();
300
+ if (!component || busy) return;
301
+ generation += 1;
302
+ const currentGeneration = generation;
303
+ page.value = 1;
304
+ goodsList.value = [];
305
+ busy = true;
306
+ setStatus('initial-loading');
307
+ try {
308
+ await loadFilters(component, currentGeneration);
309
+ await loadGoods(component, currentGeneration, false);
310
+ } catch (cause) {
311
+ if (currentGeneration === generation) fail('goods', cause);
312
+ } finally {
313
+ if (currentGeneration === generation) busy = false;
314
+ }
315
+ }
316
+
317
+ async function retry(): Promise<void> {
318
+ await init();
319
+ }
320
+
321
+ async function changeActivity(item: Dictionary): Promise<void> {
322
+ if (activityCurrent.value === item.id) return;
323
+ activityCurrent.value = item.id;
324
+ goodsParams.value.activityId = item.id === '001' ? '' : item.id;
325
+ goodsParams.value.activityStatus = item.status || '';
326
+ auctionSetParams.value = item;
327
+ await refresh();
328
+ }
329
+
330
+ async function changeTab(index: number, list: Dictionary[]): Promise<void> {
331
+ tabsCurrent.value = index;
332
+ const item = list[index] || {};
333
+ goodsParams.value.categoryId = item.paramType === 'categoryId' || !item.paramType ? item.value || '' : '';
334
+ filterParams.value = {};
335
+ tabsParams.value = item.paramType
336
+ ? { [item.paramType]: item.value }
337
+ : Object.fromEntries((item.customList || []).map((entry: Dictionary) => [entry.customLabel, entry.customValue]));
338
+ await refresh();
339
+ }
340
+
341
+ async function confirmFilter(params: Dictionary): Promise<void> {
342
+ const component = input.component();
343
+ filterParams.value = { ...params };
344
+ if (component) persistFilters(component, params);
345
+ await refresh();
346
+ }
347
+
348
+ async function navigateGoods(item: Dictionary, index: number): Promise<void> {
349
+ const component = input.component();
350
+ const service = input.options().services?.commerce;
351
+ if (!component || !service?.navigateGoods) return;
352
+ await service.navigateGoods({ ...context(component, generation), item, index });
353
+ }
354
+
355
+ function refreshQuotePay(price: unknown, index: number): void {
356
+ const activityInfo = goodsList.value[index]?.activityInfo;
357
+ if (activityInfo && typeof activityInfo === 'object') activityInfo.myPrice = price;
358
+ }
359
+
360
+ function invalidate(): void {
361
+ generation += 1;
362
+ busy = false;
363
+ }
364
+
365
+ return {
366
+ goodsList,
367
+ filtersData,
368
+ activityList,
369
+ activityCurrent,
370
+ tabsCurrent,
371
+ auctionSetParams,
372
+ status,
373
+ goodsLoadingStatus,
374
+ init,
375
+ loadMore,
376
+ refresh,
377
+ retry,
378
+ changeActivity,
379
+ changeTab,
380
+ confirmFilter,
381
+ navigateGoods,
382
+ refreshQuotePay,
383
+ invalidate
384
+ };
385
+ }
package/libs/cms/v2.ts ADDED
@@ -0,0 +1,193 @@
1
+ import type { CmsV2BuiltinComponent } from './blocks';
2
+
3
+ type Dictionary = Record<string, any>;
4
+
5
+ const link = () => ({ httpType: 10, http: '' });
6
+ const mediaStyle = () => ({
7
+ heightMode: 'auto',
8
+ height: 200,
9
+ pageMargin: 0,
10
+ imageRadius: 0,
11
+ backgroundColor: '#FFFFFF'
12
+ });
13
+
14
+ export const cmsV2Defaults: Record<CmsV2BuiltinComponent, Dictionary> = {
15
+ 'v2.carousel': {
16
+ play: { autoplay: true, interval: 3, loop: true },
17
+ indicator: { type: 'bullets', color: '#1989fa' },
18
+ style: mediaStyle(),
19
+ items: []
20
+ },
21
+ 'v2.coupon': {
22
+ mode: 'page',
23
+ popupMode: 'bottom',
24
+ coupon: null,
25
+ before: { image: '', link: link() },
26
+ after: { image: '', link: link() }
27
+ },
28
+ 'v2.custom': { slotName: 'custom' },
29
+ 'v2.divider': {
30
+ mode: 'blank',
31
+ height: 30,
32
+ lineStyle: 'solid',
33
+ margin: 0,
34
+ style: { lineColor: 'rgb(229, 229, 229)', backgroundColor: 'rgb(255, 255, 255)' }
35
+ },
36
+ 'v2.floatbutton': {
37
+ src: '',
38
+ link: link(),
39
+ position: { right: 0, bottom: 20 },
40
+ style: { width: 76, height: 76 }
41
+ },
42
+ 'v2.goodslist': {
43
+ audience: 'all',
44
+ query: {
45
+ source: 'activity',
46
+ activityId: '',
47
+ activityType: '',
48
+ activityStatus: 'ing',
49
+ userRule: 'all',
50
+ gender: [],
51
+ low: '',
52
+ high: '',
53
+ categoryId: '',
54
+ brands: [],
55
+ sellingDays: '',
56
+ sort: 'auto',
57
+ pageSize: 20,
58
+ showOutOfStock: false
59
+ },
60
+ display: {
61
+ layout: 'grid',
62
+ columns: 2,
63
+ cardType: 'sale',
64
+ subType: '',
65
+ showAttendance: true,
66
+ showTag: false
67
+ },
68
+ bars: {
69
+ filter: false,
70
+ filterList: [],
71
+ priceFilter: true,
72
+ tabs: { enabled: false, list: [] },
73
+ activitySession: false,
74
+ sticky: false
75
+ },
76
+ style: { pageMargin: 5, gap: 10, radius: 0, backgroundColor: '#F8F8F8' },
77
+ empty: { mode: 'default', image: '', text: '列表为空', color: '#000000' }
78
+ },
79
+ 'v2.hotzone': { image: '', zones: [] },
80
+ 'v2.images': {
81
+ layout: { mode: 'single', columns: 2 },
82
+ style: { ...mediaStyle(), gap: 8 },
83
+ items: []
84
+ },
85
+ 'v2.magiccube': {
86
+ layout: 'row',
87
+ columns: 4,
88
+ cells: [],
89
+ play: { interval: 3 },
90
+ style: { pageMargin: 0, gap: 0, radius: 0, height: 188, backgroundColor: '#FFFFFF' }
91
+ },
92
+ 'v2.notice': {
93
+ direction: 'row',
94
+ noticeText: '请填写内容,如果过长,将会在手机上滚动显示',
95
+ link: link(),
96
+ textList: [],
97
+ isLeftIcon: true,
98
+ style: {
99
+ height: 72,
100
+ pageMargin: 10,
101
+ backgroundColor: 'rgb(234, 241, 255)',
102
+ textColor: 'rgb(45, 87, 231)'
103
+ }
104
+ },
105
+ 'v2.popup': { image: '', link: link(), popupMode: 'bottom' },
106
+ 'v2.richtext': { content: '', style: { backgroundColor: '#FFFFFF' } },
107
+ 'v2.search': {
108
+ searchText: '请输入你想要搜索的商品名称/货号/关键词',
109
+ link: link(),
110
+ isSearch: false,
111
+ inputType: 'text',
112
+ textList: [],
113
+ rightImage: { show: false, src: '', link: link(), width: 60, height: 60, borderRadius: 0, marginRight: 0 },
114
+ icons: [],
115
+ style: {
116
+ position: 0,
117
+ height: 60,
118
+ pageMargin: 0,
119
+ borderRadius: 0,
120
+ backgroundColor: 'rgb(249, 249, 249)',
121
+ borderBgColor: 'rgb(255, 255, 255)',
122
+ textColor: 'rgb(150, 151, 153)',
123
+ borderSize: 1,
124
+ borderColor: 'rgb(0, 0, 0)',
125
+ showBorder: true
126
+ }
127
+ },
128
+ 'v2.video': { src: '', coverUrl: '', autoplay: false, style: mediaStyle() }
129
+ };
130
+
131
+ const enums: Record<string, readonly unknown[]> = {
132
+ 'v2.carousel:indicator.type': ['none', 'bullets', 'fraction', 'progressbar'],
133
+ 'v2.coupon:mode': ['page', 'popup'],
134
+ 'v2.coupon:popupMode': ['bottom', 'center'],
135
+ 'v2.divider:mode': ['blank', 'line'],
136
+ 'v2.divider:lineStyle': ['solid', 'dashed', 'dotted'],
137
+ 'v2.goodslist:audience': ['all', 'new', 'old'],
138
+ 'v2.goodslist:query.source': ['activity', 'condition'],
139
+ 'v2.goodslist:display.layout': ['grid', 'swipe'],
140
+ 'v2.goodslist:display.columns': [2, 3],
141
+ 'v2.goodslist:display.cardType': ['sale', 'auction', 'new'],
142
+ 'v2.images:layout.mode': ['single', 'grid'],
143
+ 'v2.notice:direction': ['row', 'column'],
144
+ 'v2.popup:popupMode': ['bottom', 'center'],
145
+ 'v2.search:inputType': ['text', 'number'],
146
+ 'v2.search:style.position': [0, 1]
147
+ };
148
+
149
+ const limits: Record<string, [number, number]> = {
150
+ 'v2.carousel:play.interval': [1, 3600],
151
+ 'v2.goodslist:query.pageSize': [1, 50],
152
+ 'v2.images:layout.columns': [1, 4],
153
+ 'v2.magiccube:columns': [1, 6],
154
+ 'v2.search:style.height': [60, 80]
155
+ };
156
+
157
+ function clone<T>(value: T): T {
158
+ return JSON.parse(JSON.stringify(value));
159
+ }
160
+
161
+ function normalizeValue(value: unknown, fallback: any, component: string, path: string): any {
162
+ if (Array.isArray(fallback)) return Array.isArray(value) ? clone(value) : clone(fallback);
163
+ if (fallback !== null && typeof fallback === 'object') {
164
+ const source =
165
+ value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as Dictionary) : {};
166
+ return Object.fromEntries(
167
+ Object.entries(fallback).map(([key, nested]) => [
168
+ key,
169
+ normalizeValue(source[key], nested, component, path ? `${path}.${key}` : key)
170
+ ])
171
+ );
172
+ }
173
+ if (fallback === null) return value === undefined ? null : clone(value);
174
+ const enumValues = enums[`${component}:${path}`];
175
+ if (typeof fallback === 'boolean') {
176
+ if (value === true || value === 1 || value === '1' || value === 'true') return true;
177
+ if (value === false || value === 0 || value === '0' || value === 'false') return false;
178
+ return fallback;
179
+ }
180
+ if (typeof fallback === 'number') {
181
+ const numeric = Number(value);
182
+ if (!Number.isFinite(numeric)) return fallback;
183
+ if (enumValues && !enumValues.includes(numeric)) return fallback;
184
+ const limit = limits[`${component}:${path}`];
185
+ return limit ? Math.min(limit[1], Math.max(limit[0], numeric)) : numeric;
186
+ }
187
+ const text = typeof value === 'string' || typeof value === 'number' ? String(value) : fallback;
188
+ return enumValues && !enumValues.includes(text) ? fallback : text;
189
+ }
190
+
191
+ export function normalizeCmsV2Props(component: CmsV2BuiltinComponent, value: unknown): Dictionary {
192
+ return normalizeValue(value, cmsV2Defaults[component], component, '');
193
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './state';
3
+ export * from './theme';
4
+ export { default as zIndex, type ZIndexConfig } from './zIndex';
@@ -0,0 +1,93 @@
1
+ /**
2
+ * snake-modal 函数式调用约定(事件名 + 载荷类型)。
3
+ * @description
4
+ * - useModal() 通过 emitter 派发事件
5
+ * - <snake-modal global /> / <snake-modal page /> 通过 emitter 监听并转调自身 show/hide
6
+ *
7
+ * 该模块放在 libs 层(而非 components),是为了让 `useModal` 组合式函数与
8
+ * `snake-modal` 组件共享同一套事件名与载荷,同时保持「libs 不反向依赖 components」的分层约束。
9
+ */
10
+
11
+ // 普通(页面级)modal 事件
12
+ export const SNAKE_MODAL_EVENT_SHOW = 'snake-ui:modal:show:';
13
+ export const SNAKE_MODAL_EVENT_HIDE = 'snake-ui:modal:hide:';
14
+ export const SNAKE_MODAL_EVENT_CLEAR_LOADING = 'snake-ui:modal:clear-loading:';
15
+
16
+ // 全局(App 根部)modal 事件,供 useModal() 使用
17
+ export const SNAKE_MODAL_GLOBAL_EVENT_SHOW = 'snake-ui:modal:global:show';
18
+ export const SNAKE_MODAL_GLOBAL_EVENT_HIDE = 'snake-ui:modal:global:hide';
19
+ export const SNAKE_MODAL_GLOBAL_EVENT_CLEAR_LOADING = 'snake-ui:modal:global:clear-loading';
20
+
21
+ /** 根据当前页面(或显式 pageId)拼接事件名。 */
22
+ export function getEventWithCurrentPage(event: string, page?: string | boolean): string {
23
+ if (page && typeof page === 'string' && page !== '') {
24
+ return event + page;
25
+ }
26
+ return event + getCurrentPage();
27
+ }
28
+
29
+ /** 获取当前页面路由。 */
30
+ function getCurrentPage(): string {
31
+ try {
32
+ const pages = getCurrentPages();
33
+ const currentPage = pages[pages.length - 1].route as string;
34
+ return currentPage || '';
35
+ } catch {
36
+ return '';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * snake-modal 函数式调用载荷类型。
42
+ * @description 与 `snake-modal` 组件的 props 一一对应(除 modelValue 外),额外支持确认/取消回调。
43
+ */
44
+ export interface ModalPayload {
45
+ /** 标题。 */
46
+ title?: string;
47
+ /** 内容。 */
48
+ content?: string;
49
+ /** 是否显示标题。 */
50
+ showTitle?: boolean;
51
+ /** 是否显示确认按钮。 */
52
+ showConfirmButton?: boolean;
53
+ /** 是否显示取消按钮。 */
54
+ showCancelButton?: boolean;
55
+ /** 确认按钮文案。 */
56
+ confirmText?: string;
57
+ /** 取消按钮文案。 */
58
+ cancelText?: string;
59
+ /** 确认按钮颜色。 */
60
+ confirmColor?: string;
61
+ /** 取消文字颜色。 */
62
+ cancelColor?: string;
63
+ /** 弹窗圆角值,单位 rpx。 */
64
+ borderRadius?: number | string;
65
+ /** 弹窗宽度,单位 rpx。 */
66
+ width?: number | string;
67
+ /** 层级 z-index。 */
68
+ zIndex?: number | string;
69
+ /** 是否开启缩放效果。 */
70
+ zoom?: boolean;
71
+ /** 是否异步关闭,只对确定按钮有效。 */
72
+ asyncClose?: boolean;
73
+ /** 是否允许点击遮罩关闭。 */
74
+ maskCloseAble?: boolean;
75
+ /** 往上偏移的值,避免和键盘重合。 */
76
+ negativeTop?: number | string;
77
+ /** 根节点自定义样式。 */
78
+ customStyle?: string | Record<string, any> | unknown[];
79
+ /** 根节点自定义类名。 */
80
+ customClass?: string;
81
+ /** 标题样式。 */
82
+ titleStyle?: Record<string, any>;
83
+ /** 内容样式。 */
84
+ contentStyle?: Record<string, any>;
85
+ /** 取消按钮样式。 */
86
+ cancelStyle?: Record<string, any>;
87
+ /** 确定按钮样式。 */
88
+ confirmStyle?: Record<string, any>;
89
+ /** 确认回调。 */
90
+ onConfirm?: () => void;
91
+ /** 取消回调。 */
92
+ onCancel?: () => void;
93
+ }