曹睿
2025-04-23 913796ff8463b132be6ebb92c44f0dabb2ff279f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
// [z-paging]虚拟列表模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
 
export default {
    props: {
        // 是否使用虚拟列表,默认为否
        useVirtualList: {
            type: Boolean,
            default: u.gc('useVirtualList', false)
        },
        // 在使用虚拟列表时,是否使用兼容模式,默认为否
        useCompatibilityMode: {
            type: Boolean,
            default: u.gc('useCompatibilityMode', false)
        },
        // 使用兼容模式时传递的附加数据
        extraData: {
            type: Object,
            default: u.gc('extraData', {})
        },
        // 是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true
        useInnerList: {
            type: Boolean,
            default: u.gc('useInnerList', false)
        },
        // 强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况
        forceCloseInnerList: {
            type: Boolean,
            default: u.gc('forceCloseInnerList', false)
        },
        // 内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项
        cellKeyName: {
            type: String,
            default: u.gc('cellKeyName', '')
        },
        // innerList样式
        innerListStyle: {
            type: Object,
            default: u.gc('innerListStyle', {})
        },
        // innerCell样式
        innerCellStyle: {
            type: Object,
            default: u.gc('innerCellStyle', {})
        },
        // 预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
        preloadPage: {
            type: [Number, String],
            default: u.gc('preloadPage', 12),
            validator: (value) => {
                if (value <= 0) u.consoleErr('preload-page必须大于0!');
                return value > 0;
            }
        },
        // 虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。
        cellHeightMode: {
            type: String,
            default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed)
        },
        // 固定的cell高度,cellHeightMode=fixed才有效,若设置了值,则不计算第一个cell高度而使用设置的cell高度
        fixedCellHeight: {
            type: [Number, String],
            default: u.gc('fixedCellHeight', 0)
        },
        // 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2
        virtualListCol: {
            type: [Number, String],
            default: u.gc('virtualListCol', 1)
        },
        // 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题
        virtualScrollFps: {
            type: [Number, String],
            default: u.gc('virtualScrollFps', 80)
        },
        // 虚拟列表cell id的前缀,适用于一个页面有多个虚拟列表的情况,用以区分不同虚拟列表cell的id,注意:请勿传数字或以数字开头的字符串。如设置为list1,则cell的id应为:list1-zp-id-${item.zp_index}
        virtualCellIdPrefix: {
            type: String,
            default: u.gc('virtualCellIdPrefix', '')
        },
        // 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
        // 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可
        virtualInSwiperSlot: {
            type: Boolean,
            default: false
        },
    },
    data() {
        return {
            virtualListKey: u.getInstanceId(),
            virtualPageHeight: 0,
            virtualCellHeight: 0,
            virtualScrollTimeStamp: 0,
            
            virtualList: [],
            virtualPlaceholderTopHeight: 0,
            virtualPlaceholderBottomHeight: 0,
            virtualTopRangeIndex: 0,
            virtualBottomRangeIndex: 0,
            lastVirtualTopRangeIndex: 0,
            lastVirtualBottomRangeIndex: 0,
            virtualItemInsertedCount: 0,
            
            virtualHeightCacheList: [],
            
            getCellHeightRetryCount: {
                fixed: 0,
                dynamic: 0
            },
            pagingOrgTop: -1,
            updateVirtualListFromDataChange: false
        }
    },
    watch: {
        // 监听总数据的改变,刷新虚拟列表布局
        realTotalData() {
            this.updateVirtualListRender();
        },
        // 监听虚拟列表渲染数组的改变并emit
        virtualList(newVal){
            this.$emit('update:virtualList', newVal);
            this.$emit('virtualListChange', newVal);
        },
        // 监听虚拟列表顶部占位高度改变并emit
        virtualPlaceholderTopHeight(newVal) {
            this.$emit('virtualTopHeightChange', newVal);
        }
    },
    computed: {
        virtualCellIndexKey() {
            return c.listCellIndexKey;
        },
        finalUseVirtualList() {
            if (this.useVirtualList && this.usePageScroll){
                u.consoleErr('使用页面滚动时,开启虚拟列表无效!');
            }
            return this.useVirtualList && !this.usePageScroll;
        },
        finalUseInnerList() {
            return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList);
        },
        finalCellKeyName() {
            // #ifdef APP-NVUE
            if (this.finalUseVirtualList && !this.cellKeyName.length){
                u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!');
            }
            // #endif
            return this.cellKeyName;
        },
        finalVirtualPageHeight(){
            return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
        },
        finalFixedCellHeight() {
            return u.convertToPx(this.fixedCellHeight);
        },
        fianlVirtualCellIdPrefix() {
            const prefix = this.virtualCellIdPrefix ? this.virtualCellIdPrefix + '-' : '';
            return prefix + 'zp-id';
        },
        finalPlaceholderTopHeightStyle() {
            // #ifdef VUE2
            return { transform: this.virtualPlaceholderTopHeight > 0 ? `translateY(${this.virtualPlaceholderTopHeight}px)` : 'none' };
            // #endif
            return {};
        },
        virtualRangePageHeight(){
            return this.finalVirtualPageHeight * this.preloadPage;
        },
        virtualScrollDisTimeStamp() {
            return 1000 / this.virtualScrollFps;
        }
    },
    methods: {
        // 在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始
        doInsertVirtualListItem(item, index) {
            if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
            this.realTotalData.splice(index, 0, item);
            // #ifdef VUE3
            this.realTotalData = [...this.realTotalData];
            // #endif
            this.virtualItemInsertedCount ++;
            if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
                item = { item };
            }
            const cellIndexKey = this.virtualCellIndexKey;
            item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`;
            item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
            this.$nextTick(async () => {
                let retryCount = 0;
                while (retryCount <= 10) {
                    await u.wait(c.delayTime);
                    
                    const cellNode = await this._getVirtualCellNodeByIndex(item[cellIndexKey]);
                    // 如果获取当前cell的节点信息失败,则重试(不超过10次)
                    if (!cellNode) {
                        retryCount ++;
                        continue;
                    } 
                    
                    const currentHeight = cellNode ? cellNode[0].height : 0;
                    const lastHeightCache = this.virtualHeightCacheList[index - 1];
                    const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
                    // 在缓存的cell高度数组中,插入此cell高度信息
                    this.virtualHeightCacheList.splice(index, 0, {
                        height: currentHeight,
                        lastTotalHeight,
                        totalHeight: lastTotalHeight + currentHeight
                    });
                    
                    // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell的高度
                    for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
                        const thisNode = this.virtualHeightCacheList[i];
                        thisNode.lastTotalHeight += currentHeight;
                        thisNode.totalHeight += currentHeight;
                    }
                    
                    this._updateVirtualScroll(this.oldScrollTop);
                    break;
                }
            })
        },
        // 在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变后调用);index:需要更新的cell在列表中的位置,从0开始
        didUpdateVirtualListCell(index) {
            if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
            const currentNode = this.virtualHeightCacheList[index];
            this.$nextTick(() => {
                this._getVirtualCellNodeByIndex(index).then(cellNode => {
                    // 更新当前cell的高度
                    const cellNodeHeight = cellNode ? cellNode[0].height : 0;
                    const heightDis = cellNodeHeight - currentNode.height;
                    currentNode.height = cellNodeHeight;
                    currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight;
                    
                    // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell变化的高度
                    for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
                        const thisNode = this.virtualHeightCacheList[i];
                        thisNode.totalHeight += heightDis;
                        thisNode.lastTotalHeight += heightDis;
                    }
                });
            })
        },
        // 在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始
        didDeleteVirtualListCell(index) {
            if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
            const currentNode = this.virtualHeightCacheList[index];
            // 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要减去当前cell的高度
            for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
                const thisNode = this.virtualHeightCacheList[i];
                thisNode.totalHeight -= currentNode.height;
                thisNode.lastTotalHeight -= currentNode.height;
            }
            // 将当前cell的高度信息从高度缓存数组中删除
            this.virtualHeightCacheList.splice(index, 1);
        },
        // 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况
        updateVirtualListRender() {
            // #ifndef APP-NVUE
            if (this.finalUseVirtualList) {
                this.updateVirtualListFromDataChange = true;
                this.$nextTick(() => {
                    this.getCellHeightRetryCount.fixed = 0;
                    if (this.realTotalData.length) {
                        this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight()
                    } else {
                        this._resetDynamicListState(!this.isUserPullDown);
                    }
                    this._updateVirtualScroll(this.oldScrollTop);
                })
            }
            // #endif
        },
        // 初始化虚拟列表
        _virtualListInit() {
            this.$nextTick(() => {
                u.delay(() => {
                    // 获取虚拟列表滚动区域的高度
                    this._getNodeClientRect('.zp-scroll-view').then(node => {
                        if (node) {
                            this.pagingOrgTop = node[0].top;
                            this.virtualPageHeight = node[0].height;
                        }
                    });
                });
            })
        },
        // cellHeightMode为fixed时获取第一个cell高度
        _updateFixedCellHeight() {
            if (!this.finalFixedCellHeight) {
                this.$nextTick(() => {
                    u.delay(() => {
                        this._getVirtualCellNodeByIndex(0).then(cellNode => {
                            if (!cellNode) {
                                if (this.getCellHeightRetryCount.fixed > 10) return;
                                this.getCellHeightRetryCount.fixed ++;
                                // 如果获取第一个cell的节点信息失败,则重试(不超过10次)
                                this._updateFixedCellHeight();
                            } else {
                                this.virtualCellHeight = cellNode[0].height;
                                this._updateVirtualScroll(this.oldScrollTop);
                            }
                        });
                    }, c.delayTime, 'updateFixedCellHeightDelay');
                })
            } else {
                this.virtualCellHeight = this.finalFixedCellHeight;
            }
        },
        // cellHeightMode为dynamic时获取每个cell高度
        _updateDynamicCellHeight(list, dataFrom = 'bottom') {
            const dataFromTop = dataFrom === 'top';
            const heightCacheList = this.virtualHeightCacheList;
            const currentCacheList = dataFromTop ?  [] : heightCacheList;
            let listTotalHeight = 0;
            this.$nextTick(() => {
                u.delay(async () => {
                    for (let i = 0; i < list.length; i++) {
                        const cellNode = await this._getVirtualCellNodeByIndex(list[i][this.virtualCellIndexKey]);
                        const currentHeight = cellNode ? cellNode[0].height : 0;
                        if (!cellNode) {
                            if (this.getCellHeightRetryCount.dynamic <= 10) {
                                heightCacheList.splice(heightCacheList.length - i, i);
                                this.getCellHeightRetryCount.dynamic ++;
                                // 如果获取当前cell的节点信息失败,则重试(不超过10次)
                                this._updateDynamicCellHeight(list, dataFrom);
                            }
                            return;
                        } 
                        const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null;
                        const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
                        // 缓存当前cell的高度信息:height-当前cell高度;lastTotalHeight-前面所有cell的高度总和;totalHeight-包含当前cell的所有高度总和
                        currentCacheList.push({
                            height: currentHeight,
                            lastTotalHeight,
                            totalHeight: lastTotalHeight + currentHeight
                        });
                        if (dataFromTop) {
                            listTotalHeight += currentHeight;
                        }
                    }
                    // 如果数据是从顶部拼接的
                    if (dataFromTop && list.length) {
                        for (let i = 0; i < heightCacheList.length; i++) {
                            // 更新之前所有项的缓存高度,需要加上此次插入的所有cell高度之和(因为是从顶部插入的cell)
                            const heightCacheItem = heightCacheList[i];
                            heightCacheItem.lastTotalHeight += listTotalHeight;
                            heightCacheItem.totalHeight += listTotalHeight;
                        }
                        this.virtualHeightCacheList = currentCacheList.concat(heightCacheList);
                    }
                    this._updateVirtualScroll(this.oldScrollTop);
                }, c.delayTime, 'updateDynamicCellHeightDelay')
            })
        },
        // 设置cellItem的index
        _setCellIndex(list, dataFrom = 'bottom') {
            let currentItemIndex = 0;
            const cellIndexKey = this.virtualCellIndexKey;
            dataFrom === 'bottom' && ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
            if (this.totalData.length && this.queryFrom !== Enum.QueryFrom.Refresh) {
                if (dataFrom === 'bottom') {
                    currentItemIndex = this.realTotalData.length;
                    const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null;
                    if (lastItem && lastItem[cellIndexKey] !== undefined) {
                        currentItemIndex = lastItem[cellIndexKey] + 1;
                    }
                } else if (dataFrom === 'top') {
                    const firstItem = this.realTotalData.length ? this.realTotalData[0] : null;
                    if (firstItem && firstItem[cellIndexKey] !== undefined) {
                        currentItemIndex = firstItem[cellIndexKey] - list.length;
                    }
                }
            } else {
                this._resetDynamicListState();
            }
            for (let i = 0; i < list.length; i++) {
                let item = list[i];
                if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
                    item = { item };
                }
                if (item[c.listCellIndexUniqueKey]) {
                    item = u.deepCopy(item);
                }
                item[cellIndexKey] = currentItemIndex + i;
                item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
                list[i] = item;
            }
            this.getCellHeightRetryCount.dynamic = 0;
            this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom);
        },
        // 更新scroll滚动(虚拟列表滚动时触发)
        _updateVirtualScroll(scrollTop, scrollDiff = 0) {
            const currentTimeStamp = u.getTime();
            scrollTop === 0 && this._resetTopRange();
            if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) {
                return;
            }
            this.virtualScrollTimeStamp = currentTimeStamp;
            
            let scrollIndex = 0;
            const cellHeightMode = this.cellHeightMode;
            if (cellHeightMode === Enum.CellHeightMode.Fixed) {
                // 如果是固定高度的虚拟列表
                // 计算当前滚动到的cell的index = scrollTop / 虚拟列表cell的固定高度
                scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0;
                // 更新顶部和底部占位view的高度(为兼容考虑,顶部采用transformY的方式占位)
                this._updateFixedTopRangeIndex(scrollIndex);
                this._updateFixedBottomRangeIndex(scrollIndex);
            } else if(cellHeightMode === Enum.CellHeightMode.Dynamic) {
                // 如果是不固定高度的虚拟列表
                // 当前滚动的方向
                const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom';
                // 视图区域的高度
                const rangePageHeight = this.virtualRangePageHeight;
                // 顶部视图区域外的高度(顶部不需要渲染而是需要占位部分的高度)
                const topRangePageOffset = scrollTop - rangePageHeight;
                // 底部视图区域外的高度(底部不需要渲染而是需要占位部分的高度)
                const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight;
                
                let virtualBottomRangeIndex = 0;
                let virtualPlaceholderBottomHeight = 0;
                let reachedLimitBottom = false;
                const heightCacheList = this.virtualHeightCacheList;
                const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null;
                
                let startTopRangeIndex = this.virtualTopRangeIndex;
                // 如果是向底部滚动(顶部占位的高度不断增大,顶部的实际渲染cell数量不断减少)
                if (scrollDirection === 'bottom') {
                    // 从顶部视图边缘的cell的位置开始向后查找
                    for (let i = startTopRangeIndex; i < heightCacheList.length; i++){
                        const heightCacheItem = heightCacheList[i];
                        // 如果查找到某个cell对应的totalHeight大于顶部视图区域外的高度,则此cell为顶部视图边缘的cell
                        if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) {
                            // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
                            this.virtualTopRangeIndex = i;
                            this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
                            break;
                        }
                    }
                } else {
                    // 如果是向顶部滚动(顶部占位的高度不断减少,顶部的实际渲染cell数量不断增加)
                    let topRangeMatched = false;
                    // 从顶部视图边缘的cell的位置开始向前查找
                    for (let i = startTopRangeIndex; i >= 0; i--){
                        const heightCacheItem = heightCacheList[i];
                        // 如果查找到某个cell对应的totalHeight小于顶部视图区域外的高度,则此cell为顶部视图边缘的cell
                        if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) {
                            // 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
                            this.virtualTopRangeIndex = i;
                            this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
                            topRangeMatched = true;
                            break;
                        }
                    }
                    // 如果查找不到,则认为顶部占位高度为0了,顶部cell不需要继续复用,重置topRangeIndex和placeholderTopHeight
                    !topRangeMatched && this._resetTopRange();
                }
                // 从顶部视图边缘的cell的位置开始向后查找
                for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){
                    const heightCacheItem = heightCacheList[i];
                    // 如果查找到某个cell对应的totalHeight大于底部视图区域外的高度,则此cell为底部视图边缘的cell
                    if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) {
                        // 记录底部视图边缘cell的index并更新底部占位区域的高度并停止继续查找
                        virtualBottomRangeIndex = i;
                        virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight;
                        reachedLimitBottom = true;
                        break;
                    }
                }
                if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) {
                    this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize;
                    this.virtualPlaceholderBottomHeight = 0;
                } else {
                    this.virtualBottomRangeIndex = virtualBottomRangeIndex;
                    this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight;
                }
                this._updateVirtualList();
            }
        },
        // 更新fixedCell模式下topRangeIndex&placeholderTopHeight
        _updateFixedTopRangeIndex(scrollIndex) {
            let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage;
            virtualTopRangeIndex *= this.virtualListCol;
            virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex);
            this.virtualTopRangeIndex = virtualTopRangeIndex;
            this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight;
        },
        // 更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight
        _updateFixedBottomRangeIndex(scrollIndex) {
            let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1);
            virtualBottomRangeIndex *= this.virtualListCol;
            virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex);
            this.virtualBottomRangeIndex = virtualBottomRangeIndex;
            this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol;
            this._updateVirtualList();
        },
        // 更新virtualList
        _updateVirtualList() {
            const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex);
            if (shouldUpdateList) {
                this.updateVirtualListFromDataChange = false;
                this.lastVirtualTopRangeIndex =  this.virtualTopRangeIndex;
                this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex;
                this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1);
            }
        },
        // 重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态
        _resetDynamicListState(resetVirtualList = false) {
            this.virtualHeightCacheList = [];
            if (resetVirtualList) {
                this.virtualList = [];
            }
            this.virtualTopRangeIndex = 0;
            this.virtualPlaceholderTopHeight = 0;
        },
        // 重置topRangeIndex和placeholderTopHeight
        _resetTopRange() {
            this.virtualTopRangeIndex = 0;
            this.virtualPlaceholderTopHeight = 0;
            this._updateVirtualList();
        },
        // 检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题)
        _checkVirtualListScroll() {
            if (this.finalUseVirtualList) {
                this.$nextTick(() => {
                    this._getNodeClientRect('.zp-paging-touch-view').then(node => {
                        const currentTop = node ? node[0].top : 0;
                        if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) {
                            this._updateVirtualScroll(0);
                        }
                    });
                })
            }
        },
        // 获取对应index的虚拟列表cell节点信息
        _getVirtualCellNodeByIndex(index) {
            let inDom = this.finalUseInnerList;
            // 在vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度的问题
            // 通过uni.createSelectorQuery().in(this.$parent)来解决此问题
            // #ifdef VUE3
            // #ifdef MP-WEIXIN || MP-QQ
            if (this.forceCloseInnerList && this.virtualInSwiperSlot) {
                inDom = this.$parent;
            }
            // #endif
            // #endif
            return this._getNodeClientRect(`#${this.fianlVirtualCellIdPrefix}-${index}`, inDom);
        },
        // 处理使用内置列表时点击了cell事件
        _innerCellClick(item, index) {
            this.$emit('innerCellClick', item, index);
        }
    }
}