wui-components-v2 1.1.69 → 1.1.71

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 (117) hide show
  1. package/api/core/index.ts +74 -74
  2. package/api/feishu.ts +20 -20
  3. package/api/index.ts +4 -4
  4. package/api/login.ts +51 -51
  5. package/api/menu.ts +45 -45
  6. package/api/page.ts +114 -114
  7. package/api/sys.ts +12 -12
  8. package/api/tree.ts +28 -28
  9. package/components/action-popup/action-popup.vue +116 -116
  10. package/components/add-address-list/add-address-list.vue +187 -187
  11. package/components/add-address-page/add-address-page.vue +77 -77
  12. package/components/add-address-page/config.ts +297 -297
  13. package/components/audio-play/audio-play.vue +220 -220
  14. package/components/batch-upload-file/batch-upload-file.vue +75 -75
  15. package/components/card-botom-buttons/card-botom-buttons.vue +279 -279
  16. package/components/custom-date-picker/custom-date-picker.vue +106 -106
  17. package/components/custom-select-picker/custom-select-picker.vue +95 -95
  18. package/components/demo-block/demo-block.vue +63 -63
  19. package/components/demo-card/demo-card.vue +44 -44
  20. package/components/detail-popup/detail-popup.vue +99 -99
  21. package/components/evaluation-page/evaluation-page.vue +196 -196
  22. package/components/fold-card/fold-card.vue +171 -171
  23. package/components/form-control/form-control.vue +661 -661
  24. package/components/global-loading/GlobalLoading.md +366 -366
  25. package/components/global-loading/global-loading.vue +55 -55
  26. package/components/global-message/GlobalMessage.md +570 -570
  27. package/components/global-message/global-message.vue +68 -68
  28. package/components/global-toast/GlobalToast.md +261 -261
  29. package/components/global-toast/global-toast.vue +56 -56
  30. package/components/label-value/label-value.vue +144 -144
  31. package/components/label-value/open-value-more.vue +56 -56
  32. package/components/list-top-buttons/list-top-buttons.vue +19 -19
  33. package/components/loading/loading.vue +5 -5
  34. package/components/login-form/login-form.vue +126 -126
  35. package/components/login-form-box/login-form-box.vue +65 -65
  36. package/components/mulselect-picker/mulselect-picker.vue +86 -86
  37. package/components/phone-login-form/phone-login-form.vue +204 -204
  38. package/components/privacy-popup/privacy-popup.vue +191 -191
  39. package/components/product-card/product-card.vue +201 -201
  40. package/components/scan-input/scan-input.vue +406 -406
  41. package/components/search/search.vue +128 -128
  42. package/components/select-col-picker/select-col-picker.vue +168 -168
  43. package/components/tab-search/tab-search.vue +73 -73
  44. package/components/tree-select/components/TreeNode.vue +200 -200
  45. package/components/tree-select/components/index.vue +471 -471
  46. package/components/tree-select/components/tree-select.vue +56 -56
  47. package/components/tree-select/index.vue +93 -93
  48. package/components/user-choose/user-choose.vue +169 -151
  49. package/components/video-play/video-play.vue +56 -56
  50. package/components/wui-auto-update-component/wui-auto-update-component.vue +132 -132
  51. package/components/wui-default/wui-default.vue +49 -49
  52. package/components/wui-details-page/wui-details-page.vue +147 -147
  53. package/components/wui-edit-page/wui-edit-page-copy.vue +757 -757
  54. package/components/wui-edit-page/wui-edit-page.vue +793 -793
  55. package/components/wui-enume-select-control/wui-enume-select-control.vue +92 -92
  56. package/components/wui-list/wui-list.vue +235 -235
  57. package/components/wui-login/wui-login.vue +43 -43
  58. package/components/wui-login1/wui-login.vue +41 -41
  59. package/components/wui-menus/wui-menus-top.vue +137 -137
  60. package/components/wui-menus/wui-menus-top1.vue +8 -8
  61. package/components/wui-menus/wui-menus.vue +247 -247
  62. package/components/wui-menus1/components/banner-carousel.vue +320 -320
  63. package/components/wui-menus1/components/navbar.vue +43 -43
  64. package/components/wui-menus1/components/quick-panel.vue +55 -55
  65. package/components/wui-menus1/components/search-bar.vue +49 -49
  66. package/components/wui-menus1/components/section-menus.vue +208 -208
  67. package/components/wui-menus1/wui-menus-top.vue +718 -718
  68. package/components/wui-menus1/wui-menus-top1.vue +398 -398
  69. package/components/wui-menus1/wui-menus.vue +564 -564
  70. package/components/wui-notify-info/notify-handle.vue +53 -53
  71. package/components/wui-notify-info/wui-notify-info.vue +280 -280
  72. package/components/wui-scan-binding-sensor/wui-scan-binding-sensor.vue +75 -75
  73. package/components/wui-search-history-babbar/components/SearchBar.vue +70 -70
  74. package/components/wui-search-history-babbar/wui-search-history-babbar.vue +204 -204
  75. package/components/wui-select-list/wui-select-list.vue +310 -310
  76. package/components/wui-select-popup/wui-select-popup.vue +613 -612
  77. package/components/wui-system-settings/wui-system-settings.vue +144 -144
  78. package/components/wui-tabbar/wui-tabbar.vue +106 -106
  79. package/components/wui-tree-page/components/tree-item.vue +238 -238
  80. package/components/wui-tree-page/wui-tree-page.vue +297 -297
  81. package/components/wui-update-component/wui-update-component.vue +221 -221
  82. package/components/wui-user/wui-user.vue +202 -202
  83. package/composables/types/theme.ts +65 -65
  84. package/composables/types/user.ts +5 -5
  85. package/composables/useColorGenerator.ts +221 -221
  86. package/composables/useCompanyFieldFilter.ts +91 -91
  87. package/composables/useEnumes.ts +43 -43
  88. package/composables/useEventBus.ts +37 -37
  89. package/composables/useGlobalLoading.ts +47 -47
  90. package/composables/useGlobalMessage.ts +53 -53
  91. package/composables/useGlobalToast.ts +63 -63
  92. package/composables/useLocale.ts +54 -54
  93. package/composables/useManualTheme.ts +179 -179
  94. package/composables/useMenus.ts +193 -193
  95. package/composables/useSectionMenus.ts +120 -120
  96. package/composables/useTabbar.ts +50 -50
  97. package/composables/useTheme.ts +74 -74
  98. package/composables/useUser.ts +24 -24
  99. package/index.d.ts +90 -90
  100. package/index.ts +83 -83
  101. package/package.json +24 -24
  102. package/static/iconfont/iconfont.css +63 -63
  103. package/store/language.ts +151 -151
  104. package/store/manualThemeStore.ts +143 -143
  105. package/store/persist.ts +41 -41
  106. package/store/themeStore.ts +74 -74
  107. package/store/userStore.ts +22 -22
  108. package/styles/dark-mode.css +523 -523
  109. package/styles/dark-mode.min.css +1 -1
  110. package/styles/dark-mode.scss +498 -498
  111. package/type.ts +154 -154
  112. package/utils/control-tree.ts +353 -353
  113. package/utils/control-type-supportor.ts +148 -148
  114. package/utils/eventBus.ts +36 -36
  115. package/utils/idata-scan.ts +99 -99
  116. package/utils/index.ts +979 -979
  117. package/utils/rsaEncrypt.ts +10 -10
@@ -1,320 +1,320 @@
1
- <script setup lang="ts">
2
- /**
3
- * BannerCarousel.vue
4
- * 原生实现的 Banner 轮播组件
5
- * 功能:自动播放、手动滑动切换、鼠标悬停暂停、分页器点击跳转
6
- */
7
- import { onMounted, onUnmounted, ref } from 'vue'
8
-
9
- /** 从 mockData 获取轮播数据 */
10
- const banners: any = [
11
- {
12
- id: 1,
13
- title: '欢迎使用企业门户',
14
- subtitle: '高效协同,智慧办公',
15
- bgColor: 'bg-gradient-to-r from-indigo-500 to-purple-600',
16
- icon: 'building-office',
17
- },
18
- {
19
- id: 2,
20
- title: '本周会议安排',
21
- subtitle: '3场会议待参加',
22
- bgColor: 'bg-gradient-to-r from-emerald-500 to-teal-600',
23
- icon: 'calendar',
24
- },
25
- {
26
- id: 3,
27
- title: '系统升级通知',
28
- subtitle: 'v2.5.0 新功能上线',
29
- bgColor: 'bg-gradient-to-r from-orange-500 to-amber-600',
30
- icon: 'sparkles',
31
- },
32
- ]
33
-
34
- /**
35
- * 图标映射表
36
- * 根据 banner.icon 字段映射对应的 Heroicons 组件
37
- */
38
- // const iconMap: Record<string, any> = {
39
- // 'building-office': BuildingOfficeIcon,
40
- // 'calendar': CalendarIcon,
41
- // 'sparkles': SparklesIcon
42
- // }
43
-
44
- /** 当前显示的轮播项索引 */
45
- const currentIndex = ref(0)
46
-
47
- /** 鼠标是否悬停在轮播区域 */
48
- const isHovered = ref(false)
49
-
50
- /** 是否正在拖动(手动滑动中) */
51
- const isDragging = ref(false)
52
-
53
- /** 拖动偏移量(px),用于实时跟随手指/鼠标移动 */
54
- const dragOffset = ref(0)
55
-
56
- /** 自动播放的定时器引用 */
57
- let autoplayTimer: ReturnType<typeof setInterval> | null = null
58
-
59
- /** 拖动开始时的 X 坐标 */
60
- let startX = 0
61
-
62
- /**
63
- * 启动自动播放
64
- * 每 3 秒切换到下一张,循环播放
65
- */
66
- function startAutoplay() {
67
- if (autoplayTimer)
68
- clearInterval(autoplayTimer)
69
- autoplayTimer = setInterval(() => {
70
- currentIndex.value = (currentIndex.value + 1) % banners.length
71
- }, 10000)
72
- }
73
-
74
- /**
75
- * 停止自动播放
76
- * 清除定时器并置空引用
77
- */
78
- function stopAutoplay() {
79
- if (autoplayTimer) {
80
- clearInterval(autoplayTimer)
81
- autoplayTimer = null
82
- }
83
- }
84
-
85
- /**
86
- * 跳转到指定索引的轮播项
87
- * @param index 目标索引
88
- */
89
- function goToSlide(index: number) {
90
- currentIndex.value = index
91
- }
92
-
93
- /**
94
- * 切换到下一张轮播项
95
- * 使用取模实现循环(最后一张 -> 第一张)
96
- */
97
- function nextSlide() {
98
- currentIndex.value = (currentIndex.value + 1) % banners.length
99
- }
100
-
101
- /**
102
- * 切换到上一张轮播项
103
- * 使用取模实现循环(第一张 -> 最后一张)
104
- */
105
- function prevSlide() {
106
- currentIndex.value = (currentIndex.value - 1 + banners.length) % banners.length
107
- }
108
-
109
- /**
110
- * 鼠标进入轮播区域
111
- * 停止自动播放
112
- */
113
- function onMouseEnter() {
114
- isHovered.value = true
115
- stopAutoplay()
116
- }
117
-
118
- /**
119
- * 拖动开始
120
- * 记录起始 X 坐标,重置拖动偏移量
121
- * @param clientX 鼠标/触摸的 X 坐标
122
- */
123
- function onDragStart(clientX: number) {
124
- isDragging.value = true
125
- startX = clientX
126
- dragOffset.value = 0
127
- }
128
-
129
- /**
130
- * 拖动中
131
- * 实时更新 dragOffset,实现跟随效果
132
- * @param clientX 鼠标/触摸的当前 X 坐标
133
- */
134
- function onDragMove(clientX: number) {
135
- if (!isDragging.value)
136
- return
137
- dragOffset.value = clientX - startX
138
- }
139
-
140
- /**
141
- * 拖动结束
142
- * 判断滑动方向和距离:
143
- * - 左滑 > 50px:切换下一张
144
- * - 右滑 > 50px:切换上一张
145
- * - 否则:回弹到原位
146
- * 拖动结束后恢复自动播放
147
- */
148
- function onDragEnd() {
149
- if (!isDragging.value)
150
- return
151
- isDragging.value = false
152
-
153
- const threshold = 50
154
- if (dragOffset.value < -threshold) {
155
- nextSlide()
156
- }
157
- else if (dragOffset.value > threshold) {
158
- prevSlide()
159
- }
160
-
161
- dragOffset.value = 0
162
- startAutoplay()
163
- }
164
-
165
- /** 触摸开始事件(移动端) */
166
- function onTouchStart(e: TouchEvent) {
167
- onDragStart(e.touches[0]?.clientX ?? 0)
168
- }
169
-
170
- /** 触摸移动事件(移动端) */
171
- function onTouchMove(e: TouchEvent) {
172
- onDragMove(e.touches[0]?.clientX ?? 0)
173
- }
174
-
175
- /** 触摸结束事件(移动端) */
176
- function onTouchEnd() {
177
- onDragEnd()
178
- }
179
-
180
- /** 鼠标按下事件(桌面端) */
181
- function onMouseDown(e: MouseEvent) {
182
- onDragStart(e.clientX)
183
- }
184
-
185
- /** 鼠标移动事件(桌面端拖动中) */
186
- function onMouseMove(e: MouseEvent) {
187
- onDragMove(e.clientX)
188
- }
189
-
190
- /** 鼠标释放事件(桌面端拖动结束) */
191
- function onMouseUp() {
192
- onDragEnd()
193
- }
194
-
195
- /**
196
- * 鼠标离开轮播区域
197
- * 如果正在拖动中则触发拖动结束
198
- * 恢复自动播放
199
- */
200
- function onMouseLeave() {
201
- if (isDragging.value) {
202
- onDragEnd()
203
- }
204
- isHovered.value = false
205
- startAutoplay()
206
- }
207
-
208
- /** 组件挂载时启动自动播放 */
209
- onMounted(() => {
210
- startAutoplay()
211
- })
212
-
213
- /** 组件卸载时停止自动播放,防止内存泄漏 */
214
- onUnmounted(() => {
215
- stopAutoplay()
216
- })
217
- </script>
218
-
219
- <template>
220
- <!--
221
- 外层容器:
222
- - select-none:防止拖动时选中文本
223
- - 监听 mouseenter/mouseleave 用于悬停暂停
224
- -->
225
- <view
226
- class="mt-6 select-none overflow-hidden rounded-2xl pb-2"
227
- @mouseenter="onMouseEnter"
228
- @mouseleave="onMouseLeave"
229
- >
230
- <!--
231
- 轮播内容区:
232
- - overflow-hidden:裁剪只显示当前 slide
233
- - cursor-grab/active:拖动时显示抓取手势
234
- - 监听 mousedown/mousemove/mouseup(桌面端)
235
- - 监听 touchstart/touchmove/touchend(移动端)
236
- -->
237
- <view
238
- class="cursor-grab overflow-hidden active:cursor-grabbing"
239
- @mousedown="onMouseDown"
240
- @mousemove="onMouseMove"
241
- @mouseup="onMouseUp"
242
- @mouseleave="onMouseLeave"
243
- @touchstart="onTouchStart"
244
- @touchmove="onTouchMove"
245
- @touchend="onTouchEnd"
246
- >
247
- <!--
248
- 轮播轨道:
249
- - flex:水平排列所有 slide
250
- - transition-transform:
251
- - 拖动中:transition-none(实时跟随,无动画)
252
- - 正常切换:transition-transform duration-[600ms] ease(600ms 动画)
253
- - translateX 计算:
254
- - 基准位置:-${currentIndex * 100}%
255
- - 拖动偏移:+ ${dragOffset}px
256
- -->
257
- <view
258
- class="flex"
259
- :class="isDragging ? 'transition-none' : 'transition-transform duration-[600ms] ease'"
260
- :style="{ transform: `translateX(calc(-${currentIndex * 100}% + ${dragOffset}px))` }"
261
- >
262
- <!--
263
- 单个轮播项:
264
- - w-full flex-shrink-0:每个 slide 占满宽度且不压缩
265
- -->
266
- <view
267
- v-for="banner in banners"
268
- :key="banner.id"
269
- class="w-full flex-shrink-0 cursor-pointer"
270
- >
271
- <!--
272
- Banner 内容卡片:
273
- - 动态背景色(bgColor)
274
- - h-36:固定高度
275
- - flex items-center:垂直居中
276
- - px-6:水平内边距
277
- - rounded-2xl:圆角
278
- -->
279
- <view
280
- :class="banner.bgColor"
281
- class="h-36 flex items-center rounded-2xl px-6"
282
- >
283
- <!-- 左侧文字区域 -->
284
- <view class="flex-1">
285
- <h3 class="text-lg text-white font-semibold">
286
- {{ banner.title }}
287
- </h3>
288
- <p class="mt-1 text-sm text-white/80">
289
- {{ banner.subtitle }}
290
- </p>
291
- </view>
292
-
293
- <!-- 右侧图标区域 -->
294
- <view class="h-14 w-14 flex items-center justify-center rounded-full bg-white/20">
295
- <!-- <component :is="iconMap[banner.icon]" class="w-8 h-8 text-white" /> -->
296
- <wd-icon name="calendar" size="26px" />
297
- </view>
298
- </view>
299
- </view>
300
- </view>
301
- </view>
302
-
303
- <!--
304
- 分页器:
305
- - flex justify-center gap-2:水平居中,dot 之间 8px 间距
306
- - mt-4:与轮播内容保持间距
307
- -->
308
- <view class="mt-4 flex justify-center gap-2">
309
- <view
310
- v-for="(_, index) in banners"
311
- :key="index"
312
- class="h-1 w-1 cursor-pointer rounded-full transition-all duration-300"
313
- :class="index === currentIndex
314
- ? 'bg-white opacity-100'
315
- : 'bg-white/50'"
316
- @click="goToSlide(Number(index))"
317
- />
318
- </view>
319
- </view>
320
- </template>
1
+ <script setup lang="ts">
2
+ /**
3
+ * BannerCarousel.vue
4
+ * 原生实现的 Banner 轮播组件
5
+ * 功能:自动播放、手动滑动切换、鼠标悬停暂停、分页器点击跳转
6
+ */
7
+ import { onMounted, onUnmounted, ref } from 'vue'
8
+
9
+ /** 从 mockData 获取轮播数据 */
10
+ const banners: any = [
11
+ {
12
+ id: 1,
13
+ title: '欢迎使用企业门户',
14
+ subtitle: '高效协同,智慧办公',
15
+ bgColor: 'bg-gradient-to-r from-indigo-500 to-purple-600',
16
+ icon: 'building-office',
17
+ },
18
+ {
19
+ id: 2,
20
+ title: '本周会议安排',
21
+ subtitle: '3场会议待参加',
22
+ bgColor: 'bg-gradient-to-r from-emerald-500 to-teal-600',
23
+ icon: 'calendar',
24
+ },
25
+ {
26
+ id: 3,
27
+ title: '系统升级通知',
28
+ subtitle: 'v2.5.0 新功能上线',
29
+ bgColor: 'bg-gradient-to-r from-orange-500 to-amber-600',
30
+ icon: 'sparkles',
31
+ },
32
+ ]
33
+
34
+ /**
35
+ * 图标映射表
36
+ * 根据 banner.icon 字段映射对应的 Heroicons 组件
37
+ */
38
+ // const iconMap: Record<string, any> = {
39
+ // 'building-office': BuildingOfficeIcon,
40
+ // 'calendar': CalendarIcon,
41
+ // 'sparkles': SparklesIcon
42
+ // }
43
+
44
+ /** 当前显示的轮播项索引 */
45
+ const currentIndex = ref(0)
46
+
47
+ /** 鼠标是否悬停在轮播区域 */
48
+ const isHovered = ref(false)
49
+
50
+ /** 是否正在拖动(手动滑动中) */
51
+ const isDragging = ref(false)
52
+
53
+ /** 拖动偏移量(px),用于实时跟随手指/鼠标移动 */
54
+ const dragOffset = ref(0)
55
+
56
+ /** 自动播放的定时器引用 */
57
+ let autoplayTimer: ReturnType<typeof setInterval> | null = null
58
+
59
+ /** 拖动开始时的 X 坐标 */
60
+ let startX = 0
61
+
62
+ /**
63
+ * 启动自动播放
64
+ * 每 3 秒切换到下一张,循环播放
65
+ */
66
+ function startAutoplay() {
67
+ if (autoplayTimer)
68
+ clearInterval(autoplayTimer)
69
+ autoplayTimer = setInterval(() => {
70
+ currentIndex.value = (currentIndex.value + 1) % banners.length
71
+ }, 10000)
72
+ }
73
+
74
+ /**
75
+ * 停止自动播放
76
+ * 清除定时器并置空引用
77
+ */
78
+ function stopAutoplay() {
79
+ if (autoplayTimer) {
80
+ clearInterval(autoplayTimer)
81
+ autoplayTimer = null
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 跳转到指定索引的轮播项
87
+ * @param index 目标索引
88
+ */
89
+ function goToSlide(index: number) {
90
+ currentIndex.value = index
91
+ }
92
+
93
+ /**
94
+ * 切换到下一张轮播项
95
+ * 使用取模实现循环(最后一张 -> 第一张)
96
+ */
97
+ function nextSlide() {
98
+ currentIndex.value = (currentIndex.value + 1) % banners.length
99
+ }
100
+
101
+ /**
102
+ * 切换到上一张轮播项
103
+ * 使用取模实现循环(第一张 -> 最后一张)
104
+ */
105
+ function prevSlide() {
106
+ currentIndex.value = (currentIndex.value - 1 + banners.length) % banners.length
107
+ }
108
+
109
+ /**
110
+ * 鼠标进入轮播区域
111
+ * 停止自动播放
112
+ */
113
+ function onMouseEnter() {
114
+ isHovered.value = true
115
+ stopAutoplay()
116
+ }
117
+
118
+ /**
119
+ * 拖动开始
120
+ * 记录起始 X 坐标,重置拖动偏移量
121
+ * @param clientX 鼠标/触摸的 X 坐标
122
+ */
123
+ function onDragStart(clientX: number) {
124
+ isDragging.value = true
125
+ startX = clientX
126
+ dragOffset.value = 0
127
+ }
128
+
129
+ /**
130
+ * 拖动中
131
+ * 实时更新 dragOffset,实现跟随效果
132
+ * @param clientX 鼠标/触摸的当前 X 坐标
133
+ */
134
+ function onDragMove(clientX: number) {
135
+ if (!isDragging.value)
136
+ return
137
+ dragOffset.value = clientX - startX
138
+ }
139
+
140
+ /**
141
+ * 拖动结束
142
+ * 判断滑动方向和距离:
143
+ * - 左滑 > 50px:切换下一张
144
+ * - 右滑 > 50px:切换上一张
145
+ * - 否则:回弹到原位
146
+ * 拖动结束后恢复自动播放
147
+ */
148
+ function onDragEnd() {
149
+ if (!isDragging.value)
150
+ return
151
+ isDragging.value = false
152
+
153
+ const threshold = 50
154
+ if (dragOffset.value < -threshold) {
155
+ nextSlide()
156
+ }
157
+ else if (dragOffset.value > threshold) {
158
+ prevSlide()
159
+ }
160
+
161
+ dragOffset.value = 0
162
+ startAutoplay()
163
+ }
164
+
165
+ /** 触摸开始事件(移动端) */
166
+ function onTouchStart(e: TouchEvent) {
167
+ onDragStart(e.touches[0]?.clientX ?? 0)
168
+ }
169
+
170
+ /** 触摸移动事件(移动端) */
171
+ function onTouchMove(e: TouchEvent) {
172
+ onDragMove(e.touches[0]?.clientX ?? 0)
173
+ }
174
+
175
+ /** 触摸结束事件(移动端) */
176
+ function onTouchEnd() {
177
+ onDragEnd()
178
+ }
179
+
180
+ /** 鼠标按下事件(桌面端) */
181
+ function onMouseDown(e: MouseEvent) {
182
+ onDragStart(e.clientX)
183
+ }
184
+
185
+ /** 鼠标移动事件(桌面端拖动中) */
186
+ function onMouseMove(e: MouseEvent) {
187
+ onDragMove(e.clientX)
188
+ }
189
+
190
+ /** 鼠标释放事件(桌面端拖动结束) */
191
+ function onMouseUp() {
192
+ onDragEnd()
193
+ }
194
+
195
+ /**
196
+ * 鼠标离开轮播区域
197
+ * 如果正在拖动中则触发拖动结束
198
+ * 恢复自动播放
199
+ */
200
+ function onMouseLeave() {
201
+ if (isDragging.value) {
202
+ onDragEnd()
203
+ }
204
+ isHovered.value = false
205
+ startAutoplay()
206
+ }
207
+
208
+ /** 组件挂载时启动自动播放 */
209
+ onMounted(() => {
210
+ startAutoplay()
211
+ })
212
+
213
+ /** 组件卸载时停止自动播放,防止内存泄漏 */
214
+ onUnmounted(() => {
215
+ stopAutoplay()
216
+ })
217
+ </script>
218
+
219
+ <template>
220
+ <!--
221
+ 外层容器:
222
+ - select-none:防止拖动时选中文本
223
+ - 监听 mouseenter/mouseleave 用于悬停暂停
224
+ -->
225
+ <view
226
+ class="mt-6 select-none overflow-hidden rounded-2xl pb-2"
227
+ @mouseenter="onMouseEnter"
228
+ @mouseleave="onMouseLeave"
229
+ >
230
+ <!--
231
+ 轮播内容区:
232
+ - overflow-hidden:裁剪只显示当前 slide
233
+ - cursor-grab/active:拖动时显示抓取手势
234
+ - 监听 mousedown/mousemove/mouseup(桌面端)
235
+ - 监听 touchstart/touchmove/touchend(移动端)
236
+ -->
237
+ <view
238
+ class="cursor-grab overflow-hidden active:cursor-grabbing"
239
+ @mousedown="onMouseDown"
240
+ @mousemove="onMouseMove"
241
+ @mouseup="onMouseUp"
242
+ @mouseleave="onMouseLeave"
243
+ @touchstart="onTouchStart"
244
+ @touchmove="onTouchMove"
245
+ @touchend="onTouchEnd"
246
+ >
247
+ <!--
248
+ 轮播轨道:
249
+ - flex:水平排列所有 slide
250
+ - transition-transform:
251
+ - 拖动中:transition-none(实时跟随,无动画)
252
+ - 正常切换:transition-transform duration-[600ms] ease(600ms 动画)
253
+ - translateX 计算:
254
+ - 基准位置:-${currentIndex * 100}%
255
+ - 拖动偏移:+ ${dragOffset}px
256
+ -->
257
+ <view
258
+ class="flex"
259
+ :class="isDragging ? 'transition-none' : 'transition-transform duration-[600ms] ease'"
260
+ :style="{ transform: `translateX(calc(-${currentIndex * 100}% + ${dragOffset}px))` }"
261
+ >
262
+ <!--
263
+ 单个轮播项:
264
+ - w-full flex-shrink-0:每个 slide 占满宽度且不压缩
265
+ -->
266
+ <view
267
+ v-for="banner in banners"
268
+ :key="banner.id"
269
+ class="w-full flex-shrink-0 cursor-pointer"
270
+ >
271
+ <!--
272
+ Banner 内容卡片:
273
+ - 动态背景色(bgColor)
274
+ - h-36:固定高度
275
+ - flex items-center:垂直居中
276
+ - px-6:水平内边距
277
+ - rounded-2xl:圆角
278
+ -->
279
+ <view
280
+ :class="banner.bgColor"
281
+ class="h-36 flex items-center rounded-2xl px-6"
282
+ >
283
+ <!-- 左侧文字区域 -->
284
+ <view class="flex-1">
285
+ <h3 class="text-lg text-white font-semibold">
286
+ {{ banner.title }}
287
+ </h3>
288
+ <p class="mt-1 text-sm text-white/80">
289
+ {{ banner.subtitle }}
290
+ </p>
291
+ </view>
292
+
293
+ <!-- 右侧图标区域 -->
294
+ <view class="h-14 w-14 flex items-center justify-center rounded-full bg-white/20">
295
+ <!-- <component :is="iconMap[banner.icon]" class="w-8 h-8 text-white" /> -->
296
+ <wd-icon name="calendar" size="26px" />
297
+ </view>
298
+ </view>
299
+ </view>
300
+ </view>
301
+ </view>
302
+
303
+ <!--
304
+ 分页器:
305
+ - flex justify-center gap-2:水平居中,dot 之间 8px 间距
306
+ - mt-4:与轮播内容保持间距
307
+ -->
308
+ <view class="mt-4 flex justify-center gap-2">
309
+ <view
310
+ v-for="(_, index) in banners"
311
+ :key="index"
312
+ class="h-1 w-1 cursor-pointer rounded-full transition-all duration-300"
313
+ :class="index === currentIndex
314
+ ? 'bg-white opacity-100'
315
+ : 'bg-white/50'"
316
+ @click="goToSlide(Number(index))"
317
+ />
318
+ </view>
319
+ </view>
320
+ </template>