wedux-ui 0.5.0 → 0.6.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.
- package/miniprogram_dist/components/carousel/carousel.js +130 -75
- package/miniprogram_dist/components/image/image.js +95 -0
- package/miniprogram_dist/components/image/image.json +4 -0
- package/miniprogram_dist/components/image/image.scss +104 -0
- package/miniprogram_dist/components/image/image.wxml +35 -0
- package/miniprogram_dist/styles/iconfont.scss +6 -2
- package/miniprogram_dist/styles/tokens.scss +7 -0
- package/package.json +1 -1
|
@@ -94,7 +94,8 @@ Component({
|
|
|
94
94
|
this._slideSize = 0;
|
|
95
95
|
this._autoplayTimer = null;
|
|
96
96
|
this._loopResetTimer = null;
|
|
97
|
-
this.
|
|
97
|
+
this._boundaryItemIndices = [];
|
|
98
|
+
this._boundaryPrepared = false;
|
|
98
99
|
|
|
99
100
|
wx.nextTick(() => {
|
|
100
101
|
this._measure();
|
|
@@ -119,7 +120,8 @@ Component({
|
|
|
119
120
|
_onChildrenChange() {
|
|
120
121
|
const items = this._getItems();
|
|
121
122
|
const total = items.length;
|
|
122
|
-
|
|
123
|
+
const maxIndex = Math.max(0, this._getPageCount(total) - 1);
|
|
124
|
+
this._internalIndex = Math.min(this._internalIndex || 0, maxIndex);
|
|
123
125
|
this._rebuildDots();
|
|
124
126
|
if (this._slideSize > 0) {
|
|
125
127
|
this._updateChildSizes();
|
|
@@ -127,6 +129,14 @@ Component({
|
|
|
127
129
|
}
|
|
128
130
|
},
|
|
129
131
|
|
|
132
|
+
_getPageCount(total) {
|
|
133
|
+
const spv = Math.floor(this.data.slidesPerView);
|
|
134
|
+
if (this.data.effect === 'slide' && spv > 1) {
|
|
135
|
+
return Math.max(1, total - spv + 1);
|
|
136
|
+
}
|
|
137
|
+
return total;
|
|
138
|
+
},
|
|
139
|
+
|
|
130
140
|
/* ── Measure ── */
|
|
131
141
|
_measure() {
|
|
132
142
|
const query = this.createSelectorQuery();
|
|
@@ -189,27 +199,22 @@ Component({
|
|
|
189
199
|
this._updateChildSizes();
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
const pageCount = this._getPageCount(total);
|
|
192
203
|
let newIndex = index;
|
|
193
204
|
|
|
194
205
|
if (this.data.loop) {
|
|
195
|
-
newIndex = ((index %
|
|
206
|
+
newIndex = ((index % pageCount) + pageCount) % pageCount;
|
|
196
207
|
} else {
|
|
197
|
-
newIndex = Math.max(0, Math.min(index,
|
|
208
|
+
newIndex = Math.max(0, Math.min(index, pageCount - 1));
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
const prev = this._internalIndex;
|
|
201
212
|
this._internalIndex = newIndex;
|
|
202
213
|
|
|
203
214
|
// slide + loop 边界过渡:无缝动画而非瞬移
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
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;
|
|
215
|
+
if (this.data.loop && this.data.effect === 'slide' && animate && pageCount > 1) {
|
|
216
|
+
const isForwardLoop = prev === pageCount - 1 && newIndex === 0;
|
|
217
|
+
const isBackwardLoop = prev === 0 && newIndex === pageCount - 1;
|
|
213
218
|
|
|
214
219
|
if (isForwardLoop || isBackwardLoop) {
|
|
215
220
|
this._slideLoopTransition(prev, newIndex, isForwardLoop);
|
|
@@ -221,9 +226,9 @@ Component({
|
|
|
221
226
|
}
|
|
222
227
|
}
|
|
223
228
|
|
|
224
|
-
// 非边界过渡,清理拖拽时预置的 boundary
|
|
225
|
-
if (this.
|
|
226
|
-
this.
|
|
229
|
+
// 非边界过渡,清理拖拽时预置的 boundary items
|
|
230
|
+
if (this._boundaryItemIndices && this._boundaryItemIndices.length > 0) {
|
|
231
|
+
this._boundaryItemIndices = [];
|
|
227
232
|
if (animate) {
|
|
228
233
|
this._loopResetTimer = setTimeout(() => {
|
|
229
234
|
this._loopResetTimer = null;
|
|
@@ -312,78 +317,111 @@ Component({
|
|
|
312
317
|
const items = this._getItems();
|
|
313
318
|
const total = items.length;
|
|
314
319
|
const { direction, spaceBetween, duration } = this.data;
|
|
320
|
+
const spv = Math.max(1, Math.floor(this.data.slidesPerView));
|
|
315
321
|
const step = this._slideSize + spaceBetween;
|
|
316
322
|
const isH = direction === 'horizontal';
|
|
317
323
|
const axis = isH ? 'X' : 'Y';
|
|
318
324
|
const sizeStyle = this._getItemSizeStyle();
|
|
325
|
+
// 需要克隆的边界 item 数:满足视窗填充,同时避免与当前可见区域重叠
|
|
326
|
+
const cloneCount = Math.max(1, Math.min(spv, total - spv));
|
|
327
|
+
|
|
328
|
+
this._boundaryItemIndices = [];
|
|
329
|
+
|
|
330
|
+
// 边界 item 的克隆定位(子组件 setData)与 track 过渡启动(父组件 setData)
|
|
331
|
+
// 必须在同一渲染帧落地,否则 track 动画先跑、item 还没就位,
|
|
332
|
+
// 视窗扫过空白区域会表现为"某一页慢慢加载出来"的闪烁。
|
|
333
|
+
// 用 groupSetData 强制跨组件渲染同步。
|
|
334
|
+
this.groupSetData(() => {
|
|
335
|
+
if (isForward) {
|
|
336
|
+
// 最后页→第一页:把 items[0..cloneCount-1] 移到最后一张后面
|
|
337
|
+
for (let i = 0; i < cloneCount; i++) {
|
|
338
|
+
items[i]._setState(
|
|
339
|
+
'active',
|
|
340
|
+
`${sizeStyle} transform: translate${axis}(${total * step}px);`,
|
|
341
|
+
);
|
|
342
|
+
this._boundaryItemIndices.push(i);
|
|
343
|
+
}
|
|
344
|
+
// track 向前滑动 cloneCount 格
|
|
345
|
+
const offset = (fromIndex + cloneCount) * step;
|
|
346
|
+
const transform = isH ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
|
|
347
|
+
this.setData({
|
|
348
|
+
_trackStyle: `transform: ${transform}; transition: transform ${duration}ms ease;`,
|
|
349
|
+
});
|
|
350
|
+
} else {
|
|
351
|
+
// 第一页→最后页:把 items[total-cloneCount..total-1] 移到第一张前面
|
|
352
|
+
for (let i = 0; i < cloneCount; i++) {
|
|
353
|
+
const idx = total - cloneCount + i;
|
|
354
|
+
items[idx]._setState(
|
|
355
|
+
'active',
|
|
356
|
+
`${sizeStyle} transform: translate${axis}(${-total * step}px);`,
|
|
357
|
+
);
|
|
358
|
+
this._boundaryItemIndices.push(idx);
|
|
359
|
+
}
|
|
360
|
+
// track 向后滑动 cloneCount 格
|
|
361
|
+
const offset = -cloneCount * step;
|
|
362
|
+
const transform = isH ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
|
|
363
|
+
this.setData({
|
|
364
|
+
_trackStyle: `transform: ${transform}; transition: transform ${duration}ms ease;`,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
});
|
|
319
368
|
|
|
320
|
-
|
|
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
|
|
369
|
+
// 动画结束后复位所有 item 和 track —— 同样需要跨组件同步,
|
|
370
|
+
// 否则会出现"item 已复位但 track 还停在 cloneCount 偏移"或反之的一帧错位
|
|
349
371
|
this._loopResetTimer = setTimeout(() => {
|
|
350
372
|
this._loopResetTimer = null;
|
|
351
|
-
this.
|
|
352
|
-
|
|
373
|
+
this.groupSetData(() => {
|
|
374
|
+
this._updateChildSizes();
|
|
375
|
+
this._applyEffect(false);
|
|
376
|
+
});
|
|
353
377
|
}, duration + 20);
|
|
354
378
|
},
|
|
355
379
|
|
|
356
380
|
/**
|
|
357
|
-
*
|
|
381
|
+
* 按滑动方向预置边界 item,使跟手拖动时能看到下一张
|
|
382
|
+
* @param {'forward'|'backward'} swipeDir
|
|
383
|
+
* forward: 向后(下一页,手指左移),backward: 向前(上一页,手指右移)
|
|
358
384
|
*/
|
|
359
|
-
_prepareBoundarySlide() {
|
|
385
|
+
_prepareBoundarySlide(swipeDir) {
|
|
360
386
|
const items = this._getItems();
|
|
361
387
|
const total = items.length;
|
|
362
|
-
|
|
388
|
+
const spv = Math.max(1, Math.floor(this.data.slidesPerView));
|
|
389
|
+
const pageCount = this._getPageCount(total);
|
|
390
|
+
if (pageCount <= 1) return;
|
|
363
391
|
|
|
364
392
|
const cur = this._internalIndex;
|
|
393
|
+
// 只在边界处且方向匹配时才需要预置,否则 item 都在自然位置,直接可见
|
|
394
|
+
if (swipeDir === 'backward' && cur !== 0) return;
|
|
395
|
+
if (swipeDir === 'forward' && cur !== pageCount - 1) return;
|
|
396
|
+
|
|
365
397
|
const { direction, spaceBetween } = this.data;
|
|
366
398
|
const step = this._slideSize + spaceBetween;
|
|
367
399
|
const isH = direction === 'horizontal';
|
|
368
400
|
const axis = isH ? 'X' : 'Y';
|
|
369
401
|
const sizeStyle = this._getItemSizeStyle();
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
402
|
+
const cloneCount = Math.max(1, Math.min(spv, total - spv));
|
|
403
|
+
|
|
404
|
+
this._boundaryItemIndices = [];
|
|
405
|
+
|
|
406
|
+
if (swipeDir === 'backward') {
|
|
407
|
+
// 手指右移(回到最后页)→ 把末尾 cloneCount 张移到第一张前面
|
|
408
|
+
for (let i = 0; i < cloneCount; i++) {
|
|
409
|
+
const idx = total - cloneCount + i;
|
|
410
|
+
items[idx]._setState(
|
|
411
|
+
'active',
|
|
412
|
+
`${sizeStyle} transform: translate${axis}(${-total * step}px);`,
|
|
413
|
+
);
|
|
414
|
+
this._boundaryItemIndices.push(idx);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
// 手指左移(到第一页)→ 把开头 cloneCount 张移到最后一张后面
|
|
418
|
+
for (let i = 0; i < cloneCount; i++) {
|
|
419
|
+
items[i]._setState(
|
|
420
|
+
'active',
|
|
421
|
+
`${sizeStyle} transform: translate${axis}(${total * step}px);`,
|
|
422
|
+
);
|
|
423
|
+
this._boundaryItemIndices.push(i);
|
|
424
|
+
}
|
|
387
425
|
}
|
|
388
426
|
},
|
|
389
427
|
|
|
@@ -394,23 +432,25 @@ Component({
|
|
|
394
432
|
if (this._loopResetTimer) {
|
|
395
433
|
clearTimeout(this._loopResetTimer);
|
|
396
434
|
this._loopResetTimer = null;
|
|
397
|
-
|
|
398
|
-
this.
|
|
435
|
+
// item 复位和 track 复位必须同帧渲染,避免一帧错位
|
|
436
|
+
this.groupSetData(() => {
|
|
437
|
+
this._updateChildSizes();
|
|
438
|
+
this._applyEffect(false);
|
|
439
|
+
});
|
|
399
440
|
}
|
|
400
441
|
|
|
401
442
|
this._drag.start(e.touches[0]);
|
|
402
443
|
if (this.data.effect === 'slide') {
|
|
403
444
|
this._startTranslate = this._internalIndex * (this._slideSize + this.data.spaceBetween);
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
445
|
+
// 边界预置推迟到 onTouchMove 首帧检测方向后再执行,
|
|
446
|
+
// 避免在用户实际滑动方向未知时提前移动可见 item
|
|
447
|
+
this._boundaryPrepared = false;
|
|
408
448
|
}
|
|
409
449
|
},
|
|
410
450
|
|
|
411
451
|
onTouchMove(e) {
|
|
412
452
|
if (!this._drag.isActive()) return;
|
|
413
|
-
const { effect, direction } = this.data;
|
|
453
|
+
const { effect, direction, loop } = this.data;
|
|
414
454
|
const touch = e.touches[0];
|
|
415
455
|
|
|
416
456
|
if (effect === 'slide') {
|
|
@@ -418,7 +458,22 @@ Component({
|
|
|
418
458
|
const offset = this._startTranslate - delta;
|
|
419
459
|
const transform =
|
|
420
460
|
direction === 'horizontal' ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
|
|
421
|
-
|
|
461
|
+
const trackStyle = `transform: ${transform}; transition: none;`;
|
|
462
|
+
|
|
463
|
+
// 首帧检测滑动方向:仅在边界处按实际方向预置 item,
|
|
464
|
+
// 与当前帧 track offset 在同一渲染帧生效,避免提前错误移动可见 item
|
|
465
|
+
if (loop && !this._boundaryPrepared) {
|
|
466
|
+
this._boundaryPrepared = true;
|
|
467
|
+
// delta > 0: 手指右移 = 向前(上一页),delta < 0: 左移 = 向后(下一页)
|
|
468
|
+
const swipeDir = delta > 0 ? 'backward' : 'forward';
|
|
469
|
+
this.groupSetData(() => {
|
|
470
|
+
this._prepareBoundarySlide(swipeDir);
|
|
471
|
+
this.setData({ _trackStyle: trackStyle });
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.setData({ _trackStyle: trackStyle });
|
|
422
477
|
} else {
|
|
423
478
|
const containerSize =
|
|
424
479
|
direction === 'horizontal' ? this._containerWidth : this._containerHeight;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Component({
|
|
2
|
+
options: {
|
|
3
|
+
multipleSlots: true,
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
properties: {
|
|
7
|
+
src: {
|
|
8
|
+
type: String,
|
|
9
|
+
value: '',
|
|
10
|
+
},
|
|
11
|
+
mode: {
|
|
12
|
+
type: String,
|
|
13
|
+
value: 'aspectFill', // scaleToFill | aspectFit | aspectFill | widthFix | heightFix | ...
|
|
14
|
+
},
|
|
15
|
+
width: {
|
|
16
|
+
type: String,
|
|
17
|
+
value: '',
|
|
18
|
+
},
|
|
19
|
+
height: {
|
|
20
|
+
type: String,
|
|
21
|
+
value: '',
|
|
22
|
+
},
|
|
23
|
+
radius: {
|
|
24
|
+
type: String,
|
|
25
|
+
value: '',
|
|
26
|
+
},
|
|
27
|
+
round: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
value: false,
|
|
30
|
+
},
|
|
31
|
+
showLoading: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
value: true,
|
|
34
|
+
},
|
|
35
|
+
showError: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
value: true,
|
|
38
|
+
},
|
|
39
|
+
fade: {
|
|
40
|
+
type: Boolean,
|
|
41
|
+
value: true,
|
|
42
|
+
},
|
|
43
|
+
lazyLoad: {
|
|
44
|
+
type: Boolean,
|
|
45
|
+
value: false,
|
|
46
|
+
},
|
|
47
|
+
fallbackSrc: {
|
|
48
|
+
type: String,
|
|
49
|
+
value: '',
|
|
50
|
+
},
|
|
51
|
+
showMenuByLongpress: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
value: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
data: {
|
|
58
|
+
_status: 'loading', // loading | loaded | error
|
|
59
|
+
_currentSrc: '',
|
|
60
|
+
_containerStyle: '',
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
observers: {
|
|
64
|
+
src(src) {
|
|
65
|
+
this.setData({
|
|
66
|
+
_currentSrc: src,
|
|
67
|
+
_status: src ? 'loading' : 'error',
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
'width, height, radius'(width, height, radius) {
|
|
71
|
+
const parts = [];
|
|
72
|
+
if (width) parts.push(`width: ${width}`);
|
|
73
|
+
if (height) parts.push(`height: ${height}`);
|
|
74
|
+
if (radius) parts.push(`border-radius: ${radius}`);
|
|
75
|
+
this.setData({ _containerStyle: parts.join('; ') });
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
methods: {
|
|
80
|
+
handleLoad(e) {
|
|
81
|
+
this.setData({ _status: 'loaded' });
|
|
82
|
+
this.triggerEvent('load', e.detail);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
handleError(e) {
|
|
86
|
+
const { fallbackSrc, _currentSrc } = this.data;
|
|
87
|
+
if (fallbackSrc && _currentSrc !== fallbackSrc) {
|
|
88
|
+
this.setData({ _currentSrc: fallbackSrc });
|
|
89
|
+
} else {
|
|
90
|
+
this.setData({ _status: 'error' });
|
|
91
|
+
}
|
|
92
|
+
this.triggerEvent('error', e.detail);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
@import '../../styles/iconfont.scss';
|
|
2
|
+
|
|
3
|
+
.w-image {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: inline-block;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
border-radius: var(--w-image-radius, 0);
|
|
8
|
+
background-color: var(--w-image-bg, var(--color-bg-base));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.w-image--round {
|
|
12
|
+
border-radius: var(--radius-full);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Image */
|
|
16
|
+
.w-image__img {
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.w-image--fade-in .w-image__img {
|
|
23
|
+
animation: w-image-fade-in var(--w-image-fade-duration, 300ms) ease-out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes w-image-fade-in {
|
|
27
|
+
from {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
}
|
|
30
|
+
to {
|
|
31
|
+
opacity: 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Placeholder (loading & error) */
|
|
36
|
+
.w-image__placeholder {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Slot controls: hide default when slot has content */
|
|
48
|
+
.w-image__loading-slot:empty {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.w-image__loading-slot:empty + .w-image__loading-default {
|
|
53
|
+
display: flex;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.w-image__loading-slot:not(:empty) + .w-image__loading-default {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.w-image__error-slot:empty {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.w-image__error-slot:empty + .w-image__error-default {
|
|
65
|
+
display: flex;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.w-image__error-slot:not(:empty) + .w-image__error-default {
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Loading default: spinning animation */
|
|
73
|
+
.w-image__loading-default {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.w-image__loading-icon {
|
|
80
|
+
width: var(--w-image-loading-icon-size, 48rpx);
|
|
81
|
+
height: var(--w-image-loading-icon-size, 48rpx);
|
|
82
|
+
border: 4rpx solid var(--color-separator);
|
|
83
|
+
border-top-color: var(--color-text-secondary);
|
|
84
|
+
border-radius: 50%;
|
|
85
|
+
animation: w-image-spin 0.8s linear infinite;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes w-image-spin {
|
|
89
|
+
to {
|
|
90
|
+
transform: rotate(360deg);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Error default */
|
|
95
|
+
.w-image__error-default {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.w-image__error-icon {
|
|
102
|
+
font-size: var(--w-image-error-icon-size, 48rpx);
|
|
103
|
+
color: var(--color-text-placeholder);
|
|
104
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<view
|
|
2
|
+
class="w-image {{round ? 'w-image--round' : ''}} {{_status === 'loaded' && fade ? 'w-image--fade-in' : ''}}"
|
|
3
|
+
style="{{_containerStyle}}"
|
|
4
|
+
>
|
|
5
|
+
<image
|
|
6
|
+
wx:if="{{_currentSrc && _status !== 'error'}}"
|
|
7
|
+
class="w-image__img"
|
|
8
|
+
src="{{_currentSrc}}"
|
|
9
|
+
mode="{{mode}}"
|
|
10
|
+
lazy-load="{{lazyLoad}}"
|
|
11
|
+
show-menu-by-longpress="{{showMenuByLongpress}}"
|
|
12
|
+
bindload="handleLoad"
|
|
13
|
+
binderror="handleError"
|
|
14
|
+
></image>
|
|
15
|
+
|
|
16
|
+
<!-- Loading -->
|
|
17
|
+
<view wx:if="{{_status === 'loading' && showLoading}}" class="w-image__placeholder">
|
|
18
|
+
<view class="w-image__loading-slot">
|
|
19
|
+
<slot name="loading"></slot>
|
|
20
|
+
</view>
|
|
21
|
+
<view class="w-image__loading-default">
|
|
22
|
+
<view class="w-image__loading-icon"></view>
|
|
23
|
+
</view>
|
|
24
|
+
</view>
|
|
25
|
+
|
|
26
|
+
<!-- Error -->
|
|
27
|
+
<view wx:if="{{_status === 'error' && showError}}" class="w-image__placeholder">
|
|
28
|
+
<view class="w-image__error-slot">
|
|
29
|
+
<slot name="error"></slot>
|
|
30
|
+
</view>
|
|
31
|
+
<view class="w-image__error-default">
|
|
32
|
+
<view class="w-image__error-icon iconfont icon-broken-image"></view>
|
|
33
|
+
</view>
|
|
34
|
+
</view>
|
|
35
|
+
</view>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
@font-face {
|
|
2
2
|
font-family: 'iconfont'; /* Project id 5134551 */
|
|
3
3
|
src:
|
|
4
|
-
url('data:application/x-font-woff;charset=utf-8;base64,
|
|
4
|
+
url('data:application/x-font-woff;charset=utf-8;base64,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')
|
|
5
5
|
format('woff'),
|
|
6
|
-
url('//at.alicdn.com/t/c/
|
|
6
|
+
url('//at.alicdn.com/t/c/font_5134551_7oyrdzd7ntn.ttf?t=1775471224272') format('truetype');
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.iconfont {
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
-moz-osx-font-smoothing: grayscale;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
.icon-broken-image:before {
|
|
18
|
+
content: '\e600';
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
.icon-empty:before {
|
|
18
22
|
content: '\e6a6';
|
|
19
23
|
}
|
|
@@ -305,6 +305,13 @@ page {
|
|
|
305
305
|
--w-price-color: var(--color-brand);
|
|
306
306
|
--w-price-original-color: var(--color-text-secondary);
|
|
307
307
|
|
|
308
|
+
/* Image */
|
|
309
|
+
--w-image-radius: 0;
|
|
310
|
+
--w-image-bg: var(--color-bg-base);
|
|
311
|
+
--w-image-fade-duration: 300ms;
|
|
312
|
+
--w-image-loading-icon-size: 48rpx;
|
|
313
|
+
--w-image-error-icon-size: 48rpx;
|
|
314
|
+
|
|
308
315
|
/* Carousel */
|
|
309
316
|
--w-carousel-dot-color: rgba(255, 255, 255, 0.5);
|
|
310
317
|
--w-carousel-dot-color-active: #ffffff;
|