wedux-ui 0.3.1 → 0.5.0

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.
@@ -0,0 +1,568 @@
1
+ const { createDrag } = require('../../utils/drag');
2
+
3
+ Component({
4
+ relations: {
5
+ '../carousel-item/carousel-item': {
6
+ type: 'descendant',
7
+ linked: function () {
8
+ this._onChildrenChange();
9
+ },
10
+ unlinked: function () {
11
+ this._onChildrenChange();
12
+ },
13
+ },
14
+ },
15
+
16
+ properties: {
17
+ current: {
18
+ type: Number,
19
+ value: -1,
20
+ },
21
+ defaultCurrent: {
22
+ type: Number,
23
+ value: 0,
24
+ },
25
+ effect: {
26
+ type: String,
27
+ value: 'slide',
28
+ },
29
+ direction: {
30
+ type: String,
31
+ value: 'horizontal',
32
+ },
33
+ loop: {
34
+ type: Boolean,
35
+ value: true,
36
+ },
37
+ autoplay: {
38
+ type: Boolean,
39
+ value: false,
40
+ },
41
+ interval: {
42
+ type: Number,
43
+ value: 5000,
44
+ },
45
+ duration: {
46
+ type: Number,
47
+ value: 300,
48
+ },
49
+ slidesPerView: {
50
+ type: Number,
51
+ value: 1,
52
+ },
53
+ spaceBetween: {
54
+ type: Number,
55
+ value: 0,
56
+ },
57
+ showDots: {
58
+ type: Boolean,
59
+ value: true,
60
+ },
61
+ dotType: {
62
+ type: String,
63
+ value: 'dot',
64
+ },
65
+ dotPlacement: {
66
+ type: String,
67
+ value: 'bottom',
68
+ },
69
+ },
70
+
71
+ data: {
72
+ _trackStyle: '',
73
+ _dots: [],
74
+ _total: 0,
75
+ },
76
+
77
+ observers: {
78
+ current: function (val) {
79
+ if (val >= 0) {
80
+ this._goTo(val, true);
81
+ }
82
+ },
83
+ },
84
+
85
+ lifetimes: {
86
+ created() {
87
+ this._internalIndex = this.data.defaultCurrent;
88
+ this._drag = createDrag();
89
+ },
90
+
91
+ ready() {
92
+ this._containerWidth = 0;
93
+ this._containerHeight = 0;
94
+ this._slideSize = 0;
95
+ this._autoplayTimer = null;
96
+ this._loopResetTimer = null;
97
+ this._boundaryItemIndex = -1;
98
+
99
+ wx.nextTick(() => {
100
+ this._measure();
101
+ });
102
+ },
103
+
104
+ detached() {
105
+ this._stopAutoplay();
106
+ if (this._loopResetTimer) {
107
+ clearTimeout(this._loopResetTimer);
108
+ this._loopResetTimer = null;
109
+ }
110
+ },
111
+ },
112
+
113
+ methods: {
114
+ /* ── Relations ── */
115
+ _getItems() {
116
+ return this.getRelationNodes('../carousel-item/carousel-item') || [];
117
+ },
118
+
119
+ _onChildrenChange() {
120
+ const items = this._getItems();
121
+ const total = items.length;
122
+ this._internalIndex = Math.min(this._internalIndex || 0, Math.max(0, total - 1));
123
+ this._rebuildDots();
124
+ if (this._slideSize > 0) {
125
+ this._updateChildSizes();
126
+ this._goTo(this._internalIndex, false);
127
+ }
128
+ },
129
+
130
+ /* ── Measure ── */
131
+ _measure() {
132
+ const query = this.createSelectorQuery();
133
+ query.select('.w-carousel').boundingClientRect();
134
+ query.exec((res) => {
135
+ if (!res[0]) return;
136
+ const { width, height } = res[0];
137
+ this._containerWidth = width;
138
+ this._containerHeight = height;
139
+ const { slidesPerView, spaceBetween, direction } = this.data;
140
+ const containerSize = direction === 'horizontal' ? width : height;
141
+ this._slideSize = (containerSize - (slidesPerView - 1) * spaceBetween) / slidesPerView;
142
+
143
+ this._updateChildSizes();
144
+ this._goTo(this._internalIndex, false);
145
+ this._startAutoplay();
146
+ });
147
+ },
148
+
149
+ /* ── Child size dispatch (slide effect only) ── */
150
+ _updateChildSizes() {
151
+ const items = this._getItems();
152
+ const { effect, direction, spaceBetween } = this.data;
153
+ if (effect !== 'slide') return;
154
+
155
+ items.forEach((item) => {
156
+ const sizeStyle =
157
+ direction === 'horizontal'
158
+ ? `width: ${this._slideSize}px; height: 100%; margin-right: ${spaceBetween}px;`
159
+ : `width: 100%; height: ${this._slideSize}px; margin-bottom: ${spaceBetween}px;`;
160
+ item._setState('active', sizeStyle);
161
+ });
162
+ },
163
+
164
+ /* ── Dots ── */
165
+ _rebuildDots() {
166
+ const items = this._getItems();
167
+ const total = items.length;
168
+ const { slidesPerView } = this.data;
169
+ const dotCount =
170
+ slidesPerView <= 1 ? total : Math.max(0, total - Math.floor(slidesPerView) + 1);
171
+ const displayIndex = this._internalIndex;
172
+ const dots = Array.from({ length: dotCount }, (_, i) => ({
173
+ active: i === displayIndex,
174
+ }));
175
+ this.setData({ _total: total, _dots: dots });
176
+ },
177
+
178
+ /* ── Go to index ── */
179
+ _goTo(index, animate) {
180
+ if (animate === undefined) animate = true;
181
+ const items = this._getItems();
182
+ const total = items.length;
183
+ if (!total) return;
184
+
185
+ // 清除待执行的 loop 复位,立即复位
186
+ if (this._loopResetTimer) {
187
+ clearTimeout(this._loopResetTimer);
188
+ this._loopResetTimer = null;
189
+ this._updateChildSizes();
190
+ }
191
+
192
+ let newIndex = index;
193
+
194
+ if (this.data.loop) {
195
+ newIndex = ((index % total) + total) % total;
196
+ } else {
197
+ newIndex = Math.max(0, Math.min(index, total - 1));
198
+ }
199
+
200
+ const prev = this._internalIndex;
201
+ this._internalIndex = newIndex;
202
+
203
+ // slide + loop 边界过渡:无缝动画而非瞬移
204
+ if (
205
+ this.data.loop &&
206
+ this.data.effect === 'slide' &&
207
+ animate &&
208
+ total > 1 &&
209
+ this.data.slidesPerView <= 1
210
+ ) {
211
+ const isForwardLoop = prev === total - 1 && newIndex === 0;
212
+ const isBackwardLoop = prev === 0 && newIndex === total - 1;
213
+
214
+ if (isForwardLoop || isBackwardLoop) {
215
+ this._slideLoopTransition(prev, newIndex, isForwardLoop);
216
+ this._rebuildDots();
217
+ if (prev !== newIndex) {
218
+ this.triggerEvent('change', { current: newIndex, previous: prev });
219
+ }
220
+ return;
221
+ }
222
+ }
223
+
224
+ // 非边界过渡,清理拖拽时预置的 boundary item
225
+ if (this._boundaryItemIndex >= 0) {
226
+ this._boundaryItemIndex = -1;
227
+ if (animate) {
228
+ this._loopResetTimer = setTimeout(() => {
229
+ this._loopResetTimer = null;
230
+ this._updateChildSizes();
231
+ }, this.data.duration + 20);
232
+ } else {
233
+ this._updateChildSizes();
234
+ }
235
+ }
236
+
237
+ this._applyEffect(animate);
238
+ this._rebuildDots();
239
+
240
+ if (prev !== newIndex) {
241
+ this.triggerEvent('change', { current: newIndex, previous: prev });
242
+ }
243
+ },
244
+
245
+ /* ── Apply visual effect ── */
246
+ _applyEffect(animate) {
247
+ const { effect, direction, spaceBetween, duration } = this.data;
248
+ const dur = animate ? duration : 0;
249
+
250
+ if (effect === 'slide') {
251
+ const offset = this._internalIndex * (this._slideSize + spaceBetween);
252
+ const transform =
253
+ direction === 'horizontal' ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
254
+ this.setData({
255
+ _trackStyle: `transform: ${transform}; transition: transform ${dur}ms ease;`,
256
+ });
257
+ return;
258
+ }
259
+
260
+ this._applyNonSlideEffect(dur);
261
+ },
262
+
263
+ _applyNonSlideEffect(dur) {
264
+ const items = this._getItems();
265
+ const total = items.length;
266
+ const { effect, loop } = this.data;
267
+ const cur = this._internalIndex;
268
+
269
+ items.forEach((item, i) => {
270
+ let state, style;
271
+ const base = `width: 100%; height: 100%; position: absolute; top: 0; left: 0;`;
272
+
273
+ if (effect === 'fade') {
274
+ const isActive = i === cur;
275
+ state = isActive ? 'active' : 'hidden';
276
+ style = `${base} opacity: ${isActive ? 1 : 0}; transition: opacity ${dur}ms; z-index: ${isActive ? 1 : 0};`;
277
+ } else {
278
+ // card
279
+ const cardBase = `${base} border-radius: var(--w-carousel-card-radius); overflow: hidden;`;
280
+ const diff = i - cur;
281
+ const isPrev = diff === -1 || (loop && diff === total - 1);
282
+ const isNext = diff === 1 || (loop && diff === -(total - 1));
283
+
284
+ if (diff === 0) {
285
+ state = 'active';
286
+ style = `${cardBase} transform: scale(1) translateX(0); opacity: 1; transition: transform ${dur}ms ease, opacity ${dur}ms ease; z-index: 1;`;
287
+ } else if (isPrev) {
288
+ state = 'prev';
289
+ style = `${cardBase} transform: scale(0.85) translateX(-70%); opacity: 0.7; transition: transform ${dur}ms ease, opacity ${dur}ms ease; z-index: 0;`;
290
+ } else if (isNext) {
291
+ state = 'next';
292
+ style = `${cardBase} transform: scale(0.85) translateX(70%); opacity: 0.7; transition: transform ${dur}ms ease, opacity ${dur}ms ease; z-index: 0;`;
293
+ } else {
294
+ state = 'hidden';
295
+ style = `${cardBase} opacity: 0; pointer-events: none; z-index: 0;`;
296
+ }
297
+ }
298
+
299
+ item._setState(state, style);
300
+ });
301
+ },
302
+
303
+ /* ── Slide loop 无缝过渡 ── */
304
+ _getItemSizeStyle() {
305
+ const { direction, spaceBetween } = this.data;
306
+ return direction === 'horizontal'
307
+ ? `width: ${this._slideSize}px; height: 100%; margin-right: ${spaceBetween}px;`
308
+ : `width: 100%; height: ${this._slideSize}px; margin-bottom: ${spaceBetween}px;`;
309
+ },
310
+
311
+ _slideLoopTransition(fromIndex, toIndex, isForward) {
312
+ const items = this._getItems();
313
+ const total = items.length;
314
+ const { direction, spaceBetween, duration } = this.data;
315
+ const step = this._slideSize + spaceBetween;
316
+ const isH = direction === 'horizontal';
317
+ const axis = isH ? 'X' : 'Y';
318
+ const sizeStyle = this._getItemSizeStyle();
319
+
320
+ this._boundaryItemIndex = -1;
321
+
322
+ if (isForward) {
323
+ // 最后→第一:把 item[0] 移到最后一张后面
324
+ items[toIndex]._setState(
325
+ 'active',
326
+ `${sizeStyle} transform: translate${axis}(${total * step}px);`,
327
+ );
328
+ // track 向前滑动一格到 item[0] 的临时位置
329
+ const offset = total * step;
330
+ const transform = isH ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
331
+ this.setData({
332
+ _trackStyle: `transform: ${transform}; transition: transform ${duration}ms ease;`,
333
+ });
334
+ } else {
335
+ // 第一→最后:把 item[last] 移到第一张前面
336
+ items[toIndex]._setState(
337
+ 'active',
338
+ `${sizeStyle} transform: translate${axis}(${-total * step}px);`,
339
+ );
340
+ // track 向后滑动一格
341
+ const offset = -step;
342
+ const transform = isH ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
343
+ this.setData({
344
+ _trackStyle: `transform: ${transform}; transition: transform ${duration}ms ease;`,
345
+ });
346
+ }
347
+
348
+ // 动画结束后复位所有 item 和 track
349
+ this._loopResetTimer = setTimeout(() => {
350
+ this._loopResetTimer = null;
351
+ this._updateChildSizes();
352
+ this._applyEffect(false);
353
+ }, duration + 20);
354
+ },
355
+
356
+ /**
357
+ * 拖拽前预置边界 item,使跟手拖动时能看到下一张
358
+ */
359
+ _prepareBoundarySlide() {
360
+ const items = this._getItems();
361
+ const total = items.length;
362
+ if (total <= 1) return;
363
+
364
+ const cur = this._internalIndex;
365
+ const { direction, spaceBetween } = this.data;
366
+ const step = this._slideSize + spaceBetween;
367
+ const isH = direction === 'horizontal';
368
+ const axis = isH ? 'X' : 'Y';
369
+ const sizeStyle = this._getItemSizeStyle();
370
+
371
+ this._boundaryItemIndex = -1;
372
+
373
+ if (cur === 0) {
374
+ // 可能向前滑(回到最后)→ 把最后一张移到第一张前面
375
+ items[total - 1]._setState(
376
+ 'active',
377
+ `${sizeStyle} transform: translate${axis}(${-total * step}px);`,
378
+ );
379
+ this._boundaryItemIndex = total - 1;
380
+ } else if (cur === total - 1) {
381
+ // 可能向后滑(到第一张)→ 把第一张移到最后一张后面
382
+ items[0]._setState(
383
+ 'active',
384
+ `${sizeStyle} transform: translate${axis}(${total * step}px);`,
385
+ );
386
+ this._boundaryItemIndex = 0;
387
+ }
388
+ },
389
+
390
+ /* ── Touch handlers ── */
391
+ onTouchStart(e) {
392
+ this._stopAutoplay();
393
+ // 清除待执行的 loop 复位
394
+ if (this._loopResetTimer) {
395
+ clearTimeout(this._loopResetTimer);
396
+ this._loopResetTimer = null;
397
+ this._updateChildSizes();
398
+ this._applyEffect(false);
399
+ }
400
+
401
+ this._drag.start(e.touches[0]);
402
+ if (this.data.effect === 'slide') {
403
+ this._startTranslate = this._internalIndex * (this._slideSize + this.data.spaceBetween);
404
+ // loop 模式下预置边界 item,使跟手拖动无缝
405
+ if (this.data.loop && this.data.slidesPerView <= 1) {
406
+ this._prepareBoundarySlide();
407
+ }
408
+ }
409
+ },
410
+
411
+ onTouchMove(e) {
412
+ if (!this._drag.isActive()) return;
413
+ const { effect, direction } = this.data;
414
+ const touch = e.touches[0];
415
+
416
+ if (effect === 'slide') {
417
+ const delta = this._drag.delta(touch, direction);
418
+ const offset = this._startTranslate - delta;
419
+ const transform =
420
+ direction === 'horizontal' ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
421
+ this.setData({ _trackStyle: `transform: ${transform}; transition: none;` });
422
+ } else {
423
+ const containerSize =
424
+ direction === 'horizontal' ? this._containerWidth : this._containerHeight;
425
+ const progress = this._drag.progress(touch, direction, containerSize);
426
+ if (effect === 'fade') {
427
+ this._applyFadeDrag(progress);
428
+ } else {
429
+ this._applyCardDrag(progress);
430
+ }
431
+ }
432
+ },
433
+
434
+ onTouchEnd(e) {
435
+ const { direction } = this.data;
436
+ const containerSize =
437
+ direction === 'horizontal' ? this._containerWidth : this._containerHeight;
438
+ const result = this._drag.end(e.changedTouches[0], direction, containerSize);
439
+ if (!result) return;
440
+
441
+ if (result.swipe === 1) {
442
+ this._goTo(this._internalIndex + 1);
443
+ } else if (result.swipe === -1) {
444
+ this._goTo(this._internalIndex - 1);
445
+ } else {
446
+ this._goTo(this._internalIndex);
447
+ }
448
+ this._startAutoplay();
449
+ },
450
+
451
+ /* ── Fade 跟手拖动 ── */
452
+ _applyFadeDrag(progress) {
453
+ const items = this._getItems();
454
+ const total = items.length;
455
+ const cur = this._internalIndex;
456
+ const { loop } = this.data;
457
+ const absP = Math.abs(progress);
458
+
459
+ let targetIndex;
460
+ if (progress > 0) {
461
+ targetIndex = loop ? (cur + 1) % total : Math.min(cur + 1, total - 1);
462
+ } else {
463
+ targetIndex = loop ? (cur - 1 + total) % total : Math.max(cur - 1, 0);
464
+ }
465
+
466
+ const base = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0;';
467
+ items.forEach((item, i) => {
468
+ let opacity, zIndex;
469
+ if (i === cur) {
470
+ opacity = 1 - absP;
471
+ zIndex = 1;
472
+ } else if (i === targetIndex && targetIndex !== cur) {
473
+ opacity = absP;
474
+ zIndex = 1;
475
+ } else {
476
+ opacity = 0;
477
+ zIndex = 0;
478
+ }
479
+ item._setState(
480
+ i === cur ? 'active' : 'hidden',
481
+ `${base} opacity: ${opacity}; z-index: ${zIndex}; transition: none;`,
482
+ );
483
+ });
484
+ },
485
+
486
+ /* ── Card 跟手拖动 ── */
487
+ _applyCardDrag(progress) {
488
+ const items = this._getItems();
489
+ const total = items.length;
490
+ const cur = this._internalIndex;
491
+ const { loop } = this.data;
492
+ const absP = Math.abs(progress);
493
+
494
+ const prevIndex = loop ? (cur - 1 + total) % total : cur - 1;
495
+ const nextIndex = loop ? (cur + 1) % total : cur + 1;
496
+
497
+ const base =
498
+ 'width: 100%; height: 100%; position: absolute; top: 0; left: 0; border-radius: var(--w-carousel-card-radius); overflow: hidden;';
499
+ items.forEach((item, i) => {
500
+ let style;
501
+ if (i === cur) {
502
+ const tx = -progress * 70;
503
+ const scale = 1 - absP * 0.15;
504
+ const opacity = 1 - absP * 0.3;
505
+ style = `${base} transform: scale(${scale}) translateX(${tx}%); opacity: ${opacity}; z-index: 1; transition: none;`;
506
+ } else if (i === prevIndex && prevIndex >= 0) {
507
+ const tx = progress < 0 ? -70 + absP * 70 : -70;
508
+ const scale = progress < 0 ? 0.85 + absP * 0.15 : 0.85;
509
+ const opacity = progress < 0 ? 0.7 + absP * 0.3 : 0.7;
510
+ style = `${base} transform: scale(${scale}) translateX(${tx}%); opacity: ${opacity}; z-index: 0; transition: none;`;
511
+ } else if (i === nextIndex && nextIndex < total) {
512
+ const tx = progress > 0 ? 70 - absP * 70 : 70;
513
+ const scale = progress > 0 ? 0.85 + absP * 0.15 : 0.85;
514
+ const opacity = progress > 0 ? 0.7 + absP * 0.3 : 0.7;
515
+ style = `${base} transform: scale(${scale}) translateX(${tx}%); opacity: ${opacity}; z-index: 0; transition: none;`;
516
+ } else {
517
+ style = `${base} opacity: 0; pointer-events: none; z-index: 0;`;
518
+ }
519
+ item._setState(i === cur ? 'active' : 'other', style);
520
+ });
521
+ },
522
+
523
+ /* ── Card tap ── */
524
+ _onItemTap(item) {
525
+ if (this.data.effect !== 'card') return;
526
+ const items = this._getItems();
527
+ const idx = items.indexOf(item);
528
+ if (idx !== -1 && idx !== this._internalIndex) {
529
+ this._goTo(idx);
530
+ }
531
+ },
532
+
533
+ /* ── Autoplay ── */
534
+ _startAutoplay() {
535
+ if (!this.data.autoplay) return;
536
+ this._stopAutoplay();
537
+ const items = this._getItems();
538
+ if (items.length <= 1) return;
539
+ this._autoplayTimer = setInterval(() => {
540
+ this._goTo(this._internalIndex + 1);
541
+ }, this.data.interval);
542
+ },
543
+
544
+ _stopAutoplay() {
545
+ if (this._autoplayTimer) {
546
+ clearInterval(this._autoplayTimer);
547
+ this._autoplayTimer = null;
548
+ }
549
+ },
550
+
551
+ /* ── Public methods ── */
552
+ to(index) {
553
+ this._goTo(index, true);
554
+ },
555
+
556
+ prev() {
557
+ this._goTo(this._internalIndex - 1, true);
558
+ },
559
+
560
+ next() {
561
+ this._goTo(this._internalIndex + 1, true);
562
+ },
563
+
564
+ getCurrentIndex() {
565
+ return this._internalIndex;
566
+ },
567
+ },
568
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "apply-shared"
4
+ }
@@ -0,0 +1,98 @@
1
+ .w-carousel {
2
+ position: relative;
3
+ overflow: hidden;
4
+ width: 100%;
5
+ height: 100%;
6
+ }
7
+
8
+ .w-carousel__track {
9
+ display: flex;
10
+ flex-direction: row;
11
+ height: 100%;
12
+ will-change: transform;
13
+ }
14
+
15
+ .w-carousel--vertical .w-carousel__track {
16
+ flex-direction: column;
17
+ width: 100%;
18
+ }
19
+
20
+ .w-carousel__slides {
21
+ position: relative;
22
+ width: 100%;
23
+ height: 100%;
24
+ }
25
+
26
+ /* Dots container */
27
+ .w-carousel__dots {
28
+ position: absolute;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ gap: 8rpx;
33
+ z-index: 10;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .w-carousel__dots--bottom {
38
+ bottom: 24rpx;
39
+ left: 0;
40
+ right: 0;
41
+ flex-direction: row;
42
+ }
43
+
44
+ .w-carousel__dots--top {
45
+ top: 24rpx;
46
+ left: 0;
47
+ right: 0;
48
+ flex-direction: row;
49
+ }
50
+
51
+ .w-carousel__dots--left {
52
+ left: 24rpx;
53
+ top: 0;
54
+ bottom: 0;
55
+ flex-direction: column;
56
+ }
57
+
58
+ .w-carousel__dots--right {
59
+ right: 24rpx;
60
+ top: 0;
61
+ bottom: 0;
62
+ flex-direction: column;
63
+ }
64
+
65
+ /* Dot 圆点 */
66
+ .w-carousel__dot {
67
+ width: var(--w-carousel-dot-size);
68
+ height: var(--w-carousel-dot-size);
69
+ border-radius: 9999rpx;
70
+ background: var(--w-carousel-dot-color);
71
+ transition:
72
+ transform 300ms,
73
+ background 300ms;
74
+ flex-shrink: 0;
75
+ }
76
+
77
+ .w-carousel__dot--active {
78
+ background: var(--w-carousel-dot-color-active);
79
+ transform: scale(1.25);
80
+ }
81
+
82
+ /* Line 线条 */
83
+ .w-carousel__dot--line {
84
+ width: var(--w-carousel-dot-line-width);
85
+ height: 8rpx;
86
+ border-radius: 4rpx;
87
+ background: var(--w-carousel-dot-color);
88
+ transition:
89
+ width 300ms,
90
+ background 300ms;
91
+ transform: none;
92
+ }
93
+
94
+ .w-carousel__dot--line.w-carousel__dot--active {
95
+ width: var(--w-carousel-dot-line-width-active);
96
+ background: var(--w-carousel-dot-color-active);
97
+ transform: none;
98
+ }
@@ -0,0 +1,33 @@
1
+ <view
2
+ class="w-carousel w-carousel--{{effect}} w-carousel--{{direction}} w-carousel--dots-{{dotPlacement}}"
3
+ >
4
+ <view
5
+ wx:if="{{effect === 'slide'}}"
6
+ class="w-carousel__track"
7
+ style="{{_trackStyle}}"
8
+ bindtouchstart="onTouchStart"
9
+ bindtouchmove="onTouchMove"
10
+ bindtouchend="onTouchEnd"
11
+ >
12
+ <slot />
13
+ </view>
14
+ <view
15
+ wx:else
16
+ class="w-carousel__slides"
17
+ bindtouchstart="onTouchStart"
18
+ bindtouchmove="onTouchMove"
19
+ bindtouchend="onTouchEnd"
20
+ >
21
+ <slot />
22
+ </view>
23
+ <view
24
+ wx:if="{{showDots && _total > 1}}"
25
+ class="w-carousel__dots w-carousel__dots--{{dotPlacement}}"
26
+ >
27
+ <view
28
+ wx:for="{{_dots}}"
29
+ wx:key="index"
30
+ class="w-carousel__dot {{item.active ? 'w-carousel__dot--active' : ''}} {{dotType === 'line' ? 'w-carousel__dot--line' : ''}}"
31
+ />
32
+ </view>
33
+ </view>
@@ -0,0 +1,25 @@
1
+ Component({
2
+ relations: {
3
+ '../carousel/carousel': {
4
+ type: 'ancestor',
5
+ },
6
+ },
7
+
8
+ data: {
9
+ _style: '',
10
+ _state: 'hidden',
11
+ },
12
+
13
+ methods: {
14
+ _setState(state, style) {
15
+ this.setData({ _state: state, _style: style });
16
+ },
17
+
18
+ onTap() {
19
+ const parents = this.getRelationNodes('../carousel/carousel');
20
+ if (parents && parents.length) {
21
+ parents[0]._onItemTap(this);
22
+ }
23
+ },
24
+ },
25
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "apply-shared"
4
+ }
@@ -0,0 +1,6 @@
1
+ .w-carousel-item {
2
+ flex-shrink: 0;
3
+ position: relative;
4
+ overflow: hidden;
5
+ box-sizing: border-box;
6
+ }
@@ -0,0 +1,3 @@
1
+ <view class="w-carousel-item w-carousel-item--{{_state}}" style="{{_style}}" bindtap="onTap"
2
+ ><slot
3
+ /></view>
@@ -0,0 +1,57 @@
1
+ Component({
2
+ properties: {
3
+ price: {
4
+ type: null,
5
+ value: 0,
6
+ },
7
+ originalPrice: {
8
+ type: null,
9
+ value: '',
10
+ },
11
+ currency: {
12
+ type: String,
13
+ value: '¥',
14
+ },
15
+ decimal: {
16
+ type: Number,
17
+ value: 2,
18
+ },
19
+ size: {
20
+ type: String,
21
+ value: 'md',
22
+ },
23
+ color: {
24
+ type: String,
25
+ value: '',
26
+ },
27
+ },
28
+
29
+ data: {
30
+ _formattedPrice: '0.00',
31
+ _formattedOriginal: '',
32
+ _hasOriginal: false,
33
+ _style: '',
34
+ },
35
+
36
+ observers: {
37
+ color(color) {
38
+ this.setData({ _style: color ? `--w-price-color: ${color}` : '' });
39
+ },
40
+ 'price, originalPrice, decimal': function (price, originalPrice, decimal) {
41
+ const decimals = Math.max(0, Math.floor(decimal));
42
+ const numPrice = Number(price);
43
+ const formattedPrice = isNaN(numPrice) ? '0' : numPrice.toFixed(decimals);
44
+ const hasOriginal =
45
+ originalPrice !== '' && originalPrice !== undefined && originalPrice !== null;
46
+ const numOriginal = hasOriginal ? Number(originalPrice) : NaN;
47
+ const formattedOriginal =
48
+ hasOriginal && !isNaN(numOriginal) ? numOriginal.toFixed(decimals) : '';
49
+
50
+ this.setData({
51
+ _formattedPrice: formattedPrice,
52
+ _formattedOriginal: formattedOriginal,
53
+ _hasOriginal: hasOriginal,
54
+ });
55
+ },
56
+ },
57
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "apply-shared"
4
+ }
@@ -0,0 +1,51 @@
1
+ .w-price {
2
+ display: inline-flex;
3
+ align-items: baseline;
4
+ line-height: 1;
5
+ }
6
+
7
+ /* Size variants — 主价格字号 */
8
+ .w-price--sm .w-price__value {
9
+ font-size: var(--font-size-lg);
10
+ }
11
+
12
+ .w-price--sm .w-price__currency {
13
+ font-size: var(--font-size-sm);
14
+ }
15
+
16
+ .w-price--md .w-price__value {
17
+ font-size: var(--font-size-xl);
18
+ }
19
+
20
+ .w-price--md .w-price__currency {
21
+ font-size: var(--font-size-md);
22
+ }
23
+
24
+ .w-price--lg .w-price__value {
25
+ font-size: var(--font-size-h3);
26
+ }
27
+
28
+ .w-price--lg .w-price__currency {
29
+ font-size: var(--font-size-xl);
30
+ }
31
+
32
+ .w-price__currency {
33
+ font-weight: 700;
34
+ color: var(--w-price-color);
35
+ vertical-align: super;
36
+ line-height: 1;
37
+ }
38
+
39
+ .w-price__value {
40
+ font-weight: 700;
41
+ color: var(--w-price-color);
42
+ font-variant-numeric: tabular-nums;
43
+ }
44
+
45
+ .w-price__original {
46
+ font-size: var(--font-size-sm);
47
+ color: var(--w-price-original-color);
48
+ text-decoration: line-through;
49
+ margin-left: 8rpx;
50
+ font-weight: 400;
51
+ }
@@ -0,0 +1,7 @@
1
+ <view class="w-price w-price--{{size}}" style="{{_style}}">
2
+ <text class="w-price__currency">{{currency}}</text
3
+ ><text class="w-price__value">{{_formattedPrice}}</text
4
+ ><text wx:if="{{_hasOriginal}}" class="w-price__original"
5
+ >{{currency}}{{_formattedOriginal}}</text
6
+ >
7
+ </view>
@@ -1,12 +1,13 @@
1
1
  @font-face {
2
- font-family: "iconfont"; /* Project id 5134551 */
3
- src:
4
- url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
5
- url('//at.alicdn.com/t/c/font_5134551_zr54w3qnk2m.ttf?t=1775402273099') format('truetype');
2
+ font-family: 'iconfont'; /* Project id 5134551 */
3
+ src:
4
+ url('data:application/x-font-woff;charset=utf-8;base64,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')
5
+ format('woff'),
6
+ url('//at.alicdn.com/t/c/font_5134551_zr54w3qnk2m.ttf?t=1775402273099') format('truetype');
6
7
  }
7
8
 
8
9
  .iconfont {
9
- font-family: "iconfont" !important;
10
+ font-family: 'iconfont' !important;
10
11
  font-size: 16px;
11
12
  font-style: normal;
12
13
  -webkit-font-smoothing: antialiased;
@@ -14,81 +15,81 @@
14
15
  }
15
16
 
16
17
  .icon-empty:before {
17
- content: "\e6a6";
18
+ content: '\e6a6';
18
19
  }
19
20
 
20
21
  .icon-minus:before {
21
- content: "\e7fd";
22
+ content: '\e7fd';
22
23
  }
23
24
 
24
25
  .icon-plus:before {
25
- content: "\e8fe";
26
+ content: '\e8fe';
26
27
  }
27
28
 
28
29
  .icon-home:before {
29
- content: "\e6d7";
30
+ content: '\e6d7';
30
31
  }
31
32
 
32
33
  .icon-computer-border:before {
33
- content: "\e622";
34
+ content: '\e622';
34
35
  }
35
36
 
36
37
  .icon-g_my:before {
37
- content: "\e61a";
38
+ content: '\e61a';
38
39
  }
39
40
 
40
41
  .icon-diandan01:before {
41
- content: "\e606";
42
+ content: '\e606';
42
43
  }
43
44
 
44
45
  .icon-close:before {
45
- content: "\e60f";
46
+ content: '\e60f';
46
47
  }
47
48
 
48
49
  .icon-play_fill:before {
49
- content: "\e717";
50
+ content: '\e717';
50
51
  }
51
52
 
52
53
  .icon-calendar:before {
53
- content: "\e699";
54
+ content: '\e699';
54
55
  }
55
56
 
56
57
  .icon-retry:before {
57
- content: "\e601";
58
+ content: '\e601';
58
59
  }
59
60
 
60
61
  .icon-left:before {
61
- content: "\e83d";
62
+ content: '\e83d';
62
63
  }
63
64
 
64
65
  .icon-file:before {
65
- content: "\e803";
66
+ content: '\e803';
66
67
  }
67
68
 
68
69
  .icon-top:before {
69
- content: "\e681";
70
+ content: '\e681';
70
71
  }
71
72
 
72
73
  .icon-clock1:before {
73
- content: "\e66a";
74
+ content: '\e66a';
74
75
  }
75
76
 
76
77
  .icon-sort:before {
77
- content: "\e83c";
78
+ content: '\e83c';
78
79
  }
79
80
 
80
81
  .icon-add:before {
81
- content: "\e835";
82
+ content: '\e835';
82
83
  }
83
84
 
84
85
  .icon-up:before {
85
- content: "\e845";
86
+ content: '\e845';
86
87
  }
87
88
 
88
89
  .icon-right:before {
89
- content: "\e840";
90
+ content: '\e840';
90
91
  }
91
92
 
92
93
  .icon-down:before {
93
- content: "\e839";
94
+ content: '\e839';
94
95
  }
@@ -300,4 +300,16 @@ page {
300
300
  --w-empty-image-size-md: 160rpx;
301
301
  --w-empty-image-size-lg: 240rpx;
302
302
  --w-empty-image-size: var(--w-empty-image-size-md);
303
+
304
+ /* Price */
305
+ --w-price-color: var(--color-brand);
306
+ --w-price-original-color: var(--color-text-secondary);
307
+
308
+ /* Carousel */
309
+ --w-carousel-dot-color: rgba(255, 255, 255, 0.5);
310
+ --w-carousel-dot-color-active: #ffffff;
311
+ --w-carousel-dot-size: 16rpx;
312
+ --w-carousel-dot-line-width: 16rpx;
313
+ --w-carousel-dot-line-width-active: 48rpx;
314
+ --w-carousel-card-radius: var(--w-radius-lg, 16rpx);
303
315
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 拖动跟踪工具
3
+ * 封装 touch 事件的追踪、delta 计算、滑动判定
4
+ */
5
+ function createDrag() {
6
+ let startX = 0;
7
+ let startY = 0;
8
+ let startTime = 0;
9
+ let active = false;
10
+
11
+ return {
12
+ /** 记录起点 */
13
+ start(touch) {
14
+ active = true;
15
+ startX = touch.clientX;
16
+ startY = touch.clientY;
17
+ startTime = Date.now();
18
+ },
19
+
20
+ /** 计算拖动位移(px) */
21
+ delta(touch, direction) {
22
+ const dx = touch.clientX - startX;
23
+ const dy = touch.clientY - startY;
24
+ return direction === 'horizontal' ? dx : dy;
25
+ },
26
+
27
+ /**
28
+ * 计算拖动进度(-1 ~ 1),正值 = 向 next 拖动
29
+ * @param {Object} touch
30
+ * @param {string} direction
31
+ * @param {number} containerSize
32
+ */
33
+ progress(touch, direction, containerSize) {
34
+ if (!containerSize) return 0;
35
+ const d = this.delta(touch, direction);
36
+ return Math.max(-1, Math.min(1, -d / containerSize));
37
+ },
38
+
39
+ /**
40
+ * 结束拖动,返回滑动判定
41
+ * @returns {{ delta: number, velocity: number, swipe: number } | null}
42
+ * swipe: 1 = next, -1 = prev, 0 = 回弹
43
+ */
44
+ end(touch, direction, containerSize) {
45
+ if (!active) return null;
46
+ active = false;
47
+ const delta = this.delta(touch, direction);
48
+ const elapsed = Date.now() - startTime;
49
+ const velocity = elapsed > 0 ? delta / elapsed : 0;
50
+
51
+ let swipe = 0;
52
+ if (delta < -containerSize * 0.3 || velocity < -0.4) {
53
+ swipe = 1;
54
+ } else if (delta > containerSize * 0.3 || velocity > 0.4) {
55
+ swipe = -1;
56
+ }
57
+
58
+ return { delta, velocity, swipe };
59
+ },
60
+
61
+ isActive() {
62
+ return active;
63
+ },
64
+
65
+ cancel() {
66
+ active = false;
67
+ },
68
+ };
69
+ }
70
+
71
+ module.exports = { createDrag };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wedux-ui",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "微信小程序组件库,支持主题切换",
5
5
  "miniprogram": "miniprogram_dist",
6
6
  "files": [