@mpxjs/webpack-plugin 2.10.17-beta.7 → 2.10.17-beta.8

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,508 @@
1
+ <template>
2
+ <div class="mpx-recycle-view">
3
+ <ScrollView
4
+ ref="scrollView"
5
+ :enableSticky="enableSticky"
6
+ :scroll-y="true"
7
+ :enhanced="enhanced"
8
+ :scrollWithAnimation="scrollWithAnimation"
9
+ :refresherEnabled="refresherEnabled"
10
+ :refresherTriggered="refresherTriggered"
11
+ :scrollOptions="scrollOptions"
12
+ @scroll="onScroll"
13
+ @scrolltolower="onScrolltolower"
14
+ @refresherrefresh="onRefresherrefresh"
15
+ :style="scrollViewStyle"
16
+ >
17
+ <div class="content-wrapper">
18
+ <template v-if="useListHeader">
19
+ <list-header :listHeaderData="listHeaderData"></list-header>
20
+ </template>
21
+ <div class="infinite-list-placeholder" ref="infinitePlaceholder"></div>
22
+ <div class="infinite-list" ref="infiniteList">
23
+ <template v-for="item in visibleData">
24
+ <section-header
25
+ v-if="item.itemData.isSectionHeader"
26
+ :key="'header' + item._index"
27
+ :itemData="item.itemData"
28
+ />
29
+ <recycle-item
30
+ v-if="!item.itemData.isSectionHeader"
31
+ :key="'item' + item._index"
32
+ :itemData="item.itemData"
33
+ />
34
+ </template>
35
+ </div>
36
+ </div>
37
+ <template
38
+ v-if="
39
+ _stickyHeaders &&
40
+ _stickyHeaders.length &&
41
+ enableSticky &&
42
+ genericsectionHeader
43
+ "
44
+ >
45
+ <StickyHeader
46
+ v-for="stickyItem in _stickyHeaders"
47
+ :key="stickyItem._index"
48
+ class="sticky-section"
49
+ :style="{
50
+ top:
51
+ ((positions[stickyItem._index] &&
52
+ positions[stickyItem._index].top) ||
53
+ 0) + 'px',
54
+ }"
55
+ >
56
+ <section-header
57
+ :key="'header' + stickyItem._index"
58
+ :itemData="stickyItem.itemData"
59
+ />
60
+ </StickyHeader>
61
+ </template>
62
+ </ScrollView>
63
+ </div>
64
+ </template>
65
+
66
+ <script>
67
+ import ScrollView from "./mpx-scroll-view.vue";
68
+ import StickyHeader from "./mpx-sticky-header.vue";
69
+
70
+ export default {
71
+ props: {
72
+ width: String | Number,
73
+ height: String | Number,
74
+ listData: {
75
+ type: Array,
76
+ default: () => {
77
+ return []
78
+ }
79
+ },
80
+ scrollOptions: {
81
+ type: Object,
82
+ default: () => {
83
+ return {}
84
+ }
85
+ },
86
+ minRenderCount: {
87
+ type: Number,
88
+ default: 10,
89
+ },
90
+ bufferScale: {
91
+ type: Number,
92
+ default: 1,
93
+ },
94
+ itemHeight: {
95
+ type: Object,
96
+ default: () => {
97
+ return {}
98
+ }
99
+ },
100
+ listHeaderHeight: {
101
+ type: Object,
102
+ default: () => {
103
+ return {}
104
+ }
105
+ },
106
+ sectionHeaderHeight: {
107
+ type: Object,
108
+ default: () => {
109
+ return {}
110
+ }
111
+ },
112
+ listHeaderData: {
113
+ type: Object,
114
+ default: () => {
115
+ return {}
116
+ }
117
+ },
118
+ enhanced: {
119
+ type: Boolean,
120
+ default: false
121
+ },
122
+ refresherEnabled: {
123
+ type: Boolean,
124
+ default: false
125
+ },
126
+ refresherTriggered: {
127
+ type: Boolean,
128
+ default: false
129
+ },
130
+ enableSticky: {
131
+ type: Boolean,
132
+ default: false
133
+ },
134
+ scrollWithAnimation: {
135
+ type: Boolean,
136
+ default: false
137
+ },
138
+ useListHeader: {
139
+ type: Boolean,
140
+ default: true
141
+ },
142
+ generichash: String,
143
+ genericlistHeader: String,
144
+ genericrecycleItem: String,
145
+ genericsectionHeader: String
146
+ },
147
+ data() {
148
+ return {
149
+ start: 0,
150
+ end: 0,
151
+ containerHeight: 0,
152
+ positions: [],
153
+ visibleCounts: [],
154
+ };
155
+ },
156
+ computed: {
157
+ _listData() {
158
+ return this.listData.map((item, index) => {
159
+ return {
160
+ itemData: item,
161
+ _index: `_${index}`,
162
+ };
163
+ });
164
+ },
165
+ _stickyHeaders() {
166
+ const data = [];
167
+ this.listData.forEach((item, index) => {
168
+ if (item.isSectionHeader) {
169
+ data.push({
170
+ itemData: item,
171
+ _index: index,
172
+ });
173
+ }
174
+ });
175
+ return data;
176
+ },
177
+ scrollViewStyle() {
178
+ return `height: ${this.formatDimension(
179
+ this.height
180
+ )};width: ${this.formatDimension(this.width)}`;
181
+ },
182
+ visibleCount() {
183
+ if (!this.visibleCounts.length) return this.minRenderCount;
184
+ return Math.max(this.visibleCounts[this.start], this.minRenderCount);
185
+ },
186
+ aboveCount() {
187
+ if (!this._listData.length || !this.visibleCounts.length) return 0;
188
+ let count = 0;
189
+ const startIndex = Math.max(0, this.start);
190
+ const endIndex = Math.max(0, startIndex - this.bufferScale);
191
+
192
+ for (let i = startIndex; i > endIndex; i--) {
193
+ count += this.visibleCounts[i] || 0;
194
+ }
195
+
196
+ return count;
197
+ },
198
+ belowCount() {
199
+ if (!this._listData.length || !this.visibleCounts.length) return 0;
200
+ let count = 0;
201
+ const startIndex = Math.min(this.start, this._listData.length - 1);
202
+ const endIndex = Math.min(
203
+ startIndex + this.bufferScale,
204
+ this._listData.length - 1
205
+ );
206
+
207
+ for (let i = startIndex; i < endIndex; i++) {
208
+ count += this.visibleCounts[i] || 0;
209
+ }
210
+
211
+ return count;
212
+ },
213
+ visibleData() {
214
+ if (!this._listData.length) return [];
215
+
216
+ const start = Math.min(
217
+ Math.max(0, this.start - this.aboveCount),
218
+ this._listData.length - 1
219
+ );
220
+
221
+ let end = Math.min(
222
+ this._listData.length,
223
+ this.start + this.visibleCount + this.belowCount
224
+ );
225
+
226
+ // 如果接近列表末尾,确保显示所有剩余项目
227
+ if (end > this._listData.length - 3) {
228
+ end = this._listData.length;
229
+ }
230
+
231
+ return this._listData.slice(start, end).map((item, idx) => {
232
+ const realIndex = start + idx;
233
+ return {
234
+ ...item,
235
+ _index: `_${realIndex}`,
236
+ };
237
+ });
238
+ },
239
+ _listHeaderHeight() {
240
+ let listHeaderHeight = 0;
241
+ if (this.useListHeader) {
242
+ listHeaderHeight =
243
+ this.getItemHeight(this.listHeaderData, 0, "listHeaderHeight") || 0;
244
+ }
245
+ return listHeaderHeight;
246
+ },
247
+ placeholderHeight() {
248
+ if (!this.positions.length) return 0;
249
+ return (
250
+ this.positions[this.positions.length - 1].bottom -
251
+ this._listHeaderHeight || 0
252
+ );
253
+ },
254
+ },
255
+ watch: {
256
+ listData: {
257
+ handler() {
258
+ this.initPositions();
259
+ this.setPlaceholderStyle();
260
+ // 更新真实偏移量
261
+ this.setStartOffset();
262
+ },
263
+ },
264
+ containerHeight() {
265
+ this.calculateVisibleCounts();
266
+ },
267
+ },
268
+ created() {
269
+ this.registerGenericComponents();
270
+ },
271
+ mounted() {
272
+ this.initPositions();
273
+ this.containerHeight = this.$refs.scrollView?.$el?.clientHeight || 0;
274
+ this.setPlaceholderStyle();
275
+ if (!this.positions || !this.positions.length) {
276
+ return;
277
+ }
278
+ this.start = this.getStartIndex();
279
+ this.end = this.start + this.visibleCount;
280
+ this.setStartOffset();
281
+ },
282
+ methods: {
283
+ registerGenericComponents() {
284
+ if (!this.generichash || !global.__mpxGenericsMap[this.generichash]) {
285
+ return;
286
+ }
287
+
288
+ let components = null;
289
+ const genericList = {
290
+ "recycle-item": this.genericrecycleItem ,
291
+ "list-header": this.genericlistHeader ,
292
+ "section-header": this.genericsectionHeader
293
+ }
294
+
295
+ for (const key in genericList) {
296
+ const value = genericList[key]
297
+ if (value) {
298
+ components = components || {};
299
+ components[key] = global.__mpxGenericsMap[this.generichash][value]
300
+ }
301
+ }
302
+
303
+ if (components) {
304
+ this.$options.components = Object.assign(
305
+ {},
306
+ this.$options.components,
307
+ components
308
+ );
309
+ }
310
+ },
311
+ formatDimension(value) {
312
+ return typeof value === "number" ? `${value}px` : value || "100%";
313
+ },
314
+ setPlaceholderStyle() {
315
+ const infinitePlaceholder = this.$refs.infinitePlaceholder;
316
+ if (infinitePlaceholder) {
317
+ infinitePlaceholder.style.height = `${this.placeholderHeight}px`;
318
+ }
319
+ },
320
+ initPositions() {
321
+ let bottom = this._listHeaderHeight || 0;
322
+ this.positions = this._listData.map((item, index) => {
323
+ const height = this.getItemHeight(
324
+ item.itemData,
325
+ index,
326
+ item.itemData.isSectionHeader ? "sectionHeaderHeight" : "itemHeight"
327
+ );
328
+ const position = {
329
+ index,
330
+ height: height,
331
+ top: bottom,
332
+ bottom: bottom + height,
333
+ };
334
+ bottom = position.bottom;
335
+ return position;
336
+ });
337
+
338
+ if (this.containerHeight) {
339
+ this.calculateVisibleCounts();
340
+ }
341
+ },
342
+ calculateVisibleCounts() {
343
+ this.visibleCounts = this.positions.map((_, startIndex) => {
344
+ let count = 0;
345
+ let totalHeight = 0;
346
+
347
+ for (let i = startIndex; i < this.positions.length; i++) {
348
+ totalHeight += this.positions[i].height;
349
+ if (totalHeight > this.containerHeight) {
350
+ break;
351
+ }
352
+ count++;
353
+ }
354
+
355
+ // 如果是最后几个项目,确保全部显示
356
+ if (startIndex + count > this.positions.length - 3) {
357
+ count = this.positions.length - startIndex;
358
+ }
359
+
360
+ return count;
361
+ });
362
+ },
363
+
364
+ getStartIndex(scrollTop = 0) {
365
+ // 确保不会返回超出范围的索引
366
+ if (!this.positions.length) {
367
+ return 0;
368
+ }
369
+
370
+ // 如果滚动位置为0,直接返回0
371
+ if (scrollTop <= 0) {
372
+ return 0;
373
+ }
374
+ const index = this.binarySearch(this.positions, scrollTop);
375
+ return Math.max(0, Math.min(index, this._listData.length - 1));
376
+ },
377
+ binarySearch(list, value) {
378
+ if (!list.length) return 0;
379
+
380
+ // 如果 scrollTop 超过了最后一个元素的底部
381
+ if (value >= list[list.length - 1].bottom) {
382
+ return list.length - 1;
383
+ }
384
+
385
+ let start = 0;
386
+ let end = list.length - 1;
387
+
388
+ while (start <= end) {
389
+ const midIndex = Math.floor((start + end) / 2);
390
+ const midValue = list[midIndex];
391
+
392
+ if (value >= midValue.top && value < midValue.bottom) {
393
+ return midIndex;
394
+ }
395
+
396
+ if (value < midValue.top) {
397
+ end = midIndex - 1;
398
+ } else {
399
+ start = midIndex + 1;
400
+ }
401
+ }
402
+
403
+ return Math.min(Math.max(0, start - 1), list.length - 1);
404
+ },
405
+ setStartOffset() {
406
+ const infiniteList = this.$refs.infiniteList;
407
+ if (!this.positions.length || !infiniteList) return;
408
+ if (this.start >= 1) {
409
+ // 确保 startIndex 不会超出范围
410
+ const startIndex = Math.min(
411
+ Math.max(0, this.start - this.aboveCount),
412
+ this.positions.length - 1
413
+ );
414
+
415
+ const offset = this.positions[startIndex].top;
416
+ infiniteList.style.transform = `translateY(${offset}px)`;
417
+ } else {
418
+ infiniteList.style.transform = `translateY(${this.positions[0].top}px)`;
419
+ }
420
+ },
421
+ getItemHeight(item, index, key) {
422
+ const { value, getter } = this[key];
423
+ if (typeof getter === "function") {
424
+ return getter(item, index) || 0;
425
+ } else {
426
+ return value || 0;
427
+ }
428
+ },
429
+ onScroll(e) {
430
+ const { scrollTop } = e.detail;
431
+ const newStart = this.getStartIndex(scrollTop);
432
+
433
+ // 只有当start发生足够变化时才更新,避免滚动触发重渲染
434
+ if (Math.abs(newStart - this.end) >= Math.floor(this.belowCount / 2)) {
435
+ this.start = newStart;
436
+ this.end = this.start + this.visibleCount;
437
+ this.setStartOffset();
438
+ }
439
+
440
+ this.$emit("scroll", e);
441
+ },
442
+ onScrolltolower(e) {
443
+ this.$emit("scrolltolower", e);
444
+ },
445
+ onRefresherrefresh(e) {
446
+ this.$emit("refresherrefresh", e);
447
+ },
448
+ scrollToIndex({ index, animated, viewPosition = 0 }) {
449
+ const isStickyHeader = this._listData[index].itemData?.isSectionHeader;
450
+ let prevHeaderHeight = 0;
451
+ // 如果不是sticky header 查找最近一个吸顶的 sticky header
452
+ if (!isStickyHeader && this.enableSticky) {
453
+ for (let i = index - 1; i >= 0; i--) {
454
+ if (this._listData[i].itemData?.isSectionHeader) {
455
+ prevHeaderHeight = this.positions[i].height;
456
+ break;
457
+ }
458
+ }
459
+ }
460
+
461
+ const itemTop = (this.positions[index]?.top || 0) - prevHeaderHeight;
462
+ const itemHeight = this.positions[index]?.height || 0;
463
+ const containerHeight = this.containerHeight;
464
+
465
+ let targetTop = itemTop;
466
+ if (viewPosition === 1) {
467
+ // 滚动到可视区底部
468
+ targetTop = itemTop - (containerHeight - itemHeight);
469
+ } else if (viewPosition === 0.5) {
470
+ // 滚动到可视区中央
471
+ targetTop = itemTop - (containerHeight - itemHeight) / 2;
472
+ }
473
+
474
+ this.$refs.scrollView?.bs.scrollTo(0, -targetTop, animated ? 200 : 0);
475
+ },
476
+ },
477
+ components: {
478
+ ScrollView,
479
+ StickyHeader,
480
+ },
481
+ };
482
+ </script>
483
+
484
+ <style scoped>
485
+ .mpx-recycle-view {
486
+ position: relative;
487
+ overflow: hidden;
488
+ height: 100%;
489
+ }
490
+
491
+ .content-wrapper {
492
+ position: relative;
493
+ width: 100%;
494
+ }
495
+
496
+ .infinite-list {
497
+ left: 0;
498
+ right: 0;
499
+ top: 0;
500
+ position: absolute;
501
+ will-change: transform;
502
+ -webkit-backface-visibility: hidden;
503
+ backface-visibility: hidden;
504
+ }
505
+ .sticky-section {
506
+ position: absolute !important;
507
+ }
508
+ </style>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <view class="mpx-list-header-default">
3
+ <view class="mpx-default-content">list-header-default</view>
4
+ </view>
5
+ </template>
6
+
7
+ <style lang="stylus" scoped>
8
+ .mpx-list-header-default
9
+ margin 4px 16px
10
+ padding 16px
11
+ background #fff
12
+ border-radius 8px
13
+ box-shadow 0 2px 4px rgba(0,0,0,0.1)
14
+
15
+ .mpx-default-content
16
+ display flex
17
+ flex-direction column
18
+ font-size 14px
19
+ color #666
20
+
21
+ </style>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <view class="mpx-recycle-item-default">
3
+ <view class="mpx-default-content">recycle-item-default</view>
4
+ </view>
5
+ </template>
6
+
7
+ <style lang="stylus" scoped>
8
+ .mpx-recycle-item-default
9
+ margin 4px 16px
10
+ padding 16px
11
+ background #fff
12
+ border-radius 8px
13
+ box-shadow 0 2px 4px rgba(0,0,0,0.1)
14
+
15
+ .mpx-default-content
16
+ display flex
17
+ flex-direction column
18
+ font-size 14px
19
+ color #666
20
+
21
+ </style>